Skip to content

从零到进阶做 Agent(九)Agent 基础:让模型学会用工具

🕒 Published at:

前面的 RAG 让模型「会查资料」,但它本质还是被动的——只会检索加回答。Agent(智能体) 更进一步:让模型自己判断「要不要调用工具、调用哪个、用什么参数」,拿到结果再继续思考,直到能回答为止。这一篇讲 Agent 的核心:工具(Tool)ReAct 循环,以及用官方的 create_agent / createAgent 几行搭出一个会用工具的 Agent。顶部可切换 Python / Node.js。

本系列基于 LangChain 1.x / LangChain.js 1.0。它们都把 create_agent/createAgent 作为构建 Agent 的推荐入口,两种语言几乎对称。

Agent 是怎么「思考」的:ReAct

最经典的 Agent 范式叫 ReAct(Reasoning + Acting,边推理边行动)。它是一个循环:

模型收到问题后先思考「我需要什么信息?」→ 决定调用某个工具(比如查天气)→ 系统执行工具、把结果返回给模型 → 模型看到结果继续思考「够了吗?」→ 不够就再调工具,够了就给出最终回答

你不用手写这个循环——create_agent 内部就是这套逻辑。你只需要两件事:准备好模型准备好工具

用户问题思考 Reason还需要什么信息?行动 Act调用工具最终回答① 需要就调用② 返回结果,继续想③ 信息够了 → 输出ReAct:思考 ↔ 行动 反复循环,直到能回答

工具就是一个带说明的函数

工具本质是个普通函数,但要给它写清楚名字、说明、参数——因为模型是靠这段「说明书」来判断什么时候该用它的。说明写得越清楚,模型用得越准。Python 用 @tool 装饰器,Node 用 tool() 配合 zod 定义参数。

python
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """查询指定城市的当前天气。参数 city 是城市名,比如“杭州”。"""
    # 真实场景这里会调天气 API,这里先写死方便演示
    return f"{city}今天晴,26℃,微风。"

@tool
def calculator(expression: str) -> str:
    """计算一个数学表达式,例如 '15 * 23'。"""
    return str(eval(expression))   # 演示用;生产别直接 eval 用户输入
javascript
import { tool } from "@langchain/core/tools";
import { z } from "zod";

const getWeather = tool(
  async ({ city }) => `${city}今天晴,26℃,微风。`,   // 真实场景调天气 API
  {
    name: "get_weather",
    description: "查询指定城市的当前天气。参数 city 是城市名,比如“杭州”。",
    schema: z.object({ city: z.string() }),
  }
);

const calculator = tool(
  async ({ expression }) => String(eval(expression)),   // 演示用;生产别直接 eval
  {
    name: "calculator",
    description: "计算一个数学表达式,例如 '15 * 23'。",
    schema: z.object({ expression: z.string() }),
  }
);

组装 Agent

把模型和工具交给 create_agent,一个会思考、会调工具的 Agent 就好了。

python
import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

model = ChatOpenAI(
    model="qwen-plus",
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    temperature=0,
)

agent = create_agent(model=model, tools=[get_weather, calculator])

# 一个问题里同时需要「查天气」和「算数」两个工具
result = agent.invoke({
    "messages": [{"role": "user", "content": "杭州天气怎么样?顺便帮我算一下 15 乘 23。"}]
})
print(result["messages"][-1].content)   # 最后一条是 Agent 的最终回答
javascript
import { createAgent } from "langchain";
import { ChatOpenAI } from "@langchain/openai";

const model = new ChatOpenAI({
  model: "qwen-plus",
  apiKey: process.env.DASHSCOPE_API_KEY,
  temperature: 0,
  configuration: { baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1" },
});

const agent = createAgent({ model, tools: [getWeather, calculator] });

// 一个问题里同时需要「查天气」和「算数」两个工具
const result = await agent.invoke({
  messages: [{ role: "user", content: "杭州天气怎么样?顺便帮我算一下 15 乘 23。" }],
});
console.log(result.messages[result.messages.length - 1].content);   // 最后一条是最终回答

运行后,Agent 会自动判断出这句话需要先调 get_weather("杭州")、再调 calculator("15*23"),然后把两个结果综合成一句回答。整个「调用哪些工具、按什么顺序」都是模型自己决定的——这就是 Agent 和普通链的本质区别。

看清 Agent 的「内心戏」:流式观察每一步

invoke 只给最终答案。想看 Agent 中间「想了什么、调了什么工具」,用 stream 逐步观察。这对调试特别有用。

python
for step in agent.stream(
    {"messages": [{"role": "user", "content": "杭州天气怎么样?顺便算一下 15*23。"}]},
    stream_mode="values",
):
    step["messages"][-1].pretty_print()   # 打印每一步的最新消息(含工具调用与结果)
javascript
const stream = await agent.stream(
  { messages: [{ role: "user", content: "杭州天气怎么样?顺便算一下 15*23。" }] },
  { streamMode: "values" }
);
for await (const step of stream) {
  const last = step.messages[step.messages.length - 1];
  // 有 tool_calls 说明模型决定调工具;有 content 说明是思考或最终回答
  console.log(last.tool_calls?.length ? `🔧 调用工具: ${JSON.stringify(last.tool_calls)}` : last.content);
}

你会看到类似这样的过程:模型先发出「调用 get_weather」的指令 → 工具返回天气 → 模型再发出「调用 calculator」 → 返回结果 → 模型给出综合回答。这就是 ReAct 循环在眼前跑起来的样子。

给 Agent 设定角色

和聊天一样,可以给 Agent 一段系统提示词,规定它的身份、语气、行为边界。create_agentsystem_prompt / prompt 参数传入。

python
agent = create_agent(
    model=model,
    tools=[get_weather, calculator],
    system_prompt="你是一个简洁的生活助手,回答控制在两句话以内。",
)
javascript
const agent = createAgent({
  model,
  tools: [getWeather, calculator],
  prompt: "你是一个简洁的生活助手,回答控制在两句话以内。",
});

小结与预告

这一篇你迈出了从「会答」到「会做」的一步:理解了 ReAct 循环、学会了定义工具、并用 create_agent 搭出了一个能自主调用多个工具的 Agent,还能用 stream 观察它的思考过程。

现在的 Agent 还比较「素」。真实项目里,我们希望在它每次调用工具前后插入自己的逻辑(记录日志、做权限校验、监控耗时、按场景切换提示词)。下一篇《Agent 进阶》,就来讲多工具编排和中间件(middleware)——这是让 Agent 真正可控、可上生产的关键机制。