第3章:Agent 架构核心 —— Agent = Model + Harness 深度解析

本章导引

第1章我们建立了"Agent = Model + Harness"的认知框架,第2章我们深入了 Model 层面的 LLM 工程基础。本章是全书最核心的章节之一——我们将从架构层面深度解析 Agent 的内部构造,把"Agent = Model + Harness"公式从抽象概念落地为可执行的代码架构。

本章的目标是让你能够:

  1. 理解 Agent 主循环的每个环节——Perceive → Reason → Act → Observe
  2. 掌握 Harness 六大组件的职责边界、交互协议和工程约束
  3. 能够动手构建一个模块化、可扩展的 Agent 框架骨架
  4. 理解 Harness 中间件架构和配置管理的工程化设计

读完本章,你将拥有一个完整的 Agent 框架骨架代码,后续章节(第4-8章)将逐一深入每个 Harness 组件的完整实现。


3.1 Agent 的解剖学

3.1.1 Model 层:推理引擎的角色与边界

Model 层是 Agent 的"大脑",但我们需要精确地定义它的能力和边界。

Model 层的能力边界

Model 负责:                    Model 不负责:
┌─────────────────────┐        ┌─────────────────────┐
│ ✓ 理解自然语言指令    │        │ ✗ 管理对话历史        │
│ ✓ 推理和逻辑推演      │        │ ✗ 选择上下文内容       │
│ ✓ 生成文本和代码      │        │ ✗ 执行工具调用         │
│ ✓ 规划和分解任务      │        │ ✗ 验证操作安全性       │
│ ✓ 判断何时调用工具    │        │ ✗ 存储长期记忆         │
│ ✓ 评估自身输出质量    │        │ ✗ 监控运行状态         │
└─────────────────────┘        └─────────────────────┘
     大脑的职责                      身体(Harness)的职责

这个边界的核心原理是:Model 只负责"想",一切"做"的事情都交给 Harness

Model 的抽象接口

无论底层是 Claude、GPT 还是本地模型,从 Agent 架构的角度看,Model 层应该暴露统一的接口:

// TypeScript: Model 层的统一抽象接口

// 输入:Harness 构建的上下文
interface ModelInput {
  /** 系统提示词 */
  systemPrompt: string;
  /** 对话消息列表 */
  messages: Message[];
  /** 可用工具定义(JSON Schema) */
  tools?: ToolDefinition[];
  /** 模型推理参数 */
  params?: ModelParams;
}

// 输出:Model 的决策
interface ModelOutput {
  /** 决策类型 */
  type: 'text_response' | 'tool_call' | 'mixed';
  /** 文本回复 */
  textContent?: string;
  /** 工具调用列表 */
  toolCalls?: ToolCall[];
  /** Token 用量统计 */
  usage: TokenUsage;
  /** 停止原因 */
  stopReason: 'end_turn' | 'max_tokens' | 'tool_use' | 'stop_sequence';
  /** 模型标识(用于多模型路由时追踪) */
  modelId: string;
}

// Model 提供者接口
interface ModelProvider {
  /** 模型唯一标识 */
  readonly modelId: string;
  /** 最大上下文窗口 */
  readonly maxContextTokens: number;
  /** 最大输出 tokens */
  readonly maxOutputTokens: number;
  /** 是否支持工具调用 */
  readonly supportsToolUse: boolean;
  /** 是否支持流式输出 */
  readonly supportsStreaming: boolean;
  
  /** 核心推理方法 */
  reason(input: ModelInput): Promise<ModelOutput>;
  /** 流式推理 */
  reasonStream(input: ModelInput): AsyncGenerator<StreamEvent>;
}

// 具体实现:Anthropic 适配器
class AnthropicModelProvider implements ModelProvider {
  readonly modelId: string;
  readonly maxContextTokens = 200000;
  readonly maxOutputTokens = 4096;
  readonly supportsToolUse = true;
  readonly supportsStreaming = true;
  
  constructor(
    private client: Anthropic,
    modelId: string = 'claude-sonnet-4-6'
  ) {
    this.modelId = modelId;
  }
  
  async reason(input: ModelInput): Promise<ModelOutput> {
    const response = await this.client.messages.create({
      model: this.modelId,
      max_tokens: input.params?.maxTokens ?? this.maxOutputTokens,
      system: input.systemPrompt,
      messages: this.convertMessages(input.messages),
      tools: input.tools?.map(this.convertTool),
      temperature: input.params?.temperature ?? 0.7,
    });
    
    return this.parseResponse(response);
  }
  
  async *reasonStream(input: ModelInput): AsyncGenerator<StreamEvent> {
    const stream = this.client.messages.stream({
      model: this.modelId,
      max_tokens: input.params?.maxTokens ?? this.maxOutputTokens,
      system: input.systemPrompt,
      messages: this.convertMessages(input.messages),
      tools: input.tools?.map(this.convertTool),
    });
    
    for await (const event of stream) {
      yield this.convertStreamEvent(event);
    }
  }
  
  private convertMessages(messages: Message[]): AnthropicMessageParam[] {
    return messages.map(m => ({
      role: m.role as 'user' | 'assistant',
      content: m.content,
    }));
  }
  
  private parseResponse(response: AnthropicMessage): ModelOutput {
    const textBlocks = response.content.filter(b => b.type === 'text');
    const toolBlocks = response.content.filter(b => b.type === 'tool_use');
    
    const textContent = textBlocks.map(b => b.text).join('\n');
    const toolCalls = toolBlocks.map(b => ({
      id: b.id,
      name: b.name,
      arguments: b.input as Record<string, unknown>,
    }));
    
    return {
      type: toolCalls.length > 0 ? 
        (textContent ? 'mixed' : 'tool_call') : 
        'text_response',
      textContent: textContent || undefined,
      toolCalls: toolCalls.length > 0 ? toolCalls : undefined,
      usage: {
        inputTokens: response.usage.input_tokens,
        outputTokens: response.usage.output_tokens,
      },
      stopReason: response.stop_reason as ModelOutput['stopReason'],
      modelId: this.modelId,
    };
  }
}
# Python: Model 层的统一抽象接口

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import AsyncGenerator, Optional
from enum import Enum

class StopReason(Enum):
    END_TURN = "end_turn"
    MAX_TOKENS = "max_tokens"
    TOOL_USE = "tool_use"
    STOP_SEQUENCE = "stop_sequence"

@dataclass
class ToolDefinition:
    name: str
    description: str
    parameters: dict  # JSON Schema

@dataclass
class ToolCall:
    id: str
    name: str
    arguments: dict

@dataclass
class TokenUsage:
    input_tokens: int
    output_tokens: int

@dataclass
class ModelInput:
    system_prompt: str
    messages: list
    tools: Optional[list[ToolDefinition]] = None
    temperature: float = 0.7
    max_tokens: int = 4096

@dataclass
class ModelOutput:
    type: str  # 'text_response' | 'tool_call' | 'mixed'
    text_content: Optional[str] = None
    tool_calls: Optional[list[ToolCall]] = None
    usage: Optional[TokenUsage] = None
    stop_reason: Optional[str] = None
    model_id: str = ""

class ModelProvider(ABC):
    """模型提供者统一接口"""
    
    @property
    @abstractmethod
    def model_id(self) -> str: ...
    
    @property
    @abstractmethod
    def max_context_tokens(self) -> int: ...
    
    @abstractmethod
    async def reason(self, input: ModelInput) -> ModelOutput: ...
    
    @abstractmethod
    async def reason_stream(self, input: ModelInput) -> AsyncGenerator: ...

class AnthropicModelProvider(ModelProvider):
    """Anthropic Claude 适配器"""
    
    def __init__(self, client, model_id: str = "claude-sonnet-4-6"):
        self.client = client
        self._model_id = model_id
    
    @property
    def model_id(self) -> str:
        return self._model_id
    
    @property
    def max_context_tokens(self) -> int:
        return 200000
    
    async def reason(self, input: ModelInput) -> ModelOutput:
        response = await self.client.messages.create(
            model=self._model_id,
            max_tokens=input.max_tokens,
            system=input.system_prompt,
            messages=self._convert_messages(input.messages),
            tools=self._convert_tools(input.tools) if input.tools else None,
            temperature=input.temperature,
        )
        return self._parse_response(response)
    
    def _parse_response(self, response) -> ModelOutput:
        text_blocks = [b for b in response.content if b.type == "text"]
        tool_blocks = [b for b in response.content if b.type == "tool_use"]
        
        text_content = "\n".join(b.text for b in text_blocks) if text_blocks else None
        tool_calls = [
            ToolCall(id=b.id, name=b.name, arguments=b.input)
            for b in tool_blocks
        ] if tool_blocks else None
        
        return ModelOutput(
            type="tool_call" if tool_calls and not text_content else
                 "mixed" if tool_calls and text_content else "text_response",
            text_content=text_content,
            tool_calls=tool_calls,
            usage=TokenUsage(
                input_tokens=response.usage.input_tokens,
                output_tokens=response.usage.output_tokens,
            ),
            stop_reason=response.stop_reason,
            model_id=self._model_id,
        )

设计要点

  1. 统一接口:无论底层是什么模型,Agent 框架只看到 ModelProvider.reason() 这一个入口。这使得切换模型、多模型路由、A/B 测试变得简单。

  2. 输入标准化:Harness 构建好的上下文以统一的 ModelInput 格式传入。Model 层不需要关心上下文的来源——它只需要理解上下文的内容。

  3. 输出结构化:Model 的每个决策都以结构化的 ModelOutput 返回,明确定义了文本回复、工具调用或两者的混合。这使得 Harness 可以精确地根据决策类型执行后续操作。

3.1.2 Harness 层:模型之外的一切工程能力

如果说 Model 层是"大脑",Harness 层就是"身体"。大脑思考,身体行动。

┌──────────────────────────────────────────────────────────┐
│                       Harness 层                          │
│                                                           │
│  ┌─────────────┐  ┌──────────────┐  ┌──────────────────┐ │
│  │ Context      │  │ Tool         │  │ Reasoning        │ │
│  │ Manager      │  │ Executor     │  │ Controller       │ │
│  │              │  │              │  │                  │ │
│  │ • 窗口管理   │  │ • 工具注册   │  │ • 策略选择       │ │
│  │ • System     │  │ • 参数校验   │  │ • 循环控制       │ │
│  │   Prompt     │  │ • 沙箱执行   │  │ • 超时熔断       │ │
│  │ • RAG 注入   │  │ • 结果格式化 │  │ • 质量检查       │ │
│  │ • 记忆融合   │  │ • MCP 协议   │  │ • ReAct/CoT      │ │
│  └──────┬───────┘  └──────┬───────┘  └────────┬─────────┘ │
│         │                 │                   │           │
│  ┌──────┴───────┐  ┌──────┴───────┐  ┌────────┴─────────┐ │
│  │ Safety       │  │ Memory       │  │ Observability    │ │
│  │ Guard        │  │ System       │  │                  │ │
│  │              │  │              │  │                  │ │
│  │ • 输入过滤   │  │ • 工作记忆   │  │ • 全链路追踪     │ │
│  │ • 输出审核   │  │ • 短期记忆   │  │ • 指标收集       │ │
│  │ • 权限控制   │  │ • 长期记忆   │  │ • 会话回放       │ │
│  │ • HITL 确认  │  │ • 向量存储   │  │ • 成本监控       │ │
│  └──────────────┘  └──────────────┘  └──────────────────┘ │
│                                                           │
└──────────────────────────────────────────────────────────┘

Harness 层的设计原则

  1. 关注点分离:每个组件有明确的职责边界。Context Manager 不关心工具执行,Safety Guard 不关心推理策略。

  2. 可插拔性:每个组件定义清晰的接口,具体实现可以替换。你可以用自定义的 Memory System 替换默认实现,而不需要改动其他组件。

  3. 中间件架构:Harness 组件以中间件链的方式工作,请求依次通过各组件处理。每个组件既能为下游组件提供输入,也能对上游的输出做处理。

  4. 配置驱动:Harness 的行为通过配置控制,而非硬编码。这使得同一个 Agent 框架可以适配不同场景——只需更换配置。

3.1.3 Agent 主循环:Perceive → Reason → Act → Observe

Agent 主循环是所有 Agent 系统最核心的设计模式。它定义了 Agent 如何从"接收任务"到"完成任务"的完整流程。

                    ┌──────────────────────────┐
                    │    用户输入任务            │
                    └────────────┬─────────────┘
                                 │
                                 ▼
              ┌──────────────────────────────────────┐
              │         1. PERCEIVE (感知)             │
              │  Context Manager 组装上下文             │
              │  - 加载 System Prompt                  │
              │  - 加载对话历史                         │
              │  - 加载记忆                             │
              │  - 注入工具执行结果                     │
              │  - 注入 RAG 检索结果                    │
              └──────────────┬───────────────────────┘
                             │ 完整的 Context
                             ▼
              ┌──────────────────────────────────────┐
              │         2. SAFETY CHECK (安全检查)     │
              │  Safety Guard 检查输入                  │
              │  - 输入过滤(恶意内容检测)              │
              │  - Prompt Injection 检测               │
              │  - 权限校验                             │
              └──────────────┬───────────────────────┘
                             │ 安全的 Context
                             ▼
              ┌──────────────────────────────────────┐
              │         3. REASON (推理)               │
              │  Model 基于上下文进行推理               │
              │  - 理解用户意图                         │
              │  - 决定:回复 or 调用工具 or 追问       │
              │  - 生成文本内容或工具调用指令            │
              └──────────────┬───────────────────────┘
                             │ Decision
                             ▼
              ┌──────────────────────────────────────┐
              │      4. OBSERVE & CONTROL (观察控制)   │
              │  Reasoning Controller 分析决策          │
              │  - 是最终回复? → 跳转到 6              │
              │  - 是工具调用? → 进入 5                │
              │  - 需要追问? → 返回 1                  │
              │  - 超过最大步数? → 强制结束             │
              │  - 检测到循环? → 中断并告警             │
              └──────────────┬───────────────────────┘
                             │
                ┌────────────┴────────────┐
                │                         │
                ▼                         ▼
    ┌───────────────────┐     ┌───────────────────────┐
    │ 5a. ACT (执行)     │     │ 5b. RESPOND (回复)     │
    │ Tool Executor 执行 │     │ Safety Guard 检查输出   │
    │ - 解析工具调用     │     │ - 内容审核              │
    │ - 参数校验         │     │ - 格式化校验            │
    │ - 沙箱执行         │     │ - 脱敏处理              │
    │ - 结果格式化       │     │                         │
    │ - 注入回 Context   │     │                         │
    └────────┬──────────┘     └───────────┬─────────────┘
             │                            │
             └──────────┬─────────────────┘
                        ▼
              ┌──────────────────────────────────────┐
              │       6. MEMORIZE (记忆存储)           │
              │  Memory System 存储本轮交互              │
              │  - 保存到短期记忆                       │
              │  - 触发长期记忆写入(重要信息)          │
              └──────────────┬───────────────────────┘
                             │
                             ▼
              ┌──────────────────────────────────────┐
              │       7. OBSERVE (全链路追踪)          │
              │  Observability 记录完整执行轨迹         │
              │  - 推理链追踪                          │
              │  - 工具调用追踪                         │
              │  - Token 用量统计                       │
              │  - 耗时统计                            │
              └──────────────┬───────────────────────┘
                             │
                             ▼
                    ┌──────────────────────────┐
                    │     返回最终结果给用户      │
                    └──────────────────────────┘

这个循环的核心在于信息闭环:每一步工具调用的结果注入回 Context → Model 基于更新的 Context 继续推理 → 产生新的 Decision → 重复循环,直到 Model 认为任务完成并返回最终回复。

3.1.4 实践案例:构建最小的可运行 Agent(50行代码)

在展开详细的架构之前,让我们先写一个能真正运行的 Agent。这个最小实现只有 50 行核心代码,但它已经包含了完整的 Perceive → Reason → Act → Observe 循环。

// TypeScript: 最小可运行 Agent(50行核心代码)
import { Anthropic } from '@anthropic-ai/sdk';

// ===== 1. Model 层 =====
const client = new Anthropic();

async function reason(context: { system: string; messages: any[]; tools: any[] }) {
  return client.messages.create({
    model: 'claude-sonnet-4-6',
    max_tokens: 4096,
    system: context.system,
    messages: context.messages,
    tools: context.tools,
  });
}

// ===== 2. Tool Executor =====
const tools: Record<string, (args: any) => Promise<string>> = {
  read_file: async (args) => `[文件内容] ${args.path} 的内容是: ...`,
  run_command: async (args) => `[命令输出] $ ${args.command}\n> 执行成功`,
  search_code: async (args) => `[搜索结果] 找到 3 处引用 "${args.pattern}"`,
};

async function executeToolCalls(response: any) {
  const results = [];
  for (const block of response.content) {
    if (block.type === 'tool_use') {
      const tool = tools[block.name];
      if (tool) {
        results.push({
          type: 'tool_result',
          tool_use_id: block.id,
          content: await tool(block.input),
        });
      }
    }
  }
  return results;
}

// ===== 3. Agent 主循环 =====
async function agent(task: string) {
  const messages: any[] = [{ role: 'user', content: task }];
  const system = '你是一个编程助手。可以使用文件读取、命令执行、代码搜索等工具。';
  
  for (let step = 0; step < 15; step++) {
    const response = await reason({ system, messages, tools: toolDefinitions });
    
    // 将 Model 的回复加入对话(包含所有 content blocks)
    messages.push({ role: 'assistant', content: response.content });
    
    // 检查是否需要调用工具
    const toolBlocks = response.content.filter((b: any) => b.type === 'tool_use');
    if (toolBlocks.length === 0) {
      // 无工具调用 = 最终回复
      const textBlock = response.content.find((b: any) => b.type === 'text');
      return textBlock?.text ?? '无法生成回复';
    }
    
    // 执行工具调用并将结果注入对话
    const results = await executeToolCalls(response);
    messages.push({ role: 'user', content: results });
  }
  
  return '达到最大步数限制,任务未完成。';
}
# Python: 最小可运行 Agent(50行核心代码)
from anthropic import AsyncAnthropic

# ===== 1. Model 层 =====
client = AsyncAnthropic()

async def reason(system: str, messages: list, tools: list):
    return await client.messages.create(
        model="claude-sonnet-4-6",
        max_tokens=4096,
        system=system,
        messages=messages,
        tools=tools,
    )

# ===== 2. Tool Executor =====
tools = {
    "read_file": lambda args: f"[文件内容] {args['path']} 的内容是: ...",
    "run_command": lambda args: f"[命令输出] $ {args['command']}\n> 执行成功",
    "search_code": lambda args: f'[搜索结果] 找到 3 处引用 "{args["pattern"]}"',
}

async def execute_tool_calls(response):
    results = []
    for block in response.content:
        if block.type == "tool_use":
            tool_fn = tools.get(block.name)
            if tool_fn:
                results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": await tool_fn(block.input) if asyncio.iscoroutinefunction(tool_fn) else tool_fn(block.input),
                })
    return results

# ===== 3. Agent 主循环 =====
async def agent(task: str) -> str:
    messages = [{"role": "user", "content": task}]
    system = "你是一个编程助手。可以使用文件读取、命令执行、代码搜索等工具。"
    
    for step in range(15):
        response = await reason(system, messages, tool_definitions)
        messages.append({"role": "assistant", "content": response.content})
        
        tool_blocks = [b for b in response.content if b.type == "tool_use"]
        if not tool_blocks:
            text_blocks = [b for b in response.content if b.type == "text"]
            return text_blocks[0].text if text_blocks else "无法生成回复"
        
        results = await execute_tool_calls(response)
        messages.append({"role": "user", "content": results})
    
    return "达到最大步数限制,任务未完成。"

这个最小 Agent 已经展示了 Agent 架构的核心精髓:

  1. 推理-行动循环:Model 推理 → 判断要不要工具 → 执行工具 → 结果回去 → 再推理
  2. 对话即状态:messages 数组就是 Agent 的状态。每一轮推理都在此基础上进行。
  3. 工具抽象:工具以 name → function 映射注册,统一调用接口。

后续我们将这个骨架逐步扩展为生产级框架。


3.2 Harness 六大组件详解

3.2.1 上下文管理器(Context Manager)

Context Manager 回答一个问题:Model 应该"看到"什么?

职责边界
Context Manager 的输入 ──────────────────► Context Manager 的输出
                                          
• System Prompt 模板                      • 组装好的完整 Context
• 对话历史(原始)                         • Token 计数和预算分配
• 用户画像和偏好           ──加工──►       • 被修剪/压缩后的历史
• 检索到的知识                             • 注入的检索结果
• 工具执行结果                             • 清晰的消息角色标记
• 当前环境信息(时间、位置等)               • 窗口溢出告警
核心接口设计
// TypeScript: Context Manager 接口
interface ContextManagerConfig {
  /** 最大上下文窗口(tokens) */
  maxWindowTokens: number;
  /** System Prompt 预留比例(默认 10%) */
  systemPromptReserveRatio: number;
  /** 对话历史预留比例(默认 60%) */
  historyReserveRatio: number;
  /** 压缩阈值(达到多少时触发压缩) */
  compactionThreshold: number;
}

interface AssembledContext {
  /** 最终发送给 Model 的完整上下文 */
  systemPrompt: string;
  messages: Message[];
  
  /** Token 使用情况 */
  tokenBudget: {
    total: number;
    used: number;
    systemPrompt: number;
    history: number;
    injectedContent: number;
    remaining: number;
  };
  
  /** 上下文元数据 */
  metadata: {
    messageCount: number;
    truncatedCount: number;
    compressionApplied: boolean;
  };
}

interface ContextManager {
  /** 构建完整上下文 */
  build(input: ContextInput): Promise<AssembledContext>;
  /** 增量追加内容 */
  append(current: AssembledContext, addition: ContextAddition): Promise<AssembledContext>;
  /** 压缩上下文 */
  compact(context: AssembledContext): Promise<AssembledContext>;
  /** 估计 Token 数 */
  estimateTokens(text: string): number;
}
实际工作流程

Context Manager 是 Agent 主循环中第一个被调用的组件,它负责将零散的信息源组装为 Model 可以直接消费的上下文:

Step 1: 加载 System Prompt
  ├── 从配置/模板加载基础 System Prompt
  ├── 注入动态变量(用户名、当前时间、环境信息)
  ├── 注入可用工具列表
  └── 计算 System Prompt Token 消耗

Step 2: 处理对话历史
  ├── 加载最近的对话消息
  ├── 检查 Token 预算 → 超出则触发压缩
  ├── 压缩策略:旧消息摘要化 or 滑动窗口截断
  └── 标记被截断的消息计数

Step 3: 注入动态内容
  ├── 加载用户画像(从 Memory System 获取)
  ├── 注入 RAG 检索结果(如果有检索请求)
  ├── 注入工具执行结果(如果前一轮有工具调用)
  └── 检查总 Token 预算 → 按优先级裁剪

Step 4: 返回 AssembledContext
  ├── 完整的 systemPrompt + messages
  ├── Token 预算详情
  └── 压缩/截断的元数据
简化实现
// TypeScript: Context Manager 简化实现
class SimpleContextManager implements ContextManager {
  constructor(private config: ContextManagerConfig) {}
  
  async build(input: ContextInput): Promise<AssembledContext> {
    let tokenBudget = this.config.maxWindowTokens;
    
    // 1. 构建 System Prompt
    const systemPrompt = await this.buildSystemPrompt(input);
    const systemTokens = this.estimateTokens(systemPrompt);
    tokenBudget -= systemTokens;
    
    // 2. 处理对话历史
    const budgetForHistory = Math.floor(tokenBudget * this.config.historyReserveRatio);
    const { messages, truncatedCount } = await this.processHistory(
      input.history, budgetForHistory
    );
    const historyTokens = this.estimateTokens(JSON.stringify(messages));
    tokenBudget -= historyTokens;
    
    // 3. 注入动态内容
    const injectedContent = await this.injectDynamicContent(input);
    const injectedTokens = this.estimateTokens(JSON.stringify(injectedContent));
    tokenBudget -= injectedTokens;
    
    return {
      systemPrompt,
      messages: [...injectedContent, ...messages],
      tokenBudget: {
        total: this.config.maxWindowTokens,
        used: this.config.maxWindowTokens - tokenBudget,
        systemPrompt: systemTokens,
        history: historyTokens,
        injectedContent: injectedTokens,
        remaining: Math.max(0, tokenBudget),
      },
      metadata: {
        messageCount: input.history.length,
        truncatedCount,
        compressionApplied: truncatedCount > 0,
      },
    };
  }
  
  private async buildSystemPrompt(input: ContextInput): Promise<string> {
    // 从模板加载,注入动态变量
    let prompt = input.systemTemplate || DEFAULT_SYSTEM_PROMPT;
    prompt = prompt.replace('{{user_name}}', input.userName ?? '用户');
    prompt = prompt.replace('{{current_time}}', new Date().toISOString());
    prompt = prompt.replace('{{available_tools}}', JSON.stringify(input.tools));
    return prompt;
  }
  
  private async processHistory(
    history: Message[], 
    budgetTokens: number
  ): Promise<{ messages: Message[]; truncatedCount: number }> {
    // 从最新消息开始,向前添加直到超出 Token 预算
    const result: Message[] = [];
    let usedTokens = 0;
    let truncatedCount = 0;
    
    for (let i = history.length - 1; i >= 0; i--) {
      const msgTokens = this.estimateTokens(JSON.stringify(history[i]));
      if (usedTokens + msgTokens > budgetTokens) {
        truncatedCount = i + 1;
        break;
      }
      result.unshift(history[i]);
      usedTokens += msgTokens;
    }
    
    // 如果截断了旧消息,在最前面插入摘要
    if (truncatedCount > 0) {
      result.unshift({
        role: 'user',
        content: `[已省略 ${truncatedCount} 条较早的消息]`,
      });
    }
    
    return { messages: result, truncatedCount };
  }
  
  estimateTokens(text: string): number {
    // 精确估算需要使用 tiktoken,这里用启发式方法
    const chineseChars = (text.match(/[一-鿿]/g) || []).length;
    const otherChars = text.length - chineseChars;
    return Math.ceil(chineseChars / 1.8 + otherChars / 4);
  }
}

3.2.2 工具执行器(Tool Executor)

Tool Executor 是让 Agent 拥有行动能力的关键组件。

职责边界
Tool Executor 的职责:
┌─────────────────────────────────────────┐
│ ✓ 工具注册与管理                          │
│ ✓ 工具发现(给 Model 生成工具定义)         │
│ ✓ 解析 Model 返回的工具调用指令             │
│ ✓ 参数校验(类型、必填、范围)              │
│ ✓ 执行工具(沙箱环境)                     │
│ ✓ 处理执行结果(格式化、截断、错误转换)     │
│ ✓ 执行超时控制                            │
│ ✓ 并发工具调用管理                         │
└─────────────────────────────────────────┘

Tool Executor 不负责:
┌─────────────────────────────────────────┐
│ ✗ 判断该调用什么工具(这是 Model 的事)     │
│ ✗ 安全权限校验(这是 Safety Guard 的事)   │
│ ✗ 工具组合逻辑(这是 Reasoning Controller) │
└─────────────────────────────────────────┘
核心接口
// TypeScript: Tool Executor 接口
interface ToolDefinition {
  name: string;
  description: string;
  parameters: JsonSchema;
  /** 执行超时(毫秒) */
  timeout?: number;
  /** 权限等级 */
  permission: 'read' | 'write' | 'execute' | 'admin';
}

interface ToolResult {
  toolCallId: string;
  toolName: string;
  status: 'success' | 'error' | 'timeout' | 'permission_denied';
  content: string;
  /** 执行耗时(毫秒) */
  duration: number;
  /** 错误信息(如果有) */
  error?: string;
}

interface ToolExecutor {
  /** 注册工具 */
  register(tool: ToolDefinition, handler: ToolHandler): void;
  /** 获取所有工具定义(给 Model 的 tools 参数) */
  getDefinitions(): ToolDefinition[];
  /** 执行单个工具调用 */
  execute(call: ToolCall, context: ExecutionContext): Promise<ToolResult>;
  /** 批量执行工具调用 */
  executeBatch(calls: ToolCall[], context: ExecutionContext): Promise<ToolResult[]>;
}
工具注册与发现
// TypeScript: 工具注册
type ToolHandler = (args: Record<string, unknown>, ctx: ExecutionContext) => Promise<string>;

class SimpleToolExecutor implements ToolExecutor {
  private tools: Map<string, { definition: ToolDefinition; handler: ToolHandler }> = new Map();
  
  register(definition: ToolDefinition, handler: ToolHandler): void {
    if (this.tools.has(definition.name)) {
      throw new Error(`工具 ${definition.name} 已注册`);
    }
    this.tools.set(definition.name, { definition, handler });
  }
  
  getDefinitions(): ToolDefinition[] {
    return Array.from(this.tools.values()).map(t => t.definition);
  }
  
  async execute(call: ToolCall, context: ExecutionContext): Promise<ToolResult> {
    const startTime = Date.now();
    const tool = this.tools.get(call.name);
    
    if (!tool) {
      return {
        toolCallId: call.id,
        toolName: call.name,
        status: 'error',
        content: `未知工具: ${call.name}。可用工具: ${[...this.tools.keys()].join(', ')}`,
        duration: Date.now() - startTime,
        error: 'unknown_tool',
      };
    }
    
    try {
      // 参数校验
      this.validateArgs(call.arguments, tool.definition.parameters);
      
      // 带超时的执行
      const timeout = tool.definition.timeout ?? 30000;
      const result = await Promise.race([
        tool.handler(call.arguments, context),
        new Promise<string>((_, reject) =>
          setTimeout(() => reject(new Error('Tool execution timeout')), timeout)
        ),
      ]);
      
      return {
        toolCallId: call.id,
        toolName: call.name,
        status: 'success',
        content: this.formatResult(result),
        duration: Date.now() - startTime,
      };
    } catch (error: any) {
      return {
        toolCallId: call.id,
        toolName: call.name,
        status: error.message.includes('timeout') ? 'timeout' : 'error',
        content: `工具执行失败: ${error.message}`,
        duration: Date.now() - startTime,
        error: error.message,
      };
    }
  }
  
  private validateArgs(args: Record<string, unknown>, schema: JsonSchema): void {
    // JSON Schema 校验
    // 生产代码中使用 ajv 或 zod 进行严格的 schema 校验
    for (const [key, prop] of Object.entries(schema.properties || {})) {
      if (prop.required && !(key in args)) {
        throw new Error(`缺少必填参数: ${key}`);
      }
    }
  }
  
  private formatResult(result: string): string {
    // 截断过长的结果,防止撑爆上下文
    const MAX_RESULT_LENGTH = 10000;
    if (result.length > MAX_RESULT_LENGTH) {
      return result.slice(0, MAX_RESULT_LENGTH) + 
        `\n... [结果已截断,原始长度: ${result.length}]`;
    }
    return result;
  }
}

3.2.3 推理控制器(Reasoning Controller)

Reasoning Controller 是 Agent 的"元认知"——它控制 Agent 采用什么推理策略、什么时候停止循环、什么时候切换策略。

// TypeScript: Reasoning Controller 接口
interface ReasoningStrategy {
  name: string;
  /** 策略的系统指令(注入到 System Prompt) */
  systemInstruction: string;
}

interface ReasoningDecision {
  /** 是否继续推理循环 */
  shouldContinue: boolean;
  /** 是否需要切换策略 */
  switchStrategy?: ReasoningStrategy;
  /** 当前步骤的元数据 */
  metadata: {
    stepIndex: number;
    maxSteps: number;
    elapsedTime: number;
    tokensUsedSoFar: number;
  };
}

interface ReasoningController {
  /** 分析当前状态,决定下一步 */
  evaluate(state: AgentState): ReasoningDecision;
  /** 注册可用的推理策略 */
  registerStrategy(strategy: ReasoningStrategy): void;
  /** 获取当前策略 */
  getCurrentStrategy(): ReasoningStrategy;
}

class SimpleReasoningController implements ReasoningController {
  private strategies: ReasoningStrategy[] = [];
  private currentIndex = 0;
  
  constructor(private config: { maxSteps: number; maxTokens: number; maxTimeMs: number }) {
    // 注册默认策略
    this.registerStrategy({
      name: 'react',
      systemInstruction: '使用 ReAct 模式:先思考(Thought),再行动(Action),然后观察(Observation)。重复此循环直到任务完成。',
    });
  }
  
  registerStrategy(strategy: ReasoningStrategy): void {
    this.strategies.push(strategy);
  }
  
  getCurrentStrategy(): ReasoningStrategy {
    return this.strategies[this.currentIndex];
  }
  
  evaluate(state: AgentState): ReasoningDecision {
    const metadata = {
      stepIndex: state.stepIndex,
      maxSteps: this.config.maxSteps,
      elapsedTime: Date.now() - state.startTime,
      tokensUsedSoFar: state.totalTokensUsed,
    };
    
    // 检查终止条件
    if (state.stepIndex >= this.config.maxSteps) {
      return { shouldContinue: false, metadata };
    }
    
    if (metadata.elapsedTime > this.config.maxTimeMs) {
      return { shouldContinue: false, metadata };
    }
    
    if (state.totalTokensUsed > this.config.maxTokens) {
      return { shouldContinue: false, metadata };
    }
    
    // 检测推理循环(连续3步产生相同的工具调用)
    if (this.detectLoop(state)) {
      return {
        shouldContinue: false,
        metadata: { ...metadata, loopDetected: true },
      };
    }
    
    return { shouldContinue: true, metadata };
  }
  
  private detectLoop(state: AgentState): boolean {
    const recentCalls = state.toolCallHistory.slice(-6);
    if (recentCalls.length < 6) return false;
    
    const a = recentCalls.slice(0, 3).map(c => c.name).join(',');
    const b = recentCalls.slice(3, 6).map(c => c.name).join(',');
    return a === b;
  }
}

3.2.4 安全护栏(Safety Guard)

Safety Guard 是 Agent 的"免疫系统",确保每一步操作都在安全边界内。

// TypeScript: Safety Guard 接口
interface SafetyCheckResult {
  passed: boolean;
  risk: 'none' | 'low' | 'medium' | 'high' | 'critical';
  reason?: string;
  action?: 'allow' | 'warn' | 'block' | 'require_confirmation';
}

interface SafetyGuard {
  /** 检查输入内容 */
  checkInput(input: string): Promise<SafetyCheckResult>;
  /** 检查输出内容 */
  checkOutput(output: string): Promise<SafetyCheckResult>;
  /** 检查工具调用权限 */
  checkToolCall(toolName: string, args: Record<string, unknown>): Promise<SafetyCheckResult>;
  /** 过滤敏感信息 */
  sanitize(content: string): Promise<string>;
}

class SimpleSafetyGuard implements SafetyGuard {
  private blockedPatterns = [
    /rm\s+-rf\s+\//i,           // rm -rf /
    /sudo\s+rm/i,               // sudo rm
    /DROP\s+TABLE/i,            // SQL DROP
    /eval\s*\(/i,               // eval()
  ];
  
  async checkInput(input: string): Promise<SafetyCheckResult> {
    // 检查 Prompt Injection
    const injectionPatterns = [
      /忽略(以上|之前|所有).*指令/i,
      /ignore (above|previous|all).*instructions/i,
      /you are now .*instead/i,
    ];
    
    for (const pattern of injectionPatterns) {
      if (pattern.test(input)) {
        return {
          passed: false,
          risk: 'high',
          reason: '检测到潜在的 Prompt Injection 攻击',
          action: 'block',
        };
      }
    }
    
    return { passed: true, risk: 'none', action: 'allow' };
  }
  
  async checkOutput(output: string): Promise<SafetyCheckResult> {
    // 检查敏感信息泄露
    const sensitivePatterns = [
      /sk-[a-zA-Z0-9]{32,}/,     // API Key
      /Bearer\s+[a-zA-Z0-9_-]+/, // Bearer Token
      /\d{16,19}/,               // 可能的信用卡号
    ];
    
    for (const pattern of sensitivePatterns) {
      if (pattern.test(output)) {
        return {
          passed: false,
          risk: 'critical',
          reason: '输出中包含疑似敏感信息',
          action: 'block',
        };
      }
    }
    
    return { passed: true, risk: 'none', action: 'allow' };
  }
  
  async checkToolCall(
    toolName: string,
    args: Record<string, unknown>
  ): Promise<SafetyCheckResult> {
    // 工具操作风险分级
    const toolRisk: Record<string, { risk: string; action: string }> = {
      'read_file': { risk: 'none', action: 'allow' },
      'write_file': { risk: 'low', action: 'allow' },
      'run_command': { risk: 'medium', action: 'warn' },
      'delete_file': { risk: 'high', action: 'require_confirmation' },
      'sudo_command': { risk: 'critical', action: 'block' },
    };
    
    const risk = toolRisk[toolName] ?? { risk: 'low', action: 'allow' };
    
    // 检查命令参数中的危险模式
    if (toolName === 'run_command' && args.command) {
      for (const pattern of this.blockedPatterns) {
        if (pattern.test(String(args.command))) {
          return {
            passed: false,
            risk: 'critical',
            reason: `命令包含危险操作: ${args.command}`,
            action: 'block',
          };
        }
      }
    }
    
    return {
      passed: true,
      risk: risk.risk as any,
      action: risk.action as any,
    };
  }
}

3.2.5 记忆系统(Memory System)

Memory System 让 Agent 拥有跨越时间和对话的记忆能力。

// TypeScript: Memory System 接口
interface MemorySystem {
  /** 加载相关记忆 */
  load(query: string, options?: MemoryLoadOptions): Promise<MemoryItem[]>;
  /** 保存记忆 */
  save(item: MemoryItem): Promise<void>;
  /** 更新记忆 */
  update(id: string, updates: Partial<MemoryItem>): Promise<void>;
  /** 删除记忆 */
  delete(id: string): Promise<void>;
  /** 搜索记忆 */
  search(query: string, topK?: number): Promise<MemoryItem[]>;
}

interface MemoryItem {
  id: string;
  type: 'episodic' | 'semantic' | 'procedural';
  content: string;
  metadata: Record<string, unknown>;
  createdAt: Date;
  updatedAt: Date;
  importance: number;  // 0-1,决定是否进入长期记忆
  accessCount: number;
  lastAccessedAt: Date;
}

class SimpleMemorySystem implements MemorySystem {
  // 三层存储
  private workingMemory: Map<string, MemoryItem> = new Map();  // 当前会话
  private shortTerm: MemoryItem[] = [];  // 最近 N 条交互
  private longTerm: MemoryItem[] = [];    // 持久化的重要记忆
  
  async load(query: string, options?: MemoryLoadOptions): Promise<MemoryItem[]> {
    const results: MemoryItem[] = [];
    
    // 1. 工作记忆:精确匹配
    const exact = this.workingMemory.get(query);
    if (exact) results.push(exact);
    
    // 2. 短期记忆:最近 N 条
    results.push(...this.shortTerm.slice(-(options?.shortTermLimit ?? 5)));
    
    // 3. 长期记忆:语义搜索
    const relevant = this.longTerm
      .filter(m => this.simpleRelevance(m.content, query) > 0.3)
      .sort((a, b) => b.importance - a.importance)
      .slice(0, options?.longTermLimit ?? 3);
    results.push(...relevant);
    
    return results;
  }
  
  async save(item: MemoryItem): Promise<void> {
    // 存入工作记忆
    this.workingMemory.set(item.id, item);
    
    // 存入短期记忆
    this.shortTerm.push(item);
    if (this.shortTerm.length > 50) {
      // 溢出 → 低重要性丢弃,高重要性转移到长期记忆
      const oldest = this.shortTerm.shift()!;
      if (oldest.importance > 0.7) {
        this.longTerm.push(oldest);
      }
    }
    
    // 高重要性直接进入长期记忆
    if (item.importance > 0.8) {
      this.longTerm.push(item);
    }
  }
  
  private simpleRelevance(content: string, query: string): number {
    // 简单的关键词匹配(生产环境用 Embedding + 向量相似度)
    const contentWords = new Set(content.toLowerCase().split(/\s+/));
    const queryWords = query.toLowerCase().split(/\s+/);
    const overlap = queryWords.filter(w => contentWords.has(w)).length;
    return overlap / queryWords.length;
  }
}

3.2.6 可观测性(Observability)

Observability 是 Agent 的"监控仪表盘",确保你能看到 Agent 内部的每一步发生了什么。

// TypeScript: Observability 接口
interface Observability {
  /** 记录推理步骤 */
  traceStep(step: AgentStep): void;
  /** 记录工具调用 */
  traceToolCall(toolCall: ToolCall, result: ToolResult): void;
  /** 记录最终输出 */
  traceOutput(output: string, usage: TokenUsage): void;
  /** 记录错误 */
  traceError(error: Error, context: Record<string, unknown>): void;
  /** 获取完整追踪 */
  getTrace(): AgentTrace;
}

interface AgentStep {
  stepIndex: number;
  modelInput: ModelInput;
  modelOutput: ModelOutput;
  timing: { startedAt: number; endedAt: number };
  contextSize: number;
}

interface AgentTrace {
  sessionId: string;
  task: string;
  steps: AgentStep[];
  toolCalls: Array<{ call: ToolCall; result: ToolResult }>;
  finalOutput?: string;
  totalTokens: number;
  totalCost: number;
  totalDuration: number;
  errors: Array<{ error: Error; context: Record<string, unknown> }>;
}

class SimpleObservability implements Observability {
  private trace: AgentTrace;
  
  constructor(sessionId: string, task: string) {
    this.trace = {
      sessionId,
      task,
      steps: [],
      toolCalls: [],
      totalTokens: 0,
      totalCost: 0,
      totalDuration: 0,
      errors: [],
    };
  }
  
  traceStep(step: AgentStep): void {
    this.trace.steps.push(step);
    this.trace.totalTokens += 
      step.modelOutput.usage.inputTokens + 
      step.modelOutput.usage.outputTokens;
    
    console.log(
      `[Step ${step.stepIndex}] ` +
      `耗时: ${step.timing.endedAt - step.timing.startedAt}ms, ` +
      `Token: ${step.modelOutput.usage.inputTokens}+${step.modelOutput.usage.outputTokens}, ` +
      `决策: ${step.modelOutput.type}`
    );
  }
  
  traceToolCall(call: ToolCall, result: ToolResult): void {
    this.trace.toolCalls.push({ call, result });
    console.log(
      `[Tool] ${call.name}(${JSON.stringify(call.arguments).slice(0, 100)}) ` +
      `${result.status} (${result.duration}ms)`
    );
  }
  
  getTrace(): AgentTrace {
    return this.trace;
  }
}

3.3 Harness 的工程化设计

3.3.1 中间件架构:可插拔的 Harness 组件

Harness 六大组件不是独立存在的孤岛,它们需要通过统一的中间件架构协同工作。

// TypeScript: Harness 中间件架构

type HarnessMiddleware = (
  context: AgentContext,
  next: () => Promise<AgentContext>
) => Promise<AgentContext>;

class HarnessPipeline {
  private middlewares: Array<{ name: string; fn: HarnessMiddleware }> = [];
  
  use(name: string, fn: HarnessMiddleware): this {
    this.middlewares.push({ name, fn });
    return this;
  }
  
  async execute(initial: AgentContext): Promise<AgentContext> {
    let index = -1;
    
    const dispatch = async (i: number): Promise<AgentContext> => {
      if (i <= index) throw new Error('中间件重复调用');
      index = i;
      
      if (i >= this.middlewares.length) return initial;
      
      const { name, fn } = this.middlewares[i];
      console.log(`[Pipeline] 执行中间件: ${name}`);
      return fn(initial, () => dispatch(i + 1));
    };
    
    return dispatch(0);
  }
}

// 使用示例:组装 Agent 的 Harness 管道
const pipeline = new HarnessPipeline();

pipeline
  .use('context', async (ctx, next) => {
    ctx.assembledContext = await ctx.contextManager.build({
      task: ctx.task,
      history: ctx.history,
      tools: ctx.toolExecutor.getDefinitions(),
      userProfile: ctx.userProfile,
    });
    return next();
  })
  .use('safety-input', async (ctx, next) => {
    const check = await ctx.safetyGuard.checkInput(ctx.task);
    if (!check.passed) {
      throw new Error(`输入安全检查未通过: ${check.reason}`);
    }
    return next();
  })
  .use('reason', async (ctx, next) => {
    ctx.modelOutput = await ctx.model.reason(ctx.assembledContext!);
    ctx.observability.traceStep({
      stepIndex: ctx.stepIndex,
      modelInput: ctx.assembledContext!,
      modelOutput: ctx.modelOutput,
      timing: { startedAt: Date.now(), endedAt: Date.now() },
      contextSize: ctx.assembledContext!.tokenBudget.used,
    });
    return next();
  })
  .use('reasoning-control', async (ctx, next) => {
    const decision = ctx.reasoningController.evaluate(ctx);
    ctx.shouldContinue = decision.shouldContinue;
    return next();
  })
  .use('execute-tools', async (ctx, next) => {
    if (ctx.modelOutput?.toolCalls) {
      for (const call of ctx.modelOutput.toolCalls) {
        // 安全检查
        const safetyCheck = await ctx.safetyGuard.checkToolCall(
          call.name, call.arguments
        );
        if (safetyCheck.action === 'block') {
          throw new Error(`工具调用被阻止: ${safetyCheck.reason}`);
        }
        
        // 执行
        const result = await ctx.toolExecutor.execute(call, ctx);
        ctx.observability.traceToolCall(call, result);
        ctx.toolResults.push(result);
      }
    }
    return next();
  })
  .use('memory', async (ctx, next) => {
    await ctx.memorySystem.save({
      id: `step-${ctx.stepIndex}-${Date.now()}`,
      type: 'episodic',
      content: JSON.stringify({
        task: ctx.task,
        output: ctx.modelOutput?.textContent,
        toolCalls: ctx.modelOutput?.toolCalls,
      }),
      metadata: { stepIndex: ctx.stepIndex },
      createdAt: new Date(),
      updatedAt: new Date(),
      importance: 0.5,
      accessCount: 0,
      lastAccessedAt: new Date(),
    });
    return next();
  });

3.3.2 配置管理:环境变量、配置文件、运行时配置

// TypeScript: Harness 配置管理
interface HarnessConfig {
  model: {
    provider: 'anthropic' | 'openai' | 'deepseek';
    modelId: string;
    temperature: number;
    maxTokens: number;
    fallbackChain?: Array<{ provider: string; modelId: string }>;
  };
  context: {
    maxWindowTokens: number;
    systemPromptReserveRatio: number;
    compactionThreshold: number;
  };
  tools: {
    enabledTools: string[];
    defaultTimeout: number;
    maxConcurrent: number;
  };
  reasoning: {
    strategy: 'react' | 'cot' | 'plan-execute';
    maxSteps: number;
    maxTokens: number;
    maxTimeMs: number;
  };
  safety: {
    inputFiltering: boolean;
    outputModeration: boolean;
    requireConfirmation: Array<'delete' | 'execute' | 'deploy'>;
  };
  memory: {
    shortTermLimit: number;
    longTermEnabled: boolean;
    vectorDb: 'chroma' | 'qdrant' | 'memory';
  };
  observability: {
    traceLevel: 'basic' | 'detailed' | 'debug';
    logToFile: boolean;
    metricsEnabled: boolean;
  };
}

class ConfigManager {
  private config: HarnessConfig;
  
  constructor() {
    this.config = this.loadConfig();
  }
  
  private loadConfig(): HarnessConfig {
    // 优先级:环境变量 > 配置文件 > 默认值
    if (process.env.AGENT_CONFIG) {
      return JSON.parse(process.env.AGENT_CONFIG);
    }
    
    if (fs.existsSync('./agent.config.json')) {
      return JSON.parse(fs.readFileSync('./agent.config.json', 'utf-8'));
    }
    
    return this.getDefaults();
  }
  
  private getDefaults(): HarnessConfig {
    return {
      model: {
        provider: 'anthropic',
        modelId: 'claude-sonnet-4-6',
        temperature: 0.7,
        maxTokens: 4096,
      },
      context: {
        maxWindowTokens: 180000,
        systemPromptReserveRatio: 0.1,
        compactionThreshold: 0.85,
      },
      tools: {
        enabledTools: ['read_file', 'write_file', 'search_code', 'run_command'],
        defaultTimeout: 30000,
        maxConcurrent: 5,
      },
      reasoning: {
        strategy: 'react',
        maxSteps: 50,
        maxTokens: 100000,
        maxTimeMs: 300000,
      },
      safety: {
        inputFiltering: true,
        outputModeration: true,
        requireConfirmation: ['delete', 'execute', 'deploy'],
      },
      memory: {
        shortTermLimit: 20,
        longTermEnabled: true,
        vectorDb: 'memory',
      },
      observability: {
        traceLevel: 'detailed',
        logToFile: true,
        metricsEnabled: true,
      },
    };
  }
  
  get<K extends keyof HarnessConfig>(key: K): HarnessConfig[K] {
    return this.config[key];
  }
  
  update(partial: Partial<HarnessConfig>): void {
    this.config = deepMerge(this.config, partial);
  }
}

3.3.3 实践案例:模块化 Agent 框架骨架

将以上所有组件整合为一个完整的、可工作的 Agent 框架:

// TypeScript: 模块化 Agent 框架骨架
class Agent {
  // Harness 六大组件
  private contextManager: ContextManager;
  private toolExecutor: ToolExecutor;
  private reasoningController: ReasoningController;
  private safetyGuard: SafetyGuard;
  private memorySystem: MemorySystem;
  private observability: Observability;
  
  // Model
  private model: ModelProvider;
  
  // 状态
  private history: Message[] = [];
  private config: HarnessConfig;
  
  constructor(config: HarnessConfig) {
    this.config = config;
    
    // 初始化 Model
    this.model = this.createModel(config.model);
    
    // 初始化 Harness 组件
    this.contextManager = new SimpleContextManager({
      maxWindowTokens: config.context.maxWindowTokens,
      systemPromptReserveRatio: config.context.systemPromptReserveRatio,
      historyReserveRatio: 0.6,
      compactionThreshold: config.context.compactionThreshold,
    });
    
    this.toolExecutor = new SimpleToolExecutor();
    this.reasoningController = new SimpleReasoningController({
      maxSteps: config.reasoning.maxSteps,
      maxTokens: config.reasoning.maxTokens,
      maxTimeMs: config.reasoning.maxTimeMs,
    });
    
    this.safetyGuard = new SimpleSafetyGuard();
    this.memorySystem = new SimpleMemorySystem();
  }
  
  /** 注册工具 */
  registerTool(definition: ToolDefinition, handler: ToolHandler): void {
    this.toolExecutor.register(definition, handler);
  }
  
  /** 执行任务 */
  async run(task: string): Promise<string> {
    const sessionId = crypto.randomUUID();
    const startTime = Date.now();
    
    this.observability = new SimpleObservability(sessionId, task);
    
    console.log(`[Agent] 开始执行任务: ${task.slice(0, 100)}`);
    
    // Agent 主循环
    for (let step = 0; step < this.config.reasoning.maxSteps; step++) {
      const stepStartTime = Date.now();
      
      // 1. Perceive: 构建上下文
      const context = await this.contextManager.build({
        task,
        history: this.history,
        tools: this.toolExecutor.getDefinitions(),
        userProfile: await this.memorySystem.load('user_profile'),
        memories: await this.memorySystem.search(task, 3),
      });
      
      // 2. Safety Check (Input)
      const inputCheck = await this.safetyGuard.checkInput(task);
      if (!inputCheck.passed) {
        throw new Error(`输入安全检查失败: ${inputCheck.reason}`);
      }
      
      // 3. Reason: Model 推理
      const modelInput: ModelInput = {
        systemPrompt: context.systemPrompt,
        messages: context.messages,
        tools: this.toolExecutor.getDefinitions(),
      };
      
      const modelOutput = await this.model.reason(modelInput);
      
      // 记录步骤
      this.observability.traceStep({
        stepIndex: step,
        modelInput,
        modelOutput,
        timing: { startedAt: stepStartTime, endedAt: Date.now() },
        contextSize: context.tokenBudget.used,
      });
      
      // 将 Model 输出加入历史
      this.history.push({ role: 'assistant', content: JSON.stringify(modelOutput) });
      
      // 4. 处理决策
      if (modelOutput.toolCalls && modelOutput.toolCalls.length > 0) {
        // 执行工具调用
        for (const call of modelOutput.toolCalls) {
          const safetyCheck = await this.safetyGuard.checkToolCall(
            call.name, call.arguments
          );
          
          if (safetyCheck.action === 'block') {
            const errorResult: ToolResult = {
              toolCallId: call.id,
              toolName: call.name,
              status: 'permission_denied',
              content: `工具调用被阻止: ${safetyCheck.reason}`,
              duration: 0,
              error: safetyCheck.reason,
            };
            this.observability.traceToolCall(call, errorResult);
            this.history.push({ role: 'tool', content: JSON.stringify(errorResult) });
            continue;
          }
          
          const result = await this.toolExecutor.execute(call, {
            sessionId,
            userId: 'user',
            permissions: ['read', 'write'],
          });
          
          this.observability.traceToolCall(call, result);
          this.history.push({ role: 'tool', content: JSON.stringify(result) });
        }
      } else {
        // 无工具调用,这是最终回复
        const output = modelOutput.textContent ?? '任务完成,但无法生成文本输出。';
        
        // 安全检查(输出)
        const outputCheck = await this.safetyGuard.checkOutput(output);
        if (!outputCheck.passed) {
          throw new Error(`输出安全检查失败: ${outputCheck.reason}`);
        }
        
        // 保存记忆
        await this.memorySystem.save({
          id: sessionId,
          type: 'episodic',
          content: JSON.stringify({ task, output, duration: Date.now() - startTime }),
          metadata: { sessionId, timestamp: new Date().toISOString() },
          createdAt: new Date(),
          updatedAt: new Date(),
          importance: 0.6,
          accessCount: 0,
          lastAccessedAt: new Date(),
        });
        
        console.log(`[Agent] 任务完成,共 ${step + 1} 步,耗时 ${Date.now() - startTime}ms`);
        return output;
      }
      
      // 检查是否应该继续
      const decision = this.reasoningController.evaluate({
        stepIndex: step,
        startTime,
        totalTokensUsed: context.tokenBudget.used,
        toolCallHistory: (modelOutput.toolCalls || []).map(c => ({ 
          name: c.name, args: c.arguments 
        })),
      } as any);
      
      if (!decision.shouldContinue) {
        return '任务达到限制条件(最大步数/Token数/时间),已中止。';
      }
    }
    
    return '任务超出最大步数限制。';
  }
  
  private createModel(config: HarnessConfig['model']): ModelProvider {
    switch (config.provider) {
      case 'anthropic':
        return new AnthropicModelProvider(
          new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! }),
          config.modelId
        );
      case 'openai':
        // return new OpenAIModelProvider(...);
        throw new Error('OpenAI 适配器尚未实现');
      default:
        throw new Error(`不支持的模型提供商: ${config.provider}`);
    }
  }
}

// ===== 使用示例 =====
async function main() {
  const agent = new Agent(defaultConfig);
  
  // 注册工具
  agent.registerTool(
    {
      name: 'read_file',
      description: '读取文件内容',
      parameters: {
        type: 'object',
        properties: {
          path: { type: 'string', description: '文件路径' },
        },
        required: ['path'],
      },
      permission: 'read',
    },
    async (args) => `文件 ${args.path} 的内容是: ...`
  );
  
  agent.registerTool(
    {
      name: 'search_code',
      description: '在代码库中搜索',
      parameters: {
        type: 'object',
        properties: {
          query: { type: 'string', description: '搜索关键词' },
        },
        required: ['query'],
      },
      permission: 'read',
    },
    async (args) => `搜索 "${args.query}" 找到 5 个结果`
  );
  
  // 执行任务
  const result = await agent.run(
    '分析项目中与用户认证相关的代码,找出潜在的安全问题'
  );
  console.log('最终结果:', result);
}

这个 Agent 框架骨架约 200 行核心代码,但它已经是生产级 Agent 的雏形。它具备:

  • 完整的 Agent 主循环:Perceive → Reason → Act → Observe
  • 六大 Harness 组件:每个都有清晰的接口和简化实现
  • 配置驱动:所有行为通过配置控制
  • 可扩展:每个组件都可以替换为更复杂的实现

在后续章节(第4-8章)中,我们将逐一深入每个 Harness 组件,将这个骨架中的简化实现替换为生产级的完整实现。


3.4 本章小结

本章是全书最核心的章节之一,我们完成了从"认知框架"到"可执行代码架构"的关键跨越:

  1. Model 层:推理引擎的统一抽象接口。无论底层是什么模型,Agent 框架都通过 ModelProvider 接口与之交互。Model 只负责"想",一切"做"的事情交给 Harness。

  2. Harness 层:六大组件的职责边界和交互协议。Context Manager 负责信息输入,Tool Executor 负责行动执行,Reasoning Controller 负责过程控制,Safety Guard 负责边界约束,Memory System 负责信息持久化,Observability 负责全链路可见。

  3. Agent 主循环:Perceive → Reason → Act → Observe 的信息闭环。这是所有 Agent 系统最核心的设计模式。

  4. 中间件架构:六组件通过中间件管道协同工作,支持可插拔的组件替换和灵活的顺序编排。

  5. 模块化 Agent 框架:200 行核心代码实现了完整的 Agent 框架骨架,包括 Model 适配、Harness 六组件、配置管理和主循环。

从第4章开始,我们将进入 Harness 组件篇,逐一深入六大组件的完整工程实现。


关键术语

术语 英文 定义
Agent 主循环 Agent Main Loop Perceive → Reason → Act → Observe 的核心循环
Model Provider Model Provider 模型推理引擎的统一抽象接口
中间件架构 Middleware Architecture 通过中间件链组装 Harness 组件的架构模式
信息闭环 Information Loop 工具执行结果反馈回上下文的循环机制
配置驱动 Configuration-Driven 通过配置而非硬编码控制组件行为的模式

思考与练习

  1. Agent 主循环改造:将本章3.1.4节的最小 Agent 代码扩展,加入 Context Manager(简单的窗口管理)和 Safety Guard(输入过滤),感受 Harness 组件如何增强 Agent。

  2. 多模型切换:基于 3.1.1 节的 ModelProvider 接口,实现一个 OpenAIModelProvider,然后在不改动 Agent 核心代码的前提下,从 Claude 切换到 GPT。

  3. Harness 组件评估:如果你正在构建一个 Agent 应用,对照本章3.2节评估你的六大 Harness 组件各处于什么成熟度:不存在 / 玩具级 / 可用 / 生产级?

  4. 配置设计:为你的 Agent 应用设计一份完整的配置文件(JSON 格式),覆盖 Model、Context、Tools、Reasoning、Safety、Memory、Observability 七个维度。

  5. 框架改造:运行本章3.3.3节的 Agent 框架骨架代码,尝试:

    • 注册一个新工具
    • 修改配置(如改变 maxSteps、strategy)
    • 观察 Observability 输出的追踪日志
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐