@templmf/temp-solf-lmf 0.0.114 → 0.0.116
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/agent.js +218 -0
- package/ai-service/346/234/252/346/217/220/344/272/244/347/251/272/344/272/213/345/212/241/351/227/256/351/242/230/350/247/243/345/206/263/346/255/245/351/252/244.docx +0 -0
- package/demo.js +119 -0
- package/package.json +1 -1
- package/clickhouse/346/226/260/345/242/236/350/212/202/347/202/271/345/242/236/345/212/240part/346/225/260/351/205/215/347/275/256/344/270/216/345/244/261/350/264/245/345/233/236/351/200/200/346/255/245/351/252/244.docx +0 -0
package/agent.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* agent.js
|
|
3
|
+
* 基于 LangGraph 1.3.4 + @langchain/anthropic 1.4.0 的 GitLab 意图识别 Agent
|
|
4
|
+
*
|
|
5
|
+
* 架构:
|
|
6
|
+
* START → intent_router
|
|
7
|
+
* ├── YES → gitlab_agent ⟷ gitlab_tools (ReAct 循环)
|
|
8
|
+
* └── NO → general_agent → END
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
"use strict";
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
StateGraph,
|
|
15
|
+
MessagesAnnotation,
|
|
16
|
+
Annotation,
|
|
17
|
+
START,
|
|
18
|
+
END,
|
|
19
|
+
MemorySaver,
|
|
20
|
+
} = require("@langchain/langgraph");
|
|
21
|
+
const { ToolNode, toolsCondition } = require("@langchain/langgraph/prebuilt");
|
|
22
|
+
const { ChatAnthropic } = require("@langchain/anthropic");
|
|
23
|
+
const { HumanMessage, SystemMessage } = require("@langchain/core/messages");
|
|
24
|
+
const { GITLAB_TOOLS } = require("./gitlabTools");
|
|
25
|
+
|
|
26
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
27
|
+
// 1. State 定义
|
|
28
|
+
// 在 MessagesAnnotation.spec 基础上加 intent 字段
|
|
29
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
30
|
+
const AgentAnnotation = Annotation.Root({
|
|
31
|
+
...MessagesAnnotation.spec,
|
|
32
|
+
intent: Annotation({
|
|
33
|
+
reducer: (_, newVal) => newVal, // 直接覆盖
|
|
34
|
+
default: () => "",
|
|
35
|
+
}),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
39
|
+
// 2. 模型初始化
|
|
40
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
41
|
+
const MODEL = "claude-sonnet-4-5";
|
|
42
|
+
|
|
43
|
+
/** 意图识别:不绑定 tools,只返回 YES/NO */
|
|
44
|
+
const intentLLM = new ChatAnthropic({
|
|
45
|
+
model: MODEL,
|
|
46
|
+
temperature: 0,
|
|
47
|
+
maxTokens: 16,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/** GitLab Agent:绑定全部 14 个工具 */
|
|
51
|
+
const gitlabLLM = new ChatAnthropic({
|
|
52
|
+
model: MODEL,
|
|
53
|
+
temperature: 0,
|
|
54
|
+
maxTokens: 4096,
|
|
55
|
+
}).bindTools(GITLAB_TOOLS);
|
|
56
|
+
|
|
57
|
+
/** 通用对话:不携带任何工具 */
|
|
58
|
+
const generalLLM = new ChatAnthropic({
|
|
59
|
+
model: MODEL,
|
|
60
|
+
temperature: 0.7,
|
|
61
|
+
maxTokens: 2048,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
65
|
+
// 3. System Prompts
|
|
66
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
const INTENT_SYSTEM = `你是一个意图分类器。
|
|
68
|
+
判断用户消息是否与 GitLab 操作相关(项目、Issue、MR、流水线、分支、仓库文件、成员等)。
|
|
69
|
+
只回复 YES 或 NO,不要解释,不要其他内容。`;
|
|
70
|
+
|
|
71
|
+
const GITLAB_SYSTEM = `你是一名专业的 GitLab 助手,可以通过调用工具来操作 GitLab。
|
|
72
|
+
可用操作包括:项目管理、Issue 管理、Merge Request、CI/CD 流水线、分支与文件查询、成员管理等。
|
|
73
|
+
请根据用户需求选择合适的工具;如需多步操作,请逐步完成并给出清晰说明。
|
|
74
|
+
工具调用结果如有错误,请如实告知用户并提供建议。`;
|
|
75
|
+
|
|
76
|
+
const GENERAL_SYSTEM = `你是一个全能 AI 助手,回答用户各类问题。
|
|
77
|
+
回答应简洁准确、有帮助。如果问题涉及 GitLab 操作,提示用户可以直接让你操作 GitLab。`;
|
|
78
|
+
|
|
79
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
80
|
+
// 4. 节点函数
|
|
81
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/** 意图识别节点 */
|
|
84
|
+
async function intentRouterNode(state) {
|
|
85
|
+
const lastHuman = [...state.messages]
|
|
86
|
+
.reverse()
|
|
87
|
+
.find((m) => m._getType?.() === "human" || m.constructor?.name === "HumanMessage");
|
|
88
|
+
|
|
89
|
+
if (!lastHuman) return { intent: "general" };
|
|
90
|
+
|
|
91
|
+
const response = await intentLLM.invoke([
|
|
92
|
+
new SystemMessage(INTENT_SYSTEM),
|
|
93
|
+
new HumanMessage(String(lastHuman.content)),
|
|
94
|
+
]);
|
|
95
|
+
|
|
96
|
+
const intent = response.content.toUpperCase().includes("YES") ? "gitlab" : "general";
|
|
97
|
+
return { intent };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** GitLab Agent 节点:携带工具推理 */
|
|
101
|
+
async function gitlabAgentNode(state) {
|
|
102
|
+
const messages = [new SystemMessage(GITLAB_SYSTEM), ...state.messages];
|
|
103
|
+
const response = await gitlabLLM.invoke(messages);
|
|
104
|
+
return { messages: [response] };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** 通用对话节点 */
|
|
108
|
+
async function generalAgentNode(state) {
|
|
109
|
+
const messages = [new SystemMessage(GENERAL_SYSTEM), ...state.messages];
|
|
110
|
+
const response = await generalLLM.invoke(messages);
|
|
111
|
+
return { messages: [response] };
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
// 5. 条件路由
|
|
116
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
117
|
+
|
|
118
|
+
function routeByIntent(state) {
|
|
119
|
+
return state.intent === "gitlab" ? "gitlab_agent" : "general_agent";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
123
|
+
// 6. 构建 Graph
|
|
124
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
function buildGraph() {
|
|
127
|
+
const graph = new StateGraph(AgentAnnotation);
|
|
128
|
+
|
|
129
|
+
// 节点注册
|
|
130
|
+
graph.addNode("intent_router", intentRouterNode);
|
|
131
|
+
graph.addNode("gitlab_agent", gitlabAgentNode);
|
|
132
|
+
graph.addNode("general_agent", generalAgentNode);
|
|
133
|
+
graph.addNode("gitlab_tools", new ToolNode(GITLAB_TOOLS));
|
|
134
|
+
|
|
135
|
+
// 边:入口
|
|
136
|
+
graph.addEdge(START, "intent_router");
|
|
137
|
+
|
|
138
|
+
// 边:意图路由
|
|
139
|
+
graph.addConditionalEdges("intent_router", routeByIntent, {
|
|
140
|
+
gitlab_agent: "gitlab_agent",
|
|
141
|
+
general_agent: "general_agent",
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// 边:GitLab Agent → tool_calls? YES=tools, NO=END (ReAct 循环)
|
|
145
|
+
graph.addConditionalEdges("gitlab_agent", toolsCondition, {
|
|
146
|
+
tools: "gitlab_tools",
|
|
147
|
+
__end__: END,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
// 边:工具执行完成 → 回到 GitLab Agent
|
|
151
|
+
graph.addEdge("gitlab_tools", "gitlab_agent");
|
|
152
|
+
|
|
153
|
+
// 边:通用对话直接结束
|
|
154
|
+
graph.addEdge("general_agent", END);
|
|
155
|
+
|
|
156
|
+
return graph.compile({
|
|
157
|
+
checkpointer: new MemorySaver(), // 多轮对话内存持久化
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const compiledGraph = buildGraph();
|
|
162
|
+
|
|
163
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
164
|
+
// 7. 便捷调用接口
|
|
165
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* 发送消息,返回 Agent 最终回复文本
|
|
169
|
+
* @param {string} userMessage
|
|
170
|
+
* @param {string} threadId - 相同 ID = 同一多轮对话
|
|
171
|
+
* @returns {Promise<string>}
|
|
172
|
+
*/
|
|
173
|
+
async function chat(userMessage, threadId = "default") {
|
|
174
|
+
const config = { configurable: { thread_id: threadId } };
|
|
175
|
+
const input = { messages: [new HumanMessage(userMessage)] };
|
|
176
|
+
|
|
177
|
+
const result = await compiledGraph.invoke(input, config);
|
|
178
|
+
const last = result.messages[result.messages.length - 1];
|
|
179
|
+
return typeof last.content === "string" ? last.content : JSON.stringify(last.content);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* 发送消息,返回完整执行细节(意图 + 工具调用链 + 最终回复)
|
|
184
|
+
* @param {string} userMessage
|
|
185
|
+
* @param {string} threadId
|
|
186
|
+
* @returns {Promise<{intent: string, toolCalls: Array, finalAnswer: string}>}
|
|
187
|
+
*/
|
|
188
|
+
async function chatWithDetails(userMessage, threadId = "default") {
|
|
189
|
+
const config = { configurable: { thread_id: threadId } };
|
|
190
|
+
const input = { messages: [new HumanMessage(userMessage)] };
|
|
191
|
+
|
|
192
|
+
const result = await compiledGraph.invoke(input, config);
|
|
193
|
+
|
|
194
|
+
const toolCalls = [];
|
|
195
|
+
let finalAnswer = "";
|
|
196
|
+
|
|
197
|
+
for (const msg of result.messages) {
|
|
198
|
+
if (msg.constructor?.name === "AIMessage" || msg._getType?.() === "ai") {
|
|
199
|
+
if (msg.tool_calls?.length) {
|
|
200
|
+
for (const tc of msg.tool_calls) {
|
|
201
|
+
toolCalls.push({ tool: tc.name, args: tc.args });
|
|
202
|
+
}
|
|
203
|
+
} else if (msg.content) {
|
|
204
|
+
finalAnswer = typeof msg.content === "string"
|
|
205
|
+
? msg.content
|
|
206
|
+
: JSON.stringify(msg.content);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
intent: result.intent ?? "unknown",
|
|
213
|
+
toolCalls,
|
|
214
|
+
finalAnswer,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
module.exports = { chat, chatWithDetails, compiledGraph };
|
package/demo.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* demo.js
|
|
3
|
+
* GitLab Intent Agent 演示脚本
|
|
4
|
+
*
|
|
5
|
+
* 运行前设置环境变量:
|
|
6
|
+
* export ANTHROPIC_API_KEY=sk-ant-...
|
|
7
|
+
*
|
|
8
|
+
* 运行:
|
|
9
|
+
* node demo.js
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
"use strict";
|
|
13
|
+
|
|
14
|
+
if (!process.env.ANTHROPIC_API_KEY) {
|
|
15
|
+
console.error("⚠️ 请先设置 ANTHROPIC_API_KEY 环境变量");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { chat, chatWithDetails } = require("./src/agent");
|
|
20
|
+
|
|
21
|
+
const SEP = "─".repeat(60);
|
|
22
|
+
|
|
23
|
+
async function demoCase(title, message, threadId = "demo") {
|
|
24
|
+
console.log(`\n${SEP}`);
|
|
25
|
+
console.log(`📌 ${title}`);
|
|
26
|
+
console.log(`👤 用户: ${message}`);
|
|
27
|
+
console.log(SEP);
|
|
28
|
+
|
|
29
|
+
const details = await chatWithDetails(message, threadId);
|
|
30
|
+
|
|
31
|
+
const intentIcon = details.intent === "gitlab" ? "🦊 GitLab" : "💬 通用对话";
|
|
32
|
+
console.log(`🎯 意图识别: ${intentIcon}`);
|
|
33
|
+
|
|
34
|
+
if (details.toolCalls.length > 0) {
|
|
35
|
+
console.log("🔧 工具调用链:");
|
|
36
|
+
details.toolCalls.forEach((tc, i) => {
|
|
37
|
+
const argsStr = Object.entries(tc.args)
|
|
38
|
+
.map(([k, v]) => `${k}=${JSON.stringify(v)}`)
|
|
39
|
+
.join(", ");
|
|
40
|
+
console.log(` ${i + 1}. ${tc.tool}(${argsStr})`);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`\n🤖 Agent 回复:\n${details.finalAnswer}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function runDemos() {
|
|
48
|
+
console.log("=".repeat(60));
|
|
49
|
+
console.log(" GitLab Intent Agent (Node.js) — 演示");
|
|
50
|
+
console.log("=".repeat(60));
|
|
51
|
+
|
|
52
|
+
// 通用问题 → general_agent,不触发任何工具
|
|
53
|
+
await demoCase(
|
|
54
|
+
"通用问题(非 GitLab)",
|
|
55
|
+
"JavaScript 中 Promise 和 async/await 有什么区别?",
|
|
56
|
+
"general-1"
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// GitLab:列出项目
|
|
60
|
+
await demoCase(
|
|
61
|
+
"GitLab:列出我的项目",
|
|
62
|
+
"帮我查看一下我所有的 GitLab 项目",
|
|
63
|
+
"gitlab-1"
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// GitLab:查询 Issue
|
|
67
|
+
await demoCase(
|
|
68
|
+
"GitLab:查询 bug 类 Issue",
|
|
69
|
+
"列出项目 1 里所有打开的 bug 类型 Issue",
|
|
70
|
+
"gitlab-2"
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// GitLab:创建 Issue
|
|
74
|
+
await demoCase(
|
|
75
|
+
"GitLab:创建 Issue",
|
|
76
|
+
"在项目 2 里创建一个标题为"修复首页加载慢"、标签为 bug 的 Issue,指派给 alice",
|
|
77
|
+
"gitlab-3"
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// GitLab:查询失败流水线 + 重试
|
|
81
|
+
await demoCase(
|
|
82
|
+
"GitLab:查询失败的流水线",
|
|
83
|
+
"查看项目 1 所有失败的 CI 流水线,并告诉我能不能重试",
|
|
84
|
+
"gitlab-4"
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// GitLab:列出 MR
|
|
88
|
+
await demoCase(
|
|
89
|
+
"GitLab:列出合并请求",
|
|
90
|
+
"项目 1 现在有哪些打开的 MR?",
|
|
91
|
+
"gitlab-5"
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
// ── 多轮对话演示(同一 thread_id)──
|
|
95
|
+
console.log(`\n${SEP}`);
|
|
96
|
+
console.log("📌 多轮对话演示(同一 thread)");
|
|
97
|
+
console.log(SEP);
|
|
98
|
+
|
|
99
|
+
const tid = "multi-turn";
|
|
100
|
+
|
|
101
|
+
const q1 = "帮我列出项目 1 的所有分支";
|
|
102
|
+
console.log(`👤 第1轮: ${q1}`);
|
|
103
|
+
const r1 = await chat(q1, tid);
|
|
104
|
+
console.log(`🤖 回复: ${r1.slice(0, 200)}\n`);
|
|
105
|
+
|
|
106
|
+
const q2 = "刚才你说的那些分支里,有没有已经合并了的?";
|
|
107
|
+
console.log(`👤 第2轮: ${q2}`);
|
|
108
|
+
const r2 = await chat(q2, tid);
|
|
109
|
+
console.log(`🤖 回复: ${r2.slice(0, 200)}`);
|
|
110
|
+
|
|
111
|
+
console.log(`\n${SEP}`);
|
|
112
|
+
console.log("✅ 演示完成");
|
|
113
|
+
console.log(SEP);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
runDemos().catch((err) => {
|
|
117
|
+
console.error("❌ 运行出错:", err.message);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
});
|
package/package.json
CHANGED