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/CHANGELOG.md ADDED
@@ -0,0 +1,65 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [3.0.0] - 2026-08-?
6
+
7
+ ### Added
8
+ - **公开管道 API**:`chat.pipe({ name, phase?, run(ctx) })` / `chat.unpipe(name)` / `chat.pipelineStages`
9
+ - phase `beforeSend`(默认):内部 beforeRequest 之后、构建请求体之前
10
+ - phase `afterSend`:发送完成、结果落位之后(可改 ctx.result)
11
+ - 不注册任何车间 = 行为与 2.8.x 完全一致
12
+ - **协议固化**:`assertAdapter` 校验 buildRequest/send/stream/parseResponse,缺方法抛 ConfigurationError 并列出方法名;JSDoc @typedef ChatAdapter / PipelineContext / PipeStage
13
+ - **插件车间化**:tool-calling 经 afterSend 车间、model-registry 经 beforeSend 车间,不再占用 `_processResponse` 单座槽位
14
+ - 新增测试 `tests/pipeline.test.js`(8 例:pipe 顺序/冲突/unpipe/改ctx/SSE 冒烟/工具循环端到端)
15
+
16
+ ### Changed
17
+ - `ChatService._request` 固定线拆为内部管道:prepareInput → beforeSend → autoContinue → buildRequest → send(行为等价)
18
+ - `_hooks` beforeRequest 保留为兼容桥;`_processResponse` 保留为旧通道(新代码请用 afterSend 车间)
19
+ - 版本号 2.8.0 → 3.0.0(新增公共 API 面)
20
+
21
+ ### Known delta(流式 tool-calling 时序)
22
+ - 流式工具调用时,初始回复的 `message` 事件在工具循环前发出(旧版循环后发)。非流式时序与旧版一致。
23
+
24
+ ## [2.8.0] - 2026-08-08
25
+
26
+ ### Added
27
+ - **配置归属系统**:配置按层归属(运输/请求/会话/插件),谁消费谁声明
28
+ - **工厂函数**:`createOpenAIAdapter(opts)` / `createToolCallingPlugin(opts)` / `createModelRegistryPlugin(opts)`——每个实例自持状态,规避模块级单例互踩陷阱
29
+ - **请求级参数覆盖**:`chat.send(input, params)` / `chat.stream(input, params, onProgress, onDone)` / `sendExisting(params)` / `sendExistingStream(params, ...)`,合并链 = 适配器默认 ← 会话配置 ← 本次覆盖(近者优先)
30
+ - **capabilities 按生效 model 解析**:model-registry 在 beforeRequest 钩子里按"本次请求的 model"(含请求级覆盖)查表
31
+ - `chat.use(plugin, options)`:options 透传 `plugin.install(chat, options)`
32
+ - `new ChatService({ adapter })`:构造时直接注入适配器
33
+ - 工具定义移入插件实例(不再写 `chat.config.tools`),工具超时改走 `createToolCallingPlugin({ timeout })`
34
+
35
+ ### Changed
36
+ - `updateConfig` 收窄为会话层字段(model/temperature/maxTokens/modelParams/system/retry/ephemeralContinue),非白名单或非法值抛 `ConfigurationError`(修复绕过校验问题)
37
+ - openaiAdapter 的 transport(apiKey/baseUrl/headers)从实例自持,单例 `openaiAdapter` 保留为平铺兼容路径
38
+ - `buildRequest(messages, config, systemPrompts)` 的 config 现为合并后的请求参数
39
+
40
+ ### Fixed
41
+ - TODO.md 高危 #1:插件共享可变状态——工厂化后实例隔离(单例保留为兼容路径)
42
+ - TODO.md 高危 #3:updateConfig 绕过校验——现在白名单 + 校验
43
+
44
+ ## [2.7.0] - 2026-05-25
45
+
46
+ ### Added
47
+ - **Ephemeral 临时消息**:_ephemeral: true 标记,仅底部连续时参与请求,其余自动过滤
48
+ - **续写转正**:continueLast / continueLastStream 续写 ephemeral 消息后自动转正(delete _ephemeral)
49
+ - **EventEmitter.emitAsync**:异步触发事件,支持 beforeRequest 等钩子
50
+ - **beforeRequest 钩子**:chat._hooks.on('beforeRequest', ...) 在 buildRequest 前修改 messages
51
+ - **_processResponse 钩子**:tool-calling 改为注册此钩子,不再覆盖 send/stream
52
+
53
+ ### Changed
54
+ - MessageFormatter 过滤 ephemeral:识别底部连续段,中间 ephemeral 自动跳过
55
+ - ChatService._request 拆为 4 个私有方法(_addUserMessage / _withRetry / _stream / _handleResult)
56
+ - ool-calling.js 改为注册 _processResponse,不再替换 send/stream
57
+ - dapter.stream 不再接收/操作 streamEntry,_complete 由 ChatService 管理
58
+ - openaiAdapter.stream 约减 20 行(删除所有 entry 相关代码)
59
+
60
+ ### Fixed
61
+ - retry 循环重复 push 占位消息(已修复:提到循环外)
62
+ - continueLastStream 被续写消息丢失(已修复:独立占位,ChatService 管理)
63
+ - prefix 续写空 content 被过滤(已修复:MessageFormatter 保留 prefix)
64
+
65
+ ## [2.6.0] - 2026-05-23
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 My Name
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2024 My Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,19 +1,66 @@
1
1
 
2
- **⚠️ Note**: This project is created for **learning purposes** and is **AI‑generated**. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.
3
-
4
2
  # 🤖 My AI Chat Framework
5
3
 
6
4
  A lightweight, modular AI chat framework with plugin system, unified message format, and tool calling support.
7
5
 
6
+ > **⚠️ Note**: This project is created for **learning purposes** and is **AI‑generated**. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.
7
+
8
8
  ## ✨ Features
9
9
 
10
- - **Lightweight Core** – Only ~300 lines, easy to understand and extend.
11
- - **Plugin System** – Add features like tool calling, reasoning, or custom adapters without touching the core.
10
+ - **Lightweight Core** – ~300 lines, easy to understand and extend.
11
+ - **Plugin System** – Add features (tool calling, reasoning, custom adapters) without touching the core.
12
12
  - **Unified Message Format** – Consistent data structure across all components.
13
- - **Multi‑Environment** – Builds ES module, UMD, and CommonJS formats for browser and Node.js.
13
+ - **Multi‑Environment** – Builds ES module, UMD, and CommonJS for browser & Node.js.
14
14
  - **No External Dependencies** – Uses native `fetch` (Node 18+ & modern browsers).
15
15
  - **Tool Calling** – Built‑in plugin to handle function calls from AI models.
16
16
  - **Streaming** – Full support for real‑time responses.
17
+ - **Flexible Configuration** – Supports both flat and nested `modelParams` structure.
18
+ - **Event‑Driven** – Built‑in EventEmitter for `message`, `sending`, `error`, `stream-progress` events.
19
+ - **Pipeline Stages (v3.0)** – `chat.pipe()` to hook custom logic at `beforeSend` / `afterSend` without touching the core.
20
+ - **Custom Error Classes** – `APIError`, `NetworkError`, `ConfigurationError`, `ParsingError` for fine‑grained error handling.
21
+
22
+ ---
23
+
24
+ ## 🧱 Architecture
25
+
26
+ ```
27
+ src/
28
+ ├── index.js # Public entry point (re-exports)
29
+ ├── core/
30
+ │ ├── ChatService.js # Main service: config, pipeline, send/stream, plugin hosting
31
+ │ ├── Pipeline.js # Sequential pipeline (v3.0: ctx flows through stages)
32
+ │ ├── MessageStore.js # In-memory message list with CRUD helpers
33
+ │ ├── SystemPromptStore.js # Toggleable system prompts
34
+ │ ├── EventEmitter.js # Minimal pub/sub (on/off/emit)
35
+ │ └── Errors.js # Custom error classes
36
+ ├── adapters/
37
+ │ └── openai.js # OpenAI‑compatible API adapter (protocol via assertAdapter)
38
+ ├── plugins/
39
+ │ ├── tool-calling.js # Tool calling plugin (afterSend stage, auto‑detect & loop)
40
+ │ └── model-registry.js # Model capability table (beforeSend stage)
41
+ └── utils/
42
+ ├── MessageFormatter.js # Message format conversion (registrable)
43
+ ├── typeCheck.js # Type checking helpers
44
+ └── url.js # URL joining utility
45
+ tests/ # node:test suites (core / pipeline / integration)
46
+ ```
47
+
48
+ **Data flow**:
49
+
50
+ ```
51
+ User calls chat.send(input)
52
+ → ChatService._request() → ctx flows through stages:
53
+ → prepareInput (add user msg, merge config, emit 'sending')
54
+ → beforeSend (internal hook + user beforeSend stages)
55
+ → autoContinue / buildRequest (→ request body)
56
+ → send (adapter.send / stream + retry + placeholder)
57
+ → afterSend (user stages + tool-calling loop)
58
+ → returns result, emitting 'message' along the way
59
+ ```
60
+
61
+ With `toolCallingPlugin`, the flow loops: response → detect tool_calls → execute tools → add tool results → sendExisting → repeat (max 5 iterations).
62
+
63
+ ---
17
64
 
18
65
  ## 📦 Installation
19
66
 
@@ -21,72 +68,303 @@ A lightweight, modular AI chat framework with plugin system, unified message for
21
68
  npm install my-ai-chat-framework
22
69
  ```
23
70
 
71
+ ---
72
+
24
73
  ## 🚀 Quick Start
25
74
 
75
+ ### Basic Usage (Flat Configuration)
76
+
26
77
  ```javascript
27
78
  import { ChatService, openaiAdapter, toolCallingPlugin } from 'my-ai-chat-framework';
28
79
 
29
80
  const chat = new ChatService({
30
81
  apiKey: 'your-api-key',
82
+ baseUrl: 'https://api.deepseek.com', // optional, defaults to OpenAI
31
83
  model: 'deepseek-chat',
32
- apiUrl: 'https://api.deepseek.com/v1/chat/completions'
84
+ temperature: 0.7,
85
+ maxTokens: 2000
33
86
  });
34
87
 
35
88
  chat.use(openaiAdapter);
36
89
  chat.use(toolCallingPlugin);
37
90
 
38
- chat.registerTool('greet', 'Say hello', async (args) => {
39
- return `Hello, ${args.name}!`;
91
+ chat.on('message', msg => console.log(msg.content));
92
+ await chat.send('Hello!');
93
+ ```
94
+
95
+ ### Factory Mode (Owned Configuration, Recommended)
96
+
97
+ ```javascript
98
+ import { ChatService, createOpenAIAdapter, createToolCallingPlugin } from 'my-ai-chat-framework';
99
+
100
+ // transport + request defaults belong to the adapter
101
+ const adapter = createOpenAIAdapter({
102
+ apiKey: 'your-api-key',
103
+ baseUrl: 'https://api.deepseek.com',
104
+ modelParams: { temperature: 0.8, maxTokens: 2000 }
40
105
  });
41
106
 
42
- chat.on('message', msg => console.log(msg.content));
107
+ const chat = new ChatService({
108
+ adapter, // injected at construction (same as chat.use(adapter))
109
+ model: 'deepseek-chat',
110
+ system: 'You are a helpful assistant'
111
+ });
112
+
113
+ chat.use(createToolCallingPlugin({ timeout: 30000 })); // plugin options
114
+
115
+ // per-request override (this request only)
116
+ await chat.send('Hello', { temperature: 0.2 });
117
+ ```
118
+
119
+ > ⚠️ The default exports `openaiAdapter` / `toolCallingPlugin` / `modelRegistryPlugin` are module-level singletons — installing one on multiple `ChatService` instances will clobber shared state. Use the factories (`createXxx`) for multiple instances.
120
+
121
+ ### Using `modelParams` (Recommended for Many Parameters)
122
+
123
+ ```javascript
124
+ const chat = new ChatService({
125
+ apiKey: 'your-api-key',
126
+ baseUrl: 'https://api.deepseek.com',
127
+ model: 'deepseek-chat', // still at top level for convenience
128
+ modelParams: { // optional parameters grouped
129
+ temperature: 0.8,
130
+ maxTokens: 1500,
131
+ reasoningEffort: 'medium' // for deepseek-reasoner
132
+ }
133
+ });
134
+ ```
43
135
 
44
- await chat.send('Please greet Alice.');
136
+ ### Registering a Tool
137
+
138
+ ```javascript
139
+ chat.registerTool('get_weather', 'Get current weather for a city',
140
+ async (args) => {
141
+ // args = { city: 'Beijing' }
142
+ return `Weather in ${args.city}: 22°C, sunny`;
143
+ },
144
+ { // parameter schema (optional but recommended)
145
+ city: { type: 'string', description: 'City name', required: true }
146
+ }
147
+ );
148
+
149
+ await chat.send('What\'s the weather in Beijing?');
150
+ // → AI calls get_weather, framework executes it, AI responds with weather info
45
151
  ```
46
152
 
153
+ ---
154
+
47
155
  ## 🔌 Plugins & Adapters
48
156
 
49
- - **openaiAdapter** – Converts internal messages to OpenAI-compatible format.
50
- - **toolCallingPlugin** – Detects `tool_calls` in responses, executes tools, and continues the conversation.
157
+ ### openaiAdapter
158
+
159
+ Converts internal messages to OpenAI‑compatible format. Supports:
160
+ - `apiUrl` – full URL (highest priority)
161
+ - `baseUrl` + `path` – base domain + API path
162
+ - Defaults to `https://api.openai.com/v1/chat/completions`
163
+
164
+ | Config field | Type | Default | Description |
165
+ |-------------|------|---------|-------------|
166
+ | `apiKey` | string | **required** | Bearer token for Authorization header |
167
+ | `apiUrl` | string | – | Full request URL (overrides baseUrl+path) |
168
+ | `baseUrl` | string | `https://api.openai.com` | API base domain |
169
+ | `path` | string | `/v1/chat/completions` | API endpoint path |
170
+
171
+ ### toolCallingPlugin
172
+
173
+ Detects `tool_calls` in assistant responses, executes registered tools, feeds results back, and continues the conversation (up to `maxIterations` = 5).
174
+
175
+ - `chat.registerTool(name, description, executor, parameters?)` – register a tool
176
+ - Automatically injects `tool` role messages into the conversation
177
+ - Recovers from tool execution errors gracefully (logs error, returns error message to model)
178
+
179
+ ---
180
+
181
+ ## 🔧 Extending with Pipeline (v3.0)
182
+
183
+ ```javascript
184
+ // Inject a role card before every request
185
+ chat.pipe({ name: 'role-card', phase: 'beforeSend', async run(ctx) {
186
+ ctx.messages.add({ role: 'system', content: 'You are a tsundere CEO.' });
187
+ }});
188
+
189
+ // Post-process the reply
190
+ chat.pipe({ name: 'stamp', phase: 'afterSend', run(ctx) {
191
+ if (ctx.result?.content) ctx.result.content += ' — ' + Date.now();
192
+ }});
193
+
194
+ chat.unpipe('role-card'); // remove
195
+ console.log(chat.pipelineStages); // inspect stage order
196
+ ```
197
+
198
+ See `docs/DEVELOPER.md` \u201cWrite a Stage\u201d for the full guide.
199
+
200
+ ## 📡 Events (EventEmitter)
201
+
202
+ `ChatService` extends `EventEmitter`. Subscribe with `chat.on(event, handler)`:
203
+
204
+ | Event | Payload | When |
205
+ |-------|---------|------|
206
+ | `sending` | `{ addUser, userInput, timestamp }` | Before each request |
207
+ | `message` | `{ role, content, ... }` | Full assistant message received |
208
+ | `stream-progress` | chunk object | Each streaming chunk arrives |
209
+ | `error` | `{ error, timestamp }` | Any error during request |
210
+
211
+ ```javascript
212
+ chat.on('sending', ({ userInput }) => console.log('Sending:', userInput));
213
+ chat.on('message', msg => console.log('Got:', msg.content));
214
+ chat.on('error', ({ error }) => console.error('Error:', error.message));
215
+
216
+ // on() returns an unsubscribe function
217
+ const unsubscribe = chat.on('message', handler);
218
+ unsubscribe(); // stop listening
219
+ ```
220
+
221
+ ---
222
+
223
+ ## 🧩 ChatService API
224
+
225
+ | Method | Returns | Description |
226
+ |--------|---------|-------------|
227
+ | `chat.send(userInput)` | `Promise<Message>` | Send a message, get reply (non‑streaming) |
228
+ | `chat.stream(userInput, onProgress, onDone)` | `Promise<Message>` | Send a message, get streaming reply |
229
+ | `chat.sendExisting()` | `Promise<Message>` | Re‑send current messages without adding user input |
230
+ | `chat.sendExistingStream(onProgress, onDone)` | `Promise<Message>` | Same as above, streaming |
231
+ | `chat.use(plugin)` | `this` | Install a plugin/adapter |
232
+ | `chat.setAdapter(adapter)` | `void` | Manually set the adapter |
233
+ | `chat.on(event, handler)` | `unsubscribe function` | Subscribe to events |
234
+ | `chat.registerTool(name, desc, fn, params?)` | `this` | Register a tool (requires toolCallingPlugin) |
235
+ | `chat.messages` | `MessageStore` | Access the message store directly |
236
+
237
+ ---
238
+
239
+ ## 🗄️ MessageStore API
240
+
241
+ | Method | Description |
242
+ |--------|-------------|
243
+ | `add(message)` | Add a raw message object |
244
+ | `addUser(content, meta?)` | Add a user message |
245
+ | `addAssistant(content, meta?)` | Add an assistant message |
246
+ | `addSystem(content, meta?)` | Add a system message |
247
+ | `addTool(content, toolCallId, meta?)` | Add a tool result message |
248
+ | `addOnceAssistant(content, options?)` | Add a one-shot assistant guide message (defaults to `_ephemeral: true` + `prefix: true`; options: `{ reasoningContent, prefix }`) |
249
+ | `getAll()` | Return a shallow copy of all messages |
250
+ | `getLast()` | Return the last message (or null) |
251
+ | `clear()` | Remove all messages |
252
+ | `undoToLastAssistant()` | Remove messages after the last assistant message |
253
+ | `undoToPreviousUser(id)` | Remove messages from the message with `id` (inclusive) back to, but excluding, the previous user message — useful before resending |
254
+
255
+ **Message format**:
256
+
257
+ ```javascript
258
+ {
259
+ id: string, // auto‑generated if not provided
260
+ role: 'user' | 'assistant' | 'system' | 'tool',
261
+ content: string,
262
+ toolCalls?: Array, // assistant messages with tool calls
263
+ toolCallId?: string, // tool messages
264
+ reasoningContent?: string, // deepseek-reasoner
265
+ timestamp?: number,
266
+ metadata?: any
267
+ }
268
+ ```
269
+
270
+ ---
271
+
272
+ ## ❌ Error Handling
51
273
 
52
- You can easily create your own adapter for other APIs (Anthropic, Cohere, etc.) or plugins for logging, caching, etc.
274
+ The framework throws typed errors for different failure modes:
53
275
 
54
- ## 📚 API
276
+ | Error Class | `.name` | When |
277
+ |------------|---------|------|
278
+ | `APIError` | `'APIError'` | Non‑2xx HTTP responses (401, 429, 500, etc.) |
279
+ | `NetworkError` | `'NetworkError'` | `fetch` failures, connection timeouts |
280
+ | `ConfigurationError` | `'ConfigurationError'` | Missing required config |
281
+ | `ParsingError` | `'ParsingError'` | Malformed API response |
55
282
 
56
- ### ChatService
283
+ ```javascript
284
+ import { APIError, NetworkError, ConfigurationError, ParsingError } from 'my-ai-chat-framework';
285
+
286
+ try {
287
+ await chat.send('Hello');
288
+ } catch (error) {
289
+ if (error instanceof APIError) {
290
+ console.error(`API ${error.statusCode}: ${error.message}`);
291
+ } else if (error instanceof NetworkError) {
292
+ console.error('Network issue:', error.message);
293
+ }
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## 📚 Configuration Reference
300
+
301
+ `new ChatService(config)` accepts:
302
+
303
+ | Option | Type | Default | Description |
304
+ |--------|------|---------|-------------|
305
+ | `apiKey` | string | **required** | Your API key |
306
+ | `baseUrl` | string | `'https://api.openai.com'` | API base URL (used with `path`) |
307
+ | `path` | string | `'/v1/chat/completions'` | API path (used with `baseUrl`) |
308
+ | `apiUrl` | string | – | Full API URL (overrides `baseUrl`+`path`) |
309
+ | `model` | string | **required** | Model name (e.g., `deepseek-chat`) |
310
+ | `modelParams` | object | `{}` | Grouped model parameters (see below) |
311
+ | `temperature` | number | `0.7` | Sampling temperature (0–2) |
312
+ | `maxTokens` | number | `2000` | Max tokens to generate |
313
+ | `reasoningEffort` | string | – | For `deepseek-reasoner`: `'low'`, `'medium'`, `'high'` |
314
+
315
+ > Both flat and `modelParams` styles work. `modelParams` takes precedence over top‑level values.
316
+
317
+ ### modelParams
318
+
319
+ `modelParams` groups all model-related parameters. Supported fields:
320
+
321
+ | Field | Type | Default | Description |
322
+ |-------|------|---------|-------------|
323
+ | `model` | string | – | Model name (overrides top-level `model`) |
324
+ | `temperature` | number | `0.7` | Sampling temperature (0–2) |
325
+ | `maxTokens` | number | `2000` | Max tokens to generate |
326
+ | `reasoningEffort` | string | – | For `deepseek-reasoner`: `'low'`, `'medium'`, `'high'` |
327
+ | `topP` | number | – | Nucleus sampling (0–1) |
328
+ | `frequencyPenalty` | number | – | Penalize frequent tokens (-2.0–2.0) |
329
+ | `presencePenalty` | number | – | Penalize repeated tokens (-2.0–2.0) |
330
+ | `stop` | string \| string[] | – | Stop sequences |
331
+ | `responseFormat` | object | – | e.g., `{ type: 'json_object' }` |
332
+ | `seed` | number | – | For reproducible results |
333
+
334
+ **Naming convention**: CamelCase fields (`maxTokens`, `topP`) are managed by the adapter (converted to API format). Any unknown field is passed through as‑is—use the provider's native naming (e.g., `snake_case` for OpenAI).
57
335
 
58
- ```typescript
59
- new ChatService(config: {
60
- apiKey: string;
61
- model?: string;
62
- apiUrl?: string;
63
- temperature?: number;
64
- maxTokens?: number;
65
- })
336
+ ```javascript
337
+ const chat = new ChatService({
338
+ apiKey: 'your-api-key',
339
+ baseUrl: 'https://api.deepseek.com',
340
+ model: 'deepseek-chat',
341
+ modelParams: {
342
+ temperature: 0.8,
343
+ maxTokens: 1500,
344
+ topP: 0.9,
345
+ frequencyPenalty: 0.5,
346
+ // unknown fields pass through directly (use API's native names):
347
+ logprobs: true,
348
+ top_logprobs: 5
349
+ }
350
+ });
66
351
  ```
67
352
 
68
- - `use(plugin)` – Load a plugin.
69
- - `send(message)` – Send a message and wait for the complete response.
70
- - `stream(message, onProgress, onDone)` – Stream the response.
71
- - `registerTool(name, description, executor)` – Register a tool (only available after loading `toolCallingPlugin`).
353
+ ---
72
354
 
73
- ### MessageStore
355
+ ## 🧪 Testing
74
356
 
75
- - `add(message)` – Add a message (unified format).
76
- - `addUser(content, metadata)` Convenience method.
77
- - `addAssistant(content, metadata)`
78
- - `addSystem(content, metadata)`
79
- - `addTool(content, toolCallId, metadata)`
80
- - `getAll()` – Get all messages.
81
- - `undoToLastAssistant()` – Rollback to the last assistant message.
357
+ ```bash
358
+ # 1. Create a .env file with your API key
359
+ echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env
82
360
 
83
- ### EventEmitter
361
+ # 2. Run the test
362
+ npm test
363
+ ```
84
364
 
85
- - `on(event, handler)` Subscribe to events.
86
- - `off(event, handler)` – Unsubscribe.
87
- - `emit(event, data)` – Emit an event.
365
+ Unit tests: `npm test` (`tests/core.test.js` + `tests/pipeline.test.js`, no API key needed). Integration test: `npm run test:integration` (`tests/integration.test.js`, requires `DEEPSEEK_API_KEY` in `.env`).
88
366
 
89
- Events: `sending`, `message`, `stream-progress`, `error`, etc.
367
+ ---
90
368
 
91
369
  ## 🛠️ Development
92
370
 
@@ -94,14 +372,20 @@ Events: `sending`, `message`, `stream-progress`, `error`, etc.
94
372
  git clone https://github.com/your-username/my-ai-chat-framework.git
95
373
  cd my-ai-chat-framework
96
374
  npm install
97
- npm run build # build the library
98
- npm test # run the test script
375
+
376
+ # Dev mode (watching)
377
+ npm run dev
378
+
379
+ # Build the library (ES + UMD + CJS)
380
+ npm run build
381
+
382
+ # Run tests
383
+ npm test
99
384
  ```
100
385
 
386
+ ---
387
+
101
388
  ## 📄 License
102
389
 
103
390
  MIT
104
391
 
105
- ## 🤝 Contributing
106
-
107
- Contributions are welcome! Please open an issue or submit a pull request.