在 LangChain 的生态中,当我们需要让大模型完成查天气、查数据库、调用外部接口这类任务时,核心逻辑其实高度统一:让模型决策 “是否使用工具、使用哪款工具、传入什么参数”,但绝不是模型直接执行工具操作。
围绕这一核心,LangChain 提供了两种实现路径 —— 一种是新手易上手的Agent,另一种是更适配生产环境的Tool Calling。二者看似效果相近、底层原理同源,但在工程可控性、可扩展性与生产可用性上,存在天壤之别。
简单来说,initialize_agent是帮你封装好全流程的高层工具,而bind_tools + 手动调度则是将执行权完全交还给开发者的底层实现。在实际生产落地中,后者往往是更优的选择。
无论是 OpenAI 的 Function Calling、通义千问的 Tool Calling,还是 Claude、Gemini 的工具调用机制,本质都是一套 “决策 - 执行 - 反馈” 的流程:
以下是一个简单的执行流程
python用户问题 ↓ LLM 判断是否需要工具 ↓ 返回 tool_call(函数名 + 参数) ↓ 你在代码里真正调用工具 ↓ 把工具结果再喂回 LLM ↓ LLM 给最终自然语言回答
下面两段代码,做的是同一件事,但开发者对流程的控制权截然不同。
pythonfrom langchain.agents import initialize_agent
from langchain.agents.agent_types import AgentType
from langchain_community.chat_models import ChatOpenAI
from langchain_core.tools import tool
@tool
def get_weather(city: str) -> str:
"""获取指定城市天气"""
return f"{city} 晴,26℃"
llm = ChatOpenAI(model="gpt-4", temperature=0)
# 初始化Agent并绑定工具
agent = initialize_agent(
tools=[get_weather],
llm=llm,
agent=AgentType.OPENAI_FUNCTIONS,
verbose=True
)
response = agent.run("今天上海天气怎么样?")
print(response)
使用initialize_agent时,我们只需要定义工具和模型,剩下的工作全由 Agent 自动完成:
整个流程可以简化为:
python用户问题 → agent.run() → 最终答案
适合场景
不适合场景
相较于 Agent 的 “黑盒模式”,bind_tools + 手动调度的组合,将工具调用的每一步控制权都交还给开发者,更适合复杂的生产环境。
pythonfrom langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_community.chat_models import ChatTongyi
from pydantic import SecretStr
# 初始化大模型
llm = ChatTongyi(
model="qwen-max",
api_key=SecretStr("sk-xxxx")
)
# 定义工具函数
@tool(response_format="content_and_artifact")
def weather(city: str):
"""查询指定城市的天气情况"""
return "30℃", {"city": city}
这里的关键亮点是response_format="content_and_artifact":它能让工具的返回值被明确拆分为两个部分
这种分离设计在工程实践中至关重要,既能满足大模型理解需求,又能为后续的数据存储、业务联动提供结构化支撑。
这一步的作用是告诉大模型可以调用哪些工具,但不会触发任何工具的实际执行:
pythontools = [weather]
# 为模型绑定工具集
llm_with_tools = llm.bind_tools(tools)
这一步的核心目标,是让模型判断是否需要调用工具,并输出具体的调用指令:
python# 构造用户提问的消息体
messages = [HumanMessage(content='烟台天气情况')]
# 调用模型获取决策
response = llm_with_tools.invoke(messages)
# 将模型的决策消息加入上下文
messages.append(response)
此时模型返回的不是最终答案,而是工具调用的决策指令,格式如下:
pythonAIMessage(
tool_calls=[{
name: "weather",
args: {"city": "烟台"}
}]
)
这是整个流程中最能体现可控性的环节。我们可以在这里嵌入任何自定义逻辑:
python# 构建工具名到工具函数的映射表
tool_map = {tool.name.lower(): tool for tool in tools}
# 遍历模型输出的工具调用指令
for tool_call in response.tool_calls:
selected_tool = tool_map[tool_call["name"].lower()]
# 执行工具函数
tool_msg = selected_tool.invoke(tool_call)
# 将工具执行结果加入上下文
messages.append(tool_msg)
在这个环节,我们可以轻松加入各类工程化逻辑,比如:
将工具执行结果加入上下文后,再次调用模型,即可得到基于工具数据的最终回答:
python# 基于包含工具结果的上下文,生成最终回答
response = llm_with_tools.invoke(messages)
print(response.content)
最终输出结果如下:
python烟台的天气情况是30℃。
| 对比项 | initialize_agent | bind_tools + 手动 |
|---|---|---|
| 抽象层级 | 高 | 低 |
| 控制力 | ❌ 很弱 | ✅ 极强 |
| 可观测性 | ❌ 黑盒 | ✅ 全流程 |
| 可扩展性 | ❌ 受限 | ✅ 自由 |
| 生产适用 | ⚠️ 一般 | ✅ 推荐 |
当你满足以下任一条件时,强烈建议选择 Tool Calling 方案:
本文作者:鑫 · Dev
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!