@shun-js/aibaiban-server 0.4.7 → 0.4.9
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 +2 -2
- package/server/controller/IndexController.js +3 -0
- package/server/service/IndexService.js +61 -26
- package/server/util/llm.js +15 -131
- package/server/util/llm1.js +208 -0
- package/server/util/prompt-intent.md +107 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shun-js/aibaiban-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
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": "822aa69649e8feaff1bed2c0e02c2b527d432568"
|
|
48
48
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
// llm
|
|
2
|
-
const {
|
|
2
|
+
const { llmParseIntent } = require('../util/llm');
|
|
3
3
|
|
|
4
4
|
// util
|
|
5
|
-
const { chatFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
|
|
5
|
+
const { chatFeishuMsg, chatResFeishuMsg, errorFeishuMsg } = require('../util/feishu.js');
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* index
|
|
@@ -44,28 +44,63 @@ exports.chat = async (req, res) => {
|
|
|
44
44
|
chatFeishuMsg(req);
|
|
45
45
|
|
|
46
46
|
// go
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
try {
|
|
48
|
+
const llmParseIntentRes = await llmParseIntent(userPrompt);
|
|
49
|
+
req.logger.info(methodName, 'llmParseIntentRes', llmParseIntentRes);
|
|
50
|
+
|
|
51
|
+
// r
|
|
52
|
+
chatResFeishuMsg(req, JSON.stringify(llmParseIntentRes));
|
|
53
|
+
res.jsonSuccess('success', llmParseIntentRes);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const msg = 'chat error';
|
|
56
|
+
errorFeishuMsg(req, msg);
|
|
57
|
+
req.logger.error(methodName, msg, error);
|
|
58
|
+
res.jsonFail(msg);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* chatWithStreaming
|
|
64
|
+
* @param {*} req
|
|
65
|
+
* @param {*} res
|
|
66
|
+
* @returns
|
|
67
|
+
*/
|
|
68
|
+
exports.chatWithStreaming = async () => {
|
|
69
|
+
// const methodName = 'chatWithStreaming';
|
|
70
|
+
// // check
|
|
71
|
+
// if (!req.body.userPrompt) {
|
|
72
|
+
// const msg = 'need userPrompt';
|
|
73
|
+
// req.logger.error(methodName, msg);
|
|
74
|
+
// res.jsonFail(msg);
|
|
75
|
+
// return;
|
|
76
|
+
// }
|
|
77
|
+
// // const
|
|
78
|
+
// const userPrompt = decodeURIComponent(req.body.userPrompt);
|
|
79
|
+
// req.logger.info(methodName, 'userPrompt', userPrompt);
|
|
80
|
+
// chatFeishuMsg(req);
|
|
81
|
+
// // go
|
|
82
|
+
// const userPrompts = [{ text: userPrompt }];
|
|
83
|
+
// chatWithStreaming(userPrompts, {
|
|
84
|
+
// beginCallback: () => {
|
|
85
|
+
// req.logger.info(methodName, 'beginCallback');
|
|
86
|
+
// res.streamingStart();
|
|
87
|
+
// },
|
|
88
|
+
// firstContentCallback: () => {
|
|
89
|
+
// req.logger.info(methodName, 'firstContentCallback');
|
|
90
|
+
// // res.streaming('First chunk received!');
|
|
91
|
+
// },
|
|
92
|
+
// contentCallback: (content) => {
|
|
93
|
+
// req.logger.info(methodName, 'contentCallback', content);
|
|
94
|
+
// res.streaming(content);
|
|
95
|
+
// },
|
|
96
|
+
// endCallback: () => {
|
|
97
|
+
// req.logger.info(methodName, 'endCallback');
|
|
98
|
+
// res.streamingEnd();
|
|
99
|
+
// },
|
|
100
|
+
// errorCallback: (error) => {
|
|
101
|
+
// errorFeishuMsg(req, 'errorCallback');
|
|
102
|
+
// req.logger.info(methodName, 'errorCallback', error);
|
|
103
|
+
// res.streamingEnd();
|
|
104
|
+
// },
|
|
105
|
+
// });
|
|
71
106
|
};
|
package/server/util/llm.js
CHANGED
|
@@ -8,154 +8,38 @@ const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
|
8
8
|
|
|
9
9
|
// llm
|
|
10
10
|
const { GeminiVertex } = require('viho-llm');
|
|
11
|
-
|
|
12
|
-
// gemini
|
|
13
11
|
const gemini = GeminiVertex({
|
|
14
12
|
projectId: global.QZ_CONFIG.gemini.projectId,
|
|
15
13
|
location: global.QZ_CONFIG.gemini.location,
|
|
16
14
|
modelName: global.QZ_CONFIG.gemini.modelName,
|
|
17
15
|
});
|
|
18
16
|
|
|
19
|
-
//
|
|
20
|
-
|
|
17
|
+
// const
|
|
21
18
|
let cachedSystemPrompt = null;
|
|
22
|
-
let cachedCompiledSchema = null;
|
|
23
|
-
|
|
24
|
-
// ==================== JSON Schema 定义(只构建一次)====================
|
|
25
|
-
|
|
26
19
|
const jsonSchema = (() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.describe('形状类型,默认 rectangle'),
|
|
34
|
-
color: z
|
|
35
|
-
.enum(['blue', 'green', 'purple', 'orange', 'red', 'gray', 'yellow', 'pink', 'black'])
|
|
36
|
-
.optional()
|
|
37
|
-
.describe('节点颜色,默认 blue'),
|
|
38
|
-
x: z.number().optional().describe('X 坐标,不指定则自动布局'),
|
|
39
|
-
y: z.number().optional().describe('Y 坐标,不指定则自动布局'),
|
|
40
|
-
width: z.number().optional().describe('宽度,不指定则使用默认值'),
|
|
41
|
-
height: z.number().optional().describe('高度,不指定则使用默认值'),
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const DiagramConnectionSchema = z.object({
|
|
45
|
-
from: z.string().describe('起始节点 id'),
|
|
46
|
-
to: z.string().describe('目标节点 id'),
|
|
47
|
-
label: z.string().optional().describe('连接线上的文字标签'),
|
|
48
|
-
type: z.enum(['arrow', 'line']).optional().describe('连接类型:arrow=箭头线,line=直线,默认 arrow'),
|
|
49
|
-
style: z
|
|
50
|
-
.enum(['solid', 'dashed', 'dotted'])
|
|
51
|
-
.optional()
|
|
52
|
-
.describe('线条样式:solid=实线,dashed=虚线,dotted=点线,默认 solid'),
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const TextAnnotationSchema = z.object({
|
|
56
|
-
id: z.string().describe('标注唯一标识符'),
|
|
57
|
-
text: z.string().describe('标注文本内容'),
|
|
58
|
-
x: z.number().optional().describe('X 坐标'),
|
|
59
|
-
y: z.number().optional().describe('Y 坐标'),
|
|
60
|
-
fontSize: z.number().optional().describe('字体大小'),
|
|
61
|
-
color: z.string().optional().describe('文字颜色'),
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const FrameSchema = z.object({
|
|
65
|
-
id: z.string().describe('框架唯一标识符'),
|
|
66
|
-
label: z.string().optional().describe('框架标题'),
|
|
67
|
-
children: z.array(z.string()).describe('包含的节点 id 列表'),
|
|
68
|
-
color: z.string().optional().describe('框架颜色'),
|
|
69
|
-
x: z.number().optional().describe('X 坐标'),
|
|
70
|
-
y: z.number().optional().describe('Y 坐标'),
|
|
71
|
-
width: z.number().optional().describe('宽度'),
|
|
72
|
-
height: z.number().optional().describe('高度'),
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const ImageElementSchema = z.object({
|
|
76
|
-
id: z.string().describe('图片唯一标识符'),
|
|
77
|
-
imageUrl: z.string().url().describe('图片 URL'),
|
|
78
|
-
alt: z.string().optional().describe('图片描述'),
|
|
79
|
-
x: z.number().optional().describe('X 坐标'),
|
|
80
|
-
y: z.number().optional().describe('Y 坐标'),
|
|
81
|
-
width: z.number().optional().describe('宽度'),
|
|
82
|
-
height: z.number().optional().describe('高度'),
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
const FreedrawElementSchema = z.object({
|
|
86
|
-
id: z.string().describe('手绘元素唯一标识符'),
|
|
87
|
-
points: z.array(z.tuple([z.number(), z.number()])).describe('路径点坐标数组'),
|
|
88
|
-
color: z.string().optional().describe('线条颜色'),
|
|
89
|
-
strokeWidth: z.number().optional().describe('线条宽度'),
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
const SimplifiedDiagramSchema = z.object({
|
|
93
|
-
type: z
|
|
94
|
-
.enum(['architecture', 'flowchart', 'sequence', 'custom'])
|
|
95
|
-
.describe('图表类型:architecture=架构图,flowchart=流程图,sequence=时序图,custom=自定义'),
|
|
96
|
-
title: z.string().optional().describe('图表标题'),
|
|
97
|
-
nodes: z.array(DiagramNodeSchema).describe('节点列表,至少包含一个节点'),
|
|
98
|
-
connections: z.array(DiagramConnectionSchema).describe('连接关系列表'),
|
|
99
|
-
annotations: z.array(TextAnnotationSchema).optional().describe('独立文本标注列表(可选)'),
|
|
100
|
-
frames: z.array(FrameSchema).optional().describe('分组框架列表(可选)'),
|
|
101
|
-
images: z.array(ImageElementSchema).optional().describe('图片元素列表(可选)'),
|
|
102
|
-
freedraws: z.array(FreedrawElementSchema).optional().describe('手绘元素列表(可选)'),
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
const DiagramErrorSchema = z.object({
|
|
106
|
-
type: z.literal('error').describe('固定值:error'),
|
|
107
|
-
error: z.enum(['NON_DRAWING_REQUEST', 'INVALID_REQUEST', 'UNKNOWN_ERROR']).describe('错误类型'),
|
|
108
|
-
message: z.string().describe('错误提示消息,必须使用固定的友好提示文本'),
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
return z.discriminatedUnion('type', [SimplifiedDiagramSchema, DiagramErrorSchema]);
|
|
20
|
+
return zodToJsonSchema(
|
|
21
|
+
z.object({
|
|
22
|
+
intent: z.enum(['DRAW', 'REJECT']).describe('用户意图:DRAW(绘图)或 REJECT(非绘图)'),
|
|
23
|
+
reason: z.string().describe('判断理由(一句话说明)'),
|
|
24
|
+
}),
|
|
25
|
+
);
|
|
112
26
|
})();
|
|
113
27
|
|
|
114
|
-
// ==================== 懒加载函数 ====================
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* 获取系统提示词(懒加载+缓存)
|
|
118
|
-
*/
|
|
119
|
-
async function getSystemPrompt() {
|
|
120
|
-
if (!cachedSystemPrompt) {
|
|
121
|
-
const promptPath = path.resolve(__dirname, './prompt.md');
|
|
122
|
-
cachedSystemPrompt = await readFile(promptPath);
|
|
123
|
-
}
|
|
124
|
-
return cachedSystemPrompt;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 获取编译后的 JSON Schema(懒加载+缓存)
|
|
129
|
-
*/
|
|
130
|
-
function getCompiledSchema() {
|
|
131
|
-
if (!cachedCompiledSchema) {
|
|
132
|
-
cachedCompiledSchema = zodToJsonSchema(jsonSchema, {
|
|
133
|
-
name: 'AIResponse',
|
|
134
|
-
$refStrategy: 'none',
|
|
135
|
-
});
|
|
136
|
-
}
|
|
137
|
-
return cachedCompiledSchema;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// ==================== 主要功能 ====================
|
|
141
|
-
|
|
142
28
|
/**
|
|
143
|
-
*
|
|
29
|
+
* llmParseIntent
|
|
144
30
|
* @param {*} userPrompts
|
|
145
|
-
* @param {*} callbackOptions
|
|
146
31
|
*/
|
|
147
|
-
exports.
|
|
148
|
-
//
|
|
149
|
-
|
|
150
|
-
const compiledSchema = getCompiledSchema();
|
|
32
|
+
exports.llmParseIntent = async (userPrompts) => {
|
|
33
|
+
// cache
|
|
34
|
+
if (!cachedSystemPrompt) cachedSystemPrompt = await readFile(path.resolve(__dirname, './prompt-intent.md'));
|
|
151
35
|
|
|
152
36
|
// chat options
|
|
153
37
|
const chatOptions = {
|
|
154
38
|
contents: userPrompts,
|
|
155
|
-
systemInstruction:
|
|
39
|
+
systemInstruction: cachedSystemPrompt,
|
|
156
40
|
config: {
|
|
157
41
|
responseMimeType: 'application/json',
|
|
158
|
-
responseJsonSchema:
|
|
42
|
+
responseJsonSchema: jsonSchema,
|
|
159
43
|
temperature: 0.2,
|
|
160
44
|
topP: 0.9,
|
|
161
45
|
topK: 40,
|
|
@@ -185,6 +69,6 @@ exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
|
|
|
185
69
|
},
|
|
186
70
|
};
|
|
187
71
|
|
|
188
|
-
//
|
|
189
|
-
await gemini.
|
|
72
|
+
// go
|
|
73
|
+
return await gemini.chat(chatOptions);
|
|
190
74
|
};
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// path
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { readFile } = require('qiao-file');
|
|
4
|
+
|
|
5
|
+
// z
|
|
6
|
+
const { z } = require('zod');
|
|
7
|
+
const { zodToJsonSchema } = require('zod-to-json-schema');
|
|
8
|
+
|
|
9
|
+
// llm
|
|
10
|
+
const { GeminiVertex } = require('viho-llm');
|
|
11
|
+
|
|
12
|
+
// gemini
|
|
13
|
+
const gemini = GeminiVertex({
|
|
14
|
+
projectId: global.QZ_CONFIG.gemini.projectId,
|
|
15
|
+
location: global.QZ_CONFIG.gemini.location,
|
|
16
|
+
modelName: global.QZ_CONFIG.gemini.modelName,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// ==================== 缓存(懒加载) ====================
|
|
20
|
+
|
|
21
|
+
let cachedSystemPrompt = null;
|
|
22
|
+
let cachedCompiledSchema = null;
|
|
23
|
+
|
|
24
|
+
// ==================== JSON Schema 定义(避免联合类型)====================
|
|
25
|
+
|
|
26
|
+
const jsonSchema = (() => {
|
|
27
|
+
const DiagramNodeSchema = z.object({
|
|
28
|
+
id: z.string().describe('节点唯一标识符,使用驼峰命名法,如 userService, mysqlDB'),
|
|
29
|
+
label: z.string().describe('节点显示文本,支持 \\n 换行'),
|
|
30
|
+
type: z
|
|
31
|
+
.enum(['rectangle', 'ellipse', 'diamond', 'hexagon', 'cylinder', 'cloud'])
|
|
32
|
+
.optional()
|
|
33
|
+
.describe('形状类型,默认 rectangle'),
|
|
34
|
+
color: z
|
|
35
|
+
.enum(['blue', 'green', 'purple', 'orange', 'red', 'gray', 'yellow', 'pink', 'black'])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe('节点颜色,默认 blue'),
|
|
38
|
+
x: z.number().optional().describe('X 坐标,不指定则自动布局'),
|
|
39
|
+
y: z.number().optional().describe('Y 坐标,不指定则自动布局'),
|
|
40
|
+
width: z.number().optional().describe('宽度,不指定则使用默认值'),
|
|
41
|
+
height: z.number().optional().describe('高度,不指定则使用默认值'),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const DiagramConnectionSchema = z.object({
|
|
45
|
+
from: z.string().describe('起始节点 id'),
|
|
46
|
+
to: z.string().describe('目标节点 id'),
|
|
47
|
+
label: z.string().optional().describe('连接线上的文字标签'),
|
|
48
|
+
type: z.enum(['arrow', 'line']).optional().describe('连接类型:arrow=箭头线,line=直线,默认 arrow'),
|
|
49
|
+
style: z
|
|
50
|
+
.enum(['solid', 'dashed', 'dotted'])
|
|
51
|
+
.optional()
|
|
52
|
+
.describe('线条样式:solid=实线,dashed=虚线,dotted=点线,默认 solid'),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const TextAnnotationSchema = z.object({
|
|
56
|
+
id: z.string().describe('标注唯一标识符'),
|
|
57
|
+
text: z.string().describe('标注文本内容'),
|
|
58
|
+
x: z.number().optional().describe('X 坐标'),
|
|
59
|
+
y: z.number().optional().describe('Y 坐标'),
|
|
60
|
+
fontSize: z.number().optional().describe('字体大小'),
|
|
61
|
+
color: z.string().optional().describe('文字颜色'),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const FrameSchema = z.object({
|
|
65
|
+
id: z.string().describe('框架唯一标识符'),
|
|
66
|
+
label: z.string().optional().describe('框架标题'),
|
|
67
|
+
children: z.array(z.string()).describe('包含的节点 id 列表'),
|
|
68
|
+
color: z.string().optional().describe('框架颜色'),
|
|
69
|
+
x: z.number().optional().describe('X 坐标'),
|
|
70
|
+
y: z.number().optional().describe('Y 坐标'),
|
|
71
|
+
width: z.number().optional().describe('宽度'),
|
|
72
|
+
height: z.number().optional().describe('高度'),
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const ImageElementSchema = z.object({
|
|
76
|
+
id: z.string().describe('图片唯一标识符'),
|
|
77
|
+
imageUrl: z.string().url().describe('图片 URL'),
|
|
78
|
+
alt: z.string().optional().describe('图片描述'),
|
|
79
|
+
x: z.number().optional().describe('X 坐标'),
|
|
80
|
+
y: z.number().optional().describe('Y 坐标'),
|
|
81
|
+
width: z.number().optional().describe('宽度'),
|
|
82
|
+
height: z.number().optional().describe('高度'),
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// 注意:freedraws 使用简化格式,避免 tuple 导致的兼容性问题
|
|
86
|
+
const FreedrawElementSchema = z.object({
|
|
87
|
+
id: z.string().describe('手绘元素唯一标识符'),
|
|
88
|
+
points: z.array(z.number()).describe('路径点坐标数组,格式:[x1,y1,x2,y2,...]'),
|
|
89
|
+
color: z.string().optional().describe('线条颜色'),
|
|
90
|
+
strokeWidth: z.number().optional().describe('线条宽度'),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 统一的响应 Schema(包含图表和错误两种情况)
|
|
94
|
+
// 通过 type 字段区分:'architecture'|'flowchart'|'sequence'|'custom' = 图表,'error' = 错误
|
|
95
|
+
return z.object({
|
|
96
|
+
// 通用字段:type 用于区分响应类型
|
|
97
|
+
type: z
|
|
98
|
+
.enum(['architecture', 'flowchart', 'sequence', 'custom', 'error'])
|
|
99
|
+
.describe('响应类型:architecture/flowchart/sequence/custom=图表类型,error=错误响应'),
|
|
100
|
+
|
|
101
|
+
// ========== 图表相关字段(当 type != error 时使用)==========
|
|
102
|
+
title: z.string().optional().describe('图表标题'),
|
|
103
|
+
nodes: z.array(DiagramNodeSchema).optional().describe('节点列表'),
|
|
104
|
+
connections: z.array(DiagramConnectionSchema).optional().describe('连接关系列表'),
|
|
105
|
+
annotations: z.array(TextAnnotationSchema).optional().describe('独立文本标注列表(可选)'),
|
|
106
|
+
frames: z.array(FrameSchema).optional().describe('分组框架列表(可选)'),
|
|
107
|
+
images: z.array(ImageElementSchema).optional().describe('图片元素列表(可选)'),
|
|
108
|
+
freedraws: z.array(FreedrawElementSchema).optional().describe('手绘元素列表(可选)'),
|
|
109
|
+
|
|
110
|
+
// ========== 错误相关字段(当 type = error 时使用)==========
|
|
111
|
+
error: z
|
|
112
|
+
.enum(['NON_DRAWING_REQUEST', 'INVALID_REQUEST', 'UNKNOWN_ERROR'])
|
|
113
|
+
.optional()
|
|
114
|
+
.describe('错误类型(仅当 type=error 时需要)'),
|
|
115
|
+
message: z.string().optional().describe('错误提示消息(仅当 type=error 时需要)'),
|
|
116
|
+
});
|
|
117
|
+
})();
|
|
118
|
+
|
|
119
|
+
// ==================== 懒加载函数 ====================
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 获取系统提示词(懒加载+缓存)
|
|
123
|
+
*/
|
|
124
|
+
async function getSystemPrompt() {
|
|
125
|
+
if (!cachedSystemPrompt) {
|
|
126
|
+
const promptPath = path.resolve(__dirname, './prompt.md');
|
|
127
|
+
cachedSystemPrompt = await readFile(promptPath);
|
|
128
|
+
}
|
|
129
|
+
return cachedSystemPrompt;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* 获取编译后的 JSON Schema(懒加载+缓存)
|
|
134
|
+
*/
|
|
135
|
+
function getCompiledSchema() {
|
|
136
|
+
if (!cachedCompiledSchema) {
|
|
137
|
+
const fullSchema = zodToJsonSchema(jsonSchema, {
|
|
138
|
+
name: 'AIResponse',
|
|
139
|
+
$refStrategy: 'none',
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// 关键修复:Gemini 不支持顶层 $ref,直接使用 definitions 中的定义
|
|
143
|
+
if (fullSchema.definitions && fullSchema.definitions.AIResponse) {
|
|
144
|
+
cachedCompiledSchema = fullSchema.definitions.AIResponse;
|
|
145
|
+
} else if (fullSchema.$ref) {
|
|
146
|
+
// 如果顶层是 $ref,解析它
|
|
147
|
+
const refName = fullSchema.$ref.split('/').pop();
|
|
148
|
+
cachedCompiledSchema = fullSchema.definitions[refName];
|
|
149
|
+
} else {
|
|
150
|
+
cachedCompiledSchema = fullSchema;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return cachedCompiledSchema;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ==================== 主要功能 ====================
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* chatWithStreaming
|
|
160
|
+
* @param {*} userPrompts
|
|
161
|
+
* @param {*} callbackOptions
|
|
162
|
+
*/
|
|
163
|
+
exports.chatWithStreaming = async (userPrompts, callbackOptions) => {
|
|
164
|
+
// 懒加载:第一次使用时加载并缓存
|
|
165
|
+
const systemPrompt = await getSystemPrompt();
|
|
166
|
+
const compiledSchema = getCompiledSchema();
|
|
167
|
+
console.log('compiledSchema');
|
|
168
|
+
console.log(JSON.stringify(compiledSchema));
|
|
169
|
+
|
|
170
|
+
// chat options
|
|
171
|
+
const chatOptions = {
|
|
172
|
+
contents: userPrompts,
|
|
173
|
+
systemInstruction: systemPrompt,
|
|
174
|
+
config: {
|
|
175
|
+
responseMimeType: 'application/json',
|
|
176
|
+
responseJsonSchema: compiledSchema,
|
|
177
|
+
temperature: 0.2,
|
|
178
|
+
topP: 0.9,
|
|
179
|
+
topK: 40,
|
|
180
|
+
maxOutputTokens: 8192,
|
|
181
|
+
thinkingConfig: {
|
|
182
|
+
thinkingBudget: 0,
|
|
183
|
+
includeThoughts: false,
|
|
184
|
+
},
|
|
185
|
+
safetySettings: [
|
|
186
|
+
{
|
|
187
|
+
category: 'HARM_CATEGORY_HATE_SPEECH',
|
|
188
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
189
|
+
},
|
|
190
|
+
{
|
|
191
|
+
category: 'HARM_CATEGORY_DANGEROUS_CONTENT',
|
|
192
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
category: 'HARM_CATEGORY_SEXUALLY_EXPLICIT',
|
|
196
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
category: 'HARM_CATEGORY_HARASSMENT',
|
|
200
|
+
threshold: 'BLOCK_ONLY_HIGH',
|
|
201
|
+
},
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// Send a chat message with streaming
|
|
207
|
+
await gemini.chatWithStreaming(chatOptions, callbackOptions);
|
|
208
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# 意图识别:判断用户是否要绘制图表
|
|
2
|
+
|
|
3
|
+
分析用户输入,判断是否为绘图请求。
|
|
4
|
+
|
|
5
|
+
## 输出要求
|
|
6
|
+
|
|
7
|
+
以 JSON 格式输出:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"intent": "DRAW" 或 "REJECT",
|
|
12
|
+
"reason": "判断理由(一句话说明)"
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## 判断标准
|
|
17
|
+
|
|
18
|
+
### DRAW(绘图请求)
|
|
19
|
+
|
|
20
|
+
- 包含动作词:画、绘制、设计、生成、创建、制作
|
|
21
|
+
- 包含图表词:图、流程图、架构图、思维导图、关系图、结构图、示意图
|
|
22
|
+
- 描述可视化需求:「用户登录流程」「微服务架构」「数据库关系」
|
|
23
|
+
- 描述图形元素:「三个矩形」「圆形连接」
|
|
24
|
+
|
|
25
|
+
### REJECT(非绘图请求)
|
|
26
|
+
|
|
27
|
+
- 问候寒暄:你好、早上好、谢谢
|
|
28
|
+
- 知识问答:什么是、如何、为什么、介绍、解释
|
|
29
|
+
- 代码任务:写代码、debug、实现功能
|
|
30
|
+
- 文字任务:翻译、写文章、总结
|
|
31
|
+
- 其他任务:计算、推荐、建议
|
|
32
|
+
|
|
33
|
+
## 示例
|
|
34
|
+
|
|
35
|
+
输入:画一个用户登录流程图
|
|
36
|
+
输出:
|
|
37
|
+
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"intent": "DRAW",
|
|
41
|
+
"reason": "包含动作词'画'和图表词'流程图'"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
输入:设计微服务架构
|
|
46
|
+
输出:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"intent": "DRAW",
|
|
51
|
+
"reason": "包含动作词'设计'和可视化需求'架构'"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
输入:三个矩形连接起来
|
|
56
|
+
输出:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"intent": "DRAW",
|
|
61
|
+
"reason": "描述了具体的图形元素和连接关系"
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
输入:简单介绍一下上海市
|
|
66
|
+
输出:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"intent": "REJECT",
|
|
71
|
+
"reason": "这是知识问答请求,不是绘图需求"
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
输入:什么是微服务架构?
|
|
76
|
+
输出:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"intent": "REJECT",
|
|
81
|
+
"reason": "这是知识问答,用户在询问概念而非要求绘图"
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
输入:你好,今天天气怎么样
|
|
86
|
+
输出:
|
|
87
|
+
|
|
88
|
+
```json
|
|
89
|
+
{
|
|
90
|
+
"intent": "REJECT",
|
|
91
|
+
"reason": "这是闲聊问候,与绘图无关"
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
输入:帮我写一段 Python 代码
|
|
96
|
+
输出:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"intent": "REJECT",
|
|
101
|
+
"reason": "这是代码编写任务,不是绘图请求"
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## 用户输入
|
|
106
|
+
|
|
107
|
+
{USER_INPUT}
|