@modular-prompt/process 0.5.9 → 0.5.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,845 @@
1
+ # Utilities
2
+
3
+ ## 概要
4
+
5
+ `@modular-prompt/utils`パッケージは、Moduler Promptシステムで使用される共通ユーティリティを提供します。主要な機能として、ドライバレジストリとログシステムが含まれています。
6
+
7
+ ## ログシステム (Logger System)
8
+
9
+ ### 概要
10
+
11
+ `@modular-prompt/utils`のログシステムは、構造化ログ出力とログレベル制御機能を提供します。開発・本番環境での適切なログ出力を支援します。
12
+
13
+ ### なぜログシステムが必要か
14
+
15
+ 1. **環境別の出力制御**: 本番環境では最小限のログ、開発環境では詳細なデバッグ情報を出力
16
+ 2. **構造化データの記録**: 文字列だけでなく、オブジェクトとして情報を記録し、後から解析しやすくする
17
+ 3. **パフォーマンスへの配慮**: ログレベルによって出力を制御し、不要な処理を避ける
18
+ 4. **モジュール識別**: プレフィックスにより、どのモジュールからのログかを明確にする
19
+
20
+ ### 基本コンポーネント
21
+
22
+ 1. **Logger**: メインのログ出力クラス
23
+ 2. **ログレベル**: quiet, error, warn, info, verbose, debug の階層制御
24
+ 3. **プレフィックス**: ログメッセージに自動的に付与される識別子
25
+ 4. **コンテキスト**: ログの発生源を特定するための識別子(例: runner/evaluator/experiment)
26
+ 5. **ログエントリ蓄積**: メモリ内にログを保持し、後から検索・分析できる機能
27
+ 6. **JSONL出力**: ログをJSONL形式でファイルに出力する機能
28
+
29
+ ### ログレベル階層
30
+
31
+ ```typescript
32
+ import { LogLevel } from '@modular-prompt/utils';
33
+
34
+ type LogLevel = 'quiet' | 'error' | 'warn' | 'info' | 'verbose' | 'debug';
35
+ ```
36
+
37
+ **ログレベルの詳細** (数値が小さいほど重要):
38
+
39
+ | レベル | 数値 | 用途 | 含まれる出力 |
40
+ |--------|------|------|--------------|
41
+ | `quiet` | 0 | 出力なし | - |
42
+ | `error` | 1 | エラーのみ | ERROR |
43
+ | `warn` | 2 | 警告以上 | ERROR + WARN |
44
+ | `info` | 3 | 情報以上(デフォルト) | ERROR + WARN + INFO |
45
+ | `verbose` | 4 | 詳細情報以上 | ERROR + WARN + INFO + VERBOSE |
46
+ | `debug` | 5 | すべて(デバッグ含む) | ERROR + WARN + INFO + VERBOSE + DEBUG |
47
+
48
+ **ログレベルの選び方**:
49
+ - **本番環境**: `quiet`または`error` - エラーのみ記録
50
+ - **ステージング環境**: `info` - 正常な動作も含めて記録(デフォルト)
51
+ - **詳細ログが必要な場合**: `verbose` - CLIツールの--verboseオプションに相当
52
+ - **開発環境**: `debug` - すべての情報を記録してデバッグに活用
53
+
54
+ **環境変数による設定**:
55
+ ```bash
56
+ export MODULAR_PROMPT_LOG_LEVEL=debug
57
+ ```
58
+
59
+ ## API
60
+
61
+ ### Loggerの初期化と設定
62
+
63
+ #### 基本的な初期化
64
+
65
+ ```typescript
66
+ import { Logger, logger } from '@modular-prompt/utils';
67
+
68
+ // デフォルトインスタンスを使用(すぐに使える)
69
+ logger.info('Application started');
70
+
71
+ // カスタムインスタンスを作成
72
+ const customLogger = new Logger({
73
+ level: 'debug',
74
+ prefix: 'MyModule',
75
+ context: 'runner',
76
+ accumulate: true,
77
+ maxEntries: 1000,
78
+ logFile: './logs/app.jsonl'
79
+ });
80
+
81
+ // エイリアスを使ったインポートも可能
82
+ import { logger as defaultLogger } from '@modular-prompt/utils';
83
+ defaultLogger.info('Using default logger with alias');
84
+ ```
85
+
86
+ #### 設定オプション
87
+
88
+ ```typescript
89
+ interface LoggerConfig {
90
+ level: LogLevel; // 出力するログレベル(デフォルト: 'info')
91
+ accumulateLevel: LogLevel; // 蓄積するログレベル(デフォルト: 'debug')
92
+ isMcpMode: boolean; // MCPモード(stdout汚染防止、デフォルト: false)
93
+ prefix?: string; // ログプレフィックス
94
+ context?: string; // ログコンテキスト(runner/evaluator等)
95
+ accumulate: boolean; // ログ蓄積モード(デフォルト: false)
96
+ maxEntries: number; // 蓄積する最大エントリ数(デフォルト: 1000)
97
+ logFile?: string; // JSONL出力先パス
98
+ }
99
+ ```
100
+
101
+ #### グローバル設定とインスタンス設定
102
+
103
+ Logger は2層の設定構造を持ちます:
104
+
105
+ 1. **グローバル設定**: すべてのLoggerインスタンスで共有される設定
106
+ 2. **インスタンス設定**: 特定のインスタンスのみに適用される設定(グローバル設定より優先)
107
+
108
+ ```typescript
109
+ // グローバル設定を変更(全インスタンスに影響)
110
+ Logger.configure({ level: 'debug' });
111
+
112
+ // インスタンス設定を変更(このインスタンスのみに影響)
113
+ const logger = new Logger();
114
+ logger.configure({ level: 'verbose' }); // このインスタンスだけverbose
115
+ ```
116
+
117
+ #### コンテキスト付きロガーの作成
118
+
119
+ ```typescript
120
+ const baseLogger = new Logger({ prefix: 'app' });
121
+ const apiLogger = baseLogger.context('api'); // context: 'api'
122
+ const dbLogger = baseLogger.context('db'); // context: 'db'
123
+
124
+ // インスタンス設定は引き継がれ、contextのみが変更される
125
+ ```
126
+
127
+ ### ログ出力メソッド
128
+
129
+ #### logger.error()
130
+ **用途**: システムエラーや例外など、即座に対応が必要な問題を記録
131
+ **出力条件**: `error`レベル以上で出力(ほぼすべてのレベルで出力)
132
+ **出力先**: `console.error` (stderr)
133
+
134
+ ```typescript
135
+ logger.error('Critical error occurred:', error);
136
+ logger.error('Database connection failed', { host: 'localhost', port: 5432 });
137
+ ```
138
+
139
+ #### logger.warn()
140
+ **用途**: 非推奨機能の使用、設定の不備など、将来的に問題となる可能性がある事象を記録
141
+ **出力条件**: `warn`レベル以上で出力
142
+ **出力先**: `console.warn` (stderr)
143
+
144
+ ```typescript
145
+ logger.warn('Deprecated API usage detected');
146
+ logger.warn('Configuration missing:', { key: 'API_KEY' });
147
+ ```
148
+
149
+ #### logger.info()
150
+ **用途**: 正常な処理の開始・終了、重要な状態変化など、システムの動作を追跡するための情報
151
+ **出力条件**: `info`レベル以上で出力(デフォルト設定で出力される)
152
+ **出力先**: `console.log` (stdout)
153
+
154
+ ```typescript
155
+ logger.info('Processing file:', fileName);
156
+ logger.info('Analysis completed successfully');
157
+ ```
158
+
159
+ #### logger.verbose() / logger.log()
160
+ **用途**: より詳細な処理内容、中間状態など、通常運用では不要だが調査時に有用な情報(CLIの--verboseオプション相当)
161
+ **出力条件**: `verbose`レベル以上で出力
162
+ **出力先**: `console.log` (stdout)
163
+ **注意**: `log()`は`verbose()`のエイリアスです
164
+
165
+ ```typescript
166
+ logger.verbose('Detailed processing information');
167
+ logger.verbose('Cache hit for key:', cacheKey);
168
+ logger.log('Module initialization complete with options:', options);
169
+ ```
170
+
171
+ #### logger.debug()
172
+ **用途**: 変数の内容、関数の引数、内部状態など、開発・デバッグ時のみ必要な詳細情報
173
+ **出力条件**: `debug`レベルで出力
174
+ **出力先**: `console.log` (stdout)
175
+
176
+ ```typescript
177
+ logger.debug('Function called with params:', { param1, param2 });
178
+ logger.debug('Internal state:', state);
179
+ ```
180
+
181
+ ### ログエントリの蓄積と取得
182
+
183
+ #### ログエントリの蓄積
184
+
185
+ ```typescript
186
+ const logger = new Logger({
187
+ accumulate: true, // ログ蓄積を有効化
188
+ accumulateLevel: 'debug', // debugレベル以上を蓄積(デフォルト)
189
+ maxEntries: 1000 // 最大1000エントリまで保持
190
+ });
191
+
192
+ logger.info('This will be accumulated');
193
+ logger.debug('This will also be accumulated');
194
+ ```
195
+
196
+ #### ログエントリの取得
197
+
198
+ ```typescript
199
+ // 全ログエントリを取得(現在のcontextのみ)
200
+ const entries = logger.getLogEntries();
201
+
202
+ // フィルタリングして取得
203
+ const errorEntries = logger.getLogEntries({
204
+ level: 'error', // errorレベルのみ
205
+ since: new Date('2024-01-01'), // 指定日時以降
206
+ limit: 100, // 最大100件
207
+ search: 'database', // 'database'を含むもの
208
+ filterByContext: true // 現在のcontextのみ(デフォルト: true)
209
+ });
210
+
211
+ // 複数レベルを取得
212
+ const importantEntries = logger.getLogEntries({
213
+ level: ['error', 'warn'], // errorまたはwarnレベル
214
+ filterByContext: false // 全contextから取得
215
+ });
216
+ ```
217
+
218
+ #### ログエントリの構造
219
+
220
+ ```typescript
221
+ interface LogEntry {
222
+ timestamp: string; // ISO形式のタイムスタンプ
223
+ level: LogLevel; // ログレベル
224
+ prefix?: string; // パッケージ識別子(experiment/MLX等)
225
+ context?: string; // コンテキスト名
226
+ message: string; // メッセージ
227
+ args?: any[]; // 追加引数
228
+ formatted: string; // フォーマット済みメッセージ
229
+ }
230
+ ```
231
+
232
+ #### ログ統計情報の取得
233
+
234
+ ```typescript
235
+ const stats = logger.getLogStats();
236
+ // {
237
+ // totalEntries: 150,
238
+ // entriesByLevel: {
239
+ // quiet: 0,
240
+ // error: 5,
241
+ // warn: 10,
242
+ // info: 80,
243
+ // verbose: 30,
244
+ // debug: 25
245
+ // },
246
+ // oldestEntry: '2024-01-01T10:00:00.000Z',
247
+ // newestEntry: '2024-01-01T12:00:00.000Z'
248
+ // }
249
+ ```
250
+
251
+ #### ログエントリのクリア
252
+
253
+ ```typescript
254
+ logger.clearLogEntries(); // メモリ内のログエントリをクリア
255
+ ```
256
+
257
+ ### 命名規約
258
+
259
+ プロジェクト全体でログを区別しやすくするため、`prefix`と`context`の命名規約を定めます。
260
+
261
+ #### prefix の規約
262
+
263
+ **目的**: パッケージを識別する
264
+ **形式**: パッケージごとに1つの`prefix`を持つ
265
+ **推奨される prefix**:
266
+
267
+ | パッケージ | prefix |
268
+ |-----------|--------|
269
+ | `@modular-prompt/experiment` | `experiment` |
270
+ | `@modular-prompt/simple-chat` | `simple-chat` |
271
+ | `@modular-prompt/process` | `process` |
272
+ | `@modular-prompt/driver` (MLXドライバー) | `MLX` |
273
+ | `@modular-prompt/utils` (DriverRegistry) | `DriverRegistry` |
274
+
275
+ #### context の規約
276
+
277
+ **目的**: モジュール/機能を識別する
278
+ **形式**: フラット形式または階層形式
279
+
280
+ **フラット形式**: `{モジュール名}`
281
+ ```typescript
282
+ // 例:
283
+ context: 'runner'
284
+ context: 'driver'
285
+ context: 'default'
286
+ context: 'evaluator'
287
+ ```
288
+
289
+ **階層形式**: `{ベース}:{区分}:{識別子}:{タイプ}`
290
+ ```typescript
291
+ // 例:
292
+ context: 'agentic:task:1:planning'
293
+ context: 'agentic:task:2:outputMessage'
294
+ context: 'experiment:run:baseline:evaluation'
295
+ ```
296
+
297
+ 階層形式では、コロン(`:`)で区切ることで後からフィルタリングや集計が容易になります。
298
+
299
+ **制約**:
300
+ - 同一パッケージ内で`context`名が重複しないこと
301
+ - 異なるパッケージ間では`prefix`で区別されるため、`context`の重複は許容される
302
+
303
+ #### ベースロガーパターン
304
+
305
+ 各パッケージでは、以下のパターンでLoggerを使用することを推奨します:
306
+
307
+ 1. **パッケージごとのベースロガーを作成** (`logger.ts`等で定義)
308
+ ```typescript
309
+ // packages/experiment/src/logger.ts
310
+ import { Logger } from '@modular-prompt/utils';
311
+
312
+ export const logger = new Logger({
313
+ prefix: 'experiment',
314
+ context: 'main'
315
+ });
316
+ ```
317
+
318
+ 2. **各モジュールで`.context()`を使って派生インスタンスを作成**
319
+ ```typescript
320
+ // packages/experiment/src/run-comparison.ts
321
+ import { logger as baseLogger } from './logger';
322
+
323
+ const logger = baseLogger.context('runner');
324
+ logger.info('Runner started');
325
+ ```
326
+
327
+ 3. **ワークフロー関数では独自のLoggerインスタンスを作成してもよい**
328
+ その場合も`prefix`は必ず設定すること。
329
+ ```typescript
330
+ const logger = new Logger({
331
+ prefix: 'process',
332
+ context: 'default'
333
+ });
334
+ ```
335
+
336
+ #### 現状の課題
337
+
338
+ プロジェクト全体でLoggerの使用状況を調査した結果、以下の課題が確認されています:
339
+
340
+ **未整備の箇所**:
341
+ - `DriverRegistry`で`context`が使われていない
342
+ - trace writerで設定なしのLoggerが使われている
343
+
344
+ **改善済みの項目**:
345
+ - ~~MLXドライバーの`context: 'process'`が紛らわしい問題~~ → trace writerがprefixを考慮するようになったため、`MLX_process.log`として出力され、区別可能になりました
346
+ - ~~`@modular-prompt/process` パッケージで`prefix`が設定されていない問題~~ → 全ワークフローに `prefix: 'process'` を追加済み
347
+
348
+ **trace出力のファイル名ルール**:
349
+ - trace writerは `prefix` と `context` を組み合わせてファイル名を生成します
350
+ - prefix あり + context あり: `{prefix}_{context}.log` (例: `MLX_driver.log`, `MLX_process.log`)
351
+ - prefix あり + context なし: `{prefix}.log` (例: `DriverRegistry.log`)
352
+ - prefix なし + context あり: `{context}.log` (例: `default.log`, `agentic.log`)
353
+ - prefix なし + context なし: `unknown.log`
354
+ - context の `:` は `_` に置換されます(例: `agentic:task:1:planning` → `agentic_task_1_planning.log`)
355
+
356
+ これらの課題は今後段階的に修正する予定です。
357
+
358
+ ### JSONL形式でのファイル出力
359
+
360
+ #### ファイル出力の設定
361
+
362
+ ```typescript
363
+ const logger = new Logger({
364
+ logFile: './logs/application.jsonl' // JSONL出力先
365
+ });
366
+
367
+ logger.info('This will be queued for file output');
368
+ logger.error('This will also be queued');
369
+
370
+ // ファイルに書き出す(非同期)
371
+ await logger.flush();
372
+ ```
373
+
374
+ #### フラッシュオプション
375
+
376
+ ```typescript
377
+ // 全contextのログをファイルに書き出す(デフォルト)
378
+ await logger.flush({ filterByContext: false });
379
+
380
+ // 現在のcontextのみファイルに書き出す
381
+ await logger.flush({ filterByContext: true });
382
+ ```
383
+
384
+ #### JSONL形式の例
385
+
386
+ ```json
387
+ {"timestamp":"2024-01-01T10:00:00.000Z","level":"info","prefix":"experiment","context":"runner","message":"Processing started","formatted":"2024-01-01T10:00:00.000Z INFO [experiment] Processing started"}
388
+ {"timestamp":"2024-01-01T10:00:05.000Z","level":"error","prefix":"experiment","context":"runner","message":"Error occurred","args":[{"code":"ERR_001"}],"formatted":"2024-01-01T10:00:05.000Z ERROR [experiment] Error occurred {\"code\":\"ERR_001\"}"}
389
+ ```
390
+
391
+ ### MCPモードのサポート
392
+
393
+ MCPサーバーとして動作する際、stdout汚染を防ぐためのモードです。
394
+
395
+ ```typescript
396
+ const logger = new Logger({
397
+ isMcpMode: true // MCPモードを有効化
398
+ });
399
+
400
+ // MCPモード時は、errorのみstderrに出力され、他のレベルは抑制される
401
+ logger.error('This will be output to stderr');
402
+ logger.info('This will be suppressed in MCP mode');
403
+
404
+ // ただし、accumulateやlogFileは通常通り動作する
405
+ ```
406
+
407
+ ## DriverRegistryでの使用例
408
+
409
+ DriverRegistryクラスは内部でLoggerを使用して、モデルの選択とドライバー作成プロセスを追跡します:
410
+
411
+ ```typescript
412
+ import { DriverRegistry } from '@modular-prompt/driver';
413
+ import { Logger } from '@modular-prompt/utils';
414
+
415
+ // DriverRegistryは内部でLoggerを使用
416
+ const registry = new DriverRegistry();
417
+
418
+ // モデルを登録
419
+ registry.registerModel({
420
+ model: 'llama-3.3-70b',
421
+ provider: 'mlx',
422
+ capabilities: ['local', 'fast', 'japanese']
423
+ });
424
+
425
+ // モデル選択時のログ出力例(prefix付き)
426
+ // 2024-01-01T10:00:00.000Z INFO [DriverRegistry] Selected model: llama-3.3-70b (mlx)
427
+ // 2024-01-01T10:00:00.000Z INFO [DriverRegistry] Reason: Local execution preferred
428
+ ```
429
+
430
+ ## 使用パターン
431
+
432
+ ### 1. モジュール固有のロガー
433
+
434
+ ```typescript
435
+ class MyModule {
436
+ private logger: Logger;
437
+
438
+ constructor() {
439
+ this.logger = new Logger({
440
+ prefix: 'MyModule',
441
+ level: 'info'
442
+ });
443
+ }
444
+
445
+ async process(data: any) {
446
+ this.logger.info('Processing started');
447
+
448
+ try {
449
+ const result = await this.doWork(data);
450
+ this.logger.info('Processing completed', { itemsProcessed: result.count });
451
+ return result;
452
+
453
+ } catch (error) {
454
+ this.logger.error('Processing failed:', error);
455
+ throw error;
456
+ }
457
+ }
458
+ }
459
+ ```
460
+
461
+ ### 2. コンテキスト別ロガーの使用
462
+
463
+ ```typescript
464
+ class ExperimentRunner {
465
+ private logger: Logger;
466
+
467
+ constructor() {
468
+ this.logger = new Logger({
469
+ prefix: 'Experiment',
470
+ context: 'runner',
471
+ accumulate: true,
472
+ accumulateLevel: 'verbose'
473
+ });
474
+ }
475
+
476
+ async runExperiment(name: string) {
477
+ const expLogger = this.logger.context(`experiment:${name}`);
478
+
479
+ expLogger.info('Starting experiment');
480
+
481
+ // 各コンポーネントに専用コンテキストを付与
482
+ const evalLogger = expLogger.context('evaluator');
483
+ evalLogger.verbose('Evaluator initialized');
484
+
485
+ // 後からcontextでフィルタリング可能
486
+ const expLogs = expLogger.getLogEntries({
487
+ filterByContext: true // このコンテキストのみ
488
+ });
489
+ }
490
+ }
491
+ ```
492
+
493
+ ### 3. デバッグ出力と詳細ログ
494
+
495
+ ```typescript
496
+ function analyzeData(data: any, logger: Logger) {
497
+ // 詳細情報(verbose)
498
+ logger.verbose('Starting data analysis', {
499
+ dataSize: data.length,
500
+ dataType: typeof data
501
+ });
502
+
503
+ // デバッグ情報(debug)
504
+ logger.debug('Input data structure:', {
505
+ type: typeof data,
506
+ keys: Object.keys(data),
507
+ sample: data.slice(0, 3)
508
+ });
509
+
510
+ const result = performAnalysis(data);
511
+
512
+ logger.debug('Analysis result:', {
513
+ itemsAnalyzed: result.items.length,
514
+ processingTime: result.duration
515
+ });
516
+
517
+ logger.verbose('Analysis completed successfully');
518
+ return result;
519
+ }
520
+ ```
521
+
522
+ ### 4. エラーハンドリングとログ
523
+
524
+ ```typescript
525
+ async function robustOperation(input: any) {
526
+ const logger = new Logger({ prefix: 'RobustOp' });
527
+
528
+ logger.info('Operation started');
529
+
530
+ try {
531
+ const result = await riskyOperation(input);
532
+ logger.info('Operation succeeded');
533
+ return result;
534
+
535
+ } catch (error) {
536
+ logger.error('Operation failed:', {
537
+ error: error.message,
538
+ input: typeof input,
539
+ stack: error.stack
540
+ });
541
+
542
+ throw error;
543
+ }
544
+ }
545
+ ```
546
+
547
+ ### 5. ログの蓄積と分析
548
+
549
+ ```typescript
550
+ async function processWithLogging() {
551
+ const logger = new Logger({
552
+ prefix: 'Processor',
553
+ accumulate: true,
554
+ accumulateLevel: 'debug',
555
+ maxEntries: 5000,
556
+ logFile: './logs/process.jsonl'
557
+ });
558
+
559
+ // 処理実行
560
+ await performHeavyWork(logger);
561
+
562
+ // ログ統計を取得
563
+ const stats = logger.getLogStats();
564
+ console.log(`Total logs: ${stats.totalEntries}`);
565
+ console.log(`Errors: ${stats.entriesByLevel.error}`);
566
+
567
+ // エラーのみ抽出
568
+ const errors = logger.getLogEntries({ level: 'error' });
569
+
570
+ // ファイルに書き出し
571
+ await logger.flush();
572
+
573
+ return stats;
574
+ }
575
+ ```
576
+
577
+ ### 6. グローバル設定とインスタンス設定の組み合わせ
578
+
579
+ ```typescript
580
+ // アプリケーション起動時にグローバル設定
581
+ Logger.configure({
582
+ level: 'info',
583
+ isMcpMode: process.env.MCP_MODE === 'true'
584
+ });
585
+
586
+ // 各モジュールは独自の設定を追加
587
+ const driverLogger = new Logger({ prefix: 'Driver' });
588
+ const apiLogger = new Logger({ prefix: 'API', level: 'verbose' }); // このインスタンスのみverbose
589
+
590
+ // テスト時だけデバッグモードに
591
+ if (process.env.NODE_ENV === 'test') {
592
+ Logger.configure({ level: 'debug' });
593
+ }
594
+ ```
595
+
596
+ ### 7. QueryLogger(ドライバー実装用)
597
+
598
+ `QueryLogger` は Logger の accumulate 機能を活用し、クエリ実行中のログをスコープして `QueryResult` に付与するヘルパーです。
599
+
600
+ ```typescript
601
+ import { QueryLogger } from '@modular-prompt/driver';
602
+
603
+ class MyDriver implements AIDriver {
604
+ private queryLogger = new QueryLogger('MyDriver');
605
+
606
+ async query(prompt, options) {
607
+ this.queryLogger.mark(); // クエリ開始を記録
608
+ try {
609
+ const result = await callApi(prompt);
610
+ return { ...result, ...this.queryLogger.collect() };
611
+ } catch (error) {
612
+ this.queryLogger.log.error('Query error:', error.message);
613
+ return { content: '', finishReason: 'error', ...this.queryLogger.collect() };
614
+ }
615
+ }
616
+ }
617
+ ```
618
+
619
+ - `mark()`: ログ収集の開始時刻をリセット
620
+ - `log`: 内部の Logger インスタンスへのアクセス(`error()`, `warn()`, `info()` 等)
621
+ - `collect()`: `mark()` 以降のログエントリを `{ logEntries?, errors? }` として返却
622
+
623
+ 詳細は [Driver APIリファレンス](./DRIVER_API.md) のドライバー実装者向けログ規約を参照してください。
624
+
625
+ ## 設定とベストプラクティス
626
+
627
+ ### 1. 環境別ログレベル設定
628
+
629
+ ```typescript
630
+ // 本番環境
631
+ const productionLogger = new Logger({
632
+ level: 'error', // エラーのみ
633
+ accumulate: false
634
+ });
635
+
636
+ // ステージング環境
637
+ const stagingLogger = new Logger({
638
+ level: 'info', // 情報レベル以上
639
+ accumulate: true,
640
+ accumulateLevel: 'warn', // 警告以上を蓄積
641
+ logFile: './logs/staging.jsonl'
642
+ });
643
+
644
+ // 開発環境
645
+ const developmentLogger = new Logger({
646
+ level: 'debug', // すべてのログ
647
+ accumulate: true,
648
+ accumulateLevel: 'debug'
649
+ });
650
+
651
+ // テスト環境
652
+ const testLogger = new Logger({
653
+ level: 'quiet', // 出力なし
654
+ accumulate: true, // ただし蓄積はする
655
+ accumulateLevel: 'info'
656
+ });
657
+ ```
658
+
659
+ ### 2. 構造化ログの活用
660
+
661
+ ```typescript
662
+ // ❌ 避けるべき:文字列での情報埋め込み
663
+ logger.info(`User ${userId} performed action ${action} at ${timestamp}`);
664
+
665
+ // ✅ 推奨:構造化されたデータ
666
+ logger.info('User action performed', {
667
+ userId,
668
+ action,
669
+ timestamp,
670
+ metadata: {
671
+ sessionId,
672
+ userAgent
673
+ }
674
+ });
675
+ ```
676
+
677
+ JSONL出力時、構造化データはそのまま`args`フィールドに保存されます:
678
+
679
+ ```json
680
+ {
681
+ "timestamp": "2024-01-01T10:00:00.000Z",
682
+ "level": "info",
683
+ "message": "User action performed",
684
+ "args": [{"userId": "123", "action": "login", "timestamp": "2024-01-01T10:00:00Z"}]
685
+ }
686
+ ```
687
+
688
+ ### 3. パフォーマンス考慮
689
+
690
+ ```typescript
691
+ // ⭕ 通常のケース - メソッドを直接呼び出す
692
+ logger.debug('User action:', { userId, action, timestamp });
693
+ logger.verbose('Cache status:', { hits: cacheHits, misses: cacheMisses });
694
+
695
+ // ログレベルによって自動的に出力が制御される
696
+ // レベル外の場合、内部で早期リターンされるため、パフォーマンスへの影響は最小限
697
+ ```
698
+
699
+ **推奨事項**:
700
+ - ログメソッドは直接呼び出す(内部で出力判定が行われる)
701
+ - 巨大データは要約やサンプルのみログに出力
702
+ - 出力レベルと蓄積レベルを分けて設定可能(例: 出力は`info`、蓄積は`debug`)
703
+
704
+ ### 4. 大量データのログ
705
+
706
+ ```typescript
707
+ // ❌ 大量データの直接ログ
708
+ logger.debug('All data:', massiveArray);
709
+
710
+ // ✅ サマリー情報のみログ
711
+ logger.debug('Data summary:', {
712
+ count: massiveArray.length,
713
+ sample: massiveArray.slice(0, 3),
714
+ types: [...new Set(massiveArray.map(item => typeof item))]
715
+ });
716
+ ```
717
+
718
+ ### 5. ログの蓄積容量管理
719
+
720
+ ```typescript
721
+ const logger = new Logger({
722
+ accumulate: true,
723
+ maxEntries: 1000, // 最大1000エントリ
724
+ accumulateLevel: 'info' // infoレベル以上を蓄積
725
+ });
726
+
727
+ // 古いエントリは自動的に削除される(FIFO)
728
+ // 必要に応じて手動でクリア
729
+ logger.clearLogEntries();
730
+ ```
731
+
732
+ ### 6. MCPモードとファイル出力の組み合わせ
733
+
734
+ MCPサーバーとして動作する際、stdoutを汚染せずにログを記録できます:
735
+
736
+ ```typescript
737
+ const logger = new Logger({
738
+ isMcpMode: true, // stdout汚染を防ぐ
739
+ accumulate: true, // メモリに蓄積
740
+ logFile: './logs/mcp-server.jsonl' // ファイルにも出力
741
+ });
742
+
743
+ // errorのみstderrに出力され、他は抑制される
744
+ logger.error('Critical error'); // stderr出力 + 蓄積 + ファイル
745
+ logger.info('Processing...'); // 蓄積 + ファイルのみ(stdout出力なし)
746
+
747
+ // 定期的にファイルに書き出し
748
+ setInterval(() => logger.flush(), 5000);
749
+ ```
750
+
751
+ ## 実装ファイル
752
+
753
+ - **Logger実装**: `packages/utils/src/logger/logger.ts`
754
+ - **Loggerエクスポート**: `packages/utils/src/logger/index.ts`
755
+ - **Loggerテスト**: `packages/utils/src/logger/logger.test.ts`
756
+ - **利用例**:
757
+ - `packages/driver/src/driver-registry/registry.ts` - DriverRegistry
758
+ - `packages/driver/src/mlx-ml/mlx-driver.ts` - MLXドライバー
759
+ - `packages/experiment/src/run-comparison.ts` - 実験フレームワーク
760
+ - `packages/simple-chat/src/cli.ts` - チャットCLI
761
+
762
+ ## Usage集計ユーティリティ
763
+
764
+ `@modular-prompt/process` パッケージは、複数のクエリやタスクのusage情報を集計するためのユーティリティ関数を提供します。
765
+
766
+ ### aggregateUsage()
767
+
768
+ 複数の usage オブジェクトを合算します。リトライや複数タスクのusageを集計する際に使用します。
769
+
770
+ ```typescript
771
+ import { aggregateUsage } from '@modular-prompt/process/workflows/usage-utils';
772
+
773
+ const usage1 = { promptTokens: 100, completionTokens: 50, totalTokens: 150 };
774
+ const usage2 = { promptTokens: 200, completionTokens: 80, totalTokens: 280 };
775
+
776
+ const total = aggregateUsage([usage1, usage2]);
777
+ // { promptTokens: 300, completionTokens: 130, totalTokens: 430 }
778
+
779
+ // undefined は無視される
780
+ const partialTotal = aggregateUsage([usage1, undefined, usage2]);
781
+ // { promptTokens: 300, completionTokens: 130, totalTokens: 430 }
782
+
783
+ // すべて undefined の場合は undefined を返す
784
+ const noUsage = aggregateUsage([undefined, undefined]);
785
+ // undefined
786
+ ```
787
+
788
+ `aggregateUsage` は `promptTokens` / `completionTokens` / `totalTokens` のみ合算します。`cacheReadTokens` / `cacheWriteTokens` は現時点では合算しません。
789
+
790
+ ### buildQueryUsage()(@modular-prompt/driver)
791
+
792
+ 単一クエリの usage オブジェクトを組み立てるヘルパー。カスタムドライバー実装で利用します。
793
+
794
+ ```typescript
795
+ import { buildQueryUsage } from '@modular-prompt/driver';
796
+
797
+ const usage = buildQueryUsage({
798
+ promptTokens: 100,
799
+ completionTokens: 20,
800
+ cacheReadTokens: 80,
801
+ cacheWriteTokens: 0,
802
+ });
803
+ // { promptTokens: 100, completionTokens: 20, totalTokens: 120, cacheReadTokens: 80 }
804
+ ```
805
+
806
+ 詳細は [Driver APIリファレンス](./DRIVER_API.md#共通ユーティリティquery-utils) を参照。
807
+
808
+ ### aggregateLogEntries()
809
+
810
+ 複数の LogEntry 配列をフラット化します。全タスク・全クエリのログを1つの配列にまとめる際に使用します。
811
+
812
+ ```typescript
813
+ import { aggregateLogEntries } from '@modular-prompt/process/workflows/usage-utils';
814
+
815
+ const logs1 = [
816
+ { level: 'info', message: 'Task 1 started', timestamp: '...' },
817
+ { level: 'info', message: 'Task 1 completed', timestamp: '...' }
818
+ ];
819
+ const logs2 = [
820
+ { level: 'info', message: 'Task 2 started', timestamp: '...' }
821
+ ];
822
+
823
+ const allLogs = aggregateLogEntries([logs1, logs2]);
824
+ // [
825
+ // { level: 'info', message: 'Task 1 started', ... },
826
+ // { level: 'info', message: 'Task 1 completed', ... },
827
+ // { level: 'info', message: 'Task 2 started', ... }
828
+ // ]
829
+
830
+ // undefined は無視される
831
+ const partialLogs = aggregateLogEntries([logs1, undefined]);
832
+ // logs1 のコピー
833
+
834
+ // すべて undefined の場合は undefined を返す
835
+ const noLogs = aggregateLogEntries([undefined, undefined]);
836
+ // undefined
837
+ ```
838
+
839
+ **実装ファイル**: `packages/process/src/workflows/usage-utils.ts`
840
+
841
+ ## 関連ドキュメント
842
+
843
+ - [Architecture](./ARCHITECTURE.md) - システム全体のアーキテクチャ
844
+ - [Driver API](./DRIVER_API.md) - ドライバAPIの詳細
845
+ - [Process Module Guide](./PROCESS_MODULE_GUIDE.md) - WorkflowResultの詳細
@@ -0,0 +1,207 @@
1
+ # ワークフローログ規約
2
+
3
+ ワークフロー実装者向けの Logger 使用規約。
4
+ Logger の仕様詳細については [UTILITIES.md](./UTILITIES.md) を参照してください。
5
+
6
+ ## 概要
7
+
8
+ `@modular-prompt/process` パッケージのワークフロー関数は、`@modular-prompt/utils` の Logger を使用して実行ログを記録します。この規約は、ワークフロー実装者が一貫した方法でログを出力するためのガイドラインです。
9
+
10
+ ## context 命名規則
11
+
12
+ ワークフロー関数は Logger インスタンスの `context` 名で自身を識別します。
13
+
14
+ ### 基本形
15
+
16
+ ```
17
+ {workflow名}
18
+ ```
19
+
20
+ **例:**
21
+ - `default`
22
+ - `stream`
23
+ - `agentic`
24
+
25
+ ### 階層形
26
+
27
+ 複雑なワークフロー(複数の処理単位を持つもの)では、階層的な context を使用します。
28
+
29
+ ```
30
+ {workflow名}:{区分}:{識別子}:{タイプ}
31
+ ```
32
+
33
+ **例:**
34
+ - `agentic:task:1:planning`
35
+ - `agentic:task:2:think`
36
+ - `agentic:task:3:outputMessage`
37
+
38
+ **規則:**
39
+ - 階層の区切りは `:` を使用
40
+ - 階層の深さはワークフローが自由に決定可能
41
+ - 各階層の意味はワークフローが定義
42
+
43
+ ## メッセージ prefix 規則
44
+
45
+ ログメッセージの先頭に `[tag]` を付与して、エントリの種別を示します。
46
+
47
+ | prefix | 意味 | 内容 |
48
+ |--------|------|------|
49
+ | `[start]` | 処理単位の開始 | 説明テキスト |
50
+ | `[end]` | 処理単位の完了 | なし、または所要時間等 |
51
+ | `[prompt]` | ドライバーに送るプロンプト | CompiledPrompt の JSON |
52
+ | `[output]` | ドライバーからの応答 | 応答テキスト |
53
+ | `[tool:call]` | ツール呼び出し要求 | ツール名と引数 |
54
+ | `[tool:result]` | ツール呼び出し結果 | ツール名と結果 |
55
+
56
+ **注意:**
57
+ - prefix のないメッセージは自由記述とします
58
+ - prefix は必ず角括弧 `[]` で囲みます
59
+ - prefix と本文の間にスペースを入れます
60
+
61
+ ## ログレベルの使い分け
62
+
63
+ | レベル | 用途 | 使用例 |
64
+ |--------|------|--------|
65
+ | `info` | 処理の進行状況 | `[start]`, `[end]` |
66
+ | `verbose` | 入出力の内容 | `[prompt]`, `[output]` |
67
+ | `debug` | 詳細情報 | `[tool:call]`, `[tool:result]`、内部状態 |
68
+
69
+ ## ワークフロー実装の責務
70
+
71
+ ワークフロー実装者は以下を実装する必要があります:
72
+
73
+ ### 1. Logger インスタンスの作成
74
+
75
+ モジュールスコープで `new Logger({ context: '...' })` を作成します。
76
+
77
+ ```typescript
78
+ import { Logger } from '@modular-prompt/utils';
79
+
80
+ const logger = new Logger({ context: 'myWorkflow' });
81
+ ```
82
+
83
+ ### 2. 処理単位の開始・完了ログ
84
+
85
+ 処理単位の開始時に `[start]` を、完了時に `[end]` を出力します。
86
+
87
+ ```typescript
88
+ logger.info('[start] Processing workflow');
89
+ // ... 処理 ...
90
+ logger.info('[end] Workflow completed');
91
+ ```
92
+
93
+ ### 3. ドライバー呼び出しのログ
94
+
95
+ ドライバー呼び出しの前後で `[prompt]` と `[output]` を出力します。
96
+
97
+ ```typescript
98
+ const compiledPrompt = compile(module, context);
99
+ logger.verbose('[prompt]', JSON.stringify(compiledPrompt, null, 2));
100
+
101
+ const result = await driver.query(compiledPrompt);
102
+ logger.verbose('[output]', result.output);
103
+ ```
104
+
105
+ ### 4. ツール呼び出しのログ
106
+
107
+ ツール呼び出しがある場合は `[tool:call]` と `[tool:result]` を出力します。
108
+
109
+ ```typescript
110
+ logger.debug('[tool:call]', toolName, JSON.stringify(args));
111
+ const toolResult = await executeTool(toolName, args);
112
+ logger.debug('[tool:result]', toolName, JSON.stringify(toolResult));
113
+ ```
114
+
115
+ ### 5. 階層化された Logger の作成(必要な場合)
116
+
117
+ 階層化が必要な場合(agenticProcess のタスクなど)は、context を切り替えた Logger インスタンスを作成します。
118
+
119
+ ```typescript
120
+ const baseLogger = new Logger({ context: 'agentic' });
121
+
122
+ // タスク 1 用の Logger
123
+ const task1Logger = baseLogger.context(`task:1:planning`);
124
+ task1Logger.info('[start] Planning task');
125
+
126
+ // タスク 2 用の Logger
127
+ const task2Logger = baseLogger.context(`task:2:think`);
128
+ task2Logger.info('[start] Think task');
129
+ ```
130
+
131
+ ## trace 側の責務
132
+
133
+ trace 機能(Logger の蓄積機能を利用する側)は以下の責務を持ちます:
134
+
135
+ - Logger の蓄積機能でエントリを収集する
136
+ - context でグループ化してファイルに書き出す
137
+ - ファイル形式やディレクトリ構造は trace 側が決定する
138
+
139
+ **重要**: ワークフロー側は trace の存在を知りません。ワークフロー実装者は Logger にログを出力するだけで、trace 機能の実装や設定については関知しません。
140
+
141
+ ## 実装例
142
+
143
+ ### シンプルなワークフロー
144
+
145
+ ```typescript
146
+ import { Logger } from '@modular-prompt/utils';
147
+ import { compile } from '@modular-prompt/core';
148
+
149
+ const logger = new Logger({ context: 'simple' });
150
+
151
+ export async function simpleProcess(
152
+ driver: AIDriver,
153
+ module: PromptModule,
154
+ context: Context
155
+ ): Promise<QueryResult> {
156
+ logger.info('[start] Simple workflow');
157
+
158
+ const compiledPrompt = compile(module, context);
159
+ logger.verbose('[prompt]', JSON.stringify(compiledPrompt, null, 2));
160
+
161
+ const result = await driver.query(compiledPrompt);
162
+ logger.verbose('[output]', result.output);
163
+
164
+ logger.info('[end] Simple workflow completed');
165
+ return result;
166
+ }
167
+ ```
168
+
169
+ ### 階層化されたワークフロー
170
+
171
+ ```typescript
172
+ import { Logger } from '@modular-prompt/utils';
173
+
174
+ const logger = new Logger({ context: 'agentic' });
175
+
176
+ export async function agenticProcess(
177
+ driver: AIDriver,
178
+ module: PromptModule,
179
+ context: Context
180
+ ): Promise<QueryResult> {
181
+ logger.info('[start] Agentic workflow');
182
+
183
+ const tasks = [
184
+ { id: 1, type: 'planning' },
185
+ { id: 2, type: 'think' },
186
+ ];
187
+
188
+ for (const task of tasks) {
189
+ const taskLogger = logger.context(`task:${task.id}:${task.type}`);
190
+ taskLogger.info('[start]', `Task ${task.id}: ${task.type}`);
191
+
192
+ // タスク実行
193
+ const result = await executeTask(driver, task);
194
+ taskLogger.verbose('[output]', result.output);
195
+
196
+ taskLogger.info('[end]', `Task ${task.id} completed`);
197
+ }
198
+
199
+ logger.info('[end] Agentic workflow completed');
200
+ return finalResult;
201
+ }
202
+ ```
203
+
204
+ ## 関連ドキュメント
205
+
206
+ - [UTILITIES.md](./UTILITIES.md) - Logger の詳細仕様
207
+ - [agentic-workflow/DESIGN.md](../src/workflows/agentic-workflow/DESIGN.md) - Agentic Workflow v2 の設計文書(階層化されたログの実例)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modular-prompt/process",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "Process module for modular prompt framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,11 +21,12 @@
21
21
  },
22
22
  "files": [
23
23
  "dist/**/*",
24
- "README.md"
24
+ "README.md",
25
+ "docs"
25
26
  ],
26
27
  "dependencies": {
27
28
  "@modular-prompt/core": "0.3.0",
28
- "@modular-prompt/driver": "0.16.0",
29
+ "@modular-prompt/driver": "0.17.0",
29
30
  "@modular-prompt/utils": "0.3.5"
30
31
  },
31
32
  "devDependencies": {
@@ -68,6 +69,7 @@
68
69
  "test:run": "vitest run",
69
70
  "clean": "rm -rf dist tsconfig.tsbuildinfo",
70
71
  "lint": "eslint src",
71
- "typecheck": "tsc --noEmit"
72
+ "typecheck": "tsc --noEmit",
73
+ "copy-docs": "node ../../scripts/copy-package-docs.mjs process"
72
74
  }
73
75
  }