Context Engineering

Context Engineering 实战:如何为 AI Agent 设计最优上下文

深入探讨 Context Engineering 的核心方法论,包括上下文窗口优化、信息筛选策略和动态上下文管理的工程实践。

Prompt Engineering 关注的是”怎么问”,Context Engineering 关注的是”给什么看”。在 Agent 系统中,上下文质量直接决定了 LLM 的决策质量。一个精心设计的上下文策略可以让 7B 模型胜过随意填塞上下文的 70B 模型。

上下文的四个维度

系统指令

定义 Agent 的角色、行为规范和输出格式。这是最稳定的上下文部分。

工具描述

告诉 LLM 有哪些工具可用、何时使用、如何调用。工具描述的质量直接影响调用准确率。

对话历史

用户和 Agent 之间的交互记录。需要平衡保留完整性和控制长度。

外部知识

通过工具调用获取的外部信息。这是最容易膨胀的部分。

上下文窗口预算

把上下文窗口想象成一个有限的预算,需要合理分配:

┌────────────────────────────────────────┐
│         上下文窗口(200K tokens)        │
│                                        │
│  ┌──────────┐ 系统指令 ~2K tokens       │
│  ├──────────┤ 工具描述 ~5K tokens       │
│  ├──────────┤ 对话历史 ~20K tokens      │
│  ├──────────┤ 外部知识 ~100K tokens     │
│  ├──────────┤ 预留给输出 ~50K tokens    │
│  └──────────┤ 安全余量 ~23K tokens      │
└────────────────────────────────────────┘

动态上下文管理

基于相关性的筛选

不是所有信息都同等重要。根据当前任务的相关性筛选信息:

class RelevanceFilter {
  async selectRelevant(
    candidates: ContextItem[],
    currentTask: string,
    maxTokens: number
  ): Promise<ContextItem[]> {
    // 计算每个候选项与当前任务的相关性分数
    const scored = await Promise.all(
      candidates.map(async item => ({
        item,
        score: await this.computeRelevance(item, currentTask),
      }))
    );

    // 按相关性排序
    scored.sort((a, b) => b.score - a.score);

    // 贪心选择,直到达到 token 限制
    const selected: ContextItem[] = [];
    let totalTokens = 0;

    for (const { item } of scored) {
      if (totalTokens + item.tokenCount > maxTokens) break;
      selected.push(item);
      totalTokens += item.tokenCount;
    }

    return selected;
  }
}

对话历史压缩

当对话历史过长时,使用摘要替代原始消息:

class ConversationCompressor {
  async compress(messages: Message[], maxTokens: number): Promise<Message[]> {
    const currentTokens = this.estimateTokens(messages);

    if (currentTokens <= maxTokens) {
      return messages;
    }

    // 保留最近 N 条消息
    const keepRecent = 5;
    const recent = messages.slice(-keepRecent);
    const older = messages.slice(0, -keepRecent);

    // 对较早的消息生成摘要
    const summary = await this.summarize(older);

    return [
      { role: 'system', content: `对话摘要:${summary}` },
      ...recent,
    ];
  }

  private async summarize(messages: Message[]): Promise<string> {
    const response = await this.llm.chat({
      messages: [{
        role: 'user',
        content: `请用 200 字概括以下对话的关键信息:\n${JSON.stringify(messages)}`,
      }],
    });
    return response.content;
  }
}

工具结果裁剪

工具返回的结果可能很长,需要裁剪后注入上下文:

class ResultTrimmer {
  trim(result: ToolResult, maxChars: number = 3000): ToolResult {
    const text = result.content[0]?.text || '';

    if (text.length <= maxChars) {
      return result;
    }

    // 保留开头和结尾,中间用摘要替代
    const head = text.slice(0, maxChars * 0.4);
    const tail = text.slice(-maxChars * 0.3);
    const omitted = text.length - head.length - tail.length;

    return {
      ...result,
      content: [{
        type: 'text',
        text: `${head}\n\n...(省略 ${omitted} 字符)...\n\n${tail}`,
      }],
    };
  }
}

上下文注入策略

分层注入

根据重要性分层注入信息,确保关键信息不被淹没:

class LayeredInjection {
  buildContext(layers: ContextLayer[]): string {
    const parts: string[] = [];

    // 第一层:核心指令
    parts.push('## 核心任务\n' + layers[0].content);

    // 第二层:相关工具
    parts.push('## 可用工具\n' + layers[1].content);

    // 第三层:参考信息
    if (layers[2]) {
      parts.push('## 参考资料\n' + layers[2].content);
    }

    // 第四层:对话历史
    if (layers[3]) {
      parts.push('## 对话历史\n' + layers[3].content);
    }

    return parts.join('\n\n');
  }
}

渐进式注入

先注入摘要,LLM 需要时再注入详情,避免一次性填满上下文。

常见问题(FAQ)

Context Engineering 和 RAG 有什么区别?

RAG 是 Context Engineering 的一个子集——RAG 关注的是如何检索相关文档,Context Engineering 关注的是如何组织和呈现所有上下文信息。

如何评估上下文策略的效果?

通过 A/B 测试比较不同策略下的任务完成率、工具调用准确率和用户满意度。

上下文窗口越大越好吗?

不一定。过大的上下文会增加成本、降低推理速度,还可能导致”迷失在中间”问题。关键是质量而非数量。

总结

Context Engineering 是 Agent 系统中最容易被忽视但影响最大的工程实践。通过合理的预算分配、动态筛选、压缩裁剪和分层注入,可以在有限的上下文窗口中最大化 LLM 的决策质量。