上下文工程(Context Engineering)生产落地指南

3 阅读18分钟

版本:2026.03 | 面向:有实际落地需求的工程团队 | 聚焦:LLM 应用的上下文设计与管理


目录

  1. 核心认知:什么是上下文工程
  2. 上下文的五个来源
  3. 上下文窗口管理策略
  4. Prompt 工程的生产规范
  5. 多轮对话的上下文设计
  6. Agent 场景的上下文管理
  7. 上下文压缩与摘要技术
  8. 成本与延迟优化
  9. 评估与可观测性
  10. 常见生产问题与根本解法

1. 核心认知:什么是上下文工程

不是 Prompt 工程的升级,是范式转变

"Prompt 工程"关注的是如何写好一条指令。"上下文工程"关注的是如何向 LLM 的上下文窗口填入正确的信息——包括指令、事实、示例、历史、工具结果、外部记忆,以及关键的:决定哪些信息不放进去

Andrej Karpathy 在 2025 年将上下文工程定义为:

"The delicate art and science of filling the context window with just the right information."

这个定义揭示了两个关键点:

  • 信息密度:上下文窗口是稀缺资源,填什么、不填什么同样重要
  • 动态性:正确的信息依赖于当前任务的状态,不是静态模板

为什么现在变得关键

模型能力已经足够强——大多数生产问题不是模型"不够聪明",而是模型没有获得完成任务所需的信息,或者被无关信息干扰了。上下文工程是当前 LLM 应用工程化的核心瓶颈。

上下文工程的核心张力

完整性  vs  简洁性
─────────────────────────────────
信息越完整,模型越准确
信息越冗余,模型越慢、越贵、越容易分心

没有普适最优解,只有针对具体任务的权衡决策。


2. 上下文的五个来源

理解上下文从哪里来,是设计上下文管理系统的基础。

2.1 写入上下文(Write to Context)

直接存在于当前对话请求中的内容:

类型内容位置
System Prompt角色定义、行为规则、输出格式messages[0].role=system
User Message用户当前输入messages[-1].role=user
Few-shot 示例输入输出示例对system 或 user 中
工具定义可调用工具的 schematools 字段

2.2 检索到上下文(Retrieved into Context)

通过检索机制动态注入的内容:

  • 向量检索:从知识库按语义相似度检索的文档片段(参见 RAG 落地指南)
  • 关键词检索:BM25 精确匹配的文档段落
  • 结构化查询:从数据库/API 实时拉取的结构化数据
  • 工具调用结果:Agent 执行工具后返回的结果

2.3 外部记忆(External Memory)

不在上下文窗口内,但可以被检索进来的持久化信息:

短期记忆(Session Memory):当前会话的关键事实摘要
长期记忆(Long-term Memory):用户偏好、历史决策、领域知识
实体记忆(Entity Memory):对话中出现的人、物、概念的属性

2.4 内部记忆(In-weights Knowledge)

模型训练时编码进权重的知识。无法在推理时修改,只能通过微调更新。生产中的关键认知:不要依赖模型的内部知识处理私域数据,要通过上下文显式提供。

2.5 上下文内计算(In-context Computation)

让模型在生成最终答案之前先进行推理:

  • Chain-of-Thought:让模型先写出思考过程
  • 工具调用中间步骤:ReAct 模式的 Think→Act→Observe 循环
  • 草稿区:让模型先生成草稿再润色

3. 上下文窗口管理策略

3.1 窗口容量规划

2026 年主流模型的上下文窗口:

模型上下文窗口实际可用建议
Claude Sonnet/Opus 4.x200K tokens≤ 100K(避免"中间丢失"问题)
GPT-4o128K tokens≤ 80K
Gemini 2.x1M tokens≤ 500K(但成本高)

"Lost in the Middle" 问题:研究证实,模型对上下文中间位置的信息注意力显著下降。关键信息应放在开头(system prompt)或结尾(最近的 user message),避免堆在中间。

3.2 上下文分区设计

推荐的上下文分区结构(按 token 预算分配):

┌─────────────────────────────────────┐
│  System Prompt (固定,~2K tokens)   │
│  - 角色定义                          │
│  - 核心行为规则                       │
│  - 输出格式要求                       │
├─────────────────────────────────────┤
│  静态知识注入 (~5K tokens)           │
│  - 领域术语表                         │
│  - 固定业务规则                       │
│  - 必要的 few-shot 示例              │
├─────────────────────────────────────┤
│  动态检索内容 (~20K tokens)          │
│  - 本次查询相关文档                   │
│  - 工具调用结果                       │
│  - 外部记忆检索结果                   │
├─────────────────────────────────────┤
│  对话历史 (~8K tokens)               │
│  - 经压缩/摘要的历史对话              │
├─────────────────────────────────────┤
│  当前用户输入 (~2K tokens)           │
└─────────────────────────────────────┘

3.3 Token 预算硬性控制

永远在代码层做硬性 token 上限,不要依赖模型自己控制长度。

import tiktoken

def build_context(system: str, docs: list[str], history: list[dict],
                  user_input: str, max_tokens: int = 80000) -> list[dict]:
    encoder = tiktoken.encoding_for_model("gpt-4o")

    # 预留空间:system(固定) + user_input + 输出空间
    system_tokens = len(encoder.encode(system))
    user_tokens = len(encoder.encode(user_input))
    output_reserve = 4000  # 为模型输出预留

    available = max_tokens - system_tokens - user_tokens - output_reserve

    # 历史对话:优先保留最近的,超出则截断
    history_budget = min(available * 0.3, 8000)
    trimmed_history = trim_history(history, history_budget, encoder)

    # 检索文档:剩余 budget
    docs_budget = available - count_tokens(trimmed_history, encoder)
    selected_docs = select_docs_by_budget(docs, docs_budget, encoder)

    return build_messages(system, selected_docs, trimmed_history, user_input)

4. Prompt 工程的生产规范

4.1 System Prompt 设计原则

原则一:角色定义要具体,不要泛化

# 差(泛化)
你是一个有帮助的 AI 助手。

# 好(具体)
你是 XX 公司的技术支持工程师,专门处理 [产品名] 的集成问题。
你的用户是有一定开发经验的工程师,回答应技术化、直接,避免过度解释基础概念。

原则二:约束用否定句,但要说明原因

# 差(无原因的禁止)
不要推荐竞品。

# 好(有原因,模型更倾向遵守)
不要推荐竞品产品,因为这会让用户产生混淆,且超出你的支持范围。
如果用户询问竞品对比,说明你专注于 [产品名] 的支持,建议他们查阅官方对比文档。

原则三:输出格式用示例,不用描述

# 差(描述格式)
以 JSON 格式输出,包含 answer 和 confidence 字段。

# 好(示例格式)
按以下格式输出:
{"answer": "你的回答内容", "confidence": "high|medium|low", "sources": ["来源1", "来源2"]}

4.2 Few-shot 示例选择策略

Few-shot 示例是上下文中最昂贵但最有效的内容。生产中的选择原则:

策略适用场景示例数量
静态示例任务类型固定,分布稳定3-5 个
动态检索示例任务类型多样,有历史案例库1-3 个(按相似度检索)
零示例任务描述足够清晰,模型能力足够强0 个

动态检索示例的实现:将历史高质量的(输入, 输出)对存入向量数据库,在每次请求时检索与当前输入最相似的 1-3 个作为 few-shot。这比静态示例平均提升 10-25% 的准确率,且不浪费 token。

4.3 Prompt 版本管理

生产中 prompt 必须版本化管理,与代码同等对待:

prompts/
├── system/
│   ├── support_agent_v1.2.txt      # 当前生产版本
│   ├── support_agent_v1.3-beta.txt # 测试中
│   └── changelog.md
├── templates/
│   ├── doc_retrieval.jinja2
│   └── summarization.jinja2
└── tests/
    └── regression_cases.jsonl      # 固定评估集

每次 prompt 变更必须

  1. 更新版本号
  2. 在固定评估集上对比新旧版本指标
  3. 记录变更原因和效果数据

5. 多轮对话的上下文设计

5.1 对话历史的三种处理策略

策略一:全量保留(适合短会话)

简单直接,对话轮次 ≤ 10 轮时可行。超过后 token 成本线性增长,且出现"遗忘中间"问题。

策略二:滑动窗口(适合长会话)

只保留最近 N 轮对话,丢弃更早的内容。实现简单,但会真的"遗忘"早期重要信息。

def sliding_window_history(messages: list[dict], max_rounds: int = 10) -> list[dict]:
    # 保留 system message + 最近 max_rounds 轮的 user/assistant 对
    system = [m for m in messages if m["role"] == "system"]
    conversation = [m for m in messages if m["role"] != "system"]
    # 每轮 = user + assistant,所以取最后 max_rounds * 2 条
    trimmed = conversation[-(max_rounds * 2):]
    return system + trimmed

策略三:压缩摘要(适合生产级长会话)

当对话超过阈值时,将早期历史压缩为结构化摘要,保留摘要 + 最近 N 轮原始内容。

SUMMARY_TRIGGER = 20  # 超过 20 轮触发压缩

def get_context_with_compression(session: Session) -> list[dict]:
    messages = session.messages

    if len(messages) < SUMMARY_TRIGGER:
        return messages  # 短会话直接返回

    # 取前一半进行压缩,保留后一半原始内容
    split = len(messages) // 2
    to_summarize = messages[:split]
    to_keep = messages[split:]

    # 检查是否已有该段的摘要缓存
    summary = session.get_cached_summary(split)
    if not summary:
        summary = call_llm_for_summary(to_summarize)
        session.cache_summary(split, summary)

    summary_message = {
        "role": "system",
        "content": f"[早期对话摘要]\n{summary}"
    }

    return [messages[0]] + [summary_message] + to_keep

5.2 关键信息的显式提取与注入

不要依赖模型"记住"重要信息——在对话中显式提取并结构化存储:

# 每轮对话结束后,提取关键实体和事实
def extract_key_facts(user_msg: str, assistant_msg: str) -> dict:
    extraction_prompt = """从以下对话中提取关键事实,JSON 格式:
    {"entities": [], "decisions": [], "constraints": [], "open_questions": []}

    对话:
    用户:{user_msg}
    助手:{assistant_msg}"""

    return call_llm(extraction_prompt)

# 在后续对话的 system prompt 中注入
def build_system_with_facts(base_system: str, facts: dict) -> str:
    if not facts:
        return base_system

    facts_section = f"""
[当前会话已知信息]
- 涉及实体:{', '.join(facts.get('entities', []))}
- 已确认决策:{'; '.join(facts.get('decisions', []))}
- 约束条件:{'; '.join(facts.get('constraints', []))}
"""
    return base_system + facts_section

6. Agent 场景的上下文管理

6.1 Agent 上下文的特殊挑战

Agent 系统中,上下文窗口会随着工具调用不断增长:

初始请求 → 思考(1K) → 工具调用A → 结果A(5K) → 思考(1K)
→ 工具调用B → 结果B(10K) → 思考(1K) → ... → 最终回答

一个复杂任务执行 10 次工具调用,工具结果可能累积 50K+ tokens,严重挤压有效上下文空间。

6.2 工具结果的上下文管理

原则:工具结果要摘要,不要全量保留

class ToolResultManager:
    MAX_RESULT_TOKENS = 2000  # 单次工具结果上限

    def process_tool_result(self, tool_name: str, raw_result: str) -> str:
        tokens = count_tokens(raw_result)

        if tokens <= self.MAX_RESULT_TOKENS:
            return raw_result

        # 超出上限,根据工具类型选择压缩策略
        if tool_name in ["web_search", "doc_retrieve"]:
            return self.extract_key_points(raw_result)
        elif tool_name in ["code_executor", "bash"]:
            return self.extract_execution_summary(raw_result)
        else:
            return self.truncate_with_notice(raw_result, self.MAX_RESULT_TOKENS)

    def extract_key_points(self, content: str) -> str:
        return call_llm(f"提取以下内容的关键信息,不超过 500 字:\n{content[:8000]}")

6.3 长程 Agent 任务的检查点设计

对于执行时间超过 5 分钟或步骤超过 20 步的 Agent 任务,必须实现检查点机制:

@dataclass
class AgentCheckpoint:
    task_id: str
    step: int
    completed_actions: list[dict]    # 已完成的操作
    accumulated_facts: dict           # 从工具结果中提取的事实
    current_plan: list[str]          # 当前计划(可能已更新)
    context_summary: str             # 已处理内容的摘要
    timestamp: str

class CheckpointedAgent:
    CHECKPOINT_INTERVAL = 5  # 每 5 步保存一次

    def run_step(self, step: int, context: list[dict]) -> AgentOutput:
        result = self.llm.invoke(context)

        if step % self.CHECKPOINT_INTERVAL == 0:
            # 将已处理的工具结果压缩为摘要,释放 token 空间
            checkpoint = self.create_checkpoint(step, context, result)
            self.save_checkpoint(checkpoint)

            # 用摘要替换详细历史,保持上下文窗口可控
            context = self.rebuild_context_from_checkpoint(checkpoint)

        return result

6.4 多 Agent 系统的上下文隔离

在 Orchestrator + Sub-agent 架构中:

┌──────────────────────────────────┐
│  Orchestrator Agent              │
│  上下文:任务规划 + 各子任务摘要  │
│  不包含:子任务的详细执行过程     │
└──────────┬───────────────────────┘
           │ 只传递必要的子任务指令
    ┌──────┴──────┐
    ▼             ▼
┌────────┐   ┌────────┐
│ Agent A│   │ Agent B│
│独立上下│   │独立上下│
│文窗口  │   │文窗口  │
└────────┘   └────────┘
    │             │
    └──────┬──────┘
           │ 只返回结构化摘要结果
           ▼
    Orchestrator 接收摘要,
    不接收原始执行细节

关键设计:子 Agent 的执行细节不应全量传回给 Orchestrator,只传结构化结果摘要。Orchestrator 的上下文应保持精简,专注于任务协调而非执行细节。


7. 上下文压缩与摘要技术

7.1 递归摘要(Recursive Summarization)

适用于超长文档或超长对话的处理:

def recursive_summarize(text: str, target_tokens: int = 2000,
                        chunk_size: int = 4000) -> str:
    """将长文本递归压缩到目标 token 数"""
    current_tokens = count_tokens(text)

    if current_tokens <= target_tokens:
        return text

    # 分块摘要
    chunks = split_by_tokens(text, chunk_size)
    chunk_summaries = [
        call_llm(f"摘要以下内容的核心信息(100字以内):\n{chunk}")
        for chunk in chunks
    ]

    combined = "\n".join(chunk_summaries)

    # 如果摘要的摘要还是太长,递归处理
    if count_tokens(combined) > target_tokens:
        return recursive_summarize(combined, target_tokens, chunk_size)

    return combined

7.2 结构化压缩(Structured Compression)

比自由文本摘要更可靠,强制提取特定维度信息:

COMPRESSION_SCHEMA = """
从以下内容中提取信息,严格按 JSON 格式输出:
{
  "key_facts": [],          // 关键事实,每条 ≤ 30 字
  "decisions_made": [],     // 已做决策
  "pending_items": [],      // 未解决事项
  "critical_constraints": [] // 不可违反的约束
}
内容:{content}
"""

def structured_compress(content: str) -> dict:
    result = call_llm(COMPRESSION_SCHEMA.format(content=content))
    return json.loads(result)

def inject_structured_summary(system: str, summary: dict) -> str:
    lines = ["[背景信息]"]
    if summary.get("key_facts"):
        lines.append("关键事实:" + " | ".join(summary["key_facts"]))
    if summary.get("critical_constraints"):
        lines.append("约束:" + " | ".join(summary["critical_constraints"]))
    return system + "\n" + "\n".join(lines)

7.3 缓存策略(KV Cache 利用)

现代模型 API 对前缀相同的请求有 KV Cache 优化,可大幅降低成本:

最大化 Cache 命中率的设计原则

  1. System Prompt 固定化:将所有静态内容集中在 system prompt 开头,保持不变
  2. 动态内容后置:用户输入、检索结果等变化内容放在 messages 末尾
  3. 避免随机化:时间戳、随机 ID 等每次不同的内容会破坏 cache
# 好:静态内容在前,动态内容在后
messages = [
    {"role": "system", "content": STATIC_SYSTEM_PROMPT},  # 固定,可 cache
    {"role": "user", "content": f"[检索结果]\n{retrieved_docs}\n\n用户问题:{query}"}
]

# 差:将时间戳混入 system prompt
messages = [
    {"role": "system", "content": f"{STATIC_SYSTEM_PROMPT}\n当前时间:{datetime.now()}"},  # 每次不同,无法 cache
]

Claude 的 Prompt Caching 可节省 90% 的输入 token 成本(缓存命中时),需要在 system content 中使用 cache_control 标记。


8. 成本与延迟优化

8.1 上下文成本分析

在大规模生产中,上下文 token 成本是主要开销:

典型 RAG 应用的 token 构成(每次请求):

System Prompt:    2,000 tokens  ( 5%)
检索文档:        15,000 tokens  (37%)
对话历史:         8,000 tokens  (20%)
用户输入:         1,500 tokens  ( 4%)
模型输出:        14,000 tokens  (34%)  ← 输出 token 约 3-5x 输入价格
────────────────────────────────────
总计:            40,500 tokens/请求

优化优先级:输出 token > 检索文档 token > 历史 token > System Prompt token

8.2 分级上下文策略

不是所有请求都需要完整上下文,按请求复杂度分级:

级别判断条件上下文策略典型延迟
L1 简单闲聊/明确指令/FAQ最小上下文(仅 system + user)< 1s
L2 标准需要少量知识system + 3-5 文档 + 短历史1-3s
L3 复杂多跳推理/分析完整上下文 + 压缩历史3-8s
L4 Agent需要工具调用动态管理,检查点压缩8-30s
def route_request(query: str, session: Session) -> ContextLevel:
    """请求复杂度路由"""
    # 简单规则判断(低成本)
    if is_faq(query) or is_chitchat(query):
        return ContextLevel.L1

    # 检查是否需要工具
    if requires_tool_use(query):
        return ContextLevel.L4

    # 通过轻量分类模型判断
    complexity = classify_complexity(query)  # 小模型,低成本
    return complexity

8.3 并行上下文构建

上下文的不同部分可以并行获取:

import asyncio

async def build_context_parallel(query: str, session: Session) -> list[dict]:
    # 并行执行:向量检索 + 历史摘要 + 实体提取
    retrieved_docs, history_summary, entities = await asyncio.gather(
        retrieve_documents(query),           # 向量检索
        get_compressed_history(session),     # 历史压缩
        extract_query_entities(query),       # 实体提取
    )

    return assemble_context(retrieved_docs, history_summary, entities, query)

9. 评估与可观测性

9.1 上下文质量指标

上下文工程的核心评估维度:

指标含义测量方式
相关性召回率关键信息是否被注入上下文对黄金答案做逆向检验
信息噪声比上下文中无关信息的比例人工标注抽样
Token 效率每有效 token 带来的质量提升消融实验
位置敏感性信息位置对答案质量的影响随机打乱位置测试
压缩损失率摘要后关键信息的丢失比例信息保留率评估

9.2 上下文追踪设计

生产中每次请求的上下文应完整记录:

@dataclass
class ContextTrace:
    request_id: str
    timestamp: str

    # 输入
    system_prompt_hash: str          # 不存全文,存哈希
    retrieved_doc_ids: list[str]     # 文档 ID,不存全文
    history_rounds: int              # 历史轮数
    user_input_tokens: int

    # 上下文构建
    total_input_tokens: int
    cache_hit: bool
    context_build_ms: int            # 构建耗时

    # 输出
    output_tokens: int
    latency_ms: int

    # 质量信号
    user_feedback: Optional[str]     # 用户反馈
    auto_eval_score: Optional[float] # 自动评估分数

def log_context_trace(trace: ContextTrace):
    # 写入可查询的存储(ClickHouse / BigQuery)
    analytics_client.write(trace)

9.3 上下文变更的 A/B 测试框架

Prompt 和上下文策略变更必须通过 A/B 测试验证:

class ContextABTest:
    def __init__(self, control_config: ContextConfig,
                 treatment_config: ContextConfig,
                 traffic_split: float = 0.1):  # 10% 流量走新策略
        self.control = control_config
        self.treatment = treatment_config
        self.split = traffic_split

    def get_config(self, user_id: str) -> tuple[ContextConfig, str]:
        # 用 user_id 做一致性哈希,同一用户始终走同一分支
        bucket = hash(user_id) % 100
        if bucket < self.split * 100:
            return self.treatment, "treatment"
        return self.control, "control"

    def should_stop_early(self) -> bool:
        """统计显著性检验,防止持续暴露用户于更差的体验"""
        stats = self.get_current_stats()
        return stats.p_value < 0.05 and stats.effect_size < 0

10. 常见生产问题与根本解法

问题一:模型"忽略"了上下文中的关键信息

症状:上下文中明确提供了信息,但模型给出了错误或默认答案。

根本原因

  • 关键信息被埋在上下文中间(Lost in the Middle)
  • 上下文太长,注意力被稀释
  • 信息表述方式与模型期望不匹配

解法

  1. 将关键信息移至 system prompt 开头或 user message 结尾
  2. 减少上下文总长度(删除不相关文档)
  3. 在 user message 中显式引用:"根据上面提供的[信息类型],请..."
  4. 使用结构化标记区分不同类型信息(XML 标签效果优于 Markdown)
# 效果更好的关键信息标记方式
<critical_constraint>
用户账户类型为"免费版",不能推荐付费功能。
</critical_constraint>

问题二:多轮对话中模型"忘记"了早期信息

症状:用户在前几轮说的重要条件,模型在后续轮次中没有考虑。

根本原因:早期对话被截断或摘要不完整。

解法

  • 实现关键信息的显式提取和注入(见第 5.2 节)
  • 对用户在对话开始时设定的"约束"类信息,持久化到 system prompt 中
  • 不要只做对话历史截断,要做结构化摘要

问题三:上下文冲突导致模型输出不稳定

症状:注入了多个文档,文档之间存在矛盾,模型输出随机在两种答案之间波动。

根本原因:模型没有足够指令来处理信息冲突。

解法

  1. 在 system prompt 中添加冲突处理规则
  2. 对文档添加时间戳或权威性标注,让模型知道优先采信哪个
  3. 在 retrieval 阶段做去重和冲突检测,不将冲突文档一起注入
# system prompt 中的冲突处理规则
当提供的文档之间存在矛盾时:
1. 优先采信标注为"官方文档"的内容
2. 优先采信更新日期较近的内容
3. 如果无法判断,明确告知用户存在不一致,并列出两种说法

问题四:长文档 Agent 任务执行到中途上下文溢出

症状:Agent 任务失败,错误信息为 context length exceeded 或输出质量急剧下降。

根本原因:没有对工具结果做 token 控制,上下文随任务进行线性增长。

解法

  1. 对所有工具结果设置 token 上限(见第 6.2 节)
  2. 实现检查点压缩机制(见第 6.3 节)
  3. 在任务开始前预估上下文增长,超过阈值时主动触发压缩
  4. 将大任务拆分为有明确边界的子任务,每个子任务独立维护上下文

问题五:Prompt 变更导致线上效果下降

症状:更新了 system prompt 后,部分用户反馈质量下降,但无法快速定位问题。

根本原因:没有基线评估集,无法量化变更影响。

解法

  1. 维护覆盖主要场景的固定评估集(至少 100 条,含黄金答案)
  2. 每次 prompt 变更前后必须跑评估集对比
  3. 使用 LLM-as-Judge 做自动评分(但要对评分器本身做校验)
  4. 灰度发布:先对 5% 流量生效,监控关键指标 24 小时后再全量

附录:2026 年上下文工程技术栈参考

上下文管理库

工具用途成熟度
LangChain Memory对话历史管理生产可用
LlamaIndex文档上下文管理生产可用
MemoryOS / Mem0长期记忆管理生产可用
Letta (MemGPT)持久化 Agent 记忆早期生产

Token 计算

工具适用模型
tiktokenOpenAI 系列
anthropic.count_tokens()Claude 系列
HuggingFace tokenizers开源模型

可观测性

工具用途
LangSmithPrompt 追踪、评估、版本管理
Langfuse开源替代,自托管友好
Braintrust评估实验管理
Weave (W&B)实验追踪

核心原则总结:上下文工程的本质是信息管理。在有限的 token 预算内,放入最相关的信息,排除噪声,并确保关键信息在模型注意力最强的位置。技术手段(压缩、检索、分区)都是服务于这个目标的工具。