🎯 课程主题
通过两个完整案例演示工具定义与模型调用的全流程:案例一使用 args_schema(Pydantic BaseModel)定义工具参数,案例二使用 docstring(parse_docstring=True)定义工具参数。
📝 核心知识点
1. 案例一:args_schema 方式定义工具
- 概念说明:使用 Pydantic
BaseModel定义参数字段(含Field描述和默认值),通过@tool(args_schema=...)绑定。 - 关键细节:
- 装饰器中可设置
name(工具名)和description(工具描述)。 - 参数的类型、描述、默认值均以 Pydantic 模型为准。
- 装饰器中可设置
2. 案例二:parse_docstring 方式定义工具
- 概念说明:在
@tool装饰器中设置parse_docstring=True,然后用 Google 风格 docstring(:param/:return:)描述参数。 - 关键细节:
- docstring 必须严格遵循 Google 风格:函数描述后有一个空行,然后是
Args:块,每个参数格式为param_name: 描述,可选(默认值)。 - 不遵循该格式会导致报错(强校验)。
- 此时不再需要单独的 Pydantic 类和
args_schema参数。
- docstring 必须严格遵循 Google 风格:函数描述后有一个空行,然后是
3. 工具调用完整流程(通用)
- 概念说明:手动编排工具调用流程,模拟 Agent 的核心逻辑。
- 关键步骤:
- 绑定工具:
model.bind_tools([tool1, tool2, ...]) - 维护消息列表:使用
HumanMessage初始化消息列表。 - 首次调用模型:
model_with_tools.invoke(messages),得到AIMessage(包含tool_calls)。 - 追加 AIMessage 到消息列表。
- 提取 tool_calls:
response.tool_calls,遍历判断工具名。 - 手动执行工具:
tool.invoke(tool_call)或tool.invoke(tool_call["args"]),得到ToolMessage。 - 追加 ToolMessage 到消息列表。
- 再次调用模型:模型整合工具结果,生成最终回答。
- 追加最终 AIMessage 到消息列表。
- 绑定工具:
- 关键细节:大模型本身不能直接执行工具,只能分析出需要调用哪个工具;真正的工具执行需要开发者手动调用
tool.invoke()(在 Agent 模式下则由 Agent 自动完成)。
🏗️ 架构与工作流
HumanMessage → bind_tools 的模型 → AIMessage(含 tool_calls)
→ 遍历 tool_calls → tool.invoke() → ToolMessage
→ 追加到消息列表 → 再次调用模型 → 最终 AIMessage
💻 代码实战
from langchain_core.messages import HumanMessage, AIMessage
from langchain_core.tools import tool
from pydantic import BaseModel, Field
# ========== 案例一:args_schema 方式 ==========
class WeatherSchema(BaseModel):
city: str = Field(description="具体的城市名称", default="北京")
forecast: bool = Field(description="是否包含明日天气", default=False)
@tool(name="get_weather_and_forecast", description="查询当日天气,可包含明日天气预报", args_schema=WeatherSchema)
def get_weather(city: str, forecast: bool) -> str:
result = f"{city}今天天气不错"
if forecast:
result += ",明天下雨"
return result
# 初始化模型(示例使用 DeepSeek)
from langchain_deepseek import ChatDeepSeek
model = ChatDeepSeek(model="deepseek-chat")
# 绑定工具
model_with_tools = model.bind_tools([get_weather])
# 维护消息列表
messages = [HumanMessage(content="杭州今天天气怎么样,明天的也告诉我")]
# 首次调用
response = model_with_tools.invoke(messages)
messages.append(response)
# 提取并执行工具调用
for tool_call in response.tool_calls:
if tool_call["name"] == "get_weather_and_forecast":
tool_message = get_weather.invoke(tool_call)
messages.append(tool_message)
# 再次调用模型,生成最终回答
final_response = model_with_tools.invoke(messages)
messages.append(final_response)
# 打印消息流转
for msg in messages:
print(msg.pretty_repr())
# ========== 案例二:parse_docstring 方式 ==========
@tool(parse_docstring=True)
def get_weather(
city: str = "北京",
forecast: bool = False,
) -> str:
"""查询指定城市的天气信息。
Args:
city: 城市名称
forecast: 是否包含明天的天气
Returns:
str: 天气描述字符串
"""
result = f"{city}今天天气不错"
if forecast:
result += ",明天下雨"
return result
# 后续绑定和调用流程与案例一完全相同
model_with_tools2 = model.bind_tools([get_weather])
# ... 调用流程同上 ...
⚠️ 常见问题与避坑指南
parse_docstring=True时,docstring 必须严格按 Google 风格编写,Args:和参数之间需换行分布,否则会报错。- 工具名若通过
name参数重命名,后续判断tool_call["name"]时应使用自定义的名称而非函数名。 - 当前代码中首次调用后只判断了一次 tool_calls,实际应使用
while循环不断检查直至无工具调用(参见下一课)。 - 大模型不直接执行工具,需手动调用
tool.invoke();到 Agent 阶段则会自动执行。
💡 个人总结与延伸
两个案例的核心区别在于参数定义方式:args_schema 适合复杂类型约束,parse_docstring 适合简单场景且代码更简洁。完整的消息流转(HumanMessage → AIMessage → ToolMessage → AIMessage)是理解 LangChain Agent 执行机制的基石,务必掌握。