@codehz/ai 0.1.0 → 0.1.1
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/README.md +107 -2
- package/package.json +1 -1
- package/src/adapters/chat-completions.ts +1 -0
- package/src/adapters/index.ts +21 -0
- package/src/adapters/messages.ts +1 -0
- package/src/adapters/mock.ts +721 -0
- package/src/adapters/ollama.ts +2 -0
- package/src/adapters/responses.ts +1 -0
- package/src/core/aggregator.ts +72 -177
- package/src/core/collect-stream.ts +4 -4
- package/src/core/event-factory.ts +1 -1
- package/src/helpers/adapter-base.ts +5 -5
- package/src/helpers/synthetic-stream.ts +1 -1
- package/src/types/adapter.ts +13 -1
- package/src/types/events.ts +1 -1
- package/src/types/response.ts +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# nano-ai
|
|
2
2
|
|
|
3
|
-
统一流式 AI 客户端 — 一套 canonical API
|
|
3
|
+
统一流式 AI 客户端 — 一套 canonical API,支持真实模型后端与面向测试的脚本化 `MockAdapter`(`responses` / `messages` / `chat.completions` / `ollama` / `mock`)。
|
|
4
4
|
|
|
5
5
|
## 安装
|
|
6
6
|
|
|
@@ -114,9 +114,11 @@ console.log(response.replay); // 续接材料
|
|
|
114
114
|
| OpenAI Responses API | `ResponsesAdapter` | 🌟🌟🌟 |
|
|
115
115
|
| Anthropic Messages API | `MessagesAdapter` | 🌟🌟☆ |
|
|
116
116
|
| OpenAI Chat Completions | `ChatCompletionsAdapter` | 🌟☆☆ |
|
|
117
|
+
| Ollama Chat API | `OllamaAdapter` | 🌟☆☆ |
|
|
118
|
+
| Scripted Test Backend | `MockAdapter` | 测试夹具 |
|
|
117
119
|
|
|
118
120
|
```ts
|
|
119
|
-
import { ResponsesAdapter, MessagesAdapter, ChatCompletionsAdapter } from "nano-ai";
|
|
121
|
+
import { ResponsesAdapter, MessagesAdapter, ChatCompletionsAdapter, OllamaAdapter, MockAdapter } from "nano-ai";
|
|
120
122
|
|
|
121
123
|
// OpenAI Responses API(能力最强)
|
|
122
124
|
const responses = new ResponsesAdapter({ apiKey: "sk-..." });
|
|
@@ -126,6 +128,26 @@ const messages = new MessagesAdapter({ apiKey: "sk-ant-..." });
|
|
|
126
128
|
|
|
127
129
|
// OpenAI Chat Completions(兼容层)
|
|
128
130
|
const chat = new ChatCompletionsAdapter({ apiKey: "sk-..." });
|
|
131
|
+
|
|
132
|
+
// Ollama
|
|
133
|
+
const ollama = new OllamaAdapter({ baseUrl: "http://localhost:11434" });
|
|
134
|
+
|
|
135
|
+
// 面向测试的脚本化 mock backend
|
|
136
|
+
const mock = new MockAdapter({
|
|
137
|
+
turns: [
|
|
138
|
+
{
|
|
139
|
+
steps: [
|
|
140
|
+
{ type: "message", content: "我先调用天气工具。" },
|
|
141
|
+
{
|
|
142
|
+
type: "tool_call",
|
|
143
|
+
id: "mock-call-weather",
|
|
144
|
+
name: "get_weather",
|
|
145
|
+
argumentsText: '{"city":"Hangzhou"}',
|
|
146
|
+
},
|
|
147
|
+
],
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
});
|
|
129
151
|
```
|
|
130
152
|
|
|
131
153
|
各 adapter 的能力差异通过 `capabilities` 字段暴露:
|
|
@@ -136,6 +158,89 @@ adapter.capabilities.toolCallStreaming; // 是否支持工具调用流
|
|
|
136
158
|
adapter.capabilities.replayFidelity; // "high" | "medium" | "low"
|
|
137
159
|
```
|
|
138
160
|
|
|
161
|
+
## Mock 后端
|
|
162
|
+
|
|
163
|
+
`MockAdapter` 现在不是“按关键词回文本”的通用假后端,而是专门用于测试长流程工具调用、`replay` 续接、以及异常路径的脚本化测试夹具。
|
|
164
|
+
|
|
165
|
+
核心思路是按 turn 写脚本:
|
|
166
|
+
|
|
167
|
+
- 每一轮可声明对请求格式的期望
|
|
168
|
+
- 每一轮可脚本化发出 `message` / `reasoning` / `tool_call`
|
|
169
|
+
- 可注入 `warning`、`content_filter`、transport interruption、provider-style error
|
|
170
|
+
- 可验证调用方是否把上一轮 `replay` 和当前 `tool_result` 正确带回
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
import { createAIClient, MockAdapter } from "nano-ai";
|
|
174
|
+
|
|
175
|
+
const client = createAIClient({
|
|
176
|
+
adapter: new MockAdapter({
|
|
177
|
+
turns: [
|
|
178
|
+
{
|
|
179
|
+
name: "request-tool",
|
|
180
|
+
expect: {
|
|
181
|
+
items: [{ type: "message", role: "user", textIncludes: "weather" }],
|
|
182
|
+
tools: "present",
|
|
183
|
+
toolChoice: "present",
|
|
184
|
+
},
|
|
185
|
+
steps: [
|
|
186
|
+
{ type: "message", content: "Checking weather now." },
|
|
187
|
+
{
|
|
188
|
+
type: "tool_call",
|
|
189
|
+
id: "mock-call-weather",
|
|
190
|
+
name: "get_weather",
|
|
191
|
+
argumentsText: '{"city":"Hangzhou"}',
|
|
192
|
+
},
|
|
193
|
+
],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
name: "consume-tool-result",
|
|
197
|
+
expect: {
|
|
198
|
+
requireReplayFromPreviousTurn: true,
|
|
199
|
+
requireToolResultsForPendingCalls: true,
|
|
200
|
+
},
|
|
201
|
+
steps: [{ type: "message", content: "Hangzhou is 28C and sunny." }],
|
|
202
|
+
},
|
|
203
|
+
],
|
|
204
|
+
}),
|
|
205
|
+
model: "mock-model",
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
核心类型:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
type MockTurn = {
|
|
213
|
+
name?: string;
|
|
214
|
+
expect?: MockRequestExpectation | MockTurnValidator;
|
|
215
|
+
steps: MockStep[];
|
|
216
|
+
};
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
测试工具循环时,第二轮通常会要求:
|
|
220
|
+
|
|
221
|
+
- `requireReplayFromPreviousTurn: true`
|
|
222
|
+
- `requireToolResultsForPendingCalls: true`
|
|
223
|
+
|
|
224
|
+
畸形路径示例:
|
|
225
|
+
|
|
226
|
+
```ts
|
|
227
|
+
{
|
|
228
|
+
steps: [
|
|
229
|
+
{ type: "message", content: "partial answer" },
|
|
230
|
+
{ type: "interrupt" }, // 不发 response.completed,collectStream() 应失败
|
|
231
|
+
],
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
{
|
|
237
|
+
steps: [
|
|
238
|
+
{ type: "warning", message: "content filtered by policy", code: "CONTENT_FILTERED" },
|
|
239
|
+
{ type: "complete", stopReason: "content_filter" },
|
|
240
|
+
],
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
139
244
|
## 多轮对话
|
|
140
245
|
|
|
141
246
|
库不托管会话状态。调用方自行保留 `response.replay` 并在下一轮带回:
|
package/package.json
CHANGED
|
@@ -161,6 +161,7 @@ function ensureTextCompatibleBlocks(
|
|
|
161
161
|
): import("../index.js").ContentBlock[] {
|
|
162
162
|
for (let i = 0; i < blocks.length; i++) {
|
|
163
163
|
const block = blocks[i];
|
|
164
|
+
if (!block) continue;
|
|
164
165
|
if (block.type !== "text" && block.type !== "json") {
|
|
165
166
|
throw new AIRequestError(
|
|
166
167
|
`chat-completions does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`,
|
package/src/adapters/index.ts
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* - messages
|
|
7
7
|
* - chat.completions
|
|
8
8
|
* - ollama
|
|
9
|
+
* - mock
|
|
9
10
|
*
|
|
10
11
|
* 每个 adapter 实现 BackendAdapter 内部协议。
|
|
11
12
|
*/
|
|
@@ -18,3 +19,23 @@ export { ChatCompletionsAdapter } from "./chat-completions.js";
|
|
|
18
19
|
export type { ChatCompletionsAdapterOptions } from "./chat-completions.js";
|
|
19
20
|
export { OllamaAdapter } from "./ollama.js";
|
|
20
21
|
export type { OllamaAdapterOptions } from "./ollama.js";
|
|
22
|
+
export { MockAdapter } from "./mock.js";
|
|
23
|
+
export type {
|
|
24
|
+
MockAdapterOptions,
|
|
25
|
+
MockInputExpectation,
|
|
26
|
+
MockRequestExpectation,
|
|
27
|
+
MockTurnContext,
|
|
28
|
+
MockTurnValidator,
|
|
29
|
+
MockWarningStep,
|
|
30
|
+
MockAuxiliaryStep,
|
|
31
|
+
MockMessageStep,
|
|
32
|
+
MockReasoningStep,
|
|
33
|
+
MockToolCallStep,
|
|
34
|
+
MockOutputStep,
|
|
35
|
+
MockCompleteStep,
|
|
36
|
+
MockErrorStep,
|
|
37
|
+
MockInterruptStep,
|
|
38
|
+
MockThrowStep,
|
|
39
|
+
MockStep,
|
|
40
|
+
MockTurn,
|
|
41
|
+
} from "./mock.js";
|
package/src/adapters/messages.ts
CHANGED
|
@@ -78,6 +78,7 @@ function ensureMessagesTextBlocks(
|
|
|
78
78
|
): import("../index.js").ContentBlock[] {
|
|
79
79
|
for (let i = 0; i < blocks.length; i++) {
|
|
80
80
|
const block = blocks[i];
|
|
81
|
+
if (!block) continue;
|
|
81
82
|
if (block.type !== "text" && block.type !== "json") {
|
|
82
83
|
throw new AIRequestError(
|
|
83
84
|
`messages does not support ${field}[${i}] of type "${block.type}"; only text/json blocks are supported`,
|