@bowenqt/qiniu-ai-sdk 0.27.2 โ†’ 0.28.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.md CHANGED
@@ -1,43 +1,75 @@
1
1
  # Qiniu AI SDK
2
2
 
3
- TypeScript SDK for Qiniu Cloud AI Token API.
3
+ <div align="center">
4
4
 
5
- ## Features
5
+ [![npm version](https://img.shields.io/npm/v/@bowenqt/qiniu-ai-sdk.svg?style=flat-square)](https://www.npmjs.com/package/@bowenqt/qiniu-ai-sdk)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT)
7
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18.0.0-brightgreen.svg?style=flat-square)](https://nodejs.org/)
6
8
 
7
- - ๐Ÿš€ **Chat Completions** - OpenAI-compatible interface
8
- - ๐Ÿ–ผ๏ธ **Image Generation** - Kling, Gemini models
9
- - ๐ŸŽฅ **Video Generation** - Kling, Sora, Veo models
10
- - ๐Ÿ” **Web Search** - Real-time web search API
11
- - โฑ๏ธ **Built-in Polling** - Async task management with retry and cancellation
12
- - ๐Ÿค– **Agentic Layer** - Multi-step tool execution with `generateText`
13
- - ๐Ÿ“‹ **JSON Mode** - Structured output with `response_format`
14
- - ๐Ÿ”Œ **Vercel AI SDK Adapter** - Drop-in replacement for Vercel AI SDK
15
- - ๐Ÿ“ฆ **TypeScript First** - Full type definitions included
16
- - ๐Ÿง  **Skills** - Markdown-based agent knowledge injection
17
- - ๐Ÿ”— **MCP Client** - Model Context Protocol stdio + HTTP transport
18
- - ๐Ÿ” **OAuth 2.0** - PKCE and Device Code flows for MCP HTTP
19
- - ๐Ÿ’พ **Checkpointer** - Save/restore state: Memory, Redis, PostgreSQL
20
- - ๐Ÿ“Š **Tracing** - OpenTelemetry integration with per-node spans
9
+ **TypeScript SDK for Qiniu Cloud AI Services**
21
10
 
22
- ## Requirements
11
+ [English](./README.md) | [ไธญๆ–‡](./README.zh-CN.md) | [COOKBOOK](./COOKBOOK.md)
23
12
 
24
- - Node.js >= 18.0.0 (uses native `fetch`)
13
+ </div>
25
14
 
26
- ## Installation
15
+ ---
16
+
17
+ ## โœจ Features
18
+
19
+ ### Core AI Modules
20
+ - ๐Ÿš€ **Chat Completions** โ€” OpenAI-compatible interface with streaming support
21
+ - ๐Ÿ–ผ๏ธ **Image Generation** โ€” Kling, Gemini models with unified sync/async API
22
+ - ๐ŸŽฅ **Video Generation** โ€” Kling, Sora, Veo models with first/last frame control
23
+ - ๐Ÿ” **Web Search** โ€” Real-time web search integration
24
+ - ๐Ÿ“ **OCR** โ€” High-precision text recognition for images and PDFs
25
+ - ๐ŸŽค **ASR** โ€” Multi-language speech recognition (95%+ accuracy in noisy environments)
26
+ - ๐Ÿ”Š **TTS** โ€” Text-to-speech synthesis with multiple voice options
27
+ - ๐Ÿ›ก๏ธ **Content Moderation** โ€” Image and video censorship with scene-based detection
28
+
29
+ ### Agentic Layer
30
+ - ๐Ÿค– **generateText** โ€” Multi-step tool execution with Zod schema support
31
+ - ๐Ÿ“Š **generateObject/streamObject** โ€” Structured JSON output with streaming
32
+ - ๐Ÿง  **AgentGraph** โ€” State machine-based graph execution
33
+ - ๐Ÿญ **createAgent** โ€” Reusable agent factory with configurable behaviors
34
+
35
+ ### Advanced Capabilities
36
+ - ๐Ÿ“‹ **Skills Injection** โ€” Markdown-based agent knowledge (Claude Skills compatible)
37
+ - ๐Ÿ”— **MCP Client** โ€” Model Context Protocol with stdio + HTTP + OAuth 2.0 support
38
+ - ๐Ÿ–ฅ๏ธ **MCP Server** โ€” Built-in Qiniu MCP server for OCR/Censor/Vframe tools
39
+ - ๐Ÿ’พ **Checkpointer** โ€” State persistence (Memory, Redis, PostgreSQL, Kodo)
40
+ - ๐Ÿง  **Memory Manager** โ€” Short-term + long-term memory with LLM summarization
41
+ - โœ… **Tool Approval (HITL)** โ€” Human-in-the-loop for sensitive operations
42
+ - โธ๏ธ **Interrupt/Resume** โ€” Resumable execution with checkpoint-based restore
43
+ - ๐Ÿ“Š **OpenTelemetry Tracing** โ€” Distributed tracing with per-node spans
44
+ - ๐Ÿ”Œ **Vercel AI SDK Adapter** โ€” Drop-in replacement for Vercel AI SDK
45
+
46
+ ---
47
+
48
+ ## ๐Ÿ“ฆ Installation
27
49
 
28
50
  ```bash
29
51
  npm install @bowenqt/qiniu-ai-sdk
30
52
  ```
31
53
 
32
- ### Installation for Adapter Users
33
-
34
- The Vercel AI SDK adapter is optional. Install the peer dependencies when you use it:
54
+ ### Optional Peer Dependencies
35
55
 
36
56
  ```bash
37
- npm install @bowenqt/qiniu-ai-sdk @ai-sdk/provider ai
57
+ # For Vercel AI SDK integration
58
+ npm install @ai-sdk/provider ai
59
+
60
+ # For Zod schema validation
61
+ npm install zod
62
+
63
+ # For Redis checkpointer
64
+ npm install ioredis
65
+
66
+ # For PostgreSQL checkpointer
67
+ npm install pg
38
68
  ```
39
69
 
40
- ## Quick Start
70
+ ---
71
+
72
+ ## ๐Ÿš€ Quick Start
41
73
 
42
74
  ```typescript
43
75
  import { QiniuAI } from '@bowenqt/qiniu-ai-sdk';
@@ -53,273 +85,144 @@ const chat = await client.chat.create({
53
85
  });
54
86
  console.log(chat.choices[0].message.content);
55
87
 
56
- // Image generation (async)
57
- const imageResult = await client.image.generate({
58
- model: 'kling-v1',
59
- prompt: 'A futuristic city',
60
- });
61
- const imageFinal = await client.image.waitForResult(imageResult);
62
- console.log(imageFinal.data?.[0].url || imageFinal.data?.[0].b64_json);
63
-
64
- // Video generation (async)
65
- const videoTask = await client.video.create({
66
- model: 'kling-video-o1',
67
- prompt: 'A cat walking on the beach',
68
- duration: '5',
69
- });
70
- const videoResult = await client.video.waitForCompletion(videoTask.id);
71
- console.log(videoResult.task_result?.videos[0].url);
72
-
73
- // Web search
74
- const results = await client.sys.search({
75
- query: 'Latest AI news',
76
- max_results: 5,
77
- });
78
- console.log(results);
79
-
80
- // Streaming Chat (New!)
88
+ // Streaming chat
81
89
  const stream = await client.chat.createStream({
82
90
  model: 'gemini-2.5-flash',
83
- messages: [{ role: 'user', content: 'Explain quantum computing' }],
91
+ messages: [{ role: 'user', content: 'Explain AI briefly' }],
84
92
  });
85
-
86
93
  for await (const chunk of stream) {
87
94
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
88
95
  }
89
96
  ```
90
97
 
91
- ## Advanced Usage
92
-
93
- ### StreamAccumulator (Manual Streaming)
94
-
95
- ```typescript
96
- import { QiniuAI, createStreamAccumulator, accumulateDelta } from '@bowenqt/qiniu-ai-sdk';
97
-
98
- const client = new QiniuAI({ apiKey: 'Sk-xxx' });
99
- const stream = client.chat.createStream({
100
- model: 'gemini-2.5-flash',
101
- messages: [{ role: 'user', content: 'Explain streaming in one sentence.' }],
102
- });
103
-
104
- const acc = createStreamAccumulator();
105
- for await (const chunk of stream) {
106
- const delta = chunk.choices[0]?.delta;
107
- if (delta) {
108
- accumulateDelta(acc, delta);
109
- }
110
- }
111
- console.log(acc.content);
112
- ```
113
-
114
- ### parseSSEStream (Custom SSE Parsing)
115
-
116
- ```typescript
117
- import { parseSSEStream } from '@bowenqt/qiniu-ai-sdk';
118
-
119
- const response = await fetch('https://api.qnaigc.com/v1/chat/completions', {
120
- method: 'POST',
121
- headers: {
122
- 'Content-Type': 'application/json',
123
- 'Authorization': `Bearer ${process.env.QINIU_API_KEY}`,
124
- },
125
- body: JSON.stringify({
126
- model: 'gemini-2.5-flash',
127
- messages: [{ role: 'user', content: 'Hello!' }],
128
- stream: true,
129
- }),
130
- });
131
-
132
- for await (const chunk of parseSSEStream(response)) {
133
- console.log(chunk);
134
- }
135
- ```
136
-
137
- ### createPoller (Custom Async Polling)
138
-
139
- ```typescript
140
- import { createPoller } from '@bowenqt/qiniu-ai-sdk';
141
-
142
- const poller = createPoller({
143
- intervalMs: 2000,
144
- timeoutMs: 60000,
145
- isTerminal: (result) => result.status === 'succeed' || result.status === 'failed',
146
- getStatus: (id) => client.video.get(id),
147
- });
148
-
149
- const result = await poller.poll('task-id');
150
- console.log(result.result);
151
- ```
152
-
153
- ## Agentic Layer
98
+ ---
154
99
 
155
- The SDK provides a high-level `generateText` function for multi-step tool execution:
100
+ ## ๐Ÿค– Agentic Usage
156
101
 
157
- ### Basic Tool Usage
102
+ ### Tool Execution with generateText
158
103
 
159
104
  ```typescript
160
105
  import { QiniuAI, generateText } from '@bowenqt/qiniu-ai-sdk';
106
+ import { z } from 'zod';
161
107
 
162
108
  const client = new QiniuAI({ apiKey: process.env.QINIU_API_KEY || '' });
163
109
 
164
110
  const result = await generateText({
165
111
  client,
166
112
  model: 'gemini-2.5-flash',
167
- prompt: 'What is the weather in Beijing?',
113
+ prompt: 'What is 42 * 17?',
168
114
  tools: {
169
- getWeather: {
170
- description: 'Get weather for a city',
171
- parameters: {
172
- type: 'object',
173
- properties: { city: { type: 'string' } },
174
- required: ['city'],
115
+ calculate: {
116
+ description: 'Perform calculation',
117
+ parameters: z.object({
118
+ operation: z.enum(['add', 'subtract', 'multiply', 'divide']),
119
+ a: z.number(),
120
+ b: z.number(),
121
+ }),
122
+ execute: async ({ operation, a, b }) => {
123
+ const ops = { add: a + b, subtract: a - b, multiply: a * b, divide: a / b };
124
+ return ops[operation];
175
125
  },
176
- execute: async ({ city }) => ({ temperature: 25, city }),
177
126
  },
178
127
  },
179
128
  maxSteps: 3,
180
129
  });
181
130
 
182
- console.log(result.text); // Final response
183
- console.log(result.steps); // All intermediate steps
184
- console.log(result.toolCalls); // Tool calls from last step
131
+ console.log(result.text); // Final answer
132
+ console.log(result.toolCalls); // Tool calls made
185
133
  ```
186
134
 
187
- ### Zod Schema Support
188
-
189
- The SDK auto-converts Zod schemas to JSON Schema:
135
+ ### Structured Output
190
136
 
191
137
  ```typescript
138
+ import { generateObject } from '@bowenqt/qiniu-ai-sdk';
192
139
  import { z } from 'zod';
193
140
 
194
- const tools = {
195
- calculate: {
196
- description: 'Calculate math',
197
- parameters: z.object({
198
- operation: z.enum(['add', 'subtract', 'multiply']),
199
- a: z.number(),
200
- b: z.number(),
201
- }),
202
- execute: async ({ operation, a, b }) => {
203
- if (operation === 'add') return a + b;
204
- if (operation === 'subtract') return a - b;
205
- return a * b;
206
- },
207
- },
208
- };
209
- ```
210
-
211
- ### JSON Mode (Structured Output)
212
-
213
- ```typescript
214
- const result = await generateText({
141
+ const result = await generateObject({
215
142
  client,
216
143
  model: 'gemini-2.5-flash',
217
- prompt: 'List 3 scientists as JSON array',
218
- responseFormat: { type: 'json_object' },
219
- temperature: 0,
144
+ prompt: 'Generate a product listing',
145
+ schema: z.object({
146
+ name: z.string(),
147
+ price: z.number(),
148
+ category: z.string(),
149
+ }),
220
150
  });
221
151
 
222
- const data = JSON.parse(result.text);
152
+ console.log(result.object); // Typed object
223
153
  ```
224
154
 
225
- ### Cancellation
155
+ ---
156
+
157
+ ## ๐Ÿ–ผ๏ธ Image & Video Generation
226
158
 
227
159
  ```typescript
228
- const controller = new AbortController();
229
- setTimeout(() => controller.abort(), 5000);
230
-
231
- try {
232
- const result = await generateText({
233
- client,
234
- model: 'gemini-2.5-flash',
235
- prompt: 'Write a long essay...',
236
- abortSignal: controller.signal,
237
- });
238
- } catch (error) {
239
- if (error.code === 'CANCELLED') {
240
- console.log('Request cancelled');
241
- }
242
- }
243
- ```
244
-
245
- ## Agent SDK (Agentic Layer)
160
+ // Image generation (async task)
161
+ const imageResult = await client.image.generate({
162
+ model: 'kling-v2',
163
+ prompt: 'A futuristic city at sunset',
164
+ });
165
+ const finalImage = await client.image.waitForResult(imageResult);
166
+ console.log(finalImage.data?.[0].url);
246
167
 
247
- Phase 2 introduces advanced agentic capabilities with Skills injection, MCP integration, and Graph-based execution.
168
+ // Video generation with first/last frame
169
+ const videoTask = await client.video.create({
170
+ model: 'kling-video-o1',
171
+ prompt: 'A cat jumps from one ledge to another',
172
+ frames: {
173
+ first: { url: 'https://example.com/start.jpg' },
174
+ last: { url: 'https://example.com/end.jpg' },
175
+ },
176
+ duration: '5',
177
+ });
178
+ const videoResult = await client.video.waitForCompletion(videoTask.id);
179
+ console.log(videoResult.task_result?.videos[0].url);
180
+ ```
248
181
 
249
- ### generateTextWithGraph
182
+ ---
250
183
 
251
- Extended version of `generateText` with Skills, context compaction, and Graph events:
184
+ ## ๐Ÿ”Œ Vercel AI SDK Adapter
252
185
 
253
186
  ```typescript
254
- import { QiniuAI, generateTextWithGraph, SkillLoader } from '@bowenqt/qiniu-ai-sdk';
255
-
256
- const client = new QiniuAI({ apiKey: process.env.QINIU_API_KEY });
257
-
258
- // Load skills
259
- const loader = new SkillLoader({ skillsDir: './skills' });
260
- const skills = await loader.loadAll();
187
+ import { createQiniu } from '@bowenqt/qiniu-ai-sdk/adapter';
188
+ import { streamText } from 'ai';
261
189
 
262
- const result = await generateTextWithGraph({
263
- client,
264
- model: 'deepseek-v3',
265
- messages: [{ role: 'user', content: 'Help me with Git' }],
266
- skills, // Injected into system prompt
267
- maxContextTokens: 32000, // Automatic context compaction
268
- onStepFinish: (step) => console.log(step.type, step.content),
269
- onNodeEnter: (node) => console.log(`Entering: ${node}`),
190
+ const qiniu = createQiniu({
191
+ apiKey: process.env.QINIU_API_KEY,
270
192
  });
271
193
 
272
- console.log(result.text);
273
- console.log(result.graphInfo?.skillsInjected); // ['git-workflow', ...]
194
+ const { textStream } = await streamText({
195
+ model: qiniu.languageModel('gemini-2.5-flash'),
196
+ prompt: 'Introduce Qiniu Cloud briefly.',
197
+ });
274
198
 
275
- // Check if compaction occurred
276
- if (result.graphInfo?.compaction?.occurred) {
277
- console.log('Dropped skills:', result.graphInfo.compaction.droppedSkills);
199
+ for await (const text of textStream) {
200
+ process.stdout.write(text);
278
201
  }
279
202
  ```
280
203
 
281
- **Context Compaction**: When `maxContextTokens` is set, the SDK automatically:
282
- - Estimates tokens using CJK-aware algorithm (1.5x weight for Chinese/Japanese/Korean)
283
- - Drops low-priority skills first when context exceeds budget
284
- - Preserves tool call/result pairs (never orphaned)
285
- - Reports dropped content in `graphInfo.compaction`
204
+ ---
286
205
 
287
- ### Skills
206
+ ## ๐Ÿง  Advanced Features
288
207
 
289
- Load and inject agent skills (Markdown-based knowledge):
208
+ ### Skills Injection
290
209
 
291
210
  ```typescript
292
- import { SkillLoader } from '@bowenqt/qiniu-ai-sdk';
293
-
294
- const loader = new SkillLoader({
295
- skillsDir: './skills',
296
- maxFileSize: 64 * 1024, // 64KB limit
297
- allowedExtensions: ['.md', '.txt'],
298
- });
299
-
300
- // Load single skill
301
- const skill = await loader.load('git-workflow');
302
- console.log(skill.name, skill.tokenCount);
211
+ import { SkillLoader, generateTextWithGraph } from '@bowenqt/qiniu-ai-sdk';
303
212
 
304
- // Load all skills (sorted by name)
305
- const allSkills = await loader.loadAll();
306
- ```
307
-
308
- Skill format (SKILL.md):
309
- ```markdown
310
- ---
311
- name: git-workflow
312
- description: Git best practices
313
- ---
314
-
315
- # Git Workflow
213
+ const loader = new SkillLoader({ skillsDir: './skills' });
214
+ const skills = await loader.loadAll();
316
215
 
317
- Always use conventional commits...
216
+ const result = await generateTextWithGraph({
217
+ client,
218
+ model: 'deepseek-v3',
219
+ messages: [{ role: 'user', content: 'Help me with Git' }],
220
+ skills,
221
+ maxContextTokens: 32000,
222
+ });
318
223
  ```
319
224
 
320
- ### MCP Client (stdio)
321
-
322
- Connect to Model Context Protocol servers:
225
+ ### MCP Client (stdio + HTTP)
323
226
 
324
227
  ```typescript
325
228
  import { MCPClient } from '@bowenqt/qiniu-ai-sdk';
@@ -331,642 +234,128 @@ const mcpClient = new MCPClient({
331
234
  transport: 'stdio',
332
235
  command: 'npx',
333
236
  args: ['-y', '@modelcontextprotocol/server-github'],
334
- token: process.env.GITHUB_TOKEN, // โ†’ MCP_BEARER_TOKEN env
237
+ token: process.env.GITHUB_TOKEN,
335
238
  },
336
239
  ],
337
240
  });
338
241
 
339
242
  await mcpClient.connect();
340
243
  const tools = mcpClient.getAllTools();
341
- console.log(tools.map(t => t.name));
342
-
343
- // Use tools with generateText
344
- const registeredTools = adaptMCPToolsToRegistry(tools, 'github', mcpClient);
345
244
  ```
346
245
 
347
- > **Note**: Bearer token is injected via `MCP_BEARER_TOKEN` environment variable for stdio transport.
348
-
349
- ### MCP Client (HTTP + OAuth 2.0)
350
-
351
- Connect to remote MCP servers with OAuth authentication:
246
+ ### Checkpointer (State Persistence)
352
247
 
353
248
  ```typescript
354
- import {
355
- MCPClient,
356
- PKCEFlow,
357
- TokenManager,
358
- MemoryTokenStore
359
- } from '@bowenqt/qiniu-ai-sdk';
360
-
361
- // Step 1: Obtain tokens via PKCE flow
362
- const oauthConfig = {
363
- clientId: 'my-app',
364
- scopes: ['mcp:read', 'mcp:write'],
365
- authorizationUrl: 'https://auth.example.com/authorize',
366
- tokenUrl: 'https://auth.example.com/token',
367
- };
368
-
369
- const pkceFlow = new PKCEFlow(oauthConfig);
370
- const state = generateState();
371
- const authUrl = pkceFlow.buildAuthorizationUrl('http://localhost:3000/callback', state);
372
- // Redirect user to authUrl...
373
-
374
- // After callback:
375
- const { code } = await pkceFlow.waitForCallback({ expectedState: state, timeoutMs: 300000 });
376
- const tokens = await pkceFlow.exchangeCode(code, 'http://localhost:3000/callback');
377
-
378
- // Step 2: Set up TokenManager
379
- const tokenManager = new TokenManager(new MemoryTokenStore(), oauthConfig);
380
- await tokenManager.setTokens(tokens);
381
-
382
- // Step 3: Connect with tokenProvider
383
- const mcpClient = new MCPClient({
384
- servers: [
385
- {
386
- name: 'remote-server',
387
- transport: 'http',
388
- url: 'https://mcp.example.com/mcp',
389
- tokenProvider: () => tokenManager.getAccessToken(),
390
- oauth: oauthConfig, // Required: validates auth is configured
391
- },
392
- ],
393
- });
394
-
395
- await mcpClient.connect();
396
- ```
397
-
398
- > **Note**: If `oauth` is configured without `token` or `tokenProvider`, MCPClient throws an error to prevent unauthenticated connections.
399
-
400
- ### Checkpointer
249
+ import { MemoryCheckpointer, RedisCheckpointer, KodoCheckpointer } from '@bowenqt/qiniu-ai-sdk';
401
250
 
402
- Save and restore conversation state:
251
+ // In-memory (dev/testing)
252
+ const memoryCheckpointer = new MemoryCheckpointer({ maxItems: 100 });
403
253
 
404
- ```typescript
405
- import { MemoryCheckpointer, deserializeCheckpoint } from '@bowenqt/qiniu-ai-sdk';
406
-
407
- const checkpointer = new MemoryCheckpointer({ maxItems: 100 });
408
-
409
- // Save checkpoint
410
- const metadata = await checkpointer.save('thread-123', agentState);
411
-
412
- // Load latest checkpoint
413
- const checkpoint = await checkpointer.load('thread-123');
414
- if (checkpoint) {
415
- const restored = deserializeCheckpoint(checkpoint, toolsMap);
416
- // Resume from restored state
417
- }
418
-
419
- // List all checkpoints
420
- const list = await checkpointer.list('thread-123');
421
- ```
422
-
423
- ### Redis/PostgreSQL Checkpointer
424
-
425
- For production persistence:
426
-
427
- ```typescript
428
- import { RedisCheckpointer, PostgresCheckpointer } from '@bowenqt/qiniu-ai-sdk';
429
-
430
- // Redis (requires ioredis peer dependency)
254
+ // Redis (production)
431
255
  const redisCheckpointer = new RedisCheckpointer(redisClient, {
432
- keyPrefix: 'chat:',
433
- ttlSeconds: 86400, // 24 hours
256
+ keyPrefix: 'agent:',
257
+ ttlSeconds: 86400,
434
258
  });
435
259
 
436
- // PostgreSQL (requires pg peer dependency)
437
- const pgCheckpointer = new PostgresCheckpointer(pgPool, {
438
- tableName: 'checkpoints',
439
- autoCreateTable: true,
260
+ // Kodo (cloud-native/serverless)
261
+ const kodoCheckpointer = new KodoCheckpointer({
262
+ accessKey: process.env.QINIU_ACCESS_KEY!,
263
+ secretKey: process.env.QINIU_SECRET_KEY!,
264
+ bucket: 'checkpoints',
265
+ region: 'z0',
440
266
  });
441
267
  ```
442
268
 
443
- ### Tracing (OpenTelemetry)
444
-
445
- Integrate with OpenTelemetry for distributed tracing:
269
+ ### OpenTelemetry Tracing
446
270
 
447
271
  ```typescript
448
- import { setGlobalTracer, OTelTracer, ConsoleTracer } from '@bowenqt/qiniu-ai-sdk';
449
-
450
- // Console tracer for development
451
- setGlobalTracer(new ConsoleTracer({ redactPII: true }));
452
-
453
- // OpenTelemetry tracer for production
272
+ import { setGlobalTracer, OTelTracer } from '@bowenqt/qiniu-ai-sdk';
454
273
  import { trace } from '@opentelemetry/api';
274
+
455
275
  const otelTracer = new OTelTracer(trace.getTracerProvider());
456
276
  setGlobalTracer(otelTracer);
457
277
 
458
- // AgentGraph automatically creates spans:
459
- // - agent_graph.invoke (top-level)
460
- // - agent_graph.predict (per LLM call)
461
- // - agent_graph.execute (per tool execution round)
462
- ```
463
-
464
-
465
- ## Vercel AI SDK Adapter
466
-
467
- Use the adapter to integrate with the Vercel AI SDK (`streamText`, `generateText`).
468
-
469
- ```typescript
470
- import { createQiniu } from '@bowenqt/qiniu-ai-sdk/adapter';
471
- import { streamText } from 'ai';
472
-
473
- const qiniu = createQiniu({
474
- apiKey: process.env.QINIU_API_KEY || process.env.OPENAI_API_KEY,
475
- });
476
-
477
- const { textStream } = await streamText({
478
- model: qiniu.languageModel('gemini-2.5-flash'),
479
- prompt: 'Introduce Qiniu Cloud in one sentence.',
480
- });
481
-
482
- for await (const text of textStream) {
483
- process.stdout.write(text);
484
- }
485
- ```
486
-
487
- Notes:
488
- - If you already have a `QiniuAI` client, pass it via `createQiniu({ client })`.
489
- - You can override `baseUrl` using `createQiniu({ baseUrl })` or `QINIU_BASE_URL`.
490
-
491
- ## API Reference
492
-
493
- ### Client Initialization
494
-
495
- ```typescript
496
- import { QiniuAI, consoleLogger } from '@bowenqt/qiniu-ai-sdk';
497
-
498
- const client = new QiniuAI({
499
- apiKey: string; // Required: Your Qiniu AI API key
500
- baseUrl?: string; // Optional: API base URL (default: https://api.qnaigc.com/v1)
501
- timeout?: number; // Optional: Request timeout in ms (default: 60000)
502
- logger?: Logger; // Optional: Custom logger (use consoleLogger for debug output)
503
- logLevel?: LogLevel; // Optional: 'debug' | 'info' | 'warn' | 'error' | 'silent' (default: 'info')
504
- });
505
- ```
506
-
507
- ### Logging
508
-
509
- Enable debug logging to see request/response details:
510
-
511
- ```typescript
512
- import { QiniuAI, consoleLogger } from '@bowenqt/qiniu-ai-sdk';
513
-
514
- const client = new QiniuAI({
515
- apiKey: 'Sk-xxx',
516
- logger: consoleLogger,
517
- logLevel: 'debug',
518
- });
519
-
520
- // Now you'll see logs like:
521
- // [QiniuAI:DEBUG] HTTP Request { requestId: 'req_123...', method: 'POST', url: '...', timeout: 60000 }
522
- // [QiniuAI:DEBUG] HTTP Response { requestId: 'req_123...', status: 200, duration: 1234 }
523
- ```
524
-
525
- Use a custom logger (e.g., pino, winston):
526
-
527
- ```typescript
528
- import { QiniuAI, Logger } from '@bowenqt/qiniu-ai-sdk';
529
- import pino from 'pino';
530
-
531
- const pinoLogger = pino();
532
-
533
- const customLogger: Logger = {
534
- debug: (msg, meta) => pinoLogger.debug(meta, msg),
535
- info: (msg, meta) => pinoLogger.info(meta, msg),
536
- warn: (msg, meta) => pinoLogger.warn(meta, msg),
537
- error: (msg, meta) => pinoLogger.error(meta, msg),
538
- };
539
-
540
- const client = new QiniuAI({
541
- apiKey: 'Sk-xxx',
542
- logger: customLogger,
543
- });
544
- ```
545
-
546
- ### Middleware
547
-
548
- Use middleware to intercept and modify requests/responses:
549
-
550
- ```typescript
551
- import { QiniuAI, Middleware, retryMiddleware, headersMiddleware } from '@bowenqt/qiniu-ai-sdk';
552
-
553
- // Built-in retry middleware (retries on 5xx errors)
554
- const retry = retryMiddleware({ maxRetries: 3, retryDelay: 1000 });
555
-
556
- // Built-in headers middleware (adds custom headers)
557
- const customHeaders = headersMiddleware({
558
- 'X-Custom-Header': 'my-value',
559
- });
560
-
561
- // Custom middleware
562
- const loggingMiddleware: Middleware = async (request, next) => {
563
- console.log('Request:', request.method, request.url);
564
- const response = await next(request);
565
- console.log('Response:', response.status, response.duration + 'ms');
566
- return response;
567
- };
568
-
569
- const client = new QiniuAI({
570
- apiKey: 'Sk-xxx',
571
- middleware: [retry, customHeaders, loggingMiddleware],
572
- });
573
- ```
574
-
575
- ### Custom HTTP Adapter
576
-
577
- Replace the default `fetch` with a custom HTTP client:
578
-
579
- ```typescript
580
- import { QiniuAI, FetchAdapter } from '@bowenqt/qiniu-ai-sdk';
581
- import axios from 'axios';
582
-
583
- const axiosAdapter: FetchAdapter = {
584
- async fetch(url, init) {
585
- const response = await axios({
586
- url,
587
- method: init.method as any,
588
- headers: init.headers as Record<string, string>,
589
- data: init.body,
590
- signal: init.signal,
591
- validateStatus: () => true, // Don't throw on non-2xx
592
- });
593
-
594
- return new Response(JSON.stringify(response.data), {
595
- status: response.status,
596
- headers: response.headers as any,
597
- });
598
- },
599
- };
600
-
601
- const client = new QiniuAI({
602
- apiKey: 'Sk-xxx',
603
- adapter: axiosAdapter,
604
- });
605
- ```
606
-
607
- ### Modules
608
-
609
- #### `client.chat`
610
-
611
- - `create(params: ChatCompletionRequest): Promise<ChatCompletionResponse>`
612
- - `createStream(params: ChatCompletionRequest): AsyncGenerator<ChatCompletionChunk>` (New!)
613
-
614
- **Streaming Example with Function Calling & Reasoning:**
615
-
616
- ```typescript
617
- const stream = await client.chat.createStream({
618
- model: 'gemini-2.5-flash', // Models supporting reasoning
619
- messages: [{ role: 'user', content: 'Solve this puzzle...' }],
620
- });
621
-
622
- for await (const chunk of stream) {
623
- const delta = chunk.choices[0]?.delta;
624
-
625
- // Text content
626
- if (delta?.content) {
627
- process.stdout.write(delta.content);
628
- }
629
-
630
- // Reasoning content (Gemini/Claude thinking process)
631
- if (delta?.reasoning_content) {
632
- console.log('[Thinking]:', delta.reasoning_content);
633
- }
634
-
635
- // Function calling arguments are also streamed incrementally
636
- }
637
- ```
638
-
639
- #### `client.image`
640
-
641
- - `generate(params: ImageGenerationRequest): Promise<ImageCreateResult>`
642
- - `waitForResult(result: ImageCreateResult, options?: WaitOptions): Promise<ImageGenerateResult>`
643
- - `create(params: ImageGenerationRequest): Promise<{ task_id: string }>` (deprecated)
644
- - `edit(params: ImageEditRequest): Promise<ImageEditResponse>`
645
- - `get(taskId: string): Promise<ImageTaskResponse>`
646
- - `waitForCompletion(taskId: string, options?: WaitOptions): Promise<ImageTaskResponse>` (deprecated)
647
-
648
- Note: For sync models (Gemini), `waitForResult` returns `status: 'succeed'` set by the SDK when data is returned.
649
-
650
- **Image Edit (Kling/Gemini):**
651
-
652
- ```typescript
653
- // Kling multi-image edit
654
- const editTask = await client.image.edit({
655
- model: 'kling-v1',
656
- prompt: 'Make it watercolor style',
657
- image_reference: 'subject',
658
- subject_image_list: [{ image: 'https://example.com/subject.jpg', image_type: 'subject' }],
659
- scene_image: { image: 'https://example.com/scene.jpg', image_type: 'scene' },
660
- style_image: { image: 'https://example.com/style.jpg', image_type: 'style' },
661
- });
662
-
663
- // Gemini edit
664
- const geminiEdit = await client.image.edit({
665
- model: 'gemini-3.0-pro-image-preview',
666
- prompt: 'Add a sunset sky',
667
- image_url: 'https://example.com/input.png',
668
- image_config: { aspect_ratio: '16:9', image_size: '2K' },
669
- mask: 'base64-mask-data',
670
- });
671
- ```
672
-
673
- #### `client.video`
674
-
675
- - `create(params: VideoGenerationRequest): Promise<{ id: string }>`
676
- - `get(id: string): Promise<VideoTaskResponse>`
677
- - `remix(id: string, params: VideoRemixRequest): Promise<{ id: string }>`
678
- - `waitForCompletion(id: string, options?: WaitOptions): Promise<VideoTaskResponse>`
679
-
680
- **First & Last Frame Video Generation:**
681
-
682
- The SDK provides a unified `frames` parameter that works across all models (Kling, Veo):
683
-
684
- ```typescript
685
- // Kling first/last frame (multi-frame generation)
686
- const klingTask = await client.video.create({
687
- model: 'kling-video-o1',
688
- prompt: '่ง†้ข‘่ฟž่ดฏๅœจไธ€่ตท',
689
- frames: {
690
- first: { url: 'https://example.com/start.jpg' },
691
- last: { url: 'https://example.com/end.jpg' }
692
- },
693
- size: '1920x1080',
694
- mode: 'pro'
695
- });
696
-
697
- // Veo first/last frame
698
- const veoTask = await client.video.create({
699
- model: 'veo-2.0-generate-001',
700
- prompt: 'A cat jumping from chair to table',
701
- frames: {
702
- first: { url: 'https://example.com/cat-chair.jpg' },
703
- last: { url: 'https://example.com/cat-table.jpg' }
704
- },
705
- generate_audio: true,
706
- resolution: '720p',
707
- seed: 12345,
708
- sample_count: 1
709
- });
710
-
711
- // Wait for completion (works with both Kling and Veo)
712
- const result = await client.video.waitForCompletion(veoTask.id);
713
- console.log(result.task_result?.videos[0].url);
714
- ```
715
-
716
- **Video Reference Generation (Kling):**
717
-
718
- ```typescript
719
- const task = await client.video.create({
720
- model: 'kling-video-o1',
721
- prompt: '่žๅˆ่ง†้ข‘้ฃŽๆ ผ็”Ÿๆˆๆ–ฐๅ†…ๅฎน',
722
- video_list: [{
723
- video_url: 'https://example.com/reference.mp4',
724
- refer_type: 'base',
725
- keep_original_sound: 'yes'
726
- }]
727
- });
728
- ```
729
-
730
- **Kling Native Parameters:**
731
-
732
- For backward compatibility, you can also use Kling's native parameters directly:
733
-
734
- ```typescript
735
- // Using image_list directly (kling-video-o1)
736
- const task = await client.video.create({
737
- model: 'kling-video-o1',
738
- prompt: '...',
739
- image_list: [
740
- { image: 'https://...', type: 'first_frame' },
741
- { image: 'https://...', type: 'end_frame' }
742
- ]
743
- });
744
-
745
- // Using image_tail (kling-v2-5-turbo)
746
- const task = await client.video.create({
747
- model: 'kling-v2-5-turbo',
748
- prompt: '...',
749
- input_reference: 'https://example.com/start.jpg',
750
- image_tail: 'https://example.com/end.jpg'
751
- });
752
- ```
753
-
754
- **Video Remix (Sora):**
755
-
756
- ```typescript
757
- const remixTask = await client.video.remix('videos-123...', {
758
- prompt: 'Make it cinematic',
759
- });
760
- console.log(remixTask.id);
761
- ```
762
-
763
- #### `client.ocr`
764
-
765
- - `detect(params: OcrRequest): Promise<OcrResponse>`
766
-
767
- **OCR Example:**
768
-
769
- ```typescript
770
- const ocrResult = await client.ocr.detect({
771
- url: 'https://static.qiniu.com/ai-inference/example-resources/ocr-example.png',
772
- });
773
- console.log(ocrResult.text);
774
- ```
775
-
776
- #### `client.asr`
777
-
778
- - `transcribe(params: AsrRequest): Promise<AsrResponse>`
779
-
780
- **ASR Example:**
781
-
782
- ```typescript
783
- const asrResult = await client.asr.transcribe({
784
- audio: {
785
- format: 'mp3',
786
- url: 'https://static.qiniu.com/ai-inference/example-resources/example.mp3',
787
- },
788
- });
789
- console.log(asrResult.text);
790
- ```
791
-
792
- #### `client.account`
793
-
794
- - `usage(params: UsageQuery): Promise<UsageResponse>`
795
-
796
- **Account Usage Example (API Key):**
797
-
798
- ```typescript
799
- const usage = await client.account.usage({
800
- granularity: 'day',
801
- start: '2024-01-01T00:00:00+08:00',
802
- end: '2024-01-31T23:59:59+08:00',
803
- });
804
- console.log(usage.data.length);
805
- ```
806
-
807
- **Account Usage Example (AK/SK):**
808
-
809
- ```typescript
810
- const usage = await client.account.usage({
811
- granularity: 'day',
812
- start: '2024-01-01T00:00:00+08:00',
813
- end: '2024-01-31T23:59:59+08:00',
814
- auth: {
815
- accessKey: 'your-ak',
816
- secretKey: 'your-sk',
817
- },
818
- });
819
- console.log(usage.data.length);
278
+ // AgentGraph will now emit spans:
279
+ // - agent_graph.invoke
280
+ // - agent_graph.predict
281
+ // - agent_graph.execute
820
282
  ```
821
283
 
822
- #### `client.sys`
823
-
824
- - `search(params: WebSearchRequest): Promise<WebSearchResult[]>`
825
-
826
- ### Advanced Usage: Generic API Access
827
-
828
- For features not yet fully wrapped in modules, use the generic `post` and `get` methods.
829
-
830
- **OCR (Optical Character Recognition):**
831
-
832
- ```typescript
833
- const response = await client.post<any>('/images/ocr', {
834
- model: 'ocr',
835
- url: 'https://example.com/image.png'
836
- });
837
- console.log(response.data.result.text);
838
- ```
839
-
840
- **TTS (Text to Speech):**
841
-
842
- ```typescript
843
- // Get Voice List
844
- const voices = await client.get<any[]>('/voice/list');
845
-
846
- // Synthesize Audio
847
- const res = await client.post<any>('/voice/tts', {
848
- request: { text: 'Hello world' },
849
- audio: { voice_type: 'qiniu_zh_female_xxx' }
850
- });
851
- ```
852
-
853
- ### Wait Options
854
-
855
- For `waitForCompletion` and `waitForResult` methods:
856
-
857
- ```typescript
858
- interface WaitOptions {
859
- intervalMs?: number; // Polling interval (default: 2000 for image, 3000 for video)
860
- timeoutMs?: number; // Max wait time (default: 120000 for image, 600000 for video)
861
- signal?: AbortSignal; // For cancellation support
862
- maxRetries?: number; // Max retries for transient errors (default: 3)
863
- }
864
- ```
865
-
866
- ### Error Handling
284
+ ---
867
285
 
868
- ```typescript
869
- import { QiniuAI, APIError } from '@bowenqt/qiniu-ai-sdk';
870
-
871
- try {
872
- await client.chat.create({ ... });
873
- } catch (error) {
874
- if (error instanceof APIError) {
875
- console.log(error.status); // HTTP status code
876
- console.log(error.code); // API error code (if any)
877
- console.log(error.message); // Error message
878
- }
879
- }
880
- ```
286
+ ## ๐Ÿ“š Supported Models
881
287
 
882
- ### Cancellation
288
+ ### Chat & Reasoning (66+ models)
883
289
 
884
- ```typescript
885
- const controller = new AbortController();
886
-
887
- // Cancel after 10 seconds
888
- setTimeout(() => controller.abort(), 10000);
889
-
890
- try {
891
- const createResult = await client.image.generate({
892
- model: 'kling-v2',
893
- prompt: 'A cute cat',
894
- });
895
- const result = await client.image.waitForResult(createResult, {
896
- signal: controller.signal,
897
- });
898
- } catch (error) {
899
- if (error.message === 'Operation cancelled') {
900
- console.log('Task was cancelled');
901
- }
902
- }
903
- ```
904
-
905
- ## Supported Models
906
-
907
- ### Chat & Reasoning (66 models)
290
+ | Provider | Models |
291
+ |----------|--------|
292
+ | **Qwen** | qwen3-235b, qwen3-max, qwen3-32b, qwen-turbo |
293
+ | **Claude** | claude-4.5-opus/sonnet/haiku, claude-4.0-opus/sonnet, claude-3.7-sonnet, claude-3.5-sonnet/haiku |
294
+ | **Gemini** | gemini-3.0-flash/pro, gemini-2.5-flash/pro, gemini-2.0-flash |
295
+ | **DeepSeek** | deepseek-r1, deepseek-v3/v3.1/v3.2 |
296
+ | **Doubao** | doubao-seed-1.6, doubao-1.5-pro |
297
+ | **GLM** | glm-4.5/4.6/4.7 |
298
+ | **Grok** | grok-4-fast, grok-4.1-fast |
299
+ | **OpenAI** | gpt-5/5.2, gpt-oss-20b/120b |
300
+ | **Kimi** | kimi-k2 |
301
+ | **MiniMax** | minimax-m2/m2.1 |
302
+
303
+ ### Image Generation
908
304
 
909
305
  | Provider | Models |
910
306
  |----------|--------|
911
- | **Qwen** | `qwen3-235b-a22b-thinking-2507`, `qwen3-235b-a22b-instruct-2507`, `qwen3-235b-a22b`, `qwen3-max-preview`, `qwen3-max`, `qwen3-32b`, `qwen3-30b-a3b`, `qwen3-next-80b-a3b-thinking`, `qwen3-next-80b-a3b-instruct`, `qwen3-coder-480b-a35b-instruct`, `qwen-max-2025-01-25`, `qwen-turbo` |
912
- | **Claude** | `claude-4.5-opus`, `claude-4.5-haiku`, `claude-4.5-sonnet`, `claude-4.1-opus`, `claude-4.0-opus`, `claude-4.0-sonnet`, `claude-3.7-sonnet`, `claude-3.5-sonnet`, `claude-3.5-haiku` |
913
- | **Gemini** | `gemini-3.0-flash-preview`, `gemini-3.0-pro-preview`, `gemini-2.5-flash-lite`, `gemini-2.5-flash`, `gemini-2.5-pro`, `gemini-2.0-flash-lite`, `gemini-2.0-flash` |
914
- | **DeepSeek** | `deepseek-r1-0528`, `deepseek-r1`, `deepseek-v3`, `deepseek-v3-0324`, `deepseek-v3.1`, `deepseek/deepseek-v3.2-251201`, `deepseek/deepseek-v3.2-exp-thinking`, `deepseek/deepseek-v3.2-exp`, `deepseek/deepseek-v3.1-terminus-thinking`, `deepseek/deepseek-v3.1-terminus` |
915
- | **Doubao** | `doubao-seed-1.6-thinking`, `doubao-seed-1.6-flash`, `doubao-seed-1.6`, `doubao-1.5-thinking-pro`, `doubao-1.5-pro-32k` |
916
- | **GLM** | `glm-4.5`, `glm-4.5-air`, `z-ai/glm-4.7`, `z-ai/glm-4.6` |
917
- | **Grok** | `x-ai/grok-4-fast-reasoning`, `x-ai/grok-4-fast-non-reasoning`, `x-ai/grok-4-fast`, `x-ai/grok-4.1-fast-non-reasoning`, `x-ai/grok-4.1-fast-reasoning`, `x-ai/grok-4.1-fast`, `x-ai/grok-code-fast-1` |
918
- | **OpenAI** | `openai/gpt-5.2`, `openai/gpt-5`, `gpt-oss-20b`, `gpt-oss-120b` |
919
- | **Kimi** | `moonshotai/kimi-k2-thinking`, `moonshotai/kimi-k2-0905`, `kimi-k2` |
920
- | **MiniMax** | `minimax/minimax-m2`, `minimax/minimax-m2.1`, `MiniMax-M1`, `mimo-v2-flash` |
921
- | **Meituan** | `meituan/longcat-flash-chat` |
922
- | **StepFun** | `stepfun-ai/gelab-zero-4b-preview` |
923
- | **AutoGLM** | `z-ai/autoglm-phone-9b` |
924
-
925
- ### Image Generation & Vision
926
-
927
- | Type | Models |
928
- |------|--------|
929
- | **Kling** | `kling-v1`, `kling-v1-5`, `kling-v2`, `kling-v2-new`, `kling-v2-1` |
930
- | **Gemini** | `gemini-3.0-pro-image-preview`, `gemini-2.5-flash-image` |
931
- | **Vision** | `doubao-1.5-vision-pro`, `qwen2.5-vl-7b-instruct`, `qwen2.5-vl-72b-instruct`, `qwen-vl-max-2025-01-25` |
307
+ | **Kling** | kling-v1, kling-v1-5, kling-v2, kling-v2-1 |
308
+ | **Gemini** | gemini-3.0-pro-image, gemini-2.5-flash-image |
932
309
 
933
310
  ### Video Generation
934
311
 
935
312
  | Provider | Models |
936
313
  |----------|--------|
937
- | **Kling** | `kling-video-o1`, `kling-v2-1`, `kling-v2-5-turbo` |
938
- | **Sora** | `sora-2` |
939
- | **Veo** | `veo-2.0-generate-001`, `veo-3.0-generate-001`, `veo-3.0-fast-generate-001`, `veo-3.0-generate-preview`, `veo-3.0-fast-generate-preview`, `veo-3.1-generate-preview`, `veo-3.1-fast-generate-preview` |
940
- | **Other** | `minimax/minimax-m2`, `mimo-v2-flash` |
314
+ | **Kling** | kling-video-o1, kling-v2-1, kling-v2-5-turbo |
315
+ | **Sora** | sora-2 |
316
+ | **Veo** | veo-2.0, veo-3.0, veo-3.1 |
941
317
 
942
- ### OCR (ๆ–‡ๅญ—่ฏ†ๅˆซ)
318
+ ---
319
+
320
+ ## ๐Ÿ“ Package Exports
321
+
322
+ | Entry Point | Description |
323
+ |-------------|-------------|
324
+ | `@bowenqt/qiniu-ai-sdk` | Main entry (universal) |
325
+ | `@bowenqt/qiniu-ai-sdk/node` | Node.js-only features (SkillLoader, MCPClient stdio) |
326
+ | `@bowenqt/qiniu-ai-sdk/browser` | Browser-compatible subset |
327
+ | `@bowenqt/qiniu-ai-sdk/adapter` | Vercel AI SDK adapter |
328
+ | `@bowenqt/qiniu-ai-sdk/ai-tools` | Qiniu native cloud tools (OCR/Censor/Vframe) |
329
+
330
+ ---
943
331
 
944
- | Model | Description |
945
- |-------|-------------|
946
- | `ocr` | ๅ›พ็‰‡/PDFๆ–‡ๆกฃ้ซ˜็ฒพๅบฆๆ–‡ๅญ—่ฏ†ๅˆซ๏ผŒๆ”ฏๆŒ PNGใ€JPGใ€PDF ็ญ‰ๆ ผๅผ |
332
+ ## ๐Ÿ› ๏ธ CLI: MCP Server
947
333
 
948
- ### ASR (่ฏญ้Ÿณ่ฏ†ๅˆซ)
334
+ Run the built-in Qiniu MCP Server:
949
335
 
950
- | Model | Description |
951
- |-------|-------------|
952
- | `asr` | ไธญ่‹ฑ็ญ‰ๅคš่ฏญ็ง่ฏญ้Ÿณ่ฏ†ๅˆซ๏ผŒๅ˜ˆๆ‚็Žฏๅขƒ่ฏ†ๅˆซๅ‡†็กฎ็އ่ถ…95%๏ผŒๆ”ฏๆŒ raw/wav/mp3/ogg ๆ ผๅผ |
336
+ ```bash
337
+ npx qiniu-mcp-server
338
+ ```
953
339
 
954
- ### TTS (่ฏญ้Ÿณๅˆๆˆ)
340
+ **Environment variables:**
341
+ - `QINIU_API_KEY` โ€” API key for OCR/Censor operations
342
+ - `QINIU_ACCESS_KEY` / `QINIU_SECRET_KEY` โ€” For Vframe/signed operations
955
343
 
956
- ้€š่ฟ‡ `/voice/list` ๆŽฅๅฃ่Žทๅ–ๅฎŒๆ•ด้Ÿณ่‰ฒๅˆ—่กจ๏ผŒไฝฟ็”จ `voice_type` ๅ‚ๆ•ฐๆŒ‡ๅฎš้Ÿณ่‰ฒ๏ผš
344
+ ---
957
345
 
958
- | Voice Type | Description |
959
- |------------|-------------|
960
- | `qiniu_zh_female_tmjxxy` | ็”œ็พŽๆ•™ๅญฆๅฐๆบ |
961
- | `qiniu_zh_female_wwxkjx` | ๆธฉๅฉ‰ๅฐ่ฏพๅ ‚ |
962
- | ... | ๆ›ดๅคš้Ÿณ่‰ฒ่ฏท้€š่ฟ‡ API ่Žทๅ– |
346
+ ## ๐Ÿ“– Documentation
347
+
348
+ - **[COOKBOOK.md](./COOKBOOK.md)** โ€” Copy-ready code examples for all features
349
+ - **[Qiniu AI Developer Center](https://developer.qiniu.com/aitokenapi)** โ€” Full API reference & pricing
963
350
 
964
351
  ---
965
352
 
966
- **Summary**: 66 Chat models, 11 Image models, 12 Video models, OCR, ASR, TTS
353
+ ## ๐Ÿ“„ License
967
354
 
968
- For the full list and pricing, check [Qiniu AI Developer Center](https://developer.qiniu.com/aitokenapi).
355
+ MIT ยฉ 2024-2026
969
356
 
970
- ## License
357
+ ---
971
358
 
972
- MIT
359
+ <div align="center">
360
+ <sub>Built with โค๏ธ for the Qiniu Cloud ecosystem</sub>
361
+ </div>