@shun-js/aibaiban-server 1.2.0 → 1.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shun-js/aibaiban-server",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "aibaiban.com server",
5
5
  "keywords": [
6
6
  "ai aibaiban"
@@ -37,7 +37,7 @@
37
37
  "qiao-z-nuser": "^6.0.0",
38
38
  "qiao-z-service": "^6.0.0",
39
39
  "qiao-z-sms": "^6.0.0",
40
- "viho-llm": "^1.0.8",
40
+ "viho-llm": "^1.0.9",
41
41
  "zod": "^4.3.6",
42
42
  "zod-to-json-schema": "^3.25.1"
43
43
  },
@@ -45,5 +45,5 @@
45
45
  "access": "public",
46
46
  "registry": "https://registry.npmjs.org/"
47
47
  },
48
- "gitHead": "7b094023fede487784f42ca71277997906841cb1"
48
+ "gitHead": "9f171abf1a9b47fa0c4d33d38852e1778c533fdb"
49
49
  }
@@ -1,18 +1,12 @@
1
1
  // service
2
2
  const service = require('../service/LLMService.js');
3
- const service2 = require('../service/LLMServiceV2.js');
4
3
 
5
4
  /**
6
5
  * controller
7
6
  */
8
7
  module.exports = (app) => {
9
- // draw agent
10
- app.post('/chat-streaming', (req, res) => {
11
- service.drawAgent(req, res);
12
- });
13
-
14
8
  // draw agent
15
9
  app.post('/draw-agent', (req, res) => {
16
- service2.drawAgent(req, res);
10
+ service.drawAgent(req, res);
17
11
  });
18
12
  };
@@ -13,15 +13,15 @@ const modelName = finalLLMConfig.modelName;
13
13
  const thinking = finalLLMConfig.thinking;
14
14
 
15
15
  /**
16
- * drawAgent - 流式 Agent 接口
17
- * 流程:router -> classify -> elaborate -> generate
16
+ * drawAgent - 对话式 Agent
17
+ * 1 次决策 LLM reply / generate(elaborate+generate) / irrelevant
18
18
  */
19
19
  exports.drawAgent = async (req, res) => {
20
20
  const methodName = 'drawAgent';
21
+ const messages = req.body.messages;
21
22
 
22
- // check
23
- if (!req.body.userPrompt) {
24
- const msg = 'need userPrompt';
23
+ if (!messages?.length) {
24
+ const msg = 'need messages';
25
25
  req.logger.error(methodName, msg);
26
26
  res.jsonFail(msg);
27
27
  return;
@@ -30,142 +30,122 @@ exports.drawAgent = async (req, res) => {
30
30
  // 启动 SSE
31
31
  res.streamingStart();
32
32
 
33
- const input = decodeURIComponent(req.body.userPrompt);
34
- req.logger.info(methodName, 'userPrompt', input);
35
- chatFeishuMsg(req, `userPrompt-${input}`);
33
+ const lastUserMsg = messages.filter((m) => m.role === 'user').pop()?.content || '';
34
+ req.logger.info(methodName, 'lastUserMsg', lastUserMsg);
35
+ chatFeishuMsg(req, `${lastUserMsg}`);
36
36
 
37
37
  try {
38
38
  const startTime = Date.now();
39
- let stepStart = startTime;
40
39
 
41
- // agent 管线共享状态
42
- let diagramType = '';
43
- let elaboration = '';
40
+ // 1. Agent 决策
41
+ res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'thinking' })}\n\n`);
44
42
 
43
+ let decision = null;
45
44
  await runAgents([
46
- // 1. router - 意图分类
47
45
  {
48
46
  agentStartCallback: () => {
49
- stepStart = Date.now();
50
- res.streaming(`data: ${JSON.stringify({ step: 'router', status: 'start' })}\n\n`);
51
- req.logger.info(methodName, 'step: router');
47
+ req.logger.info(methodName, 'step: agent decision');
52
48
  },
53
49
  agentRequestOptions: {
54
50
  llm,
55
51
  modelName,
56
- messages: [{ role: 'user', content: prompts.ROUTER_PROMPT.replace('{input}', input) }],
57
- thinking: thinking,
52
+ thinking,
58
53
  isJson: true,
54
+ messages: [{ role: 'system', content: prompts.AGENT_PROMPT }, ...messages],
59
55
  },
60
56
  agentEndCallback: (result) => {
61
- const intent = result.intent;
62
- const duration = Date.now() - stepStart;
63
- req.logger.info(methodName, 'intent', intent, `${duration}ms`);
64
- chatFeishuMsg(req, `intent-${intent}`);
65
- res.streaming(`data: ${JSON.stringify({ step: 'router', intent, duration })}\n\n`);
66
- return intent === 'irrelevant';
67
- },
68
- agentBreakCallback: () => {
69
- res.streaming(
70
- `data: ${JSON.stringify({ step: 'router', result: 'irrelevant', response: prompts.FIXED_REPLY })}\n\n`,
71
- );
57
+ decision = result;
58
+ const duration = Date.now() - startTime;
59
+ req.logger.info(methodName, 'decision', JSON.stringify(decision), `${duration}ms`);
60
+ chatFeishuMsg(req, `decision-${decision.action}`);
72
61
  },
73
62
  },
74
- // 2. classify - 分类图表类型
75
- {
76
- agentStartCallback: () => {
77
- stepStart = Date.now();
78
- res.streaming(`data: ${JSON.stringify({ step: 'classify', status: 'start' })}\n\n`);
79
- req.logger.info(methodName, 'step: classify');
80
- },
81
- agentRequestOptions: {
82
- llm,
83
- modelName,
84
- messages: [{ role: 'user', content: prompts.CLASSIFY_PROMPT.replace('{input}', input) }],
85
- thinking: thinking,
86
- isJson: true,
87
- },
88
- agentEndCallback: (result) => {
89
- diagramType = result.diagramType;
90
- const duration = Date.now() - stepStart;
91
- req.logger.info(methodName, 'diagramType', diagramType, `${duration}ms`);
92
- chatFeishuMsg(req, `diagramType-${diagramType}`);
93
- res.streaming(`data: ${JSON.stringify({ step: 'classify', diagramType, duration })}\n\n`);
94
- },
95
- },
96
- // 3. elaborate - 细化内容
97
- {
98
- agentStartCallback: () => {
99
- stepStart = Date.now();
100
- res.streaming(`data: ${JSON.stringify({ step: 'elaborate', status: 'start' })}\n\n`);
101
- req.logger.info(methodName, 'step: elaborate');
102
- },
103
- agentRequestOptions: {
104
- llm,
105
- modelName,
106
- get messages() {
107
- return [
108
- {
109
- role: 'user',
110
- content: prompts.ELABORATE_PROMPT.replace('{input}', input).replace('{diagramType}', diagramType),
111
- },
112
- ];
63
+ ]);
64
+
65
+ // 2. 分发
66
+ if (decision.action === 'reply') {
67
+ res.streaming(`data: ${JSON.stringify({ type: 'message', content: decision.message })}\n\n`);
68
+ } else if (decision.action === 'irrelevant') {
69
+ res.streaming(`data: ${JSON.stringify({ type: 'message', content: prompts.FIXED_REPLY })}\n\n`);
70
+ } else if (decision.action === 'generate') {
71
+ let elaboration = '';
72
+
73
+ await runAgents([
74
+ // elaborate
75
+ {
76
+ agentStartCallback: () => {
77
+ res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'elaborate' })}\n\n`);
78
+ req.logger.info(methodName, 'step: elaborate');
113
79
  },
114
- thinking: thinking,
115
- },
116
- agentEndCallback: (result) => {
117
- elaboration = result;
118
- const duration = Date.now() - stepStart;
119
- req.logger.info(methodName, 'elaboration', elaboration.slice(0, 100) + '...', `${duration}ms`);
120
- chatFeishuMsg(req, `elaboration-${elaboration}`);
121
- res.streaming(`data: ${JSON.stringify({ step: 'elaborate', done: true, duration })}\n\n`);
122
- },
123
- },
124
- // 4. generate - 生成 Mermaid
125
- {
126
- agentStartCallback: () => {
127
- stepStart = Date.now();
128
- res.streaming(`data: ${JSON.stringify({ step: 'generate', status: 'start' })}\n\n`);
129
- req.logger.info(methodName, 'step: generate');
130
- },
131
- agentRequestOptions: {
132
- llm,
133
- modelName,
134
- get messages() {
135
- return [
136
- {
137
- role: 'user',
138
- content: prompts.GENERATE_PROMPT.replace('{diagramType}', diagramType).replace(
139
- '{elaboration}',
140
- elaboration,
141
- ),
142
- },
143
- ];
80
+ agentRequestOptions: {
81
+ llm,
82
+ modelName,
83
+ thinking,
84
+ get messages() {
85
+ return [
86
+ {
87
+ role: 'user',
88
+ content: prompts.ELABORATE_PROMPT.replace('{input}', decision.description).replace(
89
+ '{diagramType}',
90
+ decision.diagramType,
91
+ ),
92
+ },
93
+ ];
94
+ },
95
+ },
96
+ agentEndCallback: (result) => {
97
+ elaboration = result;
98
+ req.logger.info(methodName, 'elaboration', String(elaboration).slice(0, 100) + '...');
144
99
  },
145
- thinking: thinking,
146
100
  },
147
- agentEndCallback: (result) => {
148
- const mermaidCode = result;
149
- const duration = Date.now() - stepStart;
150
- const totalDuration = Date.now() - startTime;
151
- req.logger.info(
152
- methodName,
153
- 'mermaidCode',
154
- mermaidCode.slice(0, 100) + '...',
155
- `${duration}ms`,
156
- `total: ${totalDuration}ms`,
157
- );
158
- chatFeishuMsg(req, `mermaidCode-${mermaidCode}`);
159
- res.streaming(`data: ${JSON.stringify({ step: 'generate', mermaidCode, duration, totalDuration })}\n\n`);
101
+ // generate
102
+ {
103
+ agentStartCallback: () => {
104
+ res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'generate' })}\n\n`);
105
+ req.logger.info(methodName, 'step: generate');
106
+ },
107
+ agentRequestOptions: {
108
+ llm,
109
+ modelName,
110
+ thinking,
111
+ get messages() {
112
+ return [
113
+ {
114
+ role: 'user',
115
+ content: prompts.GENERATE_PROMPT.replace('{diagramType}', decision.diagramType).replace(
116
+ '{elaboration}',
117
+ elaboration,
118
+ ),
119
+ },
120
+ ];
121
+ },
122
+ },
123
+ agentEndCallback: (result) => {
124
+ const mermaidCode = result;
125
+ const duration = Date.now() - startTime;
126
+ req.logger.info(
127
+ methodName,
128
+ 'mermaidCode',
129
+ String(mermaidCode).slice(0, 100) + '...',
130
+ `total: ${duration}ms`,
131
+ );
132
+ chatFeishuMsg(req, `mermaid-${mermaidCode}`);
133
+ res.streaming(`data: ${JSON.stringify({ type: 'mermaid', code: mermaidCode, duration })}\n\n`);
134
+ },
160
135
  },
161
- },
162
- ]);
136
+ ]);
137
+ } else {
138
+ // 未知 action fallback
139
+ res.streaming(
140
+ `data: ${JSON.stringify({ type: 'message', content: decision.message || '可以再说一次吗?' })}\n\n`,
141
+ );
142
+ }
163
143
 
164
144
  res.streamingEnd();
165
145
  } catch (error) {
166
146
  req.logger.error(methodName, 'error', error);
167
147
  errorFeishuMsg(req, error.message);
168
- res.streaming(`data: ${JSON.stringify({ step: 'error', message: error.message })}\n\n`);
148
+ res.streaming(`data: ${JSON.stringify({ type: 'error', message: error.message })}\n\n`);
169
149
  res.streamingEnd();
170
150
  }
171
151
  };
@@ -1,38 +1,44 @@
1
1
  /**
2
2
  * Agent Prompts
3
- * 从 test-agent/src/prompts/index.ts 复制
4
3
  */
5
4
 
6
5
  module.exports = {
7
6
  /**
8
- * 意图分类 - 判断是否与白板相关
7
+ * Agent 决策 prompt
8
+ * 一次 LLM 调用完成意图判断 + 决策
9
9
  */
10
- ROUTER_PROMPT: `你是一个意图分类器。判断用户输入是否与"画图/白板/图表/流程图/时序图/类图/ER图"相关。
10
+ AGENT_PROMPT: `你是 AI 白板助手,帮用户在白板上画图表。
11
11
 
12
- 用户输入: {input}
12
+ ## 能力
13
+ 支持 4 种图表:flowchart(流程图)、sequence(时序图)、classDiagram(类图)、erDiagram(ER图)。
13
14
 
14
- 只回复 JSON: {"intent": "whiteboard"} 或 {"intent": "irrelevant"}`,
15
+ ## 决策规则
16
+ 收到用户消息后判断,只回复 JSON:
15
17
 
16
- /**
17
- * 图表类型分类
18
- */
19
- CLASSIFY_PROMPT: `你是一个图表类型分析专家。根据用户需求,判断最适合的图表类型。
18
+ 1. 回复文字(追问、确认、引导):
19
+ {"action":"reply","message":"你的回复内容"}
20
20
 
21
- 可选类型:
22
- - flowchart: 流程图、步骤图、决策流程、状态图、思维导图等通用图表
23
- - sequence: 时序图、交互流程、调用链、消息传递
24
- - classDiagram: 类图、对象关系、继承结构、接口设计
25
- - erDiagram: ER图、实体关系图、数据库设计、数据模型
21
+ 2. 生成图表(信息已足够明确):
22
+ {"action":"generate","diagramType":"类型","description":"详细描述,包含所有节点、关系、步骤"}
26
23
 
27
- 注意:
28
- - 只能从以上4种类型中选择
29
- - 提到"ER图"、"实体关系"、"数据库"、"表结构"时必须选 erDiagram
30
- - 提到"类"、"继承"、"接口"、"对象"时选 classDiagram
31
- - 如果不确定,默认选 flowchart
24
+ 3. 与画图完全无关:
25
+ {"action":"irrelevant"}
32
26
 
33
- 用户输入: {input}
27
+ ## 判断标准
28
+ - 用户说了图表类型 + 具体场景/内容 → generate
29
+ - 用户只说了类型没说内容 → reply 追问具体场景
30
+ - 用户只说了内容没说类型 → 你来判断最合适的类型,直接 generate
31
+ - 用户说"帮我画个图" → reply 追问想画什么
32
+ - 用户要修改已有图表 → 根据对话历史理解上下文,generate 完整新图表
33
+ - 用户闲聊但话题相关 → reply 自然回应并引导回画图
34
+ - 用户闲聊话题无关 → irrelevant
35
+
36
+ ## 回复风格
37
+ - 追问时友好自然,像朋友聊天,不要生硬
38
+ - 可以给建议和示例帮助用户想清楚需求
39
+ - generate 时 description 要尽可能详细完整
34
40
 
35
- 只回复 JSON: {"diagramType": "类型"}`,
41
+ 只回复 JSON。`,
36
42
 
37
43
  /**
38
44
  * 图表内容细化
@@ -1,188 +0,0 @@
1
- // llm
2
- const { OpenAIAPI, runAgents } = require('viho-llm');
3
- const prompts = require('../util/prompt-agent-v2.js');
4
-
5
- // util
6
- const { chatFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
7
-
8
- // LLM 配置
9
- const llmConfig = global.QZ_CONFIG.llm;
10
- const finalLLMConfig = llmConfig[llmConfig.default];
11
- const llm = OpenAIAPI(finalLLMConfig);
12
- const modelName = finalLLMConfig.modelName;
13
- const thinking = finalLLMConfig.thinking;
14
-
15
- /**
16
- * 从 LLM 返回文本中提取 JSON(兼容 markdown 代码块、前后有多余文字等情况)
17
- */
18
- function extractJSON(text) {
19
- if (typeof text === 'object' && text !== null) return text;
20
- const str = String(text).trim();
21
-
22
- // 1. 尝试直接解析
23
- try {
24
- return JSON.parse(str);
25
- } catch (_) {
26
- /* ignore */
27
- }
28
-
29
- // 2. 尝试从 markdown 代码块提取
30
- const codeBlockMatch = str.match(/```(?:json)?\s*\n?([\s\S]*?)\n?```/);
31
- if (codeBlockMatch) {
32
- try {
33
- return JSON.parse(codeBlockMatch[1].trim());
34
- } catch (_) {
35
- /* ignore */
36
- }
37
- }
38
-
39
- // 3. 尝试提取第一个 {...} 块
40
- const firstBrace = str.indexOf('{');
41
- const lastBrace = str.lastIndexOf('}');
42
- if (firstBrace !== -1 && lastBrace > firstBrace) {
43
- try {
44
- return JSON.parse(str.slice(firstBrace, lastBrace + 1));
45
- } catch (_) {
46
- /* ignore */
47
- }
48
- }
49
-
50
- throw new Error(`Cannot parse JSON from LLM response: ${str.slice(0, 200)}`);
51
- }
52
-
53
- /**
54
- * drawAgentV2 - 对话式 Agent
55
- * 1 次决策 LLM → reply / generate(elaborate+generate) / irrelevant
56
- */
57
- exports.drawAgent = async (req, res) => {
58
- const methodName = 'drawAgentV2';
59
- const messages = req.body.messages;
60
-
61
- if (!messages?.length) {
62
- const msg = 'need messages';
63
- req.logger.error(methodName, msg);
64
- res.jsonFail(msg);
65
- return;
66
- }
67
-
68
- // 启动 SSE
69
- res.streamingStart();
70
-
71
- const lastUserMsg = messages.filter((m) => m.role === 'user').pop()?.content || '';
72
- req.logger.info(methodName, 'lastUserMsg', lastUserMsg);
73
- chatFeishuMsg(req, `v2-${lastUserMsg}`);
74
-
75
- try {
76
- const startTime = Date.now();
77
-
78
- // 1. Agent 决策
79
- res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'thinking' })}\n\n`);
80
-
81
- let decision = null;
82
- await runAgents([
83
- {
84
- agentStartCallback: () => {
85
- req.logger.info(methodName, 'step: agent decision');
86
- },
87
- agentRequestOptions: {
88
- llm,
89
- modelName,
90
- thinking,
91
- messages: [{ role: 'system', content: prompts.AGENT_PROMPT }, ...messages],
92
- },
93
- agentEndCallback: (result) => {
94
- decision = extractJSON(result);
95
- const duration = Date.now() - startTime;
96
- req.logger.info(methodName, 'decision', JSON.stringify(decision), `${duration}ms`);
97
- chatFeishuMsg(req, `v2-decision-${decision.action}`);
98
- },
99
- },
100
- ]);
101
-
102
- // 2. 分发
103
- if (decision.action === 'reply') {
104
- res.streaming(`data: ${JSON.stringify({ type: 'message', content: decision.message })}\n\n`);
105
- } else if (decision.action === 'irrelevant') {
106
- res.streaming(`data: ${JSON.stringify({ type: 'message', content: prompts.FIXED_REPLY })}\n\n`);
107
- } else if (decision.action === 'generate') {
108
- let elaboration = '';
109
-
110
- await runAgents([
111
- // elaborate
112
- {
113
- agentStartCallback: () => {
114
- res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'elaborate' })}\n\n`);
115
- req.logger.info(methodName, 'step: elaborate');
116
- },
117
- agentRequestOptions: {
118
- llm,
119
- modelName,
120
- thinking,
121
- get messages() {
122
- return [
123
- {
124
- role: 'user',
125
- content: prompts.ELABORATE_PROMPT.replace('{input}', decision.description).replace(
126
- '{diagramType}',
127
- decision.diagramType,
128
- ),
129
- },
130
- ];
131
- },
132
- },
133
- agentEndCallback: (result) => {
134
- elaboration = result;
135
- req.logger.info(methodName, 'elaboration', String(elaboration).slice(0, 100) + '...');
136
- },
137
- },
138
- // generate
139
- {
140
- agentStartCallback: () => {
141
- res.streaming(`data: ${JSON.stringify({ type: 'status', step: 'generate' })}\n\n`);
142
- req.logger.info(methodName, 'step: generate');
143
- },
144
- agentRequestOptions: {
145
- llm,
146
- modelName,
147
- thinking,
148
- get messages() {
149
- return [
150
- {
151
- role: 'user',
152
- content: prompts.GENERATE_PROMPT.replace('{diagramType}', decision.diagramType).replace(
153
- '{elaboration}',
154
- elaboration,
155
- ),
156
- },
157
- ];
158
- },
159
- },
160
- agentEndCallback: (result) => {
161
- const mermaidCode = result;
162
- const duration = Date.now() - startTime;
163
- req.logger.info(
164
- methodName,
165
- 'mermaidCode',
166
- String(mermaidCode).slice(0, 100) + '...',
167
- `total: ${duration}ms`,
168
- );
169
- chatFeishuMsg(req, `v2-mermaid-${mermaidCode}`);
170
- res.streaming(`data: ${JSON.stringify({ type: 'mermaid', code: mermaidCode, duration })}\n\n`);
171
- },
172
- },
173
- ]);
174
- } else {
175
- // 未知 action fallback
176
- res.streaming(
177
- `data: ${JSON.stringify({ type: 'message', content: decision.message || '可以再说一次吗?' })}\n\n`,
178
- );
179
- }
180
-
181
- res.streamingEnd();
182
- } catch (error) {
183
- req.logger.error(methodName, 'error', error);
184
- errorFeishuMsg(req, error.message);
185
- res.streaming(`data: ${JSON.stringify({ type: 'error', message: error.message })}\n\n`);
186
- res.streamingEnd();
187
- }
188
- };
@@ -1,43 +0,0 @@
1
- /**
2
- * Agent V2 Prompts - 对话式 Agent
3
- */
4
- const { ELABORATE_PROMPT, GENERATE_PROMPT, FIXED_REPLY } = require('./prompt-agent.js');
5
-
6
- /**
7
- * Agent 决策 prompt
8
- * 一次 LLM 调用完成意图判断 + 决策
9
- */
10
- const AGENT_PROMPT = `你是 AI 白板助手,帮用户在白板上画图表。
11
-
12
- ## 能力
13
- 支持 4 种图表:flowchart(流程图)、sequence(时序图)、classDiagram(类图)、erDiagram(ER图)。
14
-
15
- ## 决策规则
16
- 收到用户消息后判断,只回复 JSON:
17
-
18
- 1. 回复文字(追问、确认、引导):
19
- {"action":"reply","message":"你的回复内容"}
20
-
21
- 2. 生成图表(信息已足够明确):
22
- {"action":"generate","diagramType":"类型","description":"详细描述,包含所有节点、关系、步骤"}
23
-
24
- 3. 与画图完全无关:
25
- {"action":"irrelevant"}
26
-
27
- ## 判断标准
28
- - 用户说了图表类型 + 具体场景/内容 → generate
29
- - 用户只说了类型没说内容 → reply 追问具体场景
30
- - 用户只说了内容没说类型 → 你来判断最合适的类型,直接 generate
31
- - 用户说"帮我画个图" → reply 追问想画什么
32
- - 用户要修改已有图表 → 根据对话历史理解上下文,generate 完整新图表
33
- - 用户闲聊但话题相关 → reply 自然回应并引导回画图
34
- - 用户闲聊话题无关 → irrelevant
35
-
36
- ## 回复风格
37
- - 追问时友好自然,像朋友聊天,不要生硬
38
- - 可以给建议和示例帮助用户想清楚需求
39
- - generate 时 description 要尽可能详细完整
40
-
41
- 只回复 JSON。`;
42
-
43
- module.exports = { AGENT_PROMPT, ELABORATE_PROMPT, GENERATE_PROMPT, FIXED_REPLY };