AgentHarness 课程

第十章:实操练习

5.0K字·13分钟·
5个实操练习

学习时间: 8 小时 | 难度: ⭐⭐⭐⭐ | 前置: 第一至九章


学习目标

完成本章后,学员将能够:

  • 通过阅读源码理解 Tool 接口设计
  • 绘制查询循环的完整流程图
  • 分析 StreamingToolExecutor 的并发模型
  • 设计改进方案并实现简化版权限管道

练习 1: 阅读 Tool.ts,理解接口设计

时长: 1.5 小时 | 难度: ⭐⭐⭐

任务

阅读 src/Tool.ts (792行),完成以下任务:

  1. 列出所有接口字段:整理 Tool 类型的完整字段列表,包括类型、默认值、用途
  2. 识别元数据字段:找出所有「行为元数据」字段(isConcurrencySafe、isReadOnly 等),分析它们如何被编排层使用
  3. 找出 buildTool 函数:分析它的默认值设置逻辑,理解 Fail-Closed Defaults 模式

交付物

## Tool 接口字段分析

| 字段 | 类型 | 默认值 | 用途 | 被谁使用 |
|------|------|--------|------|---------|
| name | string | 必填 | ... | ... |
| ... | ... | ... | ... | ... |

## 行为元数据分析

### isConcurrencySafe
- 默认值: false
- 被 StreamingToolExecutor 使用
- 决定工具是否可以并行执行

### isReadOnly
- 默认值: false
- 被权限系统使用
- 决定是否需要权限确认

...

## buildTool 默认值分析

提示

  • 注意区分「可选字段」和「有默认值的字段」
  • 关注 call() vs execute() 的命名(文档中常见的错误)
  • 找出 validateInputcheckPermissions 的区别

练习 2: 阅读 query.ts,画出查询循环流程图

时长: 2 小时 | 难度: ⭐⭐⭐⭐

任务

阅读 src/query.ts (1732行),完成以下任务:

  1. 识别状态转换:找出所有 Continue 类型及其触发条件
  2. 绘制流程图:用 Mermaid 或 ASCII art 绘制完整的查询循环流程图
  3. 分析错误处理:找出 Withhold-then-Recover 的具体实现

交付物

graph TD
    A[query 入口] --> B[构建 System Prompt]
    B --> C[准备消息]
    C --> D[流式 API 调用]
    D --> E{stop_reason?}
    E -->|tool_use| F[StreamingToolExecutor]
    F --> G[注入结果]
    G --> C
    E -->|end_turn| H[返回结果]
    E -->|max_tokens| I{恢复次数 < 3?}
    I -->|是| J[恢复尝试]
    J --> C
    I -->|否| K[报告错误]
    E -->|prompt_too_long| L[context-collapse]
    L --> C
    E -->|error| M[重试/报告]
    E -->|interrupt| N[用户中断]

提示

  • 关注 switch 语句中的 stop_reason 分支
  • 找出恢复尝试的计数逻辑
  • 注意 prompt_too_long 的多级恢复策略

练习 3: 阅读 StreamingToolExecutor,理解并发模型

时长: 1.5 小时 | 难度: ⭐⭐⭐⭐

任务

阅读 src/services/tools/StreamingToolExecutor.ts (530行),完成以下任务:

  1. 分析并发模型:理解读并行写串行的实现机制
  2. 找出分区逻辑isConcurrencySafe 如何决定工具进入并行队列还是串行队列
  3. 分析错误级联:AbortController 如何取消所有兄弟工具

交付物

## 并发模型分析

### 队列结构
- parallelQueue: Tool[] — 并行执行队列
- serialQueue: Tool[] — 串行执行队列

### 分区逻辑
当 LLM 流式输出 tool_use block 时:
1. 调用 tool.isConcurrencySafe(input)
2. 如果 true → 加入 parallelQueue,立即开始执行
3. 如果 false → 等待 parallelQueue 清空,独占执行

### 错误级联
当任何工具执行失败时:
1. 调用 abortController.abort()
2. 所有正在并行执行的工具被取消
3. 串行队列中的工具不会开始执行

### 结果收集
- 结果按原始顺序缓冲输出
- 即使并行执行的工具,结果也按 LLM 输出顺序返回

提示

  • 关注 addTool() 方法的分支逻辑
  • 找出 waitForAll() 的实现
  • 注意结果缓冲区如何维护顺序

练习 4: 对比 OMC 的工具系统,设计改进方案

时长: 2 小时 | 难度: ⭐⭐⭐⭐

任务

基于第三章学到的知识,为 OmcHarness 的工具系统设计改进方案:

  1. 现状分析:列出 OMC 当前工具接口的字段
  2. 差距识别:对比 Claude Code 的 Tool 接口,找出缺失的字段
  3. 改进设计:设计增强版的工具接口
  4. 迁移方案:如何在不破坏现有代码的情况下升级

交付物

## OMC 工具接口增强方案

### 现状
interface CurrentTool {
  name: string
  description: string
  call(input: any): Promise<any>
}

### 增强版
interface EnhancedTool {
  name: string
  description: string
  inputSchema: ZodSchema        // 新增:输入验证
  
  // 行为元数据(新增)
  isConcurrencySafe(input: any): boolean
  isReadOnly(input: any): boolean
  isDestructive(input: any): boolean
  
  // 生命周期(新增)
  validateInput?(input: any): Promise<ValidationResult>
  checkPermissions(input: any): Promise<PermissionResult>
  
  // 执行
  call(input: any): Promise<ToolResult>
}

### 迁移策略
1. 新接口向后兼容(所有新字段有默认值)
2. 现有工具无需修改即可继续工作
3. 逐步添加元数据字段
4. 新工具必须实现完整接口

提示

  • 默认值要偏向安全侧(Fail-Closed)
  • 考虑向后兼容性
  • 优先实现收益最大的字段(isConcurrencySafe, isReadOnly)

练习 5: 设计一个简化版的权限管道

时长: 1 小时 | 难度: ⭐⭐⭐

任务

基于第四章学到的知识,设计一个简化版的 3 层权限管道:

  1. Layer 1: 规则匹配(deny/allow 规则)
  2. Layer 2: 模式决策(bypass/normal 模式)
  3. Layer 3: 用户交互(确认/拒绝)

交付物

// simplified-permission-pipeline.ts

interface PermissionRule {
  tool: string
  pattern?: string
  action: 'allow' | 'deny' | 'ask'
}

interface PermissionContext {
  mode: 'normal' | 'bypass'
  rules: PermissionRule[]
}

type PermissionResult = 'allow' | 'deny' | 'ask'

async function checkPermission(
  toolName: string,
  toolInput: any,
  context: PermissionContext
): Promise<PermissionResult> {
  // Layer 1: 规则匹配
  const ruleResult = matchRules(toolName, toolInput, context.rules)
  if (ruleResult === 'deny') return 'deny'
  
  // Layer 2: 模式决策
  if (context.mode === 'bypass') return 'allow'
  
  // Layer 3: 返回规则结果
  return ruleResult || 'ask'
}

function matchRules(
  toolName: string,
  toolInput: any,
  rules: PermissionRule[]
): PermissionResult | null {
  for (const rule of rules) {
    if (rule.tool === toolName || rule.tool === '*') {
      if (!rule.pattern || matchPattern(toolInput, rule.pattern)) {
        return rule.action
      }
    }
  }
  return null  // 无匹配规则
}

提示

  • 从简单开始,逐步增加复杂度
  • 规则匹配要考虑优先级(deny > allow > ask)
  • 测试用例要覆盖边界情况

提交要求

每个练习需要提交:

  1. 分析文档 (Markdown 格式)
  2. 源码截图 (关键代码片段)
  3. 流程图 (Mermaid 或 ASCII art)
  4. 思考题答案 (每个练习 2-3 个思考题)

评分标准:

  • 完整性 (40%): 是否覆盖所有要求
  • 准确性 (30%): 分析是否正确
  • 深度 (20%): 是否有独到见解
  • 可读性 (10%): 文档是否清晰

09-OmcHarness启示 | 附录