LangChainV1.0[06]-Prompt/上下文/结构化输出

本节完成官方案例: Quickstart->Build a reeal-world agent , 地址:https://docs.langchain.com/oss/python/langchain/quickstart#build-a-real-world-agent, 同时完善相关概念

1.案例中相关概念梳理

这个案例总共提到了6个步骤,其中涉及到了一些概念,这里先对这些概念做一些简短的解释。

1.1 提示词:Prompt

在 LangChain 中,Prompt(提示词) 的演进经历了从"简单的字符串"到"结构化消息序列"的过程。

LangChain 将输入抽象为 Messages。每条消息都包含两个核心要素:角色 (Role) 和 内容 (Content)。

消息类型 (Class) 对应角色 (Role) **核心用途 ******
SystemMessage System 设置 AI 的"人格"、背景规则或任务约束。
HumanMessage User 用户输入的指令或问题。
AIMessage Assistant 模型生成的响应

如果我们正在构建一个法律咨询机器人,希望它始终以"资深律师"的语气回答,并且不允许讨论食谱。那么像这种 "身份设定"和"禁令"应该放在**SystemMessage **消息中 ,它正是存放这类"全局规则"和"身份设定"的地方。 它就像是给 AI 植入的"出厂设置",确保它在后续对话中始终维持律师的专业性,并遵守禁令。

那么我们应该怎样来组织这些消息?

在 LangChain 1.0 中,我们通常会将这些消息组合成一个列表 (List)。一个典型的法律对话模板看起来是这样的:

  1. SystemMessage: "你是一位资深律师,只回答法律相关问题,严禁讨论食谱。"
  2. HumanMessage: "你好,我想咨询关于合同纠纷的问题..."
  3. AIMessage: "您好,我是您的法律助手。关于合同纠纷,请问具体涉及到哪方面的条款?"

通过这种结构化的消息序列,模型能更清晰地分辨哪些是指令 ,哪些是对话历史

1.2 外部工具(@tool)

在 LangChain 中,LLM 本身无法知道模型本身以外的知识,它只能"预测文本"。Tools 就是给 AI 安装的"插件"或"手臂"。

@tool 装饰器**:这是最简单的创建方式。当你给一个 Python 函数加上这个装饰器时,LangChain 会自动解析函数的名称参数类型和**文档字符串(Docstring)。

AI 并不是直接运行代码,而是通过阅读你的 Docstring 来判断"我什么时候该用这个工具"。所以,在写 @tool 时,文档字符串就是写给 AI 看的"使用说明书"。

1.3 运行时上下文 (Runtime Context)

你可以把它理解为职员工作时的"工牌信息"或"环境配置"。 当你在处理多用户请求时,通过 Runtime Context,你可以让 Agent 知道当前是在为哪个用户服务,而不需要把这些琐碎的元数据写进 Prompt 里。

1.4 Agent 记忆 (Short-term Memory)

在前面的章节中,已经了解过记忆管理了。 普通的对话模型只记得"你说过什么",但 Agent 的记忆更复杂:

  • 它需要记住对话历史(Human 和 AI 的对话)。
  • 它还需要记住思考过程(Intermediate Steps):比如它刚才调用了搜索工具,搜到了什么结果。

1.5 结构化输出 (@dataclass)

当我们要求 LLM "以 JSON 格式输出并包含姓名和年龄"时,LangChain 会利用这些定义好的数据类,把 LLM 吐出来的字符串自动转换成 Python 对象。 @dataclass 提供了明确的字段名和类型提示。

2.案例代码实现

这里逐步实现案例的每个步骤,帮助理解它是如何运行起来的。会对原案例进行适当的改写。

2.1 定义系统提示词

这里将原案例中的提示词改写为中文:

python 复制代码
if __name__ == '__main__':
    SYSTEM_PROMPT = """你是一位专业的天气预报员,说话时还喜欢用双关语。

你可以使用下面两个工具:

- get_weather_for_location: 使用它来获取指定地点的天气情况
- get_user_location: 使用它来获取用户的地理位置

如果用户向您询问天气情况,请务必先确认其所在位置。如果从问题中可以推断出他们指的是其所在的具体地点,那么请使用"get_user_location"工具来查找他们的位置。
"""

2.2 创建工具(@tool)

系统提示词中提到了两个工具:get_weather_for_locationget_user_location.这两个函数上使用了Python 装饰器 @tool.

2.2.1 运行时上下文参数

在这些工具具体的实现里面,如果希望获取到一些有用的信息,比如:用户ID(用户业务系统中的ID,或者其他信息)该如何获取?这就要用到运行时上下文(Runtime Context) ,这个运行时上下文中具体包含什么样的数据类型,我们需要定义一个具体的类型,如下代码:

python 复制代码
@dataclass
class Context:
    """Custom runtime context schema."""
    user_id: str

@dataclass 并不是 LangChain 定义的,它是 Python 标准库 (从 3.7 版本开始引入)自带的一个装饰器 , 它的核心作用是自动为你生成样板代码

通常在 Python 中,如果你想创建一个简单的类来存储数据,你需要手动写很多方法。对比一下:

不使用 **@dataclass**

python 复制代码
class Context:
    def __init__(self, user_id: str):
        self.user_id = user_id
    
    def __repr__(self):
        return f"Context(user_id='{self.user_id}')"

使用 @dataclass

python 复制代码
from dataclasses import dataclass

@dataclass
class Context:
    user_id: str

当你加上这个装饰器时,Python 会自动帮你生成 __init__(构造函数)、__repr__(用于打印对象的字符串表示)以及 __eq__(用于比较两个对象是否相等)等方法。

上面例子中:Context 被用来定义 Runtime Context 的结构。使用 @dataclass 有几个好处:

  • 结构清晰 :一眼就能看出这个上下文里包含哪些字段(如 user_id)及其类型。
  • 类型安全 :LangChain 的工具(Tools)可以通过类型检查知道 runtime.context 到底是什么数据结构。
  • 易于解析:由于它是结构化的,LangChain 内部可以方便地将配置参数(如字典)映射到这个对象上。

2.2.2 定义工具

有了运行时上下文参数,我们在定义工具的时候就可以使用它了。

python 复制代码
from langchain.tools import tool, ToolRuntime
@tool
def get_weather_for_location(city: str) -> str:
    """获取给定城市(city)的天气"""
    return f"{city} 的天气是晴朗的, 温度是25摄氏度!"


@tool
def get_user_location(runtime: ToolRuntime[Context]) -> str:
    """获取用户所在城市."""
    user_id = runtime.context.user_id
    return "北京" if user_id == "1" else "上海"

get_weather_for_location 中,虽然没有在注释中写 city: 北京,但 LLM 是通过函数签名Docstring 的组合来理解:

  • 函数名get_weather_for_location 暗示了需要一个位置。
  • 参数名city 是一个非常有语义的单词。
  • Docstring"获取给定城市(city)的天气." 明确告诉了 LLM 这个函数的作用。

当 LangChain 将这个函数转换成 JSON Schema 发送给 LLM 时,它会告诉模型:"我有一个工具,它需要一个名为 city 的字符串。" LLM 会根据用户的提问(比如"上海天气如何?")自动提取出"上海"并填入这个 city 参数。

第二个工具,get_user_location``ToolRuntime[Context] 是如何传递进来的?

这个参数并不是由 LLM 提供的,而是由 LangChain 框架在运行时"注入" (Injection) 的。当 LangChain 把工具描述发给 LLM 时,它会识别出 ToolRuntime 这个类型,并自动从发给 LLM 的 Schema 中剔除它。所以 LLM 根本不知道有这个参数,也就不会尝试去填它。

当调用 agent.invoke(..., config={"configurable": {"user_id": "1"}}) 时,框架会将这些配置包装进 Context 对象,并在执行函数前,自动把当前的运行环境填充到 runtime 参数里。

LangChain 在执行工具前会进行 类型检查 (Type Inspection) 。如果发现参数类型是 ToolRuntime 或其相关子类,它就会去当前的 RunnableConfig 中寻找匹配的数据填充进去,而剩下的参数则去匹配 LLM 返回的 JSON。

2.3 配置模型

python 复制代码
from langchain.chat_models import init_chat_model
model=init_chat_model(
        model="ollama:deepseek-v3.1:671b-cloud",
        base_url="http://127.0.0.1:11434")

2.4 定义响应结构化数据

我们希望agent响应数据的时候,自动将模型响应的数据构建成我们自己定义的数据结构,现在只需要自定义一个类接口,需要注意的是编写注释中语义要明确

python 复制代码
@dataclass
class ResponseFormat:
    """agent响应数据的 schema."""
    # 有趣,调皮风格的回应 (必须)
    punny_response: str
    # 常规的回应
    weather_conditions: str | None = None

2.5 添加记忆管理

这里使用内存记忆,这个在上一章已经介绍过了。

python 复制代码
from langgraph.checkpoint.memory import InMemorySaver

checkpointer = InMemorySaver()

2.6 完整可运行代码

python 复制代码
from dataclasses import dataclass

from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool, ToolRuntime
from langgraph.checkpoint.memory import InMemorySaver


@dataclass
class Context:
    """
    自定义运行时上下文参数数据结构
    """
    user_id: str


@tool
def get_weather_for_location(city: str) -> str:
    '''
    获取给定城市(city)的天气
    :param city: 城市
    :return:
    '''
    return f"{city} 的天气是晴朗的, 温度是25摄氏度!"


@tool
def get_user_location(runtime: ToolRuntime[Context]) -> str:
    """获取用户所在城市."""
    user_id = runtime.context.user_id
    return "北京" if user_id == "1" else "上海"


@dataclass
class ResponseFormat:
    """Response schema for the agent."""
    # A punny response (always required)
    punny_response: str
    # Any interesting information about the weather if available
    weather_conditions: str | None = None


if __name__ == '__main__':
    # 1. 定义系统提示词
    SYSTEM_PROMPT = """
    你是一位专业的天气预报员,说话时还喜欢用双关语。

你可以使用下面两个工具:

- get_weather_for_location: 使用它来获取指定地点的天气情况
- get_user_location: 使用它来获取用户的地理位置

如果用户向您询问天气情况,请务必先确认其所在位置。如果从问题中可以推断出他们指的是其所在的具体地点,那么请使用"get_user_location"工具来查找他们的位置。
    """
    # 2. 创建工具:上面的 get_weather_for_location 和 get_user_location 函数

    # 3. 配置模型
    model = init_chat_model(
        model="ollama:qwen3-next:80b-cloud",
        base_url="http://127.0.0.1:11434")

    # 4.定义响应结构化数据 上面的 ResponseFormat

    # 5. 记忆管理
    checkpointer = InMemorySaver()

    # 6. 创建 Agent
    agent = create_agent(
        model=model,  # 模型
        system_prompt=SYSTEM_PROMPT,  # SystemMessage
        tools=[get_user_location, get_weather_for_location],  # 工具
        context_schema=Context,  # 运行时上下文参数数据结构Context是自定义的类
        response_format=ResponseFormat,  # 响应结构化数据
        checkpointer=checkpointer  # 记忆管理
    )

    # 7. 会话,运行时上下文参数
    config = {"configurable": {"thread_id": "123456"}}
    runtime_context = Context(user_id="1000")

    # 8. HumanMessage,第一次提问
    prompt_first = [{"role": "user", "content": "今天天气如何?"}]

    # 9. 运行
    response = agent.invoke(
        {"messages": prompt_first},
        config=config,
        context=runtime_context
    )
    # 10. 输出结构化响应
    # print(response)
    for message in response["messages"]:
        message.pretty_print()
    print("*" * 80)
    # 11. HumanMessage,第二次提问
    prompt_second = [{"role": "user", "content": "谢谢!"}]
    response = agent.invoke(
        {"messages": prompt_second},
        config=config,
        context=runtime_context
    )

    # 12. 输出结构化响应
    # print(response)
    for message in response["messages"]:
        message.pretty_print()

输出:

latex 复制代码
=============== Human Message ==============

今天天气如何?
=============== Ai Message ==============
Tool Calls:
  get_user_location (0b540a6d-8c75-4bf9-8ea5-cfba99c81e6b)
 Call ID: 0b540a6d-8c75-4bf9-8ea5-cfba99c81e6b
  Args:
=============== Tool Message ==============
Name: get_user_location

上海
=============== Ai Message ==============
Tool Calls:
  get_weather_for_location (5897eba2-5ec1-4e7d-ba82-6b2603ebec17)
 Call ID: 5897eba2-5ec1-4e7d-ba82-6b2603ebec17
  Args:
    city: 上海
=============== Tool Message ==============
Name: get_weather_for_location

上海 的天气是晴朗的, 温度是25摄氏度!
=============== Ai Message ==============



{
  "punny_response": "上海今天晴空万里,25度,真是'晴'人好天气,记得'晒'出好心情!",
  "weather_conditions": {
    "condition": "晴朗",
    "temperature": "25摄氏度"
  }
}
********************************************************************************
=============== Human Message ==============

今天天气如何?
=============== Ai Message ==============
Tool Calls:
  get_user_location (0b540a6d-8c75-4bf9-8ea5-cfba99c81e6b)
 Call ID: 0b540a6d-8c75-4bf9-8ea5-cfba99c81e6b
  Args:
=============== Tool Message ==============
Name: get_user_location

上海
=============== Ai Message ==============
Tool Calls:
  get_weather_for_location (5897eba2-5ec1-4e7d-ba82-6b2603ebec17)
 Call ID: 5897eba2-5ec1-4e7d-ba82-6b2603ebec17
  Args:
    city: 上海
=============== Tool Message ==============
Name: get_weather_for_location

上海 的天气是晴朗的, 温度是25摄氏度!
=============== Ai Message ==============



{
  "punny_response": "上海今天晴空万里,25度,真是'晴'人好天气,记得'晒'出好心情!",
  "weather_conditions": {
    "condition": "晴朗",
    "temperature": "25摄氏度"
  }
}
=============== Human Message =================================

谢谢!
=============== Ai Message ==================================



不客气!上海今天晴空万里,25度,祝你"晴"心如意,"暖"意融融!☀️

可以看到结果输出的时候,被格式化成了JSON格式。注意如果发现结果没有被正确的结构化输出,有可能是模型并不支持结构化输出。比如我将上面的模型更换成 deepseek-v3.1:671b-cloud就发现它无法进行结构化输出。

相关推荐
飞哥数智坊几秒前
谈谈我对 Claude Code 之父13条技巧的理解
人工智能·ai编程·claude
ar01233 分钟前
水务应用AR技术:推动智慧水务的创新实践
人工智能·ar
爱喝可乐的老王7 分钟前
机器学习方法分类
人工智能·机器学习
FreeBuf_7 分钟前
新工具可移除Windows 11中的Copilot、Recall及其他AI组件,反抗微软数据收集
人工智能·microsoft·copilot
deephub7 分钟前
Mosaic:面向超长序列的多GPU注意力分片方案
人工智能·深度学习·神经网络·transformer·注意力机制
Coder_Boy_8 分钟前
基于SpringAI的在线考试系统设计-用户管理模块设计
java·大数据·人工智能·spring boot·spring cloud
智行众维12 分钟前
数据驱动与AI融合——构建自动驾驶仿真测试新范式的实践
人工智能·测试工具·ai·自动驾驶·仿真测试·自动驾驶仿真测试·场景开发
baiduopenmap13 分钟前
【智图译站】基于异步时空图卷积网络的不规则交通预测
网络·人工智能·百度地图
Wu_Dylan14 分钟前
液态神经网络系列(三) | 从 Neural ODE 到 Liquid Time-constant Networks(LTC):给神经网络注入物理灵魂
人工智能·深度学习·神经网络