Back to Blog

深入剖析 Claude Code:架构模式与设计哲学(第二部分)

2026-03-31
Claude Code AI Agent TypeScript 架构设计 AsyncGenerator

深入剖析 Claude Code:架构模式与设计哲学

系列文章第二部分:架构模式深度剖析


在第一篇文章中,我们了解了 Claude Code 的整体架构和工具化 Agent 设计理念。今天,我们将深入探讨其核心架构模式,这些设计决策不仅让系统更健壮,也为构建类似 AI Agent 提供了宝贵参考。

AsyncGenerator 模式:流式处理的艺术

Claude Code 广泛使用异步生成器(AsyncGenerator)来处理长运行操作。这并非偶然,而是经过深思熟虑的架构决策。

为什么选择 AsyncGenerator?

传统 Promise 模式存在明显局限:

// 传统 Promise 模式
async function executeCommand(command: string): Promise {
  // 所有进度信息被隐藏
  const result = await spawn(command)
  return result
}

// 使用时
const result = await executeCommand('npm test')
// 在等待期间,用户什么都看不到

而 AsyncGenerator 提供了流式处理能力:

// AsyncGenerator 模式
async function* executeCommand(
  command: string,
  abortController: AbortController
): AsyncGenerator {
  yield { type: 'progress', data: 'Starting...' }

  const process = spawn(command, { shell: true })

  for await (const chunk of process.stdout) {
    if (abortController.signal.aborted) {
      process.kill()
      return
    }
    // 流式输出每一行
    yield { type: 'data', data: chunk.toString() }
  }

  yield { type: 'result', data: exitCode }
}

四大核心优势

1. 实时进度更新

// 消费端可以立即显示进度
for await (const result of executeCommand('npm test')) {
  if (result.type === 'progress') {
    updateProgressUI(result.data)
  } else if (result.type === 'data') {
    appendToTerminal(result.data)
  }
}

2. 优雅取消

const abortController = new AbortController()

// 启动长时间操作
const generator = longRunningOperation(params, abortController)

// 用户按 Ctrl+C
abortController.abort()  // 立即停止生成器

3. 背压处理

消费者控制处理速率:

for await (const result of generator) {
  // 如果这里处理慢,生成器会自动等待
  await expensiveProcessing(result)
}

4. 可组合性

生成器可以组合和转换:

async function* filterResults(generator: AsyncGenerator) {
  for await (const result of generator) {
    if (result.type === 'data') {
      yield result
    }
  }
}

实际应用:BashTool 实现

// src/tools/BashTool/BashTool.ts (简化版)
export class BashTool implements Tool {
  name = 'bash'

  async call(
    params: { command: string },
    context: ToolUseContext
  ): AsyncGenerator {
    const { command } = params
    const { signal } = context.abortController

    yield { type: 'progress', data: `Executing: ${command}` }

    const shell = process.env.SHELL || '/bin/bash'
    const args = ['-c', command]

    const childProcess = spawn(shell, args)

    // 监听 stdout
    for await (const chunk of childProcess.stdout) {
      if (signal.aborted) {
        childProcess.kill()
        return
      }
      yield { type: 'data', data: chunk.toString() }
    }

    // 等待进程结束
    const exitCode = await new Promise(resolve => {
      childProcess.on('close', resolve)
    })

    yield { type: 'result', data: { exitCode } }
  }
}

AbortController:统一取消模式

每个异步操作都接受 AbortController,这是 Claude Code 的核心设计模式之一。

工具接口设计

interface ToolUseContext {
  abortController: AbortController
  options: QueryOptions
  messageId?: string
  readFileTimestamps: Record
}

interface Tool {
  async call(
    params: any,
    context: ToolUseContext
  ): AsyncGenerator
}

取消传播链

用户按 Ctrl+C
    │
    ▼
全局 AbortController.abort()
    │
    ├─► 查询引擎取消
    │   └─► 所有正在执行的工具收到信号
    │       ├─► BashTool: kill 子进程
    │       ├─► GrepTool: 停止 ripgrep
    │       └─► FileReadTool: 停止读取
    │
    └─► Claude API 取消请求

实现细节

// src/query.ts (简化版)
async function queryWithTools(
  message: string,
  abortController: AbortController
): Promise {
  // 调用 Claude API
  const response = await anthropic.messages.create({
    model: 'claude-sonnet-4-20250514',
    messages: [{ role: 'user', content: message }],
    tools: getTools(),
    abortSignal: abortController.signal  // 传递取消信号
  })

  // 检查是否被取消
  if (abortController.signal.aborted) {
    throw new Error('Query cancelled')
  }

  // 处理工具使用
  for (const block of response.content) {
    if (block.type === 'tool_use') {
      const tool = getTool(block.name)

      // 执行工具时传递 abortController
      for await (const result of tool.call(block.input, {
        abortController
      })) {
        if (abortController.signal.aborted) {
          return  // 提前退出
        }
        yield result
      }
    }
  }
}

Ctrl+C 处理

// src/entrypoints/cli.tsx (简化版)
import readline from 'readline'

async function main() {
  const abortController = new AbortController()

  // 监听 Ctrl+C
  readline.emitKeypressEvents(process.stdin)
  process.stdin.setRawMode(true)

  process.stdin.on('keypress', (str, key) => {
    if (key.ctrl && key.name === 'c') {
      abortController.abort()
    }
  })

  try {
    await runREPL(abortController)
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('\n操作已取消')
    }
  }
}

消息流架构

Claude Code 的消息处理流程展示了精密的管道设计:

用户输入 "修复测试"
    │
    ▼
标准化 (messages.ts)
    ├─► 添加上下文
    ├─► 规范化格式
    └─► 创建消息对象
    │
    ▼
Claude API 调用 (claude.ts)
    ├─► 系统提示词 + 上下文
    ├─► 对话历史
    └─► 可用工具列表
    │
    ▼
响应处理 (query.ts)
    │
    ├─► 包含工具使用?
    │   ├─► 并行执行工具
    │   ├─► 收集结果
    │   └─► 发送回 Claude
    │
    └─► 纯文本响应?
        └─► 显示给用户
            │
            ▼
        更新对话历史
            │
            ▼
        循环继续

实现示例

// src/query.ts (简化版)
async function processMessage(
  userMessage: string,
  abortController: AbortController
): AsyncGenerator {
  // 1. 标准化消息
  const normalized = normalizeMessage(userMessage)

  // 2. 添加上下文
  const context = await getContext()

  // 3. 调用 Claude API
  const response = await queryClaude({
    message: normalized,
    context,
    tools: getTools(),
    abortSignal: abortController.signal
  })

  // 4. 处理响应
  for (const block of response.content) {
    if (block.type === 'text') {
      yield { type: 'assistant', content: block.text }
    } else if (block.type === 'tool_use') {
      // 5. 并行执行工具
      const results = await executeToolsConcurrently(
        block.tool_uses,
        abortController
      )

      // 6. 发送结果回 Claude
      const followUp = await queryClaude({
        message: results,
        context,
        tools: getTools(),
        abortSignal: abortController.signal
      })

      yield* processMessage(followUp, abortController)
    }
  }
}

并行工具执行

Claude Code 可以同时执行多个工具,大幅提升效率。

并发控制

const MAX_TOOL_USE_CONCURRENCY = 10

async function executeToolsConcurrently(
  toolUses: ToolUse[],
  abortController: AbortController
): Promise {
  // 分批执行,最多 10 个并发
  const batches = []

  for (let i = 0; i < toolUses.length; i += MAX_TOOL_USE_CONCURRENCY) {
    const batch = toolUses.slice(i, i + MAX_TOOL_USE_CONCURRENCY)
    batches.push(batch)
  }

  const allResults = []

  for (const batch of batches) {
    // 并行执行一批工具
    const batchResults = await Promise.all(
      batch.map(async (toolUse) => {
        const tool = getTool(toolUse.name)
        const results = []

        for await (const result of tool.call(toolUse.input, {
          abortController
        })) {
          if (abortController.signal.aborted) {
            throw new Error('Cancelled')
          }
          results.push(result)
        }

        return results
      })
    )

    allResults.push(...batchResults.flat())
  }

  return allResults
}

错误处理策略

async function executeToolsConcurrently(
  toolUses: ToolUse[],
  abortController: AbortController
): Promise {
  const results = await Promise.allSettled(
    toolUses.map(toolUse => executeTool(toolUse, abortController))
  )

  return results.map((result, index) => {
    if (result.status === 'fulfilled') {
      return result.value
    } else {
      return {
        type: 'error',
        tool: toolUses[index].name,
        error: result.reason.message
      }
    }
  })
}

实际场景示例

当 Claude 需要分析多个文件时:

// Claude 决定并行读取这些文件
const toolUses = [
  { name: 'read_file', input: { path: 'src/index.ts' } },
  { name: 'read_file', input: { path: 'src/utils.ts' } },
  { name: 'read_file', input: { path: 'src/config.ts' } },
  { name: 'grep', input: { pattern: 'TODO', path: 'src/' } },
  { name: 'glob', input: { pattern: '**/*.test.ts' } }
]

// 所有工具并行执行,而不是串行
const results = await executeToolsConcurrently(toolUses, abortController)

记忆化模式 (Memoization)

昂贵的操作(如构建目录结构)只计算一次并缓存。

lodash memoize 应用

import { memoize } from 'lodash-es'

// src/context.ts
export const getDirectoryStructure = memoize(
  async function (): Promise {
    let lines: string

    try {
      const abortController = new AbortController()
      setTimeout(() => abortController.abort(), 1_000)

      const model = await getSlowAndCapableModel()
      const resultsGen = LSTool.call(
        { path: '.' },
        { abortController, options: { /* ... */ } }
      )

      const result = await lastX(resultsGen)
      lines = result.data
    } catch (error) {
      logError(error)
      return ''
    }

    return `项目文件结构快照(会话开始时生成):
${lines}`
  }
)

缓存策略

// 上下文构建中的记忆化
export const getContext = memoize(
  async (): Promise<{ [k: string]: string }> => {
    // 所有这些操作都被缓存
    const [gitStatus, directoryStructure, readme] = await Promise.all([
      getGitStatus(),           // memoized
      getDirectoryStructure(),  // memoized
      getReadme(),              // memoized
    ])

    return {
      gitStatus,
      directoryStructure,
      readme,
    }
  }
)

性能提升

没有记忆化:

  • 每次查询都扫描目录树:~500ms
  • 每次都运行 git status:~200ms
  • 每次都读取 README:~50ms
  • 总计:~750ms/查询

有记忆化:

  • 第一次查询:~750ms
  • 后续查询:~0ms(从缓存读取)
  • 会话节省:假设 20 次查询 = 14,000ms

二元反馈系统(内部功能)

对于内部用户,Claude Code 实现了精巧的二元反馈系统。

A/B 测试机制

// src/query.ts:71-100 (简化版)
async function queryWithBinaryFeedback(
  toolUseContext: ToolUseContext,
  getAssistantResponse: () => Promise
): Promise {
  // 仅内部用户启用
  if (process.env.USER_TYPE !== 'ant' || !(await shouldUseBinaryFeedback())) {
    const assistantMessage = await getAssistantResponse()
    return { message: assistantMessage, shouldSkipPermissionCheck: false }
  }

  // 并行调用两次 Claude API
  const [m1, m2] = await Promise.all([
    getAssistantResponse(),
    getAssistantResponse()
  ])

  if (toolUseContext.abortController.signal.aborted) {
    return { message: null, shouldSkipPermissionCheck: false }
  }

  // 如果 m2 是错误消息,返回 m1
  if (m2.isApiErrorMessage) {
    return { message: m1, shouldSkipPermissionCheck: false }
  }

  // 比较质量(简化版)
  const valid = messagePairValidForBinaryFeedback(m1, m2)

  if (valid) {
    // 使用质量更好的响应
    const better = chooseBetterResponse(m1, m2)
    return { message: better, shouldSkipPermissionCheck: true }
  }

  // 无法比较,返回 m1
  return { message: m1, shouldSkipPermissionCheck: false }
}

质量评估标准

function messagePairValidForBinaryFeedback(
  m1: AssistantMessage,
  m2: AssistantMessage
): boolean {
  // 两者都包含工具使用
  const m1HasTools = hasToolUse(m1.message)
  const m2HasTools = hasToolUse(m2.message)

  if (m1HasTools && m2HasTools) {
    // 工具使用不同才值得比较
    return !areToolUsesIdentical(m1.message, m2.message)
  }

  // 两者都是纯文本
  if (!m1HasTools && !m2HasTools) {
    return true
  }

  return false
}

应用价值

  1. 质量提升:自动选择更好的响应
  2. 数据收集:了解模型行为的差异性
  3. 评估驱动开发:基于反馈优化系统

状态管理策略

Claude Code 使用多层状态管理:

// 全局状态(单例)
let currentWorkingDirectory = process.cwd()

// 会话状态(REPL)
const conversationHistory: Message[] = []

// 项目状态(配置)
const projectConfig = loadProjectConfig('.claude/config.json')

// 临时状态(消息)
const readFileTimestamps: Record = {}

状态持久化

// 保存对话历史
function saveConversation(messages: Message[]) {
  const logPath = getLogPath()
  const logData = {
    timestamp: Date.now(),
    messages: messages.map(m => serializeMessage(m))
  }

  fs.writeFileSync(logPath, JSON.stringify(logData, null, 2))
}

// 恢复对话
function resumeConversation(logPath: string) {
  const logData = JSON.parse(fs.readFileSync(logPath, 'utf-8'))
  return logData.messages.map(deserializeMessage)
}

设计模式总结

Claude Code 的架构体现了多个核心设计模式:

1. 工具模式 (Tool Pattern)

统一的工具接口确保可组合性:

interface Tool {
  name: string
  description: string
  parameters?: ToolParameter[]
  call(params, context): AsyncGenerator
}

2. 策略模式 (Strategy Pattern)

不同的工具实现不同的执行策略:

const strategies = {
  bash: new BashStrategy(),
  edit: new EditStrategy(),
  read: new ReadStrategy()
}

const tool = strategies[toolName]
const result = await tool.execute(params)

3. 观察者模式 (Observer Pattern)

AsyncGenerator 本质上是观察者模式:

// 生产者
async function* producer() {
  yield { type: 'update', data: 'progress' }
}

// 消费者
for await (const event of producer()) {
  handleEvent(event)
}

4. 命令模式 (Command Pattern)

CLI 命令封装为独立模块:

// src/commands/cost.ts
export const costCommand = new Command('cost')
  .description('显示成本信息')
  .action(() => {
    displayCostBreakdown()
  })

性能优化技巧

1. 流式响应

// 不等待完整响应
for await (const chunk of streamResponse(abortSignal)) {
  appendToUI(chunk)
}

2. 懒加载

// 按需加载工具
const tools = new Map()

async function getTool(name: string) {
  if (!tools.has(name)) {
    const module = await import(`./tools/${name}.js`)
    tools.set(name, module.default)
  }
  return tools.get(name)
}

3. 批处理

// 批量文件操作
const batches = chunkArray(files, 10)
for (const batch of batches) {
  await Promise.all(batch.map(processFile))
}

最佳实践建议

从 Claude Code 的架构中,我们可以总结出以下最佳实践:

  1. 使用 AsyncGenerator:任何需要进度更新的操作
  2. 传递 AbortController:所有可取消的异步操作
  3. 记忆化昂贵操作:特别是 I/O 密集型操作
  4. 并行独立操作:使用 Promise.all,但限制并发数
  5. 统一接口设计:让组件可组合和可测试
  6. 状态与逻辑分离:状态管理独立于业务逻辑
  7. 错误处理策略:明确如何处理部分失败

架构权衡

每个设计决策都有权衡:

AsyncGenerator vs Promise

AsyncGenerator 优势

  • 流式处理
  • 可取消
  • 背压控制

AsyncGenerator 劣势

  • 更复杂的 API
  • 不总是需要(简单操作用 Promise 即可)

并行 vs 串行

并行优势

  • 更快完成
  • 更好的资源利用

并行劣势

  • 更难调试
  • 错误处理复杂
  • 可能耗尽资源

记忆化 vs 重新计算

记忆化优势

  • 性能提升
  • 减少重复工作

记忆化劣势

  • 内存占用
  • 可能返回过时数据

下一步

在下一篇文章中,我们将深入探讨:

  • BashTool 实现:命令执行的细节
  • FileEditTool 机制:安全的文件修改
  • AgentTool 设计:子 Agent 生成系统
  • MCP 集成:扩展工具生态
  • Git 工作流自动化:提交、PR、冲突解决

系列目录

  1. 项目概览与核心设计
  2. 架构模式与设计哲学(本文)
  3. 关键功能与实现细节(即将推出)
  4. 代码演练与最佳实践(即将推出)
  5. 经验总结与实践应用(即将推出)
Enjoyed this article? Share it with others!