@shun-js/aibaiban-server 0.6.4 → 0.6.5
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shun-js/aibaiban-server",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"description": "aibaiban.com server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai aibaiban"
|
|
@@ -44,5 +44,5 @@
|
|
|
44
44
|
"access": "public",
|
|
45
45
|
"registry": "https://registry.npmjs.org/"
|
|
46
46
|
},
|
|
47
|
-
"gitHead": "
|
|
47
|
+
"gitHead": "a6d44d1fe7933c163c91fcc5157fcd89a8000b00"
|
|
48
48
|
}
|
|
@@ -10,8 +10,13 @@ module.exports = (app) => {
|
|
|
10
10
|
service.intent(req, res);
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
-
//
|
|
13
|
+
// draw (原有方法)
|
|
14
14
|
app.post('/draw', (req, res) => {
|
|
15
15
|
service.draw(req, res);
|
|
16
16
|
});
|
|
17
|
+
|
|
18
|
+
// drawWithTools (Tool Calling 方法)
|
|
19
|
+
app.post('/drawWithTools', (req, res) => {
|
|
20
|
+
service.drawWithTools(req, res);
|
|
21
|
+
});
|
|
17
22
|
};
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// llm
|
|
2
2
|
const { llmParseIntent, llmGetDrawJson } = require('../util/llm-v4.js');
|
|
3
3
|
|
|
4
|
+
// tool calling
|
|
5
|
+
const { generateFlowchartWithTools } = require('../util/llm-toolcall.js');
|
|
6
|
+
|
|
4
7
|
// util
|
|
5
8
|
const { chatFeishuMsg, chatResFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
|
|
6
9
|
|
|
@@ -81,3 +84,41 @@ exports.draw = async (req, res) => {
|
|
|
81
84
|
res.jsonFail(msg);
|
|
82
85
|
}
|
|
83
86
|
};
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* drawWithTools - 使用 Tool Calling 生成流程图
|
|
90
|
+
* @param {*} req
|
|
91
|
+
* @param {*} res
|
|
92
|
+
* @returns
|
|
93
|
+
*/
|
|
94
|
+
exports.drawWithTools = async (req, res) => {
|
|
95
|
+
const methodName = 'drawWithTools';
|
|
96
|
+
|
|
97
|
+
// check
|
|
98
|
+
if (!req.body.userPrompt) {
|
|
99
|
+
const msg = 'need userPrompt';
|
|
100
|
+
req.logger.error(methodName, msg);
|
|
101
|
+
res.jsonFail(msg);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// const
|
|
106
|
+
const userPrompt = decodeURIComponent(req.body.userPrompt);
|
|
107
|
+
req.logger.info(methodName, 'userPrompt', userPrompt);
|
|
108
|
+
chatFeishuMsg(req);
|
|
109
|
+
|
|
110
|
+
// go
|
|
111
|
+
try {
|
|
112
|
+
const diagram = await generateFlowchartWithTools(userPrompt);
|
|
113
|
+
req.logger.info(methodName, 'diagram', diagram);
|
|
114
|
+
|
|
115
|
+
// r
|
|
116
|
+
chatResFeishuMsg(req, JSON.stringify(diagram));
|
|
117
|
+
res.jsonSuccess('success', diagram);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
const msg = 'draw with tools error';
|
|
120
|
+
errorFeishuMsg(req, msg);
|
|
121
|
+
req.logger.error(methodName, msg, error);
|
|
122
|
+
res.jsonFail(msg);
|
|
123
|
+
}
|
|
124
|
+
};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Calling 流程图生成器
|
|
3
|
+
* 使用多步推理生成高质量的流程图
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 布局常量
|
|
7
|
+
const LAYOUT = {
|
|
8
|
+
MAIN_X: 500,
|
|
9
|
+
START_Y: 80,
|
|
10
|
+
STEP_SPACING: 150,
|
|
11
|
+
ERROR_LEFT_X: 200,
|
|
12
|
+
ERROR_RIGHT_X: 800,
|
|
13
|
+
NODE_WIDTH: 180,
|
|
14
|
+
NODE_HEIGHT: 80,
|
|
15
|
+
ELLIPSE_WIDTH: 120,
|
|
16
|
+
ELLIPSE_HEIGHT: 60,
|
|
17
|
+
DIAMOND_WIDTH: 140,
|
|
18
|
+
DIAMOND_HEIGHT: 80,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 工具定义(给 AI 使用)
|
|
23
|
+
*/
|
|
24
|
+
const TOOLS = [
|
|
25
|
+
{
|
|
26
|
+
name: 'generate_flowchart_skeleton',
|
|
27
|
+
description: '生成流程图骨架(节点列表,不包含坐标)。识别主流程步骤和判断节点,不要生成错误分支。返回节点数组。',
|
|
28
|
+
parameters: {
|
|
29
|
+
type: 'object',
|
|
30
|
+
properties: {
|
|
31
|
+
nodes: {
|
|
32
|
+
type: 'array',
|
|
33
|
+
description: '节点列表',
|
|
34
|
+
items: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
id: {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: '节点唯一标识符(驼峰命名),如 start, step1, judge1',
|
|
40
|
+
},
|
|
41
|
+
label: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: '节点显示文本,支持 \\n 换行',
|
|
44
|
+
},
|
|
45
|
+
type: {
|
|
46
|
+
type: 'string',
|
|
47
|
+
enum: ['ellipse', 'rectangle', 'diamond'],
|
|
48
|
+
description: 'ellipse=开始/结束, rectangle=处理步骤, diamond=判断节点',
|
|
49
|
+
},
|
|
50
|
+
color: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
enum: ['blue', 'green', 'purple', 'orange', 'red', 'gray'],
|
|
53
|
+
description: '节点颜色',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['id', 'label', 'type'],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
connections: {
|
|
60
|
+
type: 'array',
|
|
61
|
+
description: '主流程连接关系(不包含错误分支)',
|
|
62
|
+
items: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
from: { type: 'string', description: '起始节点 id' },
|
|
66
|
+
to: { type: 'string', description: '目标节点 id' },
|
|
67
|
+
label: { type: 'string', description: '连线标签' },
|
|
68
|
+
},
|
|
69
|
+
required: ['from', 'to'],
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
required: ['nodes', 'connections'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'add_error_branches',
|
|
78
|
+
description: '为判断节点添加错误分支。输入已布局的图表,输出添加错误节点和回退连线后的图表。',
|
|
79
|
+
parameters: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
errorNodes: {
|
|
83
|
+
type: 'array',
|
|
84
|
+
description: '错误节点列表',
|
|
85
|
+
items: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
id: { type: 'string', description: '错误节点 id' },
|
|
89
|
+
label: { type: 'string', description: '错误节点文本' },
|
|
90
|
+
relatedJudgeId: { type: 'string', description: '对应的判断节点 id' },
|
|
91
|
+
backToNodeId: { type: 'string', description: '回退到哪个输入节点的 id' },
|
|
92
|
+
},
|
|
93
|
+
required: ['id', 'label', 'relatedJudgeId', 'backToNodeId'],
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
required: ['errorNodes'],
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'finalize_diagram',
|
|
102
|
+
description: '完成图表生成,返回最终的图表 JSON。调用此工具表示图表已生成完毕。',
|
|
103
|
+
parameters: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
title: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
description: '图表标题',
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* 工具执行函数
|
|
117
|
+
*/
|
|
118
|
+
const TOOL_FUNCTIONS = {
|
|
119
|
+
/**
|
|
120
|
+
* 生成流程图骨架
|
|
121
|
+
*/
|
|
122
|
+
generate_flowchart_skeleton: async (args) => {
|
|
123
|
+
console.log('[Tool] generate_flowchart_skeleton called');
|
|
124
|
+
|
|
125
|
+
// 验证节点
|
|
126
|
+
if (!args.nodes || !Array.isArray(args.nodes) || args.nodes.length === 0) {
|
|
127
|
+
return { error: '需要至少一个节点' };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 计算坐标(自动布局)
|
|
131
|
+
const nodesWithCoords = calculateMainFlowLayout(args.nodes);
|
|
132
|
+
|
|
133
|
+
// 添加主流程连线的 routing
|
|
134
|
+
const connectionsWithRouting = args.connections.map((conn) => ({
|
|
135
|
+
...conn,
|
|
136
|
+
routing: {
|
|
137
|
+
exitSide: 'bottom',
|
|
138
|
+
entrySide: 'top',
|
|
139
|
+
},
|
|
140
|
+
}));
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
success: true,
|
|
144
|
+
diagram: {
|
|
145
|
+
type: 'flowchart',
|
|
146
|
+
nodes: nodesWithCoords,
|
|
147
|
+
connections: connectionsWithRouting,
|
|
148
|
+
},
|
|
149
|
+
message: `已生成 ${nodesWithCoords.length} 个节点的主流程骨架,所有节点已按垂直布局排列(x: ${LAYOUT.MAIN_X})`,
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* 添加错误分支
|
|
155
|
+
*/
|
|
156
|
+
add_error_branches: async (args, currentDiagram) => {
|
|
157
|
+
console.log('[Tool] add_error_branches called');
|
|
158
|
+
|
|
159
|
+
if (!args.errorNodes || !Array.isArray(args.errorNodes)) {
|
|
160
|
+
return { error: '需要 errorNodes 数组' };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!currentDiagram || !currentDiagram.nodes) {
|
|
164
|
+
return { error: '需要当前图表数据' };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 创建节点查找表
|
|
168
|
+
const nodeMap = {};
|
|
169
|
+
currentDiagram.nodes.forEach((node) => {
|
|
170
|
+
nodeMap[node.id] = node;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// 添加错误节点
|
|
174
|
+
const newErrorNodes = [];
|
|
175
|
+
const newConnections = [];
|
|
176
|
+
|
|
177
|
+
args.errorNodes.forEach((errorSpec, index) => {
|
|
178
|
+
const judgeNode = nodeMap[errorSpec.relatedJudgeId];
|
|
179
|
+
const backToNode = nodeMap[errorSpec.backToNodeId];
|
|
180
|
+
|
|
181
|
+
if (!judgeNode) {
|
|
182
|
+
console.warn(`[Tool] 判断节点未找到: ${errorSpec.relatedJudgeId}`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (!backToNode) {
|
|
186
|
+
console.warn(`[Tool] 回退目标节点未找到: ${errorSpec.backToNodeId}`);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 创建错误节点(与判断节点同高度,在左侧)
|
|
191
|
+
const errorNode = {
|
|
192
|
+
id: errorSpec.id,
|
|
193
|
+
label: errorSpec.label,
|
|
194
|
+
type: 'rectangle',
|
|
195
|
+
color: 'red',
|
|
196
|
+
x: index % 2 === 0 ? LAYOUT.ERROR_LEFT_X : LAYOUT.ERROR_RIGHT_X, // 交替左右
|
|
197
|
+
y: judgeNode.y,
|
|
198
|
+
width: LAYOUT.DIAMOND_WIDTH,
|
|
199
|
+
height: LAYOUT.NODE_HEIGHT,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
newErrorNodes.push(errorNode);
|
|
203
|
+
|
|
204
|
+
// 添加判断节点 → 错误节点的连线
|
|
205
|
+
const errorBranchConnection = {
|
|
206
|
+
from: errorSpec.relatedJudgeId,
|
|
207
|
+
to: errorSpec.id,
|
|
208
|
+
label: '失败',
|
|
209
|
+
routing: {
|
|
210
|
+
exitSide: errorNode.x < judgeNode.x ? 'left' : 'right',
|
|
211
|
+
exitRatio: 0.5,
|
|
212
|
+
entrySide: errorNode.x < judgeNode.x ? 'right' : 'left',
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
newConnections.push(errorBranchConnection);
|
|
217
|
+
|
|
218
|
+
// 添加错误节点 → 输入节点的回退连线
|
|
219
|
+
const backConnection = {
|
|
220
|
+
from: errorSpec.id,
|
|
221
|
+
to: errorSpec.backToNodeId,
|
|
222
|
+
label: '重新输入',
|
|
223
|
+
routing: {
|
|
224
|
+
exitSide: 'top', // 从错误节点顶部出
|
|
225
|
+
exitRatio: 0.5,
|
|
226
|
+
entrySide: errorNode.x < backToNode.x ? 'left' : 'right', // 进入输入节点左侧或右侧
|
|
227
|
+
entryRatio: 0.5,
|
|
228
|
+
},
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
newConnections.push(backConnection);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// 合并到当前图表
|
|
235
|
+
const updatedDiagram = {
|
|
236
|
+
...currentDiagram,
|
|
237
|
+
nodes: [...currentDiagram.nodes, ...newErrorNodes],
|
|
238
|
+
connections: [...currentDiagram.connections, ...newConnections],
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
diagram: updatedDiagram,
|
|
244
|
+
message: `已添加 ${newErrorNodes.length} 个错误分支,包含回退连线`,
|
|
245
|
+
};
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 完成图表生成
|
|
250
|
+
*/
|
|
251
|
+
finalize_diagram: async (args, currentDiagram) => {
|
|
252
|
+
console.log('[Tool] finalize_diagram called');
|
|
253
|
+
|
|
254
|
+
if (!currentDiagram) {
|
|
255
|
+
return { error: '图表数据为空' };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 最终验证
|
|
259
|
+
const validation = validateDiagram(currentDiagram);
|
|
260
|
+
|
|
261
|
+
if (!validation.valid) {
|
|
262
|
+
return {
|
|
263
|
+
success: false,
|
|
264
|
+
errors: validation.errors,
|
|
265
|
+
message: '图表验证失败',
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
success: true,
|
|
271
|
+
diagram: {
|
|
272
|
+
...currentDiagram,
|
|
273
|
+
title: args.title || '流程图',
|
|
274
|
+
},
|
|
275
|
+
message: '图表生成完成',
|
|
276
|
+
};
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* 计算主流程节点布局
|
|
282
|
+
*/
|
|
283
|
+
function calculateMainFlowLayout(nodes) {
|
|
284
|
+
return nodes.map((node, index) => {
|
|
285
|
+
// 强制 x 坐标
|
|
286
|
+
const x = LAYOUT.MAIN_X;
|
|
287
|
+
|
|
288
|
+
// 强制 y 坐标(等间距)
|
|
289
|
+
const y = LAYOUT.START_Y + index * LAYOUT.STEP_SPACING;
|
|
290
|
+
|
|
291
|
+
// 强制宽高
|
|
292
|
+
let width, height;
|
|
293
|
+
if (node.type === 'ellipse') {
|
|
294
|
+
width = LAYOUT.ELLIPSE_WIDTH;
|
|
295
|
+
height = LAYOUT.ELLIPSE_HEIGHT;
|
|
296
|
+
} else if (node.type === 'diamond') {
|
|
297
|
+
width = LAYOUT.DIAMOND_WIDTH;
|
|
298
|
+
height = LAYOUT.DIAMOND_HEIGHT;
|
|
299
|
+
} else {
|
|
300
|
+
width = LAYOUT.NODE_WIDTH;
|
|
301
|
+
height = LAYOUT.NODE_HEIGHT;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
...node,
|
|
306
|
+
x,
|
|
307
|
+
y,
|
|
308
|
+
width,
|
|
309
|
+
height,
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* 验证图表
|
|
316
|
+
*/
|
|
317
|
+
function validateDiagram(diagram) {
|
|
318
|
+
const errors = [];
|
|
319
|
+
|
|
320
|
+
// 检查节点坐标范围
|
|
321
|
+
diagram.nodes.forEach((node) => {
|
|
322
|
+
if (node.x < 40 || node.x > 1160) {
|
|
323
|
+
errors.push(`节点 ${node.id} 的 x 坐标超出范围: ${node.x}`);
|
|
324
|
+
}
|
|
325
|
+
if (node.y < 40 || node.y > 1500) {
|
|
326
|
+
errors.push(`节点 ${node.id} 的 y 坐标超出范围: ${node.y}`);
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// 检查连线引用
|
|
331
|
+
const nodeIds = new Set(diagram.nodes.map((n) => n.id));
|
|
332
|
+
diagram.connections.forEach((conn, index) => {
|
|
333
|
+
if (!nodeIds.has(conn.from)) {
|
|
334
|
+
errors.push(`连线 ${index} 的起始节点不存在: ${conn.from}`);
|
|
335
|
+
}
|
|
336
|
+
if (!nodeIds.has(conn.to)) {
|
|
337
|
+
errors.push(`连线 ${index} 的目标节点不存在: ${conn.to}`);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
valid: errors.length === 0,
|
|
343
|
+
errors,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
module.exports = {
|
|
348
|
+
TOOLS,
|
|
349
|
+
TOOL_FUNCTIONS,
|
|
350
|
+
};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool Calling 流程图生成器 - 主流程
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// path
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const { readFile } = require('qiao-file');
|
|
8
|
+
|
|
9
|
+
// llm
|
|
10
|
+
const { GeminiVertex } = require('viho-llm');
|
|
11
|
+
|
|
12
|
+
// tools
|
|
13
|
+
const { TOOLS, TOOL_FUNCTIONS } = require('./diagram-tools.js');
|
|
14
|
+
|
|
15
|
+
// gemini client
|
|
16
|
+
const gemini = GeminiVertex({
|
|
17
|
+
projectId: global.QZ_CONFIG.gemini.projectId,
|
|
18
|
+
location: global.QZ_CONFIG.gemini.location,
|
|
19
|
+
modelName: global.QZ_CONFIG.gemini.modelName,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
// const
|
|
23
|
+
const chatConfig = {
|
|
24
|
+
temperature: 0.3, // 稍微提高一点,允许更灵活的思考
|
|
25
|
+
topP: 0.9,
|
|
26
|
+
topK: 40,
|
|
27
|
+
maxOutputTokens: 8192,
|
|
28
|
+
thinkingConfig: {
|
|
29
|
+
thinkingBudget: 0,
|
|
30
|
+
includeThoughts: false,
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const safetySettings = [
|
|
35
|
+
{
|
|
36
|
+
category: 'HARM_CATEGORY_HATE_SPEECH',
|
|
37
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
|
41
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
45
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
category: 'HARM_CATEGORY_HARASSMENT',
|
|
49
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
50
|
+
},
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 使用 Tool Calling 生成流程图
|
|
55
|
+
* @param {string} userPrompt - 用户需求
|
|
56
|
+
* @returns {Object} - SimplifiedDiagram
|
|
57
|
+
*/
|
|
58
|
+
exports.generateFlowchartWithTools = async (userPrompt) => {
|
|
59
|
+
console.log('[ToolCall] Starting flowchart generation with tools');
|
|
60
|
+
console.log('[ToolCall] User prompt:', userPrompt);
|
|
61
|
+
|
|
62
|
+
// 加载系统提示词
|
|
63
|
+
let systemPrompt = await readFile(path.resolve(__dirname, './prompt-toolcall.md'));
|
|
64
|
+
|
|
65
|
+
// 初始化对话历史
|
|
66
|
+
const conversationHistory = [];
|
|
67
|
+
let currentDiagram = null;
|
|
68
|
+
let maxTurns = 10; // 最多 10 轮对话
|
|
69
|
+
let turnCount = 0;
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// 第1步:用户输入
|
|
73
|
+
conversationHistory.push({
|
|
74
|
+
role: 'user',
|
|
75
|
+
parts: [{ text: userPrompt }],
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// 对话循环
|
|
79
|
+
while (turnCount < maxTurns) {
|
|
80
|
+
turnCount++;
|
|
81
|
+
console.log(`\n[ToolCall] Turn ${turnCount}/${maxTurns}`);
|
|
82
|
+
|
|
83
|
+
// 调用 AI(带 tools)
|
|
84
|
+
const chatOptions = {
|
|
85
|
+
contents: conversationHistory,
|
|
86
|
+
systemInstruction: systemPrompt,
|
|
87
|
+
config: chatConfig,
|
|
88
|
+
safetySettings: safetySettings,
|
|
89
|
+
tools: [{ functionDeclarations: TOOLS }], // ⭐ 关键:传入工具定义
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const responseText = await gemini.chat(chatOptions);
|
|
93
|
+
|
|
94
|
+
// 解析响应(可能包含 function call)
|
|
95
|
+
const response = JSON.parse(responseText);
|
|
96
|
+
console.log('[ToolCall] AI response:', JSON.stringify(response, null, 2));
|
|
97
|
+
|
|
98
|
+
// 添加 AI 响应到历史
|
|
99
|
+
conversationHistory.push({
|
|
100
|
+
role: 'model',
|
|
101
|
+
parts: [{ text: responseText }],
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// 检查是否有 function call
|
|
105
|
+
if (response.functionCall) {
|
|
106
|
+
const functionCall = response.functionCall;
|
|
107
|
+
const functionName = functionCall.name;
|
|
108
|
+
const functionArgs = functionCall.args;
|
|
109
|
+
|
|
110
|
+
console.log(`[ToolCall] AI called function: ${functionName}`);
|
|
111
|
+
console.log(`[ToolCall] Function args:`, JSON.stringify(functionArgs, null, 2));
|
|
112
|
+
|
|
113
|
+
// 执行工具函数
|
|
114
|
+
const toolFunction = TOOL_FUNCTIONS[functionName];
|
|
115
|
+
|
|
116
|
+
if (!toolFunction) {
|
|
117
|
+
console.error(`[ToolCall] Unknown function: ${functionName}`);
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const toolResult = await toolFunction(functionArgs, currentDiagram);
|
|
122
|
+
console.log(`[ToolCall] Tool result:`, JSON.stringify(toolResult, null, 2));
|
|
123
|
+
|
|
124
|
+
// 更新当前图表
|
|
125
|
+
if (toolResult.diagram) {
|
|
126
|
+
currentDiagram = toolResult.diagram;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 将工具执行结果返回给 AI
|
|
130
|
+
conversationHistory.push({
|
|
131
|
+
role: 'function',
|
|
132
|
+
parts: [
|
|
133
|
+
{
|
|
134
|
+
functionResponse: {
|
|
135
|
+
name: functionName,
|
|
136
|
+
response: toolResult,
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 如果是 finalize_diagram,表示完成
|
|
143
|
+
if (functionName === 'finalize_diagram' && toolResult.success) {
|
|
144
|
+
console.log('[ToolCall] Diagram generation completed');
|
|
145
|
+
return currentDiagram;
|
|
146
|
+
}
|
|
147
|
+
} else if (response.text) {
|
|
148
|
+
// AI 返回普通文本(可能是思考过程或完成提示)
|
|
149
|
+
console.log('[ToolCall] AI text response:', response.text);
|
|
150
|
+
|
|
151
|
+
// 如果没有 function call 且已有图表,认为完成
|
|
152
|
+
if (currentDiagram) {
|
|
153
|
+
console.log('[ToolCall] No more function calls, returning current diagram');
|
|
154
|
+
return currentDiagram;
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
console.warn('[ToolCall] Unexpected response format:', response);
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// 达到最大轮数
|
|
163
|
+
if (turnCount >= maxTurns) {
|
|
164
|
+
console.warn('[ToolCall] Reached max turns, returning current diagram');
|
|
165
|
+
return currentDiagram || { type: 'flowchart', nodes: [], connections: [], error: '达到最大对话轮数' };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return currentDiagram || { type: 'flowchart', nodes: [], connections: [], error: '生成失败' };
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error('[ToolCall] Error:', error);
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# AI白板绘图助手 - Tool Calling 模式
|
|
2
|
+
|
|
3
|
+
你是 **AI白板** (aibaiban.com) 的专业绘图助手,使用多步推理生成高质量的流程图。
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 核心工作流程
|
|
8
|
+
|
|
9
|
+
### 第 1 步:生成流程骨架
|
|
10
|
+
|
|
11
|
+
**任务**:分析用户需求,识别主流程步骤,调用 `generate_flowchart_skeleton` 工具。
|
|
12
|
+
|
|
13
|
+
**要点**:
|
|
14
|
+
|
|
15
|
+
- 只生成**主流程**节点(正常路径)
|
|
16
|
+
- 不要生成错误分支节点
|
|
17
|
+
- 开始节点:`type: "ellipse"`, `color: "blue"`
|
|
18
|
+
- 结束节点:`type: "ellipse"`, `color: "gray"`
|
|
19
|
+
- 判断节点:`type: "diamond"`, `color: "orange"`
|
|
20
|
+
- 处理步骤:`type: "rectangle"`, `color: "blue"`
|
|
21
|
+
|
|
22
|
+
**示例**:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
用户需求:画出用户注册的完整流程
|
|
26
|
+
|
|
27
|
+
你应该调用:
|
|
28
|
+
generate_flowchart_skeleton({
|
|
29
|
+
nodes: [
|
|
30
|
+
{ id: "start", label: "开始", type: "ellipse", color: "blue" },
|
|
31
|
+
{ id: "step1", label: "访问注册页面", type: "rectangle", color: "blue" },
|
|
32
|
+
{ id: "step2", label: "输入注册信息", type: "rectangle", color: "blue" },
|
|
33
|
+
{ id: "judge1", label: "验证输入", type: "diamond", color: "orange" },
|
|
34
|
+
{ id: "step3", label: "发送验证码", type: "rectangle", color: "blue" },
|
|
35
|
+
{ id: "step4", label: "输入验证码", type: "rectangle", color: "blue" },
|
|
36
|
+
{ id: "judge2", label: "验证验证码", type: "diamond", color: "orange" },
|
|
37
|
+
{ id: "step5", label: "创建账户", type: "rectangle", color: "green" },
|
|
38
|
+
{ id: "step6", label: "注册成功", type: "rectangle", color: "green" },
|
|
39
|
+
{ id: "end", label: "结束", type: "ellipse", color: "gray" }
|
|
40
|
+
],
|
|
41
|
+
connections: [
|
|
42
|
+
{ from: "start", to: "step1" },
|
|
43
|
+
{ from: "step1", to: "step2" },
|
|
44
|
+
{ from: "step2", to: "judge1" },
|
|
45
|
+
{ from: "judge1", to: "step3", label: "通过" },
|
|
46
|
+
{ from: "step3", to: "step4" },
|
|
47
|
+
{ from: "step4", to: "judge2" },
|
|
48
|
+
{ from: "judge2", to: "step5", label: "通过" },
|
|
49
|
+
{ from: "step5", to: "step6" },
|
|
50
|
+
{ from: "step6", to: "end" }
|
|
51
|
+
]
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
### 第 2 步:添加错误分支
|
|
58
|
+
|
|
59
|
+
**任务**:收到主流程布局结果后,为每个判断节点添加错误分支。
|
|
60
|
+
|
|
61
|
+
**要点**:
|
|
62
|
+
|
|
63
|
+
- 错误节点颜色:`red`
|
|
64
|
+
- 每个错误节点需要指定:
|
|
65
|
+
- `relatedJudgeId`:对应的判断节点 id
|
|
66
|
+
- `backToNodeId`:回退到哪个输入节点的 id
|
|
67
|
+
|
|
68
|
+
**示例**:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
调用:
|
|
72
|
+
add_error_branches({
|
|
73
|
+
errorNodes: [
|
|
74
|
+
{
|
|
75
|
+
id: "error1",
|
|
76
|
+
label: "信息无效",
|
|
77
|
+
relatedJudgeId: "judge1",
|
|
78
|
+
backToNodeId: "step2"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: "error2",
|
|
82
|
+
label: "验证码错误",
|
|
83
|
+
relatedJudgeId: "judge2",
|
|
84
|
+
backToNodeId: "step4"
|
|
85
|
+
}
|
|
86
|
+
]
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### 第 3 步:完成图表
|
|
93
|
+
|
|
94
|
+
**任务**:调用 `finalize_diagram` 完成图表生成。
|
|
95
|
+
|
|
96
|
+
**示例**:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
finalize_diagram({ title: "用户注册流程图" })
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 重要规则
|
|
105
|
+
|
|
106
|
+
### 规则 1:工具调用顺序
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
第1步:generate_flowchart_skeleton(必须)
|
|
110
|
+
���2步:add_error_branches(可选,如果有判断节点)
|
|
111
|
+
第3步:finalize_diagram(必须)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 规则 2:节点命名规范
|
|
115
|
+
|
|
116
|
+
- 使用驼峰式英文:`start`, `step1`, `judge1`, `error1`, `end`
|
|
117
|
+
- ID 必须唯一
|
|
118
|
+
- label 使用中文,简洁明了
|
|
119
|
+
|
|
120
|
+
### 规则 3:判断节点处理
|
|
121
|
+
|
|
122
|
+
- 判断节点必须有两个出口:
|
|
123
|
+
- 主分支:继续主流程(label: "通过"/"是")
|
|
124
|
+
- 错误分支:指向错误节点(label: "失败"/"否")
|
|
125
|
+
|
|
126
|
+
### 规则 4:错误分支回退
|
|
127
|
+
|
|
128
|
+
- 每个错误节点都需要回退到之前的某个输入节点
|
|
129
|
+
- 例如:
|
|
130
|
+
- "信息无效" → 回退到 "输入注册信息"
|
|
131
|
+
- "验证码错误" → 回退到 "输入验证码"
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 示例对话流程
|
|
136
|
+
|
|
137
|
+
**User**: 画出用户注册的完整流程
|
|
138
|
+
|
|
139
|
+
**Assistant**: [调用 generate_flowchart_skeleton,生成主流程骨架]
|
|
140
|
+
|
|
141
|
+
**System**: 工具执行结果:已生成 10 个节点的主流程骨架,所有节点已按垂直布局排列(x: 500)
|
|
142
|
+
|
|
143
|
+
**Assistant**: [调用 add_error_branches,添加错误分支]
|
|
144
|
+
|
|
145
|
+
**System**: 工具执行结果:已添加 2 个错误分支,包含回退连线
|
|
146
|
+
|
|
147
|
+
**Assistant**: [调用 finalize_diagram,完成图表]
|
|
148
|
+
|
|
149
|
+
**System**: 工具执行结果:图表生成完成
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 注意事项
|
|
154
|
+
|
|
155
|
+
1. **坐标由系统自动计算**:你只需要提供节点的 id、label、type、color,系统会自动计算 x、y、width、height
|
|
156
|
+
2. **连线路由由系统自动添加**:你只需要指定 from、to、label,系统会自动添加 routing 配置
|
|
157
|
+
3. **专注于流程拆解**:你的核心任务是正确理解用户需求,拆解出合理的流程步骤
|
|
158
|
+
4. **使用工具循序渐进**:不要一次性生成所有内容,按照三步流程逐步完成
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 开始工作
|
|
163
|
+
|
|
164
|
+
现在,根据用户的需求,开始调用工具生成流程图吧!
|
|
165
|
+
|
|
166
|
+
记住:
|
|
167
|
+
|
|
168
|
+
- 第 1 步:调用 `generate_flowchart_skeleton` 生成主流程
|
|
169
|
+
- 第 2 步:调用 `add_error_branches` 添加错误分支(如有需要)
|
|
170
|
+
- 第 3 步:调用 `finalize_diagram` 完成图表
|