【LangChain1.0】第二篇 快速上手实战

第二篇 快速上手实战

目标 : 尽快构建出第一个可用的 Agent 系统
版本要求 : LangChain 1.0.7+、LangGraph 1.0.3+、Python 3.10+
更新日期: 2025-12


📌 前置知识说明

本篇将使用以下核心概念,如需深入理解请参考相关章节:

  • StateGraph : LangGraph的状态图,用于编排复杂流程 → 本篇仅使用基础功能,高级用法详见第三篇第7章
  • Runnable Protocol : 统一执行接口(invoke/stream/batch) → 已在第一篇第2章讲解
  • LCEL语法 : 管道操作符|和并行{} → 已在第一篇第2.2节讲解

💡 学习建议: 初学者可以先跟着本篇代码实践,遇到不理解的概念再回看相关章节。


第1章:Message 与 Tools 基础

1.1 Message 消息系统

1.1.1 消息类型:HumanMessage、AIMessage、SystemMessage、ToolMessage

LangChain 1.0 引入了统一的消息类型系统,用于表示人机对话中的不同角色和内容。

核心消息类型
BaseMessage
HumanMessage
AIMessage
SystemMessage
ToolMessage
FunctionMessage

1. HumanMessage - 用户消息

表示用户输入:

python 复制代码
from langchain_core.messages import HumanMessage

# 文本消息
message = HumanMessage(content="What is LangChain?")

# 带元数据
message = HumanMessage(
    content="Analyze this image",
    metadata={"user_id": "123"}
)

2. AIMessage - AI 响应

表示 AI 模型的回复:

python 复制代码
from langchain_core.messages import AIMessage

# 简单文本回复
response = AIMessage(content="LangChain is a framework...")

# 带工具调用(LangChain 1.0 格式)
response = AIMessage(
    content="",
    tool_calls=[
        {
            "id": "call_123",
            "name": "search",
            "args": {"query": "LangChain"}  # 直接是字典,不是 JSON 字符串
        }
    ]
)

3. SystemMessage - 系统消息

定义 AI 行为和角色:

python 复制代码
from langchain_core.messages import SystemMessage

system = SystemMessage(
    content="""You are a helpful AI assistant.
    Always be concise and accurate.
    Use tools when necessary."""
)

4. ToolMessage - 工具结果

表示工具执行的返回结果:

python 复制代码
from langchain_core.messages import ToolMessage

tool_result = ToolMessage(
    content="Search results: ...",
    tool_call_id="call_123"
)

消息对话流程
Tool AI System User Tool AI System User SystemMessage (角色定义) HumanMessage (用户问题) 决策:需要工具? AIMessage (tool_calls) ToolMessage (工具结果) AIMessage (最终回答)

完整对话示例

python 复制代码
from langchain_core.messages import (
    SystemMessage, HumanMessage, AIMessage, ToolMessage
)

# 构建完整的对话历史示例
# 这展示了一个典型的 Agent 对话流程:系统消息 → 用户问题 → AI 工具调用 → 工具结果 → AI 最终回答
conversation = [
    # 1. 系统消息:定义 AI 的角色和行为
    SystemMessage(content="You are a helpful assistant."),

    # 2. 用户消息:用户提出问题
    HumanMessage(content="What's the weather in Beijing?"),

    # 3. AI 消息:AI 决定调用工具
    AIMessage(
        content="",  # 工具调用时 content 为空
        tool_calls=[{
            "id": "call_123",  # 工具调用的唯一 ID
            "name": "get_weather",  # 工具名称
            "args": {"city": "Beijing"}  # 工具参数(字典格式)
        }]
    ),

    # 4. 工具消息:工具执行结果
    ToolMessage(
        content="Temperature: 20°C, Sunny",  # 工具返回的结果
        tool_call_id="call_123"  # 对应上面的工具调用 ID
    ),

    # 5. AI 消息:基于工具结果生成最终回答
    AIMessage(content="The weather in Beijing is 20°C and sunny.")
]

1.1.2 Content Blocks 核心创新(Text、Tool Use、Thinking、Image)

Content Blocks 是 LangChain 1.0 的重大创新,提供了跨 Provider 的统一内容表示。

为什么需要 Content Blocks?

在 LangChain 1.0 之前:

  • ❌ 不同模型的输出格式不一致
  • ❌ 无法统一处理工具调用、思考过程、多模态内容
  • ❌ Provider 切换需要重写代码

Content Blocks 解决方案
AIMessage
Content Blocks
Text Block
Tool Use Block
Thinking Block
Image Block
Citation Block

1. Text Block - 文本内容

最基础的文本内容:

python 复制代码
from langchain_core.messages import AIMessage

response = AIMessage(
    content=[
        {"type": "text", "text": "Here's the answer..."}
    ]
)

# 访问 content blocks
# 注意: content 是列表时直接迭代
for block in response.content:
    if block["type"] == "text":
        print(block["text"])

2. Tool Use Block - 工具调用

统一的工具调用格式:

python 复制代码
response = AIMessage(
    content=[
        {
            "type": "tool_use",
            "id": "call_123",
            "name": "search",
            "input": {"query": "LangChain"}
        }
    ]
)

# 提取工具调用
for block in response.content:
    if block["type"] == "tool_use":
        print(f"Tool: {block['name']}")
        print(f"Input: {block['input']}")

3. Thinking Block - 思考过程(Claude)

Claude 模型的思考过程(Extended Thinking):

python 复制代码
# Claude 返回的思考过程
response = AIMessage(
    content=[
        {
            "type": "thinking",
            "thinking": "Let me analyze this step by step..."
        },
        {
            "type": "text",
            "text": "Based on my analysis..."
        }
    ]
)

# 访问思考过程
for block in response.content:
    if block["type"] == "thinking":
        print(f"Thinking: {block['thinking']}")

4. Image Block - 图像内容

多模态图像支持:

python 复制代码
from langchain_core.messages import HumanMessage

# 发送图像
message = HumanMessage(
    content=[
        {"type": "text", "text": "What's in this image?"},
        {
            "type": "image_url",
            "image_url": {"url": "https://example.com/image.jpg"}
        }
    ]
)

5. Citation Block - 引用来源(Gemini)

Gemini 模型的引用来源:

python 复制代码
response = AIMessage(
    content=[
        {
            "type": "text",
            "text": "LangChain is a framework..."
        },
        {
            "type": "citation",
            "citation": {
                "source": "https://docs.langchain.com",
                "start_index": 0,
                "end_index": 50
            }
        }
    ]
)

1.1.3 跨 Provider 统一处理

统一的 content_blocks 接口

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic

# 1. 创建不同 Provider 的模型实例
# OpenAI 模型
openai_model = ChatOpenAI(model="gpt-4")
response1 = openai_model.invoke("Hello")

# Anthropic Claude 模型
claude_model = ChatAnthropic(model="claude-3-sonnet")
response2 = claude_model.invoke("Hello")

# 2. 统一访问方式 - 无需关心底层 Provider
# Content Blocks 提供了跨 Provider 的统一接口
for response in [response1, response2]:
    # 遍历 content blocks(可能是列表或字符串)
    if isinstance(response.content, str):
        # 简单文本响应
        print(response.content)
    else:
        # Content blocks 列表
        for block in response.content:
            if block["type"] == "text":
                print(block["text"])
            elif block["type"] == "thinking":
                # Claude 特有的思考过程
                print(f"[Thinking] {block['thinking']}")

Provider 差异自动处理
OpenAI
Anthropic
Google
User Input
Provider Type
OpenAI Format
Anthropic Format
Google Format
Unified Content Blocks
Application Code


1.1.4 多模态支持

图像输入

python 复制代码
from langchain_core.messages import HumanMessage
import base64

# 方式1:URL
message = HumanMessage(
    content=[
        {"type": "text", "text": "Describe this image"},
        {
            "type": "image_url",
            "image_url": {"url": "https://example.com/image.jpg"}
        }
    ]
)

# 方式2:Base64 编码
with open("image.jpg", "rb") as f:
    image_data = base64.b64encode(f.read()).decode()

message = HumanMessage(
    content=[
        {"type": "text", "text": "What's in this image?"},
        {
            "type": "image_url",
            "image_url": {
                "url": f"data:image/jpeg;base64,{image_data}"
            }
        }
    ]
)

音频处理

python 复制代码
# 音频输入(某些模型支持)
message = HumanMessage(
    content=[
        {
            "type": "audio_url",
            "audio_url": {"url": "https://example.com/audio.mp3"}
        }
    ]
)

混合内容

python 复制代码
from langchain_core.messages import HumanMessage
from langchain_openai import ChatOpenAI

# 1. 创建支持多模态的模型实例
model = ChatOpenAI(model="gpt-4o")  # GPT-4o 支持视觉输入

# 2. 构建混合内容消息:文本 + 图像
message = HumanMessage(
    content=[
        {"type": "text", "text": "Analyze this chart and search for related data"},
        {"type": "image_url", "image_url": {"url": "chart.png"}},
    ]
)

# 3. 调用模型
response = model.invoke(message)

# 4. 处理多模态响应
# response.content 可能包含多种类型的 content blocks
if isinstance(response.content, str):
    print(response.content)
else:
    for block in response.content:
        if block["type"] == "text":
            print(f"Text: {block['text']}")
        elif block["type"] == "tool_use":
            print(f"Tool Call: {block['name']}")

1.2 Tools 工具体系

1.2.1 工具定义方式:@tool、StructuredTool、BaseTool

方式 1:@tool 装饰器(推荐)

最简单快速的方式:

python 复制代码
from langchain_core.tools import tool

@tool
def search(query: str) -> str:
    """Search the web for information.

    Args:
        query: The search query string
    """
    # 实现搜索逻辑
    return f"Search results for: {query}"

# 自动提取:
# - 函数名 -> 工具名
# - 文档字符串 -> 工具描述
# - 参数类型注解 -> Schema

带复杂参数

python 复制代码
from typing import Optional
from pydantic import Field

@tool
def advanced_search(
    query: str = Field(description="搜索查询"),
    max_results: int = Field(default=10, description="最大结果数"),
    language: Optional[str] = Field(default="en", description="语言")
) -> str:
    """Advanced search with filters."""
    return f"Found {max_results} results for '{query}' in {language}"

方式 2:StructuredTool 类

更灵活的定义方式:

python 复制代码
from langchain_core.tools import StructuredTool
from pydantic import BaseModel

class SearchInput(BaseModel):
    query: str
    max_results: int = 10

def search_func(query: str, max_results: int = 10) -> str:
    return f"Found {max_results} results for {query}"

search_tool = StructuredTool.from_function(
    func=search_func,
    name="web_search",
    description="Search the web for information",
    args_schema=SearchInput
)

方式 3:BaseTool 继承

完全自定义控制:

python 复制代码
from langchain_core.tools import BaseTool
from typing import Optional, Type
from pydantic import BaseModel, Field

class SearchInput(BaseModel):
    query: str = Field(description="查询字符串")
    max_results: int = Field(default=10, description="最大结果数")

class CustomSearchTool(BaseTool):
    name = "custom_search"
    description = "自定义搜索工具"
    args_schema: Type[BaseModel] = SearchInput

    def _run(self, query: str, max_results: int = 10) -> str:
        """同步执行"""
        return f"Search: {query}, Max: {max_results}"

    async def _arun(self, query: str, max_results: int = 10) -> str:
        """异步执行"""
        return f"Async Search: {query}"

tool = CustomSearchTool()

三种方式对比

方式 适用场景 优点 缺点
@tool 简单工具 快速、简洁 灵活性低
StructuredTool 中等复杂度 平衡 需要额外类
BaseTool 复杂工具 完全控制 代码量多

1.2.2 参数 Schema(Pydantic)

基础 Schema 定义

python 复制代码
from pydantic import BaseModel, Field

class WeatherInput(BaseModel):
    """天气查询参数"""
    city: str = Field(description="城市名称")
    units: str = Field(
        default="celsius",
        description="温度单位",
        enum=["celsius", "fahrenheit"]
    )

@tool(args_schema=WeatherInput)
def get_weather(city: str, units: str = "celsius") -> str:
    """Get weather information for a city."""
    return f"Weather in {city}: 20°{units[0].upper()}"

复杂嵌套 Schema

python 复制代码
from typing import List, Optional

class Location(BaseModel):
    city: str
    country: str

class SearchFilters(BaseModel):
    locations: List[Location]
    date_range: Optional[tuple] = None
    categories: List[str] = Field(default_factory=list)

@tool(args_schema=SearchFilters)
def filtered_search(
    locations: List[Location],
    date_range: Optional[tuple] = None,
    categories: List[str] = []
) -> str:
    """Search with complex filters."""
    return f"Searching in {len(locations)} locations"

Schema 验证

python 复制代码
from pydantic import field_validator

class EmailInput(BaseModel):
    email: str = Field(description="邮箱地址")

    @field_validator('email')
    @classmethod
    def validate_email(cls, v):
        if '@' not in v:
            raise ValueError('Invalid email address')
        return v

@tool(args_schema=EmailInput)
def send_email(email: str) -> str:
    """Send an email."""
    return f"Email sent to {email}"

1.2.3 工具调用机制与并行调用

单工具调用流程
Tool LLM Agent Tool LLM Agent 用户问题 分析:需要工具? tool_calls 执行工具 工具结果 ToolMessage 最终答案

并行工具调用

LangChain 1.0 自动支持并行工具调用:

python 复制代码
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI

# 1. 定义多个工具
@tool
def get_weather(city: str) -> str:
    """Get weather for a city.

    Args:
        city: The name of the city
    """
    return f"Weather in {city}: Sunny"

@tool
def get_time(city: str) -> str:
    """Get current time in a city.

    Args:
        city: The name of the city
    """
    return f"Time in {city}: 10:00 AM"

# 2. 创建模型实例
model = ChatOpenAI(model="gpt-4")

# 3. 绑定工具到模型
tools = [get_weather, get_time]
model_with_tools = model.bind_tools(tools)

# 4. 调用模型(LLM 可能会并行调用多个工具)
response = model_with_tools.invoke("What's the weather and time in Beijing?")

# 5. 查看工具调用
# response.tool_calls 会包含多个工具调用(如果 LLM 决定并行调用)
print(f"工具调用数量: {len(response.tool_calls)}")
for tool_call in response.tool_calls:
    print(f"工具: {tool_call['name']}, 参数: {tool_call['args']}")

并行执行流程
LLM Decision
Tool Call 1: get_weather
Tool Call 2: get_time
Tool Call 3: search
Parallel Execution
Aggregate Results
Final Response


1.2.4 错误处理策略

基础错误处理

python 复制代码
from langchain_core.tools import tool

@tool
def divide(a: float, b: float) -> float:
    """Divide two numbers."""
    try:
        return a / b
    except ZeroDivisionError:
        return "Error: Division by zero"
    except Exception as e:
        return f"Error: {str(e)}"

返回结构化错误

python 复制代码
from typing import Union, Optional
from pydantic import BaseModel, Field
from langchain_core.tools import tool

# 定义结构化的工具结果类型
class ToolResult(BaseModel):
    """工具执行结果的结构化表示"""
    success: bool = Field(description="执行是否成功")
    data: Optional[Union[str, dict]] = Field(default=None, description="成功时的数据")
    error: Optional[str] = Field(default=None, description="失败时的错误信息")

@tool
def safe_search(query: str) -> str:
    """Safe search with error handling.

    Args:
        query: The search query string

    Returns:
        JSON string of ToolResult
    """
    try:
        # 执行搜索(示例:假设有一个 perform_search 函数)
        # results = perform_search(query)
        results = f"Search results for: {query}"  # 示例实现

        result = ToolResult(success=True, data=results)
        return result.model_dump_json()
    except Exception as e:
        result = ToolResult(success=False, error=str(e))
        return result.model_dump_json()

重试机制

python 复制代码
from tenacity import retry, stop_after_attempt, wait_exponential

@tool
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, max=10))
def reliable_api_call(endpoint: str) -> str:
    """API call with automatic retry."""
    # 会自动重试最多3次
    response = call_api(endpoint)
    return response

超时控制

python 复制代码
import asyncio

@tool
async def async_search(query: str) -> str:
    """Search with timeout."""
    try:
        result = await asyncio.wait_for(
            search_async(query),
            timeout=5.0  # 5秒超时
        )
        return result
    except asyncio.TimeoutError:
        return "Search timeout after 5 seconds"

Agent 级别错误处理最佳实践

在实际生产环境中,Agent 调用可能会遇到各种错误,需要完善的错误处理机制:

python 复制代码
from langchain_core.exceptions import OutputParserException
from langchain_core.runnables import RunnableConfig
import time
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def safe_agent_invoke(agent, input_data: dict, max_retries: int = 3):
    """带重试和错误处理的 Agent 调用

    Args:
        agent: LangGraph Agent 实例
        input_data: 输入数据字典,如 {"messages": [HumanMessage(...)]}
        max_retries: 最大重试次数

    Returns:
        Agent 输出结果或错误信息
    """
    for attempt in range(max_retries):
        try:
            # 调用 Agent
            result = agent.invoke(input_data)
            logger.info(f"Agent 调用成功 (尝试 {attempt + 1}/{max_retries})")
            return result

        except OutputParserException as e:
            # 输出解析错误(LLM 返回格式不正确)
            logger.error(f"输出解析失败: {e}")
            if attempt < max_retries - 1:
                logger.info("尝试使用降级提示词重试...")
                # 可以在这里调整提示词,要求 LLM 输出更简单的格式
                continue
            return {
                "error": "response_parse_failed",
                "message": "AI 返回格式无法解析,请重试"
            }

        except Exception as e:
            error_type = type(e).__name__

            # 速率限制错误 - 使用指数退避重试
            if "RateLimitError" in error_type or "rate_limit" in str(e).lower():
                if attempt < max_retries - 1:
                    wait_time = 2 ** attempt  # 指数退避: 1s, 2s, 4s
                    logger.warning(f"遇到速率限制,等待 {wait_time}秒后重试...")
                    time.sleep(wait_time)
                    continue
                return {
                    "error": "rate_limit_exceeded",
                    "message": "API 调用频率超限,请稍后重试"
                }

            # API 密钥错误 - 不重试
            elif "authentication" in str(e).lower() or "api_key" in str(e).lower():
                logger.error(f"认证失败: {e}")
                return {
                    "error": "authentication_failed",
                    "message": "API 密钥配置错误,请检查环境变量"
                }

            # 网络错误 - 重试
            elif "connection" in str(e).lower() or "timeout" in str(e).lower():
                if attempt < max_retries - 1:
                    logger.warning(f"网络错误,重试中... ({e})")
                    time.sleep(1)
                    continue
                return {
                    "error": "network_error",
                    "message": "网络连接失败,请检查网络状态"
                }

            # 其他未知错误
            else:
                logger.error(f"未知错误 ({error_type}): {e}")
                if attempt < max_retries - 1:
                    continue
                return {
                    "error": "unknown_error",
                    "message": f"发生未知错误: {str(e)[:100]}"
                }

    return {
        "error": "max_retries_exceeded",
        "message": f"达到最大重试次数 ({max_retries})"
    }

# 使用示例
from langchain_core.messages import HumanMessage

result = safe_agent_invoke(
    agent=my_agent,
    input_data={"messages": [HumanMessage(content="帮我查询天气")]},
    max_retries=3
)

# 检查结果
if isinstance(result, dict) and "error" in result:
    print(f"❌ 调用失败: {result['message']}")
else:
    print(f"✅ 调用成功: {result}")

错误处理最佳实践总结

错误类型 是否重试 策略
速率限制 ✅ 是 指数退避重试 (1s → 2s → 4s)
网络超时 ✅ 是 固定间隔重试 (1s)
输出解析失败 ✅ 是 调整提示词后重试
API 密钥错误 ❌ 否 立即返回,提示用户检查配置
参数验证错误 ❌ 否 立即返回,提示用户修正输入

💡 提示 : 更多生产级错误处理、监控告警、故障恢复策略,详见 第十篇《生产实践与监控评估》


本章小结

Message 系统核心要点

  • ✅ 统一消息类型:HumanMessage、AIMessage、SystemMessage、ToolMessage
  • ✅ Content Blocks:跨 Provider 的统一内容表示
  • ✅ 多模态支持:文本、图像、音频混合处理

Tools 工具体系核心要点

  • ✅ 三种定义方式:@tool、StructuredTool、BaseTool
  • ✅ Pydantic Schema:类型安全的参数定义
  • ✅ 并行调用:自动优化工具执行
  • ✅ 错误处理:重试、超时、结构化错误

设计哲学

统一的消息格式 + 类型安全的工具定义 = 可靠的 Agent 系统


思考与练习

  1. 练习 1:消息类型

    构建一个完整的对话流程,包含 SystemMessage、HumanMessage、AIMessage(带工具调用)、ToolMessage。

  2. 练习 2:工具定义

    使用三种方式分别定义一个计算器工具,对比代码量和灵活性。

  3. 练习 3:Content Blocks

    编写代码提取 AIMessage 中的所有 text block 和 tool_use block。

  4. 练习 4:错误处理

    实现一个带重试和超时的网络请求工具。

  5. 思考题:

    • Content Blocks 相比传统字符串 content 有什么优势?
    • 什么场景下应该使用 BaseTool 而不是 @tool?
    • 并行工具调用在什么情况下会失效?

第2章:create_agent 快速构建

2.1 Agent 基本概念

2.1.1 Agent 定义与执行循环

什么是 Agent?

Agent 是一个可以自主决策、调用工具、迭代求解的 AI 系统。

核心特征

  • 🤖 自主性:根据环境动态决策
  • 🔧 工具使用:调用外部工具获取信息
  • 🔄 迭代执行:多轮推理直到解决问题
  • 🎯 目标导向:朝着目标持续行动



用户问题
Agent
需要工具?
调用工具
直接回答
获取结果
返回答案

Agent 执行循环

python 复制代码
# Agent 执行循环伪代码
while not solved:
    # 1. 思考:分析当前情况
    thought = model.think(current_state)

    # 2. 决策:选择行动
    if need_more_info:
        # 调用工具
        action = select_tool(available_tools)
        result = execute_tool(action)
        current_state.update(result)
    else:
        # 给出答案
        final_answer = model.generate(current_state)
        break

return final_answer

执行流程图
Tools LLM Agent User Tools LLM Agent User alt [需要工具] [得到答案] loop [迭代循环] 输入问题 当前状态 + 历史 决策:工具调用 / 最终答案 执行工具 工具结果 返回最终答案


2.1.2 Agent 与 Chain 的本质区别

Chain(链)- 预定义流程

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

# 1. 定义组件
prompt = ChatPromptTemplate.from_template("Tell me a joke about {topic}")
model = ChatOpenAI(model="gpt-4")
parser = StrOutputParser()

# 2. Chain: 固定的线性流程(使用 LCEL 管道操作符)
chain = prompt | model | parser

# 3. 执行(流程固定:prompt → model → parser)
result = chain.invoke({"topic": "programming"})
print(result)  # 直接输出笑话文本

特点

  • ✅ 流程固定,可预测
  • ✅ 性能稳定
  • ❌ 缺乏灵活性
  • ❌ 无法动态调整

Agent(代理)- 动态决策

python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

# 1. 定义工具
@tool
def search(query: str) -> str:
    """Search for information."""
    return f"Results for: {query}"

# 2. 创建模型和工具列表
model = ChatOpenAI(model="gpt-4")
tools = [search]

# 3. Agent: 动态决策流程
# create_agent 会自动构建一个可以决策、调用工具、迭代执行的 Agent
agent = create_agent(model, tools)

# 4. 执行(Agent 内部会自动循环:思考 → 决策 → 执行工具 → 再思考...)
result = agent.invoke({"messages": [("user", "Search for LangChain documentation")]})

# 5. 获取最终结果
print(result["messages"][-1].content)

特点

  • ✅ 动态决策,灵活
  • ✅ 可以使用工具
  • ❌ 行为不可预测
  • ❌ Token 消耗可能较高

对比表

维度 Chain Agent
执行流程 固定线性 动态循环
决策能力 无决策 LLM 自主决策
工具使用 无(或固定) 动态选择工具
适用场景 简单、确定性任务 复杂、需要推理的任务
Token 消耗 可预测 不可预测
可靠性 中(依赖 LLM)





任务
是否需要推理?
使用 Chain
是否需要工具?
使用 Agent


2.1.3 适用场景分析

使用 Agent 的场景

信息检索 :需要搜索、查询数据库

多步骤推理 :需要分解任务、逐步求解

动态决策 :根据中间结果调整策略

工具组合:需要组合使用多个工具

示例场景

  • 📊 数据分析:查询数据库 → 分析 → 生成报告
  • 🔍 研究助手:搜索 → 总结 → 引用来源
  • 💼 客服机器人:查询订单 → 检查库存 → 推荐解决方案

不适合使用 Agent 的场景

简单转换 :文本翻译、摘要(用 Chain)

确定性流程 :已知步骤的工作流(用 LangGraph)

低延迟要求 :实时响应(Chain 更快)

成本敏感:Token 预算有限


2.2 核心参数详解

2.2.1 model、tools、system_prompt

model - 选择 LLM

python 复制代码
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic

# OpenAI
model = ChatOpenAI(model="gpt-4", temperature=0)

# Anthropic
model = ChatAnthropic(model="claude-sonnet-4-5-20250929", temperature=0)

模型选择建议

  • GPT-4:推理能力强,适合复杂任务
  • GPT-3.5-Turbo:速度快,成本低,适合简单任务
  • Claude 3 Sonnet:平衡性能和成本
  • Claude 3 Opus:最强推理能力

tools - 定义工具列表

python 复制代码
from langchain_core.tools import tool

@tool
def search(query: str) -> str:
    """Search the web."""
    return f"Results for: {query}"

@tool
def calculate(expression: str) -> str:
    """Calculate mathematical expression."""
    return str(eval(expression))

# 工具列表
tools = [search, calculate]

system_prompt - 定义 Agent 行为

python 复制代码
system_prompt = """You are a helpful research assistant.

Your capabilities:
- Search the web for information
- Perform calculations
- Provide accurate citations

Guidelines:
1. Always verify information before answering
2. Use tools when necessary
3. Be concise and accurate
4. Cite your sources
"""

提示词设计原则

  • ✅ 明确角色和能力
  • ✅ 提供清晰的指导
  • ✅ 说明工具使用场景
  • ✅ 定义输出格式要求

2.2.2 进阶配置:Middleware 简介

核心问题create_agent 已经很好用了,为什么还需要 Middleware?

真实场景

  1. 📝 对话太长,Token 超限 → 需要自动摘要历史消息
  2. 🔒 用户输入包含敏感信息 → 需要脱敏后再发送给 LLM
  3. 🔄 工具调用失败 → 需要自动重试
  4. 💰 成本控制 → 简单问题用便宜模型,复杂问题用高级模型

Middleware 是什么?

Middleware 是 LangChain 1.0 引入的核心机制,用于在 Agent 执行过程中注入自定义逻辑和控制流程。

常见 Middleware 类型

Middleware 功能 使用场景
SummarizationMiddleware 自动摘要长对话 Token 超限风险
PIIMiddleware 敏感信息脱敏 处理用户隐私数据
ToolRetryMiddleware 工具调用重试 提高工具调用可靠性
LLMToolSelectorMiddleware 动态筛选工具 工具太多导致混乱

💡 深入学习 :Middleware 的完整用法、Hook 体系、自定义策略等内容,请参见 第八篇《Middleware 工程化》


recursion_limit - 防止无限循环

通过 config 控制最大迭代次数:

python 复制代码
from langgraph.errors import GraphRecursionError
from langchain.agents import create_agent

agent = create_agent(model=model, tools=tools)

try:
    result = agent.invoke(
        {"messages": [("user", "复杂问题")]},
        config={"recursion_limit": 11}  # 最多 5 次迭代
    )
except GraphRecursionError as e:
    print(f"达到最大迭代次数:{e}")

计算公式recursion_limit = 2 × 期望迭代次数 + 1

期望迭代次数 recursion_limit 适用场景
3 7 查天气等简单任务
5 11 多步搜索
10 21 研究报告等复杂任务

💡 默认值 10000,开发时基本不会触发


Callback Handler - 简单日志(选学)

用途:观察 Agent 执行过程,不修改行为

最简示例

python 复制代码
from langchain_core.callbacks import BaseCallbackHandler

class SimpleLogger(BaseCallbackHandler):
    """简单的日志回调"""

    def on_tool_start(self, serialized, input_str, **kwargs):
        print(f"🔧 调用工具: {serialized.get('name')}")

    def on_tool_end(self, output, **kwargs):
        preview = getattr(output, "content", output)
        if isinstance(preview, str):
            text = preview
        else:
            text = str(preview)
        print(f"✅ 工具结果: {text[:50]}...")

        
# 使用
result = agent.invoke(
    {"messages": [("user", "天气如何?")]},
    config={"callbacks": [SimpleLogger()]}
)

输出示例

复制代码
🔧 调用工具: get_weather
✅ 工具结果: Weather in Beijing: Sunny, 20°C...

💡 提示:Callback 主要用于监控和调试。生产环境建议使用 LangSmith(第13章详解)。


2.2.3 response_format 结构化输出

⚠️ 重要说明create_agent 支持 response_format 参数用于结构化输出。也可以使用 model.with_structured_output() 方法。

Pydantic Schema 定义

python 复制代码
from pydantic import BaseModel, Field
from typing import List
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

class ResearchReport(BaseModel):
    """研究报告格式"""
    title: str = Field(description="报告标题")
    summary: str = Field(description="执行摘要")
    findings: List[str] = Field(description="主要发现")
    sources: List[str] = Field(description="引用来源")
    confidence: float = Field(description="置信度 0-1")

# ✅ 正确方法:使用 with_structured_output
model = ChatOpenAI(model="gpt-4")
structured_model = model.with_structured_output(ResearchReport)

# 创建 Agent(不使用 response_format)
agent = create_agent(
    model=structured_model,  # 使用结构化模型
    tools=tools
)

# 返回结构化对象
result = agent.invoke({"messages": [("user", "Research about LangChain")]})
# 从最后一条 AI 消息中提取结构化输出
last_message = result['messages'][-1]
# 注意:具体提取方式取决于模型返回格式

JSON Schema

python 复制代码
# 使用 JSON Schema 定义输出格式
response_schema = {
    "title": "SearchResponse",  # ⚠️ 必须包含 title
    "type": "object",
    "properties": {
        "answer": {"type": "string"},
        "confidence": {"type": "number"},
        "sources": {
            "type": "array",
            "items": {"type": "string"}
        }
    },
    "required": ["answer", "confidence"]
}

# ✅ 正确方法:使用 with_structured_output
from langchain.agents import create_agent

model = ChatOpenAI(model="gpt-4")
structured_model = model.with_structured_output(response_schema)

agent = create_agent(
    model=structured_model,
    tools=tools
)

2.3 第一个 Agent 实战

2.3.1 环境准备与工具定义

安装依赖

bash 复制代码
pip install langchain langchain-openai langchain-core python-dotenv

环境配置

python 复制代码
# .env 文件
OPENAI_API_KEY=sk-...
LANGSMITH_API_KEY=ls...
LANGSMITH_TRACING=true
python 复制代码
import os
from dotenv import load_dotenv

# 加载 .env 文件中的环境变量
# 这会读取项目根目录下的 .env 文件,并将其中的变量加载到环境中
load_dotenv()

# 验证环境变量已加载
if not os.getenv("OPENAI_API_KEY"):
    raise ValueError("OPENAI_API_KEY not found in environment variables")

定义工具

python 复制代码
from langchain_core.tools import tool
from typing import Dict, Any
import requests
import math

@tool
def search_wikipedia(query: str) -> str:
    """Search Wikipedia for information.

    Args:
        query: The search query string

    Returns:
        str: Search result snippet or error message
    """
    # 1. 构建 Wikipedia API 请求
    url = "https://en.wikipedia.org/w/api.php"
    params: Dict[str, Any] = {
        "action": "query",
        "list": "search",
        "srsearch": query,
        "format": "json"
    }

    # 2. 发送 HTTP 请求
    response = requests.get(url, params=params)
    data = response.json()

    # 3. 解析并返回结果
    if data["query"]["search"]:
        # 返回第一个搜索结果的摘要
        return data["query"]["search"][0]["snippet"]
    return "No results found"

@tool
def calculate(expression: str) -> str:
    """Calculate a mathematical expression.

    Args:
        expression: Math expression like "2+2" or "sqrt(16)"

    Returns:
        str: Calculation result or error message
    """
    try:
        # 安全的 eval(仅支持数学函数)
        # 通过限制 __builtins__ 和提供白名单函数,防止代码注入
        result = eval(
            expression,
            {"__builtins__": None},  # 禁用内置函数
            {
                # 只允许这些安全的数学函数
                "sqrt": math.sqrt,
                "sin": math.sin,
                "cos": math.cos,
                "pi": math.pi
            }
        )
        return str(result)
    except Exception as e:
        return f"Error: {str(e)}"

# 创建工具列表
tools = [search_wikipedia, calculate]

2.3.2 创建 Agent 与运行调试

创建 Agent

python 复制代码
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent
from typing import List
from langchain_core.tools import BaseTool

# 1. 配置 LLM 模型
model = ChatOpenAI(
    model="gpt-4",  # 模型名称
    temperature=0  # 温度设为 0,确保输出稳定
)

# 2. 定义系统提示词
# 系统提示词用于指导 Agent 的行为和决策逻辑
system_prompt = """You are a helpful research assistant.

You have access to:
1. Wikipedia search - for factual information
2. Calculator - for mathematical calculations

When answering:
- Use tools to verify facts
- Show your reasoning
- Cite sources when applicable
"""

# 3. 创建 Agent
# create_agent 接收模型、工具列表、系统提示词,返回一个可执行的 Agent
agent = create_agent(
    model=model,  # LLM 模型实例
    tools=tools,  # 工具列表:[search_wikipedia, calculate]
    system_prompt=system_prompt  # 系统提示词
)

# Agent 现在已准备好处理用户请求
# 它会自动决策是否需要调用工具,并进行多轮迭代直到得出答案

运行 Agent

python 复制代码
from typing import Dict, List, Any
from langchain_core.messages import BaseMessage

# 1. 简单问题(可能不需要工具)
result: Dict[str, Any] = agent.invoke({
    "messages": [("user", "What is the capital of France?")]
})
# 获取最后一条 AI 消息的内容
final_answer: str = result['messages'][-1].content
print(final_answer)

# 2. 需要计算的问题(Agent 会调用 calculate 工具)
result = agent.invoke({
    "messages": [("user", "What is the square root of 144?")]
})
print(result['messages'][-1].content)
# 输出:The square root of 144 is 12.

# 3. 需要搜索 + 计算(Agent 会调用多个工具)
result = agent.invoke({
    "messages": [("user", "Search for the population of Tokyo and calculate its square root")]
})
print(result['messages'][-1].content)

# 4. 查看完整的消息历史(包括工具调用)
for i, msg in enumerate(result['messages']):
    print(f"[{i}] {msg.type}: {msg.content[:100] if msg.content else '(tool call)'}...")

调试输出

python 复制代码
# 启用详细日志
import langchain
langchain.debug = True

# 查看执行过程
result = agent.invoke({
    "messages": [("user", "Complex question")]
})

# 输出会显示:
# - LLM 思考过程
# - 工具调用决策
# - 工具执行结果
# - 最终答案生成

2.3.3 结构化输出(Pydantic Schema)

定义输出格式

python 复制代码
from pydantic import BaseModel, Field
from typing import List, Optional

class FactCheckResult(BaseModel):
    """事实核查结果"""
    claim: str = Field(description="原始声明")
    verdict: str = Field(description="判定结果: True/False/Uncertain")
    explanation: str = Field(description="详细解释")
    sources: List[str] = Field(description="验证来源")
    confidence: float = Field(ge=0, le=1, description="置信度 0-1")

创建结构化 Agent

python 复制代码
fact_checker = create_agent(
    model=model,
    tools=tools,
    response_format=FactCheckResult,
    system_prompt="""You are a fact-checking assistant.

For each claim:
1. Search for credible sources
2. Verify the information
3. Provide verdict and explanation
4. List all sources used
5. Assign confidence score
"""
)

使用结构化输出

python 复制代码
# 输入声明
claim = "The Earth is the third planet from the Sun"

# 获取结构化结果
result = fact_checker.invoke({
    "messages": [("user", claim)]
})

# 访问结构化字段(从 structured_response 中获取)
fact_result = result['structured_response']
print(f"Claim: {fact_result.claim}")
print(f"Verdict: {fact_result.verdict}")
print(f"Explanation: {fact_result.explanation}")
print(f"Sources: {', '.join(fact_result.sources)}")
print(f"Confidence: {fact_result.confidence:.2%}")

# 输出:
# Claim: The Earth is the third planet from the Sun
# Verdict: True
# Explanation: According to astronomical data, Earth orbits the Sun...
# Sources: Wikipedia - Solar System, NASA
# Confidence: 95.00%

批量处理

python 复制代码
claims = [
    "Water boils at 100°C",
    "The Great Wall is visible from space",
    "Humans use only 10% of their brain"
]

# 批量核查
results = [
    fact_checker.invoke({"messages": [("user", claim)]})
    for claim in claims
]

# 生成报告
for result in results:
    fact_result = result['structured_response']
    print(f"{fact_result.verdict}: {fact_result.claim} ({fact_result.confidence:.0%})")

本章小结

Agent 核心概念

  • ✅ Agent = 自主决策 + 工具使用 + 迭代执行
  • ✅ Agent 与 Chain 的本质区别:动态 vs 固定
  • ✅ 适用场景:信息检索、多步推理、动态决策

create_agent 核心参数

  • ✅ model:选择合适的 LLM
  • ✅ tools:定义可用工具列表
  • ✅ system_prompt:指导 Agent 行为
  • ✅ recursion_limit:防止无限循环(通过 config 传入)
  • ✅ response_format:结构化输出

实战要点

  • ✅ 工具设计:清晰的描述和参数定义
  • ✅ 提示词工程:明确角色、能力、指导原则
  • ✅ 结构化输出:Pydantic Schema 保证类型安全

设计哲学

简单的 API + 强大的 LLM + 实用的工具 = 智能的 Agent


思考与练习

  1. 练习 1:基础 Agent

    创建一个简单的天气查询 Agent,能够查询城市天气并进行温度单位转换。

  2. 练习 2:工具组合

    实现一个数学教师 Agent,能够:

    • 解答数学问题
    • 验证答案
    • 给出详细步骤
  3. 练习 3:结构化输出

    创建一个新闻摘要 Agent,输出包含:标题、摘要、关键词、情感分析。

  4. 练习 4:错误处理

    实现一个健壮的 Agent,能够处理:

    • 工具调用失败
    • 超过最大迭代次数
    • 无效输入
  5. 思考题:

    • 如何判断一个任务应该使用 Agent 还是 Chain?
    • recursion_limit 设置过小或过大会有什么问题?
    • 如何设计一个好的 system_prompt?

第3章:实战案例:RAG Agent

3.1 RAG 基础概念

3.1.1 文档处理流程

什么是 RAG?

RAG(Retrieval-Augmented Generation)是一种结合检索生成的技术,通过从外部知识库检索相关信息来增强 LLM 的回答能力。

核心优势

  • ✅ 解决 LLM 知识截止日期问题
  • ✅ 提供可验证的信息来源
  • ✅ 降低幻觉(Hallucination)
  • ✅ 支持私有知识库

用户问题
检索相关文档
向量数据库
返回 Top-K 文档
构建 Context
LLM 生成答案
返回答案 + 来源

RAG 完整流程
离线:文档处理
文档加载
文档分块 Chunking
向量化 Embedding
存储到向量数据库
在线:查询处理
用户问题
问题向量化
相似度检索
返回 Top-K 文档
LLM 生成答案

文档分块策略

python 复制代码
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 创建分块器
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,        # 每块大小
    chunk_overlap=200,      # 块之间的重叠
    length_function=len,
    separators=["\n\n", "\n", " ", ""]  # 分割优先级
)

# 分块文档
chunks = text_splitter.split_text(long_document)

分块策略对比

策略 chunk_size chunk_overlap 适用场景
小块 200-500 50-100 精确检索
中块 500-1000 100-200 平衡性能
大块 1000-2000 200-400 保持上下文

3.1.2 向量化与检索策略

向量化(Embedding)

将文本转换为高维向量:

python 复制代码
from langchain_openai import OpenAIEmbeddings

# 创建 Embedding 模型
embeddings = OpenAIEmbeddings(
    model="text-embedding-3-small"
)

# 向量化文本
vector = embeddings.embed_query("What is LangChain?")
print(len(vector))  # 1536 维度

# 批量向量化
vectors = embeddings.embed_documents([
    "Document 1",
    "Document 2",
    "Document 3"
])

向量数据库

python 复制代码
from langchain_chroma import Chroma  # ✅ 新版:使用 langchain_chroma 包

# 创建向量数据库(自动持久化)
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"  # 指定目录即自动持久化
)

# ⚠️ 注意:Chroma 0.4.x+ 已废弃 .persist() 方法
# 现在只需指定 persist_directory 参数即可自动持久化

检索策略

1. 相似度检索(Similarity Search)

python 复制代码
# 基础相似度检索
results = vectorstore.similarity_search(
    query="What is LangChain?",
    k=4  # 返回 Top-4
)

for doc in results:
    print(doc.page_content)
    print(doc.metadata)

2. MMR(最大边际相关性)

平衡相关性和多样性:

python 复制代码
# MMR 检索
results = vectorstore.max_marginal_relevance_search(
    query="What is LangChain?",
    k=4,
    fetch_k=20,  # 先检索20个
    lambda_mult=0.5  # 0=多样性, 1=相关性
)

3. 带分数的检索

python 复制代码
# 获取相似度分数
results = vectorstore.similarity_search_with_score(
    query="What is LangChain?",
    k=4
)

for doc, score in results:
    print(f"Score: {score:.4f}")
    print(doc.page_content)

3.2 RAG Agent 实现

3.2.1 构建检索工具

完整 RAG 工具实现

python 复制代码
from langchain_core.tools import tool
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma
from typing import List
from langchain_core.documents import Document

# 步骤1:加载文档
# TextLoader 用于加载纯文本文件
loader = TextLoader("knowledge_base.txt")
documents: List[Document] = loader.load()

# 步骤2:文档分块
# RecursiveCharacterTextSplitter 递归地按字符分割文档
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,  # 每个块的目标大小(字符数)
    chunk_overlap=200  # 块之间的重叠(保持上下文连续性)
)
chunks: List[Document] = text_splitter.split_documents(documents)

# 步骤3:创建向量数据库(自动持久化)
# Embeddings 模型将文本转换为向量
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# Chroma 是向量数据库,用于存储和检索文档向量
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./db"  # 指定目录即自动持久化
)

# 步骤4:创建检索工具
@tool
def search_knowledge_base(query: str) -> str:
    """Search the knowledge base for relevant information.

    Args:
        query: The search query string

    Returns:
        str: Formatted search results with document numbers
    """
    # 检索 Top-3 相关文档(基于余弦相似度)
    docs: List[Document] = vectorstore.similarity_search(query, k=3)

    # 格式化结果,添加文档编号
    results: List[str] = []
    for i, doc in enumerate(docs, 1):
        results.append(f"[Document {i}]\n{doc.page_content}\n")

    return "\n".join(results)

高级检索工具

python 复制代码
from typing import Optional, Dict, Any, List
from langchain_core.documents import Document

@tool
def advanced_search(
    query: str,
    filter_metadata: Optional[str] = None,
    max_results: int = 3
) -> str:
    """Advanced search with metadata filters.

    Args:
        query: The search query string
        filter_metadata: Optional filter in format 'key:value' (e.g., 'source:api')
        max_results: Maximum number of results to return

    Returns:
        str: JSON string of search results with metadata
    """
    # 1. 构建过滤器字典
    filter_dict: Optional[Dict[str, Any]] = None
    if filter_metadata:
        # 解析 'key:value' 格式的过滤器
        key, value = filter_metadata.split(":")
        filter_dict = {key: value}

    # 2. 执行检索(带过滤器)
    docs: List[Document] = vectorstore.similarity_search(
        query,
        k=max_results,
        filter=filter_dict  # Chroma 支持的元数据过滤
    )

    # 3. 格式化输出(包含元数据)
    results: List[Dict[str, Any]] = []
    for doc in docs:
        results.append({
            "content": doc.page_content,
            "source": doc.metadata.get("source", "unknown"),
            "page": doc.metadata.get("page", "N/A")
        })

    return str(results)

3.2.2 系统提示词设计

RAG Agent 提示词模板

python 复制代码
system_prompt = """You are a helpful AI assistant with access to a knowledge base.

Your capabilities:
- Search the knowledge base for accurate information
- Provide detailed answers based on retrieved documents
- Cite sources for all factual claims

Guidelines:
1. ALWAYS search the knowledge base before answering factual questions
2. Quote relevant passages from the documents
3. If information is not in the knowledge base, clearly state that
4. Cite document numbers when referencing information
5. Be concise but thorough

Response format:
- Answer the question directly
- Include relevant quotes in "quotation marks"
- List sources at the end: [Document 1], [Document 2], etc.

Example:
Question: What is LangChain?
Answer: LangChain is "a framework for developing applications powered by
language models" [Document 1]. It provides tools for "building applications
that combine LLMs with other sources of knowledge" [Document 2].

Sources: [Document 1], [Document 2]
"""

3.2.3 运行与优化(查询重写、相关性评分)

创建 RAG Agent

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

# 配置模型
model = ChatOpenAI(model="gpt-4", temperature=0)

# 创建工具列表
tools = [search_knowledge_base]

# 创建 RAG Agent
rag_agent = create_agent(
    model=model,
    tools=tools,
    system_prompt=system_prompt,
)

基础使用

python 复制代码
# 简单问答
result = rag_agent.invoke({
    "messages": [("user", "What is LangChain?")]
})
print(result['messages'][-1].content)

# 输出示例:
# LangChain is "a framework for developing applications powered by language
# models" [Document 1]. It enables developers to "combine LLMs with external
# data sources and APIs" [Document 2].
#
# Sources: [Document 1], [Document 2]

查询重写(Query Rewriting)

提升检索质量:

python 复制代码
@tool
def rewrite_and_search(query: str) -> str:
    """Rewrite query for better retrieval, then search.

    Args:
        query: Original user query
    """
    # 使用 LLM 重写查询
    rewrite_prompt = f"""Rewrite this query to be more specific and
    search-friendly. Only output the rewritten query.

    Original: {query}
    Rewritten:"""

    rewritten = model.invoke(rewrite_prompt).content

    # 使用重写后的查询检索
    docs = vectorstore.similarity_search(rewritten, k=3)

    return format_docs(docs)

# 添加到工具列表
tools = [search_knowledge_base, rewrite_and_search]

相关性评分与过滤

python 复制代码
@tool
def scored_search(query: str, min_score: float = 0.7) -> str:
    """Search with relevance score filtering.

    Args:
        query: Search query
        min_score: Minimum relevance score (0-1)
    """
    # 获取带分数的结果
    results = vectorstore.similarity_search_with_score(query, k=10)

    # 过滤低分结果
    filtered_results = [
        (doc, score) for doc, score in results
        if score >= min_score
    ]

    # 格式化输出
    output = []
    for doc, score in filtered_results[:3]:
        output.append(f"[Score: {score:.2f}]\n{doc.page_content}\n")

    return "\n".join(output) if output else "No relevant results found"

多查询检索

增加召回率:

python 复制代码
@tool
def multi_query_search(query: str) -> str:
    """Generate multiple queries and aggregate results.

    Args:
        query: Original query
    """
    # 生成多个查询变体
    multi_query_prompt = f"""Generate 3 different versions of this query:

    Original: {query}

    Output format (one per line):
    1. ...
    2. ...
    3. ..."""

    variants = model.invoke(multi_query_prompt).content.split("\n")

    # 对每个变体检索
    all_docs = []
    for variant in variants:
        docs = vectorstore.similarity_search(variant.strip(), k=2)
        all_docs.extend(docs)

    # 去重并返回
    unique_docs = list({doc.page_content: doc for doc in all_docs}.values())

    return format_docs(unique_docs[:5])

完整 RAG Agent 示例

python 复制代码
from pydantic import BaseModel, Field
from typing import List

# 定义结构化输出
class RAGResponse(BaseModel):
    """RAG 响应格式"""
    answer: str = Field(description="回答内容")
    quotes: List[str] = Field(description="引用片段")
    sources: List[str] = Field(description="来源列表")
    confidence: float = Field(ge=0, le=1, description="置信度")

# 创建结构化 RAG Agent
rag_agent = create_agent(
    model=model,
    tools=[
        search_knowledge_base,
        rewrite_and_search,
        scored_search,
        multi_query_search
    ],
    response_format=RAGResponse,
    system_prompt=system_prompt,
)

# 使用
result = rag_agent.invoke({
    "messages": [("user", "Explain LangChain's architecture")]
})

# 结构化访问
rag_result = result['structured_response']
print(f"Answer: {rag_result.answer}")
print(f"Quotes: {rag_result.quotes}")
print(f"Sources: {rag_result.sources}")
print(f"Confidence: {rag_result.confidence:.2%}")

本章小结

RAG 核心流程

  • ✅ 文档处理:加载 → 分块 → 向量化 → 存储
  • ✅ 检索策略:相似度检索、MMR、带分数检索
  • ✅ 工具构建:基础检索、高级过滤、多查询

优化技巧

  • ✅ 查询重写:提升检索质量
  • ✅ 相关性评分:过滤低质量结果
  • ✅ 多查询检索:增加召回率
  • ✅ 结构化输出:保证答案质量

最佳实践

  • ✅ 合适的分块大小(500-1000)
  • ✅ 适当的重叠(100-200)
  • ✅ 明确的系统提示词
  • ✅ 引用来源的验证

设计哲学

检索是为了增强,而非替代 LLM 的推理能力


思考与练习

  1. 练习 1:基础 RAG

    实现一个简单的 RAG Agent,能够从本地文档中检索并回答问题。

  2. 练习 2:查询优化

    实现查询重写功能,对比重写前后的检索质量。

  3. 练习 3:多数据源

    构建一个支持多个知识库的 RAG Agent(如:技术文档 + API 文档)。

  4. 练习 4:引用验证

    实现一个验证机制,确保 Agent 的引用确实来自检索到的文档。

  5. 思考题:

    • chunk_size 大小如何影响检索质量?
    • MMR 和相似度检索有什么区别?
    • 如何评估 RAG 系统的质量?

第4章:实战案例:SQL Agent

4.1 SQL Agent 架构

4.1.1 数据库连接与表结构获取

数据库连接

python 复制代码
from sqlalchemy import create_engine, inspect
import pandas as pd

# 连接数据库(SQLite 示例)
database_uri = "sqlite:///example.db"
engine = create_engine(database_uri)

# 测试连接
with engine.connect() as conn:
    result = conn.execute("SELECT 1")
    print("Database connected!")

支持的数据库类型

  • SQLite:sqlite:///database.db
  • PostgreSQL:postgresql://user:pass@localhost/db
  • MySQL:mysql://user:pass@localhost/db
  • SQL Server:mssql://user:pass@localhost/db

表结构获取工具

python 复制代码
from langchain_core.tools import tool

@tool
def get_table_schema(table_name: str = None) -> str:
    """Get database table schema information.

    Args:
        table_name: Specific table name, or None for all tables
    """
    inspector = inspect(engine)

    if table_name:
        # 获取特定表的信息
        columns = inspector.get_columns(table_name)
        schema = f"Table: {table_name}\nColumns:\n"
        for col in columns:
            schema += f"  - {col['name']} ({col['type']})\n"
        return schema
    else:
        # 获取所有表
        table_names = inspector.get_table_names()
        return f"Available tables: {', '.join(table_names)}"

@tool
def get_sample_data(table_name: str, limit: int = 3) -> str:
    """Get sample data from a table.

    Args:
        table_name: Table name
        limit: Number of rows to return
    """
    query = f"SELECT * FROM {table_name} LIMIT {limit}"
    df = pd.read_sql(query, engine)
    return df.to_string()

4.1.2 SQL 工具开发(查询、安全限制)

SQL 查询工具

python 复制代码
@tool
def execute_sql_query(query: str) -> str:
    """Execute a SQL SELECT query and return results.

    Args:
        query: SQL SELECT query (READ ONLY)
    """
    # 安全检查:只允许 SELECT
    query_upper = query.upper().strip()
    if not query_upper.startswith("SELECT"):
        return "Error: Only SELECT queries are allowed"

    # 危险关键词检查
    dangerous_keywords = ["DROP", "DELETE", "UPDATE", "INSERT", "ALTER", "TRUNCATE"]
    for keyword in dangerous_keywords:
        if keyword in query_upper:
            return f"Error: {keyword} operations are not allowed"

    try:
        # 执行查询
        df = pd.read_sql(query, engine)

        # 限制返回行数
        if len(df) > 100:
            return f"Warning: Result has {len(df)} rows. Showing first 100:\n{df.head(100).to_string()}"

        return df.to_string()

    except Exception as e:
        return f"Error executing query: {str(e)}"

SQL 查询验证工具

python 复制代码
@tool
def validate_sql_query(query: str) -> str:
    """Validate SQL query syntax without executing.

    Args:
        query: SQL query to validate
    """
    try:
        # 使用 EXPLAIN 验证查询
        with engine.connect() as conn:
            conn.execute(f"EXPLAIN {query}")
        return "Query syntax is valid"
    except Exception as e:
        return f"Query validation failed: {str(e)}"

安全沙箱配置

python 复制代码
from sqlalchemy import event
from sqlalchemy.pool import Pool

# 设置查询超时(5秒)
@event.listens_for(Pool, "connect")
def set_timeout(dbapi_conn, connection_record):
    dbapi_conn.execute("PRAGMA busy_timeout = 5000")

# 设置只读模式
@event.listens_for(Pool, "connect")
def set_readonly(dbapi_conn, connection_record):
    dbapi_conn.execute("PRAGMA query_only = ON")

4.2 SQL Agent 实现

4.2.1 系统提示词与 Agent 创建

SQL Agent 提示词

python 复制代码
system_prompt = """You are a SQL expert assistant with access to a database.

Your capabilities:
- Get table schemas and sample data
- Write and execute SQL SELECT queries
- Analyze query results and provide insights

Guidelines:
1. ALWAYS check table schema before writing queries
2. Use get_sample_data to understand data format
3. Write clear, optimized SQL queries
4. Only use SELECT statements (no modifications)
5. Explain your query logic
6. Validate queries before execution

Workflow:
1. User asks a question about data
2. Check relevant table schemas
3. (Optional) Get sample data to understand format
4. Write SQL query
5. Execute and analyze results
6. Provide clear answer with data

Example:
User: "How many users registered last month?"

Steps:
1. get_table_schema("users") → check columns
2. execute_sql_query("SELECT COUNT(*) FROM users WHERE created_at >= date('now', '-1 month')")
3. Return: "There were 150 new users registered last month."
"""

创建 SQL Agent

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

# 模型配置
model = ChatOpenAI(model="gpt-4", temperature=0)

# 工具列表
sql_tools = [
    get_table_schema,
    get_sample_data,
    execute_sql_query,
    validate_sql_query
]

# 创建 SQL Agent
sql_agent = create_agent(
    model=model,
    tools=sql_tools,
    system_prompt=system_prompt,
)

基础使用

python 复制代码
# 简单查询
result = sql_agent.invoke({
    "messages": [("user", "How many products are in stock?")]
})
print(result['messages'][-1].content)

# 复杂分析
result = sql_agent.invoke({
    "messages": [("user", "What are the top 5 customers by total purchase amount?")]
})
print(result['messages'][-1].content)

# 时间序列分析
result = sql_agent.invoke({
    "messages": [("user", "Show monthly sales trend for the last 6 months")]
})
print(result['messages'][-1].content)

4.2.2 Human-in-the-Loop 审批机制

审批工具实现

python 复制代码
from typing import Optional

# 全局变量存储待审批查询
pending_approval = {}

@tool
def request_query_approval(query: str, reason: str) -> str:
    """Request human approval for a SQL query.

    Args:
        query: SQL query to approve
        reason: Reason for the query
    """
    approval_id = f"approval_{len(pending_approval) + 1}"
    pending_approval[approval_id] = {
        "query": query,
        "reason": reason,
        "status": "pending"
    }

    return f"""Query requires approval: {approval_id}

Query: {query}
Reason: {reason}

Status: Waiting for approval...
(Use approve_query('{approval_id}') or reject_query('{approval_id}'))
"""

def approve_query(approval_id: str) -> str:
    """Approve a pending query (called by human)."""
    if approval_id not in pending_approval:
        return "Approval ID not found"

    query = pending_approval[approval_id]["query"]
    pending_approval[approval_id]["status"] = "approved"

    # 执行查询
    result = execute_sql_query.invoke(query)
    return f"Query approved and executed:\n{result}"

def reject_query(approval_id: str, reason: str = "") -> str:
    """Reject a pending query (called by human)."""
    if approval_id not in pending_approval:
        return "Approval ID not found"

    pending_approval[approval_id]["status"] = "rejected"
    return f"Query rejected. Reason: {reason}"

敏感操作检测

python 复制代码
@tool
def smart_sql_query(query: str) -> str:
    """Execute SQL query with automatic approval for sensitive operations.

    Args:
        query: SQL query to execute
    """
    query_upper = query.upper()

    # 定义需要审批的操作
    sensitive_patterns = [
        ("DELETE", "Deleting data"),
        ("UPDATE", "Modifying data"),
        ("DROP", "Dropping tables"),
        ("ALTER", "Altering table structure"),
        ("TRUNCATE", "Truncating table")
    ]

    # 检查是否是敏感操作
    for pattern, reason in sensitive_patterns:
        if pattern in query_upper:
            return request_query_approval.invoke({
                "query": query,
                "reason": reason
            })

    # 非敏感操作直接执行
    return execute_sql_query.invoke(query)

带审批的 SQL Agent

python 复制代码
# 更新系统提示词
approval_system_prompt = system_prompt + """

IMPORTANT - Approval Policy:
- For SELECT queries on single tables: Execute directly
- For JOIN queries on multiple tables: Request approval
- For queries affecting >1000 rows: Request approval
- For DELETE/UPDATE/DROP: ALWAYS request approval

Use smart_sql_query() which handles approval automatically.
"""

# 创建带审批的 Agent
sql_agent_with_approval = create_agent(
    model=model,
    tools=[
        get_table_schema,
        get_sample_data,
        smart_sql_query,
        request_query_approval
    ],
    system_prompt=approval_system_prompt,
)

使用示例

python 复制代码
# 简单查询(自动执行)
result = sql_agent_with_approval.invoke({
    "messages": [("user", "Count total orders")]
})
# 直接返回结果

# 敏感查询(需要审批)
result = sql_agent_with_approval.invoke({
    "messages": [("user", "Delete all orders from last year")]
})
# 返回: Query requires approval: approval_1
#       Query: DELETE FROM orders WHERE year = 2024
#       Status: Waiting for approval...

# 人工审批
approval_result = approve_query("approval_1")
# 或
rejection_result = reject_query("approval_1", "Not authorized")

结构化 SQL 响应

python 复制代码
from pydantic import BaseModel, Field
from typing import List, Optional

class SQLQueryResult(BaseModel):
    """SQL 查询结果"""
    query: str = Field(description="执行的 SQL 查询")
    result_summary: str = Field(description="结果摘要")
    data: Optional[str] = Field(description="数据表格(如果有)")
    row_count: int = Field(description="返回行数")
    insights: List[str] = Field(description="数据洞察")

# 创建结构化 SQL Agent
sql_agent_structured = create_agent(
    model=model,
    tools=sql_tools,
    response_format=SQLQueryResult,
    system_prompt=system_prompt
)

# 使用
result = sql_agent_structured.invoke({
    "messages": [("user", "Top 5 products by sales")]
})

sql_result = result['structured_response']
print(f"Query: {sql_result.query}")
print(f"Summary: {sql_result.result_summary}")
print(f"Rows: {sql_result.row_count}")
print(f"Insights: {sql_result.insights}")

本章小结

SQL Agent 核心组件

  • ✅ 数据库连接:SQLAlchemy 统一接口
  • ✅ 表结构工具:自动获取 Schema 和样本数据
  • ✅ 查询工具:安全的 SQL 执行(只读)
  • ✅ 审批机制:Human-in-the-Loop 保护敏感操作

安全最佳实践

  • ✅ 只允许 SELECT 查询
  • ✅ 关键词黑名单(DROP、DELETE 等)
  • ✅ 查询超时控制(5秒)
  • ✅ 结果行数限制(100行)
  • ✅ 敏感操作审批

系统提示词要点

  • ✅ 明确工作流程
  • ✅ 强调安全规则
  • ✅ 提供查询示例
  • ✅ 说明审批策略

设计哲学

便利性与安全性的平衡:简单查询快速执行,敏感操作人工审批


思考与练习

  1. 练习 1:基础 SQL Agent

    创建一个简单的 SQL Agent,能够查询 SQLite 数据库并回答问题。

  2. 练习 2:查询优化

    实现一个查询优化工具,能够:

    • 检测慢查询
    • 建议索引
    • 优化 JOIN 语句
  3. 练习 3:多表分析

    构建一个能够自动识别表关系并执行 JOIN 查询的 Agent。

  4. 练习 4:审批系统

    实现一个完整的审批工作流:

    • 请求审批
    • 邮件通知
    • 审批记录
    • 超时处理
  5. 思考题:

    • 如何防止 SQL 注入攻击?
    • 什么样的查询应该需要审批?
    • 如何处理大结果集(>10000行)?
    • SQL Agent 与 RAG Agent 有什么本质区别?


第5章:Memory 与上下文管理

目标: 让 Agent 拥有记忆能力,构建有连续性的对话系统

在前面的章节中,我们构建的 Agent 都是无记忆的------每次对话都是独立的,Agent 无法记住之前的交互。本章将教你如何让 Agent 拥有记忆。

5.1 为什么需要 Memory

5.1.1 无记忆 Agent 的问题
python 复制代码
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from typing import Dict, Any, List
from langchain_core.messages import BaseMessage

# 1. 创建无记忆 Agent(没有 checkpointer)
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[]  # 简单示例,不使用工具
)

# 2. 第一轮对话
result1: Dict[str, Any] = agent.invoke({"messages": [("user", "我叫张三")]})
print(result1["messages"][-1].content)
# 输出:你好张三!很高兴认识你!

# 3. 第二轮对话(新的 invoke 调用,状态被重置)
result2: Dict[str, Any] = agent.invoke({"messages": [("user", "我叫什么名字?")]})
print(result2["messages"][-1].content)
# 输出:抱歉,我不知道你的名字
# ❌ Agent 忘记了第一轮对话!因为每次 invoke 都是全新的状态

5.2 使用 LangGraph Checkpointer 实现记忆

LangChain 1.0 推荐使用 LangGraph 的 Checkpointer 机制来实现对话记忆。

5.2.1 基础记忆实现
python 复制代码
from langchain.agents import create_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI
from typing import Dict, Any
from langchain_core.runnables import RunnableConfig

# 1. 创建 Checkpointer(内存存储)
# InMemorySaver 会在内存中保存对话历史
checkpointer = InMemorySaver()

# 2. 创建带记忆的 Agent
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[],  # 简单示例
    checkpointer=checkpointer  # 关键!传入 checkpointer 启用记忆
)

# 3. 使用 thread_id 实现会话隔离
# thread_id 用于区分不同的对话会话
config: RunnableConfig = {"configurable": {"thread_id": "user-123"}}

# 4. 第一轮对话
result1: Dict[str, Any] = agent.invoke(
    {"messages": [("user", "我叫张三")]},
    config=config  # 传入 config,指定会话 ID
)
print(result1["messages"][-1].content)
# 输出:你好张三!很高兴认识你!

# 5. 第二轮对话(使用同一个 thread_id)
result2: Dict[str, Any] = agent.invoke(
    {"messages": [("user", "我叫什么名字?")]},
    config=config  # 使用相同的 thread_id
)
print(result2["messages"][-1].content)
# ✅ 输出:你叫张三
# 成功记住了之前的对话!Checkpointer 保存了历史消息
5.2.2 持久化记忆(SQLite)
python 复制代码
from langgraph.checkpoint.sqlite import SqliteSaver
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from typing import Dict, Any
from langchain_core.runnables import RunnableConfig

# 1. 使用 SQLite 持久化记忆
# 记忆会保存到磁盘文件,重启程序后仍然存在
checkpointer = SqliteSaver.from_conn_string("./checkpoints.db")

# 2. 创建带持久化记忆的 Agent
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[],
    checkpointer=checkpointer  # 使用 SQLite checkpointer
)

# 3. 使用会话 ID
config: RunnableConfig = {"configurable": {"thread_id": "user-123"}}

# 4. 查询之前的对话
# 即使重启程序,也能记住之前的对话内容
result: Dict[str, Any] = agent.invoke(
    {"messages": [("user", "我之前告诉过你什么?")]},
    config=config
)
print(result["messages"][-1].content)
# 如果之前有对话,Agent 会记得
5.2.3 生产级记忆(PostgreSQL)
python 复制代码
from langgraph.checkpoint.postgres import PostgresSaver
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

# 1. 使用 PostgreSQL(适合生产环境)
# PostgreSQL 提供更好的性能、并发支持和数据持久性
checkpointer = PostgresSaver.from_conn_string(
    "postgresql://user:pass@localhost/chatdb"
)

# 2. 创建生产级 Agent
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[],
    checkpointer=checkpointer
)

# 3. 生产环境优势:
# - 支持高并发访问
# - 数据备份和恢复
# - 跨服务器共享记忆
# - 更好的查询性能

5.3 记忆管理最佳实践

5.3.1 会话隔离
python 复制代码
# 不同用户使用不同的 thread_id
user1_config = {"configurable": {"thread_id": "user-001"}}
user2_config = {"configurable": {"thread_id": "user-002"}}

# 用户1的对话
agent.invoke({"messages": [("user", "我是用户1")]}, config=user1_config)

# 用户2的对话(完全独立)
agent.invoke({"messages": [("user", "我是用户2")]}, config=user2_config)
5.3.2 查看对话历史
python 复制代码
# 获取某个会话的完整历史
from langgraph.checkpoint.base import Checkpoint

config = {"configurable": {"thread_id": "user-123"}}
state = agent.get_state(config)

# 查看所有消息
for msg in state.values["messages"]:
    print(f"{msg.type}: {msg.content}")
5.3.3 清除记忆
python 复制代码
# 方法1:使用新的 thread_id 开始新对话
new_config = {"configurable": {"thread_id": "user-123-new-session"}}

# 方法2:手动清除 checkpointer 中的数据
# 具体方法取决于使用的 checkpointer 类型

5.4 成本优化策略

长对话会导致 token 消耗快速增长。以下是优化策略:

5.4.1 限制历史长度
python 复制代码
from langchain_core.messages import trim_messages

# 只保留最近的 10 条消息
def limit_history(state):
    messages = state["messages"]
    # 保留系统消息 + 最近10条
    trimmed = trim_messages(
        messages,
        max_tokens=2000,
        strategy="last",
        token_counter=len,
    )
    return {"messages": trimmed}
5.4.2 使用总结策略
python 复制代码
from langchain_openai import ChatOpenAI

summarizer = ChatOpenAI(model="gpt-4o-mini")  # 使用便宜模型

def summarize_history(messages):
    """对历史对话进行总结"""
    if len(messages) < 10:
        return messages

    # 总结前面的消息
    old_messages = messages[:-5]  # 保留最近5条

    summary_prompt = f"""请简要总结以下对话:
    {old_messages}

    总结:"""

    summary = summarizer.invoke(summary_prompt).content

    # 返回总结 + 最近消息
    return [
        ("system", f"之前的对话总结:{summary}"),
        *messages[-5:]
    ]

5.5 综合实战:客服机器人

python 复制代码
from langchain.agents import create_agent
from langgraph.checkpoint.postgres import PostgresSaver
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

# 1. 定义工具
@tool
def query_order(order_id: str) -> str:
    """查询订单状态"""
    # 模拟查询
    return f"订单 {order_id} 已发货,预计明天送达"

# 2. 创建持久化 checkpointer
checkpointer = PostgresSaver.from_conn_string(
    "postgresql://localhost/customer_service"
)

# 3. 创建 Agent
agent = create_agent(
    model=ChatOpenAI(model="gpt-4o"),
    tools=[query_order],
    checkpointer=checkpointer
)

# 4. 客服对话
def chat_with_customer(user_id: str, message: str):
    config = {"configurable": {"thread_id": f"customer-{user_id}"}}

    result = agent.invoke(
        {"messages": [("user", message)]},
        config=config
    )

    return result["messages"][-1].content

# 使用示例
chat_with_customer("user-456", "你好,我叫李四")
# 输出:你好李四!

chat_with_customer("user-456", "我的订单号是 12345")
# 输出:好的,让我查一下... 订单 12345 已发货,预计明天送达

# 第二天客户再次咨询
chat_with_customer("user-456", "我昨天问的订单到了吗?")
# 输出:您昨天咨询的订单 12345 应该今天送达
# ✅ Agent 记住了之前的订单号!

5.6 本章小结

LangChain 1.0 记忆机制

  • ✅ 使用 Checkpointer 而非旧的 Memory 类
  • ✅ 三种 Checkpointer:InMemorySaver、SqliteSaver、PostgresSaver
  • ✅ 通过 thread_id 实现会话隔离
  • ✅ 支持持久化,重启不丢失

最佳实践

  • ✅ 开发测试:InMemorySaver
  • ✅ 单机部署:SqliteSaver
  • ✅ 生产环境:PostgresSaver
  • ✅ 成本控制:限制历史长度或总结策略

下一步:第三篇将深入 LangGraph,教你完全自定义 Agent 的执行流程和状态管理。


第二篇完结

恭喜!你已经完成了《快速上手实战》篇的学习:

  • ✅ 第3章:Message 与 Tools 基础
  • ✅ 第4章:create_agent 快速构建
  • ✅ 第5章:实战案例:RAG Agent
  • ✅ 第6章:实战案例:SQL Agent
  • ✅ 第7章:Memory 与上下文管理

下一步:第三篇《LangGraph 深入》将带你理解 create_agent 背后的机制,掌握完全自定义能力。

相关推荐
BHXDML1 小时前
第二章:决策树与集成算法
算法·决策树·机器学习
无忧智库1 小时前
一网统飞:城市级低空空域精细化管理与服务平台建设方案深度解析(WORD)
大数据·网络·人工智能
橘颂TA1 小时前
【剑斩OFFER】算法的暴力美学——力扣 692 题:前 K 个高频单词
网络·算法·leetcode·哈希算法·结构与算法
练习时长一年1 小时前
LeetCode热题100(乘积最大子序列)
数据结构·算法·leetcode
张彦峰ZYF1 小时前
Java+Python双语言开发AI工具全景分析与选型指南
java·人工智能·python
仰泳的熊猫1 小时前
题目1109:Hanoi双塔问题
数据结构·c++·算法·蓝桥杯
AlenTech1 小时前
169. 多数元素 - 力扣(LeetCode)
算法·leetcode·职场和发展
Java后端的Ai之路1 小时前
【AI大模型开发】-基于FAISS的语义搜索系统(实战)
人工智能·faiss·向量数据库
橘颂TA1 小时前
【剑斩OFFER】算法的暴力美学——LeetCode 295 题:数据流的中位数
算法·结构与算法