第 26 章

Memory Tool:memory_20250818 实现跨会话持久记忆的完整机制

第二十六章:Context Editing:上下文注入、修改与精确控制

26.1 上下文是 Claude 的全部世界

对于 Claude 而言,它所知道的一切来源于它在当前调用中收到的信息:系统提示(system prompt)、对话历史(messages)、工具调用结果(tool results)。这些内容共同构成了 Claude 的"感知世界"。

**上下文工程(Context Engineering)**正是对这个感知世界的精确设计。它不是简单地向 Claude 堆砌信息,而是精心决策:什么时候注入什么信息、以什么格式呈现、放在什么位置。

Context Editing 与 Memory Tool 的区别在于:

26.2 上下文的四个维度

维度一:位置(Position)

Claude 对消息的位置非常敏感。Anthropic 的研究显示,信息在上下文中的位置影响其被"注意"的程度:

维度二:格式(Format)

信息的格式决定 Claude 如何解析和使用它:

<!-- XML 标签格式:结构清晰,适合多块内容 -->
<user_profile>
  <name>张伟</name>
  <expertise>Python, FastAPI, PostgreSQL</expertise>
  <preference>简洁风格,不需要过多解释</preference>
</user_profile>

<current_task>
  重构认证模块,要求符合 JWT 最佳实践
</current_task>
# 当前会话上下文
## 用户信息
- 姓名:张伟
- 技术栈:Python, FastAPI, PostgreSQL
- 偏好:简洁风格,不需要过多解释

## 当前任务
重构认证模块,要求符合 JWT 最佳实践

XML 标签适合多类型信息混合注入,Markdown 格式在代码和文档生成任务中更自然。

维度三:时机(Timing)

什么时候注入信息与注入什么同样重要:

时机 注入内容 方式
会话初始化 用户画像、项目背景、记忆预取 System prompt
工具调用后 工具执行结果 Tool result
用户消息前 实时数据(股价、天气、数据库查询) 注入到 user message
长对话中间 上下文压缩摘要 替换历史消息

维度四:密度(Density)

上下文越多不等于越好。信息密度过高会导致"迷失在中间"(Lost in the Middle)问题——Claude 会更关注头部和尾部,中间部分的信息被忽视。

原则:只注入当前任务真正需要的信息。

26.3 系统提示的结构化设计

一个设计良好的系统提示应当分层组织:

import anthropic
from datetime import datetime

def build_system_prompt(
    user: dict,
    memories: list[dict],
    current_tools: list[str],
    domain: str = "general"
) -> str:
    """构建分层系统提示"""
    
    sections = []
    
    # 第一层:角色与核心原则(固定)
    sections.append("""## 角色定义
你是 YiteAI 的智能助手,专注于帮助用户提高工作效率。
你的回复应当:
- 简洁直接,避免冗长解释
- 优先使用代码示例而非文字描述
- 遇到不确定的信息,明确标注而非猜测""")
    
    # 第二层:用户上下文(动态)
    if user:
        user_ctx = f"""## 用户信息
- 姓名:{user.get('name', '未知')}
- 技术栈:{', '.join(user.get('skills', []))}
- 语言偏好:{user.get('language', 'Chinese')}"""
        sections.append(user_ctx)
    
    # 第三层:持久化记忆(动态)
    if memories:
        mem_lines = [f"- [{m['category']}] {m['content']}" for m in memories[:5]]
        sections.append("## 历史记忆\n" + "\n".join(mem_lines))
    
    # 第四层:可用工具说明(按需)
    if current_tools:
        tool_desc = "## 可用能力\n你可以使用以下工具:" + ", ".join(current_tools)
        sections.append(tool_desc)
    
    # 第五层:实时上下文(最后注入)
    realtime = f"""## 当前状态
- 时间:{datetime.now().strftime('%Y-%m-%d %H:%M')} (CST)
- 领域:{domain}"""
    sections.append(realtime)
    
    return "\n\n".join(sections)

26.4 上下文注入技术

技术一:用户消息前缀注入

在用户消息前插入相关数据,而不污染系统提示:

def inject_realtime_data(user_message: str, context: dict) -> str:
    """在用户消息前注入实时数据"""
    
    injections = []
    
    if "stock_query" in context:
        stock = context["stock_query"]
        injections.append(
            f"[实时数据] {stock['symbol']} 当前价格: {stock['price']}, "
            f"涨跌: {stock['change']:+.2f}%"
        )
    
    if "db_results" in context:
        results = context["db_results"]
        injections.append(
            f"[数据库查询结果]\n```json\n{json.dumps(results, ensure_ascii=False, indent=2)}\n```"
        )
    
    if injections:
        prefix = "\n".join(injections) + "\n\n---\n\n"
        return prefix + user_message
    
    return user_message


# 使用示例
client = anthropic.Anthropic()

user_msg = "分析一下公司的用户增长趋势"
enriched_msg = inject_realtime_data(user_msg, {
    "db_results": {
        "monthly_users": [1200, 1450, 1680, 1920, 2310],
        "churn_rate": 0.032,
        "period": "2025-01 to 2025-05"
    }
})

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    messages=[{"role": "user", "content": enriched_msg}]
)

技术二:Assistant 预填充(Prefill)

通过预填充 assistant 的开头,精确控制输出格式:

def get_structured_output(prompt: str, output_template: str) -> str:
    """使用预填充引导结构化输出"""
    client = anthropic.Anthropic()
    
    response = client.messages.create(
        model="claude-opus-4-5",
        max_tokens=2048,
        messages=[
            {"role": "user", "content": prompt},
            # 预填充 assistant 回复的开头
            {"role": "assistant", "content": output_template}
        ]
    )
    
    # 完整输出 = 预填充部分 + 生成部分
    return output_template + response.content[0].text


# 示例:强制输出 JSON
result = get_structured_output(
    prompt="分析以下代码的时间复杂度:\n\n```python\ndef bubble_sort(arr):\n    n = len(arr)\n    for i in range(n):\n        for j in range(n-i-1):\n            if arr[j] > arr[j+1]:\n                arr[j], arr[j+1] = arr[j+1], arr[j]\n```",
    output_template='{"time_complexity": "'
)
# 输出类似:{"time_complexity": "O(n²)", "space_complexity": "O(1)", "explanation": "..."}

技术三:多轮历史的精确修改

在某些情况下,需要修改历史对话以纠正错误或注入新信息:

class ConversationEditor:
    """精确控制对话历史的编辑器"""
    
    def __init__(self):
        self.messages: list[dict] = []
    
    def append(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
    
    def inject_after_turn(self, turn_index: int, role: str, content: str):
        """在指定轮次后注入新消息"""
        # turn_index 是 assistant/user 对的索引
        msg_index = turn_index * 2 + 1
        self.messages.insert(msg_index, {"role": role, "content": content})
    
    def replace_last_assistant(self, new_content: str):
        """替换最后一条 assistant 消息(纠正输出)"""
        for i in range(len(self.messages) - 1, -1, -1):
            if self.messages[i]["role"] == "assistant":
                self.messages[i]["content"] = new_content
                return
    
    def trim_to_last_n_turns(self, n: int):
        """保留最近 N 轮对话,截断早期历史"""
        # 每轮包含 user + assistant 两条消息
        keep = n * 2
        if len(self.messages) > keep:
            self.messages = self.messages[-keep:]
    
    def inject_system_reminder(self, reminder: str):
        """在最后一条用户消息前注入提醒(模拟 system 注入)"""
        # 找到最后一条 user 消息
        for i in range(len(self.messages) - 1, -1, -1):
            if self.messages[i]["role"] == "user":
                # 在内容前添加提醒
                original = self.messages[i]["content"]
                if isinstance(original, str):
                    self.messages[i]["content"] = f"[系统提醒: {reminder}]\n\n{original}"
                return
    
    def get_messages(self) -> list[dict]:
        return self.messages.copy()

技术四:动态 System Prompt 拼接

对于需要根据任务类型切换角色的 Agent,动态拼接系统提示比维护多个固定提示更灵活:

class DynamicPromptBuilder:
    """动态系统提示构建器"""
    
    ROLE_BLOCKS = {
        "code_reviewer": """
## 代码审查模式
你现在是一位严格的代码审查专家。重点关注:
- 安全漏洞(SQL 注入、XSS、越权)
- 性能问题(N+1 查询、内存泄漏)
- 代码可维护性(命名规范、函数长度)
每个问题必须标注严重程度:[CRITICAL/HIGH/MEDIUM/LOW]""",
        
        "architect": """
## 架构设计模式
你现在是一位系统架构师。重点关注:
- 可扩展性:系统在 10x 负载下是否仍然可靠
- 解耦程度:各组件是否可以独立部署和替换
- 数据一致性:分布式场景下的一致性保证
使用架构图(用文字描述 C4 Model 各层)说明设计""",
        
        "writer": """
## 技术写作模式
你现在是一位技术文档专家。重点关注:
- 读者背景:假设读者是高级工程师
- 结构清晰:使用标题、列表、代码块组织内容
- 示例驱动:每个概念配备可运行的代码示例"""
    }
    
    def build(self, base_prompt: str, roles: list[str], 
              extra_context: str = "") -> str:
        blocks = [base_prompt]
        for role in roles:
            if role in self.ROLE_BLOCKS:
                blocks.append(self.ROLE_BLOCKS[role])
        if extra_context:
            blocks.append(f"## 额外上下文\n{extra_context}")
        return "\n\n".join(blocks)

26.5 长对话的上下文管理策略

当对话超过数十轮时,上下文管理成为核心挑战。

滑动窗口策略

def sliding_window_messages(messages: list[dict], 
                             max_tokens: int = 150000,
                             min_keep_turns: int = 5) -> list[dict]:
    """
    保持对话历史在 token 预算内。
    始终保留最近 min_keep_turns 轮。
    较早的历史按 token 成本从最旧开始裁剪。
    """
    import anthropic
    
    # 粗略估算:每个字符约 0.4 token(中文约 0.8)
    def estimate_tokens(msg: dict) -> int:
        content = msg.get("content", "")
        if isinstance(content, str):
            return len(content) // 2
        return 100
    
    # 必须保留的最近消息
    must_keep = messages[-(min_keep_turns * 2):]
    optional = messages[:-(min_keep_turns * 2)]
    
    # 从最新开始,贪心选取可选消息
    selected_optional = []
    remaining_budget = max_tokens - sum(estimate_tokens(m) for m in must_keep)
    
    for msg in reversed(optional):
        cost = estimate_tokens(msg)
        if remaining_budget - cost > 0:
            selected_optional.insert(0, msg)
            remaining_budget -= cost
        else:
            break
    
    return selected_optional + must_keep

摘要注入策略

当早期历史需要丢弃时,先生成摘要再注入:

async def summarize_and_inject(
    client: anthropic.Anthropic,
    old_messages: list[dict],
    current_messages: list[dict]
) -> list[dict]:
    """将早期历史压缩为摘要,注入到新对话开头"""
    
    history_text = "\n".join([
        f"{m['role'].upper()}: {m['content'][:500]}"
        for m in old_messages
    ])
    
    summary_response = client.messages.create(
        model="claude-haiku-4-5",  # 用便宜模型做摘要
        max_tokens=512,
        messages=[{
            "role": "user",
            "content": f"请用 3-5 句话摘要以下对话的关键信息和决策:\n\n{history_text}"
        }]
    )
    
    summary = summary_response.content[0].text
    summary_injection = {
        "role": "user",
        "content": f"[早期对话摘要]\n{summary}\n\n---"
    }
    summary_ack = {
        "role": "assistant", 
        "content": "已理解早期对话上下文,请继续。"
    }
    
    return [summary_injection, summary_ack] + current_messages

26.6 上下文质量的评估

如何判断上下文设计是否有效?以下是几个实用指标:

指令遵循率

对于关键格式要求,通过程序化验证检查 Claude 是否遵循:

def evaluate_context_quality(
    client: anthropic.Anthropic,
    system: str,
    test_cases: list[dict]
) -> float:
    """测量系统提示在测试集上的指令遵循率"""
    
    pass_count = 0
    for case in test_cases:
        response = client.messages.create(
            model="claude-haiku-4-5",
            max_tokens=512,
            system=system,
            messages=[{"role": "user", "content": case["input"]}]
        )
        output = response.content[0].text
        
        # 检查所有必须满足的条件
        if all(checker(output) for checker in case["checks"]):
            pass_count += 1
    
    return pass_count / len(test_cases)


# 使用示例
test_cases = [
    {
        "input": "分析冒泡排序的时间复杂度",
        "checks": [
            lambda r: "O(n" in r,              # 必须包含大O表示
            lambda r: r.startswith("{"),        # 必须是JSON格式
        ]
    }
]

quality = evaluate_context_quality(client, system_prompt, test_cases)
print(f"指令遵循率: {quality:.1%}")

小结

Context Editing 是 Claude 工程实践中最精细的技术之一。通过精确控制信息的位置、格式、时机和密度,可以显著提升 Claude 的任务表现和输出一致性。

核心技术点:

下一章将介绍 Claude 内置的上下文压缩(Compaction)机制——当上下文窗口快要填满时,如何自动完成摘要与无损延续。

本章评分
4.6  / 5  (6 评分)

💬 留言讨论