LangChain 实战:工具调用与结构化输出
工具调用(Tool Calling)和结构化输出是 LangChain 让 AI 从'聊天'走向'干活'的关键能力。前者允许模型调用外部函数或 API,后者则确保返回数据符合特定格式,便于程序处理。
工具调用(Tool Calling)
当 LLM 无法直接获取实时信息(如天气、数据库内容)时,我们可以借助工具完成查询。
创建工具的三种方式
- 直接用
@tool装饰函数 最简单,适合轻量级小工具。from langchain_core.tools import tool @tool
def add(a: int, b: int) -> int: """计算两个数的和""" return a + b
2. **用 `@tool` + 自定义参数结构(Pydantic)**
参数更清晰,能写详细说明。
```python
from pydantic import BaseModel, Field
class AddInput(BaseModel):
a: int = Field(description="第一个数")
b: int = Field(description="第二个数")
@tool(args_schema=AddInput)
def add(a: int, b: int) -> int:
return a + b
- 继承
BaseTool写类 最灵活,适合复杂逻辑或异步操作。from langchain_core.tools import BaseTool class AddTool(BaseTool): name = "add_tool" description = "用于加法运算的工具" def _run(self, a: int, b: int) -> int: return a + b
本地自定义工具实战
定义好工具后,需要将其绑定到模型。这里以 DeepSeek 模型为例。
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
llm = init_chat_model(
model="deepseek-chat",
model_provider="deepseek",
api_key="your-api-key",
temperature=0.7,
max_tokens=8192,
)
# 绑定工具
bound_llm = llm.bind_tools([add])
调用流程解析:
- 构建用户消息,包含系统提示和具体请求。
- 模型决定是否调用工具。如果决定调用,响应中会包含
tool_calls。 - 执行工具,并将结果包装为
ToolMessage返回给模型。 - 模型根据工具结果生成最终回答。
message = [
SystemMessage(content="你是一个数学助手。当用户给你一个数字 n 时,使用 add 工具计算结果。"),
HumanMessage(content="请计算 5 加 3 的结果。"),
]
ai_message = bound_llm.invoke(message)
message.append(ai_message)
# 如果有工具调用,执行它
if ai_message.tool_calls:
for tool_call in ai_message.tool_calls:
if tool_call["name"] == "add":
result = add.invoke(tool_call["args"])
message.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
final_msg = bound_llm.invoke(message).content
print(final_msg)
注意: ai_message 是经过框架整合的响应,必须使用 ToolMessage 包装执行结果并提供 tool_call_id,否则模型无法关联上下文。
第三方工具集成
集成第三方服务(如 Tavily 搜索)通常需要提供 API Key。
from langchain_tavily import TavilySearch
tavily_tool = TavilySearch(
max_results=4,
tavily_api_key="your-tavily-api-key"
)
bound_llm = llm.bind_tools([tavily_tool])
支持多轮调用,模型可以根据搜索结果自动判断是否需要进一步搜索。
结构化输出(Structured Output)
结构化输出允许模型返回符合特定格式的数据,而不是纯文本。这对于数据存储、API 响应生成等场景非常有用。
Pydantic BaseModel(推荐)
这是最常用且类型安全的方式。
from pydantic import BaseModel, Field
class WeatherInfo(BaseModel):
city: str = Field(description="城市名称")
weather: str = Field(description="天气状况")
temperature: str = Field(description="温度范围")
model = llm.with_structured_output(WeatherInfo)
result = model.invoke("上海现在的天气如何?")
print(f"城市:{result.city}")
优点: 类型安全,IDE 有完整提示,支持字段验证。
TypedDict
轻量级方案,返回纯字典。
from typing import TypedDict, Annotated
class WeatherDict(TypedDict):
city: Annotated[str, "城市名称"]
weather: Annotated[str, "天气状况"]
model = llm.with_structured_output(WeatherDict)
result = model.invoke("北京天气")
print(result["city"])
缺点: 类型检查较弱,不支持 Field() 的高级功能。
JSON Schema
标准化格式,跨语言通用,适合复杂嵌套结构。
import json
json_schema = {
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名称"},
"weather": {"type": "string", "description": "天气状况"}
},
"required": ["city", "weather"]
}
model = llm.with_structured_output(json_schema)
result = model.invoke("广州天气")
print(json.dumps(result, ensure_ascii=False, indent=2))
可选结构化输出(动态类型选择)
当需要根据输入自动选择返回不同类型的数据时,可以使用 Literal 类型配合嵌套模型。
from typing import Literal
class PersonInfo(BaseModel):
name: str
age: int
class CityInfo(BaseModel):
city: str
population: int
class Response(BaseModel):
type: Literal["person", "city"] = Field(description="响应类型")
person: PersonInfo | None = None
city: CityInfo | None = None
model = llm.with_structured_output(Response)
result = model.invoke("介绍一下张三")
if result.type == "person":
print(f"姓名:{result.person.name}")
三大实际应用场景
1. 信息提取器
将非结构化文本转化为结构化数据。例如从简历中提取姓名、教育经历和工作经历。
from pydantic import BaseModel, Field
class Education(BaseModel):
school: str
degree: str
class ResumeInfo(BaseModel):
name: str
education: list[Education]
model = llm.with_structured_output(ResumeInfo)
resume_text = "...简历内容..."
result = model.invoke(f"提取信息:\n{resume_text}")
2. 提示词增强
帮助 AI 更好理解用户意图。将模糊需求转换为明确的结构化指令。
class SearchIntent(BaseModel):
intent_type: Literal["informational", "transactional"]
keywords: list[str]
intent = model.invoke("我想买个性价比高的笔记本")
# 根据 intent.intent_type 执行不同策略
3. 与 Tool 联合使用
结构化输出 + 工具调用的完美组合。AI 先分析需求并输出结构化决策,然后调用相应工具执行。
# 步骤 1:绑定工具
bound_llm = llm.bind_tools([tavily_tool])
# 步骤 2:绑定结构化输出
structured_llm = bound_llm.with_structured_output(WeatherResult)
# 步骤 3:直接调用
result = structured_llm.invoke("北京天气")
总结
| 方式 | 优点 | 适用场景 |
|---|---|---|
| Pydantic | 类型安全、验证强 | 复杂数据结构 |
| TypedDict | 轻量、纯字典 | 简单键值对 |
| JSON Schema | 标准化、跨语言 | 跨系统对接 |
| 可选输出 | 灵活性高 | 多类型动态响应 |
选择合适的输出方式取决于你的业务需求。如果是内部 Python 项目,Pydantic 通常是首选;如果需要与其他语言交互,JSON Schema 更稳妥。


