@easbot/ollama-sdk 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 CHANGED
@@ -5,11 +5,13 @@ AI SDK v2 兼容的 Ollama Provider,自动生成完整的事件流。
5
5
  ## 特性
6
6
 
7
7
  - ✅ 完整的 AI SDK v2 兼容性
8
- - ✅ 自动生成缺失的事件流事件
9
- - ✅ 基于 `ai-sdk-ollama` 的轻量封装
8
+ - ✅ 工具调用支持(Tool Calling)
9
+ - ✅ 自动生成完整的事件流
10
+ - ✅ 原生 Ollama 实现,无需额外依赖
10
11
  - ✅ TypeScript 类型支持
11
12
  - ✅ 流式和非流式生成
12
13
  - ✅ 完整的错误处理
14
+ - ✅ <think> 标签推理支持
13
15
 
14
16
  ## 安装
15
17
 
@@ -22,17 +24,17 @@ pnpm add @easbot/ollama-sdk ai
22
24
  ### 基本用法
23
25
 
24
26
  ```typescript
25
- import { createOllama } from '@easbot/ollama-sdk';
27
+ import { NativeOllamaLanguageModel } from '@easbot/ollama-sdk';
26
28
  import { streamText } from 'ai';
27
29
 
28
- // 创建 Ollama provider
29
- const ollama = createOllama({
30
+ // 创建 Ollama 模型
31
+ const model = new NativeOllamaLanguageModel('qwen2.5:0.5b', {
30
32
  baseURL: 'http://localhost:11434', // 可选,默认值
31
33
  });
32
34
 
33
35
  // 流式生成
34
36
  const result = await streamText({
35
- model: ollama('llama2'),
37
+ model,
36
38
  prompt: 'Hello, world!',
37
39
  });
38
40
 
@@ -41,96 +43,407 @@ for await (const chunk of result.textStream) {
41
43
  }
42
44
  ```
43
45
 
46
+ ### 工具调用(Tool Calling)
47
+
48
+ ```typescript
49
+ import { NativeOllamaLanguageModel } from '@easbot/ollama-sdk';
50
+ import { generateText } from 'ai';
51
+
52
+ const model = new NativeOllamaLanguageModel('qwen2.5:0.5b');
53
+
54
+ // 定义工具
55
+ const tools = {
56
+ read_file: {
57
+ description: '读取文件内容',
58
+ parameters: {
59
+ type: 'object',
60
+ properties: {
61
+ path: {
62
+ type: 'string',
63
+ description: '文件路径',
64
+ },
65
+ },
66
+ required: ['path'],
67
+ },
68
+ execute: async ({ path }: { path: string }) => {
69
+ const fs = await import('fs/promises');
70
+ return await fs.readFile(path, 'utf-8');
71
+ },
72
+ },
73
+ };
74
+
75
+ // 使用工具调用
76
+ const result = await generateText({
77
+ model,
78
+ tools,
79
+ prompt: '请读取 package.json 文件的内容',
80
+ });
81
+
82
+ console.log(result.text);
83
+ ```
84
+
44
85
  ### 非流式生成
45
86
 
46
87
  ```typescript
47
88
  import { generateText } from 'ai';
48
89
 
90
+ const model = new NativeOllamaLanguageModel('qwen2.5:0.5b');
91
+
49
92
  const result = await generateText({
50
- model: ollama('llama2'),
93
+ model,
51
94
  prompt: 'What is the capital of France?',
52
95
  });
53
96
 
54
97
  console.log(result.text);
55
98
  ```
56
99
 
100
+ ## 系统要求
101
+
102
+ ### Ollama 版本要求
103
+
104
+ - **工具调用功能**: 需要 Ollama 0.1.26 或更高版本
105
+ - **基本文本生成**: 支持所有 Ollama 版本
106
+
107
+ ### 支持的模型
108
+
109
+ 工具调用功能需要使用支持工具调用的模型,推荐:
110
+
111
+ - `qwen2.5:0.5b` - 轻量级,适合开发测试
112
+ - `qwen2.5:7b` - 平衡性能和质量
113
+ - `llama3.1:8b` - 高质量工具调用
114
+ - `mistral:7b` - 通用模型
115
+
116
+ 检查模型是否支持工具调用:
117
+
118
+ ```bash
119
+ ollama show <model-name>
120
+ ```
121
+
122
+ 查看模型信息中的 `tools` 字段。
123
+
57
124
  ### 多种调用方式
58
125
 
59
126
  ```typescript
60
- // 方式 1: 直接调用 provider
61
- const model1 = ollama('llama2');
127
+ // 方式 1: 直接创建模型实例
128
+ const model1 = new NativeOllamaLanguageModel('qwen2.5:0.5b');
129
+
130
+ // 方式 2: 自定义 baseURL
131
+ const model2 = new NativeOllamaLanguageModel('qwen2.5:0.5b', {
132
+ baseURL: 'http://192.168.1.100:11434',
133
+ });
134
+
135
+ // 方式 3: 使用不同的模型
136
+ const model3 = new NativeOllamaLanguageModel('llama3.1:8b');
137
+ ```
138
+
139
+ ## 工具调用详解
140
+
141
+ ### 工具定义格式
142
+
143
+ 工具定义遵循 AI SDK v2 规范,使用 `inputSchema` 字段:
144
+
145
+ ```typescript
146
+ const tools = {
147
+ tool_name: {
148
+ description: '工具描述',
149
+ inputSchema: {
150
+ type: 'object',
151
+ properties: {
152
+ param1: {
153
+ type: 'string',
154
+ description: '参数描述',
155
+ },
156
+ param2: {
157
+ type: 'number',
158
+ description: '数字参数',
159
+ },
160
+ },
161
+ required: ['param1'],
162
+ },
163
+ execute: async (args) => {
164
+ // 工具执行逻辑
165
+ return result;
166
+ },
167
+ },
168
+ };
169
+ ```
170
+
171
+ ### 工具调用事件流
172
+
173
+ 流式工具调用会生成以下事件序列:
174
+
175
+ ```
176
+ response-metadata
177
+
178
+ tool-input-start (工具调用开始)
179
+
180
+ tool-input-delta (参数累积,多次)
181
+
182
+ tool-input-end (参数接收完成)
183
+
184
+ tool-call (工具调用完成)
185
+
186
+ finish (finishReason: 'tool-calls')
187
+ ```
188
+
189
+ ### 工具调用循环
190
+
191
+ AI SDK 会自动处理工具调用循环:
192
+
193
+ ```typescript
194
+ const result = await generateText({
195
+ model,
196
+ tools,
197
+ prompt: '请读取 package.json 并分析其内容',
198
+ maxSteps: 5, // 最多执行 5 轮工具调用
199
+ });
200
+
201
+ // AI SDK 会自动:
202
+ // 1. 调用工具
203
+ // 2. 将工具结果返回给模型
204
+ // 3. 继续生成直到完成或达到 maxSteps
205
+ ```
206
+
207
+ ### 工具调用 + 推理
208
+
209
+ 模型可以同时使用工具调用和推理(<think> 标签):
210
+
211
+ ```typescript
212
+ const result = await streamText({
213
+ model,
214
+ tools,
215
+ prompt: '分析这个问题并使用工具解决',
216
+ });
217
+
218
+ for await (const event of result.fullStream) {
219
+ switch (event.type) {
220
+ case 'reasoning-delta':
221
+ console.log('[推理]', event.delta);
222
+ break;
223
+ case 'text-delta':
224
+ console.log('[文本]', event.delta);
225
+ break;
226
+ case 'tool-call':
227
+ console.log('[工具调用]', event.toolName, event.input);
228
+ break;
229
+ }
230
+ }
231
+ ```
232
+
233
+ ## 限制和注意事项
234
+
235
+ ### 工具调用限制
236
+
237
+ 1. **模型支持**: 并非所有 Ollama 模型都支持工具调用,请使用支持的模型
238
+ 2. **参数格式**: 工具参数必须是有效的 JSON 对象
239
+ 3. **并发调用**: 单次响应可以包含多个工具调用,但它们是顺序执行的
240
+ 4. **参数大小**: 避免传递过大的参数(建议 < 10KB)
241
+
242
+ ### 推理标签限制
243
+
244
+ 1. **标签格式**: 必须使用 `<think>...</think>` 格式
245
+ 2. **嵌套**: 不支持嵌套的 think 标签
246
+ 3. **混合内容**: 可以与普通文本和工具调用混合使用
247
+
248
+ ### 性能考虑
249
+
250
+ 1. **流式优先**: 对于长文本生成,优先使用流式 API
251
+ 2. **工具数量**: 建议每次请求提供的工具数量 < 20 个
252
+ 3. **参数验证**: 在工具执行前验证参数,避免无效调用
253
+
254
+ ## 常见问题(FAQ)
255
+
256
+ ### Q: 如何检查 Ollama 版本?
257
+
258
+ ```bash
259
+ ollama --version
260
+ ```
261
+
262
+ 如果版本低于 0.1.26,请升级:
263
+
264
+ ```bash
265
+ # macOS/Linux
266
+ curl -fsSL https://ollama.com/install.sh | sh
267
+
268
+ # Windows
269
+ # 从 https://ollama.com/download 下载最新版本
270
+ ```
271
+
272
+ ### Q: 工具调用不生效怎么办?
273
+
274
+ 1. 检查 Ollama 版本是否 >= 0.1.26
275
+ 2. 确认模型支持工具调用(使用 `ollama show <model>`)
276
+ 3. 检查工具定义格式是否正确(使用 `inputSchema` 而不是 `parameters`)
277
+ 4. 查看日志输出,确认是否有错误信息
278
+
279
+ ### Q: 如何调试工具调用?
280
+
281
+ 启用调试日志:
282
+
283
+ ```typescript
284
+ import { Log } from '@easbot/ollama-sdk';
62
285
 
63
- // 方式 2: 使用 languageModel 方法
64
- const model2 = ollama.languageModel('mistral');
286
+ // 初始化日志系统
287
+ await Log.init({
288
+ print: true,
289
+ dev: true,
290
+ level: 'DEBUG', // 启用 DEBUG 级别日志
291
+ });
65
292
 
66
- // 方式 3: 使用 chat 方法(别名)
67
- const model3 = ollama.chat('codellama');
293
+ // 现在所有工具调用的详细信息都会输出到控制台
68
294
  ```
69
295
 
296
+ ### Q: 支持哪些工具参数类型?
297
+
298
+ 支持所有 JSON Schema 类型:
299
+
300
+ - `string` - 字符串
301
+ - `number` - 数字
302
+ - `boolean` - 布尔值
303
+ - `object` - 对象
304
+ - `array` - 数组
305
+ - `null` - 空值
306
+
307
+ ### Q: 如何处理工具调用错误?
308
+
309
+ ```typescript
310
+ const tools = {
311
+ risky_operation: {
312
+ description: '可能失败的操作',
313
+ inputSchema: { /* ... */ },
314
+ execute: async (args) => {
315
+ try {
316
+ // 执行操作
317
+ return result;
318
+ } catch (error) {
319
+ // 返回错误信息给模型
320
+ throw new Error(`操作失败: ${error.message}`);
321
+ }
322
+ },
323
+ },
324
+ };
325
+ ```
326
+
327
+ ### Q: 向后兼容性如何?
328
+
329
+ 完全向后兼容:
330
+
331
+ - 不提供 `tools` 参数时,行为与之前完全一致
332
+ - 现有的文本生成和推理功能不受影响
333
+ - API 签名保持不变
334
+
335
+ ## 示例代码
336
+
337
+ 完整的使用示例请参考:
338
+
339
+ - [基本工具调用示例](./.easbot/test-ollama-tool-calling.ts)
340
+ - [多工具调用示例](./.easbot/test-ollama-tool-calling.ts#example2)
341
+ - [工具调用 + 推理示例](./.easbot/test-ollama-tool-calling.ts#example3)
342
+ - [错误处理示例](./.easbot/test-ollama-tool-calling.ts#example4)
343
+ - [工具调用循环示例](./.easbot/test-ollama-tool-calling.ts#example5)
344
+
70
345
  ## API 参考
71
346
 
72
- ### `createOllama(config?)`
347
+ ### `NativeOllamaLanguageModel`
348
+
349
+ 原生 Ollama 语言模型实现,完全兼容 AI SDK v2。
350
+
351
+ **构造函数:**
73
352
 
74
- 创建 Ollama provider 实例。
353
+ ```typescript
354
+ new NativeOllamaLanguageModel(modelId: string, config?: {
355
+ baseURL?: string;
356
+ })
357
+ ```
75
358
 
76
359
  **参数:**
77
360
 
78
- - `config` (可选): Provider 配置
361
+ - `modelId` (string): Ollama 模型 ID(例如:'qwen2.5:0.5b'、'llama3.1:8b')
362
+ - `config` (可选): 模型配置
79
363
  - `baseURL` (string): Ollama API 基础 URL,默认 `'http://localhost:11434'`
80
- - `headers` (Record<string, string>): 自定义请求头
81
- - `fetch` (typeof fetch): 自定义 fetch 实现
82
364
 
83
- **返回:**
365
+ **方法:**
84
366
 
85
- `OllamaProvider` - 可调用的 provider 对象
367
+ - `doStream(options)`: 流式生成,返回 ReadableStream
368
+ - `doGenerate(options)`: 非流式生成,返回完整结果
86
369
 
87
370
  **示例:**
88
371
 
89
372
  ```typescript
90
- const ollama = createOllama({
373
+ const model = new NativeOllamaLanguageModel('qwen2.5:0.5b', {
91
374
  baseURL: 'http://localhost:11434',
92
- headers: {
93
- 'X-Custom-Header': 'value',
94
- },
375
+ });
376
+
377
+ // 流式生成
378
+ const streamResult = await model.doStream({
379
+ prompt: [
380
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] }
381
+ ],
382
+ tools: [/* ... */],
383
+ });
384
+
385
+ // 非流式生成
386
+ const generateResult = await model.doGenerate({
387
+ prompt: [
388
+ { role: 'user', content: [{ type: 'text', text: 'Hello' }] }
389
+ ],
390
+ tools: [/* ... */],
95
391
  });
96
392
  ```
97
393
 
98
- ### `OllamaProvider`
394
+ ## 事件流
99
395
 
100
- Provider 对象,提供三种方式创建语言模型:
396
+ SDK 生成完整的 AI SDK v2 事件流,包括:
101
397
 
102
- ```typescript
103
- // 直接调用
104
- const model = ollama('llama2');
398
+ ### 基本事件
399
+
400
+ 1. **response-metadata** - 首个事件,包含模型 ID 和时间戳
401
+ 2. **text-start** - 在第一个 text-delta 之前发出
402
+ 3. **text-delta** - 文本内容增量(多次)
403
+ 4. **text-end** - 在所有 text-delta 之后发出
404
+ 5. **finish** - 最后一个事件,包含 finishReason 和 usage
405
+
406
+ ### 工具调用事件
407
+
408
+ 6. **tool-input-start** - 工具调用开始,包含 toolName
409
+ 7. **tool-input-delta** - 工具参数增量(多次)
410
+ 8. **tool-input-end** - 工具参数接收完成
411
+ 9. **tool-call** - 工具调用完成,包含完整参数
412
+
413
+ ### 推理事件
105
414
 
106
- // 使用 languageModel 方法
107
- const model = ollama.languageModel('llama2');
415
+ 10. **reasoning-start** - 推理开始(<think> 标签)
416
+ 11. **reasoning-delta** - 推理内容增量(多次)
417
+ 12. **reasoning-end** - 推理结束(</think> 标签)
108
418
 
109
- // 使用 chat 方法
110
- const model = ollama.chat('llama2');
419
+ ### 事件流顺序示例
420
+
421
+ **纯文本生成:**
422
+
423
+ ```
424
+ response-metadata → text-start → text-delta (多次) → text-end → finish
111
425
  ```
112
426
 
113
- ## 事件流增强
427
+ **工具调用:**
114
428
 
115
- 本 SDK 自动增强 `ai-sdk-ollama` 的事件流,添加以下缺失的事件:
429
+ ```
430
+ response-metadata → tool-input-start → tool-input-delta (多次) →
431
+ tool-input-end → tool-call → finish (finishReason: 'tool-calls')
432
+ ```
116
433
 
117
- 1. **response-metadata** - 首个事件,包含模型 ID 和时间戳
118
- 2. **text-start** - 在第一个 text-delta 之前发出
119
- 3. **text-end** - 在所有 text-delta 之后发出
120
- 4. **finish** - 如果 ai-sdk-ollama 没有发出,自动补充
434
+ **推理 + 文本:**
435
+
436
+ ```
437
+ response-metadata reasoning-start reasoning-delta (多次) →
438
+ reasoning-end → text-start → text-delta (多次) → text-end → finish
439
+ ```
121
440
 
122
- ### 事件流顺序
441
+ **工具调用 + 推理:**
123
442
 
124
443
  ```
125
- response-metadata
126
-
127
- text-start (如果有文本生成)
128
-
129
- text-delta (多个)
130
-
131
- text-end (如果有文本生成)
132
-
133
- finish
444
+ response-metadata → reasoning-start → reasoning-delta (多次) →
445
+ reasoning-end → tool-input-start → tool-input-delta (多次) →
446
+ tool-input-end → tool-call → finish
134
447
  ```
135
448
 
136
449
  ## 错误处理
@@ -139,64 +452,96 @@ SDK 提供了完整的错误类型:
139
452
 
140
453
  ```typescript
141
454
  import {
142
- OllamaError,
143
- ConnectionError,
144
- ModelNotFoundError,
145
- ValidationError,
146
- TimeoutError,
455
+ InvalidToolDefinitionError,
147
456
  } from '@easbot/ollama-sdk';
148
457
 
149
458
  try {
459
+ const model = new NativeOllamaLanguageModel('qwen2.5:0.5b');
150
460
  const result = await generateText({
151
- model: ollama('llama2'),
461
+ model,
462
+ tools: {
463
+ invalid_tool: {
464
+ // 缺少 inputSchema 字段
465
+ description: '无效的工具',
466
+ } as any,
467
+ },
152
468
  prompt: 'Hello',
153
469
  });
154
470
  } catch (error) {
155
- if (error instanceof ConnectionError) {
156
- console.error('无法连接到 Ollama 服务:', error.url);
157
- } else if (error instanceof ModelNotFoundError) {
158
- console.error('模型不存在:', error.modelId);
159
- } else if (error instanceof ValidationError) {
160
- console.error('参数验证失败:', error.field, error.value);
161
- } else if (error instanceof TimeoutError) {
162
- console.error('请求超时:', error.timeoutMs);
471
+ if (error instanceof InvalidToolDefinitionError) {
472
+ console.error('工具定义无效:', error.toolName, error.missingField);
473
+ } else {
474
+ console.error('其他错误:', error);
475
+ }
476
+ }
477
+ ```
478
+
479
+ ### 错误事件
480
+
481
+ 在流式处理中,错误会作为事件发出:
482
+
483
+ ```typescript
484
+ const result = await model.doStream({
485
+ prompt: [{ role: 'user', content: [{ type: 'text', text: 'Hello' }] }],
486
+ });
487
+
488
+ for await (const event of result.stream) {
489
+ if (event.type === 'error') {
490
+ console.error('流式错误:', event.error);
491
+ } else if (event.type === 'finish') {
492
+ if (event.finishReason === 'error') {
493
+ console.error('生成过程出错');
494
+ }
163
495
  }
164
496
  }
165
497
  ```
166
498
 
167
499
  ## 架构
168
500
 
169
- 本 SDK 是一个轻量级封装层,依赖 `ai-sdk-ollama` 进行实际的 LLM 请求:
501
+ 本 SDK 是完全原生的 Ollama 实现,直接调用 Ollama HTTP API:
170
502
 
171
503
  ```
172
504
  用户代码
173
505
 
174
- @easbot/ollama-sdk (封装层)
175
- ├── createOllama() - Provider 工厂
176
- ├── OllamaLanguageModel - 语言模型封装
177
- ├── doGenerate() - 委托给 ai-sdk-ollama
178
- │ └── doStream() - 委托 + 事件流增强
179
- └── enhanceStream() - 事件流增强器
180
-
181
- ai-sdk-ollama (底层 LLM 请求)
506
+ @easbot/ollama-sdk
507
+ ├── NativeOllamaLanguageModel - 语言模型实现
508
+ ├── doGenerate() - 非流式生成
509
+ └── doStream() - 流式生成 + 事件流生成
510
+ ├── NativeOllamaClient - HTTP 客户端
511
+ │ ├── chat() - 非流式请求
512
+ │ └── streamChat() - 流式请求
513
+ ├── prepareTools() - 工具定义转换
514
+ ├── convertToOllamaMessages() - 消息格式转换
515
+ └── Log - 结构化日志系统
182
516
 
183
517
  Ollama HTTP API
184
518
  ```
185
519
 
186
- ## 与其他 Provider 的对比
520
+ ### 特点
521
+
522
+ - **零依赖**: 不依赖 `ai-sdk-ollama` 或其他第三方库
523
+ - **完整实现**: 完全符合 AI SDK v2 规范
524
+ - **高性能**: 批量事件队列、及时状态清理
525
+ - **可调试**: 结构化日志、trace ID 追踪
526
+
527
+ ## 与其他实现的对比
187
528
 
188
529
  ### vs `ai-sdk-ollama`
189
530
 
190
- - ✅ 完整的 AI SDK v2 事件流(自动添加缺失事件)
531
+ - ✅ 完整的工具调用支持(ai-sdk-ollama 不支持)
532
+ - ✅ 完整的 AI SDK v2 事件流
191
533
  - ✅ 更好的 TypeScript 类型支持
192
534
  - ✅ 完整的错误处理系统
193
- - ✅ 保持与 `ai-sdk-ollama` 相同的性能
535
+ - ✅ 结构化日志和调试支持
536
+ - ✅ 零额外依赖
194
537
 
195
538
  ### vs `ollama-ai-provider`
196
539
 
197
- - ✅ 基于官方 `ai-sdk-ollama`,更稳定
198
- - ✅ 自动事件流增强
540
+ - ✅ 原生实现,更稳定
541
+ - ✅ 工具调用支持
542
+ - ✅ 自动事件流生成
199
543
  - ✅ 更简洁的 API
544
+ - ✅ 完整的文档和示例
200
545
 
201
546
  ## 开发
202
547