my-ai-chat-framework 2.0.0 → 3.0.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/README_ZH.md ADDED
@@ -0,0 +1,375 @@
1
+
2
+ # 🤖 我的AI聊天框架
3
+
4
+ 一个轻量级、模块化的AI聊天框架,内置插件系统、统一消息格式和工具调用支持。
5
+
6
+ > **⚠️ 注意**:本项目用于**学习目的**,由 **AI 生成**,不适用于生产环境。不保证向后兼容性,请谨慎使用。
7
+
8
+ ## ✨ 特性
9
+
10
+ - **轻量核心** – 仅约300行代码,易于理解和扩展。
11
+ - **插件系统** – 添加功能(工具调用、思维链、自定义适配器)无需修改核心。
12
+ - **统一消息格式** – 所有组件使用一致的数据结构。
13
+ - **多环境支持** – 构建为 ES 模块、UMD 和 CommonJS 格式,可用于浏览器和 Node.js。
14
+ - **无外部依赖** – 使用原生 `fetch`(Node 18+ 及现代浏览器)。
15
+ - **工具调用** – 内置插件处理 AI 的函数调用(自动多轮循环)。
16
+ - **流式传输** – 完整支持实时响应。
17
+ - **灵活配置** – 支持平铺和嵌套 `modelParams` 两种配置风格。
18
+ - **事件驱动** – 内置 EventEmitter,支持 `message`、`sending`、`error`、`stream-progress` 事件。
19
+ - **管道车间(v3.0)** – `chat.pipe()` 在任何阶段挂自定义功能(beforeSend/afterSend),核心代码不用改。
20
+ - **类型化错误** – `APIError`、`NetworkError`、`ConfigurationError`、`ParsingError`,方便分类处理。
21
+
22
+ ---
23
+
24
+ ## 🧱 架构概览
25
+
26
+ ```
27
+ src/
28
+ ├── index.js # 公共入口(统一导出)
29
+ ├── core/
30
+ │ ├── ChatService.js # 核心:配置、管道调度、发送/流式、插件托管
31
+ │ ├── Pipeline.js # 顺序管道(v3.0:小车 ctx 依次过车间)
32
+ │ ├── MessageStore.js # 内存消息列表(增删改查)
33
+ │ ├── SystemPromptStore.js # 可开关的 system 提示词
34
+ │ ├── EventEmitter.js # 极简发布/订阅(on/off/emit)
35
+ │ └── Errors.js # 自定义错误类
36
+ ├── adapters/
37
+ │ └── openai.js # OpenAI 兼容 API 适配器(协议见 assertAdapter)
38
+ ├── plugins/
39
+ │ ├── tool-calling.js # 工具调用插件(afterSend 车间,自动检测&循环执行)
40
+ │ └── model-registry.js # 模型能力表(beforeSend 车间)
41
+ └── utils/
42
+ ├── MessageFormatter.js # 消息格式转换(可注册)
43
+ ├── typeCheck.js # 类型判断工具
44
+ └── url.js # URL 拼接工具
45
+ tests/ # node:test 单元测试(core / pipeline / integration)
46
+ ```
47
+
48
+ **数据流**:
49
+
50
+ ```
51
+ 用户调用 chat.send(input)
52
+ → ChatService._request() → 小车 ctx 依次开过车间:
53
+ → prepareInput(加用户消息、合并配置、发出 'sending')
54
+ → beforeSend(内部钩子 + 用户 beforeSend 车间)
55
+ → autoContinue / buildRequest(→ 请求体)
56
+ → send(adapter.send / stream + 重试 + 占位消息)
57
+ → afterSend(用户车间 + tool-calling 循环)
58
+ → 返回结果,过程中发出 'message' 事件
59
+ ```
60
+
61
+ 使用 `toolCallingPlugin` 时,流程会循环:响应 → 检测 tool_calls → 执行工具 → 添加工具结果 → sendExisting → 重复(最多 5 轮)。
62
+
63
+ ---
64
+
65
+ ## 📦 安装
66
+
67
+ ```bash
68
+ npm install my-ai-chat-framework
69
+ ```
70
+
71
+ ---
72
+
73
+ ## 🚀 快速开始
74
+
75
+ ### 基础用法(平铺配置)
76
+
77
+ ```javascript
78
+ import { ChatService, openaiAdapter, toolCallingPlugin } from 'my-ai-chat-framework';
79
+
80
+ const chat = new ChatService({
81
+ apiKey: 'your-api-key',
82
+ baseUrl: 'https://api.deepseek.com', // 可选,默认 OpenAI
83
+ model: 'deepseek-chat',
84
+ temperature: 0.7,
85
+ maxTokens: 2000
86
+ });
87
+
88
+ chat.use(openaiAdapter);
89
+ chat.use(toolCallingPlugin);
90
+
91
+ chat.on('message', msg => console.log(msg.content));
92
+ await chat.send('你好!');
93
+ ```
94
+
95
+ ### 工厂模式(配置归属更清晰,推荐)
96
+
97
+ ```javascript
98
+ import { ChatService, createOpenAIAdapter, createToolCallingPlugin } from 'my-ai-chat-framework';
99
+
100
+ // 运输层 + 请求默认参数归适配器
101
+ const adapter = createOpenAIAdapter({
102
+ apiKey: 'your-api-key',
103
+ baseUrl: 'https://api.deepseek.com',
104
+ modelParams: { temperature: 0.8, maxTokens: 2000 }
105
+ });
106
+
107
+ const chat = new ChatService({
108
+ adapter, // 构造时注入(等价于 chat.use(adapter))
109
+ model: 'deepseek-chat',
110
+ system: '你是乐于助人的AI助手'
111
+ });
112
+
113
+ chat.use(createToolCallingPlugin({ timeout: 30000 })); // 插件自带配置
114
+
115
+ // 请求级覆盖:只对本次请求生效
116
+ await chat.send('你好', { temperature: 0.2 });
117
+ ```
118
+
119
+ ### 使用 `modelParams`(参数较多时推荐)
120
+
121
+ ```javascript
122
+ const chat = new ChatService({
123
+ apiKey: 'your-api-key',
124
+ baseUrl: 'https://api.deepseek.com',
125
+ model: 'deepseek-chat', // 顶层仍保留 model 方便
126
+ modelParams: { // 可选参数分组
127
+ temperature: 0.8,
128
+ maxTokens: 1500,
129
+ reasoningEffort: 'medium' // 用于 deepseek-reasoner
130
+ }
131
+ });
132
+ ```
133
+
134
+ ### 注册工具
135
+
136
+ ```javascript
137
+ chat.registerTool('get_weather', '获取指定城市的天气',
138
+ async (args) => {
139
+ // args = { city: '北京' }
140
+ return `${args.city}天气:22°C,晴`;
141
+ },
142
+ { // 参数 schema(可选但推荐)
143
+ city: { type: 'string', description: '城市名称', required: true }
144
+ }
145
+ );
146
+
147
+ await chat.send('北京今天天气怎么样?');
148
+ // → AI 调用 get_weather,框架执行,AI 返回天气信息
149
+ ```
150
+
151
+ ---
152
+
153
+ ## 🔌 插件与适配器
154
+
155
+ ### openaiAdapter
156
+
157
+ 将内部消息转换为 OpenAI 兼容格式。支持:
158
+ - `apiUrl` – 完整 URL(最高优先级)
159
+ - `baseUrl` + `path` – 基础域名 + API 路径
160
+ - 默认:`https://api.openai.com/v1/chat/completions`
161
+
162
+ | 配置项 | 类型 | 默认值 | 描述 |
163
+ |--------|------|--------|------|
164
+ | `apiKey` | string | **必填** | Authorization 请求头中的 Bearer token |
165
+ | `apiUrl` | string | – | 完整请求 URL(优先级高于 baseUrl+path) |
166
+ | `baseUrl` | string | `https://api.openai.com` | API 基础域名 |
167
+ | `path` | string | `/v1/chat/completions` | API 端点路径 |
168
+
169
+ ### toolCallingPlugin
170
+
171
+ 检测助手响应中的 `tool_calls`,执行已注册的工具,将结果回传并继续对话(最多 `maxIterations` = 5 轮)。
172
+
173
+ - `chat.registerTool(name, description, executor, parameters?)` – 注册工具
174
+ - 自动向对话中插入 `role: 'tool'` 消息
175
+ - 工具执行出错时优雅降级(记录日志,将错误信息返回给模型)
176
+ - 配置:`createToolCallingPlugin({ timeout: 30000, maxIterations: 5 })`
177
+
178
+ > ⚠️ **多实例注意**:`openaiAdapter` / `toolCallingPlugin` / `modelRegistryPlugin` 默认导出是模块级单例,装到多个 ChatService 实例会互相覆盖。多实例场景请用工厂:`createOpenAIAdapter(opts)` / `createToolCallingPlugin(opts)` / `createModelRegistryPlugin(opts)`。
179
+
180
+ ---
181
+
182
+ ## 📡 事件系统(EventEmitter)
183
+
184
+ `ChatService` 继承自 `EventEmitter`。通过 `chat.on(event, handler)` 订阅:
185
+
186
+ | 事件 | 数据 | 触发时机 |
187
+ |------|------|----------|
188
+ | `sending` | `{ addUser, userInput, timestamp }` | 每次请求发送前 |
189
+ | `message` | `{ role, content, ... }` | 收到完整助手消息 |
190
+ | `stream-progress` | chunk 对象 | 每个流式块到达时 |
191
+ | `error` | `{ error, timestamp }` | 请求过程中发生错误 |
192
+
193
+ ```javascript
194
+ chat.on('sending', ({ userInput }) => console.log('发送中:', userInput));
195
+ chat.on('message', msg => console.log('收到:', msg.content));
196
+ chat.on('error', ({ error }) => console.error('错误:', error.message));
197
+
198
+ // on() 返回取消订阅函数
199
+ const unsubscribe = chat.on('message', handler);
200
+ unsubscribe(); // 停止监听
201
+ ```
202
+
203
+ ---
204
+
205
+ ## 🧩 ChatService API
206
+
207
+ | 方法 | 返回值 | 描述 |
208
+ |------|--------|------|
209
+ | `chat.send(userInput)` | `Promise<Message>` | 发送消息,获取回复(非流式) |
210
+ | `chat.stream(userInput, onProgress, onDone)` | `Promise<Message>` | 发送消息,获取流式回复 |
211
+ | `chat.sendExisting()` | `Promise<Message>` | 用现有消息重新请求(不添加用户消息) |
212
+ | `chat.sendExistingStream(onProgress, onDone)` | `Promise<Message>` | 同上,流式 |
213
+ | `chat.use(plugin)` | `this` | 安装插件/适配器 |
214
+ | `chat.setAdapter(adapter)` | `void` | 手动设置适配器 |
215
+ | `chat.on(event, handler)` | `取消订阅函数` | 订阅事件 |
216
+ | `chat.registerTool(name, desc, fn, params?)` | `this` | 注册工具(需安装 toolCallingPlugin) |
217
+ | `chat.messages` | `MessageStore` | 直接访问消息存储 |
218
+
219
+ ---
220
+
221
+ ## 🗄️ MessageStore API
222
+
223
+ | 方法 | 描述 |
224
+ |------|------|
225
+ | `add(message)` | 添加原始消息对象 |
226
+ | `addUser(content, meta?)` | 添加用户消息 |
227
+ | `addAssistant(content, meta?)` | 添加助手消息 |
228
+ | `addSystem(content, meta?)` | 添加系统消息 |
229
+ | `addTool(content, toolCallId, meta?)` | 添加工具结果消息 |
230
+ | `addOnceAssistant(content, options?)` | 添加一次性引导消息(默认 `_ephemeral: true` + `prefix: true`;options: `{ reasoningContent, prefix }`) |
231
+ | `getAll()` | 返回所有消息的浅拷贝 |
232
+ | `getLast()` | 返回最后一条消息(或 null) |
233
+ | `clear()` | 清空所有消息 |
234
+ | `undoToLastAssistant()` | 移除最后一条助手消息之后的所有消息 |
235
+ | `undoToPreviousUser(id)` | 撤回到指定消息之前的最近 user,删除该 user 之后到该消息(含)之间的消息(适用于重发) |
236
+
237
+ **消息格式**:
238
+
239
+ ```javascript
240
+ {
241
+ id: string, // 未提供时自动生成
242
+ role: 'user' | 'assistant' | 'system' | 'tool',
243
+ content: string,
244
+ toolCalls?: Array, // 助手消息中的工具调用
245
+ toolCallId?: string, // 工具消息
246
+ reasoningContent?: string, // deepseek-reasoner 思维链
247
+ timestamp?: number,
248
+ metadata?: any
249
+ }
250
+ ```
251
+
252
+ ---
253
+
254
+ ## ❌ 错误处理
255
+
256
+ 框架针对不同失败场景抛出类型化错误:
257
+
258
+ | 错误类 | `.name` | 触发场景 |
259
+ |--------|---------|----------|
260
+ | `APIError` | `'APIError'` | 非 2xx HTTP 响应(401, 429, 500 等) |
261
+ | `NetworkError` | `'NetworkError'` | `fetch` 失败、连接超时等 |
262
+ | `ConfigurationError` | `'ConfigurationError'` | 缺少必要配置项 |
263
+ | `ParsingError` | `'ParsingError'` | API 响应格式异常 |
264
+
265
+ ```javascript
266
+ import { APIError, NetworkError, ConfigurationError, ParsingError } from 'my-ai-chat-framework';
267
+
268
+ try {
269
+ await chat.send('你好');
270
+ } catch (error) {
271
+ if (error instanceof APIError) {
272
+ console.error(`API 错误 ${error.statusCode}: ${error.message}`);
273
+ } else if (error instanceof NetworkError) {
274
+ console.error('网络问题:', error.message);
275
+ }
276
+ }
277
+ ```
278
+
279
+ ---
280
+
281
+ ## 📚 配置参考
282
+
283
+ `new ChatService(config)` 可接受以下选项:
284
+
285
+ | 选项 | 类型 | 默认值 | 描述 |
286
+ |------|------|--------|------|
287
+ | `apiKey` | string | **必填** | API 密钥 |
288
+ | `baseUrl` | string | `'https://api.openai.com'` | API 基础域名(与 `path` 配合) |
289
+ | `path` | string | `'/v1/chat/completions'` | API 路径(与 `baseUrl` 配合) |
290
+ | `apiUrl` | string | – | 完整 API URL(优先级高于 `baseUrl`+`path`) |
291
+ | `model` | string | **必填** | 模型名称(如 `deepseek-chat`) |
292
+ | `modelParams` | object | `{}` | 分组模型参数(见下) |
293
+ | `temperature` | number | `0.7` | 温度(0–2) |
294
+ | `maxTokens` | number | `2000` | 最大生成 token 数 |
295
+ | `reasoningEffort` | string | – | 仅 `deepseek-reasoner`:`'low'`、`'medium'`、`'high'` |
296
+
297
+ > 平铺和 `modelParams` 两种风格都支持。`modelParams` 中的值优先级高于顶层同名属性。
298
+
299
+ ---
300
+
301
+ ## 🧪 测试
302
+
303
+ ```bash
304
+ # 1. 创建 .env 文件,写入 API key
305
+ echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env
306
+
307
+ # 2. 运行测试
308
+ npm test
309
+ ```
310
+
311
+ 单元测试:`npm test`(`tests/core.test.js` + `tests/pipeline.test.js`,无需 API Key)。集成测试:`npm run test:integration`(`tests/integration.test.js`,需要 .env 中的 `DEEPSEEK_API_KEY`)。
312
+
313
+ ---
314
+
315
+ ## 🛠️ 开发
316
+
317
+ ```bash
318
+ git clone https://github.com/your-username/my-ai-chat-framework.git
319
+ cd my-ai-chat-framework
320
+ npm install
321
+
322
+ # 开发模式(监听文件变化)
323
+ npm run dev
324
+
325
+ ## 🧩 扩展:管道车间(v3.0)
326
+
327
+ 核心流程是一辆小车(ctx)依次开过车间;加新功能 = 注册一个车间,核心代码不用改。
328
+
329
+ ```javascript
330
+ // 发送前注入角色卡
331
+ chat.pipe({
332
+ name: '角色卡注入',
333
+ phase: 'beforeSend', // 默认
334
+ async run(ctx) {
335
+ ctx.messages.add({ role: 'system', content: '你是傲娇霸总' });
336
+ }
337
+ });
338
+
339
+ // 发送后改写回复
340
+ chat.pipe({
341
+ name: '加时间戳',
342
+ phase: 'afterSend',
343
+ run(ctx) { if (ctx.result?.content) ctx.result.content += ' — ' + Date.now(); }
344
+ });
345
+
346
+ chat.unpipe('角色卡注入'); // 移除
347
+ console.log(chat.pipelineStages); // 查看车间顺序
348
+ ```
349
+
350
+ **写一个 adapter(同级服务商)**:实现 4 个方法即可,`assertAdapter` 会帮你在装错时点名哪个方法缺失:
351
+
352
+ ```javascript
353
+ const myAdapter = {
354
+ buildRequest(messages, config) { /* 内部消息 → 请求体 */ },
355
+ async send(body, config, opts) { /* 非流式 → 原始响应 */ },
356
+ async stream(body, config, onProgress, onDone, opts) { /* 流式 */ },
357
+ parseResponse(resp) { /* 原始响应 → 内部消息格式 */ },
358
+ };
359
+ chat.setAdapter(myAdapter);
360
+ ```
361
+
362
+ ---
363
+
364
+ # 构建库(ES + UMD + CJS)
365
+ npm run build
366
+
367
+ # 运行测试
368
+ npm test
369
+ ```
370
+
371
+ ---
372
+
373
+ ## 📄 许可证
374
+
375
+ MIT