Vue创建一个简单的Agent聊天——工具调用

实现工具调用

工具调用,就是需要的操作内容(查询浏览器、天气......)封装成一个函数,让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
}

目录

相关推荐
锋行天下1 分钟前
DeepSeek写Langgraph就是个大傻逼,一本正经胡说八道
前端·人工智能·后端
jktantan16 分钟前
每个节点 41 字节:把一棵百万节点树拆进 14 个 typed array
前端·javascript·vue.js·算法·typescript
计算机魔术师39 分钟前
Anthropic官方插件被「寄生」,背后是同一类攻击又来了
前端
我命由我123451 小时前
访客系统形式(有用户概念、无用户概念)
java·开发语言·前端·javascript·python·产品运营·产品经理
key_3_feng1 小时前
评估与对齐:Agent 自我进化的护栏
前端·数据库·agent
LaughingZhu2 小时前
Product Hunt 热榜 2026-10-30:Empacer、Scape、Floot in ChatGPT
java·前端·数据库·人工智能·chatgpt·notion
Lank_M2 小时前
抠图结果缩放后边缘发脏,该怪的不是插值
前端
jerrywus2 小时前
Skill、MCP、Workflow、Rules、Memory 有什么区别?拿几次真实开发任务讲清楚
前端·agent·claude
补码缩写2 小时前
canvas 与 sharp 缩放透明图:默认预乘,非预乘得拆通道
前端
静默回滚2 小时前
抠图 PNG 缩小再放大,边缘又糊又白是两件事
前端