LangChain1.2 学习笔记(自用)(未完结)

1. 介绍

1.1 概述

LangChain 是一款面向大语言模型(LLM)驱动类应用的开源开发框架。它并非只是对模型 API 的简单封装,而是提供了成熟的智能体(Agent)架构与丰富的工具集成,让开发者无需从零搭建复杂的底层整合方案,就能快速构建出高阶 LLM 应用。

该开源框架诞生于 2022 年 10 月 ,由哈佛大学的 Harrison Chase(哈里森・蔡斯)发起。其名称取自 Language(语言模型)Chain(链式调用) 的组合,直观体现了它的核心设计思想:将大语言模型与外部计算资源、数据源进行链式连接,从而打造能力更强、更实用的 AI 应用

LangChain 的出现,源于一个关键判断:

单一大语言模型虽能力强劲,但在真实落地场景中存在明显短板:

  • 知识仅覆盖训练数据截止前的信息,无法获取最新内容;
  • 无法直接与数据库、外部 API 等系统交互;
  • 自身不具备状态记忆能力,难以实现连贯的多轮对话。

因此,想要打造真正可用的 AI 应用,就必须将 LLM 与外部工具、数据源和记忆机制深度结合,这也正是 LangChain 框架的核心设计初衷。

1.2 特点

  1. 统一的模型接口

LangChain通过统一的API抽象层,解决了不同模型提供商接口各异的问题。各大模型提供商(如OpenAI、Anthropic、Google等)都有独特的API接口、参数规范和响应格式。LangChain通过标准化模型交互接口,使开发者可以无缝切换不同模型而无需重写大量代码。这一特性不仅降低了技术锁定风险,还使得开发者能够轻松利用最新最先进的模型,加速实验和创新周期

  1. 模块化架构

LangChain采用高度模块化架构,将复杂的大模型应用分解为可复用的构建块。核心组件包括模型(Models)、提示模板(Prompts)、记忆(Memory)、链(Chains)、智能体(Agents)和工具(Tools)等。这种设计使开发者可以像搭积木一样组合各种功能,快速构建符合特定需求的AI应用。

组件的可组合性体现在LangChain表达式语言(LCEL)中,它允许开发者通过管道操作符(|)将多个组件连接成复杂的工作流。例如,一个简单的检索增强生成流程可以通过组合检索器、提示模板和LLM来实现。这种声明式的工作流定义方式不仅代码简洁,而且天然支持流式输出、异步调用和并行执行等高级特性,显著提升了开发效率和运行时性能。

  1. 智能体与工具调用

智能体是LangChain最强大的特性之一,它将大语言模型从被动的文本生成器转变为能够主动决策和执行任务的智能系统。智能体的核心思想是使用LLM作为"大脑",通过观察-思考-行动的循环来动态决定如何解决用户问题。

LangChain智能体支持丰富的外部工具集成,包括搜索引擎、数据库、API接口等。工具是标准的函数接口,包含名称、描述和执行函数三个基本要素。智能体通过分析用户查询,自动选择适当的工具,执行后根据结果决定下一步行动,直至问题解决。这种机制极大地扩展了大模型的能力边界,使其能够处理需要实时数据或具体操作的任务。

  1. 记忆管理机制

LangChain的记忆系统使大模型应用能够保持对话状态和历史上下文,实现了真正有意义的多轮交互。记忆机制解决了纯LLM应用的无状态性问题,通过维护短期和长期记忆,使AI应用能够参考之前的对话内容,提供更加连贯和个性化的体验。

记忆系统的分层架构支持不同存储后端(内存、文件、数据库)和检索策略。开发者可以根据应用需求选择适当的记忆类型,如客服系统可能需要长期记忆用户偏好,而数据查询应用可能只需短期记忆当前会话上下文。这种灵活的记忆管理是构建高质量对话应用的关键。

1.3 环境准备

LangChain 1.2版本要求Python版本为3.10+以上。

cmd 复制代码
pip install -U langchain

快速上手:

简单的Agent查询天气

前提:在阿里云百炼中申请一个API KEY,链接:https://bailian.console.aliyun.com/

python 复制代码
import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

os.environ["DASHSCOPE_API_KEY"] = "your-api-key"

def get_weather(city: str) -> str:
    """获取指定城市的天气。"""
    return f"{city}总是阳光明媚!"

# 配置通义千问模型
model = ChatOpenAI(
    api_key=os.getenv("DASHSCOPE_API_KEY"),
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
    model="qwen-plus"
)

agent = create_agent(
    model=model,
    tools=[get_weather],
    system_prompt="你是一个乐于助人的助手",
)

# 运行代理
rs = agent.invoke(
    {"messages": [{"role": "user", "content": "旧金山的天气怎么样"}]}
)
print(rs)

运行结果:

python 复制代码
{
    "messages": [
        {
            "content": "旧金山的天气怎么样",
            "additional_kwargs": {},
            "response_metadata": {},
            "id": "3546a3b3-cb1f-4f68-b2ea-8efc5cf790b2",
            "type": "HumanMessage"  # 补充类型标识,便于识别
        },
        {
            "content": "",
            "additional_kwargs": {"refusal": None},
            "response_metadata": {
                "token_usage": {
                    "completion_tokens": 20,
                    "prompt_tokens": 158,
                    "total_tokens": 178,
                    "completion_tokens_details": None,
                    "prompt_tokens_details": {"audio_tokens": None, "cached_tokens": 0}
                },
                "model_provider": "openai",
                "model_name": "qwen-plus",
                "system_fingerprint": None,
                "id": "chatcmpl-007ac435-b061-9c32-be5d-d2917c01db33",
                "finish_reason": "tool_calls",
                "logprobs": None
            },
            "id": "lc_run--019ceb8a-3a62-7a33-993c-d8a573b3784c-0",
            "tool_calls": [
                {
                    "name": "get_weather",
                    "args": {"city": "旧金山"},
                    "id": "call_7fa160198adb48e985a89c",
                    "type": "tool_call"
                }
            ],
            "invalid_tool_calls": [],
            "usage_metadata": {
                "input_tokens": 158,
                "output_tokens": 20,
                "total_tokens": 178,
                "input_token_details": {"cache_read": 0},
                "output_token_details": {}
            },
            "type": "AIMessage"  # 补充类型标识
        },
        {
            "content": "旧金山总是阳光明媚!",
            "name": "get_weather",
            "id": "e0a29efc-bf11-4092-83ea-000e894f9678",
            "tool_call_id": "call_7fa160198adb48e985a89c",
            "type": "ToolMessage"  # 补充类型标识
        },
        {
            "content": "旧金山的天气确实常常阳光明媚,不过它也以多变的气候著称------尤其是夏季常有晨雾(当地人称为"海雾"),午后才逐渐散去,带来凉爽晴朗的天气。这种"雾进雾出"的特点让旧金山的气温常年温和宜人,很少出现极端高温或严寒。\n\n如果你计划前往,建议随身带件薄外套,以应对海边或傍晚的微凉哦!需要我帮你查实时天气或旅行小贴士吗? 😊",
            "additional_kwargs": {"refusal": None},
            "response_metadata": {
                "token_usage": {
                    "completion_tokens": 111,
                    "prompt_tokens": 198,
                    "total_tokens": 309,
                    "completion_tokens_details": None,
                    "prompt_tokens_details": {"audio_tokens": None, "cached_tokens": 64}
                },
                "model_provider": "openai",
                "model_name": "qwen-plus",
                "system_fingerprint": None,
                "id": "chatcmpl-4b4d1b60-3785-94b5-ad17-8102b53b0138",
                "finish_reason": "stop",
                "logprobs": None
            },
            "id": "lc_run--019ceb8a-3e5a-70b3-b7be-342b71111139-0",
            "tool_calls": [],
            "invalid_tool_calls": [],
            "usage_metadata": {
                "input_tokens": 198,
                "output_tokens": 111,
                "total_tokens": 309,
                "input_token_details": {"cache_read": 64},
                "output_token_details": {}
            },
            "type": "AIMessage"  # 补充类型标识
        }
    ]
}

2. 模型(Models)

LLMs 是强大的人工智能工具,可以像人类一样理解和生成文本。它们具有足够的通用性,可以编写内容、翻译语言、总结和回答问题,而无需为每项任务进行专门培训。

除了文本生成之外,许多模型还支持:

  • 工具调用 (Tool calling) - 调用外部工具(如数据库查询或 API 调用)并在响应中使用结果。
  • 结构化输出 - 模型的响应被限制为遵循定义的格式。
  • 多模态 - 处理并返回文本以外的数据,如图像、音频和视频。
  • 推理 - 模型执行多步推理以得出结论。

模型是 智能体 (agents)的推理引擎。它们驱动智能体的决策过程,决定调用哪些工具、如何解释结果以及何时提供最终答案。模型质量和功能直接影响智能体的基础可靠性和性能。不同的模型擅长不同的任务------有些更擅长遵循复杂的指令,有些擅长结构化推理,有些则支持更大的上下文窗口以处理更多信息。LangChain 的标准模型接口允许您访问许多不同的提供商集成,这使得试验和切换模型以找到最适合用例的模型变得非常容易。

2.1 模型初始化

在 LangChain 中开始使用独立模型最简单的方法是使用 init_chat_model 初始化一个模型。或者使用特定的Model Class来初始化。

2.1.1 Model Class初始化

Model Class方式初始化模型最为直接,需要根据要使用的模型提供商,导入对应的具体类(如 ChatOpenAI, ChatAnthropic)并进行实例化。

下面以聊天模型为例介绍常见的聊天模式初始化:ChatOpenAI、ChatAnthropic、ChatDeepSeek、ChatOllama、ChatHunyuan、ChatTongyi、ChatZhipuAI按照如下步骤初始化各个模型。

python 复制代码
#安装ChatOpenAI依赖包
pip install langchain-openai==1.1.6

#安装ChatAnthropic依赖包
pip install langchain-anthropic==1.3.1

#安装ChatDeepSeek 依赖包
pip install langchain-deepseek==1.0.1

#安装ChatOllama 依赖包
pip install langchain-ollama==1.0.1

#安装 ChatHunyuan、ChatTongyi、ChatZhipuAI依赖包
pip install langchain-community==0.4.1

#安装 ChatHunyuan依赖包
pip install tencentcloud-sdk-python==3.1.28

#安装 ChatTongyi依赖包
pip install dashscope==1.25.6

#安装 ChatZhipuAI依赖包
pip install pyjwt==2.10.1

示例代码:

python 复制代码
from langchain_anthropic import ChatAnthropic
from langchain_community.chat_models import ChatHunyuan, ChatTongyi, ChatZhipuAI
from langchain_deepseek import ChatDeepSeek
from langchain_ollama import ChatOllama
from langchain_openai import ChatOpenAI


# 创建DeepSeek LLM
deepseek_llm = ChatDeepSeek(
    api_key=DEEPSEEK_API_KEY,
    api_base='https://api.deepseek.com', # 注意:这里是api_base,不是base_url
    model="deepseek-chat",
)


openai_llm = ChatOpenAI(
    api_key=OPENAI_API_KEY,
    base_url='https://api.uchat.site/v1',
    model="gpt-4",
)

anthropic_llm = ChatAnthropic(
    api_key=ANTHROPIC_API_KEY,
    base_url='https://api.uchat.site',
    model="claude-3-5-haiku-latest",
)


ollama_llm = ChatOllama(
    base_url="http://192.168.1.106:11434",
    model="deepseek-r1:1.5b",
)

hunyuan_llm = ChatHunyuan(
    hunyuan_app_id = HUNYUAN_APP_ID,
    hunyuan_secret_id = HUNYUAN_SECRET_ID,
    hunyuan_secret_key = HUNYUAN_SECRET_KEY,
    model="hunyuan-lite",
)

tongyi_llm = ChatTongyi(
    api_key=DASHSCOPE_API_KEY,
    model="qwen-plus",
)


zhipu_llm = ChatZhipuAI(
    api_key=ZHIPUAI_API_KEY,
    model="glm-4",
)

print(openai_llm.invoke("请介绍一下你自己"))
print(anthropic_llm.invoke("请介绍一下你自己"))
print(deepseek_llm.invoke("请介绍一下你自己"))
print(ollama_llm.invoke("请介绍一下你自己"))
print(hunyuan_llm.invoke("请介绍一下你自己"))
print(tongyi_llm.invoke("请介绍一下你自己"))
print(zhipu_llm.invoke("请介绍一下你自己"))

注意:使用不同的模型可能传入的参数名称不同,可以参考对应的源码。

如果一些模型供应商也同时兼容OpenAI ,那么也可以直接使用ChatOpenAI方式进行连接,这样对于一些目前LangChain不支持的模型供应商,但这些供应商支持标准的OpenAI 方式连接,就可以使用这种方式。

python 复制代码
# 通过ChatOpenAI 连接deepseek模型
deepseek_llm2 = ChatOpenAI(
    api_key=DEEPSEEK_API_KEY,
    base_url=DEEPSEEK_BASE_URL,
    model="deepseek-chat",
)

#通过ChatOpenAI 连接通义千问模型
tongyi_llm2 = ChatOpenAI(
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
    model="qwen-plus",
)

#通过ChatOpenAI 连接智普AI模型
zhipu_llm2 = ChatOpenAI(
    api_key=ZHIPUAI_API_KEY,
    base_url=ZHIPUAI_BASE_URL,
    model="glm-4",
)
  • 关于各个聊天模型LangChain使用方式可以参考如下官网
模型供应商 LangChain使用相关链接
OpenAI https://docs.langchain.com/oss/python/integrations/chat/openai
Anthropic https://docs.langchain.com/oss/python/integrations/chat/anthropic
DeepSeek https://docs.langchain.com/oss/python/integrations/chat/deepseek
Ollama https://docs.langchain.com/oss/python/integrations/chat/ollama
腾讯混元 https://docs.langchain.com/oss/python/integrations/chat/tencent_hunyuan
通义千问 https://docs.langchain.com/oss/python/integrations/chat/tongyi
智普AI https://docs.langchain.com/oss/python/integrations/chat/zhipuai
  • 关于各个聊天模型在线API_Key及BaseURL查找
模型供应商 相关链接
OpenAI api_key地址:https://platform.openai.com/api-keysbase_ url地址:https://api.openai.com/v1 模型地址:https://platform.openai.com/docs/models 模型价格:https://platform.openai.com/docs/pricing
Anthropic api_key地址:https://console.anthropic.com/settings/keysbase_ url地址:https://console.anthropic.com/docs/zh-CN/get-started 模型地址:https://console.anthropic.com/docs/zh-CN/about-claude/models/overview 模型价格:https://console.anthropic.com/docs/zh-CN/about-claude/pricing
DeepSeek api_key地址:https://platform.deepseek.com/api_keysbase_ url地址:https://api-docs.deepseek.com/zh-cn/ 模型地址:https://api-docs.deepseek.com/zh-cn/quick_start/pricing 模型价格:https://api-docs.deepseek.com/zh-cn/quick_start/pricing
腾讯混元 api_key地址:https://console.cloud.tencent.com/cam/capibase_ url地址:https://cloud.tencent.com/document/api/1729/105701 模型地址:https://cloud.tencent.com/document/product/1729/104753 模型价格:https://cloud.tencent.com/document/product/1729/97731
通义千问 api_key地址:https://bailian.console.aliyun.com/?tab=model#/api-keybase_ url地址:https://bailian.console.aliyun.com/?tab=api#/api 模型地址:https://bailian.console.aliyun.com/?tab=model#/model-market/all 模型价格:https://bailian.console.aliyun.com/?tab=doc#/doc/?type=model\&url=2987148
智普AI api_key地址:https://bigmodel.cn/usercenter/proj-mgmt/apikeysbase_ url地址:https://docs.bigmodel.cn/cn/api/introduction 模型地址:https://docs.bigmodel.cn/cn/guide/start/model-overview 模型价格:https://open.bigmodel.cn/pricing
2.1.2 init_chat_model初始化

init_chat_model 初始化模型是LangChain v1.0版本后推出的模型统一初始化方法,该方法像一个智能工厂,需要传入"model"(模型)、"model_provider"(模型提供商)、"api_key"、"base_url"参数自动创建出对应的模型实例。初始化模型后,调用模型方式与ModelClass 方式完全一致。

model_provider支持的常见参数:openai、anthropic、deepseek、ollama,同样,如果使用的模型供应商没有对应的provider,但是该模型供应商支持标准OpenAI访问,那么可以设置"model_provider"为"openai"。

部分示例:

python 复制代码
from langchain.chat_models import init_chat_model


openai_llm = init_chat_model(
    model="gpt-4",
    model_provider="openai",
    api_key=OPENAI_API_KEY,
    base_url=OPENAI_BASE_URL,
)

anthropic_llm = init_chat_model(
    model="claude-3-5-haiku-latest",
    model_provider="anthropic",
    api_key=ANTHROPIC_API_KEY,
    base_url=ANTHROPIC_BASE_URL,
)

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",	# 没有provider,但支持OpenAI
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

print(openai_llm.invoke("请介绍一下你自己"))
print(anthropic_llm.invoke("请介绍一下你自己"))
print(tongyi_llm.invoke("请介绍一下你自己"))
2.1.3 参数解释

在LangChain中,Model Class 和init_chat_model初始化模型共同的参数及解释:

参数 类型 参数描述
model string 指定要使用的模型标识符。
api_key string 用于身份验证的API密钥。建议通过环境变量设置,避免硬编码。
base_url string 指定API端点。
temperature number 控制输出的随机性,值越低,结果越确定、保守;值越高,结果越多样、有创意。
max_tokens number 限制模型响应生成的最大令牌数,有效控制回复长度。
timeout number 设置等待模型响应的最大时间(秒),超时则取消请求。
max_retries number 定义请求失败(如网络问题、速率限制)时的最大重试次数,提高鲁棒性。

Model Class初始化模型中每个聊天模型可能有额外的参数,可以参考对应的LangChain集成页面查看;

model_provider是init_chat_model方法中指定模型供应商的参数。

Token并非简单的"字"或"词",而是大模型通过分词器(Tokenizer)将输入文本拆分后的最小语义单元。不同的模型采用不同的分词算法(如BPE、WordPiece),因此同一段文本在不同模型中的Token数量可能不同。

  • 英文Token估算:1个Token约对应0.75个英文单词或4个字符;
  • 中文Token估算:1个汉字通常对应1~2个Token,但优化较好的模型(如通义千问、文心一言)约1:1的映射。

2.2 模型调用

在 LangChain 中,**模型调用(Invocation)**是指通过特定方法触发大语言模型生成输出的过程。根据不同的应用场景和需求,LangChain 提供了几种核心的调用方式,主要是 invoke(), stream()batch()方法,以及它们的异步版本 ainvoke(), astream(), 和 abatch(),下面将系统地介绍这些方法。

核心方法 主要特点 适用场景
invoke() 阻塞式,一次性返回完整结果 简单问答、批处理任务、无需实时反馈的场景。
ainvoke() 非阻塞,提高系统吞吐量 高并发Web应用、IO密集型任务。
stream() 流式输出,实时返回每个token 聊天机器人、长文本生成、需要提升用户体验的交互应用。
asteam() 非阻塞,提高系统吞吐量 高并发Web应用、IO密集型任务。
batch() 批量处理多个输入 高并发场景,需要同时处理大量请求。
abatch() 非阻塞,提高系统吞吐量 高并发Web应用、IO密集型任务。
2.2.1 invoke()

invoke()是最直接、最常用的模型调用方法。它的工作模式是阻塞式的,即程序会等待模型完全生成整个响应后,再一次性将结果返回给用户。

invoke方法非常灵活,支持三种形式的输入:单条消息、消息列表(字典格式)、消息列表(消息对象格式)。

(1)单条消息

该方式是最简单的方式,直接传入一个问题或指令,适用于不需要保留对话历史的简单生成任务。

python 复制代码
from init_llm import deepseek_llm

# 单条消息调用
resp = deepseek_llm.invoke("请介绍一下你自己")
print(resp.content)  # 输出:你好!我是DeepSeek,由深度求索公司创造的AI助手...

(2)消息列表(字典格式)

该方式用于表达多轮对话历史,每条消息都需要通过 role字段指定其角色(如 system, user, assistant)。

python 复制代码
from init_llm import deepseek_llm
#消息列表(字典格式)调用
conversation = [
    {"role": "system", "content": "你是一个有帮助的助手,可以将汉语翻译成英语。"},
    {"role": "user", "content": "翻译: 我喜欢编程"},
    {"role": "assistant", "content": "I love programming."},
    {"role": "user", "content": "翻译: 我喜欢大模型"}
]
resp= deepseek_llm.invoke(conversation)
print(resp.content)  # 输出: I like large models.

(3)消息列表(消息对象格式)

这是LangChain推荐的方式,使用内置的消息类(如 SystemMessage, HumanMessage, AIMessage),类型更安全,功能也更丰富。

python 复制代码
from init_llm import deepseek_llm
from langchain_core.messages import SystemMessage, HumanMessage, AIMessage

conversation = [
    SystemMessage("你是一个有帮助的助手,可以将汉语翻译成英语。"),
    HumanMessage("翻译: 我喜欢编程"),
    AIMessage("I love programming."),
    HumanMessage("翻译: 我喜欢大模型")
]
resp = deepseek_llm.invoke(conversation)
print(resp.content)  # 输出: I like large models.

使用Invoke时注意如下几点:

  • 关于Message types有 System message、Human message、AI message 、ToolMessage四种,解释如下:
    • System message:用于在对话开始时为模型设定角色、行为准则和上下文背景。它像是给AI助手的一份工作说明书,决定了其回答问题的风格、领域和专业范围。
    • HumanMessage:代表用户的输入,包含简单的文本问题,也可以是复杂的多模态内容(如图片、音频、文档等)。在多轮对话中,它表示用户的一次发言。
    • AIMessage :代表模型的输出或回复,包括生成的文本、工具调用、元数据等。
    • ToolMessage:将工具执行的结果返回给模型,让模型基于这个结果继续生成回复。
  • 在"消息列表(字典格式)"输入中,支持"system、user、assistant"写法,在某些支持高级功能(如工具调用)的场景下,还可能看到 "tool"等角色,虽然 "user"和 "human"有时可以互换,但遵循你选择的主要模型提供商(如OpenAI)的惯例使用 "user"是最稳妥的做法。
  • ChatModel(聊天模型)的 invoke方法返回的是一个 AIMessage对象,你需要通过 .content属性来获取模型生成的文本内容。
2.2.2 stream()

流式传输调用大模型允许大语言模型(LLM)在生成内容的过程中,逐块(Chunk)地实时输出结果,而不是等待整个响应完全生成后再一次性返回。流式调用大模型使用方式是通过调用 model.stream()方法会返回一个迭代器(Iterator),你可以通过循环来实时处理每一个新生成的内容块。

使用示例:

python 复制代码
import os
from langchain.chat_models import init_chat_model

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

res = tongyi_llm.stream("使用20个字给我介绍什么是大模型?")

for chunk in res:
    print(chunk.content, end="", flush=True)    # end="" 避免打印时自动换行,flush=True 及时刷新输出

注意:stream()方法是流式调用,立即返回一个迭代器,逐个产生 AIMessageChunk对象。每个块都包含输出内容的一部分,并且这些块可以通过拼接,最终组合成一个完整的消息,其最终效果与使用 invoke()得到的结果一致。

2.2.3 batch()

LangChain 中的批处理功能是一种通过并行处理多个独立请求来显著提升性能、降低成本的强大机制。批处理的核心思想是将多个独立的请求集合成一个批次,并行发送给模型处理。这与逐个顺序调用(invoke)相比,能大幅减少网络往返开销和等待时间,尤其适合处理问答、文本分类、情感分析等独立任务。

python 复制代码
from langchain.chat_models import init_chat_model

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

res = tongyi_llm.batch([
    "为什么鹦鹉的羽毛是彩色的?",
    "飞机是如何飞行的?",
    "什么是量子计算?"
])

for chunk in res:
    print(chunk.content)

batch()特点是等待所有 请求处理完毕,按原始输入顺序返回结果列表。当输入列表很大或单个模型调用耗时差异显著时,batch_as_completed()允许应用在收到第一个结果后立即开始后续处理,而不必等待最慢的那个请求,也就是说batch_as_completed() 每个请求完成后逐个返回结果,结果可能乱序,但包含索引信息。

python 复制代码
····
responses = tongyi_llm.batch_as_completed([
    "为什么鹦鹉的羽毛是彩色的?",
    "飞机是如何飞行的?",
    "什么是量子计算?"
])
for chunk in res:
    print(chunk) # chunk 是一个元组,包含索引和输出
    # chunk 包含结果及其在原始列表中的索引
    print(f"第 {chunk[0]} 个问题回答完毕: {chunk[1].content}")

此外,当需要处理大量输入时,为了避免对模型服务造成过大压力或触发速率限制,可以通过 RunnableConfig字典中的 max_concurrency参数来控制最大并行数。

使用示例如下:

python 复制代码
# 限制最多同时进行5个并发调用
model.batch(
    large_list_of_inputs,
    config={
        'max_concurrency': 5  # 限制最大并发数为5
    }
)
2.2.4 异步调用

在LangChain框架中,异步方法(ainvoke、astream、abatch)与它们的同步版本(invoke、stream、batch)相比,具备如下特点:

  • 避免阻塞主线程:同步调用会阻塞程序执行,而异步方法让应用程序在等待API响应时保持响应性。
  • 优化资源利用:异步操作可以更高效地利用系统资源,减少空闲等待时间
python 复制代码
import asyncio
import time
from langchain.chat_models import init_chat_model


# 初始化模型
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)


async def demo_async_invoke():
    """演示单个异步调用的非阻塞特性"""
    print("=== 演示:ainvoke 的异步(非阻塞)效果 ===")

    print("程序开始...")

    # 1. 发起一个异步请求,但不等待它完成
    print(">>> 发起异步模型调用 (ainvoke)...")
    async_task = llm.ainvoke("用一句话解释人工智能。")

    # 2. 在等待模型响应的同时,主程序可以继续执行其他任务
    print(">>> 模型请求已发送,程序无需等待,继续执行...")
    for i in range(3):
        # 等待1s
        time.sleep(1)
        print(f">>> 正在执行第{i + 1}个任务... ")

    # 3. 现在,我们需要模型的结果了,所以用 await 等待它完成
    print(">>> 其他任务已完成,现在等待模型返回结果...")
    response = await async_task  # 此时才开始等待

    print(f">>> 模型返回: {response.content}")


async def demo_async_stream():
    """演示异步调用的非阻塞特性"""
    print("=== 演示:astream 的异步(非阻塞)效果 ===")

    print("程序开始...")

    # 1. 发起异步流式请求,但不立即处理结果
    print(">>> 发起异步流式调用 (astream)...")
    stream_resp = llm.astream("请一句话解释机器学习的基本概念。")

    # 2. 在等待流式响应的同时,执行其他任务
    print(">>> 流式请求已发送,程序无需等待,继续执行...")
    for i in range(3):
        # 等待1s
        time.sleep(1)
        print(f">>> 正在执行第{i + 1}个任务... ")

    # 3. 现在开始处理流式结果
    print(">>> 其他任务已完成,开始处理流式结果...")

    print(">>> 流式输出: ", end="", flush=True)
    async for chunk in stream_resp:
        if hasattr(chunk, 'content'):
            print(chunk.content, end="", flush=True)
    print(">>> 流式输出结束\n")

async def demo_async_batch():
    """演示单个异步调用的非阻塞特性"""
    print("=== 演示:abatch 的异步(非阻塞)效果 ===")

    print("程序开始...")

    # 准备批量输入(即使是单个输入,也用列表形式)
    questions = ["用一句话说明深度学习与传统机器学习的区别"]

    # 1. 发起异步批量请求
    print(">>> 发起异步批量调用 (abatch)...")
    batch_resp = llm.abatch(questions)

    # 2. 在等待批量处理的同时,执行其他任务
    print(">>> 批量请求已发送,程序无需等待,继续执行...")
    for i in range(3):
        # 等待1s
        time.sleep(1)
        print(f">>> 正在执行第{i + 1}个任务... ")

    # 3. 等待批量处理结果
    print(">>> 其他任务已完成,现在等待批量处理结果...")
    responses = await batch_resp

    for response in responses:
        print(f">>> 批量响应: {response.content}")

async def main():
    """主函数"""
    await demo_async_invoke()
    await demo_async_stream()
    await demo_async_batch()


if __name__ == "__main__":
    asyncio.run(main())

2.3 结构化输出

调用模型时,我们可以设置结构化输出来约束大模型输出结果,使其符合预定义的数据结构(如 JSON、Pydantic 模型或字典),而不是任意的自然文本。确保模型生成的结果能够被程序精准解析,并无缝集成到下游系统(如数据库、API 或前端展示逻辑)中,从而显著提升应用的可靠性和自动化程度。

2.3.1 定义输出结构的模式

在 LangChain 中,可以使用如下三种方式定义期望的输出结构:Pydantic 模型、TypedDict、JSON Schema,调用大模型时,三种方式都可通过with_structured_output方法来返回结构化结果。

2.3.1.1 Pydantic模型

Pydantic 模型是使用 Python 的 Pydantic 库定义强类型数据模型,支持复杂嵌套结构。适合需要严格数据验证和复杂结构的场景,如生成 API 响应。

Pydanitc是一个基于 Python 类型注解的库,它通过在运行时强制执行类型提示,确保数据的正确性和一致性,定义这种类型时需要创建一个继承自BaseModel的类,使用类型提示(如 str, int)和 Field函数来声明每个字段的名称、类型、默认值和描述。使用方式参考:https://docs.pydantic.dev/latest/concepts/models/#basic-model-usage。

(1)简单结构

python 复制代码
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

class Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")
    director: str = Field(description="导演")
    rating: float = Field(description="评分(10分制)")

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 设置模型结构化输出
model_with_structure = tongyi_llm.with_structured_output(Movie)

# 调用模型并获取结构化输出
res: Movie = model_with_structure.invoke("给我介绍下电影《星际穿越》")

print(type(res))
print(res)

输出:

python 复制代码
<class '__main__.Movie'>
title='星际穿越(Interstellar)' year=2014 director='克里斯托弗·诺兰' rating=9.3

注意:定义类型中字段的description 可以不写,但是强烈建议写上,这样可以帮助模型理解该字段应该存入什么样的数据。

(2)嵌套结构

python 复制代码
from typing import List

from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 1. 定义嵌套的 Pydantic 模型
class Actor(BaseModel):
    name: str = Field(description="演员姓名")
    role: str = Field(description="饰演的角色")

class Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")
    director: str = Field(description="导演")
    cast: List[Actor] = Field(description="演员列表")  # 定义列表字段
    rating: float = Field(description="评分")


# 2. 初始化模型并绑定输出结构
structured_model = tongyi_llm.with_structured_output(Movie)

# 3. 调用模型,直接获取 Movie 实例
res = structured_model.invoke("请介绍电影《盗梦空间》")

# 4. 访问嵌套数据
print(f"电影名: {res.title}")
print(f"上映年份: {res.year}")
print(f"导演: {res.director}")
print(f"演员列表: {res.cast}")
print(f"评分: {res.rating}")

输出:

python 复制代码
电影名: 盗梦空间(Inception)
上映年份: 2010
导演: 克里斯托弗·诺兰
演员列表: [Actor(name='莱昂纳多·迪卡普里奥', role='科布(Cobb)------前CIA特工,顶尖'盗梦者',擅长潜入他人梦境窃取机密;因被诬陷谋杀妻子玛尔而流亡海外,渴望重返美国与孩子团聚。'), Actor(name='约瑟夫·高登-莱维特', role='亚瑟(Arthur)------科布的长期搭档与'筑梦师',理性缜密、行动力强,负责构建稳定梦境层级与后勤协调。'), Actor(name='艾伦·佩吉(现为埃利奥特·佩吉)', role='阿里阿德涅(Ariadne)------天才建筑系毕业生,被科布招募为新任'筑梦师';她不仅设计梦境空间,更成为揭示科布内心创伤的关键人物。'), Actor(name='汤姆·哈迪', role='伊姆斯(Eames)------'伪造者',擅于在梦中变形伪装,提供情报与战术支援,幽默犀利,是团队中的'变色龙'。'), Actor(name='渡边谦', role='斋藤(Saito)------日本能源巨头,委托科布执行'植入想法'(inception)任务;其真实身份与时间感知的异常成为影片哲学内核的重要伏笔。'), Actor(name='玛丽昂·歌迪亚', role='玛尔(Mal)------科布已故妻子,以'投影'形式反复侵入梦境,象征未化解的愧疚与执念,是全片最危险的心理障碍。')]
评分: 9.3
2.3.1.2 TypedDict

TypedDict 是 Python 3.8+ 引入的一种类型提示工具,它允许为字典对象定义固定的键名和对应的值类型。TypeDict是使用 Python 的 TypedDict来定义带有类型提示的字典结构,无需额外依赖,适合需要快速定义字典结构且无需 Pydantic 重量级功能的场景。

(1)简单结构

python 复制代码
from langchain.chat_models import init_chat_model
from typing_extensions import TypedDict, Annotated

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

class MovieTypedDict(TypedDict):
    title: Annotated[str, "电影的正式名称,例如《盗梦空间》"]
    year: Annotated[int, "电影的公映年份,使用四位数字表示"]
    director: Annotated[str, "电影导演的全名"]
    rating: Annotated[float, "电影在10分制下的评分,可包含一位小数"]

# 设置模型结构化输出
model_with_structure  = tongyi_llm.with_structured_output(MovieTypedDict)

# 调用模型并获取结构化输出
resp:MovieTypedDict = model_with_structure.invoke("给我介绍下电影《星际穿越》")

print(type(resp))
print(resp)

输出:

python 复制代码
<class 'dict'>
{'director': '克里斯托弗·诺兰', 'rating': 9.3, 'title': '《星际穿越》', 'year': 2014}

(2)嵌套结构

python 复制代码
from typing import List

from langchain.chat_models import init_chat_model
from typing_extensions import TypedDict, Annotated

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 使用TypedDict定义嵌套结构
class Actor(TypedDict):
    name: Annotated[str, "演员姓名"]
    role: Annotated[str, "饰演的角色"]

class Movie(TypedDict):
    title: Annotated[str, "电影标题"]
    year: Annotated[int, "上映年份"]
    director: Annotated[str, "导演"]
    cast: Annotated[List[Actor], "演员列表"]  # 嵌套列表定义
    rating: Annotated[float, "评分"]

# 设置模型结构化输出
model_with_structure = tongyi_llm.with_structured_output(Movie)

# 调用模型并获取结构化输出
resp: Movie = model_with_structure.invoke("给我介绍下电影《盗梦空间》")

# 访问嵌套数据
print(f"电影名: {resp['title']}")
print(f"上映年份: {resp['year']}")
print(f"导演: {resp['director']}")
print(f"演员列表:{resp['cast']}")
print(f"评分: {resp['rating']}")

输出:

python 复制代码
电影名: 盗梦空间
上映年份: 2010
导演: 克里斯托弗·诺兰
演员列表:[{'name': '莱昂纳多·迪卡普里奥', 'role': '多姆·柯布'}, {'name': '约瑟夫·高登-莱维特', 'role': '亚瑟'}, {'name': '艾伦·佩姬', 'role': '阿里阿德涅'}, {'name': '汤姆·哈迪', 'role': '伊姆斯'}, {'name': '渡边谦', 'role': '斋藤'}, {'name': '玛丽昂·歌迪亚', 'role': '梅尔'}]
评分: 8.8

不同模型可能输出不一致,也可能演员列表部分数据过多导致错误。

2.3.1.3 JSON Schema

(1)简单结构

JSON Schema是提供一个标准的 JSON Schema 字典来定义结构。适合需要与多种编程语言交互或进行复杂数据约束定义的场景。

python 复制代码
from langchain.chat_models import init_chat_model

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 使用 JSON Schema(最灵活,跨语言友好)
json_schema = {
    "title": "MovieInfo",
    "description": "包含电影标题、上映年份、导演和评分的电影对象",
    "type": "object",
    "properties": {
        "title": {"type": "string", "description": "电影标题"},
        "year": {"type": "integer", "description": "上映年份"},
        "director": {"type": "string", "description": "导演"},
        "rating": {"type": "number", "description": "评分(10分制)"}
    },
    "required": ["title", "year", "director", "rating"]
}

# 设置模型结构化输出
model_with_structure = tongyi_llm.with_structured_output(json_schema)

# 调用模型并获取结构化输出
resp = model_with_structure.invoke("给我介绍下电影《星际穿越》")

print(type(resp))
print(resp)

输出:

python 复制代码
<class 'dict'>
{'director': '克里斯托弗·诺兰', 'rating': 9.3, 'title': '星际穿越', 'year': 2014}

注意以上代码中定义json_schema的时候指定的title, description, type, properties, required是是遵循 JSON Schema 规范的标准关键字,是固定写法。几个关键字的解释如下:

title:为整个 Schema 或特定属性提供一个人类可读的标题,不能是中文,用于提高可读性。

description:提供更详细的文字描述,说明 Schema 或属性的用途等,和 title一样,旨在帮助理解。

type:定义当前数据节点必须是什么数据类型。常见类型有 string, number, integer, boolean, object, array, null。object即是json对象。

properties:用于定义JSON 对象(Object)中可以包含哪些属性(键),以及每个属性对应的值类型和说明。

required:当 type为 "object"时使用,是一个数组,列出了对象中必须存在的属性名。

(2)嵌套结构

python 复制代码
from langchain.chat_models import init_chat_model

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

"""
使用 JSON Schema 定义嵌套结构
"""
# 1. 定义嵌套的 JSON Schema
project_schema = {
    "title": "MovieInfo",
    "description": "包含电影标题、上映年份、导演、演员和评分的电影对象",
    "type": "object",
    "properties": {
        "title": {"type": "string", "description": "电影标题"},
        "year": {"type": "integer", "description": "上映年份"},
        "director": {"type": "string", "description": "导演"},
        "cast": {  # 定义嵌套数组
            "type": "array",
            "description": "演员列表",
            "items": {
                "type": "object",
                "properties": {
                    "name": {"type": "string", "description": "演员姓名"},
                    "role": {"type": "string", "description": "演员角色"}
                },
                "required": ["name", "role"]
            }
        },
        "rating": {"type": "number", "description": "评分(10分制)"}
    },
    "required": ["title", "year", "director", "cast", "rating"]
}

# 绑定 JSON Schema 到模型
structured_model = tongyi_llm.with_structured_output(project_schema)

# 调用模型
response = structured_model.invoke("生成一个关于《星际穿越》的电影信息,包含导演、演员、评分")
print(response)

输出:

python 复制代码
{'cast': [{'name': '马修·麦康纳', 'role': '库珀'}, {'name': '安妮·海瑟薇', 'role': '布兰德博士'}, {'name': '杰西卡·查斯坦', 'role': '成年墨菲'}, {'name': '迈克尔·凯恩', 'role': '布兰德教授'}], 'director': '克里斯托弗·诺兰', 'rating': 9.3, 'title': '星际穿越', 'year': 2014}
2.3.2 获取结构化输出结果

以上定义输出结构的三种模式中,我们都是通过调用"with_structured_output"来获取结构化输出结果,除了这种方式外,还可以通过使用输出解释器来获取结构化输出结果。下面介绍这两种获取结构化结果的方式。

2.3.2.1 with_structured_output

这种方式是最新、最简洁的API,直接让模型"理解"你需要的数据结构,并返回解析好的对象。但是一些国内大模型不支持这种方式,例如:deepseek-reasoner不支持该方式,那么就考虑其他两种方式。

此外,我们可以在with_structured_output方法中传入include_raw=True参数同时获取原始的 AI 消息对象,从而访问令牌用量等元数据,操作如下:

python 复制代码
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)


class Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")
    director: str = Field(description="导演")
    rating: float = Field(description="评分(10分制)")

# 设置模型结构化输出
model_with_structure = tongyi_llm.with_structured_output(Movie,include_raw=True)

# 调用模型并获取结构化输出
resp: Movie = model_with_structure.invoke("给我介绍下电影《星际穿越》")

print(type(resp))
print(resp)

输出:

python 复制代码
<class 'dict'>
{'raw': AIMessage(content='{\n  "director": "克里斯托弗·诺兰",\n  "rating": 9.3,\n  "title": "星际穿越(Interstellar)",\n  "year": 2014\n}', additional_kwargs={'parsed': Movie(title='星际穿越(Interstellar)', year=2014, director='克里斯托弗·诺兰', rating=9.3), 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 46, 'prompt_tokens': 18, 'total_tokens': 64, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'qwen-plus', 'system_fingerprint': None, 'id': 'chatcmpl-f52e9611-c539-968b-9ca2-32540f7aaa24', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019cf02a-ff75-79b3-905a-155ed79f8fe2-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 18, 'output_tokens': 46, 'total_tokens': 64, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), 'parsed': Movie(title='星际穿越(Interstellar)', year=2014, director='克里斯托弗·诺兰', rating=9.3), 'parsing_error': None}
2.3.2.2 输出解析器

LangChain 输出解析器(Output Parser),这种方法更传统,依赖于在提示词中明确指示模型输出特定格式的文本,然后使用解析器进行转换。其流程是:提示词指导 (引导生成指定类型)→ 模型生成文本 → 解析器转换。

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Field

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 1. 定义结构
class Movie(BaseModel):
    title: str = Field(description="电影标题")
    year: int = Field(description="上映年份")

# 2. 创建解析器和提示模板
parser = JsonOutputParser(pydantic_object=Movie)

prompt = ChatPromptTemplate.from_template("""
回答用户问题。
问题:{question}
你必须始终输出一个包含title(电影标题)和year(上映年份)的 JSON 对象。
""")

# 3. 创建链
chain = prompt | tongyi_llm | parser

# 4. 调用(返回字典)
response = chain.invoke({"question": "介绍电影《盗梦空间》"})
print(response)

输出:

python 复制代码
{'title': '盗梦空间', 'year': 2010}

此外输出解析器还有其他类型,表格如下:

解析器类型 作用 适用场景 核心特点
PydanticOutputParser 解析为 Pydantic 模型 需要结构化数据、强类型校验 类型安全、自动校验、支持复杂嵌套
JsonOutputParser 解析为 JSON 对象 通用 JSON 输出、简单结构 轻量、无强校验、直接返回 dict
StructuredOutputParser 按字段解析为字典 简单键值对、快速结构化 无需定义模型、提示词自带格式说明
CommaSeparatedListOutputParser 解析为逗号分隔列表 列表类结果(标签、关键词) 极简、专门处理列表
DatetimeOutputParser 解析为 datetime 对象 时间日期提取 自动转 Python 日期类型
EnumOutputParser 解析为枚举值 固定选项分类(情感、类型) 限定枚举、自动匹配
RetryOutputParser 解析失败自动重试 LLM 偶尔格式错乱 带重试、可指定重试解析器
FixOutputParser 修复破损 JSON / 格式 输出残缺、语法错误 自动修复、无需重新调用 LLM
XMLOutputParser 解析 XML 格式输出 偏好 XML 结构的场景 专门处理 XML 标签结构

chain = prompt | tongyi_llm | parser解释:

是 LangChain 的**表达式语法(LangChain Expression Language,LCEL)****,其中 |(管道符)的作用和 Linux 中的管道完全一致:把前一个组件的输出,作为后一个组件的输入 ,按从左到右的顺序将 prompt(提示词模板)、tongyi_llm(通义千问大模型实例)、parser(输出解析器)串联成一个可直接执行的完整 Chain。

2.4 工具调用

大模型工具调用(Tool Calling)可以扩展模型能力,使模型能够突破纯文本生成的限制,与外部系统和功能进行交互。在LangChain框架中,这一功能也被称为函数调用(Function Calling),两种术语可以互换使用。

2.4.1 模型调用工具步骤

如下是定义一个查询天气工具,工具名称为get_weather,具体工具创建可以参考Agent章节部分工具创建方式。

python 复制代码
from langchain_core.tools import tool

@tool
def get_weather(location: str) -> str:
    """获取指定位置的天气"""
    return f" {location}的天气是晴朗的。"

大模型使用工具前,需要通过bind_tools()方法将工具与模型绑定,让模型能够识别可用工具,绑定后,模型会根据输入内容自动判断是否需要调用工具:

python 复制代码
from langchain.chat_models import init_chat_model

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 绑定工具
model_with_tools = tongyi_llm.bind_tools([get_weather])

# 查询触发工具调用
response = model_with_tools.invoke("北京今天天气如何?")

print(type(response))
response.pretty_print()

输出:

python 复制代码
<class 'langchain_core.messages.ai.AIMessage'>
================================== Ai Message ==================================
Tool Calls:
  get_weather (call_5ba29ccbc05e40a4a71a27)
 Call ID: call_5ba29ccbc05e40a4a71a27
  Args:
    location: 北京

以上代码运行后response对象是一个AIMessage,大模型根据用户提问如果调用了工具,该对象输出内容中只有调用工具的参数请求信息。如果真正要大模型根据工具调用结果进行回复,完整的调用流程包括如下四个步骤:

  1. 模型绑定工具:通过model.bind_tools([...])绑定一个或者多个工具。
  2. 模型生成工具调用请求:用户输入问题,如果调用工具,模型返回工具调用信息(如工具名称和参数)。
  3. 开发者手动执行工具:用户从响应中提取工具调用信息并手动调用对应的工具。
  4. 将工具执行结果传递给模型生成最终结果:将之前用户提问内容和手动执行工具结果返回模型,模型最终生成回复。
python 复制代码
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langchain_core.messages import HumanMessage


@tool
def get_weather(location: str) -> str:
    """获取指定位置的天气"""
    return f" {location}的天气是晴朗的。"

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 1.模型绑定工具
model_with_tools = tongyi_llm.bind_tools([get_weather])

messages = []
human_message = HumanMessage(content="北京的天气")
# human_message = HumanMessage(content="海水为什么是咸的?")
messages.append(human_message)

# 2. 模型生成调用工具请求
response = model_with_tools.invoke(messages)
print("response", response)

messages = []
messages.append(response)

# 3.开发者根据模型的响应,调用工具并获取结果
for tool_call in response.tool_calls:
    if tool_call['name'] == 'get_weather':
        # 调用工具并获取结果
        tool_result = get_weather.invoke(tool_call)
        messages.append(tool_result)

# 4. 模型根据工具调用结果生成最终响应
final_response = model_with_tools.invoke(messages)
print("final_response", final_response)
print(final_response.content)
2.4.2 模型多工具调用

大模型调用工具是单次推理,即每次运行调用一个工具,当调用多个工具时,需要用户自己管理多次调用循环。

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 定义股票查询工具
@tool
def get_stock_price(company: str, timeframe: str = "today") -> str:
    """获取指定公司的股票价格信息

    Args:
        company: 公司名称(如:苹果公司, 微软公司, 谷歌公司)
        timeframe: 时间范围(today-今日, week-本周, month-本月)
    """
    # 模拟股票数据
    mock_data = {
        "苹果公司": {"today": 185.20, "week": 183.50, "month": 180.75},
        "微软公司": {"today": 415.86, "week": 412.30, "month": 405.42},
        "谷歌公司": {"today": 15.42, "week": 15.20, "month": 14.85}
    }

    if company in mock_data:
        price = mock_data[company].get(timeframe, "未知时间范围")
        return f"{company} {timeframe}价格: {price}美元"
    else:
        return f"未找到股票代码 {company} 的数据"


# 定义新闻搜索工具
@tool
def search_news(company: str) -> str:
    """搜索指定公司的财经新闻

    Args:
        company: 公司名称
    Return:
        公司的财经新闻,每个新闻占一行
    """
    # 模拟新闻数据
    mock_news = {
        "苹果公司": [
            "苹果发布新款iPhone,股价上涨3%",
            "苹果与欧盟达成反垄断和解协议",
            "苹果将在印度扩大生产规模"
        ],
        "微软公司": [
            "微软Azure云业务季度增长超预期",
            "微软完成对Nuance的收购",
            "微软推出新一代AI助手Copilot"
        ],
        "谷歌公司": [
            "谷歌发布新AI模型,性能提升20%",
            "谷歌与OpenAI合作,开发新的AI助手",
            "谷歌在欧洲展开AI研究项目"
        ]
    }

    news_list = mock_news.get(company, [f"未找到{company}的相关新闻"])
    return "\n".join(news_list)


# 1. 初始化模型并绑定工具
tools = [get_stock_price, search_news]
model_with_tools = tongyi_llm.bind_tools(tools)

message = []
human_message = HumanMessage(content="苹果公司今天的股价是多少?最近有什么新闻?")
# human_message = HumanMessage(content="比较一下微软和苹果的股价")
# human_message = HumanMessage(content="腾讯最近有什么重大新闻?")
# human_message = HumanMessage(content="海水为什么是咸的?")
message.append(human_message)

# 2. 工具调用
while True:
    response = model_with_tools.invoke(message)

    message.append(response)

    # 如果有调用工具,处理工具调用响应
    # 3.开发者根据模型的响应,调用工具并获取结果
    if response.tool_calls:
        for tool_call in response.tool_calls:
            if tool_call["name"] == "get_stock_price":
                stock_result = get_stock_price.invoke(tool_call)
                print("stock_result", stock_result)
                message.append(stock_result)
            if tool_call["name"] == "search_financial_news":
                news_result = search_news.invoke(tool_call)
                print("news_result", news_result)
                message.append(news_result)
    else:
        print("没有工具调用,直接返回答案")
        break

print("response", response)
print(response.content)

以上示例中,有两个工具get_stock_price(获取公司股票价格)、search_news(获取新闻),当用户提问"苹果公司今天的股价是多少?最近有什么新闻?"显然需要调用两个工具,那么第一次"model_with_tools.invoke(message)"调用执行会调用到"get_stock_price"工具(AIMessage对象中的tool_calls有工具调用),第二次"model_with_tools.invoke(message)"会调用到"search_news"工具(AIMessage对象中的tool_calls有工具调用),当不再调用工具时,返回的AIMessage对象中的tool_calls为空,所以我们使用一个While循环来自己管理工具多次调用循环,这样最终将所有消息交个大模型后,得到最终结果。

2.5 其他使用

2.5.1 Reasoning-模型推理

大模型的"推理"能力,指的是其处理复杂问题时,模拟人类思维过程,进行逻辑链式思考和多步骤分析,而不仅仅是依赖模式匹配或简单检索来给出答案。

如果底层模型支持推理(一般是"深度思考模型"或"推理模型",例如:deepseek-r1、openai o1/o3、qwen3-max等),我们可以在输出结果中显示推理过程。

2.5.2. Rate limiting-速率限制

大模型服务商(如OpenAI、Anthropic)通常会设定调用速率上限,例如每分钟或每秒最多允许一定数量的请求或处理的Token数量。如果短时间内发送过多请求,就会收到 429 Too Many Requests错误,导致后续请求失败。

LangChain 提供了 InMemoryRateLimiter对象用于限制调用大模型,实现在客户端主动控制请求发送的节奏,确保不会超过服务商的限制,调用大模型时通过rate_limiter参数指定该速率限制对象。此限制器是线程安全的,可以由同一进程中的多个线程共享。

InMemoryRateLimiter对象参数如下:

参数名 作用
requests_per_second 核心限制:每秒允许的最大请求数。例如:0.1表示平均每10秒才允许发送1个请求。
check_every_n_seconds 控制检查频率的时间粒度。例如:0.1表示每100毫秒检查一次是否可发送新请求。
max_bucket_size 允许的瞬时突发请求量。例如:10表示最多允许连续突发10个请求。

示例:

python 复制代码
# 初始化速率限制器
rate_limiter = InMemoryRateLimiter(
    requests_per_second=0.1,  # 每10s 允许1个请求
    check_every_n_seconds=0.1,  # 每100毫秒检查一次,控制更精细
    max_bucket_size=5,  # 允许短时间突发5个请求,应对流量小高峰
)

# 将限制器绑定到模型
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
    rate_limiter=rate_limiter,  # 关键步骤
)
2.5.3 Invocation Config-调用配置

在调用模型时(如使用 invoke(), ainvoke(), stream()等方法时),我们可以传入config参数(类型为 RunnableConfig)允许在运行时(即在调用模型时)动态地配置和控制模型的行为,而无需在初始化时就固定所有参数,这为应用带来了极大的灵活性和可维护性。

python 复制代码
tongyi_llm.invoke(
    "你好",
    config={
        "run_name": "...",      # 在LangSmith中这次运行会显示为指定名称
        "tags": ["tag1", "tag2"],           # 打上标签便于分类查找
        "metadata": {"user_id": "123"},     # 记录用户ID
        "callbacks": [custom_handler],      # 启用自定义回调函数
        "configurable":{
            "model": "qwen-plus",   # 配置模型参数
            "temperature": 0.7,             # 配置温度参数
            "max_tokens": 100               # 配置最大令牌数
        }
    }
)

config中支持配置的参数如下:

配置项 类型 描述
run_name str 为当前运行设置一个可读的名称。如在LangSmith追踪系统中快速定位和识别不同的运行任务。
tags List[str] 为运行设置标签,用于分类和过滤。如在LangSmith追踪系统中快速定位和识别不同的运行任务。
metadata Dict[str,Any] 附加任意的键值对元数据。记录本次调用的业务上下文,如{"user_id": "123", "session_id": "abc"}
callbacks List[BaseCallbackHandler] 设置回调处理器,在运行的不同阶段(开始、流输出、结束等)触发。与一些监控平台(如LangSmith)集成进行深度追踪和调试。
max_concurrency int 限制当前可运行对象的最大并发运行数。防止对API接口或本地资源造成过大压力,实现简单的速率限制。
recursion_limit int 限制运行时递归调用的最大深度。主要在复杂的工作流(如Agent执行多步工具调用)中,防止出现无限递归循环。
configurable Dict[Str,Any] 一个万能字典,用于传递其他可配置参数。实现更高级的动态行为,如配置可替代的模型或组件。

以上configurable中可配置的参数与init_chat_model初始化模型参数一样,与在初始化模型时设置的参数(如 temperature=0.7)的关键区别在于:

  • 初始化参数:是模型的默认设置,适用于该模型实例的大部分场景。
  • 运行时 config:是单次调用的特定设置,优先级更高,允许针对本次调用进行特殊调整。

3. Agent智能体

3.1 介绍

3.1.1 什么是Agent

在LangChain框架中,Agent(智能体)是一个高级组件,它通过将大型语言模型(LLM)与一系列外部工具(Tools)相结合,构建了一个能够自主推理并执行复杂任务的智能系统。其核心思想是利用LLM作为推理引擎(Reasoning Engine),让模型能够动态地决定为解决用户问题所需采取的行动序列,包括选择何种工具、以何种顺序调用,并迭代地处理工具返回的结果,直至任务完成。

简单来说,一个Agent不再仅仅是生成文本的模型,而是一个具备"思考-行动-观察"循环的自主工作者。当用户提出一个复杂需求时,Agent会像人类一样,先理解任务、规划步骤、使用合适的工具(如搜索网络、查询数据库、执行计算)获取信息,最后综合所有信息给出最终答案。

3.1.2 Agent原理与执行流程

Agent的核心工作原理遵循ReAct(Reasoning + Acting,推理+行动)框架,即在一个循环中交替进行推理和行动,这个过程会涉及到模型、工具、记忆、中间件等核心组件。

ReAct 以三步循环迭代,直到任务完成或达步数上限:

  1. Thought(思考 / 推理)

    • LLM 分析问题、上下文与当前状态,生成显式推理轨迹(如 "我需要查实时天气")
    • 决定下一步做什么、调用哪个工具、参数是什么
  2. Action(行动 / 执行)

    • 基于 Thought,调用外部工具:搜索、计算器、API、数据库、代码解释器等
    • 格式通常为:Action: 工具名[参数]
  3. Observation(观察 / 反馈)

    • 接收工具返回结果,作为新事实输入模型
    • 验证假设、补充信息、修正偏差,驱动下一轮 Thought
3.1.3. LLM、LLM工具调用与Agent区别
对比角度 LLM LLM+工具调用 Agent工具调用
本质定位 文本生成器,基于训练数据生成连贯文本 增强型LLM,通过函数调用扩展能力边界 一个具备规划和执行能力的智能系统,以任务闭环为目标
核心功能 语言理解、文本生成、知识问答 基础工具调用、实时数据获取、简单操作执行 多步规划、工具编排、状态管理、错误恢复
工作模式 单次交互,静态响应 单轮"请求-调用-响应" 多轮"感知-规划-执行-反馈"循环(ReAct框架)
系统架构 单一模型接口 模型+工具绑定+手动执行循环 LLM+规划+记忆+工具使用+防护措施
状态管理 需外部维护状态 需外部维护状态 内置记忆系统,支持短期/长期状态管理
错误处理 失败即终止,无自动恢复 失败即终止,无自动恢复 支持重试、回滚等恢复机制

3.2 Agent创建与调用

3.2.1 Agent创建方式

在 LangChain 1.2 中,create_agent API 是构建智能体的核心方式。它通过将语言模型与工具相结合,创建能够自主决策并执行复杂任务的系统。

模型是 Agent 的"大脑",负责决策和推理。LangChain 在模型配置上提供了极大的灵活性,支持静态模型动态模型两种方式,以适应不同的应用场景和成本考量。

3.2.1.1 静态模型

静态模型在 Agent 创建时一次性配置,在整个执行过程中保持不变。这是最简单、最常用的方法。

代码示例:使用创建好的模型构建Agent。

python 复制代码
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。"""
    return f"{city}的天气为晴朗,25°C。"

# 通过模型实例创建,可精细控制参数
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)
agent = create_agent(tongyi_llm, tools=[get_weather])

print(type(agent))  # CompiledStateGraph

resp = agent.invoke({"messages": [{"role": "user", "content": "查询北京的天气"}]})
print(type(resp))  # dict
print(resp)

输出:

python 复制代码
<class 'langgraph.graph.state.CompiledStateGraph'>
<class 'dict'>
{'messages': [HumanMessage(content='查询北京的天气', additional_kwargs={}, response_metadata={}, id='1162427b-c311-43b3-9fa9-a08b2b958c2b'), AIMessage(content='', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 19, 'prompt_tokens': 150, 'total_tokens': 169, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'qwen-plus', 'system_fingerprint': None, 'id': 'chatcmpl-ae692e25-ef49-971d-8250-c54f75b4de49', 'finish_reason': 'tool_calls', 'logprobs': None}, id='lc_run--019cf08b-890e-7242-842d-65bde8f85d4b-0', tool_calls=[{'name': 'get_weather', 'args': {'city': '北京'}, 'id': 'call_56eb813cfd9f410a80aa0c', 'type': 'tool_call'}], invalid_tool_calls=[], usage_metadata={'input_tokens': 150, 'output_tokens': 19, 'total_tokens': 169, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}}), ToolMessage(content='北京的天气为晴朗,25°C。', name='get_weather', id='aa417cc9-9d88-4413-b64f-ee26737e4bbf', tool_call_id='call_56eb813cfd9f410a80aa0c'), AIMessage(content='北京当前天气晴朗,气温为25°C。', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 12, 'prompt_tokens': 193, 'total_tokens': 205, 'completion_tokens_details': None, 'prompt_tokens_details': {'audio_tokens': None, 'cached_tokens': 0}}, 'model_provider': 'openai', 'model_name': 'qwen-plus', 'system_fingerprint': None, 'id': 'chatcmpl-b30228a5-9a34-9332-aa0b-9e888d86988b', 'finish_reason': 'stop', 'logprobs': None}, id='lc_run--019cf08b-8eb2-7f43-8cb7-916d577e00bf-0', tool_calls=[], invalid_tool_calls=[], usage_metadata={'input_tokens': 193, 'output_tokens': 12, 'total_tokens': 205, 'input_token_details': {'cache_read': 0}, 'output_token_details': {}})]}

新版langchain中(1.0+)创建智能体API做了统一,由 create_agent创建 agent,create_agent函数返回的本身就是一个具备执行能力的实体,这个实体内部基于 LangGraph 构建了一个执行图(Graph)。create_agent函数更多参数参考:Agents | LangChain 参考文档

3.2.1.2 动态模型

动态模型允许在运行时根据当前对话的状态或上下文智能地选择不同的模型,这对于优化成本和处理不同复杂度的任务非常有用,这是通过中间件机制实现的,要使用动态模型,请使用 @wrap_model_call 装饰器创建中间件,该中间件会修改请求中的模型。

代码示例:当对话消息数大于等于3条后,使用复杂模型,否则使用简单模型。

python 复制代码
from langchain.tools import tool

from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from langchain.chat_models import init_chat_model


# 定义两个模型:基础版和高级版
basic_model = init_chat_model(
    model="deepseek-chat",
    model_provider="deepseek",
    api_key=DEEPSEEK_API_KEY,
    base_url=DEEPSEEK_BASE_URL,
)

advanced_model = init_chat_model(
    model="qwen-plus",
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)

# 1. 定义动态模型选择中间件
@wrap_model_call
def dynamic_model_selection(request: ModelRequest, handler) -> ModelResponse:
    """根据对话消息数动态选择模型"""
    #获取当前对话内容
    # current_messages = request.state["messages"]
    # print(f"当前对话内容: {current_messages}")

    # 获取当前对话消息数
    message_count = len(request.state["messages"])

    print(f"当前对话消息数: {message_count}")

    if message_count >=3:
        # 对话消息数超过2条,切换至更强大的模型处理复杂对话
        model = advanced_model
    else:
        model = basic_model

    return handler(request.override(model=model))


# 2.定义工具
@tool
def get_current_location() -> str:
    """获取当前位置。"""
    return "当前位置为北京市。"

@tool
def get_weather(city: str) -> str:
    """获取指定城市的天气信息。"""
    return f"{city}的天气为晴朗,25°C。"

# 2. 创建Agent,并传入动态模型选择中间件
agent = create_agent(
    model=basic_model,  # 此处设置一个默认模型
    tools=[get_current_location,get_weather],  # 集成工具
    system_prompt="你是一个助手,可以帮助用户回答各种问题。",
    middleware=[dynamic_model_selection]  # 挂载中间件
)

# 模拟一个对话的调用,包含获取当前位置和天气信息
print(agent.invoke({"messages": [{"role": "user", "content": "获取当前位置,并告诉我天气情况"}]}))
  1. @wrap_model_call 标记的方法是模型选择中间件方法,create_agent时通过middleware参数指定该方法。
  2. @wrap_model_call方法中的两个参数含义:
  • request:ModelRequest,封装了当前模型调用的所有请求信息。
  • handler:回调函数,代表后续处理链,调用它会继续执行模型调用流程,通过 handler(request.override(...))传递修改后的请求。
  1. agent.invoke()启动了一个复杂的、可能包含多次LLM调用和工具调用的推理循环。wrap_model_call在这个循环的每个推理步骤(LLM调用)前都会被触发。
3.2.2 Agent调用

invoke是 Agent 最基本的同步调用方法,它会阻塞程序执行直到返回最终结果。

python 复制代码
agent = create_agent(tongyi_llm, tools=[get_weather])
resp = agent.invoke( {"messages": [{"role": "user", "content": "查询北京的天气"}]})

invoke中传入的 {"messages": [{"role": "user", "content": "查询北京的天气"}]}对应的是 invoke方法中的 input参数,这种写法在基于消息(Message)的LangChain Agent中是一种标准且常见的输入格式,其核心输入就是一系列消息(messages),每条消息通常包含 role(如 "user", "assistant", "system", "tool")和 "content"。

我们也可以在message列表的开头加system角色的消息来定义Agent的行为,代码如下:

python 复制代码
resp = agent.invoke( {
    "messages": [
        {"role": "system", "content": "你是一个天气查询助手,只回答天气相关的问题,其他问题请直接回答:我不清楚这问题答案。"},
        {"role": "user", "content": "100加上50等于多少?"}
    ]
})

3.3 提示词(Prompt)

在LangChain中,提示词为Agent提供了任务背景、行为准则和操作指南。通过 system_prompt参数设置,它本质上定义了Agent的"角色"和"使命"。

提示词设置有两种方式:基础设置和动态设置。

3.3.1 基础设置

提示词基础设置是在create_agent的时候传入一个参数system_prompt,这个参数可以是str或者SystemMessage类型。

python 复制代码
agent = create_agent(
        model=tongyi_llm,
        tools=[add_numbers],
        # system_prompt="你是一个数学助手,可以实现两数相加。"
        system_prompt = SystemMessage(content="你是一个数学助手,可以实现两数相加。")
    )
3.3.2 动态设置

对于需要根据运行时上下文或Agent状态调整提示词的高级场景,可以使用中间件Middleware实现动态提示词。例如,通过 @dynamic_prompt装饰器创建中间件,根据用户角色生成不同的系统提示。

python 复制代码
@dynamic_prompt
def dynamic_support_prompt(request: ModelRequest) -> str:
    """
    根据 query_type 生成不同的系统提示词。
    """
    # print("request:", request)
    query_type = request.runtime.context.get("query_type", "normal")
    base_instruction = "你是一名专业的电商客服助手。请根据工具查询结果,准确、清晰地回答用户问题。"

    if query_type == "vip":
        # 针对复杂或需要升级处理的问题
        return f"""{base_instruction}
                当前角色:高级支持专员
                工作要求:
                1.深度分析:仔细分析用户描述,识别潜在的根本问题。
                2.精准分类:将问题明确归类(如"物流问题"、"产品质量"、"售后申请")。
                3.方案规划:若工具能解决,提供具体步骤;若需人工,明确告知后续流程。
                请使用更专业、严谨的语言。
                """
    else:
        # 针对常规客服问题
        return f"""{base_instruction}
                当前角色:一线客服助手
                工作要求:
                1.快速响应:优先使用工具获取订单/政策信息。
                2.直接解答:对于明确问题(如退货、物流),直接给出基于知识的答案。
                3.简洁友好:回复要简单明了,避免复杂术语。
                保持友好和高效的沟通风格。
                """
    
agent = create_agent(
        model=deepseek_llm,
        tools=[query_order_info, search_faq], # 工具列表
        middleware=[dynamic_support_prompt],  # 挂载动态提示词中间件
        context_schema=AgentContext  # 关联上下文schema
    )    
  1. 针对普通用户和vip用户使用agent时采用不同的提示词,这里使用动态提示词,动态提示词能根据运行时上下文(如用户身份、问题类型)实时生成最合适的系统提示词,实现精准化、个性化回复。动态提示词通过create_agent时传入指定自己定义中间件函数。

  2. create_agentcontext_schema参数作用是定义运行时上下文有哪些数据,是一个TypedDict类型,这样可以指定执行单次任务时可能需要的动态数据(如用户ID,用户等级)等。定义上下文可使用哪些数据步骤如下:

首先使用TypedDict定义上下文的数据结构,如:

python 复制代码
class AgentContext(TypedDict):
    query_type: str  # 定义一个名为 query_type,类型为字符串的字段
    # user_id: int    # 可以继续扩展其他字段,例如用户ID

然后,在 create_agent时通过 context_schema参数告知Agent你所定义的上下文结构,最后在调用 agent.invoke时,通过 context参数传入符合该结构的实际数据:

python 复制代码
result = agent.invoke(
    {"messages": [...]},
    context={"query_type": "vip"}  # 这里传入的数据必须符合 AgentContext 的定义
)

3.4 工具(Tools)

在LangChain中,工具(Tools)是赋予大语言模型与外部世界交互能力的关键组件。它们实际上是具有明确定义输入和输出的可调用函数,模型能根据对话上下文决定何时调用工具以及传递哪些参数。工具让智能体能够获取实时数据、执行代码、查询外部数据库等,从而突破模型自身知识局限,构建功能强大的AI应用。

3.4.1 工具创建方式

创建工具有三种核心方式:使用@tool装饰器创建工具、使用Pydantic模型定义工具、使用Json Schema定义工具。下面进行介绍。

3.4.1.1 使用@tool装饰器定义工具

使用@tool装饰器定义工具是最直接的工具创建方法,能自动将普通 Python 函数转化为智能体可调用的工具。

python 复制代码
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

@tool("get_employee_info")
def get_employee_info(employee_id: str) -> str:
    """
    根据员工ID查询员工的详细信息,包括姓名、部门和职位。

    Args:
        employee_id (str): 员工的唯一标识ID,例如 'E001'

    Returns:
        str: 包含员工详细信息的JSON字符串
    """
    # 模拟一个简单的数据库
    mock_employee_database = {
        "E001": {"name": "张三", "department": "技术部", "position": "高级软件工程师", "email": "zhangsan@company.com"},
        "E002": {"name": "李四", "department": "市场部", "position": "市场经理", "email": "lisi@company.com"},
        "E003": {"name": "王五", "department": "人力资源部", "position": "招聘专员", "email": "wangwu@company.com"}
    }

    print(f"正在查询数据库,员工ID: {employee_id}")

    # 查询数据库
    employee_record = mock_employee_database.get(employee_id)

    if employee_record:
        # 返回格式化的员工信息
        return employee_record
    else:
        return f"未找到ID为 {employee_id} 的员工信息"

tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)


# 创建Agent
agent = create_agent(
    model=tongyi_llm,
    tools=[get_employee_info],
    system_prompt="你是一个助手,你可以查询员工的详细信息。",
)

resp = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下员工E001的详细信息"}]})

print("resp", resp)

print(resp["messages"][-1].content)
  1. @tool装饰器创建工具时默认的名称与函数名相同,也可以通过@tool("name")指定工具名称。
  2. @tool定义工具时,可以在函数内进行注释,可以使用python注释(无需参数和返回值说明),也可以使用"Google风格"对工具进行注解,即使用 Args:、Returns:、Raises:等关键字,这种方式可读性强。Agent通过工具的这些注释来理解工具的用途和调用时机,因此清晰、准确的文档字符串是工具能被正确调用的前提。
  3. 也可以在@tool定义工具时传入description参数,传入description参数后,它会完全替代函数的文档字符串成为工具的唯一描述。
python 复制代码
@tool("calculator", description="Performs arithmetic calculations. Use this for any math problems.")
def calc(expression: str) -> str:
    """Evaluate mathematical expressions."""
    return str(eval(expression))
3.4.1.2 使用Pydantic模型定义工具

Pydantic 模型模式的主要优势在于能够精确控制工具参数的格式和验证规则,让大模型更准确地理解如何调用工具。这种方式特别适合参数复杂、有特定约束条件的业务场景。

python 复制代码
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field, field_validator
from typing import Literal, Optional
from langchain_core.tools import tool
import json

# 1.定义复杂的工单查询参数模型
class TicketQueryInput(BaseModel):
    """工单查询输入参数 - 支持多种筛选条件"""
    ticket_id: Optional[str] = Field(
        default=None,
        description="工单ID"
    )
    assigner: Optional[str] = Field(
        default=None,
        description="负责人姓名"
    )
    status: Optional[Literal["open", "in_progress", "resolved", "closed"]] = Field(
        default=None,
        description="工单状态: open(待处理), in_progress(处理中), resolved(已解决), closed(已关闭)"
    )
    priority: Optional[Literal["low", "medium", "high", "urgent"]] = Field(
        default=None,
        description="优先级: low(低), medium(中), high(高), urgent(紧急)"
    )

    @field_validator("ticket_id")
    def convert_ticket_id_to_upper(cls, v: Optional[str]) -> Optional[str]:
        """将工单ID转换为大写"""
        return v.upper() if v else None


# 2. 使用@tool装饰器定义工具,并通过args_schema指定参数模型
@tool(args_schema=TicketQueryInput)
def query_tickets(
        ticket_id: Optional[str] = None,
        assigner: Optional[str] = None,
        status: Optional[str] = None,
        priority: Optional[str] = None,
) -> str:
    """
    查询工单系统,支持多条件筛选。

    此工具用于从工单管理系统中检索符合特定条件的工单信息。
    至少需要提供一个筛选条件,否则返回最近创建的工单。
    """
    try:
        # 模拟一个简单的工单数据库
        mock_tickets_db = [
            {"ticket_id": "TK2025012001", "assigner": "张三", "title": "登录页面加载缓慢", "status": "open", "priority": "low"},
            {"ticket_id": "TK2025012002", "assigner": "李四", "title": "用户头像上传失败", "status": "open", "priority": "medium"},
            {"ticket_id": "TK2025011901", "assigner": "张三", "title": "支付成功通知未发送", "status": "resolved", "priority": "high"},
            {"ticket_id": "TK2025011902", "assigner": "马六", "title": "订单查询接口返回空值", "status": "closed", "priority": "high"},
        ]

        filtered_tickets = mock_tickets_db

        if ticket_id:
            filtered_tickets = [t for t in filtered_tickets if t["ticket_id"] == ticket_id]

        if assigner:
            filtered_tickets = [t for t in filtered_tickets if t["assigner"] == assigner]

        if status:
            filtered_tickets = [t for t in filtered_tickets if t["status"] == status]

        if priority:
            filtered_tickets = [t for t in filtered_tickets if t["priority"] == priority]

        if not filtered_tickets:
            return "未找到符合条件的工单"

        # 格式化返回结果
        result = {
            "total_count": len(filtered_tickets),
            "tickets": filtered_tickets
        }

        return json.dumps(result, ensure_ascii=False, indent=2)

    except Exception as e:
        return f"查询工单时发生错误: {str(e)}"


# 3.创建智能体
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)
agent = create_agent(
    model=tongyi_llm,
    tools=[query_tickets],
    system_prompt="你是一个助手,你可以查询工单系统的工单信息。",
)

# 4. 测试智能体
# 4.1 测试基本功能
# response1 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下TK2025012001工单的详细信息"}]})
# print("response1 content", response1["messages"][-1].content)
#
# response2 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下张三负责的工单"}]})
# print("response2 content", response2["messages"][-1].content)
#
# response3 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下所有高优先级的工单"}]})
# print("response3 content", response3["messages"][-1].content)
#
# response4 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下所有关闭工单"}]})
# print("response4 content", response4["messages"][-1].content)


# 4.2 测试 Pydantic 参数验证
response5 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下tk2025012001工单的详细信息"}]})
print(response5)
print("response5 content", response5["messages"][-1].content)
  1. 这种方式定义工具,通过 @tool(args_schema=PydanticModelCls)将这个 Pydantic 模型与工具函数关联。
  2. PydanticModelCls 需要继承自 BaseModel的类,使用类型提示(如 str, int)和 Field函数来声明每个字段的名称、类型、默认值和描述。每个字段的 description参数至关重要,它直接影响大模型理解参数含义的能力。
  3. 可以利用 Pydantic 的类型系统进行参数验证,当大模型需要调用工具前,Pydantic 会自动验证参数的类型和有效性。
3.4.1.3 使用Json Schema定义工具

在 LangChain 中,还可以直接使用 JSON Schema 字典来定义工具的参数模式。这种方式提供了极大的灵活性,特别适合参数结构需要动态生成的场景。

python 复制代码
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
import json


# 1. 直接使用JSON Schema字典定义复杂的查询参数
book_query_schema = {
    "type": "object",
    "properties": {
        "title_keyword": {
            "type": "string",
            "description": "图书标题关键词,支持模糊匹配"
        },
        "author": {
            "type": "string",
            "description": "图书作者姓名"
        },
        "category": {
            "type": "string",
            "enum": ["技术", "文学", "历史", "科学", "经济学", "传记"],
            "description": "图书分类"
        }
    },
    "required": [], # 至少需要提供标题关键词、作者或分类中的一个条件,所以这里为空
}

# 2. 使用@tool装饰器定义工具,并通过args_schema指定JSON Schema
@tool(args_schema=book_query_schema)
def query_books(title_keyword: str = None,
                author: str = None,
                category: str = None) -> str:
    """
    根据多种条件查询企业图书库中的图书信息。

    此工具用于从企业图书管理系统中检索图书。
    至少需要提供标题关键词、作者或分类中的一个条件。
    """
    try:
        # 模拟一个简单的图书数据库
        mock_books_db = [
            {"book_id": "BK1001", "title": "人工智能导论", "author": "张明", "category": "技术"},
            {"book_id": "BK1002", "title": "机器学习实战", "author": "李华", "category": "技术"},
            {"book_id": "BK1003", "title": "中国近代史", "author": "王伟", "category": "历史"},
            {"book_id": "BK1004", "title": "红楼梦", "author": "曹雪芹", "category": "文学"},
            {"book_id": "BK1005", "title": "经济学原理", "author": "刘强", "category": "经济学"},
            {"book_id": "BK1006", "title": "文学导论", "author": "张明", "category": "文学"},
            {"book_id": "BK1007", "title": "Python编程基础", "author": "王丽", "category": "技术"}
        ]

        filtered_books = mock_books_db

        # 根据条件过滤图书
        if title_keyword:
            filtered_books = [book for book in filtered_books if title_keyword.lower() in book["title"].lower()]
        if author:
            filtered_books = [book for book in filtered_books if book["author"] == author]
        if category:
            filtered_books = [book for book in filtered_books if book["category"] == category]

        if not filtered_books:
            return "未找到符合条件的图书。"

        # 格式化返回结果
        result = {
            "total_count": len(filtered_books),
            "books": filtered_books
        }

        return json.dumps(result, ensure_ascii=False, indent=2)

    except Exception as e:
        return f"查询图书时发生错误: {str(e)}"


# 3. 创建智能体
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)
agent = create_agent(
    model=tongyi_llm,
    tools=[query_books],
    system_prompt="你是一个企业图书管理员,可以帮助员工查询图书信息。",
)

# 4. 测试智能体
# 测试1:按图书种类精确查询
response1 = agent.invoke({"messages": [{"role": "user", "content": "请帮我查一下历史类图书"}]})
print("=== 测试1:按图书种类精确查询 ===")
print(response1["messages"][-1].content)

# 测试2:多条件组合查询
response2 = agent.invoke({"messages": [{"role": "user", "content": "我想找张明写的技术类图书"}]})
print(response2)
print("\n=== 测试2:多条件组合查询 ===")
print(response2["messages"][-1].content)

# 测试3:关键词模糊查询
response3 = agent.invoke({"messages": [{"role": "user", "content": "搜索包含'Python'关键词的图书"}]})
print("\n=== 测试3:关键词模糊查询 ===")
print(response3["messages"][-1].content)
  1. Json Schema定义工具方式通过 @tool(args_schema=json_schema_dict)将一个符合 JSON Schema 标准的字典与工具函数关联。
  2. JSON Schema 是行业标准,用于描述 JSON 数据结构和验证规则。

JSON Schema结构:

(1) type:定义当前数据节点必须是什么数据类型。常见类型有 string, number, integer, boolean, object, array, null。object即是json对象。

(2) properties:用于定义JSON 对象(Object)中可以包含哪些属性(键),以及每个属性对应的值类型和说明。

(3) required:当 type为 "object"时使用,是一个数组,列出了对象中必须存在的属性名。本案例中指定为空表示json对象中的属性都是非必须。

3.4.2 调用工具错误处理

调用工具错误处理是指当智能体调用外部工具(如API、数据库查询等)遇到异常时,系统能够优雅地捕获、处理这些错误,并向大模型返回有意义的错误信息,而不是让未处理的异常直接中断整个流程。

当Agent调用工具异常时,我们可以使用@wrap_tool_call中间件来灵活处理调用工具发生的异常错误,如下:

python 复制代码
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_tool_call
from langchain.messages import ToolMessage


@wrap_tool_call
def handle_tool_errors(request, handler):
    """使用自定义消息处理工具执行错误"""
    try:
        return handler(request)
    except Exception as e:
        # 向模型返回自定义错误消息
        return ToolMessage(
            content=f"调用工具错误:请检查输入参数并重试. ({str(e)})",
            tool_call_id=request.tool_call["id"]
        )

agent = create_agent(
    model="gpt-4o",
    tools=[search, get_weather],
    middleware=[handle_tool_errors]
)

当工具调用失败时,Agent将返回带有自定义错误消息的ToolMessageToolMessage必须传递content(工具执行的结果或错误信息)和tool_call_id(所调工具唯一标识符,将此消息与引发此次调用的特定 AIMessage中的工具调用关联起来)两个核心参数。

以上中间件的执行时机如下:用户提问 → LLM分析并决定调用工具 → LangChain准备工具调用参数 → 错误处理中间件介入 → 实际工具执行 → 结果/错误返回。

所以@wrap_tool_call中间接执行流程如下:

  1. 当Agent需要调用工具时,执行流程会先经过所有注册的 wrap_tool_call中间件。
  2. 每个中间件都可以选择直接处理请求,或者将请求通过"handler(request)"传递给下一个处理程序,下一个处理程序就是执行工具。
  3. 最终到达实际的工具执行层,然后结果再逆序返回。

3.5 Agent结构化输出

结构化输出是LangChain Agent的核心功能之一,它允许Agent以特定、可预测的格式返回数据,而不是传统的自然语言响应。通过结构化输出,开发者可以直接获得JSON对象、Pydantic模型或数据类等结构化数据,这些数据能够被应用程序直接使用,无需复杂的解析过程。

3.5.1. 结构化输出支持的策略

LangChain的create_agent函数自动处理结构化输出的全过程。用户只需通过response_format参数设置期望的输出模式(Schema),当模型生成结构化数据时,系统会自动捕获、验证并将结果存储在Agent状态的structured_response键中。

python 复制代码
def create_agent(
    ...
    response_format: Union[
        ToolStrategy[StructuredResponseT],
        ProviderStrategy[StructuredResponseT],
        type[StructuredResponseT],
        None,
    ]

create_agent函数中的response_format参数是控制结构化输出的核心配置项,支持四种不同的策略设置方式:

1. ProviderStrategy[StructuredResponseT]

这种设置方式是使用模型提供商的原生结构化输出功能实现结构化输出,适用于支持原生结构化输出的模型。这里所说的"原生结构化输出"指的是大语言模型(LLM)提供商通过其API直接提供的、在模型响应阶段就强制保证输出格式符合预定规范的能力,这种能力能够在模型生成内容的源头确保结构化准确性。支持原生结构化输出的模型有OpenAI、Anthropic Claude或xAI Grok(中转key不支持)。这些模型构建的Agent结构化输出时,可以使用ProviderStrategy策略。

python 复制代码
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ProviderStrategy

class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str

agent = create_agent(
    model="gpt-4o",
    response_format=ProviderStrategy(ContactInfo)
)

2. ToolStrategy[StructuredResponseT]

对于不支持原生结构化输出的模型,LangChain采用ToolStrategy工具调用的方式实现结构化输出。此策略兼容绝大多数支持工具调用的现代模型,其核心原理是动态创建一个"虚拟工具",该工具的输入参数对应着期望的数据结构。

当模型需要生成最终答案时,系统会引导模型"调用"这个虚拟工具,从而间接产生符合要求的结构化数据。

python 复制代码
from pydantic import BaseModel
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy


class ContactInfo(BaseModel):
    name: str
    email: str
    phone: str

agent = create_agent(
    model="gpt-4o-mini",
    tools=[search_tool],
    response_format=ToolStrategy(ContactInfo)
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Extract contact info from: John Doe, john@example.com, (555) 123-4567"}]
})

result["structured_response"]
# ContactInfo(name='John Doe', email='john@example.com', phone='(555) 123-4567')

3. type[StructuredResponseT]

当直接传入一个定义类型时,LangChain会根据模型能力自动选择策略:如果模型支持原生结构化输出(如OpenAI、Anthropic Claude或xAI Grok),则优先使用ProviderStrategy;否则使用ToolStrategy。

特别注意:在LangChain 1.0及以上版本中,直接传递模式(如response_format=ContactInfo)不再支持,必须显式使用ToolStrategy或ProviderStrategy。(经过测试,目前langchain1.2版本还可使用)。

python 复制代码
from pydantic import BaseModel, Field
from langchain.agents import create_agent


class ContactInfo(BaseModel):
    """Contact information for a person."""
    name: str = Field(description="The name of the person")
    email: str = Field(description="The email address of the person")
    phone: str = Field(description="The phone number of the person")

agent = create_agent(
    model="gpt-5",
    response_format=ContactInfo  # Auto-selects ProviderStrategy
)

result = agent.invoke({
    "messages": [{"role": "user", "content": "Extract contact info from: John Doe, john@example.com, (555) 123-4567"}]
})

print(result["structured_response"])
# ContactInfo(name='John Doe', email='john@example.com', phone='(555) 123-4567')

4. None

默认配置,表示不以结构化输出,以自然语言响应用户问题。

综上所述,在实际大模型Agent开发场景中,如果使用到了结构化输出,推荐使用ToolStrategy策略,所以后续重点介绍这种策略方式结构化输出。

3.5.2 ToolStrategy

ToolStrategy通过工具调用(Tool Calling)实现结构化输出,适用于任何支持工具调用的现代模型。

ToolStrategy的配置包含三个主要参数:

python 复制代码
class ToolStrategy(Generic[SchemaT]):
    schema: type[SchemaT]
    tool_message_content: str | None
    handle_errors: Union[bool, str, type[Exception], tuple[type[Exception], ...], Callable[[Exception], str]]
  • schema(必需参数):与提供商策略的schema参数功能一致,支持Pydantic模型、数据类、TypedDict、JSON Schema,同时还支持联合类型(允许模型根据输入内容选择最匹配的数据结构)。
  • tool_message_content(可选参数):用于自定义生成结构化输出时,会话历史中记录的提示信息。默认使用展示输出数据的标准响应语句。
  • handle_errors(可选参数):用于指定数据校验失败时的重试策略,默认值为True。
3.5.2.1 四种结构化输出Schema

(1)Pydantic类型Schema(推荐)

Pydantic类型的Scheam支持数据验证,是优先推荐使用的方式

python 复制代码
from langchain.chat_models import init_chat_model
from langchain_core.messages import SystemMessage
from pydantic import BaseModel, Field, field_validator
from typing import Literal
from langchain.agents import create_agent
from langchain.agents.structured_output import ToolStrategy
from langchain.tools import tool


# 定义工具
@tool
def search_customer_database(query: str) -> str:
    """
    在客户数据库中搜索信息
    Args:
        query (str): 客户查询字符串,例如 "张三" 或 "李四"
    Returns:
        str: 客户记录字符串,包含客户姓名、等级、最近购买日期和累计消费
    """
    # 模拟数据库查询结果
    if "张三" in query.lower():
        return "客户记录:张三,VIP客户,最近购买日期:2024-01-15,累计消费:$15,000"
    elif "李四" in query.lower():
        return "客户记录:李四,普通客户,最近购买日期:2023-12-20,累计消费:$3,200"
    else:
        return f"关于客户{query},无记录"

@tool
def send_email(customer: str) -> str:
    """
    发送感谢邮件
    Args:
        customer (str): 客户名称,例如 "张三" 或 "李四"
    Returns:
        str: 确认消息,包含已发送的客户名称
    """
    return f"已向 {customer} 发送感谢邮件"


# 定义Pydantic Schema
class CustomerAnalysis(BaseModel):
    """客户分析报告"""
    customer_name: str = Field(None, description="客户姓名")
    customer_tier: Literal["潜在客户", "普通客户", "VIP客户", "流失风险"] = Field("潜在客户", description="客户等级,只能是潜在客户、普通客户、VIP客户或流失风险")
    recent_activity: str = Field(None, description="最近活动")
    spending_level: Literal["低", "中", "高"] = Field(None, description="消费水平")
    send_email: bool = Field(False, description="是否已发送感谢邮件")

    @field_validator('spending_level')
    def validate_spending(cls, v):
        if v not in ["低", "中", "高"]:
            raise ValueError('消费水平必须是"低"、"中"或"高"')
        return v


# 创建智能体
tongyi_llm = init_chat_model(
    model='qwen-plus',
    model_provider="openai",
    api_key=DASHSCOPE_API_KEY,
    base_url=DASHSCOPE_BASE_URL,
)
agent = create_agent(
    model=tongyi_llm,
    system_prompt=SystemMessage(content=""
                                        "请分析指定客户的情况:"
                                        "1. 先搜索客户数据库了解最新情况 "
                                        "2. 如果是VIP客户,则发送感谢邮件 "
                                        "3. 基于搜索结果生成结构化分析报告 "
                                        "4. 如果用户提问与客户记录无关或找不到客户信息,则返回空对象,不发送感谢邮件"
                                        ),
    tools=[search_customer_database, send_email],
    response_format=ToolStrategy(CustomerAnalysis)
)

# 执行分析
result = agent.invoke({
    "messages": [{"role": "user", "content": "请分析客户张三"}]
    # "messages": [{"role": "user","content": "请分析客户李四"}]
    # "messages": [{"role": "user","content": "请分析客户王五"}]
    # "messages": [{"role": "user","content": "今天天气如何"}]
})


# 处理结果
print("result:", result)
if "structured_response" in result:
    analysis = result["structured_response"]
    print(analysis)
  1. 如果是结构化输出,在系统提示词中最后提示结构化输出结果,如果提示词中先结构化输出结果(Agent已经执行完成),可能会导致一些工具不会再被调用。
  2. 系统提示词中最后加入"未找到用户"时的处理提示,避免程序一直调用工具尝试查找对应用户信息。

(2)Dataclass类型Schema

Dataclass是Python 3.7引入的一个装饰器,用于简化数据存储类的定义。

相同案例只需要将结构修改为CustomerAnalysis

python 复制代码
from dataclasses import dataclass, field

@dataclass
class CustomerAnalysis:
    """客户分析报告"""
    customer_name: Optional[str] = field(default=None, metadata={"description": "客户姓名"})
    customer_tier: Literal["潜在客户", "普通客户", "VIP客户", "流失风险"] = field(
        default="潜在客户",
        metadata={"description": "客户等级,只能是潜在客户、普通客户、VIP客户或流失风险"}
    )
    recent_activity: Optional[str] = field(default=None, metadata={"description": "最近活动"})
    spending_level: Optional[Literal["低", "中", "高"]] = field(default=None, metadata={"description": "消费水平"})
    send_email: bool = field(default=False, metadata={"description": "是否已发送感谢邮件"})

(3)TypedDict类型Schema

TypedDict 是 Python 3.8+ 引入的一种类型提示工具,它允许为字典对象定义固定的键名和对应的值类型,TypeDict是使用 Python 的 TypedDict来定义带有类型提示的字典结构,无需额外依赖,适合需要快速定义字典结构且无需 Pydantic 重量级功能的场景。

python 复制代码
from typing import Literal, Optional
from typing_extensions import TypedDict, Annotated

# 使用 TypedDict 定义客户分析报告 Schema
class CustomerAnalysis(TypedDict):
    """客户分析报告"""
    customer_name: Annotated[Optional[str], None, "客户姓名"]
    customer_tier: Annotated[Literal["潜在客户", "普通客户", "VIP客户", "流失风险"], "潜在客户", "客户等级"]
    recent_activity: Annotated[Optional[str], None, "最近活动"]
    spending_level: Annotated[Optional[Literal["低", "中", "高"]], None, "消费水平"]
    send_email: Annotated[bool, False, "是否已发送感谢邮件"]

(4)JsonSchema类型Schema

JSON Schema是提供一个标准的 JSON Schema 字典来定义结构。适合需要与多种编程语言交互或进行复杂数据约束定义的场景。

python 复制代码
# 定义 JSON Schema 替代 Pydantic 模型
customer_analysis_schema = {
    "title": "CustomerAnalysis",
    "type": "object",
    "description": "客户分析报告",
    "properties": {
        "customer_name": {
            "type": "string",
            "default": "",
            "description": "客户姓名"
        },
        "customer_tier": {
            "type": "string",
            "enum": ["潜在客户", "普通客户", "VIP客户", "流失风险"],
            "default": "潜在客户",
            "description": "客户等级"
        },
        "recent_activity": {
            "type": "string",
            "default": "",
            "description": "最近活动"
        },
        "spending_level": {
            "type": "string",
            "enum": ["低", "中", "高"],
            "default": "低",
            "description": "消费水平"
        },
        "send_email": {
            "type": "boolean",
            "default": False,
            "description": "是否已发送感谢邮件"
        }
    },
    # 所有字段都是必须输出的
    "required": ["customer_name", "customer_tier", "recent_activity", "spending_level"]
}
3.5.2.2 自定义工具消息

在LangChain中,ToolStrategy的 tool_message_content参数允许你自定义工具调用成功后,将指定的内容写入对话历史的提示信息,这样做的好处如下:

  1. 在最终用户可见的对话流中,使用更自然的消息替代原始数据。
  2. 用简短的确认信息替代可能很长的数据块,减少token消耗。

当不设置 tool_message_content时,模型收到的 ToolMessage里就包含了像 {'name': '张三', 'email': 'zhangsan@email.com'... ...}这样的具体数据,当设置 tool_message_content时,模型收到的 ToolMessage只是一个预定义的确认信息,如"格式化输出成功!"。这种方式节省了上下文窗口的令牌消耗,并且让对话流对最终用户更友好。

特别提示: 无论 tool_message_content如何设置,成功提取的结构化数据最终都会正确存入 result["structured_response"]返回,自定义消息仅影响对话历史中的一条记录。

python 复制代码
agent = create_agent(
    model= deepseek_llm,
    system_prompt=SystemMessage(content="你是一个专业的联系信息提取器,负责从文本中提取个人的姓名、电子邮箱和电话号码。"),
    response_format=ToolStrategy(
        ContactInfo,
        tool_message_content="联系信息提取完成!",
    )
)

for msg in result["messages"]:
    msg.pretty_print()
3.5.2.3 错误处理

LangChain 中 ToolStrategy的错误处理机制可以提升Agent生成结构化输出时的可靠性,通过智能重试自动处理模型输出中的常见错误,确保最终能得到符合预定格式的有效数据。

ToolStrategy通过其 handle_errors参数提供了结构化过程错误处理策略,以下是主要的几种方式及其用途:

策略 适用场景
handle_errors=True 默认方式,捕获所有异常,并使用LangChain 内置的、信息明确的错误消息模板提示模型重试。适用于大多数希望自动处理错误的通用场景。
handle_errors=False 关闭自动重试机制,任何异常都会直接抛出,会中断程序运行。
handle_errors="自定义字符串" 捕获所有异常,但使用开发者预设的固定字符串作为错误消息。适用于需要统一、友好的用户提示,或进行特定业务引导的场景。
handle_errors=ExceptionType 仅捕获指定类型(如ValueError)或元组中的异常类型并进行重试,其他异常直接抛出。适用于需要精准控制,只对特定错误进行重试的场景。
handle_errors=callable 灵活性最高的方式。使用开发者自定义的函数来处理异常,可根据不同的异常类型返回差异化的提示信息。适用于需要复杂、精细化错误处理的场景。

3.6 Agent异步调用

LangChain Agent 通过 agent.ainvoke()方法支持异步调用,其底层基于 Python 的 asyncio库。该机制的核心优势在于,当 Agent 执行过程中需要调用外部工具(如查询网络或访问数据库)时,异步版本不会阻塞以等待结果返回,而是会暂停当前任务,将 CPU 控制权交还给事件循环,以执行其他就绪的任务。待相应的 I/O 操作完成后,事件循环再调度该 Agent 继续执行。

这种基于事件循环的异步调度机制,能显著提升需要集成多个外部服务或API 的 Agent 的执行效率。例如,当一个旅行规划Agent需要同时查询航班、酒店和天气时,异步机制允许在等待这些外部API返回结果的期间,事件循环能够去执行程序中的其他就绪任务,从而避免CPU空闲等待,大幅提升整体效率。

python 复制代码
def create_travel_agent() -> CompiledStateGraph:
    """创建旅行规划智能体"""
    # 创建智能体
    agent = create_agent(
        model=deepseek_llm,
        tools=[get_weather, get_transport_info, get_scenic_spots],
        system_prompt="你是一个专业的旅行规划助手,能够帮助用户查询天气、交通和景点信息。拒绝回答与旅行规划无关的问题。",
    )
    return agent

async def async_sequential_query() -> Dict[str, Any]:
    # 创建智能体实例
    agent = create_travel_agent()

    # 异步调用智能体
    response = await agent.ainvoke({"messages": [
        {"role": "user", "content": "请帮我查询北京的天气信息,并推荐一些历史类型的景点"},
        {"role": "user", "content": "我要从北京出发到上海,请帮我查询上海的天气信息,并推荐一些现代类型的景点"},
        {"role": "user", "content": "天空为什么是蓝色的"},
    ]})

    return response


# 运行程序
if __name__ == "__main__":
   response = asyncio.run(async_sequential_query())
   print(response)
   print(response['messages'][-1].content)

3.7 Agent流式输出

LangChain 实现了一套强大的流式传输系统,用于实时显示 Agent 运行过程中的更新。流式传输通过渐进式显示输出(即使在完整响应准备好之前)来显著改善用户体验,特别是在处理 LLM 延迟时尤其有效。

相关推荐
四代机您发多少2 小时前
从零开始训练一个大模型
人工智能·pytorch·python·transformer
秦牛正威2 小时前
MacOS:Python `command not found` 问题修复记录
python
SmartBrain2 小时前
基于SpringAI架构的多智能体协作(进阶版)
人工智能·spring boot·python·spring cloud
智者知已应修善业2 小时前
【任何一个自然数m的立方均可写成m个连续奇数之和】2024-10-17
c语言·数据结构·c++·经验分享·笔记·算法
前端付豪2 小时前
AI知识库 + RAG数学解析增强
前端·python·llm
Eanve2 小时前
python搭建webrtc音视频服务端客户端
python·音视频·webrtc
七夜zippoe2 小时前
PostgreSQL高级特性在Python中的实战:JSONB、全文搜索、物化视图与分区表深度解析
数据库·python·postgresql·性能优化·分区表
郝学胜-神的一滴2 小时前
一序平衡,括号归真:单括号匹配算法的优雅美学
java·前端·数据结构·c++·python·算法