Appearance
函数调用快速提取结构化数据使用技巧
01.结构化输出策略与选择
让LLM返回结构化输出对下游程序至关重要,下游程序通常需要特定格式的参数,目前主流的LLM结构化输出策略有3种:
- Prompt:通过提示词让LLM输出特定结构内容,兼容所有LLM,但输出不稳定;
- 函数/工具调用:为LLM绑定函数并设置强制调用模式,通过函数调用获取结构化输出,稳定性高;
- JSON模式:针对支持JSON输出的LLM,直接设置输出模式为JSON,稳定性高但支持模型少。
其中后两种策略稳定性更优,LangChain为其封装了**.with_structured_output()** 方法,这是获取结构化输出最简单、可靠的方式,底层会自动调用LLM原生的函数调用或JSON模式。
.with_structured_output() 核心特性
- 接收BaseModel子类作为输入,该子类需定义输出属性的名称、描述、类型;
- 返回Runnable可运行对象,输出为与模型对应的Pydantic对象/字典,而非字符串或AI消息;
- 可通过
method参数指定使用函数调用(默认) 或JSON模式; - 可通过LangChain源码/高级功能列表检测LLM是否支持该方法:https://imooc-langchain.shortvar.com/docs/integcations/chat
基础使用示例(函数调用模式)
python
import dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 定义结构化输出的Pydantic模型
class QAExtra(BaseModel):
"""一个问答键值对工具,传递对应的假设性问题+答案"""
question: str = Field(description="假设性问题")
answer: str = Field(description="假设性问题对应的答案")
# 初始化模型并绑定结构化输出
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
structured_llm = llm.with_structured_output(QAExtra)
# 构建Prompt和链
prompt = ChatPromptTemplate.from_messages([
("system", "请从用户传递的query中提取出假设性的问题+答案。"),
("human", "{query}")
])
chain = {"query": RunnablePassthrough()} | prompt | structured_llm
# 调用链
print(chain.invoke("我叫慕小课,我喜欢打篮球,游泳"))输出:question='你喜欢打篮球吗?' answer='是的,我喜欢打篮球。'
JSON模式使用示例
python
import dotenv
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.runnables import RunnablePassthrough
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
class QAExtra(BaseModel):
"""一个问答键值对工具,传递对应的假设性问题+答案"""
question: str = Field(description="假设性问题")
answer: str = Field(description="假设性问题对应的答案")
# 初始化支持JSON模式的模型,指定method="json_mode"
llm = ChatOpenAI(model="gpt-4o")
structured_llm = llm.with_structured_output(QAExtra, method="json_mode")
# Prompt需明确告知JSON输出结构
prompt = ChatPromptTemplate.from_messages([
("system", "请从用户传递的query中提取出假设性的问题+答案,响应格式为JSON,并携带question和answer两个字段。"),
("human", "{query}")
])
chain = {"query": RunnablePassthrough()} | prompt | structured_llm
print(chain.invoke("我叫慕小课,我喜欢打篮球,游泳。"))输出:question='你叫什么名字?你喜欢做什么?' answer='我叫慕小课,我喜欢打篮球,游泳。'
JSON模式注意点
- 仅部分LLM支持JSON模式(如GPT-4o),需查阅模型官方文档确认;
- Prompt中必须明确JSON输出结构,否则会报错;
- 支持JSON模式的LLM较少,且Prompt易受干扰,优先使用函数调用模式更稳定。
关键提示
创建BaseModel子类时,类名、文档字符串、参数名/描述至关重要,因为底层基于函数调用实现,这些信息是LLM绑定函数、生成正确结果的基础。
02..with_structured_output() 源码解析
该方法底层根据method参数选择函数调用或JSON模式,完成模型参数绑定和输出解析,核心代码逻辑如下:
python
def with_structured_output(
self,
schema: Optional[_DictOrPydanticClass] = None,
method: Literal["function_calling", "json_mode"] = "function_calling",
include_raw: bool = False,
**kwargs: Any,
) -> Runnable[LanguageModelInput, _DictOrPydantic]:
if kwargs:
raise ValueError(f"Received unsupported arguments {kwargs}")
is_pydantic_schema = _is_pydantic_class(schema)
# 函数调用模式
if method == "function_calling":
if schema is None:
raise ValueError("schema must be specified when method is 'function_calling'. Received None.")
llm = self.bind_tools([schema], tool_choice="any")
# 选择输出解析器(Pydantic/JSON)
if is_pydantic_schema:
output_parser: OutputParserLike = PydanticToolsParser(tools=[schema], first_tool_only=True)
else:
key_name = convert_to_openai_tool(schema)["function"]["name"]
output_parser = JsonOutputKeyToolsParser(key_name=key_name, first_tool_only=True)
# JSON模式
elif method == "json_mode":
llm = self.bind(response_format={"type": "json_object"})
output_parser = (
PydanticOutputParser(pydantic_object=schema)
if is_pydantic_schema
else JsonOutputParser()
)
else:
raise ValueError(f"unrecognized method argument. Expected one of 'function_calling' or 'json_mode'. Received: '{method}'")
# 包含原始输出的处理逻辑
if include_raw:
parser_assign = RunnablePassthrough.assign(
parsed=itemgetter("raw") | output_parser,
parsing_error=lambda _: None
)
parser_none = RunnablePassthrough.assign(parsed=lambda _: None)
parser_with_fallback = parser_assign.with_fallbacks([parser_none], exception_key="parsing_error")
return RunnableMap(raw=llm) | parser_with_fallback
else:
return llm | output_parser核心设计思路
即使不使用LangChain,也可借鉴该思路实现结构化输出:构建虚假函数→让LLM绑定函数→设置tool_choice="any"强制调用,最大程度保证LLM稳定输出结构化数据。
函数调用出错捕获提升程序健壮性
函数调用过程中易出现LLM参数生成错误、工具内部异常等问题,若不做错误处理,程序会异常脆弱,可通过异常捕获、回退、重试三种策略提升健壮性。
01.try/except捕获工具错误
问题示例:参数缺失引发的校验错误
python
import dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 定义含多参数的复杂工具
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
"""使用复杂工具进行复杂计算操作"""
return int_arg * float_arg
# 模型绑定工具并构建链
llm = ChatOpenAI(model="gpt-3.5-turbo-16k", temperature=0)
llm_with_tools = llm.bind_tools([complex_tool])
chain = llm_with_tools | (lambda msg: msg.tool_calls[0]["args"]) | complex_tool
# 调用链(LLM仅生成前2个参数,缺失dict_arg)
print(chain.invoke("使用复杂工具,对应参数为5和2.1"))报错:pydantic.v1.error_wrappers.ValidationError: 1 validation error for complex_tool schema - dict_arg field required
解决方案:try/except捕获异常并返回提示
python
from langchain_core.runnables import RunnableConfig
def try_except_tool(tool_args: dict, config: RunnableConfig) -> Any:
try:
return complex_tool.invoke(tool_args, config)
except Exception as e:
return f"调用工具时使用以下参数:\n\n{tool_args}\n\n引发了以下错误:\n\n{type(e)}:{e}"
# 重构链
chain = llm_with_tools | (lambda msg: msg.tool_calls[0]["args"]) | try_except_tool
print(chain.invoke("使用复杂工具,对应参数为5和2.1"))输出:
调用工具时使用以下参数:
{'int_arg':5,'float_arg': 2.1}
引发了以下错误:
<class 'pydantic.v1.error_wrappers.Validationerror'>: 1 validation error for complex_toolschema dict_arg field required (type=value_error.missing)最优实践
将错误信息重新返回给LLM,LLM会基于错误信息补全参数并重新调用工具,这是最推荐的错误处理方案,核心是在工具调用层捕获异常,独立返回错误信息。
02.回退与重试处理
利用LangChain的Runnable组件特性,为函数调用添加模型回退和错误重试策略,解决LLM参数生成错误的问题。
策略1:模型回退(低性能模型→高性能模型)
当基础模型生成参数出错时,回退到更强大的模型(如GPT-3.5→GPT-4o)重新生成,示例:
python
import dotenv
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
"""使用复杂工具进行复杂计算操作"""
return int_arg * float_arg
# 基础模型+高性能模型
llm = ChatOpenAI(model="gpt-3.5-turbo-16k").bind_tools([complex_tool])
better_llm = ChatOpenAI(model="gpt-4o").bind_tools([complex_tool], tool_choice="complex_tool")
# 构建基础链和回退链
better_chain = better_llm | (lambda msg: msg.tool_calls[0]["args"]) | complex_tool
base_chain = llm | (lambda msg: msg.tool_calls[0]["args"]) | complex_tool
# 为基础链添加回退策略
chain = base_chain.with_fallbacks([better_chain])
# 调用链
print(chain.invoke("使用复杂工具,对应参数为5和2.1"))输出:10.5(GPT-4o补全dict_arg={},工具正常执行)
策略2:携带错误信息的自我重试
抛出异常时,将错误信息+历史调用记录传递给LLM,让其基于错误自我纠正并重新生成参数,示例:
python
from typing import Any
import dotenv
from langchain_core.messages import ToolCall, AIMessage, ToolMessage, HumanMessage
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnableConfig
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 自定义工具异常
class CustomToolException(Exception):
"""自定义的工具错误异常"""
def __init__(self, tool_call: ToolCall, exception: Exception) -> None:
super().__init__()
self.tool_call = tool_call
self.exception = exception
@tool
def complex_tool(int_arg: int, float_arg: float, dict_arg: dict) -> int:
"""使用复杂工具进行复杂计算操作"""
return int_arg * float_arg
# 捕获异常并抛出自定义异常
def tool_custom_exception(msg: AIMessage, config: RunnableConfig) -> Any:
try:
return complex_tool.invoke(msg.tool_calls[0]["args"], config)
except Exception as e:
raise CustomToolException(msg.tool_calls[0], e)
# 异常信息转消息,传递给LLM
def exception_to_messages(inputs: dict) -> dict:
exception = inputs.pop("exception")
messages = [
AIMessage(content="", tool_calls=[exception.tool_call]),
ToolMessage(tool_call_id=exception.tool_call["id"], content=str(exception.exception)),
HumanMessage(content="最后一次工具调用引发了异常,请尝试使用正确的参数再次调用该工具,不要重复犯错。")
]
inputs["last_output"] = messages
return inputs
# 构建带异常占位符的Prompt
prompt = ChatPromptTemplate.from_messages([
("human", "{query}"),
("placeholder", "{last_output}"),
])
# 初始化模型并构建链
llm = ChatOpenAI(model="gpt-4o", temperature=0).bind_tools(tools=[complex_tool])
chain = prompt | llm | tool_custom_exception
# 添加重试回退策略
self_correcting_chain = chain.with_fallbacks(
[exception_to_messages | chain],
exception_key="exception",
)
# 调用重试链
print(self_correcting_chain.invoke({"query": "使用复杂工具,对应参数为5和2.1"}))输出:10.5
注意点
- 回退策略并非万能,若高性能模型仍生成错误参数,需检查工具描述和Prompt编写是否清晰;
- 重试策略的核心是将错误信息结构化传递给LLM,利用其自然语言理解能力实现自我纠正。
多模态LLM执行函数调用的技巧
多模态LLM(如GPT-4o)支持文本、图像、音频等多类型输入,且原生支持函数调用,使用时只需按正常方式绑定工具,并按多模态模型规则传递输入即可,同时可通过函数调用实现多模态输出(如文生图)。
01.GPT-4o多模态输入+函数调用
GPT-4o支持图片URL作为输入,结合函数调用可实现图片分析+工具调用的复合能力,例如识别图片城市→查询该城市天气。
GPT-4o原生多模态输入示例
python
from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": "what's in this image?"},
{
"type": "image_url",
"image_url": {
"url": "https://upload.wikimedia.org/wikipedia/commons/thumb/d/dd/cfp-wisconsin-madison-the-nature-boardwalk.jpg/2560px-Gfp-wisconsin-madison-the-nature-boardwalk.jpg"
},
},
],
}
],
max_tokens=300,
)
print(response.choices[0])LangChain中多模态Prompt构建
将文本和图片URL封装为列表格式传入Prompt,与普通Prompt使用方式一致:
python
from langchain_core.prompts import ChatPromptTemplate
prompt = ChatPromptTemplate.from_messages([
("human", [
{"type": "text", "text": "{query}"},
{"type": "image_url", "image_url": {"url": "{image_url}"}}
])
])
# 调用Prompt
print(prompt.invoke({
"query": "这张图片所在的地址的天气怎么样",
"image_url": "https://img1.baidu.com/it/u=644490943,1781886584&fm=253&fmt=auto&app=138&f=JPEG"
}).to_messages())实战:图片识别+高德天气查询
python
import json
import os
from typing import Type, Any
import dotenv
import requests
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import Field, BaseModel
from langchain_core.runnables import RunnablePassthrough
from langchain_core.tools import BaseTool
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 定义高德天气工具(BaseTool子类)
class GaodeWeatherArgsSchema(BaseModel):
city: str = Field(description="需要查询天气预报的目标城市,例如:广州")
class GaodeWeatherTool(BaseTool):
"""根据传入的城市名查询天气"""
name = "gaode_weather"
description = "当你想询问天气或与天气相关的问题时的工具。"
args_schema: Type[BaseModel] = GaodeWeatherArgsSchema
def _run(self, *args: Any, **kwargs: Any) -> str:
"""运行工具获取对应城市的天气预报"""
try:
gaode_api_key = os.getenv("GAODE_API_KEY")
if not gaode_api_key:
return f"高德开放平台API秘钥未配置"
city = kwargs.get("city", "")
session = requests.session()
api_domain = "https://restapi.amap.com/v3"
# 行政区域查询获取城市编码
city_response = session.request(
method="GET",
url=f"{api_domain}/config/district?keywords={city}&subdistrict=0&extensions=all&key={gaode_api_key}",
headers={"Content-Type": "application/json;charset=utf-8"},
)
city_response.raise_for_status()
city_data = city_response.json()
# 天气预报查询
if city_data.get("info") == "OK" and len(city_data.get("districts")) > 0:
ad_code = city_data["districts"][0]["adcode"]
weather_response = session.request(
method="GET",
url=f"{api_domain}/weather/weatherInfo?city={ad_code}&extensions=all&key={gaode_api_key}&output=json",
headers={"Content-Type": "application/json; charset=utf-8"},
)
weather_response.raise_for_status()
weather_data = weather_response.json()
if weather_data.get("info") == "OK":
return json.dumps(weather_data)
session.close()
return f"获取{kwargs.get('city')}天气预报信息失败"
except Exception as e:
return f"获取{kwargs.get('city')}天气预报信息失败"
# 1.构建多模态Prompt
prompt = ChatPromptTemplate.from_messages([
("human", [
{"type": "text", "text": "请获取下上传图片对应城市的天气信息。"},
{"type": "image_url", "image_url": {"url": "{image_url}"}}
])
])
# 2.构建天气信息整理Prompt
weather_prompt = ChatPromptTemplate.from_template("""
请整理下传递的城市的天气预报信息,并以用户友好的方式输出。
<weather>
{weather}
</weather>
""")
# 3.初始化GPT-4o并绑定天气工具
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools=[GaodeWeatherTool()], tool_choice="gaode_weather")
# 4.构建多模态链
chain = (
{
"weather": (
{"image_url": RunnablePassthrough()}
| prompt
| llm_with_tools
| (lambda msg: msg.tool_calls[0]["args"])
| GaodeWeatherTool()
)
}
| weather_prompt
| llm
| StrOutputParser()
)
# 调用链(传入图片URL)
print(chain.invoke("https://imooc-langchain.shortvar.com/guangzhou.jpg"))输出:广州未来几天的天气预报(结构化、用户友好的文本)
02.GPT-4o接入DALL-E实现多模态输出
通过函数调用绑定DALL-E文生图工具,让LLM将自然语言请求转换为文生图Prompt,实现自然语言→图片的多模态输出,底层是LLM函数调用+工具执行的组合。
实战:自然语言生成图片
python
import dotenv
from langchain_community.tools.openai_dalle_image_generation import OpenAIDALLEImageGenerationTool
from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 初始化DALL-E工具
dalle = OpenAIDALLEImageGenerationTool(
name="openai_dalle",
api_wrapper=DallEAPIWrapper(model="dall-e-3")
)
# 初始化GPT-4o并绑定文生图工具
llm = ChatOpenAI(model="gpt-4o")
llm_with_tools = llm.bind_tools(tools=[dalle], tool_choice="openai_dalle")
# 构建链
chain = llm_with_tools | (lambda msg: msg.tool_calls[0]["args"]) | dalle
# 调用链(自然语言请求)
print(chain.invoke("帮我绘制一幅老爷爷的图片"))输出:DALL-E生成的图片URL
基于ReACT架构的Agent智能体设计与实现
01.Agent概念与介绍
在LLM应用中,若工具使用顺序/次数由用户输入动态决定,普通LCEL链无法满足需求,而Agent智能体可让LLM自主决定工具的使用顺序、次数和时机,核心是解决复杂、多步骤、需外部数据的任务。
Agent核心定义
LangChain中的Agent是利用LLM+工具执行复杂任务的系统,设计目的是处理LLM无法直接解决的问题,其基础工作流程包含5个核心步骤:
- 输入理解:解析用户输入,理解意图和需求;
- 计划定制:基于输入制定执行计划,确定工具和执行顺序;
- 工具调用:按计划调用工具,执行具体操作;
- 结果整合:收集工具结果,解析并整合为中间输出;
- 反馈循环:若任务未完成,迭代上述步骤直至满足条件。
Agent核心组成模块
- Tools:Agent可访问的工具集,每个工具执行特定功能;
- Executor:执行Agent计划的核心逻辑(如AgentExecutor);
- Prompt Templates:指导Agent理解输入、选择工具的提示词模板,可定制化。
LangChain传统Agent类型(LangChain v0.2.0)
LangChain封装了8种传统Agent,适配不同类型的模型,核心分类如下:
| Agent类型 | 模型类型 | 适用场景 | 核心要求 |
|---|---|---|---|
| 工具调用 | Chat | 使用支持工具调用的模型 | 传入tools参数 |
| OpenAI工具 | Chat | 遗留:最新OpenAI模型 | 传入tools参数 |
| OpenAI函数 | Chat | 旧版:OpenAI/兼容函数调用模型 | 传入functions参数 |
| XML | LLM | Anthropic/擅长XML的模型 | 无特殊参数 |
| 结构化聊天 | Chat | 需要传递多个工具时 | 无特殊参数 |
| JSON聊天 | Chat | 擅长JSON的模型 | 无特殊参数 |
| ReACT | LLM | 简单模型 | 无特殊参数 |
| 带搜索的自我问答 | LLM | 简单模型+单一搜索工具 | 无特殊参数 |
文档链接:https://python.langchain.com/v0.1/docs/modules/agents/agent_types/
02.ReACT智能体运行流程与实现
ReACT(Reason+Action,推理+行动)是LangChain最早支持的Agent架构,目前绝大部分Agent架构均基于其衍生,由Yao等人在2022年提出(论文:https://arxiv.org/pdf/2210.03629)。
ReACT核心思想
让LLM通过自然语言推理明确下一步行动,再调用工具执行,并将工具结果作为观察反馈给LLM,迭代直至生成最终答案,核心格式为:Thought→Action→Action Input→Observation。
ReACT智能体实现步骤
- 定义工具集;
- 加载ReACT专用Prompt(LangChain Hub提供);
- 初始化LLM;
- 通过
create_react_agent创建ReACT Agent; - 通过
AgentExecutor封装执行逻辑。
实战:谷歌搜索ReACT智能体
python
import dotenv
from langchain import hub
from langchain.agents import create_react_agent, AgentExecutor
from langchain_community.tools import GoogleSerperRun
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_core.tools import render_text_description_and_args
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 1.定义谷歌搜索工具的参数模型和工具
class GoogleSerperArgsSchema(BaseModel):
query: str = Field(description="执行谷歌搜索的查询语句")
google_serper = GoogleSerperRun(
name="google_serper",
description=(
"一个低成本的谷歌搜索API。"
"当你需要回答有关时事的问题时,可以调用该工具。"
"该工具的输入是搜索查询语句。"
),
args_schema=GoogleSerperArgsSchema,
api_wrapper=GoogleSerperAPIWrapper(),
)
tools = [google_serper]
# 2.加载ReACT官方Prompt
prompt = hub.pull("hwchase17/react")
# 3.初始化LLM
llm = ChatOpenAI(model="gpt-3.5-turbo-16k")
# 4.创建ReACT Agent
agent = create_react_agent(
llm=llm,
tools=tools,
prompt=prompt,
tools_renderer=render_text_description_and_args,
)
# 5.创建Agent执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 调用Agent
print(agent_executor.invoke({"input": "马拉松的最新世界记录是多少?"}))典型输出流程
> Entering new AgentExecutor chain...
Thought: I should use the google_serper tool to search for the latest world record in marathon.
Action: google_serper
Action Input: {'query':'latest world record in marathon'}
Observation: Kenyan athlete Kelvin Kiptum set a men's world record time of 2:00:35 on october 8,2023,at the 2023 Chicago Marathon...
Thought: I now know the final answer
Final Answer: The latest world record in marathon is 2:00:35.
> Finished chain.
{'input':'马拉松的最新世界记录是多少?','output':'The latest world record in marathon is 2:00:35.'}03.ReACT智能体的缺陷
ReACT架构的核心是通过解析LLM自然语言输出中的关键词(Thought/Action/Final Answer) 决定下一步操作,因LLM输出的不稳定性,存在诸多缺陷:
缺陷1:LLM输出格式不规范导致程序报错
当提问无需调用工具的问题(如“你好,你是?”),ReACT要求LLM输出:
Thought: I now know the final answer
Final Answer: 我是一个人工智能助手,旨在帮助回答问题和提供信息。但LLM可能直接输出:我是一个人工智能助手,旨在帮助回答问题和提供信息。,此时ReACT解析不到Final Answer关键词,直接抛出错误。
缺陷2:Prompt国际化改造成本高
若将ReACT Prompt改为中文,需同步修改输出解析器(底层仍默认提取英文关键词Thought/Action),否则程序100%报错,定制化成本高。
缺陷3:对LLM能力依赖度高
低性能LLM易出现推理逻辑混乱、行动选择错误、输入参数缺失等问题,导致Agent执行流程中断。
基于工具调用的智能体设计与实现
为解决ReACT架构的稳定性问题,基于ChatModel+原生工具调用的Agent架构应运而生,核心是将ReACT的自然语言推理+关键词解析替换为结构化工具调用参数+模型原生解析,大幅提升稳定性。
01.工具调用智能体核心思想
与ReACT架构的核心差异
| 维度 | ReACT架构 | 工具调用架构 |
|---|---|---|
| 推理输出形式 | 自然语言文本 | 结构化工具调用参数 |
| 下一步解析方式 | 关键词匹配(如Thought) | 模型原生解析tool_calls参数 |
| 稳定性 | 低(依赖LLM输出规范) | 高(结构化参数无歧义) |
| 多工具调用 | 支持差 | 原生支持多工具并行调用 |
| Prompt定制成本 | 高(需同步修改解析器) | 低(无需修改解析器) |
核心设计思路
将ReACT的Prompt内工具描述+智能体草稿迁移为ChatModel的消息列表+工具绑定,通过检测LLM输出是普通文本还是tool_calls参数决定下一步操作:
- 若输出tool_calls→调用对应工具,将结果作为ToolMessage加入消息列表;
- 若输出普通文本→直接作为最终答案返回。
关键优势
- 利用ChatModel原生工具调用能力,结构化输出无歧义,稳定性大幅提升;
- 支持多工具并行调用,执行效率更高;
- Prompt定制化简单,无需修改输出解析器;
- 兼容所有支持工具调用的ChatModel(如GPT-3.5/4o、文心一言等)。
官方文档:https://python.langchain.com/0.1/docs/modules/agents/agent_types/tool_calling/ Prompt链接:https://smith.langchain.com/hub/hwchase17/openai-tools-agent
02.工具调用智能体实现示例
实现一个支持谷歌实时搜索+DALL-E文生图的工具调用Agent,LLM可根据用户输入自主选择工具,步骤与ReACT Agent基本一致,核心差异为使用create_tool_calling_agent创建Agent。
python
import dotenv
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_community.tools import GoogleSerperRun
from langchain_community.tools.openai_dalle_image_generation import OpenAIDALLEImageGenerationTool
from langchain_community.utilities import GoogleSerperAPIWrapper
from langchain_community.utilities.dalle_image_generator import DallEAPIWrapper
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.pydantic_v1 import BaseModel, Field
from langchain_openai import ChatOpenAI
dotenv.load_dotenv()
# 1.定义工具参数模型和工具集
## 谷歌搜索工具
class GoogleSerperArgsSchema(BaseModel):
query: str = Field(description="执行谷歌搜索的查询语句")
google_serper = GoogleSerperRun(
name="google_serper",
description=(
"一个低成本的谷歌搜索API。"
"当你需要回答有关时事的问题时,可以调用该工具。"
"该工具的输入是搜索查询语句。"
),
args_schema=GoogleSerperArgsSchema,
api_wrapper=GoogleSerperAPIWrapper(),
)
## DALL-E文生图工具
class DallEArgsSchema(BaseModel):
query: str = Field(description="输入应该是生成图像的文本提示(prompt)")
dalle = OpenAIDALLEImageGenerationTool(
name="openai_dalle",
api_wrapper=DallEAPIWrapper(model="dall-e-3"),
args_schema=DallEArgsSchema,
)
tools = [google_serper, dalle]
# 2.构建工具调用Agent的Prompt
prompt = ChatPromptTemplate.from_messages([
("placeholder", "{chat_history}"),
("system", "You are a helpful assistant"),
("human", "{input}"),
("placeholder", "{agent_scratchpad}"),
])
# 3.初始化支持工具调用的LLM
llm = ChatOpenAI(model="gpt-4o-mini")
# 4.创建工具调用Agent
agent = create_tool_calling_agent(
prompt=prompt,
llm=llm,
tools=tools,
)
# 5.创建Agent执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 调用Agent(文生图请求)
print(agent_executor.invoke({"input": "请帮我绘制一张老爷爷爬山的图片"}))
# 调用Agent(搜索请求)
print(agent_executor.invoke({"input": "马拉松的世界记录是多少?"}))文生图请求输出
> Entering new AgentExecutor chain...
Invoking: 'openai_dalle' with '{'query':'An elderly man climbing a mountain, with a determined expression...'}'
https://dalleproduse.blob.core.windows.net/.../generated_00.png
> Finished chain.
{'input':'请帮我绘制一张老爷爷爬山的图片','output':'这是我为您绘制的老爷爷爬山的图片:[图片URL]'}搜索请求输出
> Entering new AgentExecutor chain...
Invoking: 'google_serper' with '{'query':'马拉松世界纪录2023'}'
2023年10月芝加哥马拉松,基普图姆以2小时00分35秒打破世界纪录...
> Finished chain.
{'input':'马拉松的世界记录是多少?','output':'截至2023年10月,马拉松的世界纪录是2小时00分35秒,由基普图姆在芝加哥马拉松创造。'}内置的其他Agent类型介绍与上手
LangChain v0.2.0之前封装了多种传统Agent,均基于推理-行动-观察核心思想,适配不同类型的LLM/ChatModel(如擅长XML/JSON的模型),使用时仅需修改Prompt+替换创建方法,其余逻辑完全复用。
01.典型Agent示例:XML Agent
适配擅长XML解析的模型(如Anthropic),通过<tool>/<tool_input>/<final_answer>等XML标签定义执行流程,实现步骤:
- 构建XML格式的Prompt;
- 使用
create_xml_agent创建Agent; - 封装AgentExecutor执行。
python
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain.agents import create_xml_agent, AgentExecutor
# 1.构建XML Agent Prompt
prompt = ChatPromptTemplate.from_messages([
("human", """
You are a helpful assistant. Help the user answer any questions.
You have access to the following tools:
{tools}
In order to use a tool, you can use <tool></tool> and <tool_input></tool_input> tags.
You will then get back a response in the form <observation></observation>
When you are done, respond with a final answer between <final_answer></final_answer>.
Begin!
Previous Conversation:
{chat_history}
Question: {input}
{agent_scratchpad}
""")
])
# 2.初始化LLM和Agent
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_xml_agent(
llm=llm,
prompt=prompt,
tools=tools, # 复用谷歌搜索+文生图工具
)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)
# 调用Agent
print(agent_executor.invoke({"input": "马拉松的世界记录是多少?", "chat_history": ""}))02.典型Agent示例:JSON Agent
适配擅长JSON解析的模型,要求LLM输出指定格式的JSON数据(action/action_input),核心是修改Prompt为JSON格式规范,使用create_json_agent创建Agent。
JSON Agent核心Prompt片段
python
prompt = ChatPromptTemplate.from_messages([
("system", "Assistant is a large language model trained by OpenAI..."),
("placeholder", "{chat_history}"),
("human", """
TOOLS
Assistant can ask the user to use tools to look up information...
RESPONSE FORMAT INSTRUCTIONS
when responding to me, please output a response in one of two formats:
**Option 1 (call tool):**
```json
{{"action": "工具名", "action_input": "工具输入"}}Option 2 (final answer):
json
{{"action": "Final Answer", "action_input": "最终答案"}}USER'S INPUT Here is the user's input: {input} {agent_scratchpad} """) ])
## 03.所有传统Agent的异同点
### 相同点
1. 均包含**input**(原始问题)和**agent_scratchpad**(智能体草稿)两个核心输入变量;
2. 均为**单Agent自我执行**,不支持多Agent协作;
3. 均可通过**切换Prompt+create_xxx_agent()** 快速切换,无需修改大量代码;
4. 核心设计思想均为**推理-行动-观察**,仅Prompt格式不同;
5. 均使用**单一LLM**完成推理和答案生成,不支持多LLM分工;
6. 除工具调用Agent外,其余Agent**国际化改造需同步修改输出解析器**。
### 不同点
1. **Prompt风格**:ReACT为自然语言、XML Agent为XML标签、JSON Agent为JSON格式;
2. **输出解析器**:解析规则由Prompt决定,部分支持多工具调用,部分不支持;
3. **输入编码**:部分支持历史聊天记录(chat_history),部分不支持;
4. **模型适配性**:不同Agent适配不同能力的LLM/ChatModel。
# AgentExecutor源码解析与Agent组件缺陷
## 01.AgentExecutor源码解析
LangChain中所有传统Agent均需通过**AgentExecutor**封装执行,其核心作用是实现**工具调用的循环执行+结果反馈**,是Agent的“执行引擎”。
### 核心运行逻辑(伪代码)
```python
# 获取初始行动
next_action = agent.get_action(inputs)
# 循环执行,直至生成Final Answer
while next_action != AgentFinish:
# 执行工具,获取观察结果
observation = run(next_action)
# 将观察结果反馈给Agent,获取下一个行动
next_action = agent.get_action(inputs, next_action, observation)
# 返回最终答案
return next_action核心代码:_call()函数
python
def _call(
self,
inputs: Dict[str, str],
run_manager: Optional[CallbackManagerForChainRun] = None,
) -> Dict[str, Any]:
# 工具名映射,便于快速查询
name_to_tool_map = {tool.name: tool for tool in self.tools}
# 中间步骤:存储(AgentAction, Observation)元组
intermediate_steps: List[Tuple[AgentAction, str]] = []
iterations = 0 # 迭代次数
start_time = time.time() # 开始时间
# 循环执行:未超过迭代次数/时间限制
while self._should_continue(iterations, time.time() - start_time):
# 获取下一步行动
next_step_output = self._take_next_step(
name_to_tool_map, inputs, intermediate_steps, run_manager=run_manager
)
# 若为最终答案,直接返回
if isinstance(next_step_output, AgentFinish):
return self._return(next_step_output, intermediate_steps, run_manager)
# 将步骤添加到中间结果
intermediate_steps.extend(next_step_output)
# 检测工具是否需要直接返回结果
if len(next_step_output) == 1:
tool_return = self._get_tool_return(next_step_output[0])
if tool_return is not None:
return self._return(tool_return, intermediate_steps, run_manager)
# 迭代次数+1
iterations += 1
# 超过限制,返回停止响应
output = self.agent.return_stopped_response(self.early_stopping_method, intermediate_steps, **inputs)
return self._return(output, intermediate_steps, run_manager)核心运行流程
- 初始化:构建工具映射、中间步骤存储、迭代计数器;
- 循环判断:检查是否超过最大迭代次数/时间限制;
- 获取行动:调用Agent的
_take_next_step方法获取下一步行动(AgentAction); - 工具执行:根据AgentAction调用对应工具,获取观察结果(Observation);
- 结果反馈:将Observation加入中间步骤,反馈给Agent;
- 终止判断:若获取到AgentFinish(最终答案)或工具需直接返回,终止循环并返回结果;
- 超时处理:若超过限制,返回停止响应。
02.传统Agent组件的缺陷
LangChain传统Agent(基于AgentExecutor)解决了普通LCEL链无法循环执行的问题,但因过度封装+黑盒机制,存在诸多致命缺陷,无法满足复杂Agent应用的需求:
缺陷1:无条件分支,仅支持线性循环
Agent执行流程为单一线性循环,无法根据输入/工具结果实现条件分支/路由(如数学问题走数学工具链,物理问题走物理工具链)。
缺陷2:不支持多Agent协作
所有传统Agent均为单Agent独立执行,无法实现多Agent分工、协作、通信(如一个Agent负责搜索,一个Agent负责数据分析)。
缺陷3:Prompt与解析器过度耦合
传统Agent的Prompt和输出解析器高度绑定,定制化修改成本极高,例如修改Prompt语言、调整执行逻辑,需同步修改底层解析器。
缺陷4:黑盒机制,无中间干预能力
Agent和AgentExecutor均为黑盒执行,无法在执行过程中插入自定义逻辑(如手动修改工具参数、中断执行、添加人工审核)。
缺陷5:扩展能力差,难以动态切换LLM/工具
传统Agent的LLM和工具在初始化时绑定,运行时无法动态切换,也难以添加记忆、知识库等扩展功能,灵活性极低。
03.解决方案:LangGraph
上述缺陷的核心解决方案是LangGraph(LangChain v0.2.0核心特性),它基于图结构实现Agent的执行逻辑,支持:
- 条件分支/路由:基于节点输出动态决定下一步执行节点;
- 多Agent协作:多个Agent作为独立节点,实现分工、通信;
- 透明化执行:所有节点和边均可定制,支持中间干预;
- 动态扩展:运行时可动态切换LLM、工具、添加记忆/知识库;
- 兼容LCEL:与LangChain的LCEL表达式无缝集成,调用统一(invoke/stream/batch)。
LangGraph是LangChain构建复杂Agent应用的核心框架,可实现串联数百个节点、上百种工具的复杂工作流。
核心知识点总结
一、LLM工具调用
- 函数调用的核心价值:获取结构化输出、弥补LLM知识时效性、对接外部工具/系统;
- LangChain创建自定义工具的3种方式:
@tool装饰器、StructuredTool.from_function()、BaseTool子类; - 工具绑定方法:
bind()和bind_tools(),后者为工具调用专用快捷方法; - 非原生工具调用LLM的兼容方案:Prompt指定格式+输出解析;
- 结构化输出最优方案:
with_structured_output(),底层基于函数调用/JSON模式; - 函数调用错误处理:
try/except异常捕获、模型回退、携带错误信息的重试。
二、多模态LLM工具调用
- 多模态输入:按模型规则传递文本+图片URL,LangChain中封装为列表格式;
- 多模态输出:通过函数调用绑定DALL-E文生图工具,实现自然语言→图片;
- 核心实战:图片识别+工具调用(如图片城市→天气查询)。
三、Agent智能体开发
- Agent核心定义:LLM+工具+自主决策,解决复杂、多步骤、需外部数据的任务;
- ReACT架构:Reason+Action,自然语言推理+关键词解析,稳定性差;
- 工具调用架构:结构化工具调用参数+模型原生解析,稳定性高,为当前主流方案;
- 传统Agent类型:ReACT/XML/JSON/工具调用等,均基于推理-行动-观察思想;
- AgentExecutor:Agent的执行引擎,实现循环工具调用+结果反馈。
四、传统Agent的缺陷与解决方案
- 核心缺陷:无分支、不支持多Agent协作、黑盒机制、扩展能力差;
- 解决方案:LangGraph,基于图结构实现Agent的灵活、可扩展、可干预的执行逻辑。