@shun-js/aibaiban-server 0.8.1 → 0.8.3

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.8.1",
3
+ "version": "0.8.3",
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": "fcc8c9fb781e34771e75f1df5615d912a50ff63e"
47
+ "gitHead": "331bc8dc9ccfa3de0ea2c3c7326301f660cab528"
48
48
  }
@@ -14,4 +14,9 @@ module.exports = (app) => {
14
14
  app.post('/drawWithTools', (req, res) => {
15
15
  service.drawWithTools(req, res);
16
16
  });
17
+
18
+ // draw/agent (流式 Agent)
19
+ app.post('/draw/agent', (req, res) => {
20
+ service.drawAgent(req, res);
21
+ });
17
22
  };
@@ -7,6 +7,10 @@ const { generateFlowchartWithTools } = require('../util/llm-toolcall.js');
7
7
  // util
8
8
  const { chatFeishuMsg, chatResFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
9
9
 
10
+ // llm agent
11
+ const { callLLMForJSON, callLLM } = require('../util/llm-agent.js');
12
+ const prompts = require('../util/prompt-agent.js');
13
+
10
14
  /**
11
15
  * intent
12
16
  * @param {*} req
@@ -83,3 +87,104 @@ exports.drawWithTools = async (req, res) => {
83
87
  res.jsonFail(msg);
84
88
  }
85
89
  };
90
+
91
+ /**
92
+ * drawAgent - 流式 Agent 接口
93
+ * 流程:router -> classify -> elaborate -> review -> generate
94
+ */
95
+ exports.drawAgent = async (req, res) => {
96
+ const methodName = 'drawAgent';
97
+
98
+ // check
99
+ if (!req.body.userPrompt) {
100
+ const msg = 'need userPrompt';
101
+ req.logger.error(methodName, msg);
102
+ res.jsonFail(msg);
103
+ return;
104
+ }
105
+
106
+ // 启动 SSE
107
+ res.streamingStart();
108
+
109
+ const input = decodeURIComponent(req.body.userPrompt);
110
+ req.logger.info(methodName, 'userPrompt', input);
111
+
112
+ try {
113
+ // 1. router - 判断意图
114
+ res.streaming(`data: ${JSON.stringify({ step: 'router', status: 'start' })}\n\n`);
115
+ req.logger.info(methodName, 'step: router');
116
+ const intentResult = await callLLMForJSON(prompts.ROUTER_PROMPT.replace('{input}', input), res, 'router');
117
+ const intent = intentResult.intent;
118
+ req.logger.info(methodName, 'intent', intent);
119
+
120
+ // 非白板请求
121
+ if (intent === 'irrelevant') {
122
+ res.streaming(
123
+ `data: ${JSON.stringify({ step: 'router', result: 'irrelevant', response: prompts.FIXED_REPLY })}\n\n`,
124
+ );
125
+ res.streamingEnd();
126
+ return;
127
+ }
128
+
129
+ // 2. classify - 分类图表类型
130
+ res.streaming(`data: ${JSON.stringify({ step: 'classify', status: 'start' })}\n\n`);
131
+ req.logger.info(methodName, 'step: classify');
132
+ const classifyResult = await callLLMForJSON(prompts.CLASSIFY_PROMPT.replace('{input}', input), res, 'classify');
133
+ const diagramType = classifyResult.diagramType;
134
+ req.logger.info(methodName, 'diagramType', diagramType);
135
+
136
+ // 3. elaborate - 细化内容
137
+ res.streaming(`data: ${JSON.stringify({ step: 'elaborate', status: 'start' })}\n\n`);
138
+ req.logger.info(methodName, 'step: elaborate');
139
+ const elaboration = await callLLM(
140
+ prompts.ELABORATE_PROMPT.replace('{input}', input).replace('{diagramType}', diagramType),
141
+ res,
142
+ 'elaborate',
143
+ );
144
+ req.logger.info(methodName, 'elaboration', elaboration.slice(0, 100) + '...');
145
+
146
+ // 4. review - 质量检查
147
+ res.streaming(`data: ${JSON.stringify({ step: 'review', status: 'start' })}\n\n`);
148
+ req.logger.info(methodName, 'step: review');
149
+ const reviewResult = await callLLMForJSON(
150
+ prompts.REVIEW_PROMPT.replace('{input}', input)
151
+ .replace('{diagramType}', diagramType)
152
+ .replace('{elaboration}', elaboration),
153
+ res,
154
+ 'review',
155
+ );
156
+ req.logger.info(methodName, 'reviewResult', reviewResult);
157
+
158
+ // 信息不足,追问用户
159
+ if (reviewResult.result === 'need_more_info') {
160
+ res.streaming(
161
+ `data: ${JSON.stringify({
162
+ step: 'review',
163
+ result: 'need_more_info',
164
+ questions: reviewResult.questions,
165
+ })}\n\n`,
166
+ );
167
+ res.streamingEnd();
168
+ return;
169
+ }
170
+
171
+ // 5. generate - 生成 Mermaid
172
+ res.streaming(`data: ${JSON.stringify({ step: 'generate', status: 'start' })}\n\n`);
173
+ req.logger.info(methodName, 'step: generate');
174
+ const mermaidCode = await callLLM(
175
+ prompts.GENERATE_PROMPT.replace('{diagramType}', diagramType).replace('{elaboration}', elaboration),
176
+ res,
177
+ 'generate',
178
+ );
179
+
180
+ req.logger.info(methodName, 'mermaidCode', mermaidCode.slice(0, 100) + '...');
181
+
182
+ // 返回最终结果
183
+ res.streaming(`data: ${JSON.stringify({ step: 'generate', mermaidCode })}\n\n`);
184
+ res.streamingEnd();
185
+ } catch (error) {
186
+ req.logger.error(methodName, 'error', error);
187
+ res.streaming(`data: ${JSON.stringify({ step: 'error', message: error.message })}\n\n`);
188
+ res.streamingEnd();
189
+ }
190
+ };
@@ -0,0 +1,81 @@
1
+ // llm
2
+ const { OpenAIAPI } = require('viho-llm');
3
+
4
+ /**
5
+ * LLM 配置 - 使用 moonshot 平台的 kimi-k2.5
6
+ */
7
+ const llmConfig = {
8
+ apiKey: global.QZ_CONFIG.kimi.apiKey,
9
+ baseURL: global.QZ_CONFIG.kimi.baseURL,
10
+ modelName: global.QZ_CONFIG.kimi.modelID,
11
+ };
12
+
13
+ const llm = OpenAIAPI(llmConfig);
14
+
15
+ /**
16
+ * 从文本中提取 JSON
17
+ */
18
+ function extractJSON(text) {
19
+ const cleaned = text.replace(/\{\{/g, '{').replace(/\}\}/g, '}');
20
+ try {
21
+ return JSON.parse(cleaned);
22
+ } catch (e) {
23
+ // try next
24
+ }
25
+ const codeBlock = cleaned.match(/```(?:json)?\s*([\s\S]*?)```/);
26
+ if (codeBlock) {
27
+ try {
28
+ return JSON.parse(codeBlock[1].trim());
29
+ } catch (e) {
30
+ // try next
31
+ }
32
+ }
33
+ const match = cleaned.match(/(\{[\s\S]*\}|\[[\s\S]*\])/);
34
+ if (match) {
35
+ try {
36
+ return JSON.parse(match[1]);
37
+ } catch (e) {
38
+ // try next
39
+ }
40
+ }
41
+ throw new Error(`Cannot extract JSON from: ${text.slice(0, 200)}`);
42
+ }
43
+
44
+ /**
45
+ * 流式调用 LLM 并解析 JSON
46
+ * 使用非流式 API,但过程流式输出给客户端
47
+ */
48
+ exports.callLLMForJSON = async (prompt, res, step) => {
49
+ // 非流式调用,等待完整响应
50
+ const response = await llm.chat({
51
+ model: llmConfig.modelName,
52
+ messages: [{ role: 'user', content: prompt }],
53
+ });
54
+
55
+ const fullContent = response.content || '';
56
+ // 流式输出给客户端
57
+ res.streaming(`data: ${JSON.stringify({ step, delta: fullContent })}\n\n`);
58
+
59
+ return extractJSON(fullContent);
60
+ };
61
+
62
+ /**
63
+ * 流式调用 LLM(普通文本)
64
+ */
65
+ exports.callLLM = async (prompt, res, step) => {
66
+ let fullContent = '';
67
+
68
+ await llm.chatWithStreaming(
69
+ { model: llmConfig.modelName, messages: [{ role: 'user', content: prompt }] },
70
+ {
71
+ contentCallback: (chunk) => {
72
+ fullContent += chunk;
73
+ // 直接流式返回给客户端
74
+ res.streaming(`data: ${JSON.stringify({ step, delta: chunk })}\n\n`);
75
+ },
76
+ },
77
+ );
78
+
79
+ // 返回完整内容(用于后续处理)
80
+ return fullContent;
81
+ };
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Agent Prompts
3
+ * 从 test-agent/src/prompts/index.ts 复制
4
+ */
5
+
6
+ module.exports = {
7
+ /**
8
+ * 意图分类 - 判断是否与白板相关
9
+ */
10
+ ROUTER_PROMPT: `你是一个意图分类器。判断用户输入是否与"画图/白板/图表/流程图/甘特图/思维导图/时序图/PRD"相关。
11
+
12
+ 用户输入: {input}
13
+
14
+ 只回复 JSON: {"intent": "whiteboard"} 或 {"intent": "irrelevant"}`,
15
+
16
+ /**
17
+ * 图表类型分类
18
+ */
19
+ CLASSIFY_PROMPT: `你是一个图表类型分析专家。根据用户需求,判断最适合的图表类型。
20
+
21
+ 可选类型:
22
+ - flowchart: 流程图、步骤图、决策流程
23
+ - gantt: 甘特图、项目计划、时间线
24
+ - prd: 产品需求文档结构图
25
+ - mindmap: 思维导图、知识结构
26
+ - sequence: 时序图、交互流程
27
+
28
+ 用户输入: {input}
29
+
30
+ 只回复 JSON: {"diagramType": "类型"}`,
31
+
32
+ /**
33
+ * 图表内容细化
34
+ */
35
+ ELABORATE_PROMPT: `你是一个图表内容规划专家。根据用户需求和图表类型,细化出具体的节点、关系和步骤。
36
+
37
+ 用户输入: {input}
38
+ 图表类型: {diagramType}
39
+
40
+ 请输出结构化的 JSON 描述,包含图表的所有节点、连接关系、标签等信息。
41
+ 根据图表类型调整结构:
42
+ - flowchart: {"nodes": [...], "edges": [...]}
43
+ - gantt: {"tasks": [{"name": "", "start": "", "duration": ""}]}
44
+ - mindmap: {"root": "", "children": [...]}
45
+ - sequence: {"participants": [...], "messages": [...]}
46
+ - prd: {"modules": [...], "relations": [...]}
47
+
48
+ 只回复 JSON。`,
49
+
50
+ /**
51
+ * 质量检查
52
+ */
53
+ REVIEW_PROMPT: `你是一个质量检查专家。检查以下图表描述是否信息充足,能否生成完整的图表。
54
+
55
+ 用户原始输入: {input}
56
+ 图表类型: {diagramType}
57
+ 细化描述: {elaboration}
58
+
59
+ 判断标准:
60
+ 1. 节点/步骤是否足够(至少3个有意义的元素)
61
+ 2. 关系/连接是否清晰
62
+ 3. 是否有明显缺失的关键信息
63
+
64
+ 如果信息充足,回复: {"result": "pass"}
65
+ 如果信息不足,回复: {"result": "need_more_info", "questions": ["问题1", "问题2"]}
66
+
67
+ 只回复 JSON。`,
68
+
69
+ /**
70
+ * Mermaid 生成
71
+ */
72
+ GENERATE_PROMPT: `你是一个 Mermaid 图表生成专家。根据以下信息生成正确的 Mermaid 代码。
73
+
74
+ 图表类型: {diagramType}
75
+ 细化描述: {elaboration}
76
+
77
+ 要求:
78
+ 1. 生成合法的 Mermaid 语法
79
+ 2. 使用中文标签
80
+ 3. 结构清晰美观
81
+
82
+ 只回复 Mermaid 代码,不要包含 \`\`\`mermaid 标记。`,
83
+
84
+ /**
85
+ * 非白板请求的固定回复
86
+ */
87
+ FIXED_REPLY:
88
+ '我是一个白板助手,专门帮你生成各种图表。你可以让我画流程图、甘特图、思维导图、时序图等。请告诉我你想画什么图吧!',
89
+ };