实现工具调用
工具调用,就是需要的操作内容(查询浏览器、天气......)封装成一个函数,让ai自行判断并调用。 先让ai调用工具,再将工具返回的数据喂给ai再调用对话
实现工具函数
ts
import { tool } from "@langchain/core/tools";
const GAO_DE_KEY = import.meta.env.VITE_AMAP_KEY;
// 获取ip信息
export const ip_tool = async () => {
const data = {
key: GAO_DE_KEY,
}
const params = new URLSearchParams(data);
const url = `https://restapi.amap.com/v3/ip?${params}`;
const response = await fetch(url);
const ip_data = await response.json();
return ip_data;
}
// 获取天气信息
export const weather_tool = async () => {
const ipInfo = await ip_tool();
const data = {
key: GAO_DE_KEY,
city: ipInfo.city,
extensions: "all",
output: "json"
};
const params = new URLSearchParams(data);
const url = `https://restapi.amap.com/v3/weather/weatherInfo?${params}`;
const response = await fetch(url);
const weather_data = await response.json();
return weather_data
}
// 获取当前时间
export const date_tool = () => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0');
const day = String(now.getDate()).padStart(2, '0');
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
const weatherTool = tool(weather_tool, {
name: "weather",
description: "获取当前天气信息,不需要输入参数"
})
const ipTool = tool(ip_tool, {
name: "ip",
description: "获取当前IP地址"
})
const dateTool = tool(date_tool, {
name: "date",
description: "获取当前日期和时间"
})
export const tools = [weatherTool, ipTool, dateTool]
修改Agent.ts
ts
import type { Chat } from "@/types/chat";
import { AIMessage, HumanMessage } from "@langchain/core/messages";
import { ChatPromptTemplate } from "@langchain/core/prompts";
import { RunnableSequence } from "@langchain/core/runnables";
import { ChatOpenAI } from "@langchain/openai";
import { tools } from "./tools";
function createLLM() {
// 创建LLM并绑定工具
const llm = new ChatOpenAI({
model: import.meta.env.VITE_AI_MODEL,
apiKey: import.meta.env.VITE_API_KEY,
configuration: {
baseURL: import.meta.env.VITE_AI_BASE_URL,
},
temperature: 0.7, // ai生成的内容丰富度
streaming: true,
})
return llm.bindTools(tools) // 绑定工具,让AI知道有哪些工具可用
}
......
export default async function chat(
input: string,
onChat: (text: string) => void,
messages: Chat[] = []) {
// 历史记录
const allMessages: Array<HumanMessage | AIMessage> = []
// 遍历历史聊天记录
for (const msg of messages) {
if (msg.role === "user") {
allMessages.push(new HumanMessage(msg.messages))
} else if (msg.role === "assistant") {
allMessages.push(new AIMessage(msg.messages))
}
}
// 添加当前用户信息
allMessages.push(new HumanMessage(input))
const chain = createChain()
// 第一次调用:AI决定是否使用工具
const res = await chain.stream({
chat_history: allMessages,
input: input,
})
let resText = ""
let hasToolCalls = false
const toolCalls: Array<{ name: string; args: Record<string, unknown> }> = []
// 处理流式响应
for await (const chunk of res) {
// 检查是需要工具调用
if (chunk.tool_calls && chunk.tool_calls.length > 0) {
hasToolCalls = true
toolCalls.push(...chunk.tool_calls.map(tc => ({
name: tc.name,
args: tc.args
})))
} else {
const content = String(chunk.content) ?? ""
resText += content
if (onChat) {
onChat(String(chunk.content) || "")
}
}
}
// 如果有工具调用,执行工具并继续对话
if (hasToolCalls && toolCalls.length > 0) {
// 执行工具调用
const toolResults: string[] = [] // 存储工具调用结果
for (const tc of toolCalls) {
const tool = tools.find(t => t.name === tc.name) // 找到工具
if (tool) {
try {
// 调用工具
const result = await tool.invoke(tc.args)
toolResults.push(`工具 ${tc.name} 执行结果: ${JSON.stringify(result)}`)
} catch (error) {
toolResults.push(`工具 ${tc.name} 执行失败: ${error}`)
}
}
}
// 将工具结果添加到消息历史(并非页面显示的内容,这里是已经传递来的参数)
allMessages.push(new AIMessage(resText))
allMessages.push(new HumanMessage(`工具执行结果:\n${toolResults.join('\n')}`))
// 第二次调用:AI根据工具结果生成最终回复
const finalRes = await chain.stream({
chat_history: allMessages,
input: "请根据工具执行结果回答用户的问题",
})
for await (const chunk of finalRes) {
const content = String(chunk.content) ?? ""
resText += content
if (onChat) {
onChat(String(chunk.content) || "")
}
}
}
return resText
}