看了 3 天官方文档后,我决定自己写一篇 LangChain 人话教程

0 阅读20分钟

扫码_搜索联合传播样式-标准色版.png

一、文章摘要

本文解决什么问题?

很多同学在入门 LangChain 时都会遇到这些问题:

  • 模型初始化搞不定,官方文档看不懂
  • Agent 和 Tool 的关系理不清
  • 消息结构混乱,不知道怎么传参
  • 提示词写得烂,AI 输出不理想
  • 动态模型选择不会用,成本控制不了

为什么值得读?

这不是一篇翻译官方式的教程,而是我在真实项目中踩过坑、改过 Bug、熬过夜后总结出来的实战经验。我会用最通俗的语言,带你从零开始搭建一个完整的 LangChain Agent。

你能获得什么?

看完这篇文章,你将能够:

  1. 独立搭建一个 LangChain Agent
  2. 理解 Agent、Tool、Message 的核心概念
  3. 掌握生产级的代码写法(不是 Demo 级别)
  4. 避开我踩过的那些坑

二、开头

最近做 AI 项目时, 我遇到一个离谱问题:

明明代码跑起来了, 但 AI 回答像在"一本正经胡说八道"。

一开始我以为是模型太菜, 后来发现:

锅居然在工具定义上。

说实话,刚开始学 LangChain 时,我也和很多人一样:

看了官方文档 → 觉得懂了 → 写代码 → 报错 → 放弃 😅

但这次不一样。

我把所有踩过的坑、所有的调试过程、所有的最佳实践,全部整理成了这篇保姆级教程。

承诺:看完就能上手,复制就能运行。


三、问题背景

但实际开发中,我遇到了这些问题:

问题 1:模型初始化就卡住了

官方文档默认用的是 OpenAI,但我用的是通义千问。怎么配?base_url 写哪?model_provider 选什么?

翻了一下午文档,才搞明白 init_chat_model 的正确用法。

问题 2:Agent 和 Tool 的关系搞不清

什么是 Agent?什么是 Tool?它们怎么配合?

官方文档讲得太抽象,我看了三遍都没理解。

问题 3:消息结构一团糟

HumanMessageAIMessageSystemMessageToolMessage... 这么多类型,什么时候用哪个?怎么传参?

问题 4:工具定义报 TypeError

这是最离谱的一个坑

@tool
def get_weather(location: str) -> str:
    return f"今天{location}的天气是晴朗"

看起来没问题对吧?

结果运行时报错:

TypeError: got an unexpected keyword argument 'resource_url'

我排查了两小时,最后发现是 args_schema 参数的已知 Bug...

问题 5:提示词写得烂

一开始我是这样写的 System Prompt:

你是一个智能助手

结果 AI 回答得像机器人,完全没有灵魂。

后来我才明白,提示词工程是一门艺术


四、正文内容

第一章:环境准备与模型初始化

1.1 安装依赖

首先,我们需要安装 LangChain 和阿里模型的依赖:

# 安装 LangChain 核心库
uv add langchain

# 安装阿里通义千问的依赖
uv add dashscope

💡 小贴士:这里用的是 uv(Python 包管理器),比 pip 快 10 倍以上。如果你还在用 pip,建议换一下。

1.2 三种模型初始化方式

LangChain 提供了三种方式来初始化模型,我分别介绍一下:

方式一:使用 ChatTongyi(推荐新手)
from dotenv import load_dotenv
from langchain_community.chat_models.tongyi import ChatTongyi

load_dotenv()

# 直接初始化
model = ChatTongyi(model="qwen-turbo")

# 调用模型
response = model.invoke("你好")
print(response.content)

优点:简单直接,适合快速上手
缺点:灵活性不够,无法自定义参数

方式二:使用 langchain-qwq 包(官方推荐)
from dotenv import load_dotenv
from langchain_qwq import ChatQwen

load_dotenv()

model = ChatQwen(
    model="qwen-turbo",
    max_tokens=3000,
)

response = model.invoke("你好")
print(response.content)

# 流式输出
streamR = model.stream("给我写一个爱情诗")
for chunk in streamR:
    print(chunk.content, end="", flush=True)

优点:功能更强大,支持更多配置
缺点:需要额外安装包

方式三:使用 init_chat_model(通用方式,⭐ 推荐)
from dotenv import load_dotenv
from langchain.chat_models import init_chat_model

load_dotenv()

# 通过 OpenAI 兼容模式调用阿里百炼
model = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",  # 关键:指定为 openai 兼容模式
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

response = model.invoke("你好")

# 流式输出
for chunk in model.stream("给我写一个爱情诗"):
    print(chunk.content, end="", flush=True)

print(response.content)

优点

  • ✅ 统一的 API,支持多种模型
  • ✅ 可以灵活切换模型提供商
  • ✅ 生产环境推荐使用

关键点

  • model_provider="openai" 表示使用 OpenAI 兼容接口
  • base_url 必须指向阿里的兼容模式端点
  • 国内用户必须配置国内端点,否则会超时

1.3 使用模型实例创建 Agent(更精细控制)

有时候我们需要对模型参数进行更精细的控制,比如设置 temperature、timeout 等:

from langchain.agents import create_agent
from langchain_community.chat_models.tongyi import ChatTongyi

# 初始化模型实例,设置详细参数
model = ChatTongyi(
    model="qwen-turbo",
    temperature=0.1,      # 低温度,更确定性的输出
    max_tokens=1000,      # 限制最大输出长度
    timeout=30,           # 请求超时时间
    max_retries=3         # 失败重试次数
)

# 使用模型实例创建 Agent
agent = create_agent(model=model, tools=[])

这种方式的好处是:在创建 Agent 之前就能完全控制模型的行为

1.4 常用 kwargs 参数速查表

在实际项目中,我们经常需要调整模型参数。这里整理了一份最常用的参数表

参数类型常见值作用使用建议
temperaturefloat0~2控制回答随机性写代码 0~0.3,日常 0.7,创意 1+
max_tokensint500~4000限制最大输出长度回复被截断时调大
top_pfloat0~1控制采样范围一般默认 1 即可
frequency_penaltyfloat-2~2降低重复概率文案重复时调 0.3~0.8
presence_penaltyfloat-2~2鼓励生成新内容创意写作可适当增加
stoplist[str]["结束"]遇到指定字符停止Agent 中常用
seedint42固定随机结果调试时很有用
timeoutint30~300请求超时时间本地模型建议 120+
max_retriesint2~6失败重试次数网络差建议 6

⚠️ 踩坑提醒temperature 不是越大越好!写代码时建议设为 0~0.3,否则模型会"放飞自我"。


第二章:Agent 入门 - 让 AI 变成"智能体"

2.1 什么是 Agent?

说白了,Agent 就是一个能够自主决策的 AI 助手

普通 AI 模型:

用户提问 → 模型回答

Agent:

用户提问 → 理解意图 → 决策(是否需要调用工具)→ 执行工具 → 整合结果 → 回答

区别在于:Agent 可以主动调用外部工具,而不只是被动回答。

2.2 创建第一个 Agent

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model

# 初始化模型(必须手动指定,create_agent 默认用 OpenAI)
model_qwen_turbo = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 创建 Agent
agent = create_agent(model=model_qwen_turbo)

# 调用 Agent
response = agent.invoke({
    "messages": [{"role": "user", "content": "你是谁?"}]
})

# 返回结构
print(response)  # 完整的响应对象
print(response["messages"][-1].content)  # 只拿最后一句话

返回结构说明

{
    'messages': [
        HumanMessage(content='你是谁?', ...),
        AIMessage(content='我是通义千问...', ...)
    ]
}

2.3 流式输出(打字机效果)

在生产环境中,流式输出是标配。用户不想等 10 秒才看到结果。

方式一:使用 stream_mode="messages"
streamR = agent.stream(
    {"messages": [{"role": "user", "content": "给我写一个爱情诗"}]},
    stream_mode="messages",  # 指定流式模式
    version="v2",           # 使用 v2 版本格式
)

for chunk in streamR:
    if chunk["type"] == "messages":
        token, metadata = chunk["data"]
        if token.content_blocks:
            for block in token.content_blocks:
                if block.get("type") == "text":
                    print(block["text"], end="", flush=True)
方式二:更简洁的方式(⭐ 推荐)
for token, metadata in agent.stream(
    {"messages": [{"role": "user", "content": "月亮的首都是哪里?"}]},
    stream_mode="messages"
):
    if token.content:
        print(token.content, end="", flush=True)
方式三:使用 updates 模式(官方示例)
response = agent.stream(
    {
        "messages": [HumanMessage(content="北京的天气怎么样?")]
    },
    stream_mode="updates"
)

for chunk in response:
    if isinstance(chunk, dict):
        for node_name, state_update in chunk.items():
            if "messages" in state_update:
                last_msg = state_update["messages"][-1]
                if hasattr(last_msg, 'content') and last_msg.content:
                    print(last_msg.content, end="", flush=True)

💡 三种方式对比

  • messages 模式:适合需要精细化控制的场景
  • updates 模式:官方推荐,适合大多数场景
  • 选择哪种取决于你的需求

第三章:工具(Tools)- 让 Agent 拥有"超能力"

3.1 什么是 Tool?

Tool 就是 Agent 的外部能力扩展

比如:

  • 查天气 → 调用天气 API
  • 搜索信息 → 调用搜索引擎
  • 查数据库 → 执行 SQL 查询

没有 Tool 的 Agent,就像没有手的超人——只能动嘴,不能干活。

3.2 定义工具的三种方式

方式一:基础版(⚠️ 有坑)
from langchain.tools import tool

@tool("获取天气的工具", description="获取指定位置的天气")
def get_current_weather(location: str) -> str:
    return f"今天{location}的天气是晴朗,温度是25摄氏度"

# 构建 Agent
agent = create_agent(
    model=model_qwen_turbo,
    system_prompt="你是一个智能助手,擅长回答用户问题",
    tools=[get_current_weather],
)

# 测试
response = agent.stream(
    {
        "messages": [HumanMessage(content="北京的天气怎么样?")]
    },
    stream_mode="updates"
)

问题:这种写法虽然简单,但参数描述不够清晰,AI 可能会调用错误。

方式二:使用 Annotated + Field(⭐⭐⭐ 强烈推荐)
from typing import Annotated
from pydantic import Field
from langchain.tools import tool

@tool(
    name_or_callable="get_weather_tool",          # 自定义工具标识
    description="获取指定城市的实时天气状况,包括温度、湿度、风力等信息。当用户询问天气相关问题时调用此工具。",
    return_direct=False,                           # 天气信息可能需要进一步处理
    response_format="content",                      # 返回纯文本即可
    parse_docstring=True,                          # 启用文档解析
)
def get_current_weather(
    location: Annotated[str, Field(description="需要查询的城市名称,例如:北京、上海、广州")]
) -> str:
    """
    获取指定位置的实时天气数据
    
    根据城市名称查询当前天气状况,返回温度、天气现象、湿度等详细信息。
    
    Args:
        location: 目标城市名称,支持中文城市名
        
    Returns:
        格式化的天气信息字符串
        
    Example:
        >>> get_current_weather("北京")
        '北京今天: 晴朗, 温度25°C, 湿度45%, 微风'
    """
    # TODO: 接入真实天气API(如和风天气、OpenWeatherMap)
    return f"今天{location}的天气是晴朗,温度是25摄氏度"

优点

  • ✅ 参数描述清晰,AI 更容易正确调用
  • ✅ 自动校验参数,减少报错
  • ✅ 更适合复杂业务场景
  • ✅ 可维护性更强
方式三:使用 Pydantic Model 定义复杂参数(官方推荐)

当工具需要多个参数,且参数之间有依赖关系时,可以使用 Pydantic Model:

from pydantic import BaseModel, Field
from typing import Literal
from langchain.tools import tool

class WeatherInput(BaseModel):
    """天气查询输入参数"""
    location: str = Field(description="城市名称或坐标")
    units: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="温度单位偏好"
    )
    include_forecast: bool = Field(
        default=False,
        description="是否包含5天预报"
    )

@tool(args_schema=WeatherInput)
def get_weather(location: str, units: str = "celsius", include_forecast: bool = False) -> str:
    """获取当前天气和可选预报。"""
    temp = 22 if units == "celsius" else 72
    result = f"当前{location}的天气: {temp}度"
    if include_forecast:
        result += "\n未来5天: 晴朗"
    return result

# 创建带工具的 Agent
agent = create_agent(model_qwen_turbo, tools=[get_weather])

这种方式特别适合:

  • ✅ 参数较多的工具
  • ✅ 需要参数验证的场景
  • ✅ 参数有默认值或可选值的情况
方式四:使用预定义工具(Tavily 搜索)

LangChain 提供了很多现成的工具,比如搜索工具:

# 安装依赖
# uv add langchain-tavily

from langchain_tavily import TavilySearch

# 初始化搜索工具
tavily_search = TavilySearch(
    max_results=5,
    topic="general",
)

# 测试搜索
test_result = tavily_search.invoke({"query": "长沙未来7天的天气怎么样?"})
print(test_result)

注意

  • 需要在 Tavily 官网 获取 API Key
  • 环境变量必须配置 TAVILY_API_KEY
  • 适合需要实时信息的场景

3.3 @tool 装饰器参数速查表

参数类型默认值推荐场景重要程度
name_or_callablestr | Callable函数名自定义工具标识⭐⭐⭐⭐⭐
descriptionstrdocstring优化 LLM 理解⭐⭐⭐⭐⭐
return_directboolFalse终止性工具⭐⭐⭐⭐
args_schemaBaseModelNone复杂参数验证⭐⭐⭐⭐
infer_schemaboolTrue精确控制 schema⭐⭐⭐
response_formatLiteral"content"返回复杂数据⭐⭐⭐
parse_docstringboolFalse自动提取文档⭐⭐⭐

⚠️ 踩坑提醒:不要使用 configruntime 作为参数名!这两个是保留字段。

3.4 工具定义最佳实践(避坑指南)

根据官方文档和实战经验,总结以下最佳实践:

from langchain.tools import tool

@tool(parse_docstring=True)  # 启用 docstring 解析
def search_orders(
    user_id: str,
    status: str,
    limit: int = 10
) -> str:
    """搜索用户订单。
    
    当用户询问订单历史或想查看订单状态时使用此工具。
    始终按提供的状态进行过滤。
    
    Args:
        user_id: 用户的唯一标识符
        status: 订单状态:'pending'、'shipped' 或 'delivered'
        limit: 返回的最大结果数量
    """
    # 实现代码...
    pass

关键点

  1. 启用 parse_docstring=True:自动从 docstring 提取参数描述
  2. 写清楚工具用途:第一段说明什么时候调用这个工具
  3. 详细的 Args 说明:每个参数都要有清晰的描述
  4. 使用类型注解:帮助 LangChain 自动生成参数 schema

第四章:消息(Messages)- Agent 的"记忆系统"

4.1 消息类型一览

在 LangChain 中,消息是最基本的交互单位。常见的消息类型有:

类型Role用途示例
SystemMessagesystem设定角色和背景"你是一个诗人"
HumanMessageuser用户输入"写一首诗"
AIMessageassistantAI 输出"好的,这是一首诗..."
ToolMessagetool工具返回结果"今天北京晴天"

4.2 消息的基本结构

每条消息都包含以下内容:

{
    "role": "user",              # 角色
    "content": "你好",            # 内容(文本/图片/视频)
    "name": "张三",               # 发送者名称(可选)
    "id": "123456",              # 消息 ID(可选)
    "metadata": {},              # 元数据(时间戳等)
}

4.3 实战示例:构建多轮对话

from langchain.messages import SystemMessage, HumanMessage, AIMessage
from langchain.agents import create_agent

# 初始化模型
model_qwen_turbo = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)

# 构建消息列表
system_message = SystemMessage(content="你是一个诗人叫小礼拜,你会模仿诗仙李白风格的诗")
human_message_1 = HumanMessage(content="你叫什么名字?")
ai_message_1 = AIMessage(content="我是张三")
human_message_2 = HumanMessage(content="写一首表白的诗")

# 创建 Agent 并调用
agent = create_agent(model=model_qwen_turbo)

response = agent.invoke({
    "messages": [
        system_message,
        human_message_1,
        ai_message_1,
        human_message_2
    ]
})

# 打印结果(更友好的方式)
for message in response["messages"]:
    message.pretty_print()

# 获取最终回复
print(response["messages"][-1].content)

输出效果

================================ System Message =================================

你是一个诗人叫小礼拜,你会模仿诗仙李白风格的诗

================================ Human Message =================================

你叫什么名字?

================================== Ai Message ===================================

我是张三

================================== Ai Message ===================================

张三啊,张三,你来得真好!  
我这诗仙小礼拜,最爱与人对酒当歌。  
...

4.4 使用字典格式创建消息(更简洁)

除了使用消息对象,还可以使用字典格式,更简洁直观:

# 使用字典格式
messages = [
    {"role": "system", "content": "你是一个 helpful 的助手"},
    {"role": "user", "content": "你好,请介绍一下自己"},
    {"role": "assistant", "content": "你好!我是一个 AI 助手..."},
    {"role": "user", "content": "你能做什么?"}
]

response = agent.invoke({"messages": messages})

两种方式等价,选择你喜欢的即可。

4.5 高级用法:多模态支持

现代大模型(如 GPT-4o、Qwen-VL)支持图片输入

human_message = HumanMessage(content=[
    {'type': 'text', 'text': '识别图片中的内容'},
    {'type': 'image', 'url': 'https://example.com/image.jpg'}
])

response = agent.invoke({
    "messages": [
        SystemMessage(content="你是一个图像识别专家"),
        human_message
    ]
})

适用场景

  • OCR 文字识别
  • 图像内容分析
  • 身份证/护照识别
  • 医疗影像诊断

第五章:提示词工程(Prompt Engineering)

5.1 什么是提示词工程?

说白了,就是通过优化 SystemPrompt,让 AI 输出更符合预期的结果

好的提示词 = 身份 + 说明 + 示例 + 限制 + 格式

5.2 提示词的核心要素

system_prompt = """
# 身份
- 你是一个科幻作家,根据用户的要求创建一个太空之都。

# 说明
请详细描述这个城市的地理位置、建筑风格、科技特点、文化特色等。

# 示例
user:月球的首都是什么?
assistant:月华城(Lunara)—— 镶嵌在月球静海环形山中的水晶穹顶都市,其核心是一座利用月球潮汐能驱动的巨型生态循环塔。

user:火星的首都是什么?
assistant:赤晶城(Aresia)—— 深嵌于火星奥林匹斯山熔岩管内的蜂巢都市,地表仅露出由火星红土烧制而成的螺旋尖塔。

# 限制
- 不要超过 200 字
- 必须包含城市名称的中英文
- 要有科幻感

# 格式
请按照以下格式输出:
**城市名(英文名)** —— 详细描述
"""

5.3 动态提示词(Dynamic Prompt)

有时候我们需要根据运行时上下文动态生成 System Prompt,比如根据用户角色调整回答风格:

from typing import TypedDict
from langchain.agents import create_agent
from langchain.agents.middleware import dynamic_prompt, ModelRequest

class Context(TypedDict):
    user_role: str

@dynamic_prompt
def user_role_prompt(request: ModelRequest) -> str:
    """根据用户角色生成系统提示词"""
    user_role = request.runtime.context.get("user_role", "user")
    base_prompt = "你是一个 helpful 的助手。"
    
    if user_role == "expert":
        return f"{base_prompt} 提供详细的技术性回答,使用专业术语。"
    elif user_role == "beginner":
        return f"{base_prompt} 用简单易懂的方式解释概念,避免使用专业术语。"
    
    return base_prompt

# 创建 Agent,传入动态提示词中间件
agent = create_agent(
    model="gpt-5.4",
    tools=[web_search],
    middleware=[user_role_prompt],
    context_schema=Context
)

# 调用时传入上下文
result = agent.invoke(
    {"messages": [{"role": "user", "content": "解释一下机器学习"}]},
    context={"user_role": "expert"}
)

适用场景

  • 根据用户角色调整回答风格
  • 根据环境(开发/生产)调整安全策略
  • 根据用户偏好调整语言风格

5.4 结构化输出(Pydantic 模型)

有时候我们需要 AI 返回结构化数据,而不是纯文本:

from pydantic import BaseModel

class CapitalInfo(BaseModel):
    name: str       # 城市名称
    location: str   # 城市位置
    vibe: str       # 城市氛围
    economy: str    # 经济情况

agent = create_agent(
    model=model_qwen_turbo,
    system_prompt=SystemMessage(content="你是一个专业的导游"),
    response_format=CapitalInfo  # 设置结构化输出格式
)

response = agent.stream(
    {
        "messages": [HumanMessage(content="帮我介绍一下长沙")]
    },
    stream_mode="updates"
)

for chunk in response:
    if isinstance(chunk, dict):
        for node_name, state_update in chunk.items():
            if "messages" in state_update:
                last_msg = state_update["messages"][-1]
                if hasattr(last_msg, 'content') and last_msg.content:
                    print(last_msg.content, end="", flush=True)

输出效果

Returning structured response: 
name='长沙' 
location='长沙位于中国湖南省东部,湘江之滨,是湖南省的政治、经济、文化中心。' 
vibe='长沙是一座充满活力和魅力的城市,既有悠久的历史文化,又有现代化的城市风貌,是一个宜居宜业的地方。' 
economy='长沙是湖南省的省会,经济实力在中部地区处于领先地位,以制造业、电子信息、文化创意等产业为主。'

优点

  • ✅ 返回的数据可以直接用于程序逻辑
  • ✅ 字段含义明确,便于后续处理
  • ✅ 减少解析错误

第六章:动态模型选择 - 成本控制的艺术

6.1 为什么需要动态模型选择?

在实际项目中,我们会遇到这种情况:

  • 简单问题(如"你好"):用便宜快速的模型(如 qwen-turbo)
  • 复杂问题(如"帮我写一个排序算法"):用强大的模型(如 qwen-plus)

如果所有请求都用贵模型,成本会爆炸 💰

如果都用便宜模型,质量会下降 😅

所以,我们需要根据任务复杂度,自动选择合适的模型

6.2 实现动态模型路由(基于消息数量)

官方推荐的方式是使用 @wrap_model_call 装饰器:

from collections.abc import Callable
from langchain.agents.middleware import ModelRequest, ModelResponse, wrap_model_call
from langchain.chat_models import init_chat_model

# 初始化不同级别的模型
simple_model = init_chat_model("qwen-turbo")      # 便宜快速
standard_model = init_chat_model("qwen-plus")     # 标准模型
advanced_model = init_chat_model("qwen-max")      # 强大但贵

@wrap_model_call
def dynamic_model(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    """根据对话长度选择模型"""
    message_count = len(request.messages)
    
    if message_count > 20:
        # 长对话用高级模型(需要更强的上下文理解)
        model = advanced_model
    elif message_count > 10:
        # 中等对话用标准模型
        model = standard_model
    else:
        # 短对话用简单模型(省钱!)
        model = simple_model
    
    # 使用选择的模型处理请求
    return handler(request.override(model=model))

# 创建 Agent,传入动态模型中间件
agent = create_agent(
    model=simple_model,  # 默认模型
    tools=[get_weather],
    middleware=[dynamic_model]
)

6.3 基于对话复杂度的智能路由

除了消息数量,还可以根据其他因素选择模型:

@wrap_model_call
def smart_model_selection(request: ModelRequest, handler) -> ModelResponse:
    """智能模型选择策略"""
    messages = request.messages
    last_message = messages[-1]["content"] if messages else ""
    
    # 根据问题复杂度选择模型
    complex_keywords = ["算法", "代码", "编程", "优化", "架构"]
    is_complex = any(keyword in last_message for keyword in complex_keywords)
    
    if is_complex:
        model = advanced_model
        logger.info("检测到复杂问题,使用高级模型")
    elif len(messages) > 15:
        model = standard_model
        logger.info("对话较长,使用标准模型")
    else:
        model = simple_model
        logger.info("使用简单模型")
    
    return handler(request.override(model=model))

agent = create_agent(
    model=simple_model,
    tools=tools,
    middleware=[smart_model_selection]
)

6.4 成本监控与日志

建议加上成本监控,实时了解模型调用情况:

from loguru import logger
import sys

# 配置日志
logger.remove()
logger.add(sys.stdout, level="INFO")

@wrap_model_call
def model_with_cost_tracking(request: ModelRequest, handler) -> ModelResponse:
    """带成本追踪的模型选择"""
    message_count = len(request.messages)
    
    if message_count > 10:
        model = advanced_model
        model_name = "qwen-max (高级)"
        estimated_cost = 0.02 * message_count
    else:
        model = simple_model
        model_name = "qwen-turbo (经济)"
        estimated_cost = 0.002 * message_count
    
    logger.info(f"选择模型: {model_name}, 消息数: {message_count}, 预估成本: ${estimated_cost:.4f}")
    
    return handler(request.override(model=model))

第七章:常见问题修复

7.1 ❌ 流式输出解析错误

问题描述

for chunk in agent.stream(...):
    print(chunk)  # 输出乱七八糟的字典

原因

不同 stream_mode 的返回格式不同,需要正确解析

解决方案

参考第二章的 2.3 节,选择正确的解析方式。

7.2 ❌ 模型无法识别

问题描述

agent = create_agent(model="qwen-turbo")  # 直接写字符串

报错:

ValueError: Unknown model: qwen-turbo

原因

create_agent 默认只识别 OpenAI 的模型名称。对于其他模型,需要先初始化再传入

解决方案

# ❌ 错误写法
agent = create_agent(model="qwen-turbo")

# ✅ 正确写法
model = init_chat_model(
    model="qwen-turbo",
    model_provider="openai",
    base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
agent = create_agent(model=model)

7.3 ❌ 流式输出无反应

问题描述

for chunk in agent.stream(...):
    print(chunk)  # 什么都不输出

原因

不同 stream_mode 的返回格式不同,需要正确解析

解决方案

参考第二章的 2.3 节,选择正确的解析方式。

7.4 ❌ 工具调用参数错误

问题描述

AI 调用工具时传错参数,或者根本不调用工具。

原因

工具描述不够清晰,AI 不知道什么场景下该调用。

解决方案

  1. 使用 Annotated + Field 清晰定义参数
  2. description 中明确说明调用场景
  3. 启用 parse_docstring=True 自动提取文档

五、总结与最佳实践

📋 核心要点回顾

1️⃣ 模型初始化

  • ✅ 推荐使用 init_chat_model(统一 API)
  • ✅ 国内用户必须配置 base_url
  • ✅ 合理设置 temperaturemax_tokens 等参数
  • ✅ 需要精细控制时,先初始化模型实例再传入

2️⃣ Agent 创建

  • ✅ 使用 create_agent 创建智能体
  • ✅ 支持同步(invoke)和异步(stream)调用
  • ✅ 流式输出提升用户体验

3️⃣ 工具定义

  • ✅ 使用 @tool 装饰器定义工具
  • ✅ 推荐使用 Annotated + Field 定义参数(避免 TypeError)
  • ✅ 复杂参数使用 Pydantic Model(args_schema
  • ✅ 编写清晰的 description 帮助 AI 理解
  • ✅ 启用 parse_docstring=True 自动提取文档

4️⃣ 消息管理

  • ✅ 熟练使用 SystemMessageHumanMessageAIMessage
  • ✅ 支持字典格式创建消息(更简洁)
  • ✅ 支持多模态输入(文本 + 图片)
  • ✅ 合理使用 nameid 元数据

5️⃣ 提示词工程

  • ✅ 遵循「身份 + 说明 + 示例 + 限制 + 格式」原则
  • ✅ 使用 @dynamic_prompt 实现动态提示词
  • ✅ 使用 Pydantic 模型实现结构化输出
  • ✅ 提供 Few-shot 示例提升质量

6️⃣ 动态模型选择

  • ✅ 使用 @wrap_model_call 实现模型路由
  • ✅ 根据消息数量、问题复杂度选择模型
  • ✅ 平衡成本和质量
  • ✅ 添加成本监控和日志

📌 写文不易,Bug 更不易。

如果这篇文章对你有帮助,可以搜一搜:空门技术栈

这里分享:

  • ✅ Java / Spring AI / 企业级项目实战
  • ✅ Docker / RAG知识库 / 微服务踩坑
  • ✅ Python、前端、AI应用落地
  • ✅ 偶尔分享一些「头发保卫战」经验 😆

一个热爱技术、持续填坑的开发者, 陪你一起少踩坑,少加班,多写优雅代码。

📖 推荐阅读


💬 想要源码?

别光收藏不点赞,评论区喊一声「源码」,我私发你!