Claude Code 源码分析第 15 课 · 第 05
第 15 课

上下文压缩

Claude Code 如何管理跨长时间会话的有限上下文窗口 - 从即时微型紧凑到完整的 LLM 总结。

01 Overview

每个法学硕士都有一个有限的上下文窗口。 对于 Claude Code 运行长时间的编码会话(读取文件、运行 shell 命令、迭代错误),该窗口填满的速度比您预期的要快。 上下文压缩 是 Claude Code 用来在令牌压力增大时保持对话活跃且有用的一组策略。

覆盖源文件
services/compact/microCompact.ts  ·  services/compact/compact.ts  ·  services/compact/autoCompact.ts  ·  services/compact/sessionMemoryCompact.ts  ·  services/compact/prompt.ts  ·  services/compact/postCompactCleanup.ts  ·  services/compact/timeBasedMCConfig.ts  ·  services/compact/apiMicrocompact.ts  ·  commands/compact/compact.ts  ·  commands/context/context.tsx

四大策略,每个都有不同的成本/保真度权衡:

策略1

Microcompact

从内存消息数组中静默清除旧的工具结果内容。 零 API 调用,即时。

策略2

会话内存紧凑

用预先构建的会话内存文件替换旧消息。 零汇总API调用。

策略3

完整的法学硕士紧凑型

分叉一个子代理来编写 9 部分的结构化摘要。 一次额外的 API 调用,最高保真度。

策略4

反应紧凑型

由 413 提示太长的 API 错误触发。 从尾部剥落 API 圆,直到符合要求为止。

心智模型
将这些策略视为成本阶梯。 Claude Code 总是首先尝试最便宜的选项。 微型紧凑型是免费的。 会话内存几乎是免费的。 完整的 LLM 紧凑型需要一次额外的 API 调用。 反应式紧凑型是当其他一切都失败时的紧急逃生口。
02 阈值和令牌警告状态

压缩是阈值控制的。 在每次 API 调用之前, autoCompact.ts 计算一个 有效上下文窗口 通过从原始上下文大小中减去输出余量,然后得出四个不同的警报级别。

// autoCompact.ts — effective window calculation
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000  // p99.99 of compact output

export function getEffectiveContextWindowSize(model: string): number {
  const reservedTokensForSummary = Math.min(
    getMaxOutputTokensForModel(model),
    MAX_OUTPUT_TOKENS_FOR_SUMMARY,
  )
  const contextWindow = getContextWindowForModel(model, getSdkBetas())
  return contextWindow - reservedTokensForSummary
}
State 缓冲区低于有效窗口 Effect Constant
Normal > 剩余 20 000 个代币 无动作
Warning 剩余 ≤ 20 000 个代币 UI 显示黄色指示器 WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
Error 剩余 ≤ 20 000 个代币(相同级别) UI 显示红色指示器 ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
Auto-Compact 剩余 ≤ 13 000 个代币 触发自动压缩 AUTOCOMPACT_BUFFER_TOKENS = 13_000
Blocking 剩余 ≤ 3 000 个代币 阻止新用户输入 MANUAL_COMPACT_BUFFER_TOKENS = 3_000
为什么要预留 20k 代币?
紧凑摘要代理本身生成输出。 该团队测量了 p99.99 的紧凑输出为 17,387 个代币,四舍五入为 20k。 如果有效窗口没有留下这个余量,压缩请求本身可能会出现提示太长的情况——调试时会出现严重的失败。
深入探讨:断路器

自动压缩可能会失败(网络超时、压缩请求本身提示太长)。 如果没有防护装置,随后的每个回合都会重试压实,从而以注定失败的尝试来打击 API。 代码跟踪 consecutiveFailures 并在 3 后停止:

// autoCompact.ts
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272)
// in a single session, wasting ~250K API calls/day globally.
if (tracking?.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
  return { wasCompacted: false }
}

这个评论异常坦率:在这个断路器存在之前,一种故障模式是全球每天消耗 25 万个 API 调用。 修复方法是三行。

03 Microcompact — 即时工具结果修剪

Microcompact 是一个 API 调用前通道,可直接清除内存消息数组中旧工具结果的内容。 确实如此 not 打电话给法学硕士并做 not 将任何内容写入磁盘。 目标:在发送之前缩小提示,无需支付任何费用。

哪些工具符合条件?

// microCompact.ts — only results from these tools can be cleared
const COMPACTABLE_TOOLS = new Set<string>([
  FILE_READ_TOOL_NAME,
  ...SHELL_TOOL_NAMES,     // Bash, etc.
  GREP_TOOL_NAME,
  GLOB_TOOL_NAME,
  WEB_SEARCH_TOOL_NAME,
  WEB_FETCH_TOOL_NAME,
  FILE_EDIT_TOOL_NAME,
  FILE_WRITE_TOOL_NAME,
])

只有读取/搜索/shell 工具结果才符合资格。 这些工具的结果很大,通常是陈旧的,并且在几次轮换后不太可能逐字需要。 来自自定义 MCP 工具、代理生成或用户可见操作的工具结果将被保留。

三个微型紧凑路径

flowchart TD A[microcompactMessages called] --> B{Time-based trigger?} B -- Yes, gap > threshold --> C[Time-Based MC\nContent-clear old tool results\nmutate messages directly] B -- No --> D{feature CACHED_MC\n+ supported model?} D -- Yes --> E[Cached MC Path\nQueue cache_edits block\ndo NOT mutate messages] D -- No --> F[Return messages unchanged\nautocompact handles pressure] C --> G[Reset cachedMCState\nreturn mutated messages] E --> H[Return messages + pendingCacheEdits\nAPI layer inserts edits] style C fill:#b8965e,color:#fff style E fill:#22201d,color:#fff style F fill:#141211,color:#5c564f

路径 1:基于时间的微型紧凑型

如果自上一条助理消息以来的间隔超过阈值(默认值:60 分钟),则服务器端提示缓存几乎肯定已过期。 重写提示是不可避免的 - 因此内容清除旧工具的结果 before 该请求缩小了重写的内容。 该逻辑纯粹是客户端的,并且会就地改变消息。

// timeBasedMCConfig.ts — GrowthBook-controlled config
const TIME_BASED_MC_CONFIG_DEFAULTS: TimeBasedMCConfig = {
  enabled: false,
  gapThresholdMinutes: 60,  // server 1h cache TTL
  keepRecent: 5,            // always keep the last 5 tool results
}
// microCompact.ts — content-clearing loop
const keepSet = new Set(compactableIds.slice(-keepRecent))
const clearSet = new Set(compactableIds.filter(id => !keepSet.has(id)))

// Replace each cleared block's content with a sentinel string
return { ...block, content: TIME_BASED_MC_CLEARED_MESSAGE }
// TIME_BASED_MC_CLEARED_MESSAGE = '[Old tool result content cleared]'

路径 2:缓存的微型紧凑型(实验性)

常规的基于时间的路径会改变消息内容,这会破坏服务器端提示缓存(前缀已更改)。 缓存 MC 以不同的方式解决了这个问题:它不是重写消息内容,而是将消息排队 cache_edits 块为 API层 应用服务器端,保持缓存的前缀不变。

// microCompact.ts — cached MC result shape
return {
  messages,  // UNCHANGED — messages are not mutated
  compactionInfo: {
    pendingCacheEdits: {
      trigger: 'auto',
      deletedToolIds: toolsToDelete,
      baselineCacheDeletedTokens: baseline,
    },
  },
}
缓存编辑见解
关键的设计见解:API 允许您告诉它“从缓存的前缀中删除这些工具结果”,而无需重新发送整个提示。 这意味着即使在修剪之后缓存仍保持温暖。 常规的 microcompact(内容清除)必然会使缓存失效,因为提示文本发生了变化。
深入探讨:微型决策的代币估计

Microcompact 需要估计工具结果包含多少个标记,以便决定要清除哪些内容。 它使用粗略的基于字符的启发式,用 4/3 填充:

// microCompact.ts
export function estimateMessageTokens(messages: Message[]): number {
  // ... walk all blocks ...
  // Pad estimate by 4/3 to be conservative since we're approximating
  return Math.ceil(totalTokens * (4 / 3))
}

无论格式如何,图像和文档始终计为 2,000 个标记(IMAGE_MAX_TOKEN_SIZE = 2000)。 4/3 填充补偿了高于 1:1 的字符与标记的比率。

04 会话内存压缩

会话内存压缩是一种实验性路径,可以完全避免完整 LLM 汇总调用的成本。 它没有要求克劳德总结对话,而是使用不断更新的 会话内存文件 在后台编写,作为压缩会话的上下文。

什么时候激活?

Both autoCompactIfNeeded/compact 命令首先尝试会话内存压缩,然后再回退到完整的 LLM 压缩:

// autoCompact.ts — session memory is always tried first
const sessionMemoryResult = await trySessionMemoryCompaction(
  messages,
  toolUseContext.agentId,
  recompactionInfo.autoCompactThreshold,
)
if (sessionMemoryResult) {
  setLastSummarizedMessageId(undefined)
  runPostCompactCleanup(querySource)
  return { wasCompacted: true, compactionResult: sessionMemoryResult }
}

保留哪些消息?

关键功能 calculateMessagesToKeepIndex 找到已汇总到会话内存中的内容与足以保留逐字记录的最新内容之间的界限。 它从最后汇总的消息向后扩展,直到满足可配置的最小值:

// sessionMemoryCompact.ts — default config (can be overridden by GrowthBook)
export const DEFAULT_SM_COMPACT_CONFIG: SessionMemoryCompactConfig = {
  minTokens: 10_000,          // keep at least 10k tokens of recent context
  minTextBlockMessages: 5,    // keep at least 5 messages with text content
  maxTokens: 40_000,          // hard cap: don't keep more than 40k tokens
}

工具对不变量

一个微妙的正确性要求:API 拒绝以下对话: tool_result 块引用a tool_use 之前未出现在消息列表中的块。 当我们进行切片以仅保留最近的消息时,我们可能会意外地包含一条用户消息 tool_result 阻止但排除前面具有相应内容的辅助消息 tool_use。 功能 adjustIndexToPreserveAPIInvariants 向后行走以查找并包含任何孤立的工具使用对。

flowchart LR A["Messages: [... old ... | SUMMARIZED | NEW ]"] --> B["findLastSummarizedIndex()"] B --> C["calculateMessagesToKeepIndex()\n(start from summarized+1,\nexpand backward to meet minimums)"] C --> D["adjustIndexToPreserveAPIInvariants()\n(pull in orphaned tool_use pairs\n+ thinking block partners)"] D --> E["messagesToKeep = messages.slice(startIndex)\n.filter(!isCompactBoundary)"] E --> F["Build CompactionResult\nwith session memory as summary\nno LLM call needed"] style F fill:#22201d,color:#fff
深入探讨:处理两种压缩场景

场景 1 — 正常情况: lastSummarizedMessageId 已设置。 会话内存提取至少运行一次,我们确切地知道它覆盖了哪些消息。 我们只保留该 ID 之后的消息(扩展到满足最小值)。

场景 2 — 恢复会议: 会话内存有内容但是 lastSummarizedMessageId 未设置(例如,会话从之前的记录中恢复)。 我们将此视为“一切都已概括”并设定 lastSummarizedIndex to messages.length - 1。 无论如何,扩展循环可能会保留一些最近的消息以满足最小值。

// sessionMemoryCompact.ts
if (!lastSummarizedMessageId) {
  // Resumed session: session memory has content but we don't know the boundary
  lastSummarizedIndex = messages.length - 1
  logEvent('tengu_sm_compact_resumed_session', {})
}
05 完整的法学硕士压缩——9部分总结提示

当 microcompact 和会话内存压缩都不可用时,Claude Code 分叉一个子代理并要求它编写整个对话的结构化摘要。 这是最昂贵的路径 - 一个额外的 API 调用 - 但会产生最忠实的摘要。

压实流量

sequenceDiagram participant Q as Query Loop participant C as compactConversation() participant H as Pre-Compact Hooks participant F as Forked Agent participant P as Post-Compact Q->>C: messages, toolUseContext C->>H: executePreCompactHooks() H-->>C: customInstructions (merged) C->>F: streamCompactSummary() with 9-section prompt F-->>C: summary text (analysis + summary XML) Note over C: formatCompactSummary() strips <analysis> block C->>P: createCompactBoundaryMessage() C->>P: createPostCompactFileAttachments() — up to 5 files C->>P: processSessionStartHooks() C-->>Q: CompactionResult{boundaryMarker, summaryMessages, attachments}

9 部分摘要提示

压缩提示指示模型生成包含这九个部分的结构化摘要。 这种结构是有意为之的:它确保每个后续会话即使在没有其他上下文的情况下也能理解正在发生的事情。

  1. 主要请求和意图 用户的所有明确请求和意图的详细信息。
  2. 关键技术概念 讨论了重要的技术、框架和设计模式。
  3. 文件和代码部分 检查/修改/创建特定文件,并在适用的情况下提供完整的代码片段。
  4. 错误和修复 遇到的每个错误以及如何解决。 用户反馈被突出显示。
  5. 解决问题 已解决的问题和持续的故障排除工作。
  6. 所有用户留言 每条非工具结果用户消息均逐字列出。 对于跟踪意图漂移至关重要。
  7. 待处理任务 明确分配但尚未完成的任务。
  8. 目前的工作 正是在压缩之前发生的事情,包括文件名和片段。
  9. 可选的下一步 仅当直接符合最近的用户请求时。 必须包含逐字引用。
为什么第 6 课 节很重要
“所有用户消息”是唯一不能仅从工具使用中推断出来的部分。 没有它,摘要将捕获克劳德的内容 did 但却失去了用户 意图转变。 包含逐字的用户消息可以防止模型产生与实际发生的情况不相符的连贯叙述的幻觉。

分析暂存器模式

提示要求模型将其推理包含在 <analysis> 生产前的标签 <summary>。 分析部分是一个起草草稿本——它在摘要到达上下文之前被删除:

// prompt.ts — formatCompactSummary strips analysis block
export function formatCompactSummary(summary: string): string {
  let formatted = summary

  // Strip analysis section — drafting scratchpad, no informational value
  formatted = formatted.replace(
    /<analysis>[\s\S]*?<\/analysis>/,
    '',
  )

  // Extract and format <summary> section
  const match = formatted.match(/<summary>([\s\S]*?)<\/summary>/)
  if (match) {
    formatted = formatted.replace(
      /<summary>[\s\S]*?<\/summary>/,
      `Summary:\n${match[1]!.trim()}`,
    )
  }
  return formatted.trim()
}

无工具序言

由于紧凑请求分叉了主对话的工具集(用于缓存键匹配),因此模型可能会尝试调用工具,尽管被要求进行总结。 工具调用浪费了唯一的回合并且不产生摘要。 提示以咄咄逼人的序言开头:

// prompt.ts
const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.

- Do NOT use Read, Bash, Grep, Glob, Edit, Write, or ANY other tool.
- You already have all the context you need in the conversation above.
- Tool calls will be REJECTED and will waste your only turn — you will fail the task.
- Your entire response must be plain text: an <analysis> block followed by a <summary> block.

`
压缩本身提示时间太长
如果对话很长,紧凑请求本身可能会返回 413。 该代码通过最多 3 次重试来处理此问题:每次重试都会删除最旧的 API 轮组,直到请求适合或无法删除任何内容。 这被跟踪为 tengu_compact_ptl_retry events.
深入探讨:部分紧凑提示

实际上有三种紧凑的提示变体,而不是一种:

  • BASE_COMPACT_PROMPT — 完整对话摘要,第 1 课-9 部分,包括“当前工作”
  • PARTIAL_COMPACT_PROMPT (direction: 'from')——仅总结 recent 部分; 较早的消息保持不变
  • PARTIAL_COMPACT_UP_TO_PROMPT (direction: 'up_to') — 摘要放置在 start 连续会议; 第 9 课 节变为“继续工作的背景”而不是“可选的下一步”

反应式紧凑路径使用部分提示来仅总结需要丢弃的对话部分。

06 反应式紧凑 — Context Collapse

主动自动紧凑型火灾 before 上下文窗口已满。 但是,如果会话启动时窗口已超出限制(例如,具有较大转录本的恢复会话或禁用自动压缩的会话),该怎么办? 反应式紧凑路径可以处理这种情况。

反应式紧凑型何时起火?

反应式紧凑型以两种模式激活:

  • 仅反应模式 (tengu_cobalt_raccoon 功能标志):完全抑制主动自动压缩; 来自 API 的 413 错误是唯一的触发因素。
  • 紧急后备:API 返回 413 prompt_too_long 正常请求时出错。 该代码从最旧的一端剥离 API 轮组并重试。
// autoCompact.ts — reactive-only mode short-circuit
if (feature('REACTIVE_COMPACT')) {
  if (getFeatureValue_CACHED_MAY_BE_STALE('tengu_cobalt_raccoon', false)) {
    return false  // suppress proactive autocompact
  }
}

上下文崩溃

还有一个单独的 上下文崩溃 特征 (CONTEXT_COLLAPSE)在启用时完全抑制自动压缩。 上下文崩溃是它自己的上下文管理系统,在 90%(提交)和 95%(阻塞)阈值下运行,比压缩更细粒度。 自动紧凑坐姿约为 93% 会与之竞争:

// autoCompact.ts — suppress autocompact when context collapse is active
if (feature('CONTEXT_COLLAPSE')) {
  if (isContextCollapseEnabled()) {
    return false  // let collapse manage the headroom problem
  }
}
递归守卫
自动压缩不得触发 querySource === 'session_memory' or querySource === 'compact' ——这些是分叉的代理,如果它们试图压缩自己,就会陷入僵局。 在任何压缩逻辑运行之前都会检查防护。
07 按 API 轮次进行消息分组

紧凑请求提示过长重试和反应式紧凑路径都需要将消息丢弃在安全单元中。 该单位是一个 API轮:来自一个完整的请求-响应对的一组消息。

// grouping.ts — group at assistant message.id boundaries
export function groupMessagesByApiRound(messages: Message[]): Message[][] {
  const groups: Message[][] = []
  let current: Message[] = []
  let lastAssistantId: string | undefined

  for (const msg of messages) {
    if (
      msg.type === 'assistant' &&
      msg.message.id !== lastAssistantId &&
      current.length > 0
    ) {
      groups.push(current)
      current = [msg]
    } else {
      current.push(msg)
    }
    if (msg.type === 'assistant') lastAssistantId = msg.message.id
  }
  if (current.length > 0) groups.push(current)
  return groups
}

边界信号是 助理消息ID 改变。 流媒体发送一 AssistantMessage 每个内容块(思考、工具使用、文本)都共享相同的内容 message.id。 新的 ID 意味着真正的新 API 往返。 这使得代码可以在轮次边界处安全地分割,而不会破坏属于同一轮次的 tool_use/tool_result 对。

08 压缩后清理

在任何成功的压缩(微型压缩、会话内存或完整 LLM)之后,都会运行一个清理函数以使缓存无效,并指出新上下文窗口中可能出现的错误。

// postCompactCleanup.ts — called by ALL compaction paths
export function runPostCompactCleanup(querySource?: QuerySource): void {
  const isMainThread =
    querySource === undefined ||
    querySource.startsWith('repl_main_thread') ||
    querySource === 'sdk'

  resetMicrocompactState()           // always

  if (feature('CONTEXT_COLLAPSE') && isMainThread) {
    resetContextCollapse()           // main thread only
  }
  if (isMainThread) {
    getUserContext.cache.clear?.()   // re-read CLAUDE.md on next turn
    resetGetMemoryFilesCache()       // arm InstructionsLoaded hook
  }
  clearSystemPromptSections()
  clearClassifierApprovals()
  clearSpeculativeChecks()
  clearBetaTracingState()
  clearSessionMessagesCache()
}
分代理安全
子代理与主线程运行在同一操作系统进程中,并共享模块级状态。 如果子代理的压缩已清除 getUserContext,它会破坏主线程的内存文件缓存。 这 isMainThread 警卫阻止了这一点。 守卫使用 startsWith('repl_main_thread') 因为输出样式变体产生像这样的源 'repl_main_thread:outputStyle:custom'.

故意不清除的内容

清理是故意的 not reset sentSkillNames。 在每个紧凑型上重新注入完整的技能列表(~4k 令牌)将是纯粹的缓存失效,带来的好处最小——模型仍然具有 SkillTool 架构,并且 invoked_skills 附件保留使用的技能内容。 这是评论中记录的故意的性能权衡。

后压缩文件附件

在完整的 LLM 紧凑之后,系统重新注入模型之前读取的文件,因此不需要在新会话中重新读取它们:

// compact.ts — constants for post-compact file restoration
export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5
export const POST_COMPACT_TOKEN_BUDGET = 50_000
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000
export const POST_COMPACT_MAX_TOKENS_PER_SKILL = 5_000
export const POST_COMPACT_SKILLS_TOKEN_BUDGET = 25_000

技能是按技能设置上限的,而不是完全删除:每个技能文件可以是 18-20 KB,之前它们是无限制地重新注入的,每个契约花费 5-10k 代币。 按技能截断保留最重要的指令(位于文件顶部),同时限制总成本。

09 /context 命令

The /context 命令向用户显示其上下文窗口的完整程度。 重要的设计细节:它应用了与之前相同的 API 转换 query.ts 适用,因此用户看到的内容反映了模型实际接收到的内容,而不是原始的 REPL 回滚历史记录。

// context.tsx — mirrors the query.ts pre-API transform pipeline
function toApiView(messages: Message[]): Message[] {
  // 1. Slice to only messages after the last compact boundary
  let view = getMessagesAfterCompactBoundary(messages)
  // 2. Apply context-collapse projection if enabled
  if (feature('CONTEXT_COLLAPSE')) {
    view = projectView(view)
  }
  return view
}

// Then apply microcompact to get accurate token count
const { messages: compacted } = await microcompactMessages(apiView)

如果没有这个管道,无论上下文崩溃保存了多少,令牌计数都会超算——当 API 只看到 120k 时,用户会看到“180k,3 个跨度崩溃”。 应用与真实 API 调用路径相同的转换可以使显示准确。

要点

  • 压缩是一个成本阶梯:微型压缩是免费的,会话内存几乎是免费的,完整的LLM压缩需要一次额外的API调用,反应式压缩是逃生舱口。
  • 有效上下文窗口是原始窗口减去为紧凑摘要输出本身保留的 20k 标记——一个基于 p99.99 的常量。
  • 自动压缩在有效窗口限制之前的 13k 个令牌处触发(AUTOCOMPACT_BUFFER_TOKENS); 在 3k 处阻止触发器。
  • Microcompact 从不调用 API - 它会清除本地消息数组中旧工具结果的内容,或者(使用缓存的 MC)对不会破坏提示缓存的服务器端 cache_edit 进行排队。
  • 会话内存压缩通过使用持续更新的内存文件完全避免了汇总 API 调用。 它由两个功能标志控制,并具有可配置的最小/最大令牌预算,用于保存逐字记录的最近消息数。
  • 9 部分的摘要提示是一个经过深思熟虑的结构:第 6 课 部分(所有用户消息)捕获了仅使用工具使用历史记录会错过的意图转变。
  • The <analysis> 紧凑输出中的块是一个暂存器——在摘要进入上下文窗口之前它总是被删除。
  • 压缩后清理集中在 runPostCompactCleanup 并保护子代理,子代理与主线程共享模块级状态。
  • The /context 命令应用与查询循环相同的预 API 转换来显示准确的标记计数,而不是原始 REPL 历史记录。

知识检查

Q1. 有效上下文窗口小于模型的原始上下文窗口的主要原因是什么?
Q2. 缓存微型紧凑型与常规(基于时间的)微型紧凑型不同,主要是因为它:
Q3. 当工具使用历史记录已经捕获了所做的操作时,为什么简洁的摘要提示包含第 6 课 节“所有用户消息”?
Q4. The adjustIndexToPreserveAPIInvariants 函数向后扩展会话内存紧凑“保持”边界。 它保护哪两个不变量?
Q5. 为什么会 runPostCompactCleanup 只清楚 getUserContext and getMemoryFilesCache 对于主线程压缩,而不是子代理压缩?
Q6. The /context 命令在显示令牌使用情况之前应用 microcomp。 为什么?
0/6

完成所有问题即可查看您的分数。