Skip to content

17 context上下文

前面的文章中,我们学了工具、记忆、中间件、Runtime等等。现在把这些东西串起来,聊一个更根本的问题:怎么让Agent更可靠?

Agent失败的原因通常有两种:

  1. 模型本身能力不够
  2. 没有给模型正确的上下文

实际项目中,第二种原因占了大多数。模型其实挺聪明的,但如果你没给它正确的信息、正确的工具、正确的提示词,它就会做出错误的决定。

上下文工程就是解决这个问题的——以正确的格式,在正确的时机,给Agent正确的信息和工具。这是构建可靠Agent最重要的工作。

一、三种上下文类型

Agent执行过程中,你可以控制三类上下文:

类型控制什么生命周期
模型上下文系统提示词、消息、工具、模型、输出格式瞬态(每次调用独立)
工具上下文工具能读写什么数据持久(写入后持久保存)
生命周期上下文模型调用和工具调用之间发生什么持久(影响后续步骤)

二、三种数据来源

这三类上下文都可以从三个数据来源获取信息:

数据来源作用域举例
Runtime Context单次调用用户ID、角色、API密钥
State单次对话当前消息列表、上传的文件
Store跨对话用户偏好、历史记忆

三、模型上下文

模型上下文控制每次LLM调用时它能看到什么。这是影响Agent决策质量最直接的因素。

3.1 系统提示词

系统提示词是给LLM的"人设"和"工作指南"。不同用户、不同场景需要不同的提示词。

根据用户角色动态生成:

python
from dataclasses import dataclass
from langchain.agents import create_agent
from langchain.agents.middleware import wrap_model_call, ModelRequest, ModelResponse
from typing import Callable


@dataclass
class Context:
    user_role: str
    user_name: str


@wrap_model_call
def dynamic_system_prompt(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    user_role = request.runtime.context.user_role
    user_name = request.runtime.context.user_name

    prompts = {
        "admin": f"你是管理员助手,正在和管理员 {user_name} 对话。你可以执行所有操作。",
        "user": f"你是用户助手,正在和 {user_name} 对话。你只能查询信息。",
        "guest": "你是访客助手,只能查看公开信息。",
    }

    system_prompt = prompts.get(user_role, prompts["guest"])

    # 根据对话长度调整
    if len(request.messages) > 10:
        system_prompt += "\n对话已经很长了,请尽量简洁回答。"

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


agent = create_agent(
    model="deepseek-v4-flash",
    tools=[...],
    middleware=[dynamic_system_prompt],
    context_schema=Context,
)

3.2 消息

消息是LLM看到的对话历史。你可以往消息里注入额外的上下文信息。

注入用户上传的文件信息:

python
@wrap_model_call
def inject_file_context(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    uploaded_files = request.state.get("uploaded_files", [])

    if uploaded_files:
        file_info = "\n".join(
            f"- {f['name']} ({f['type']}): {f['summary']}"
            for f in uploaded_files
        )
        context_message = {
            "role": "user",
            "content": f"当前可用的文件:\n{file_info}\n\n回答问题时可以参考这些文件。"
        }
        messages = [*request.messages, context_message]
        return handler(request.override(messages=messages))

    return handler(request)

注入用户的写作风格偏好(从长期记忆中读取):

python
@wrap_model_call
def inject_writing_style(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    user_id = request.runtime.context.user_id
    store = request.runtime.store

    if store:
        style = store.get(("writing_style",), user_id)
        if style:
            style_guide = (
                f"写作风格要求:\n"
                f"- 语气: {style.value.get('tone', '专业')}\n"
                f"- 称呼: {style.value.get('greeting', '你好')}\n"
                f"- 署名: {style.value.get('sign_off', '祝好')}"
            )
            messages = [*request.messages, {"role": "user", "content": style_guide}]
            return handler(request.override(messages=messages))

    return handler(request)

3.3 工具

工具太多会让LLM选不好,工具太少又限制了能力。根据场景动态选择工具:

python
@wrap_model_call
def select_tools_by_context(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    user_role = request.runtime.context.user_role

    if user_role == "admin":
        pass  # 管理员可以用所有工具
    elif user_role == "editor":
        # 编辑不能删除
        tools = [t for t in request.tools if t.name != "delete_data"]
        return handler(request.override(tools=tools))
    else:
        # 访客只能用查询工具
        tools = [t for t in request.tools if t.name.startswith("query_")]
        return handler(request.override(tools=tools))

    return handler(request)

根据对话阶段选择工具:

python
@wrap_model_call
def evolve_tools(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    is_authenticated = request.state.get("authenticated", False)
    message_count = len(request.messages)

    tools = request.tools

    if not is_authenticated:
        # 未认证只能用公开工具
        tools = [t for t in tools if t.name.startswith("public_")]
    elif message_count < 5:
        # 对话初期限制高级工具
        tools = [t for t in tools if t.name != "advanced_search"]

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

3.4 模型

不同场景可以用不同模型,平衡成本和效果:

python
from langchain.chat_models import init_chat_model

# 预初始化模型
large_model = init_chat_model("deepseek-v3")
standard_model = init_chat_model("deepseek-v4-flash")
budget_model = init_chat_model("deepseek-v4-flash")

@wrap_model_call
def dynamic_model(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    message_count = len(request.messages)

    if message_count > 20:
        model = large_model       # 长对话用大模型
    elif message_count > 10:
        model = standard_model    # 中等对话用标准模型
    else:
        model = budget_model      # 短对话用经济模型

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

3.5 输出格式

根据场景动态选择输出格式:

python
from pydantic import BaseModel, Field

class SimpleResponse(BaseModel):
    """简单回复"""
    answer: str = Field(description="简短回答")

class DetailedResponse(BaseModel):
    """详细回复"""
    answer: str = Field(description="详细回答")
    reasoning: str = Field(description="推理过程")
    confidence: float = Field(description="置信度 0-1")

@wrap_model_call
def dynamic_output_format(
    request: ModelRequest,
    handler: Callable[[ModelRequest], ModelResponse],
) -> ModelResponse:
    user_role = request.runtime.context.user_role

    if user_role == "admin":
        # 管理员看到详细信息
        return handler(request.override(response_format=DetailedResponse))
    else:
        # 普通用户看到简洁信息
        return handler(request.override(response_format=SimpleResponse))

四、工具上下文

工具不仅能返回结果给LLM,还能读写各种数据源。

4.1 读取上下文

工具可以通过ToolRuntime读取Runtime Context、State和Store:

python
from langchain.tools import tool, ToolRuntime

@tool
def get_user_orders(runtime: ToolRuntime[Context]) -> str:
    """获取当前用户的订单"""
    # 读取Runtime Context
    user_id = runtime.context.user_id

    # 读取State
    is_authenticated = runtime.state.get("authenticated", False)
    if not is_authenticated:
        return "请先登录"

    # 读取Store
    if runtime.store:
        prefs = runtime.store.get(("preferences",), user_id)
        if prefs:
            # 根据用户偏好调整查询
            pass

    return f"用户 {user_id} 的订单: [...]"

4.2 写入上下文

工具可以通过Command更新State,通过store写入长期记忆:

python
from langchain.tools import tool, ToolRuntime
from langgraph.types import Command

@tool
def authenticate(password: str, runtime: ToolRuntime) -> Command:
    """验证用户密码"""
    if password == "correct":
        # 写入State:标记为已认证
        return Command(update={"authenticated": True})
    return Command(update={"authenticated": False})


@tool
def save_preference(key: str, value: str, runtime: ToolRuntime) -> str:
    """保存用户偏好到长期记忆"""
    user_id = runtime.context.user_id
    if runtime.store:
        runtime.store.put(("preferences",), user_id, {key: value})
        return f"偏好已保存: {key} = {value}"
    return "存储不可用"

五、生命周期上下文

用中间件在模型调用和工具调用之间插入处理逻辑。

5.1 消息摘要

对话太长时自动压缩,节省token:

python
from langchain.agents import create_agent
from langchain.agents.middleware import SummarizationMiddleware

agent = create_agent(
    model="deepseek-v4-flash",
    tools=[...],
    middleware=[
        SummarizationMiddleware(
            max_tokens=4000,  # 超过4000 token时触发摘要
        ),
    ],
)

5.2 安全护栏

在工具执行前检查安全性:

python
from langchain.agents import AgentState
from langchain.agents.middleware import before_model

@before_model
def safety_check(state: AgentState, runtime) -> dict | None:
    last_message = state["messages"][-1].content

    # 检查是否包含敏感操作
    dangerous_keywords = ["DELETE", "DROP", "TRUNCATE"]
    if any(keyword in last_message.upper() for keyword in dangerous_keywords):
        print("[安全警告] 检测到潜在的危险操作")

    return None

5.3 审计日志

记录每次模型调用的上下文:

python
from langchain.agents import AgentState
from langchain.agents.middleware import after_model

@after_model
def audit_log(state: AgentState, runtime) -> dict | None:
    user_id = runtime.context.user_id
    last_response = state["messages"][-1].content

    print(f"[审计] 用户: {user_id}")
    print(f"[审计] 模型响应: {last_response[:100]}...")

    return None

六、瞬态vs持久

一个重要区分:

  • 模型上下文是瞬态的:用wrap_model_call修改的消息、工具、提示词只影响当前这次调用,不会改变State中保存的数据
  • 生命周期上下文是持久的:用before_modelafter_model等钩子修改的State会永久保存
python
# 瞬态:只改当前调用看到的消息,不改State
@wrap_model_call
def transient_change(request, handler):
    messages = [*request.messages, {"role": "user", "content": "临时注入"}]
    return handler(request.override(messages=messages))

# 持久:修改State,影响后续所有调用
@after_model
def persistent_change(state, runtime):
    return {"some_key": "永久保存的值"}

七、最佳实践

  1. 从简单开始:先用静态提示词和固定工具,确认基本功能正常
  2. 逐步增加动态性:一次只加一个上下文工程特性,测试通过再加下一个
  3. 监控性能:关注模型调用次数、token消耗、响应延迟
  4. 用内置中间件SummarizationMiddlewareHumanInTheLoopMiddleware等已经实现了常见的上下文管理逻辑
  5. 文档化你的上下文策略:清楚记录每个Agent看到了什么信息、为什么

八、总结

上下文工程是构建可靠Agent的核心:

  • 模型上下文:控制系统提示词、消息、工具、模型、输出格式
  • 工具上下文:工具可以从State、Store、Runtime读取数据,也可以写入
  • 生命周期上下文:用中间件在步骤之间做摘要、护栏、日志
  • 三种数据来源:Runtime Context(静态)、State(短期)、Store(长期)

掌握了上下文工程,你就掌握了让Agent从"能用"变成"好用"的关键。


这是本系列的最后一篇文章。回顾一下我们学过的内容:

  1. 创建第一个Agent
  2. 模型组件
  3. 记忆组件
  4. Tool组件
  5. 多轮对话
  6. Agent状态
  7. 事件流
  8. 流式输出
  9. 结构化输出
  10. 中间件
  11. 自定义中间件
  12. RAG检索
  13. MCP
  14. 多Agent
  15. 人在环路
  16. Runtime运行时信息
  17. 上下文工程

从最基础的Agent创建,到最核心的上下文工程,你已经掌握了LangChain v1的完整知识体系。接下来就是动手实践了——选一个你感兴趣的场景,从零开始构建一个Agent,遇到问题再回来查对应的文章。祝你构建出可靠的AI应用!