@jungtz/chat-kit 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/LICENSE +21 -0
- package/README.md +162 -0
- package/dist/engine/engine.d.ts +18 -0
- package/dist/engine/types.d.ts +133 -0
- package/dist/guards/input.d.ts +16 -0
- package/dist/guards/output.d.ts +45 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -0
- package/dist/models/create-model.d.ts +21 -0
- package/dist/models/fallback-model.d.ts +25 -0
- package/dist/models/from-config.d.ts +55 -0
- package/dist/models/index.d.ts +19 -0
- package/dist/models/index.js +1 -0
- package/dist/models/index.js.map +1 -0
- package/dist/models/key-ring.d.ts +70 -0
- package/dist/models/list-models.d.ts +30 -0
- package/dist/models/model-spec.d.ts +34 -0
- package/dist/models/protocol.d.ts +33 -0
- package/dist/models/quirks.d.ts +54 -0
- package/dist/models/routed-model.d.ts +43 -0
- package/dist/models/stream-peek.d.ts +27 -0
- package/dist/models/types.d.ts +51 -0
- package/dist/prompt/assemble.d.ts +39 -0
- package/dist/prompt/markers.d.ts +20 -0
- package/dist/prompt/types.d.ts +55 -0
- package/docs/API.md +153 -0
- package/package.json +65 -0
package/docs/API.md
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# API 速查(@jungtz/chat-kit)
|
|
2
|
+
|
|
3
|
+
> 對象:使用端工程師與 AI agent。此檔隨 npm 包出貨(`node_modules/@jungtz/chat-kit/docs/API.md`)。
|
|
4
|
+
> 流程、責任邊界與範例見 `docs/FLOW.md`(repo 內);型別的完整宣告見 `dist/**/*.d.ts`(包內)。
|
|
5
|
+
> 兩個進入點:`.`(引擎/提示詞/閘門)與 `./models`(設定 → model,亦可從 `.` 匯入)。
|
|
6
|
+
> 套件沒有任何 import 副作用:不讀 env、不讀檔、不建全域 singleton。
|
|
7
|
+
|
|
8
|
+
## 1. 引擎
|
|
9
|
+
|
|
10
|
+
```ts
|
|
11
|
+
createChatEngine(options: { domains: Record<string, DomainAdapter> }): ChatEngine
|
|
12
|
+
|
|
13
|
+
interface ChatEngine {
|
|
14
|
+
runTurn(input: TurnInput): AsyncGenerator<ChatEvent, void, undefined>; // 串流
|
|
15
|
+
completeTurn(input: TurnInput): Promise<TurnResult>; // 一次回完整結果
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### TurnInput
|
|
20
|
+
|
|
21
|
+
| 欄位 | 型別 | 說明 |
|
|
22
|
+
| :-- | :-- | :-- |
|
|
23
|
+
| `domain` | `string` | 已註冊的 Domain id |
|
|
24
|
+
| `channel` | `string` | 通道;決定 `channelParts` 與 `GuardContext.channel` |
|
|
25
|
+
| `messages` | `readonly ChatMessage[]` | 最後一則必須是 `user`;system 訊息接在 Domain 片段之後(`extraSystem`,原樣帶入、不檢查佔位符) |
|
|
26
|
+
| `model` | `LanguageModel`(AI SDK) | `models/` 產出的 model,或任何自建 model |
|
|
27
|
+
| `tenantId?` | `string` | 原樣傳給 Domain |
|
|
28
|
+
| `entity?` | `Readonly<Record<string, unknown>>` | 對話標的,原樣傳給 Domain |
|
|
29
|
+
| `modelOptions?` | `{ temperature?, maxOutputTokens?, providerOptions?, timeout? }` | `timeout` 為 `number` 或 `{ totalMs?, stepMs?, chunkMs? }`(`chunkMs` 是串流閒置上限) |
|
|
30
|
+
| `signal?` | `AbortSignal` | 使用者離開時中止上游 |
|
|
31
|
+
|
|
32
|
+
### ChatEvent
|
|
33
|
+
|
|
34
|
+
| 型別 | 欄位 |
|
|
35
|
+
| :-- | :-- |
|
|
36
|
+
| `context` | `injectedKeys: string[]` |
|
|
37
|
+
| `thinking` / `content` | `text`(content 只含新增部分) |
|
|
38
|
+
| `tool-call` / `tool-result` | `toolName`、`input`/`output` |
|
|
39
|
+
| `guard` | `name`、`action: 'neutralized'\|'transformed'\|'rewrite'\|'observed'`、`detail?` |
|
|
40
|
+
| `usage` | `usage: TurnUsage` |
|
|
41
|
+
| `done` | `text`(套用全部閘門後)、`rewritten`、`finishReason` |
|
|
42
|
+
| `error` | `error: unknown` |
|
|
43
|
+
|
|
44
|
+
- **出錯不送 `done`**:用「有沒有收到 `done`」判斷成功,不可用「串流結束了」。
|
|
45
|
+
- `TurnUsage`:`inputTokens`/`outputTokens`/`reasoningTokens`/`totalTokens`,缺席為 `undefined`(用量紀錄記 `null`,不記 `0`)。
|
|
46
|
+
- `TurnResult`(`completeTurn`):`text`、`thinking`、`rewritten`、`finishReason`、`usage`(可為 `null`)、`toolCalls[]`、`guards[]`、`injectedKeys[]`。
|
|
47
|
+
- `messages` 不合法或未註冊 domain:**在第一個事件前拋錯**(呼叫端 try/catch);其餘失敗走 `error` 事件。
|
|
48
|
+
|
|
49
|
+
## 2. DomainAdapter
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
interface DomainAdapter {
|
|
53
|
+
id: string;
|
|
54
|
+
prompt: PromptSpec | ((turn: TurnContext) => PromptSpec | Promise<PromptSpec>);
|
|
55
|
+
buildContext?(turn: TurnContext): Promise<ContextBlock[]> | ContextBlock[];
|
|
56
|
+
tools?(turn: TurnContext): ToolSet | Promise<ToolSet>; // AI SDK tool(),含 execute
|
|
57
|
+
maxToolSteps?: number; // 預設 3
|
|
58
|
+
prepareStep?: PrepareStepFunction<ToolSet>; // 每步呼叫前的鉤子(如強制重查)
|
|
59
|
+
outputGuards?: readonly OutputGuard[]; // 接在內建閘門之後
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`TurnContext`:`{ domain, channel, tenantId?, entity?, messages(已過輸入閘門), userMessage(已中和標記), signal? }`——刻意**不含 model**,Domain 不該依模型改變資料。
|
|
64
|
+
|
|
65
|
+
## 3. 提示詞
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
interface PromptSpec {
|
|
69
|
+
parts: readonly string[]; // 依序拼接的 system 片段
|
|
70
|
+
channelParts?: Readonly<Record<Channel, readonly string[]>>; // 接在 parts 之後
|
|
71
|
+
placeholders?: Readonly<Record<string, string>>; // {{NAME}} 展開,殘留即拋錯
|
|
72
|
+
composeUserMessage?(args: { blocks: string; message: string }): string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface ContextBlock {
|
|
76
|
+
key: string; // 同一輪內不可重複
|
|
77
|
+
order: number; // 小的在前;同 order 依 key 字典序,可重現
|
|
78
|
+
trust: 'authoritative' | 'reference' | 'realtime';
|
|
79
|
+
placement: 'system' | 'user';
|
|
80
|
+
title: string;
|
|
81
|
+
content: string; // 空字串=本輪無資料,整塊略過
|
|
82
|
+
format?: 'marked' | 'raw'; // 預設 marked([REFERENCE: 標題] … [END REFERENCE])
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
- 佔位符**只掃模板本身**:值裡剛好出現 `{{x}}` 不展開、不算殘留。
|
|
87
|
+
- 一般使用端不必呼叫;測試/除錯可用:`assemblePrompt`、`expandPlaceholders`、`orderBlocks`、`renderBlock`、`openMarker`/`closeMarker`/`markerPattern`/`TRUST_LABELS`。
|
|
88
|
+
|
|
89
|
+
## 4. 閘門
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
interface OutputGuard {
|
|
93
|
+
name: string;
|
|
94
|
+
transform?(buffer: string, ctx: GuardContext): string; // 串流中,純函式
|
|
95
|
+
check?(finalText: string, ctx: GuardContext): GuardVerdict | Promise<GuardVerdict>; // 結束後
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
interface GuardVerdict { action: 'pass' | 'rewrite'; text?: string; reason?: string }
|
|
99
|
+
interface GuardContext { modelId: string; channel: string }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- `rewrite` 帶 `text`:改寫只反映在 `done.text`(已送出的串流收不回來)。
|
|
103
|
+
- **觀察模式**:`pass` 帶 `reason` = 放行但回報 `guard` 事件(`action: 'observed'`),供規則上線前量測命中率。
|
|
104
|
+
- 內建:`stripMarkersGuard`(清內部標記,引擎一律套用);`selfIntroGuard(modelNames: string[])`(模型自我介紹過濾,含備援模型名也要列)。
|
|
105
|
+
- 低階工具:`neutralizeMarkers(text)`(輸入中和,引擎已內建)、`applyTransforms(buffer, guards, ctx)`。
|
|
106
|
+
|
|
107
|
+
## 5. models/(設定 → model)
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
createModelFromConfig(providers: ProviderMap, rawConfig: RawModelConfig | ModelConfig,
|
|
111
|
+
options?: ModelFromConfigOptions): { model: LanguageModelV3; targets: ModelTarget[] }
|
|
112
|
+
|
|
113
|
+
resolveModelChain(providers, rawConfig, options?): ModelTarget[] // 只解析不建 model
|
|
114
|
+
resolveModelTarget(providers, providerKey, options?): ModelTarget // 單一 provider
|
|
115
|
+
createRoutedModel(target: ModelTarget, options?: RoutedModelOptions): LanguageModelV3
|
|
116
|
+
createFallbackModel(models: readonly LanguageModelV3[], options?: FallbackModelOptions): LanguageModelV3
|
|
117
|
+
reasoningOptions(effort: ReasoningEffort | null): SharedV3ProviderOptions
|
|
118
|
+
createKeyRing(options?: KeyRingOptions): KeyRing
|
|
119
|
+
listModels(baseURL, apiKeys, options?): Promise<ListedModel[]>
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
| 類型 | 要點 |
|
|
123
|
+
| :-- | :-- |
|
|
124
|
+
| `ProviderDef` | `{ baseURL?, apiKey? \| apiKeys?, defaultModel?, sdk?, reasoningEffort? }`;金鑰支援 `${ENV}` 展開 |
|
|
125
|
+
| `ModelTarget` | 整包 `{ providerKey, baseURL, apiKeys[], model, protocol, reasoningEffort? }`,不拆開傳 |
|
|
126
|
+
| `Protocol` | `'openai-compatible'`(`/chat/completions`)/`'openai'`(`/responses`)/`'anthropic'`(`/messages`) |
|
|
127
|
+
| `ReasoningEffort` | `'none' \| 'minimal' \| 'low' \| 'medium' \| 'high' \| 'xhigh' \| 'max'` |
|
|
128
|
+
| `ModelFromConfigOptions` | `defaultProviderKey?`、`defaultReasoningEffort?`、`requireApiKey?`、`env?`、`logger?`、`routed?`、`fallback?` |
|
|
129
|
+
| `FallbackEvent` | `{ from, to, error, index }`——`onFallback` 用來記「實際回應者」,用量才不會記錯 provider |
|
|
130
|
+
| `KeyRing` | `pick`/`markExhausted`/`status()`/`reset()`;429 冷卻遵守 `Retry-After` |
|
|
131
|
+
|
|
132
|
+
設定形狀(`rawConfig`):`{ model: 'provider' 或 'provider/model', reasoningEffort?, backup? }`;主設定無效直接拋錯,備援無效只警告並略過。備援只沿用推理強度、不沿用模型名(改用備援自己的 `defaultModel`)。
|
|
133
|
+
|
|
134
|
+
## 6. 常見陷阱
|
|
135
|
+
|
|
136
|
+
| 陷阱 | 正確做法 |
|
|
137
|
+
| :-- | :-- |
|
|
138
|
+
| `ai` 裝到 7.x | 釘 `ai@^6`、`@ai-sdk/provider@^3`(`ai@7` 介面變動未支援) |
|
|
139
|
+
| 自己呼叫 `streamText` 沒設 `maxRetries` | 一律 `maxRetries: 0`——輪替與備援已在 models 層,SDK 重試會再疊一層 |
|
|
140
|
+
| 用「串流結束了」判斷成功 | 用「有沒有收到 `done`」 |
|
|
141
|
+
| usage 缺席填 `0` | 記 `null`——`0` 是「真的消耗 0」,會污染統計 |
|
|
142
|
+
| 手組 `providerOptions` 且命名空間含點 | 一律用 `reasoningOptions()`——含點的鍵會讓推理強度被 AI SDK 靜默丟掉 |
|
|
143
|
+
| `messages` 最後一則不是 user/空內容 | 第一個事件前拋錯,呼叫端要 try/catch |
|
|
144
|
+
| 依賴 `file:` 或 `npm link` | 釘死版本(npm 版本號或 git tag) |
|
|
145
|
+
|
|
146
|
+
## 7. 對應檔案(深入時)
|
|
147
|
+
|
|
148
|
+
| 主題 | 檔案 |
|
|
149
|
+
| :-- | :-- |
|
|
150
|
+
| 五步管線與事件 | `dist/engine/types.d.ts`(原始碼 `src/engine/engine.ts`) |
|
|
151
|
+
| 組裝與信任標記 | `src/prompt/assemble.ts`、`src/prompt/markers.ts` |
|
|
152
|
+
| 換手與金鑰輪替 | `src/models/fallback-model.ts`、`stream-peek.ts`、`key-ring.ts` |
|
|
153
|
+
| provider 特例 | `src/models/quirks.ts` |
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jungtz/chat-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "通用 Chat 引擎:AI SDK 模型層、金鑰輪替、主備援、提示詞組裝與閘門",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./models": {
|
|
12
|
+
"types": "./dist/models/index.d.ts",
|
|
13
|
+
"import": "./dist/models/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"docs/API.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json --outDir .build && rollup -c",
|
|
23
|
+
"build:prod": "cross-env NODE_ENV=production npm run build",
|
|
24
|
+
"prepare": "npm run build:prod",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"test": "node --import tsx --test \"test/**/*.test.ts\"",
|
|
27
|
+
"prepublishOnly": "npm run build:prod && npm test"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20.0.0"
|
|
31
|
+
},
|
|
32
|
+
"peerDependencies": {
|
|
33
|
+
"@ai-sdk/anthropic": "^3.0.0",
|
|
34
|
+
"@ai-sdk/openai": "^3.0.0",
|
|
35
|
+
"@ai-sdk/openai-compatible": "^2.0.0",
|
|
36
|
+
"@ai-sdk/provider": "^3.0.0",
|
|
37
|
+
"ai": "^6.0.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"@ai-sdk/anthropic": {
|
|
41
|
+
"optional": true
|
|
42
|
+
},
|
|
43
|
+
"@ai-sdk/openai": {
|
|
44
|
+
"optional": true
|
|
45
|
+
},
|
|
46
|
+
"@ai-sdk/openai-compatible": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@ai-sdk/anthropic": "^3.0.127",
|
|
53
|
+
"@ai-sdk/openai": "^3.0.124",
|
|
54
|
+
"@ai-sdk/openai-compatible": "^2.0.81",
|
|
55
|
+
"@ai-sdk/provider": "^3.0.18",
|
|
56
|
+
"@rollup/plugin-terser": "^1.0.0",
|
|
57
|
+
"@types/node": "^22.20.5",
|
|
58
|
+
"ai": "^6.0.300",
|
|
59
|
+
"cross-env": "^10.1.0",
|
|
60
|
+
"rollup": "^4.63.6",
|
|
61
|
+
"tsx": "^4.23.15",
|
|
62
|
+
"typescript": "5.9",
|
|
63
|
+
"zod": "^4.6.5"
|
|
64
|
+
}
|
|
65
|
+
}
|