@rex0220/llm-task-router 0.1.0
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.
- package/.env.example +6 -0
- package/LICENSE +21 -0
- package/README.ja.md +147 -0
- package/README.md +147 -0
- package/config/criteria/default.md +43 -0
- package/config/criteria/note.md +32 -0
- package/config/models.yaml +111 -0
- package/config/profiles/blog.yaml +9 -0
- package/config/profiles/note.yaml +11 -0
- package/config/profiles/qiita.yaml +13 -0
- package/config/profiles/zenn.yaml +9 -0
- package/dist/llm-task-router.js +1465 -0
- package/docs/api-smoke-test.md +291 -0
- package/docs/thin-model-router-design.md +1069 -0
- package/docs/thin-model-router-implementation-plan.md +479 -0
- package/package.json +74 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
# 薄い ModelRouter 実装計画書
|
|
2
|
+
|
|
3
|
+
作成日: 2026-06-16
|
|
4
|
+
参照設計書: [thin-model-router-design.md](./thin-model-router-design.md)
|
|
5
|
+
|
|
6
|
+
## 1. 目的
|
|
7
|
+
|
|
8
|
+
Qiita記事作成などの個人・小規模用途に限定した、TypeScript製の薄いModelRouter CLIを実装する。
|
|
9
|
+
|
|
10
|
+
この計画では、設計書のMVPスコープを優先し、以下を満たす最小構成を段階的に作る。
|
|
11
|
+
|
|
12
|
+
- タスク別にモデルを選択する
|
|
13
|
+
- OpenAI / Anthropic をProviderとして呼び分ける
|
|
14
|
+
- rate limit / timeout / overloaded / 一時的な5xx系エラーのみフォールバックする
|
|
15
|
+
- 中間成果物を `runs/<runId>/` に保存し、途中再開できる
|
|
16
|
+
- ZodでJSON成果物を検証する
|
|
17
|
+
- ログにAPIキーや全文プロンプトを残さない
|
|
18
|
+
- Web UI、外部公開API、任意コード実行、任意URL取得は実装しない
|
|
19
|
+
|
|
20
|
+
## 2. 実装方針
|
|
21
|
+
|
|
22
|
+
### 2.1 基本方針
|
|
23
|
+
|
|
24
|
+
- CLI専用ツールとして実装する
|
|
25
|
+
- `ModelRouter` はProvider固有APIを知らない構造にする
|
|
26
|
+
- 設定は `config/models.yaml` から読み込む
|
|
27
|
+
- APIキーは `.env` から読み込み、`.env.example` のみコミット対象にする
|
|
28
|
+
- 成果物とメタ情報はファイル保存に限定する
|
|
29
|
+
- DB、HTTPサーバー、Web管理画面は導入しない
|
|
30
|
+
|
|
31
|
+
### 2.2 過剰実装を避ける項目
|
|
32
|
+
|
|
33
|
+
- 動的な設定変更APIは作らない
|
|
34
|
+
- プラグイン機構は作らない
|
|
35
|
+
- 任意シェル実行やコード実行機能は作らない
|
|
36
|
+
- 外部URL取得機能は作らない
|
|
37
|
+
- プロンプト全文をログDBに蓄積する仕組みは作らない
|
|
38
|
+
- 複雑なコスト最適化やモデル自動評価はMVPに入れない
|
|
39
|
+
|
|
40
|
+
## 3. 成果物
|
|
41
|
+
|
|
42
|
+
MVP完了時点で、次のファイルと機能を用意する。
|
|
43
|
+
|
|
44
|
+
```text
|
|
45
|
+
package.json
|
|
46
|
+
tsconfig.json
|
|
47
|
+
.gitignore
|
|
48
|
+
.env.example
|
|
49
|
+
config/
|
|
50
|
+
models.yaml
|
|
51
|
+
src/
|
|
52
|
+
index.ts
|
|
53
|
+
router/
|
|
54
|
+
ModelRouter.ts
|
|
55
|
+
config.ts
|
|
56
|
+
errors.ts
|
|
57
|
+
types.ts
|
|
58
|
+
providers/
|
|
59
|
+
ModelProvider.ts
|
|
60
|
+
OpenAIProvider.ts
|
|
61
|
+
AnthropicProvider.ts
|
|
62
|
+
schemas/
|
|
63
|
+
ArticleBriefSchema.ts
|
|
64
|
+
ArticleOutlineSchema.ts
|
|
65
|
+
ReviewResultSchema.ts
|
|
66
|
+
index.ts
|
|
67
|
+
workflows/
|
|
68
|
+
createQiitaArticle.ts
|
|
69
|
+
resumeQiitaArticle.ts
|
|
70
|
+
storage/
|
|
71
|
+
RunStore.ts
|
|
72
|
+
logger/
|
|
73
|
+
RunLogger.ts
|
|
74
|
+
utils/
|
|
75
|
+
cost.ts
|
|
76
|
+
hash.ts
|
|
77
|
+
json.ts
|
|
78
|
+
timeout.ts
|
|
79
|
+
tests/
|
|
80
|
+
router/
|
|
81
|
+
ModelRouter.test.ts
|
|
82
|
+
storage/
|
|
83
|
+
RunStore.test.ts
|
|
84
|
+
workflows/
|
|
85
|
+
createQiitaArticle.test.ts
|
|
86
|
+
runs/
|
|
87
|
+
.gitkeep
|
|
88
|
+
README.md
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 4. 依存関係
|
|
92
|
+
|
|
93
|
+
### 4.1 実行・ビルド依存
|
|
94
|
+
|
|
95
|
+
- `typescript`
|
|
96
|
+
- `tsx`
|
|
97
|
+
- `commander`
|
|
98
|
+
- `yaml`
|
|
99
|
+
- `zod`
|
|
100
|
+
- `dotenv`
|
|
101
|
+
- `openai`
|
|
102
|
+
- `@anthropic-ai/sdk`
|
|
103
|
+
|
|
104
|
+
### 4.2 開発・テスト依存
|
|
105
|
+
|
|
106
|
+
- `vitest`
|
|
107
|
+
- `@types/node`
|
|
108
|
+
|
|
109
|
+
### 4.3 npm script
|
|
110
|
+
|
|
111
|
+
```json
|
|
112
|
+
{
|
|
113
|
+
"scripts": {
|
|
114
|
+
"build": "tsc -p tsconfig.json",
|
|
115
|
+
"test": "vitest run",
|
|
116
|
+
"article:create": "tsx src/index.ts article:create",
|
|
117
|
+
"article:resume": "tsx src/index.ts article:resume",
|
|
118
|
+
"article:review": "tsx src/index.ts article:review"
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## 5. フェーズ別実装計画
|
|
124
|
+
|
|
125
|
+
### Phase 0: プロジェクト初期化
|
|
126
|
+
|
|
127
|
+
目的: TypeScript CLIとして動く最小土台を作る。
|
|
128
|
+
|
|
129
|
+
作業:
|
|
130
|
+
|
|
131
|
+
- `package.json` を作成する
|
|
132
|
+
- `tsconfig.json` を作成する
|
|
133
|
+
- `.gitignore` を作成し、`.env` と `runs/*` を除外する
|
|
134
|
+
- `.env.example` を作成する
|
|
135
|
+
- `src/index.ts` にCLIエントリポイントを作成する
|
|
136
|
+
- `runs/.gitkeep` を追加する
|
|
137
|
+
|
|
138
|
+
受け入れ条件:
|
|
139
|
+
|
|
140
|
+
- `npm run build` が成功する
|
|
141
|
+
- `npm test` が空または最小テストで成功する
|
|
142
|
+
- `.env` がGit管理対象にならない
|
|
143
|
+
|
|
144
|
+
### Phase 1: 型定義と設定読み込み
|
|
145
|
+
|
|
146
|
+
目的: ルーティングに必要な共通型と `models.yaml` 読み込みを実装する。
|
|
147
|
+
|
|
148
|
+
作業:
|
|
149
|
+
|
|
150
|
+
- `ModelTask`、`ModelRequest`、`ModelResponse` を定義する
|
|
151
|
+
- `ProviderRequest`、`ProviderResponse`、`ModelProvider` を定義する
|
|
152
|
+
- `RouterErrorKind`、`RouterError`、`normalizeProviderError` の型を定義する
|
|
153
|
+
- `RouterConfig`、`TaskConfig`、`ModelCandidate` を定義する
|
|
154
|
+
- `config/models.yaml` のMVP用初期設定を作成する
|
|
155
|
+
- `models.yaml` にproviderごとの `api_key_env` と、任意の `prices` を持てる余地を作る
|
|
156
|
+
- YAML読み込みとZod検証を `src/router/config.ts` に実装する
|
|
157
|
+
- 未定義タスク、primary未指定、不正providerを検出する
|
|
158
|
+
- `priority` はMVPでは実装しないため、初期型には入れない
|
|
159
|
+
|
|
160
|
+
受け入れ条件:
|
|
161
|
+
|
|
162
|
+
- 正常な `models.yaml` を読み込める
|
|
163
|
+
- 不正な設定でわかりやすいエラーを返す
|
|
164
|
+
- 実際のモデル名は設定値として扱い、コードに固定しない
|
|
165
|
+
- APIキーの参照先env名を設定から解決できる
|
|
166
|
+
- コスト概算は単価がある場合だけベストエフォートで計算できる
|
|
167
|
+
|
|
168
|
+
### Phase 2: Provider実装
|
|
169
|
+
|
|
170
|
+
目的: OpenAI / Anthropic のAPI差分をProvider層に閉じ込める。
|
|
171
|
+
|
|
172
|
+
作業:
|
|
173
|
+
|
|
174
|
+
- `OpenAIProvider` を実装する
|
|
175
|
+
- `AnthropicProvider` を実装する
|
|
176
|
+
- `.env` からAPIキーを読み込む
|
|
177
|
+
- Providerごとの `api_key_env` を優先し、未指定時だけ標準env名を使う
|
|
178
|
+
- Providerごとのレスポンスを共通 `ProviderResponse` に変換する
|
|
179
|
+
- token使用量が取得できる場合は `usage` に詰める
|
|
180
|
+
- APIキー未設定時は認証系エラーとして扱う
|
|
181
|
+
- SDK固有例外を `RouterErrorKind` に正規化する
|
|
182
|
+
- ProviderごとにSDKのリトライ回数とタイムアウト方針を明示する
|
|
183
|
+
- `AbortSignal` またはSDKのtimeout指定で中断できるようにする
|
|
184
|
+
- AnthropicProviderではモデル仕様に応じて未対応の `temperature` などを送らない
|
|
185
|
+
- AnthropicProviderでは `maxTokens` 未指定時に安全なデフォルトを補う
|
|
186
|
+
|
|
187
|
+
受け入れ条件:
|
|
188
|
+
|
|
189
|
+
- Router側がSDK固有型をimportしない
|
|
190
|
+
- Provider単位の単体テストはSDK呼び出しをmockする
|
|
191
|
+
- APIキーやリクエスト本文を例外ログに直接出さない
|
|
192
|
+
- rate limit、timeout、overloaded、5xx、認証、課金枠不足を型またはstatus/codeで区別できる
|
|
193
|
+
- 未対応パラメータ送信による400を避ける、または設定エラーとして明示的に返せる
|
|
194
|
+
|
|
195
|
+
### Phase 3: ModelRouter中核処理
|
|
196
|
+
|
|
197
|
+
目的: タスク別候補選択、リトライ、タイムアウト、フォールバックを実装する。
|
|
198
|
+
|
|
199
|
+
作業:
|
|
200
|
+
|
|
201
|
+
- `ModelRouter.run()` を実装する
|
|
202
|
+
- primaryとfallbackを順に試行する
|
|
203
|
+
- リクエストの `temperature` / `maxTokens` があれば設定値より優先する
|
|
204
|
+
- timeout処理をProvider呼び出しに適用し、`AbortSignal` をProviderへ渡す
|
|
205
|
+
- 一時的な失敗のみfallbackする
|
|
206
|
+
- 認証エラー、設定ミス、入力過大、schemaName不正、料金上限超過はfallbackしない
|
|
207
|
+
- 成功・失敗を `RunLogger` に渡す
|
|
208
|
+
- fallback判定は文字列マッチではなく `RouterErrorKind` で行う
|
|
209
|
+
- SDK内リトライはProvider責務、candidate切り替えはRouter責務として分離する
|
|
210
|
+
- `schemaName` 指定時はRouter内で検証・修復を呼び出す
|
|
211
|
+
|
|
212
|
+
受け入れ条件:
|
|
213
|
+
|
|
214
|
+
- primary成功時にfallbackが呼ばれない
|
|
215
|
+
- rate limit / timeout / overloaded / 一時的な5xx でfallbackする
|
|
216
|
+
- 認証エラーでfallbackしない
|
|
217
|
+
- 課金枠不足や支払い上限でfallbackしない
|
|
218
|
+
- AI出力のschema検証失敗は、同一candidateでの修復1回後に次candidateへ進む
|
|
219
|
+
- schemaName不正などの設定ミスではfallbackしない
|
|
220
|
+
- 全候補失敗時に最後のエラー情報を含む例外を返す
|
|
221
|
+
|
|
222
|
+
### Phase 4: スキーマ検証とJSON処理
|
|
223
|
+
|
|
224
|
+
目的: 中間成果物のJSONをZodで検証し、失敗時の扱いを明確にする。
|
|
225
|
+
|
|
226
|
+
作業:
|
|
227
|
+
|
|
228
|
+
- `ArticleBriefSchema` を実装する
|
|
229
|
+
- `ArticleOutlineSchema` を実装する
|
|
230
|
+
- `ReviewResultSchema` を実装する
|
|
231
|
+
- schemaNameからZod schemaを解決するregistryを作る
|
|
232
|
+
- JSON parse処理を `src/utils/json.ts` に集約する
|
|
233
|
+
- JSON検証失敗時の修復依頼用フローを `ModelRouter.run()` から呼べる形で実装する
|
|
234
|
+
- 修復依頼は同一candidateにつき最大1回に制限する
|
|
235
|
+
- Providerが構造化出力に対応する場合に備えて `responseFormat` を渡せる型にする
|
|
236
|
+
- AI出力の修復失敗時は `schema_validation` としてfallback候補へ進める
|
|
237
|
+
- `schemaName` 未定義・不正などの設定ミスは `config` として即終了する
|
|
238
|
+
|
|
239
|
+
受け入れ条件:
|
|
240
|
+
|
|
241
|
+
- schemaName未指定のタスクは通常テキストとして処理できる
|
|
242
|
+
- schemaName指定時はparseとZod検証を通過した成果物だけ次工程へ進む
|
|
243
|
+
- AI出力のスキーマ不正を無制限に別モデルへ投げ続けない
|
|
244
|
+
- schemaName不正はfallbackせず設定エラーとして終了する
|
|
245
|
+
- parse失敗・Zod失敗・修復失敗をログへ全文出力せずに追跡できる
|
|
246
|
+
|
|
247
|
+
### Phase 5: RunStoreとRunLogger
|
|
248
|
+
|
|
249
|
+
目的: 途中成果物、メタ情報、最小ログを安全に保存する。
|
|
250
|
+
|
|
251
|
+
作業:
|
|
252
|
+
|
|
253
|
+
- `RunStore` で `runs/<runId>/` を作成する
|
|
254
|
+
- `brief.json`、`outline.json`、`draft.md`、`review.json`、`final.md` を保存する
|
|
255
|
+
- `meta.json` に工程状態を保存する
|
|
256
|
+
- `RunLogger` でJSON Lines形式のメタログを保存する
|
|
257
|
+
- input本文は保存せず、`sha256` hashのみ保存する
|
|
258
|
+
- elapsed、provider、model、status、usage、cost概算を保存する
|
|
259
|
+
- errorは正規化済みの `kind`、status code、短い要約だけ保存する
|
|
260
|
+
|
|
261
|
+
受け入れ条件:
|
|
262
|
+
|
|
263
|
+
- 途中停止後も `meta.json` から完了済み工程を判定できる
|
|
264
|
+
- ログにAPIキー、`.env` 内容、全文プロンプトが含まれない
|
|
265
|
+
- `runs/<runId>/` 以外へ成果物を書き込まない
|
|
266
|
+
|
|
267
|
+
### Phase 6: Qiita記事作成ワークフロー
|
|
268
|
+
|
|
269
|
+
目的: 設計書のQiita記事作成フローをCLIから実行できるようにする。
|
|
270
|
+
|
|
271
|
+
作業:
|
|
272
|
+
|
|
273
|
+
- `createQiitaArticle(topic, options)` を実装する
|
|
274
|
+
- 記事作成ステップを宣言的な配列で定義する
|
|
275
|
+
- 共通ランナーで `article_brief`、`outline`、`draft_markdown`、`technical_review`、`rewrite` を実行する
|
|
276
|
+
- 共通ランナーが `meta.json` を見て完了済み工程をスキップする
|
|
277
|
+
- `brief.json`、`outline.json`、`draft.md`、`review.json`、`final.md` を保存する
|
|
278
|
+
- `article:create` コマンドを実装する(`--topic` インライン、または `--topic-file` でテキストファイル指定)
|
|
279
|
+
- `--topic` / `--topic-file` のどちらか必須とし、両方指定時はエラーにする
|
|
280
|
+
- `--topic-file` 指定時の `runId` はファイル名ベースで生成する
|
|
281
|
+
- `--profile <name>` で `config/profiles/<name>.yaml`(platform/style/language)を読み込み、本文生成プロンプトに作法を注入する(既定 `qiita`、`--platform` はラベル上書き)
|
|
282
|
+
- 解決した platform/style を `meta.json` に保存し、resume/review/revise/evaluate が継承する
|
|
283
|
+
- `article:resume` コマンドを実装する
|
|
284
|
+
- `article:review` コマンドを実装する
|
|
285
|
+
- `article:revise` コマンドを実装する(`final.md` に `--instruction` / `--instruction-file` の修正指示を反映)
|
|
286
|
+
- `article:revise` は上書き前の `final.md` を `final.bak.md` に退避する
|
|
287
|
+
- `article:export` コマンドを実装する(`final.md` を `--out <path>` へ書き出し。秘密名拒否・`--force` 上書き・ワークスペース外警告)
|
|
288
|
+
- `article:evaluate` コマンドを実装する(`final.md` を別系統モデルで評価し `final-review.json` を保存)
|
|
289
|
+
- 評価結果から `revise-instruction.md`(フィルタ済み修正指示)と `final-review.md`(全指摘の人向けサマリ)をローカル整形で生成する(追加APIコール無し、自動rewriteしない)
|
|
290
|
+
- `--min-severity` で指示に含める指摘を絞り、`--criteria` / `--criteria-file` で評価観点を指定できる
|
|
291
|
+
- 評価用の `final_review` タスクを `models.yaml` に追加し、既定で本文と別providerに向ける
|
|
292
|
+
- 工程の進捗(開始/完了/スキップ・使用provider/model・所要時間・概算コスト)を stderr に出力する
|
|
293
|
+
- 進捗は `WorkflowReporter` コールバックで渡し、ワークフロー関数は省略可能(テストではno-op)にする
|
|
294
|
+
- コストは `usage` × `prices` のローカル概算とし、表示のための追加APIコールを行わない。run合計も表示する
|
|
295
|
+
- 本文工程の保存前ガードを設ける(truncation検知の警告、全体コードフェンス除去、ラップ文=前置き/後置きの検知警告)。スキーマ工程は対象外
|
|
296
|
+
- ラップ文は自然文のため自動削除せず警告のみとする(正当な導入/結論の誤削除を避ける)
|
|
297
|
+
|
|
298
|
+
受け入れ条件:
|
|
299
|
+
|
|
300
|
+
- `npm run article:create -- --topic "..."` で新規runを作れる
|
|
301
|
+
- `npm run article:create -- --topic-file <path>` で長文指示ファイルから新規runを作れる
|
|
302
|
+
- `--topic` も `--topic-file` も無い場合はわかりやすいエラーになる
|
|
303
|
+
- `npm run article:resume -- --run <runId>` で未完了工程から再開できる
|
|
304
|
+
- `npm run article:review -- --run <runId>` でレビュー工程以降を再実行できる
|
|
305
|
+
- `npm run article:revise -- --run <runId> --instruction "..."` で final.md に修正指示を反映できる
|
|
306
|
+
- 各工程の成果物が期待ファイル名で保存される
|
|
307
|
+
- create / resume / review が同じステップ定義を共有している
|
|
308
|
+
- 実行中に工程の進捗が stderr に表示され、stdout には `runId` / `final` のみ出力される
|
|
309
|
+
|
|
310
|
+
### Phase 7: テスト
|
|
311
|
+
|
|
312
|
+
目的: 課金や情報送信に関わる分岐を重点的に保護する。
|
|
313
|
+
|
|
314
|
+
作業:
|
|
315
|
+
|
|
316
|
+
- `ModelRouter` のfallback判定テストを追加する
|
|
317
|
+
- `ModelRouter` のprovider未登録テストを追加する
|
|
318
|
+
- timeout時のfallbackテストを追加する
|
|
319
|
+
- 認証エラーでfallbackしないテストを追加する
|
|
320
|
+
- 課金枠不足でfallbackしないテストを追加する
|
|
321
|
+
- AI出力のschema_validationで次candidateへ進むテストを追加する
|
|
322
|
+
- schemaName不正でfallbackしないテストを追加する
|
|
323
|
+
- SDKエラー正規化のテストをProviderごとに追加する
|
|
324
|
+
- AnthropicProviderが未対応パラメータを送らないテストを追加する
|
|
325
|
+
- `RunStore` の保存・再開テストを追加する
|
|
326
|
+
- `RunLogger` が全文inputを保存しないテストを追加する
|
|
327
|
+
- `RunLogger` が生errorやヘッダを保存しないテストを追加する
|
|
328
|
+
- workflowの工程スキップ・再開テストを追加する
|
|
329
|
+
|
|
330
|
+
受け入れ条件:
|
|
331
|
+
|
|
332
|
+
- `npm test` が成功する
|
|
333
|
+
- 外部APIを呼ばずにテストできる
|
|
334
|
+
- 失敗時にログへ機密情報が混入しないことをテストで確認する
|
|
335
|
+
|
|
336
|
+
### Phase 8: READMEと利用手順
|
|
337
|
+
|
|
338
|
+
目的: 最小限の使い方、設定、注意点を利用者が迷わず確認できるようにする。
|
|
339
|
+
|
|
340
|
+
作業:
|
|
341
|
+
|
|
342
|
+
- READMEに概要を追加する
|
|
343
|
+
- セットアップ手順を追加する
|
|
344
|
+
- `.env` の設定例を追加する
|
|
345
|
+
- `models.yaml` の編集方法を追加する
|
|
346
|
+
- `api_key_env` とモデル別単価設定の扱いを追加する
|
|
347
|
+
- Anthropicなどモデルによって `temperature` が使えない場合があることを明記する
|
|
348
|
+
- `article:create`、`article:resume`、`article:review` の使用例を追加する
|
|
349
|
+
- セキュリティ上やらないことを明記する
|
|
350
|
+
|
|
351
|
+
受け入れ条件:
|
|
352
|
+
|
|
353
|
+
- READMEだけでローカル実行まで進められる
|
|
354
|
+
- APIキーや秘密情報をログに残さない方針が明記されている
|
|
355
|
+
- MVPで未対応の機能が明記されている
|
|
356
|
+
|
|
357
|
+
## 6. 実装順序
|
|
358
|
+
|
|
359
|
+
推奨順序:
|
|
360
|
+
|
|
361
|
+
1. Phase 0: プロジェクト初期化
|
|
362
|
+
2. Phase 1: 型定義と設定読み込み
|
|
363
|
+
3. Phase 5: RunStoreとRunLogger
|
|
364
|
+
4. Phase 3: ModelRouter中核処理
|
|
365
|
+
5. Phase 4: スキーマ検証とJSON処理
|
|
366
|
+
6. Phase 2: Provider実装
|
|
367
|
+
7. Phase 6: Qiita記事作成ワークフロー
|
|
368
|
+
8. Phase 7: テスト拡充
|
|
369
|
+
9. Phase 8: README整備
|
|
370
|
+
|
|
371
|
+
理由:
|
|
372
|
+
|
|
373
|
+
- 先にProviderを作ると外部API都合に引っ張られるため、mock可能なRouterとStoreを先に固める
|
|
374
|
+
- Router、Store、Loggerは課金や情報保護に直結するため、早い段階でテストを書く
|
|
375
|
+
- Providerは最後寄りに実装し、外部SDK依存を境界に閉じ込める
|
|
376
|
+
|
|
377
|
+
## 7. テスト計画
|
|
378
|
+
|
|
379
|
+
### 7.1 単体テスト
|
|
380
|
+
|
|
381
|
+
- config読み込み
|
|
382
|
+
- fallback判定
|
|
383
|
+
- timeout処理
|
|
384
|
+
- schema registry
|
|
385
|
+
- JSON parse / validation
|
|
386
|
+
- RunStoreの保存と読み込み
|
|
387
|
+
- RunLoggerの秘匿情報非保存
|
|
388
|
+
|
|
389
|
+
### 7.2 結合テスト
|
|
390
|
+
|
|
391
|
+
- mock providerを使った `createQiitaArticle`
|
|
392
|
+
- `meta.json` を使った `resumeQiitaArticle`
|
|
393
|
+
- review工程のみ再実行
|
|
394
|
+
|
|
395
|
+
### 7.3 手動確認
|
|
396
|
+
|
|
397
|
+
- `.env` 未設定時のエラーメッセージ
|
|
398
|
+
- `models.yaml` 不正時のエラーメッセージ
|
|
399
|
+
- runId指定時の保存先
|
|
400
|
+
- ログに全文プロンプトが含まれないこと
|
|
401
|
+
- 実APIを使った最小1回の疎通確認
|
|
402
|
+
|
|
403
|
+
## 8. エラー分類
|
|
404
|
+
|
|
405
|
+
### 8.1 fallbackする
|
|
406
|
+
|
|
407
|
+
- rate limit
|
|
408
|
+
- timeout
|
|
409
|
+
- overloaded
|
|
410
|
+
- temporary unavailable
|
|
411
|
+
- service unavailable
|
|
412
|
+
- provider側の5xx
|
|
413
|
+
- API connection error
|
|
414
|
+
- AI出力のJSON parse / Zod検証失敗後、同一candidateでの修復も失敗した場合
|
|
415
|
+
|
|
416
|
+
### 8.2 fallbackしない
|
|
417
|
+
|
|
418
|
+
- APIキー未設定
|
|
419
|
+
- APIキー不正
|
|
420
|
+
- 認証エラー
|
|
421
|
+
- 入力が長すぎる
|
|
422
|
+
- schemaName不正
|
|
423
|
+
- 禁止された内容
|
|
424
|
+
- 課金枠不足
|
|
425
|
+
- 料金上限超過
|
|
426
|
+
- `models.yaml` の設定ミス
|
|
427
|
+
|
|
428
|
+
`quota` という文字列では判定しない。
|
|
429
|
+
一時的なrate limitと、支払い・課金枠・プロジェクト上限などのbilling quota系エラーは別の `RouterErrorKind` として扱う。
|
|
430
|
+
|
|
431
|
+
スキーマ系は `config` と `schema_validation` に分ける。
|
|
432
|
+
`schemaName` 不正やschema registry不整合は設定ミスなのでfallbackしない。AI出力のparse / Zod検証失敗は、同一candidateで最大1回修復を試し、それでも失敗した場合に `schema_validation` として次candidateへ進める。
|
|
433
|
+
|
|
434
|
+
## 9. セキュリティチェックリスト
|
|
435
|
+
|
|
436
|
+
- `.env` をGit管理しない
|
|
437
|
+
- `.env.example` に実キーを書かない
|
|
438
|
+
- ログにAPIキーを書かない
|
|
439
|
+
- ログに全文プロンプトを書かない
|
|
440
|
+
- エラーメッセージにSDKの生レスポンスを丸ごと保存しない
|
|
441
|
+
- fallback判定をエラーメッセージ文字列の部分一致に依存しない
|
|
442
|
+
- 成果物保存先を `runs/<runId>/` に限定する
|
|
443
|
+
- runIdにパストラバーサル文字列を許可しない
|
|
444
|
+
- CLI引数から任意ファイル書き込み先を受け取らない(例外: `article:export` の `--out` は明示エクスポートとして許可。`.env` 等の秘密名は拒否、`--force` 無しでは上書きしない、ワークスペース外は警告。対象は `final.md` のみ)
|
|
445
|
+
- `--topic-file` / `--instruction-file` で `.env` 等の秘密ファイルを読み込ませない(ワークスペース外は警告)
|
|
446
|
+
- 任意コード実行機能を追加しない
|
|
447
|
+
- 任意URL取得機能を追加しない
|
|
448
|
+
|
|
449
|
+
## 10. 完了条件
|
|
450
|
+
|
|
451
|
+
MVPは次を満たした時点で完了とする。
|
|
452
|
+
|
|
453
|
+
- `npm run build` が成功する
|
|
454
|
+
- `npm test` が成功する
|
|
455
|
+
- `npm run article:create -- --topic "..."` がmockまたは実APIで動く
|
|
456
|
+
- `runs/<runId>/` に `brief.json`、`outline.json`、`draft.md`、`review.json`、`final.md`、`meta.json` が保存される
|
|
457
|
+
- `npm run article:resume -- --run <runId>` で途中再開できる
|
|
458
|
+
- OpenAI / Anthropic のprimary / fallbackを設定で切り替えられる
|
|
459
|
+
- fallbackすべきでないエラーで別providerへ送信されない
|
|
460
|
+
- fallback判定が正規化済みエラー種別で実装されている
|
|
461
|
+
- schemaName指定時の検証・修復責務がRouterに実装されている
|
|
462
|
+
- ログにAPIキーや全文プロンプトが残らない
|
|
463
|
+
- READMEにセットアップとCLI利用例がある
|
|
464
|
+
|
|
465
|
+
## 11. MVP後の拡張候補
|
|
466
|
+
|
|
467
|
+
優先度順:
|
|
468
|
+
|
|
469
|
+
1. GeminiProvider追加
|
|
470
|
+
2. LocalProvider / Ollama対応
|
|
471
|
+
3. 記事テンプレート切り替え
|
|
472
|
+
4. コスト上限の厳格化
|
|
473
|
+
5. Qiita API下書き投稿
|
|
474
|
+
6. GitHubへの成果物保存
|
|
475
|
+
7. LangGraph連携
|
|
476
|
+
8. Dify連携
|
|
477
|
+
9. Web UI
|
|
478
|
+
|
|
479
|
+
MVP後も、外部公開API、任意コード実行、任意URL取得は必要性とリスクを再評価してから追加する。
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rex0220/llm-task-router",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Thin, file-based CLI that routes a multi-step article workflow (brief → outline → draft → review → final) across OpenAI and Anthropic with fallback, schema validation, judge-model evaluation, and platform profiles (Qiita/Zenn/blog/note).",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"llm",
|
|
8
|
+
"openai",
|
|
9
|
+
"anthropic",
|
|
10
|
+
"claude",
|
|
11
|
+
"gpt",
|
|
12
|
+
"article",
|
|
13
|
+
"writing",
|
|
14
|
+
"content-generation",
|
|
15
|
+
"model-router",
|
|
16
|
+
"fallback",
|
|
17
|
+
"qiita",
|
|
18
|
+
"zenn"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": "rex0220 <rex0220@gmail.com>",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/rex0220/llm-task-router.git"
|
|
25
|
+
},
|
|
26
|
+
"homepage": "https://github.com/rex0220/llm-task-router#readme",
|
|
27
|
+
"bugs": "https://github.com/rex0220/llm-task-router/issues",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"type": "module",
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=18"
|
|
34
|
+
},
|
|
35
|
+
"bin": {
|
|
36
|
+
"llm-task-router": "dist/llm-task-router.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"dist",
|
|
40
|
+
"config",
|
|
41
|
+
"docs",
|
|
42
|
+
".env.example",
|
|
43
|
+
"README.md",
|
|
44
|
+
"README.ja.md"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc --noEmit -p tsconfig.json && tsup",
|
|
48
|
+
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
49
|
+
"prepack": "npm run build",
|
|
50
|
+
"test": "npm run build && vitest run",
|
|
51
|
+
"test:unit": "vitest run",
|
|
52
|
+
"article:create": "tsx src/index.ts article:create",
|
|
53
|
+
"article:resume": "tsx src/index.ts article:resume",
|
|
54
|
+
"article:review": "tsx src/index.ts article:review",
|
|
55
|
+
"article:revise": "tsx src/index.ts article:revise",
|
|
56
|
+
"article:evaluate": "tsx src/index.ts article:evaluate",
|
|
57
|
+
"article:export": "tsx src/index.ts article:export"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@anthropic-ai/sdk": "^0.56.0",
|
|
61
|
+
"commander": "^14.0.0",
|
|
62
|
+
"dotenv": "^16.4.7",
|
|
63
|
+
"openai": "^5.6.0",
|
|
64
|
+
"yaml": "^2.8.0",
|
|
65
|
+
"zod": "^3.25.67"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@types/node": "^22.15.32",
|
|
69
|
+
"tsup": "^8.5.1",
|
|
70
|
+
"tsx": "^4.20.3",
|
|
71
|
+
"typescript": "^5.8.3",
|
|
72
|
+
"vitest": "^3.2.4"
|
|
73
|
+
}
|
|
74
|
+
}
|