使用AI学习AI - LangChain - Quickstart

langchain

学习[[Langchain]]中,根据官方demo让Claude Code生成的详细注释和教程,以便更好的学习AI。

完整代码示例

from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.tools import tool, ToolRuntime
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
from langgraph.checkpoint.memory import InMemorySaver  
from langchain.agents.structured_output import ToolStrategy  
from pprint import pprint
import os
 
SYSTEM_PROMPT = """你是一位擅长使用双关语的专业天气预报员。  
  
你可以使用两种工具:  
  
- get_weather_for_location:用于获取指定地点的天气信息  
- get_user_location: 使用此方法获取用户位置  
  
如果用户向你询问天气,请务必明确其所在位置。若从问题中能判断出对方指的是其当前所在地,需调用get_user_location工具来定位其位置。"""  
   
@tool
def get_weather_for_location(city: str) -> str:  
    """  
    获取指定城市的天气  
  
    参数:  
        city: 城市名称  
  
    返回:  
        天气描述字符串  
    """     
    return f'{city} 今天是晴天。'  
  
@dataclass
class Context:  
    """  
    自定义运行时上下文模式  
  
    这个类定义了在工具调用时可以传递给 Agent 的额外数据。  
    例如:用户ID、会话信息、认证信息等。  
    """    
	user_id: str  # 用户ID,用于识别当前对话属于哪个用户  
 
@tool  
def get_user_location(runtime: ToolRuntime[Context]) -> str:  
    """  
    根据用户ID获取用户信息  
  
    参数:  
        runtime: 工具运行时对象,包含 context 上下文  
  
    返回:  
        用户所在的城市名称  
    """       
	user_id = runtime.context.user_id    
    return "罗马" if user_id == "1" else "西安"  
  
load_dotenv()  
  
api_key = os.getenv("GLM_API_KEY")
base_url = os.getenv("GLM_BASE_URL")
    
model = ChatOpenAI(  
    model="glm-4.7-flash",
    api_key=api_key,
    base_url=base_url,
    temperature=0.5,
    timeout=10,
    max_tokens=1000
)  
 
@dataclass  
class ResponseFormat:  
    """  
    智能体的响应模式  
  
    这个类定义了 Agent 最终返回的数据结构。  
    使用 @dataclass 也可以用 Pydantic 模型(更强大的验证功能)  
    """      
    punny_response: str  
    weather_conditions: str | None = None
 
checkpointer = InMemorySaver()  
   
agent = create_agent(  
    model=model,
    system_prompt=SYSTEM_PROMPT, 
    tools=[get_user_location, get_weather_for_location],  
    context_schema=Context, 
    response_format=ToolStrategy(ResponseFormat), 
    checkpointer=checkpointer
)  
 
config = {"configurable": {"thread_id": "1"}}  
   
response = agent.invoke(  
    {  
        "messages": [  
            {  
                "role": "user", 
                "content": "外面天气怎么样?"
            }  
        ]  
    },  
    config=config, 
    context=Context(user_id="1")
)  

pprint(response['structured_response'])  
  
# 预期输出类似:  
# ResponseFormat(  
#     punny_response="罗马今天是个'日'不落的好天气!阳光普照,让你心情'阳光'起来!",  
#     weather_conditions="罗马 今天是晴天。"  
# )  

# 注意:我们可以使用相同的 thread_id 继续对话  
response = agent.invoke(  
    {  
        "messages": [  
            {  
                "role": "user", 
                "content": "thank you!" 
            }  
        ]  
    },  
    config=config,  # 使用相同的 thread_id,Agent 会"记住"之前的对话  
    context=Context(user_id="1")  # 传递相同的上下文  
)  
  
pprint(response['structured_response'])  
  
# 预期输出类似:  
# ResponseFormat(  
#     punny_response="不客气!很高兴能为你'预报'一个阳光灿烂的心情!",  
#     weather_conditions=None  # 这次没有天气信息  
# ) 

详细注释版代码

"""  
LangChain Agent 进阶示例 - 详细注释版  
========================================  
  
这个示例演示了如何创建一个具有以下功能的 AI Agent:  
1. 使用工具(tools)来扩展 AI 的能力  
2. 自定义上下文(context)来传递运行时数据  
3. 结构化输出(structured response)来规范响应格式  
4. 记忆功能(checkpointer)来支持多轮对话  
  
核心概念:  
-----------  
Agent: 智能体,是一个使用 LLM 来决定采取什么行动的系统  
Tool: 工具,是 Agent 可以调用的函数,用于扩展 LLM 的能力  
Context: 上下文,用于在工具调用时传递额外的运行时数据  
Checkpointer: 检查点保存器,用于存储对话历史,实现记忆功能  
Structured Output: 结构化输出,强制 Agent 返回特定格式的响应  
"""  
  
# ============================================================================  
# 1. 导入必要的库  
# ============================================================================  
  
from dataclasses import dataclass  # Python标准库,用于创建简单的数据类  
from langchain.agents import create_agent  # LangChain核心函数,用于创建Agent  
from langchain.tools import tool, ToolRuntime  # @tool装饰器和工具运行时上下文  
from langchain_openai import ChatOpenAI  # LangChain对OpenAI兼容API的封装  
from dotenv import load_dotenv  # 用于加载.env环境变量文件  
from langgraph.checkpoint.memory import InMemorySaver  # 内存检查点保存器,用于记忆  
from langchain.agents.structured_output import ToolStrategy  # 结构化输出策略  
from pprint import pprint  # 用于美观打印  
import os  # Python标准库,用于读取环境变量  
  
# ============================================================================  
# 2. 定义系统提示词(System Prompt)  
# ============================================================================  
  
SYSTEM_PROMPT = """你是一位擅长使用双关语的专业天气预报员。  
  
你可以使用两种工具:  
  
- get_weather_for_location:用于获取指定地点的天气信息  
- get_user_location: 使用此方法获取用户位置  
  
如果用户向你询问天气,请务必明确其所在位置。若从问题中能判断出对方指的是其当前所在地,需调用get_user_location工具来定位其位置。"""  
  
# 【原理】系统提示词(System Prompt)是给 LLM 的"角色设定"和"行为指导"  
# 在这里,我们告诉 Agent:  
# 1. 它的角色:擅长双关语的天气预报员  
# 2. 它可以使用哪些工具  
# 3. 什么时候使用哪个工具  
  
# ============================================================================  
# 3. 定义工具(Tools)  
# ============================================================================  
  
# 【工具1】获取天气的工具  
@tool  # @tool 装饰器将普通函数转换为 LangChain 可以识别的工具  
def get_weather_for_location(city: str) -> str:  
    """  
    获取指定城市的天气  
  
    参数:  
        city: 城市名称  
  
    返回:  
        天气描述字符串  
    """    
	# 在实际应用中,这里可能会调用真实的天气API  
    return f'{city} 今天是晴天。'  
  
# 【原理】@tool 装饰器的作用:  
# 1. 将函数的文档字符串(docstring)转换为工具的描述  
# 2. 将函数的参数类型提示转换为工具的参数schema  
# 3. 让 Agent 能够理解如何调用这个工具  
  
# ============================================================================  
# 4. 定义自定义上下文(Context)  
# ============================================================================  
  
@dataclass  # @dataclass 自动生成 __init__, __repr__ 等方法  
class Context:  
    """  
    自定义运行时上下文模式  
  
    这个类定义了在工具调用时可以传递给 Agent 的额外数据。  
    例如:用户ID、会话信息、认证信息等。  
    """    
	user_id: str  # 用户ID,用于识别当前对话属于哪个用户  
  
# 【原理】Context 的作用:  
# 1. 允许在运行时向工具传递额外的数据  
# 2. 这些数据不会出现在 Agent 的思维链中(更安全)  
# 3. 工具可以通过 runtime.context 访问这些数据  
  
# 【工具2】获取用户位置的工具(使用Context)  
@tool  
def get_user_location(runtime: ToolRuntime[Context]) -> str:  
    """  
    根据用户ID获取用户信息  
  
    参数:  
        runtime: 工具运行时对象,包含 context 上下文  
  
    返回:  
        用户所在的城市名称  
    """    
	# 从运行时上下文中获取 user_id    
	user_id = runtime.context.user_id  
    # 根据 user_id 返回对应的位置(实际应用中可能查询数据库)  
    return "罗马" if user_id == "1" else "西安"  
  
# 【原理】这里演示了工具如何访问 Context:  
# 1. 函数参数声明为 runtime: ToolRuntime[Context]
# 2. 通过 runtime.context.user_id 获取用户ID  
# 3. 这样可以根据不同的用户返回不同的位置信息  
  
# ============================================================================  
# 5. 加载环境变量并初始化模型  
# ============================================================================  
  
# 加载 .env 文件中的环境变量  
load_dotenv()  
  
# 从环境变量中读取API配置  
api_key = os.getenv("GLM_API_KEY")  # API密钥  
base_url = os.getenv("GLM_BASE_URL")  # API基础URL  
  
# 创建 ChatOpenAI 模型实例  
model = ChatOpenAI(  
    model="glm-4.7-flash",  # 使用智谱AI的 glm-4.7-flash 模型  
    api_key=api_key,  # API密钥  
    base_url=base_url,  # API基础URL  
    temperature=0.5,  # 控制输出的随机性(0-1,越低越确定)  
    timeout=10,  # 请求超时时间(秒)  
    max_tokens=1000  # 最大输出token数  
)  
  
# 【原理】ChatOpenAI 是 LangChain 对 OpenAI 兼容 API 的封装  
# 智谱AI的 API 兼容 OpenAI 的格式,所以可以用同一个类  
# temperature 参数:  
#   - 0: 更加确定、一致的输出  
#   - 0.5: 平衡确定性和创造性  
#   - 1: 更加随机、有创造性的输出  
  
# ============================================================================  
# 6. 定义结构化输出格式  
# ============================================================================  
  
@dataclass  
class ResponseFormat:  
    """  
    智能体的响应模式  
  
    这个类定义了 Agent 最终返回的数据结构。  
    使用 @dataclass 也可以用 Pydantic 模型(更强大的验证功能)  
    """    
	# 一个巧妙的双关回应(必不可少)  
    punny_response: str  
    # 如有相关的任何有趣天气信息,请提供  
    weather_conditions: str | None = None  # None 表示可选字段  
  
# 【原理】结构化输出的作用:  
# 1. 强制 Agent 返回符合特定格式的数据  
# 2. 方便程序处理 Agent 的响应(解析、验证、使用)  
# 3. 避免返回不可预测的文本格式  
  
# ============================================================================  
# 7. 创建检查点保存器(实现记忆功能)  
# ============================================================================  
  
checkpointer = InMemorySaver()  
  
# 【原理】Checkpointer(检查点保存器)的作用:  
# 1. 存储对话的历史记录  
# 2. 每次调用 Agent 时,会自动加载该 thread_id 的历史  
# 3. 这样 Agent 可以"记住"之前的对话内容  
# 4. InMemorySaver 将历史保存在内存中(程序运行期间有效)  
#    实际应用中可能用 PostgresSaver、RedisSaver 等持久化存储  
  
# ============================================================================  
# 8. 创建 Agent
# ============================================================================  
  
agent = create_agent(  
    model=model,  # 使用的 LLM 模型  
    system_prompt=SYSTEM_PROMPT,  # 系统提示词  
    tools=[get_user_location, get_weather_for_location],  # 可用工具列表  
    context_schema=Context,  # 上下文的数据结构定义  
    response_format=ToolStrategy(ResponseFormat),  # 响应格式策略  
    checkpointer=checkpointer  # 检查点保存器  
)  
  
# 【原理】create_agent 创建的是一个"有状态的 Agent":  
# 1. 它包含一个 LLM 用于推理  
# 2. 它有权访问定义的工具  
# 3. 它可以根据用户输入决定调用哪个工具  
# 4. 它可以记住对话历史(通过 checkpointer)  
# 5. 它可以接收运行时上下文  
# 6. 它可以返回结构化的响应  
  
# 【Agent 的工作流程】(简化版):  
# 用户输入 → Agent 思考 → 需要信息? → 调用工具 → 获取结果 → 继续思考 → 返回响应  
#                              ↓  
#                           不需要 → 直接返回响应  
  
# ============================================================================  
# 9. 配置对话线程ID  
# ============================================================================  
  
# thread_id 是特定对话的唯一标识符  
# 相同的 thread_id 会共享相同的对话历史  
config = {"configurable": {"thread_id": "1"}}  
  
# 【原理】thread_id 的作用:  
# 1. 每个唯一的 thread_id 代表一个独立的对话会话  
# 2. 使用相同的 thread_id 可以继续之前的对话  
# 3. 使用不同的 thread_id 会开始新的对话  
# 4. 类似于聊天应用中的"会话ID"  
  
# ============================================================================  
# 10. 第一次调用 Agent
# ============================================================================  
  
response = agent.invoke(  
    {  
        "messages": [  
            {  
                "role": "user",  # 消息角色:user(用户)  
                "content": "外面天气怎么样?"  # 用户的问题  
            }  
        ]  
    },  
    config=config,  # 使用之前定义的配置(包含thread_id)  
    context=Context(user_id="1")  # 传递上下文:用户ID为1  
)  
  
# 【第一次调用的执行流程】:  
# 1. Agent 接收到用户问题:"外面天气怎么样?"  
# 2. Agent 思考:用户没有指定位置  
# 3. Agent 查看系统提示词:需要调用 get_user_location 获取位置  
# 4. Agent 调用 get_user_location 工具,传入 Context(user_id="1")
# 5. 工具返回:"罗马"(因为 user_id="1")  
# 6. Agent 现在知道位置是罗马  
# 7. Agent 调用 get_weather_for_location 工具,city="罗马"  
# 8. 工具返回:"罗马 今天是晴天。"  
# 9. Agent 将天气信息包装成双关语回应  
# 10. Agent 按照 ResponseFormat 格式化输出  
  
# 打印结构化响应  
pprint(response['structured_response'])  
  
# 预期输出类似:  
# ResponseFormat(  
#     punny_response="罗马今天是个'日'不落的好天气!阳光普照,让你心情'阳光'起来!",  
#     weather_conditions="罗马 今天是晴天。"  
# )  
  
# ============================================================================  
# 11. 第二次调用 Agent(继续对话)  
# ============================================================================  
  
# 注意:我们可以使用相同的 thread_id 继续对话  
response = agent.invoke(  
    {  
        "messages": [  
            {  
                "role": "user",  # 消息角色  
                "content": "thank you!"  # 用户感谢  
            }  
        ]  
    },  
    config=config,  # 使用相同的 thread_id,Agent 会"记住"之前的对话  
    context=Context(user_id="1")  # 传递相同的上下文  
)  
  
# 【第二次调用的执行流程】:  
# 1. Agent 接收到用户输入:"thank you!"  
# 2. Agent 从 checkpointer 加载历史:知道之前查询了罗马的天气  
# 3. Agent 思考:用户在表示感谢,不需要调用工具  
# 4. Agent 直接生成双关语回应  
# 5. Agent 按照 ResponseFormat 格式化输出  
# 6. 注意:weather_conditions 为 None,因为这次没有查询天气  
  
# 打印结构化响应  
pprint(response['structured_response'])  
  
# 预期输出类似:  
# ResponseFormat(  
#     punny_response="不客气!很高兴能为你'预报'一个阳光灿烂的心情!",  
#     weather_conditions=None  # 这次没有天气信息  
# )  
  
# ============================================================================  
# 总结:完整的数据流  
# ============================================================================  
"""  
用户输入  
  ↓Agent(LLM) → 思考需要什么信息  
  ↓需要位置信息?  
  ↓ Yes调用 get_user_location(runtime.context.user_id="1") → 返回 "罗马"  
  ↓调用 get_weather_for_location(city="罗马") → 返回 "罗马 今天是晴天。"  
  ↓Agent 生成双关语回应  
  ↓按照 ResponseFormat 格式化输出  
  ↓返回给用户 ResponseFormat(punny_response, weather_conditions)  
对话历史被 checkpointer 保存,下次对话可以继续...  
"""  
  
# ============================================================================  
# 扩展:如何测试不同的用户  
# ============================================================================  
  
# 如果想测试不同用户,只需改变 user_id:  
# context = Context(user_id="2")  # 会返回 "西安" 而不是 "罗马"  
# response = agent.invoke(  
#     {"messages": [{"role": "user", "content": "天气怎么样?"}]},  
#     config=config,  
#     context=Context(user_id="2")  
# )

Demo 的核心概念和工作原理:

核心概念图解

  ┌─────────────────────────────────────────────────────────────────┐
  │                        LangChain Agent 架构                      │
  ├─────────────────────────────────────────────────────────────────┤
  │                                                                 │
  │  用户输入 ──→ Agent ──→ LLM ──→ 思考                          │
  │                    ↓                                          │
  │              需要外部信息?                                     │
  │                    ↓ Yes                                      │
  │              调用 Tool ──→ 获取结果                           │
  │                    ↓                                          │
  │              继续思考 ──→ 生成响应                            │
  │                    ↓                                          │
  │            结构化输出 ──→ 返回给用户                          │
  │                                                                 │
  │  伴随过程:                                                     │
  │  ├─ Context: 传递运行时数据(如 user_id)                    │
  │  ├─ Checkpointer: 保存对话历史                                │
  │  └─ ResponseFormat: 规范输出格式                              │
  └─────────────────────────────────────────────────────────────────┘

关键组件说明

┌────────────────┬────────────────────────────────┬───────────┐
│      组件      │              作用               │   类比    │
├────────────────┼────────────────────────────────┼───────────┤
│ Agent          │ 决定调用什么工具、如何响应用户      │ 指挥官    │
├────────────────┼────────────────────────────────┼───────────┤
│ Tool           │ Agent 可以调用的函数             │ 士兵/工具 │
├────────────────┼────────────────────────────────┼───────────┤
│ LLM            │ 进行推理的"大脑"                 │ 智能大脑  │
├────────────────┼────────────────────────────────┼───────────┤
│ Context        │ 传递运行时数据(不暴露给LLM)      │ 秘密情报  │
├────────────────┼────────────────────────────────┼───────────┤
│ Checkpointer   │ 保存对话历史                     │ 记忆本    │
├────────────────┼────────────────────────────────┼───────────┤
│ ResponseFormat │ 规范输出格式                     │ 表格模板  │
└────────────────┴────────────────────────────────┴───────────┘

代码执行流程

  第一次调用:"外面天气怎么样?"
  ├─ Agent 思考:用户没说位置
  ├─ 调用 get_user_location(user_id="1") → "罗马"
  ├─ 调用 get_weather_for_location("罗马") → "罗马 今天是晴天"
  ├─ Agent 生成双关语 + 格式化输出
  └─ 返回 ResponseFormat(punny_response, weather_conditions)

  第二次调用:"thank you!"(相同 thread_id)
  ├─ Agent 从记忆加载历史:知道之前问了罗马天气
  ├─ Agent 思考:用户感谢,不需要调用工具
  ├─ Agent 直接生成双关语
  └─ 返回 ResponseFormat(punny_response, weather_conditions=None)

为什么要这样设计?

  1. 工具化(Tools):LLM 不能实时获取数据(如天气),通过工具扩展能力
  2. 上下文(Context):传递敏感数据(如用户ID)给工具,但不暴露给LLM
  3. 记忆(Checkpointer):支持多轮对话,Agent 能"记住"之前的交流
  4. 结构化输出:便于程序处理响应,而不是解析自然语言文本

关键点学习

ChatOpenAI

概览

ChatOpenAI 是 LangChain 提供的核心类,用于与兼容 OpenAI API 格式的大语言模型(LLM)进行交互。该类封装了所有与 LLM 通信所需的逻辑,包括请求构建、响应解析、错误处理等。

架构示意

┌─────────────────────────────────────────────────────────────────┐
│                    ChatOpenAI 架构                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   应用层                                                            │
│   ┌─────────────────────────────────────────────────────┐        │
│   │  ChatOpenAI 实例                                     │        │
│   │  ├─ 配置参数 (model, temperature, etc.)             │        │
│   │  ├─ 请求构建                                        │        │
│   │  └─ 响应解析                                        │        │
│   └─────────────────────────────────────────────────────┘        │
│                        │                                       │
│                        ↓                                       │
│   协议层                                                            │
│   ┌─────────────────────────────────────────────────────┐        │
│   │  HTTP/HTTPS 请求 (OpenAI API 格式)                   │        │
│   └─────────────────────────────────────────────────────┘        │
│                        │                                       │
│                        ↓                                       │
│   服务层                                                            │
│   ┌─────────────────────────────────────────────────────┐        │
│   │  LLM 服务提供商                                          │        │
│   │  ├─ OpenAI / 智谱AI / 通义千问 / DeepSeek / ...    │        │
│   └─────────────────────────────────────────────────────┘        │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

语法

ChatOpenAI(
    model: str,  # 模型名称 (必填)
    api_key: Optional[str] = None,  # API认证密钥
    base_url: Optional[str] = None,  # API基础URL
    temperature: float = 0.7,  # 输出随机性(0-2),值越低越确定,越高越发散
    max_tokens: Optional[int] = None,  # 最大输出token数
    timeout: Optional[float] = None,  # 请求超时时间(秒)
    max_retries: int = 2,  # 失败重试次数
    api_version: Optional[str] = None,  # API版本
    organization: Optional[str] = None,  # 组织ID
    n: int = 1,  # 生成候选数量
    top_p: float = 1.0,  # nucleus采样参数(0-1)
    presence_penalty: float = 0,  # 存在惩罚(-2到2)
    frequency_penalty: float = 0,  # 频率惩罚(-2到2)
    streaming: bool = False,  # 是否启用流式输出
    stop: Optional[Union[str, List[str]]] = None,  # 停止序列
    stop_sequences: Optional[List[str]] = None,  # 停止序列(别名)
    model_kwargs: Optional[Dict[str, Any]] = None,  # 额外模型参数
    seed: Optional[int] = None,  # 随机种子
    tiktoken_model_name: Optional[str] = None,  # token计数模型名称
    cache: Optional[BaseCache] = None,  # 缓存实例
) -> ChatOpenAI

create_agent()

概览

create_agent() 是 LangChain 提供的函数,用来创建一个智能代理(Agent)。Agent 是一个可以自动决定调用哪些工具来解决问题的程序。

参数说明

create_agent( 
	llm: BaseLanguageModel, # 大模型实例(必填) 
	tools: list[BaseTool], # 工具列表(必填) 
	prompt: ChatPromptTemplate = None, # 提示词模板(可选) 
	**kwargs # 扩展参数 
) -> Runnable:

agent.invoke()

概述

agent.invoke() 是 LangChain Agent 的核心调用方法,用于与 Agent 交互并执行任务。它接收用户输入,让 Agent 使用工具进行推理和执行,最后返回结果。

参数说明

agent.invoke(
    input: Union[str, Dict[str, Any]],
    config: Optional[AgentExecutorConfig] = None,
    *,
    **kwargs: Any
) -> AgentExecutorOutput
posted @ 2026-04-08 23:41  xiong4110  阅读(41)  评论(0)    收藏  举报