@wwkit/freetoken 1.0.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/LICENSE +21 -0
- package/README.md +96 -0
- package/cli/helpers/args.js +48 -0
- package/cli/index.js +374 -0
- package/cli/pid-manager.js +87 -0
- package/client/index.html +25 -0
- package/client/public/favicon.svg +1 -0
- package/client/public/icons.svg +24 -0
- package/client/src/App.vue +136 -0
- package/client/src/assets/vite.svg +1 -0
- package/client/src/assets/vue.svg +1 -0
- package/client/src/components/ChatBox.vue +320 -0
- package/client/src/components/CheckList.vue +52 -0
- package/client/src/components/CodeBlock.vue +99 -0
- package/client/src/components/DetailBlock.vue +47 -0
- package/client/src/components/HelloWorld.vue +95 -0
- package/client/src/components/LangSwitch.vue +32 -0
- package/client/src/components/LinkList.vue +32 -0
- package/client/src/components/OsSwitch.vue +42 -0
- package/client/src/components/OsToggle.vue +15 -0
- package/client/src/components/PageHeader.vue +31 -0
- package/client/src/components/StepList.vue +92 -0
- package/client/src/composables/useClipboard.js +26 -0
- package/client/src/composables/useOs.js +22 -0
- package/client/src/data/docs/agent.js +1002 -0
- package/client/src/data/docs/auth.js +631 -0
- package/client/src/data/docs/builtin-tools.js +322 -0
- package/client/src/data/docs/chat.js +181 -0
- package/client/src/data/docs/index.js +9 -0
- package/client/src/data/docs/skills.js +396 -0
- package/client/src/data/docs/spec-conversion.js +1042 -0
- package/client/src/data/freeModels/bluesliu.js +97 -0
- package/client/src/data/freeModels/index.js +11 -0
- package/client/src/data/freeModels/nvidia.js +126 -0
- package/client/src/data/freeModels/openrouter.js +116 -0
- package/client/src/data/harness/claude.js +89 -0
- package/client/src/data/harness/codex.js +66 -0
- package/client/src/data/harness/dsh.js +86 -0
- package/client/src/data/harness/hermes.js +56 -0
- package/client/src/data/harness/index.js +22 -0
- package/client/src/data/harness/opencode.js +77 -0
- package/client/src/data/proxy/api-relay.js +53 -0
- package/client/src/data/proxy/builtin-proxy.js +54 -0
- package/client/src/data/proxy/cf-workers.js +67 -0
- package/client/src/data/proxy/ecs-forward.js +75 -0
- package/client/src/data/proxy/ecs-reverse.js +89 -0
- package/client/src/data/proxy/index.js +15 -0
- package/client/src/docs/claudecode.md +44 -0
- package/client/src/docs/codex.md +90 -0
- package/client/src/docs/hermesagent.md +42 -0
- package/client/src/docs/opencode.md +103 -0
- package/client/src/locales/en.js +436 -0
- package/client/src/locales/index.js +31 -0
- package/client/src/locales/zh-CN.js +461 -0
- package/client/src/main.js +16 -0
- package/client/src/router/index.js +76 -0
- package/client/src/style.css +15 -0
- package/client/src/views/AdminModelsView.vue +214 -0
- package/client/src/views/DocsView.vue +1604 -0
- package/client/src/views/FreeModelsView.vue +518 -0
- package/client/src/views/HarnessView.vue +314 -0
- package/client/src/views/HomeView.vue +147 -0
- package/client/src/views/ProxyView.vue +112 -0
- package/client/src/views/TokenMarketView.vue +26 -0
- package/client/vite.config.js +41 -0
- package/package.json +85 -0
- package/scripts/build-zip.sh +61 -0
- package/scripts/postinstall.js +7 -0
- package/server/src/config/targets.json +1 -0
- package/server/src/index.js +52 -0
- package/server/src/lib/coding-test.js +215 -0
- package/server/src/lib/database.js +353 -0
- package/server/src/lib/run-test.js +15 -0
- package/server/src/lib/scheduler.js +21 -0
- package/server/src/lib/tester.js +310 -0
- package/server/src/routes/admin.js +48 -0
- package/server/src/routes/proxy.js +64 -0
- package/server/src/routes/speed.js +87 -0
- package/src/config.js +45 -0
- package/src/config.json5 +27 -0
- package/src/index.js +10 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
// Agent 子模块:Tool Calling 全流程详解(基于真实 opencode agent 数据)
|
|
2
|
+
import { builtinTools } from './builtin-tools.js'
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
id: 'agent',
|
|
6
|
+
name: 'Agent',
|
|
7
|
+
badge: 'Tool Call',
|
|
8
|
+
tagline: '大语言模型 Agent 模式:工具调用全流程',
|
|
9
|
+
description:
|
|
10
|
+
'当 Chat 接口携带 tools 参数时,大语言模型进入 Agent 模式——模型不再直接回答问题,而是决定调用哪些工具、传入什么参数,拿到工具执行结果后再生成最终回复。本节使用 opencode agent 的真实工具定义和接口数据,展示 tool 从选择到执行的完整流程。',
|
|
11
|
+
|
|
12
|
+
// ── 工具定义格式 ──
|
|
13
|
+
toolDefs: {
|
|
14
|
+
title: '工具定义格式',
|
|
15
|
+
description:
|
|
16
|
+
'tools 数组中每个元素描述一个可供模型调用的工具。模型根据工具的 name 和 description 决定是否调用,根据 parameters JSON Schema 构造调用参数。',
|
|
17
|
+
tools: [
|
|
18
|
+
{
|
|
19
|
+
type: 'function',
|
|
20
|
+
function: {
|
|
21
|
+
name: 'bash',
|
|
22
|
+
description:
|
|
23
|
+
'Executes a given bash command in a persistent shell session with optional timeout.',
|
|
24
|
+
parameters: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
command: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
description: 'The command to execute',
|
|
30
|
+
},
|
|
31
|
+
workdir: {
|
|
32
|
+
type: 'string',
|
|
33
|
+
description: 'The working directory to run the command in',
|
|
34
|
+
},
|
|
35
|
+
timeout: {
|
|
36
|
+
type: 'integer',
|
|
37
|
+
description: 'Optional timeout in milliseconds',
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
required: ['command'],
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: 'function',
|
|
46
|
+
function: {
|
|
47
|
+
name: 'read',
|
|
48
|
+
description: 'Read a file or directory from the local filesystem.',
|
|
49
|
+
parameters: {
|
|
50
|
+
type: 'object',
|
|
51
|
+
properties: {
|
|
52
|
+
filePath: {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'The absolute path to the file to read',
|
|
55
|
+
},
|
|
56
|
+
offset: {
|
|
57
|
+
type: 'integer',
|
|
58
|
+
description: 'The line number to start reading from (1-indexed)',
|
|
59
|
+
},
|
|
60
|
+
limit: {
|
|
61
|
+
type: 'integer',
|
|
62
|
+
description: 'The maximum number of lines to read',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
required: ['filePath'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
type: 'function',
|
|
71
|
+
function: {
|
|
72
|
+
name: 'edit',
|
|
73
|
+
description: 'Performs exact string replacements in files.',
|
|
74
|
+
parameters: {
|
|
75
|
+
type: 'object',
|
|
76
|
+
properties: {
|
|
77
|
+
filePath: {
|
|
78
|
+
type: 'string',
|
|
79
|
+
description: 'The absolute path to the file to modify',
|
|
80
|
+
},
|
|
81
|
+
oldString: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'The text to replace',
|
|
84
|
+
},
|
|
85
|
+
newString: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
description: 'The text to replace it with',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
required: ['filePath', 'oldString', 'newString'],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: 'function',
|
|
96
|
+
function: {
|
|
97
|
+
name: 'glob',
|
|
98
|
+
description: 'Fast file pattern matching tool that works with any codebase size.',
|
|
99
|
+
parameters: {
|
|
100
|
+
type: 'object',
|
|
101
|
+
properties: {
|
|
102
|
+
pattern: {
|
|
103
|
+
type: 'string',
|
|
104
|
+
description: 'The glob pattern to match files against',
|
|
105
|
+
},
|
|
106
|
+
path: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: 'The directory to search in',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
required: ['pattern'],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
type: 'function',
|
|
117
|
+
function: {
|
|
118
|
+
name: 'grep',
|
|
119
|
+
description: 'Fast content search tool that works with any codebase size.',
|
|
120
|
+
parameters: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
pattern: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'The regex pattern to search for in file contents',
|
|
126
|
+
},
|
|
127
|
+
include: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
description: 'File pattern to include in the search',
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
required: ['pattern'],
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
],
|
|
137
|
+
notes: [
|
|
138
|
+
'每个工具的 parameters 遵循 JSON Schema 规范,模型据此构造合法参数',
|
|
139
|
+
'description 是模型选择工具的核心依据——描述越清晰,工具选择越准确',
|
|
140
|
+
'required 数组标注必填参数,模型必须为这些参数生成值',
|
|
141
|
+
'当模型返回 tool_calls 时,content 字段通常为 null——模型此时不生成自然语言文本,而是表达"我需要先调用工具"。部分模型也会在 content 中附带一段思考说明(如"让我先查看文件内容"),但 content 为 null 是主流行为,客户端应兼容处理',
|
|
142
|
+
'工具结果必须以 role: "tool" 消息回传,并携带对应的 tool_call_id。如果错误地使用 role: "user" 包装工具结果,大多数 OpenAI 兼容模型会报 400 错误(消息角色序列不合法),因为模型期望在 assistant 的 tool_calls 之后紧跟 role: "tool" 消息。少数模型可能不报错但会丢失工具结果的语义关联,导致模型"看不到"执行结果而重复调用同一工具或直接胡编答案',
|
|
143
|
+
'示例 2 的并行多工具调用和示例 3 的多轮工具调用均为模型自主决策,而非客户端指令——客户端在每次请求中只传 messages + tools,不指定"请并行调用"或"请分两轮调用"。是否一次返回多个 tool_calls、是否在拿到结果后继续调用下一个工具,完全由模型根据任务语义推理决定。客户端唯一能施加的影响是 tool_choice 参数:"auto"(默认,模型自主决定)、"required"(强制必须调用工具)、"none"(禁止调用),但具体调用哪个工具、几个工具、分几轮,始终是模型的决策',
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// ── 示例列表 ──
|
|
148
|
+
examples: [
|
|
149
|
+
// 示例 1:单工具调用
|
|
150
|
+
{
|
|
151
|
+
id: 'single-tool',
|
|
152
|
+
title: '示例 1:单工具调用(bash)',
|
|
153
|
+
scenario: '用户让 agent 列出当前项目的文件结构',
|
|
154
|
+
description:
|
|
155
|
+
'最基础的 Agent 交互:模型收到用户问题后,判断需要执行 shell 命令,调用 bash 工具,拿到输出后组织成自然语言回复。',
|
|
156
|
+
steps: [
|
|
157
|
+
{
|
|
158
|
+
step: 1,
|
|
159
|
+
type: 'user-message',
|
|
160
|
+
title: '用户提问',
|
|
161
|
+
label: 'User',
|
|
162
|
+
labelType: 'primary',
|
|
163
|
+
content:
|
|
164
|
+
'帮我看一下当前项目的 client 目录下有哪些文件?',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
step: 2,
|
|
168
|
+
type: 'request',
|
|
169
|
+
title: '请求(携带 tools 定义)',
|
|
170
|
+
label: 'Request',
|
|
171
|
+
labelType: 'info',
|
|
172
|
+
description:
|
|
173
|
+
'客户端将用户消息和可用工具列表一起发送给模型。模型分析后决定调用 bash 工具。',
|
|
174
|
+
code: `POST /v1/chat/completions
|
|
175
|
+
Authorization: Bearer sk-xxx
|
|
176
|
+
|
|
177
|
+
{
|
|
178
|
+
"model": "maas-glm-5.1-zhipu",
|
|
179
|
+
"messages": [
|
|
180
|
+
{
|
|
181
|
+
"role": "user",
|
|
182
|
+
"content": "帮我看一下当前项目的 client 目录下有哪些文件?"
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"tools": [
|
|
186
|
+
{
|
|
187
|
+
"type": "function",
|
|
188
|
+
"function": {
|
|
189
|
+
"name": "bash",
|
|
190
|
+
"description": "Executes a given bash command in a persistent shell session.",
|
|
191
|
+
"parameters": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"properties": {
|
|
194
|
+
"command": { "type": "string", "description": "The command to execute" }
|
|
195
|
+
},
|
|
196
|
+
"required": ["command"]
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"type": "function",
|
|
202
|
+
"function": {
|
|
203
|
+
"name": "read",
|
|
204
|
+
"description": "Read a file or directory from the local filesystem.",
|
|
205
|
+
"parameters": {
|
|
206
|
+
"type": "object",
|
|
207
|
+
"properties": {
|
|
208
|
+
"filePath": { "type": "string", "description": "The absolute path" }
|
|
209
|
+
},
|
|
210
|
+
"required": ["filePath"]
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"tool_choice": "auto"
|
|
216
|
+
}`,
|
|
217
|
+
codeLang: 'json',
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
step: 3,
|
|
221
|
+
type: 'tool-call',
|
|
222
|
+
title: '模型返回 tool_calls',
|
|
223
|
+
label: 'Model',
|
|
224
|
+
labelType: 'warning',
|
|
225
|
+
description:
|
|
226
|
+
'模型没有直接回答,而是返回 tool_calls,指定调用 bash 工具,参数为 ls 命令。finish_reason 为 "tool_calls" 表示需要执行工具。',
|
|
227
|
+
code: `{
|
|
228
|
+
"id": "chatcmpl-a1b2c3d4",
|
|
229
|
+
"object": "chat.completion",
|
|
230
|
+
"model": "maas-glm-5.1-zhipu",
|
|
231
|
+
"choices": [
|
|
232
|
+
{
|
|
233
|
+
"index": 0,
|
|
234
|
+
"message": {
|
|
235
|
+
"role": "assistant",
|
|
236
|
+
"content": null,
|
|
237
|
+
"tool_calls": [
|
|
238
|
+
{
|
|
239
|
+
"id": "call_abc123",
|
|
240
|
+
"type": "function",
|
|
241
|
+
"function": {
|
|
242
|
+
"name": "bash",
|
|
243
|
+
"arguments": "{\\"command\\": \\"ls -la client/\\"}"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
"finish_reason": "tool_calls"
|
|
249
|
+
}
|
|
250
|
+
],
|
|
251
|
+
"usage": { "prompt_tokens": 156, "completion_tokens": 28, "total_tokens": 184 }
|
|
252
|
+
}`,
|
|
253
|
+
codeLang: 'json',
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
step: 4,
|
|
257
|
+
type: 'execution',
|
|
258
|
+
title: '工具执行',
|
|
259
|
+
label: 'Tool',
|
|
260
|
+
labelType: 'success',
|
|
261
|
+
description:
|
|
262
|
+
'客户端解析 tool_calls,提取工具名 bash 和参数 { command: "ls -la client/" },在本地执行该命令。',
|
|
263
|
+
toolName: 'bash',
|
|
264
|
+
toolInput: `ls -la client/`,
|
|
265
|
+
toolOutput: `total 24
|
|
266
|
+
drwxr-xr-x 3 user user 4096 Sep 22 10:00 .
|
|
267
|
+
drwxr-xr-x 8 user user 4096 Sep 22 10:00 ..
|
|
268
|
+
-rw-r--r-- 1 user user 412 Sep 22 10:00 index.html
|
|
269
|
+
drwxr-xr-x 2 user user 4096 Sep 22 10:00 public/
|
|
270
|
+
drwxr-xr-x 5 user user 4096 Sep 22 10:00 src/
|
|
271
|
+
-rw-r--r-- 1 user user 892 Sep 22 10:00 vite.config.js`,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
step: 5,
|
|
275
|
+
type: 'tool-result',
|
|
276
|
+
title: '工具结果回传',
|
|
277
|
+
label: 'Tool Result',
|
|
278
|
+
labelType: 'success',
|
|
279
|
+
description:
|
|
280
|
+
'客户端将工具执行结果以 role: "tool" 消息追加到 messages 数组,连同之前的对话一起再次请求模型。',
|
|
281
|
+
code: `POST /v1/chat/completions
|
|
282
|
+
Authorization: Bearer sk-xxx
|
|
283
|
+
|
|
284
|
+
{
|
|
285
|
+
"model": "maas-glm-5.1-zhipu",
|
|
286
|
+
"messages": [
|
|
287
|
+
{
|
|
288
|
+
"role": "user",
|
|
289
|
+
"content": "帮我看一下当前项目的 client 目录下有哪些文件?"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
"role": "assistant",
|
|
293
|
+
"content": null,
|
|
294
|
+
"tool_calls": [
|
|
295
|
+
{
|
|
296
|
+
"id": "call_abc123",
|
|
297
|
+
"type": "function",
|
|
298
|
+
"function": {
|
|
299
|
+
"name": "bash",
|
|
300
|
+
"arguments": "{\\"command\\": \\"ls -la client/\\"}"
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
]
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
"role": "tool",
|
|
307
|
+
"tool_call_id": "call_abc123",
|
|
308
|
+
"content": "total 24\\ndrwxr-xr-x 3 user user 4096 Sep 22 10:00 .\\ndrwxr-xr-x 8 user user 4096 Sep 22 10:00 ..\\n-rw-r--r-- 1 user user 412 Sep 22 10:00 index.html\\ndrwxr-xr-x 2 user user 4096 Sep 22 10:00 public/\\ndrwxr-xr-x 5 user user 4096 Sep 22 10:00 src/\\n-rw-r--r-- 1 user user 892 Sep 22 10:00 vite.config.js"
|
|
309
|
+
}
|
|
310
|
+
],
|
|
311
|
+
"tools": [ /* ... 同上 ... */ ]
|
|
312
|
+
}`,
|
|
313
|
+
codeLang: 'json',
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
step: 6,
|
|
317
|
+
type: 'final-response',
|
|
318
|
+
title: '模型最终回复',
|
|
319
|
+
label: 'Final',
|
|
320
|
+
labelType: 'primary',
|
|
321
|
+
description: '模型根据工具返回的文件列表,组织成自然语言回复给用户。',
|
|
322
|
+
code: `{
|
|
323
|
+
"id": "chatcmpl-e5f6g7h8",
|
|
324
|
+
"object": "chat.completion",
|
|
325
|
+
"model": "maas-glm-5.1-zhipu",
|
|
326
|
+
"choices": [
|
|
327
|
+
{
|
|
328
|
+
"index": 0,
|
|
329
|
+
"message": {
|
|
330
|
+
"role": "assistant",
|
|
331
|
+
"content": "client 目录下有以下文件和子目录:\\n\\n- **index.html** — HTML 入口文件\\n- **public/** — 静态资源目录\\n- **src/** — 源代码目录(包含 components、views 等)\\n- **vite.config.js** — Vite 构建配置文件"
|
|
332
|
+
},
|
|
333
|
+
"finish_reason": "stop"
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
}`,
|
|
337
|
+
codeLang: 'json',
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
// 示例 2:并行多工具调用
|
|
343
|
+
{
|
|
344
|
+
id: 'parallel-tools',
|
|
345
|
+
title: '示例 2:并行多工具调用(read + glob)',
|
|
346
|
+
scenario: '用户让 agent 查看项目配置并找到所有 Vue 页面组件',
|
|
347
|
+
description:
|
|
348
|
+
'模型可以在一次响应中返回多个 tool_calls,客户端并行执行这些工具后将所有结果一起回传。这展示了 Agent 模式的并行能力。',
|
|
349
|
+
steps: [
|
|
350
|
+
{
|
|
351
|
+
step: 1,
|
|
352
|
+
type: 'user-message',
|
|
353
|
+
title: '用户提问',
|
|
354
|
+
label: 'User',
|
|
355
|
+
labelType: 'primary',
|
|
356
|
+
content:
|
|
357
|
+
'帮我看一下 package.json 的内容,并列出所有 .vue 文件。',
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
step: 2,
|
|
361
|
+
type: 'request',
|
|
362
|
+
title: '请求(携带 tools 定义)',
|
|
363
|
+
label: 'Request',
|
|
364
|
+
labelType: 'info',
|
|
365
|
+
description:
|
|
366
|
+
'请求中携带 read、glob 等工具定义。模型分析后决定同时调用 read(读 package.json)和 glob(找 .vue 文件)。',
|
|
367
|
+
code: `POST /v1/chat/completions
|
|
368
|
+
Authorization: Bearer sk-xxx
|
|
369
|
+
|
|
370
|
+
{
|
|
371
|
+
"model": "maas-glm-5.1-zhipu",
|
|
372
|
+
"messages": [
|
|
373
|
+
{
|
|
374
|
+
"role": "user",
|
|
375
|
+
"content": "帮我看一下 package.json 的内容,并列出所有 .vue 文件。"
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
"tools": [
|
|
379
|
+
{
|
|
380
|
+
"type": "function",
|
|
381
|
+
"function": {
|
|
382
|
+
"name": "read",
|
|
383
|
+
"description": "Read a file from the local filesystem.",
|
|
384
|
+
"parameters": {
|
|
385
|
+
"type": "object",
|
|
386
|
+
"properties": {
|
|
387
|
+
"filePath": { "type": "string", "description": "The absolute path" }
|
|
388
|
+
},
|
|
389
|
+
"required": ["filePath"]
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
"type": "function",
|
|
395
|
+
"function": {
|
|
396
|
+
"name": "glob",
|
|
397
|
+
"description": "Fast file pattern matching tool.",
|
|
398
|
+
"parameters": {
|
|
399
|
+
"type": "object",
|
|
400
|
+
"properties": {
|
|
401
|
+
"pattern": { "type": "string", "description": "Glob pattern" }
|
|
402
|
+
},
|
|
403
|
+
"required": ["pattern"]
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
],
|
|
408
|
+
"tool_choice": "auto"
|
|
409
|
+
}`,
|
|
410
|
+
codeLang: 'json',
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
step: 3,
|
|
414
|
+
type: 'tool-call',
|
|
415
|
+
title: '模型返回多个 tool_calls(并行)',
|
|
416
|
+
label: 'Model',
|
|
417
|
+
labelType: 'warning',
|
|
418
|
+
description:
|
|
419
|
+
'模型在一次响应中返回两个 tool_calls:read 读取 package.json,glob 查找所有 .vue 文件。两个调用无依赖关系,可并行执行。',
|
|
420
|
+
code: `{
|
|
421
|
+
"id": "chatcmpl-i9j0k1l2",
|
|
422
|
+
"object": "chat.completion",
|
|
423
|
+
"model": "maas-glm-5.1-zhipu",
|
|
424
|
+
"choices": [
|
|
425
|
+
{
|
|
426
|
+
"index": 0,
|
|
427
|
+
"message": {
|
|
428
|
+
"role": "assistant",
|
|
429
|
+
"content": null,
|
|
430
|
+
"tool_calls": [
|
|
431
|
+
{
|
|
432
|
+
"id": "call_def456",
|
|
433
|
+
"type": "function",
|
|
434
|
+
"function": {
|
|
435
|
+
"name": "read",
|
|
436
|
+
"arguments": "{\\"filePath\\": \\"/usr1/wwkit-v1.0.x/packages/freetoken/package.json\\"}"
|
|
437
|
+
}
|
|
438
|
+
},
|
|
439
|
+
{
|
|
440
|
+
"id": "call_ghi789",
|
|
441
|
+
"type": "function",
|
|
442
|
+
"function": {
|
|
443
|
+
"name": "glob",
|
|
444
|
+
"arguments": "{\\"pattern\\": \\"client/src/**/*.vue\\"}"
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
]
|
|
448
|
+
},
|
|
449
|
+
"finish_reason": "tool_calls"
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
}`,
|
|
453
|
+
codeLang: 'json',
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
step: 4,
|
|
457
|
+
type: 'execution',
|
|
458
|
+
title: '工具并行执行',
|
|
459
|
+
label: 'Tools',
|
|
460
|
+
labelType: 'success',
|
|
461
|
+
description: '客户端并行执行两个工具调用,互不阻塞。',
|
|
462
|
+
parallelTools: [
|
|
463
|
+
{
|
|
464
|
+
toolName: 'read',
|
|
465
|
+
toolInput: `filePath: /usr1/wwkit-v1.0.x/packages/freetoken/package.json`,
|
|
466
|
+
toolOutput: `{
|
|
467
|
+
"name": "freetoken",
|
|
468
|
+
"version": "1.0.0",
|
|
469
|
+
"type": "module",
|
|
470
|
+
"bin": { "freetoken": "cli/index.js" },
|
|
471
|
+
"main": "src/index.js",
|
|
472
|
+
"dependencies": {
|
|
473
|
+
"express": "^4.18.0",
|
|
474
|
+
"vue": "^3.4.0",
|
|
475
|
+
"element-plus": "^2.7.0",
|
|
476
|
+
"axios": "^1.6.0"
|
|
477
|
+
}
|
|
478
|
+
}`,
|
|
479
|
+
},
|
|
480
|
+
{
|
|
481
|
+
toolName: 'glob',
|
|
482
|
+
toolInput: `pattern: client/src/**/*.vue`,
|
|
483
|
+
toolOutput: `client/src/App.vue
|
|
484
|
+
client/src/components/ChatBox.vue
|
|
485
|
+
client/src/components/CodeBlock.vue
|
|
486
|
+
client/src/components/DetailBlock.vue
|
|
487
|
+
client/src/components/PageHeader.vue
|
|
488
|
+
client/src/views/AdminModelsView.vue
|
|
489
|
+
client/src/views/FreeModelsView.vue
|
|
490
|
+
client/src/views/HarnessView.vue
|
|
491
|
+
client/src/views/HomeView.vue
|
|
492
|
+
client/src/views/ProxyView.vue
|
|
493
|
+
client/src/views/TokenMarketView.vue`,
|
|
494
|
+
},
|
|
495
|
+
],
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
step: 5,
|
|
499
|
+
type: 'tool-result',
|
|
500
|
+
title: '工具结果回传(多条)',
|
|
501
|
+
label: 'Tool Results',
|
|
502
|
+
labelType: 'success',
|
|
503
|
+
description:
|
|
504
|
+
'客户端将两个工具的结果分别以 role: "tool" 消息追加到 messages,tool_call_id 对应各自的调用 ID。',
|
|
505
|
+
code: `POST /v1/chat/completions
|
|
506
|
+
Authorization: Bearer sk-xxx
|
|
507
|
+
|
|
508
|
+
{
|
|
509
|
+
"model": "maas-glm-5.1-zhipu",
|
|
510
|
+
"messages": [
|
|
511
|
+
{ "role": "user", "content": "帮我看一下 package.json 的内容,并列出所有 .vue 文件。" },
|
|
512
|
+
{
|
|
513
|
+
"role": "assistant",
|
|
514
|
+
"content": null,
|
|
515
|
+
"tool_calls": [
|
|
516
|
+
{ "id": "call_def456", "type": "function", "function": { "name": "read", "arguments": "{\\"filePath\\": \\".../package.json\\"}" } },
|
|
517
|
+
{ "id": "call_ghi789", "type": "function", "function": { "name": "glob", "arguments": "{\\"pattern\\": \\"client/src/**/*.vue\\"}" } }
|
|
518
|
+
]
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
"role": "tool",
|
|
522
|
+
"tool_call_id": "call_def456",
|
|
523
|
+
"content": "{ \\"name\\": \\"freetoken\\", \\"version\\": \\"1.0.0\\", ... }"
|
|
524
|
+
},
|
|
525
|
+
{
|
|
526
|
+
"role": "tool",
|
|
527
|
+
"tool_call_id": "call_ghi789",
|
|
528
|
+
"content": "client/src/App.vue\\nclient/src/components/ChatBox.vue\\n...(共 11 个 .vue 文件)"
|
|
529
|
+
}
|
|
530
|
+
],
|
|
531
|
+
"tools": [ /* ... */ ]
|
|
532
|
+
}`,
|
|
533
|
+
codeLang: 'json',
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
step: 6,
|
|
537
|
+
type: 'final-response',
|
|
538
|
+
title: '模型最终回复',
|
|
539
|
+
label: 'Final',
|
|
540
|
+
labelType: 'primary',
|
|
541
|
+
description: '模型综合两个工具的结果,给出结构化回复。',
|
|
542
|
+
code: `{
|
|
543
|
+
"choices": [
|
|
544
|
+
{
|
|
545
|
+
"message": {
|
|
546
|
+
"role": "assistant",
|
|
547
|
+
"content": "## package.json 摘要\\n\\n- **包名**: freetoken v1.0.0\\n- **类型**: ESM (type: module)\\n- **入口**: src/index.js\\n- **核心依赖**: express, vue 3, element-plus, axios\\n\\n## Vue 文件列表(共 11 个)\\n\\n**页面组件 (views/)**:\\n- AdminModelsView.vue — 模型管理\\n- FreeModelsView.vue — 免费模型\\n- HarnessView.vue — Harness 工具\\n- HomeView.vue — 首页\\n- ProxyView.vue — 代理方案\\n- TokenMarketView.vue — Token 市场\\n\\n**公共组件 (components/)**:\\n- App.vue, ChatBox.vue, CodeBlock.vue, DetailBlock.vue, PageHeader.vue"
|
|
548
|
+
},
|
|
549
|
+
"finish_reason": "stop"
|
|
550
|
+
}
|
|
551
|
+
]
|
|
552
|
+
}`,
|
|
553
|
+
codeLang: 'json',
|
|
554
|
+
},
|
|
555
|
+
],
|
|
556
|
+
},
|
|
557
|
+
|
|
558
|
+
// 示例 3:多轮工具调用
|
|
559
|
+
{
|
|
560
|
+
id: 'multi-turn',
|
|
561
|
+
title: '示例 3:多轮工具调用(read → edit)',
|
|
562
|
+
scenario: '用户让 agent 读取文件并修改其中的内容',
|
|
563
|
+
description:
|
|
564
|
+
'真实 Agent 场景中,模型经常需要先读取文件内容,基于内容决定下一步操作。本例展示 read → edit 的多轮工具调用链:第一轮模型调用 read 获取文件内容,第二轮基于内容调用 edit 完成修改。',
|
|
565
|
+
steps: [
|
|
566
|
+
{
|
|
567
|
+
step: 1,
|
|
568
|
+
type: 'user-message',
|
|
569
|
+
title: '用户提问',
|
|
570
|
+
label: 'User',
|
|
571
|
+
labelType: 'primary',
|
|
572
|
+
content:
|
|
573
|
+
'读取 client/src/router/index.js,把路由 /token-market 的组件改成 DocsView。',
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
step: 2,
|
|
577
|
+
type: 'request',
|
|
578
|
+
title: '第一轮请求',
|
|
579
|
+
label: 'Request #1',
|
|
580
|
+
labelType: 'info',
|
|
581
|
+
description: '模型收到请求后,需要先看到文件内容才能知道如何修改。',
|
|
582
|
+
code: `POST /v1/chat/completions
|
|
583
|
+
Authorization: Bearer sk-xxx
|
|
584
|
+
|
|
585
|
+
{
|
|
586
|
+
"model": "maas-glm-5.1-zhipu",
|
|
587
|
+
"messages": [
|
|
588
|
+
{
|
|
589
|
+
"role": "user",
|
|
590
|
+
"content": "读取 client/src/router/index.js,把路由 /token-market 的组件改成 DocsView。"
|
|
591
|
+
}
|
|
592
|
+
],
|
|
593
|
+
"tools": [
|
|
594
|
+
{
|
|
595
|
+
"type": "function",
|
|
596
|
+
"function": {
|
|
597
|
+
"name": "read",
|
|
598
|
+
"description": "Read a file from the local filesystem.",
|
|
599
|
+
"parameters": {
|
|
600
|
+
"type": "object",
|
|
601
|
+
"properties": {
|
|
602
|
+
"filePath": { "type": "string", "description": "The absolute path" }
|
|
603
|
+
},
|
|
604
|
+
"required": ["filePath"]
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
"type": "function",
|
|
610
|
+
"function": {
|
|
611
|
+
"name": "edit",
|
|
612
|
+
"description": "Performs exact string replacements in files.",
|
|
613
|
+
"parameters": {
|
|
614
|
+
"type": "object",
|
|
615
|
+
"properties": {
|
|
616
|
+
"filePath": { "type": "string", "description": "The file to modify" },
|
|
617
|
+
"oldString": { "type": "string", "description": "Text to replace" },
|
|
618
|
+
"newString": { "type": "string", "description": "Replacement text" }
|
|
619
|
+
},
|
|
620
|
+
"required": ["filePath", "oldString", "newString"]
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
],
|
|
625
|
+
"tool_choice": "auto"
|
|
626
|
+
}`,
|
|
627
|
+
codeLang: 'json',
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
step: 3,
|
|
631
|
+
type: 'tool-call',
|
|
632
|
+
title: '第一轮:模型调用 read',
|
|
633
|
+
label: 'Model #1',
|
|
634
|
+
labelType: 'warning',
|
|
635
|
+
description: '模型选择先调用 read 工具读取文件内容,而非直接调用 edit(因为它还不知道文件的确切内容)。',
|
|
636
|
+
code: `{
|
|
637
|
+
"choices": [
|
|
638
|
+
{
|
|
639
|
+
"message": {
|
|
640
|
+
"role": "assistant",
|
|
641
|
+
"content": null,
|
|
642
|
+
"tool_calls": [
|
|
643
|
+
{
|
|
644
|
+
"id": "call_jkl012",
|
|
645
|
+
"type": "function",
|
|
646
|
+
"function": {
|
|
647
|
+
"name": "read",
|
|
648
|
+
"arguments": "{\\"filePath\\": \\"/usr1/wwkit-v1.0.x/packages/freetoken/client/src/router/index.js\\"}"
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
]
|
|
652
|
+
},
|
|
653
|
+
"finish_reason": "tool_calls"
|
|
654
|
+
}
|
|
655
|
+
]
|
|
656
|
+
}`,
|
|
657
|
+
codeLang: 'json',
|
|
658
|
+
},
|
|
659
|
+
{
|
|
660
|
+
step: 4,
|
|
661
|
+
type: 'execution',
|
|
662
|
+
title: '第一轮工具执行(read)',
|
|
663
|
+
label: 'Tool #1',
|
|
664
|
+
labelType: 'success',
|
|
665
|
+
toolName: 'read',
|
|
666
|
+
toolInput: `filePath: /usr1/wwkit-v1.0.x/packages/freetoken/client/src/router/index.js`,
|
|
667
|
+
toolOutput: `import { createRouter, createWebHistory } from 'vue-router'
|
|
668
|
+
import HomeView from '../views/HomeView.vue'
|
|
669
|
+
import TokenMarketView from '../views/TokenMarketView.vue'
|
|
670
|
+
|
|
671
|
+
const routes = [
|
|
672
|
+
{ path: '/', name: 'home', component: HomeView },
|
|
673
|
+
{
|
|
674
|
+
path: '/token-market',
|
|
675
|
+
name: 'token-market',
|
|
676
|
+
component: TokenMarketView,
|
|
677
|
+
meta: { titleKey: 'router.tokenMarket' },
|
|
678
|
+
},
|
|
679
|
+
]`,
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
step: 5,
|
|
683
|
+
type: 'tool-result',
|
|
684
|
+
title: '第一轮结果回传',
|
|
685
|
+
label: 'Tool Result #1',
|
|
686
|
+
labelType: 'success',
|
|
687
|
+
description: '客户端将 read 结果回传给模型。模型现在知道了文件的确切内容,可以构造精确的 edit 参数。',
|
|
688
|
+
code: `POST /v1/chat/completions
|
|
689
|
+
Authorization: Bearer sk-xxx
|
|
690
|
+
|
|
691
|
+
{
|
|
692
|
+
"model": "maas-glm-5.1-zhipu",
|
|
693
|
+
"messages": [
|
|
694
|
+
{ "role": "user", "content": "读取 client/src/router/index.js,把路由 /token-market 的组件改成 DocsView。" },
|
|
695
|
+
{
|
|
696
|
+
"role": "assistant",
|
|
697
|
+
"content": null,
|
|
698
|
+
"tool_calls": [
|
|
699
|
+
{ "id": "call_jkl012", "type": "function", "function": { "name": "read", "arguments": "{\\"filePath\\": \\".../router/index.js\\"}" } }
|
|
700
|
+
]
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
"role": "tool",
|
|
704
|
+
"tool_call_id": "call_jkl012",
|
|
705
|
+
"content": "import { createRouter, ... }\\nimport TokenMarketView from '../views/TokenMarketView.vue'\\n\\nconst routes = [\\n { path: '/', ... },\\n {\\n path: '/token-market',\\n name: 'token-market',\\n component: TokenMarketView,\\n ...\\n },\\n]"
|
|
706
|
+
}
|
|
707
|
+
],
|
|
708
|
+
"tools": [ /* ... read + edit ... */ ]
|
|
709
|
+
}`,
|
|
710
|
+
codeLang: 'json',
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
step: 6,
|
|
714
|
+
type: 'tool-call',
|
|
715
|
+
title: '第二轮:模型调用 edit',
|
|
716
|
+
label: 'Model #2',
|
|
717
|
+
labelType: 'warning',
|
|
718
|
+
description:
|
|
719
|
+
'模型基于读取到的文件内容,精确构造 edit 调用:将 TokenMarketView 的 import 和 component 引用替换为 DocsView。注意模型在 arguments 中给出了精确的 oldString 和 newString。',
|
|
720
|
+
code: `{
|
|
721
|
+
"choices": [
|
|
722
|
+
{
|
|
723
|
+
"message": {
|
|
724
|
+
"role": "assistant",
|
|
725
|
+
"content": null,
|
|
726
|
+
"tool_calls": [
|
|
727
|
+
{
|
|
728
|
+
"id": "call_mno345",
|
|
729
|
+
"type": "function",
|
|
730
|
+
"function": {
|
|
731
|
+
"name": "edit",
|
|
732
|
+
"arguments": "{\\"filePath\\": \\"/usr1/wwkit-v1.0.x/packages/freetoken/client/src/router/index.js\\", \\"oldString\\": \\"import TokenMarketView from '../views/TokenMarketView.vue'\\", \\"newString\\": \\"import DocsView from '../views/DocsView.vue'\\"}"
|
|
733
|
+
}
|
|
734
|
+
},
|
|
735
|
+
{
|
|
736
|
+
"id": "call_pqr678",
|
|
737
|
+
"type": "function",
|
|
738
|
+
"function": {
|
|
739
|
+
"name": "edit",
|
|
740
|
+
"arguments": "{\\"filePath\\": \\"/usr1/wwkit-v1.0.x/packages/freetoken/client/src/router/index.js\\", \\"oldString\\": \\"component: TokenMarketView,\\", \\"newString\\": \\"component: DocsView,\\"}"
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
]
|
|
744
|
+
},
|
|
745
|
+
"finish_reason": "tool_calls"
|
|
746
|
+
}
|
|
747
|
+
]
|
|
748
|
+
}`,
|
|
749
|
+
codeLang: 'json',
|
|
750
|
+
},
|
|
751
|
+
{
|
|
752
|
+
step: 7,
|
|
753
|
+
type: 'execution',
|
|
754
|
+
title: '第二轮工具执行(edit x2)',
|
|
755
|
+
label: 'Tool #2',
|
|
756
|
+
labelType: 'success',
|
|
757
|
+
description: '客户端执行两次 edit:替换 import 语句和 component 引用。',
|
|
758
|
+
parallelTools: [
|
|
759
|
+
{
|
|
760
|
+
toolName: 'edit',
|
|
761
|
+
toolInput: `filePath: .../router/index.js
|
|
762
|
+
oldString: "import TokenMarketView from '../views/TokenMarketView.vue'"
|
|
763
|
+
newString: "import DocsView from '../views/DocsView.vue'"`,
|
|
764
|
+
toolOutput: `File edited successfully. Replaced import statement.`,
|
|
765
|
+
},
|
|
766
|
+
{
|
|
767
|
+
toolName: 'edit',
|
|
768
|
+
toolInput: `filePath: .../router/index.js
|
|
769
|
+
oldString: "component: TokenMarketView,"
|
|
770
|
+
newString: "component: DocsView,"`,
|
|
771
|
+
toolOutput: `File edited successfully. Replaced component reference.`,
|
|
772
|
+
},
|
|
773
|
+
],
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
step: 8,
|
|
777
|
+
type: 'final-response',
|
|
778
|
+
title: '模型最终回复',
|
|
779
|
+
label: 'Final',
|
|
780
|
+
labelType: 'primary',
|
|
781
|
+
description: '模型确认修改完成,告知用户具体做了哪些改动。',
|
|
782
|
+
code: `{
|
|
783
|
+
"choices": [
|
|
784
|
+
{
|
|
785
|
+
"message": {
|
|
786
|
+
"role": "assistant",
|
|
787
|
+
"content": "已完成修改!具体改动如下:\\n\\n1. 将 import 语句从 \`TokenMarketView\` 替换为 \`DocsView\`\\n2. 将 /token-market 路由的 component 从 \`TokenMarketView\` 替换为 \`DocsView\`\\n\\n现在访问 /token-market 路由将渲染 DocsView 组件。"
|
|
788
|
+
},
|
|
789
|
+
"finish_reason": "stop"
|
|
790
|
+
}
|
|
791
|
+
]
|
|
792
|
+
}`,
|
|
793
|
+
codeLang: 'json',
|
|
794
|
+
},
|
|
795
|
+
],
|
|
796
|
+
},
|
|
797
|
+
|
|
798
|
+
// 示例 4:单工具流式调用
|
|
799
|
+
{
|
|
800
|
+
id: 'streaming-tool',
|
|
801
|
+
title: '示例 4:单工具流式调用(bash + stream)',
|
|
802
|
+
scenario: '用户让 agent 列出当前目录文件,请求和响应均使用流式模式',
|
|
803
|
+
description:
|
|
804
|
+
'当 stream: true 与 tools 同时启用时,tool_calls 不再一次性返回,而是通过 SSE 逐块增量推送——首个 chunk 携带工具 id 和 name,后续 chunk 携带 arguments 的部分 JSON 字符串,客户端必须拼接后才能得到完整调用参数。工具执行完毕后,第二轮请求同样以 stream: true 返回,最终回复的文本内容也逐块推送。',
|
|
805
|
+
steps: [
|
|
806
|
+
{
|
|
807
|
+
step: 1,
|
|
808
|
+
type: 'user-message',
|
|
809
|
+
title: '用户提问',
|
|
810
|
+
label: 'User',
|
|
811
|
+
labelType: 'primary',
|
|
812
|
+
content: '查看当前目录下有哪些文件?',
|
|
813
|
+
},
|
|
814
|
+
{
|
|
815
|
+
step: 2,
|
|
816
|
+
type: 'request',
|
|
817
|
+
title: '第一轮请求(stream: true + tools)',
|
|
818
|
+
label: 'Request #1',
|
|
819
|
+
labelType: 'info',
|
|
820
|
+
description: '请求同时携带 stream: true 和 tools 数组。模型将以 SSE 方式增量返回 tool_calls。',
|
|
821
|
+
code: `POST /v1/chat/completions
|
|
822
|
+
Content-Type: application/json
|
|
823
|
+
Authorization: Bearer sk-xxx
|
|
824
|
+
|
|
825
|
+
{
|
|
826
|
+
"model": "maas-glm-5.1-zhipu",
|
|
827
|
+
"messages": [
|
|
828
|
+
{
|
|
829
|
+
"role": "user",
|
|
830
|
+
"content": "查看当前目录下有哪些文件?"
|
|
831
|
+
}
|
|
832
|
+
],
|
|
833
|
+
"tools": [
|
|
834
|
+
{
|
|
835
|
+
"type": "function",
|
|
836
|
+
"function": {
|
|
837
|
+
"name": "bash",
|
|
838
|
+
"description": "Executes a given bash command in a persistent shell session.",
|
|
839
|
+
"parameters": {
|
|
840
|
+
"type": "object",
|
|
841
|
+
"properties": {
|
|
842
|
+
"command": { "type": "string", "description": "The command to execute" }
|
|
843
|
+
},
|
|
844
|
+
"required": ["command"]
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
],
|
|
849
|
+
"tool_choice": "auto",
|
|
850
|
+
"stream": true
|
|
851
|
+
}`,
|
|
852
|
+
codeLang: 'json',
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
step: 3,
|
|
856
|
+
type: 'tool-call',
|
|
857
|
+
title: 'SSE 增量返回 tool_calls(逐块拼接)',
|
|
858
|
+
label: 'SSE Chunks',
|
|
859
|
+
labelType: 'warning',
|
|
860
|
+
description:
|
|
861
|
+
'与非流式不同,tool_calls 被拆分为多个 SSE chunk 增量推送。首个 chunk 携带 tool_call 的 id、type 和 function.name(arguments 为空字符串);后续 chunk 只携带 arguments 的部分 JSON 文本;最后一个 chunk 的 finish_reason 为 "tool_calls"。客户端必须按 tool_calls[0].index 将所有增量片段拼接,才能得到完整的 arguments JSON。',
|
|
862
|
+
code: `data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","created":1716900000,"model":"maas-glm-5.1-zhipu","choices":[{"index":0,"delta":{"role":"assistant","tool_calls":[{"index":0,"id":"call_st001","type":"function","function":{"name":"bash","arguments":""}}]},"finish_reason":null}]}
|
|
863
|
+
|
|
864
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\\""}}]},"finish_reason":null}]}
|
|
865
|
+
|
|
866
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"command"}}]},"finish_reason":null}]}
|
|
867
|
+
|
|
868
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\":\\""}}]},"finish_reason":null}]}
|
|
869
|
+
|
|
870
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"ls -la"}}]},"finish_reason":null}]}
|
|
871
|
+
|
|
872
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\\"}"}}]},"finish_reason":null}]}
|
|
873
|
+
|
|
874
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
|
|
875
|
+
|
|
876
|
+
data: [DONE]`,
|
|
877
|
+
codeLang: 'json',
|
|
878
|
+
},
|
|
879
|
+
{
|
|
880
|
+
step: 4,
|
|
881
|
+
type: 'execution',
|
|
882
|
+
title: '客户端拼接 + 工具执行',
|
|
883
|
+
label: 'Accumulate + Execute',
|
|
884
|
+
labelType: 'success',
|
|
885
|
+
description:
|
|
886
|
+
'客户端按 index=0 将 6 个 chunk 的 arguments 片段拼接,得到完整 JSON:{"command":"ls -la"}。解析后执行 bash 工具。',
|
|
887
|
+
accumulate: `// 拼接过程(伪代码)
|
|
888
|
+
let arguments = ''
|
|
889
|
+
for (const chunk of sseChunks) {
|
|
890
|
+
if (chunk.choices[0]?.delta?.tool_calls?.[0]?.function?.arguments) {
|
|
891
|
+
arguments += chunk.choices[0].delta.tool_calls[0].function.arguments
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
// 拼接结果
|
|
895
|
+
// arguments = '{"command":"ls -la"}'
|
|
896
|
+
// 解析为对象
|
|
897
|
+
const toolInput = JSON.parse(arguments)
|
|
898
|
+
// { command: 'ls -la' }`,
|
|
899
|
+
toolName: 'bash',
|
|
900
|
+
toolInput: `ls -la`,
|
|
901
|
+
toolOutput: `total 48
|
|
902
|
+
drwxr-xr-x 4 user user 4096 Sep 22 10:00 .
|
|
903
|
+
drwxr-xr-x 8 user user 4096 Sep 22 10:00 ..
|
|
904
|
+
-rw-r--r-- 1 user user 412 Sep 22 10:00 index.html
|
|
905
|
+
drwxr-xr-x 2 user user 4096 Sep 22 10:00 public/
|
|
906
|
+
drwxr-xr-x 5 user user 4096 Sep 22 10:00 src/
|
|
907
|
+
-rw-r--r-- 1 user user 892 Sep 22 10:00 vite.config.js`,
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
step: 5,
|
|
911
|
+
type: 'tool-result',
|
|
912
|
+
title: '第二轮请求(工具结果回传 + stream: true)',
|
|
913
|
+
label: 'Request #2',
|
|
914
|
+
labelType: 'info',
|
|
915
|
+
description:
|
|
916
|
+
'客户端将拼接出的完整 assistant 消息(含 tool_calls)和工具结果一起回传,同样以 stream: true 请求。模型将以 SSE 逐块返回最终文本回复。',
|
|
917
|
+
code: `POST /v1/chat/completions
|
|
918
|
+
Content-Type: application/json
|
|
919
|
+
Authorization: Bearer sk-xxx
|
|
920
|
+
|
|
921
|
+
{
|
|
922
|
+
"model": "maas-glm-5.1-zhipu",
|
|
923
|
+
"messages": [
|
|
924
|
+
{
|
|
925
|
+
"role": "user",
|
|
926
|
+
"content": "查看当前目录下有哪些文件?"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
"role": "assistant",
|
|
930
|
+
"content": null,
|
|
931
|
+
"tool_calls": [
|
|
932
|
+
{
|
|
933
|
+
"id": "call_st001",
|
|
934
|
+
"type": "function",
|
|
935
|
+
"function": {
|
|
936
|
+
"name": "bash",
|
|
937
|
+
"arguments": "{\\"command\\": \\"ls -la\\"}"
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
]
|
|
941
|
+
},
|
|
942
|
+
{
|
|
943
|
+
"role": "tool",
|
|
944
|
+
"tool_call_id": "call_st001",
|
|
945
|
+
"content": "total 48\\ndrwxr-xr-x 4 user user 4096 ... index.html\\n... src/\\n... vite.config.js"
|
|
946
|
+
}
|
|
947
|
+
],
|
|
948
|
+
"tools": [ /* ... 同上 ... */ ],
|
|
949
|
+
"stream": true
|
|
950
|
+
}`,
|
|
951
|
+
codeLang: 'json',
|
|
952
|
+
},
|
|
953
|
+
{
|
|
954
|
+
step: 6,
|
|
955
|
+
type: 'final-response',
|
|
956
|
+
title: 'SSE 增量返回最终回复(逐块文本)',
|
|
957
|
+
label: 'SSE Final',
|
|
958
|
+
labelType: 'primary',
|
|
959
|
+
description:
|
|
960
|
+
'第二轮的 SSE chunks 与普通流式 chat 一致——delta.content 携带增量文本片段,客户端拼接后展示给用户打字机效果。finish_reason 为 "stop" 表示回复完成。',
|
|
961
|
+
code: `data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
|
|
962
|
+
|
|
963
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"当前"},"finish_reason":null}]}
|
|
964
|
+
|
|
965
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"目录下"},"finish_reason":null}]}
|
|
966
|
+
|
|
967
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"有以下文件:\\n\\n- **index.html**"},"finish_reason":null}]}
|
|
968
|
+
|
|
969
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" — HTML 入口文件\\n"},"finish_reason":null}]}
|
|
970
|
+
|
|
971
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"- **public/** — 静态资源目录\\n"},"finish_reason":null}]}
|
|
972
|
+
|
|
973
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"- **src/** — 源代码目录\\n"},"finish_reason":null}]}
|
|
974
|
+
|
|
975
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"- **vite.config.js** — Vite 构建配置"},"finish_reason":null}]}
|
|
976
|
+
|
|
977
|
+
data: {"id":"chatcmpl-x7y8z9","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
|
|
978
|
+
|
|
979
|
+
data: [DONE]`,
|
|
980
|
+
codeLang: 'json',
|
|
981
|
+
},
|
|
982
|
+
],
|
|
983
|
+
},
|
|
984
|
+
],
|
|
985
|
+
|
|
986
|
+
// ── Agent 循环总结 ──
|
|
987
|
+
summary: {
|
|
988
|
+
title: 'Agent 循环总结',
|
|
989
|
+
description: '从以上示例可以总结出 Agent 模式的核心循环流程:',
|
|
990
|
+
flow: [
|
|
991
|
+
{ phase: '1. 用户输入', detail: '用户发送自然语言请求' },
|
|
992
|
+
{ phase: '2. 模型决策', detail: '模型分析请求 + 工具列表,决定是否调用工具及调用哪个' },
|
|
993
|
+
{ phase: '3. 工具调用', detail: '模型返回 tool_calls(含工具名 + 参数 JSON)' },
|
|
994
|
+
{ phase: '4. 工具执行', detail: '客户端解析 tool_calls,在本地执行对应工具' },
|
|
995
|
+
{ phase: '5. 结果回传', detail: '将工具执行结果以 role: "tool" 消息追加到 messages' },
|
|
996
|
+
{ phase: '6. 循环 / 终止', detail: '模型根据结果决定继续调用工具(回到步骤 2)或生成最终回复(finish_reason: "stop")' },
|
|
997
|
+
],
|
|
998
|
+
},
|
|
999
|
+
|
|
1000
|
+
// ── opencode 内置工具完整描述 ──
|
|
1001
|
+
builtinTools,
|
|
1002
|
+
}
|