AI产品狙击手

专注于大语言模型LLM,文生图模型Stable Diffusion, 视频生成模型等免费AI工具的分享和应用,助你掌握最前沿的AI技术

0%

手把手教你写AI智能体!Function Calling版

最近想花点时间重新熟悉一下Agent构建框架LangChain,其实如果大家记得的话,我两年多前ChatGPT和LangChain刚出来的时候我就分享过不少LangChain的编程知识。但时隔多年,很多功能都已经改变了。现在他们主要的功能编程AI智能体Agent的开发了。如果有长期关注我的,应该知道,我们不用LangChain,直接通过大语言模型的function calling也能做Agent啊,为什么非要用它呢?其实我们还真不是非要用。我们知道框架和库的作用其实就是帮我们做好了封装,让我们用起来更容易而已。今天,我们先试下直接用nodejs来写一个简单的取天气的智能体,让大家先了解下智能体开发的基础流程,然后再下一个视频再用LangChain来做同样事情,大家就会很清楚LangChain带给我们的好处了。我这里用的是DeepSeek的API来实现的。大家先看下…

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
/**
* 纯 Node.js Agent 示例 (不依赖 LangChain)
*
* 此文件演示了 Agent 的核心逻辑,并使用 DeepSeek API 的真实结构进行调用。
* 目标:通过多轮 API 调用,让 LLM (DeepSeek) 调用本地工具 getWeather 来回答用户问题。
*
* 注意:要实际运行此文件,您需要:
* 1. 在代码中替换 YOUR_DEEPSEEK_API_KEY 为您的密钥。
* 2. 确保运行环境支持 Node.js 的 fetch 或安装 node-fetch 库。
*/

// ----------------------------------------------------
// 1. 工具函数定义 (Execution Logic) - 本地可执行的代码
// ----------------------------------------------------

/**
* 实际执行获取天气数据的函数。
* 这是一个模拟函数,返回固定格式的字符串。
* @param {string} cityName - 要查询天气的城市名称。
* @returns {string} 模拟的天气结果。
*/
function getWeather(cityName) {
// f-string 的 JavaScript 版本是模板字符串 `...${variable}...`
return `天气总是晴朗的,位于 ${cityName}!`;
}

// ----------------------------------------------------
// 2. 工具的 JSON Schema 定义 (供 LLM 理解)
// ----------------------------------------------------

// LLM 需要的标准 JSON Schema 格式。这段定义告知 LLM:
// - 有一个名为 "get_weather" 的函数
// - 它需要一个名为 "cityName" 的字符串参数
const toolSchema = {
type: "function", // 必须指定类型为 function
function: {
name: "get_weather", // 工具名称,必须与 availableTools 映射的键一致
description: "查询指定城市的天气情况。", // 描述,LLM 依赖此信息决定是否调用
parameters: {
type: "object",
properties: {
cityName: {
type: "string",
description: "要查询天气的城市名称",
},
},
required: ["cityName"], // 必须包含 cityName 参数
},
},
};

// ----------------------------------------------------
// 3. DeepSeek API 调用函数 (实际的 API Wrapper)
// ----------------------------------------------------

/**
* 调用 DeepSeek API 进行 Chat Completions。
* 负责与远程 LLM 进行通信。
* @param {Array<Object>} messages - 当前的对话历史。
* @param {Array<Object>} tools - 传入的工具 Schema 列表。
* @returns {Promise<Object>} API 响应的 message 对象,包含 content 或 tool_calls。
*/
async function callDeepSeekApi(messages, tools) {
// 替换为您实际的 DeepSeek API 密钥
const apiKey = "xxxx";
// DeepSeek 兼容 OpenAI API 的 endpoint
const apiUrl = "https://api.deepseek.com/v1/chat/completions";
const model = "deepseek-chat"; // 模型选择


const payload = {
model: model,
messages: messages, // 传入整个对话历史
// 只有当 LLM 需要工具时才将 tools 数组发送
tools: tools.length > 0 ? tools : undefined,
temperature: 0.0, // 温度设为 0 以确保推理稳定
};

try {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`,
},
body: JSON.stringify(payload),
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`API 调用失败: ${response.status} - ${errorText}`);
}

const jsonResponse = await response.json();

// 返回 LLM 最终的 message 对象 (其中包含 content 或 tool_calls)
return jsonResponse.choices[0].message;

} catch (error) {
console.error("DeepSeek API 调用出错:", error.message);
throw error;
}
}

// ----------------------------------------------------
// 4. Agent 运行循环 (复制 LangChain 的核心逻辑 - 状态机)
// ----------------------------------------------------

async function runAgent(userQuery) {
// 1. 初始化对话历史,包含系统提示词和用户查询
let messages = [
{ role: "system", content: "你是一个乐于助人的助手,擅长使用工具来获取信息并用中文回复。" },
{ role: "user", content: userQuery },
];

// 2. 定义可用的工具映射 (将工具名映射到实际的函数)
const availableTools = {
get_weather: getWeather,
};

// Agent 循环开始 (限制步数,防止无限循环)
let maxSteps = 3;
let step = 0;
while (step < maxSteps) {
step++;
console.log(`\n--- Agent 步骤 ${step} ---`);

let apiResponse;
try {
// Step A: 调用 LLM,传入当前所有消息和可用工具
apiResponse = await callDeepSeekApi(messages, [toolSchema]);
} catch (e) {
console.error("Agent 运行中断。", e.message);
break;
}

// Step B: 检查 LLM 的回复:是调用工具还是最终回复?
if (apiResponse.tool_calls && apiResponse.tool_calls.length > 0) {
// 路径 1: LLM 决定调用工具 (Tool Call)

// 记录 LLM 的决策消息到历史中
messages.push(apiResponse);

const toolCall = apiResponse.tool_calls[0];
const toolName = toolCall.function.name;

try {
// LLM 返回的 arguments 是一个 JSON 字符串,需要解析成 JavaScript 对象
const args = JSON.parse(toolCall.function.arguments);
const toolFunc = availableTools[toolName];

if (toolFunc) {
console.log(`LLM 请求工具: ${toolName}, 参数: ${JSON.stringify(args)}`);

// Step C: 关键步骤:执行本地函数
const toolResult = toolFunc(args.cityName);

console.log(`工具执行结果: ${toolResult}`);

// Step D: 将工具结果包装成 "tool" 消息,添加到历史中,准备下次调用 LLM
messages.push({
role: "tool", // 角色必须是 "tool"
tool_call_id: toolCall.id, // 必须携带 LLM 返回的 tool call ID
name: toolName,
content: toolResult,
});

} else {
console.error(`错误:找不到工具 ${toolName}`);
break;
}
} catch (e) {
console.error(`解析参数或工具执行出错: ${e.message}`);
break;
}

} else {
// 路径 2: LLM 给出最终回复 (Final Answer)
messages.push(apiResponse);
console.log("\n--- Agent 最终回复 ---");
console.log(apiResponse.content);
break; // 结束 Agent 循环
}
}
}

// ----------------------------------------------------
// 5. 运行程序
// ----------------------------------------------------
runAgent("东京的天气怎么样?");