四、AI Agent 设计模式:上下文管理

上一篇我们分析了工具编排。今天深入第三个主题:上下文管理。Agent 的上下文包含消息历史、文件状态、Token 预算、配置等。如何高效管理这些状态?

ToolUseContext:共享状态容器

Context 是 Agent 执行的"记忆":

typescript 复制代码
type ToolUseContext = {
  // === 执行控制 ===
  abortController: AbortController

  // === 文件状态 ===
  readFileState: FileStateCache

  // === 消息历史 ===
  messages: Message[]

  // === UI 状态 ===
  getAppState(): AppState
  setAppState(updater: (prev: AppState) => AppState): void

  // === 配置选项 ===
  options: {
    tools: Tools
    commands: Command[]
    mcpClients: MCPServerConnection[]
    mainLoopModel: string
    thinkingConfig: ThinkingConfig
    isNonInteractiveSession: boolean
    maxBudgetUsd?: number
    customSystemPrompt?: string
    agentDefinitions: AgentDefinitionsResult
  }

  // === Agent 信息 ===
  agentId?: AgentId
  agentType?: string
  toolUseId?: string

  // === 追踪信息 ===
  queryTracking?: QueryChainTracking

  // === 限制配置 ===
  fileReadingLimits?: { maxTokens?: number; maxSizeBytes?: number }
  globLimits?: { maxResults?: number }

  // === Hook 触发器 ===
  nestedMemoryAttachmentTriggers?: Set<string>
  loadedNestedMemoryPaths?: Set<string>
  dynamicSkillDirTriggers?: Set<string>

  // === 特殊状态 ===
  renderedSystemPrompt?: SystemPrompt
  localDenialTracking?: DenialTrackingState
  preserveToolUseResults?: boolean
}

Context 包含了执行所需的所有信息。

文件读取状态缓存

为什么需要缓存?

每次读取文件都发送内容给 API,浪费 Token。缓存避免重复发送:

typescript 复制代码
type FileReadState = {
  content: string
  timestamp: number
  offset?: number
  limit?: number
  isPartialView: boolean
}

type FileStateCache = Map<string, FileReadState>

// 读取时记录状态
const content = await fs.readFile(filePath)
readFileState.set(filePath, {
  content,
  timestamp: Date.now(),
  offset: input.offset,
  limit: input.limit,
  isPartialView: input.offset !== undefined || input.limit !== undefined,
})

缓存命中检测

typescript 复制代码
// 再次读取同一范围时
const existingState = readFileState.get(filePath)

if (existingState) {
  // 范围匹配
  const rangeMatch =
    existingState.offset === input.offset &&
    existingState.limit === input.limit

  // 文件未修改
  const mtimeMs = await getFileModificationTime(filePath)
  const unchanged = mtimeMs === existingState.timestamp

  if (rangeMatch && unchanged) {
    // 返回 stub,不发送内容
    return {
      type: 'file_unchanged',
      file: { filePath },
    }
  }
}

编辑前的状态检查

typescript 复制代码
// Edit 工具要求先读取
const readState = readFileState.get(filePath)

if (!readState) {
  return {
    result: false,
    message: 'File has not been read yet. Read it first.',
    errorCode: 6,
  }
}

// 文件可能被外部修改
if (lastWriteTime > readState.timestamp) {
  // 检查内容是否真的变了
  const currentContent = await fs.readFile(filePath)
  if (currentContent !== readState.content) {
    return {
      result: false,
      message: 'File has been modified since read',
      errorCode: 7,
    }
  }
}

缓存既是优化,也是安全机制------确保编辑的文件是已知的。

子 Agent 的缓存继承

typescript 复制代码
// Fork 模式:子 Agent 克隆缓存
const agentReadFileState = forkContextMessages !== undefined
  ? cloneFileStateCache(toolUseContext.readFileState)
  : createFileStateCacheWithSizeLimit(READ_FILE_STATE_CACHE_SIZE)

// 克隆函数
function cloneFileStateCache(source: FileStateCache): FileStateCache {
  const clone = new Map()
  for (const [key, value] of source.entries()) {
    clone.set(key, { ...value })
  }
  return clone
}

子 Agent 继承父 Agent 已读取的文件状态,但独立管理更新。

Token 预算管理

Token 计算

typescript 复制代码
// 粗略估算
function roughTokenCountEstimation(text: string): number {
  // 英文 ~4 chars/token
  // 中文 ~1.5 chars/token
  return Math.ceil(text.length / 4)
}

// 精确计算(使用 tokenizer)
function exactTokenCount(text: string, model: string): number {
  const tokenizer = getTokenizerForModel(model)
  return tokenizer.encode(text).length
}

预算分配

typescript 复制代码
// Token 预算来源
function getTokenBudget(model: string): number {
  // Claude 模型限制
  if (model.includes('claude-3')) {
    return 200_000 - SYSTEM_PROMPT_TOKENS - SAFETY_BUFFER
  }

  // 默认
  return 100_000 - SAFETY_BUFFER
}

// 预算分配
type TokenBudgetAllocation = {
  systemPrompt: number       // 系统提示固定预算
  userContext: number        // CLAUDE.md 等
  conversationHistory: number // 消息历史
  toolDescriptions: number   // 工具提示
  responseBudget: number     // 响应预留
}

function allocateTokenBudget(total: number): TokenBudgetAllocation {
  return {
    systemPrompt: 20_000,
    userContext: 30_000,
    conversationHistory: total - 20_000 - 30_000 - 10_000 - 40_000,
    toolDescriptions: 10_000,
    responseBudget: 40_000,
  }
}

Token 监控

typescript 复制代码
type TokenTracker = {
  currentCount: number
  budget: number
  warningThreshold: number  // 80%
  criticalThreshold: number // 90%
}

function checkTokenStatus(tracker: TokenTracker): TokenStatus {
  const usageRatio = tracker.currentCount / tracker.budget

  if (usageRatio >= tracker.criticalThreshold) {
    return 'critical'  // 触发压缩
  }

  if (usageRatio >= tracker.warningThreshold) {
    return 'warning'   // 显示警告
  }

  return 'ok'
}

自动压缩触发

typescript 复制代码
// 每轮结束后检查
const tokenCount = estimateTokenCount(currentMessages)
const tokenBudget = context.tokenBudget

if (tokenCount >= tokenBudget * 0.9) {
  // 触发压缩
  const compactResult = await compactConversation(
    currentMessages,
    context,
    cacheSafeParams,
    false,  // suppressFollowUpQuestions
    undefined,  // customInstructions
    true,  // isAutoCompact
  )

  currentMessages = compactResult.compactedMessages
  yield createCompactBoundaryMessage(compactResult)
}

消息历史管理

消息类型

typescript 复制代码
type Message =
  | UserMessage           // 用户输入
  | AssistantMessage      // AI 响应
  | ToolResultMessage     // 工具结果
  | SystemMessage         // 系统提示
  | AttachmentMessage     // 文件附件
  | CompactBoundaryMessage // 压缩边界

消息过滤

typescript 复制代码
// 发送给 API 时过滤
function filterMessagesForAPI(messages: Message[]): APIMessage[] {
  return messages
    .filter(m => m.type !== 'compact_boundary')
    .filter(m => m.type !== 'attachment' || m.shouldInclude)
    .map(normalizeMessageForAPI)
}

// 不完整工具调用过滤
function filterIncompleteToolCalls(messages: Message[]): Message[] {
  // 移除没有 result 的 tool_use
  const toolUseIds = new Set<string>()
  const completeMessages: Message[] = []

  for (const message of messages) {
    if (message.type === 'assistant') {
      for (const block of message.message.content) {
        if (block.type === 'tool_use') {
          toolUseIds.add(block.id)
        }
      }
    }

    if (message.type === 'tool_result') {
      if (toolUseIds.has(message.toolUseId)) {
        completeMessages.push(message)
      }
    }
  }

  return completeMessages
}

消息截断

typescript 复制代码
// 消息太大时截断
function truncateMessageContent(message: Message, maxTokens: number): Message {
  const content = getMessageContent(message)
  const tokenCount = estimateTokenCount(content)

  if (tokenCount > maxTokens) {
    // 截断并添加提示
    const truncatedContent = content.slice(0, maxTokens * 4)
    return {
      ...message,
      message: {
        ...message.message,
        content: truncatedContent + '\n[truncated]',
      },
    }
  }

  return message
}

CLAUDE.md 加载

加载流程

typescript 复制代码
async function loadClaudeMd(cwd: string): ClaudeMdResult {
  const paths = [
    join(cwd, 'CLAUDE.md'),
    join(cwd, '.claude', 'CLAUDE.md'),
  ]

  for (const path of paths) {
    if (await fs.exists(path)) {
      const content = await fs.readFile(path)

      // 解析 frontmatter
      const { frontmatter, body } = parseFrontmatter(content)

      // 估算 token
      const tokenEstimate = estimateTokenCount(body)

      return {
        path,
        content: body,
        frontmatter,
        tokenEstimate,
      }
    }
  }

  return null
}

Token 预算控制

typescript 复制代码
// CLAUDE.md 太大时警告
if (claudeMdResult.tokenEstimate > 20_000) {
  addNotification({
    type: 'warning',
    message: 'CLAUDE.md is large (~${claudeMdResult.tokenEstimate} tokens). Consider splitting.',
  })
}

// 超限时截断
if (claudeMdResult.tokenEstimate > MAX_CLAUDE_MD_TOKENS) {
  const truncated = truncateClaudeMd(claudeMdResult.content, MAX_CLAUDE_MD_TOKENS)
  claudeMdResult.content = truncated
  claudeMdResult.tokenEstimate = MAX_CLAUDE_MD_TOKENS
}

嵌套加载

typescript 复制代码
// CLAUDE.md 可以引用其他文件
function processClaudeMdReferences(content: string, cwd: string): string[] {
  const references: string[] = []

  // 检测 @file 语法
  const matches = content.matchAll(/@([^\s]+)/g)
  for (const match of matches) {
    const refPath = join(cwd, match[1])
    references.push(refPath)
  }

  return references
}

// 加载引用文件
async function loadNestedClaudeMdFiles(references: string[]): AttachmentMessage[] {
  const attachments: AttachmentMessage[] = []

  for (const refPath of references) {
    if (await fs.exists(refPath)) {
      const content = await fs.readFile(refPath)
      attachments.push({
        type: 'attachment',
        attachment: {
          type: 'file',
          path: refPath,
          content,
        },
      })
    }
  }

  return attachments
}

系统提示构建

动态构建

typescript 复制代码
async function buildSystemPrompt(context: ToolUseContext): SystemPrompt {
  const parts: string[] = []

  // 1. 基础系统提示
  parts.push(getBaseSystemPrompt())

  // 2. 工具描述
  const toolDescriptions = await buildToolDescriptions(context.options.tools)
  parts.push(toolDescriptions)

  // 3. 用户上下文(CLAUDE.md)
  if (context.userContext?.claudeMd) {
    parts.push(context.userContext.claudeMd.content)
  }

  // 4. 记忆文件
  const memoryFiles = await loadMemoryFiles(context)
  for (const memory of memoryFiles) {
    parts.push(memory.content)
  }

  // 5. Skills 提示
  const skillPrompts = await buildSkillPrompts(context.options.skills)
  parts.push(skillPrompts)

  // 6. 自定义追加
  if (context.options.appendSystemPrompt) {
    parts.push(context.options.appendSystemPrompt)
  }

  return {
    content: parts.join('\n\n'),
    tokenEstimate: estimateTokenCount(parts.join('\n\n')),
  }
}

Token 预算平衡

typescript 复制代码
function balanceSystemPromptBudget(
  allocation: TokenBudgetAllocation,
  actual: { systemPrompt: number; userContext: number },
): string {
  const overhead = actual.systemPrompt + actual.userContext - allocation.systemPrompt - allocation.userContext

  if (overhead > 0) {
    // 超限:压缩用户上下文
    const compressedUserContext = compressUserContext(
      actual.userContext,
      allocation.userContext,
    )

    return rebuildSystemPrompt({
      systemPrompt: actual.systemPrompt,
      userContext: compressedUserContext,
    })
  }

  return buildFullSystemPrompt()
}

上下文隔离

子 Agent 独立上下文

typescript 复制代码
function createSubagentContext(parent: ToolUseContext, overrides: Partial<ToolUseContext>): ToolUseContext {
  return {
    ...parent,

    // 独立的消息历史
    messages: overrides.messages ?? [],

    // 克隆的文件状态
    readFileState: cloneFileStateCache(parent.readFileState),

    // 独立的 AbortController
    abortController: new AbortController(),

    // 独立的 Agent ID
    agentId: overrides.agentId,
    agentType: overrides.agentType,

    // 可能有不同的权限模式
    getAppState: overrides.getAppState ?? parent.getAppState,

    // 独立的 MCP clients(可能不同)
    options: {
      ...parent.options,
      mcpClients: overrides.mcpClients ?? parent.options.mcpClients,
      tools: overrides.tools ?? parent.options.tools,
    },
  }
}

上下文清理

typescript 复制代码
// 子 Agent 结束时清理
async function cleanupSubagentContext(context: ToolUseContext): void {
  // 清除文件状态缓存
  context.readFileState.clear()

  // 关闭 MCP connections
  for (const client of context.options.mcpClients) {
    if (client.type === 'connected' && client.isSubagentOwned) {
      await client.cleanup()
    }
  }

  // 清除 Hook 注册
  clearSessionHooks(context.agentId)

  // 清除追踪
  unregisterPerfettoAgent(context.agentId)
}

状态持久化

会话保存

typescript 复制代码
async function saveSessionState(sessionId: string, context: ToolUseContext): void {
  const sessionDir = `.claude/sessions/${sessionId}`

  // 消息历史
  await fs.writeFile(
    join(sessionDir, 'messages.json'),
    JSON.stringify(context.messages),
  )

  // 文件状态摘要(不保存完整内容)
  const fileStateSummary = {}
  for (const [path, state] of context.readFileState.entries()) {
    fileStateSummary[path] = {
      timestamp: state.timestamp,
      offset: state.offset,
      limit: state.limit,
    }
  }
  await fs.writeFile(
    join(sessionDir, 'file-state.json'),
    JSON.stringify(fileStateSummary),
  )

  // 元数据
  await fs.writeFile(
    join(sessionDir, 'metadata.json'),
    JSON.stringify({
      sessionId,
      agentId: context.agentId,
      tokenCount: estimateTokenCount(context.messages),
      savedAt: Date.now(),
    }),
  )
}

会话恢复

typescript 复制代码
async function restoreSessionState(sessionId: string): ToolUseContext {
  const sessionDir = `.claude/sessions/${sessionId}`

  // 消息历史
  const messages = JSON.parse(await fs.readFile(join(sessionDir, 'messages.json')))

  // 文件状态(重新验证)
  const fileStateSummary = JSON.parse(await fs.readFile(join(sessionDir, 'file-state.json')))
  const readFileState = new Map()

  for (const [path, summary] of Object.entries(fileStateSummary)) {
    // 验证文件是否还存在
    if (await fs.exists(path)) {
      const mtimeMs = await getFileModificationTime(path)
      if (mtimeMs === summary.timestamp) {
        readFileState.set(path, {
          timestamp: summary.timestamp,
          offset: summary.offset,
          limit: summary.limit,
          isPartialView: summary.offset !== undefined || summary.limit !== undefined,
        })
      }
    }
  }

  return buildToolUseContext({
    sessionId,
    initialMessages: messages,
    readFileState,
  })
}

总结

上下文管理展示了状态管理的复杂设计:

状态类型 管理策略
文件读取 缓存 + 去重 + 修改检测
Token 预算分配 + 监控 + 自动压缩
消息历史 过滤 + 截断 + 类型管理
CLAUDE.md 加载 + 嵌套引用 + Token 控制
系统提示 动态构建 + 预算平衡
子 Agent 克隆 + 独立 + 清理
持久化 保存 + 恢复 + 验证

核心设计:缓存 + Token 预算 + 克隆 + 持久化

下一篇,我们将深入子 Agent 架构------Fork 模式如何工作,父子 Agent 如何通信。

相关推荐
user29876982706541 小时前
三、AI Agent 设计模式:工具编排
人工智能
Elastic 中国社区官方博客1 小时前
Elasticsearch:为 AI Agent builder 创建 skill plugin
大数据·数据库·人工智能·elasticsearch·搜索引擎·ai·全文检索
肖有米XTKF86461 小时前
肖有米团队开发:青蓝送水系统模式制度商业解析
人工智能·团队开发·csdn开发云
Data_Journal1 小时前
2026年十大数据集网站
大数据·开发语言·数据库·人工智能·python
helloworddm1 小时前
Vulkan GPU图像处理之直方图均衡化:Kompute框架实战与性能分析
图像处理·人工智能
xingyuzhisuan1 小时前
适合微调Llama 3 70B模型的最低GPU配置推荐
运维·人工智能·算法·llama·gpu算力
珠海西格电力1 小时前
如何实现零碳园区管理系统“云-边-端”架构的协同
大数据·数据库·人工智能·架构·能源
初心未改HD1 小时前
机器学习之逻辑回归详解
人工智能·机器学习·逻辑回归
简简单单做算法1 小时前
基于GAN生成对抗网络模型的图像生成与虚拟场景构建系统matlab仿真
人工智能·神经网络·生成对抗网络·matlab·gan·虚拟场景构建