第3章-Agent架构核心-Agent等于Model加Harness深度解析-《Agentic AI 智能体应用开发》
第3章:Agent 架构核心 —— Agent = Model + Harness 深度解析
本章导引
第1章我们建立了"Agent = Model + Harness"的认知框架,第2章我们深入了 Model 层面的 LLM 工程基础。本章是全书最核心的章节之一——我们将从架构层面深度解析 Agent 的内部构造,把"Agent = Model + Harness"公式从抽象概念落地为可执行的代码架构。
本章的目标是让你能够:
- 理解 Agent 主循环的每个环节——Perceive → Reason → Act → Observe
- 掌握 Harness 六大组件的职责边界、交互协议和工程约束
- 能够动手构建一个模块化、可扩展的 Agent 框架骨架
- 理解 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,
)
设计要点:
-
统一接口:无论底层是什么模型,Agent 框架只看到
ModelProvider.reason()这一个入口。这使得切换模型、多模型路由、A/B 测试变得简单。 -
输入标准化:Harness 构建好的上下文以统一的
ModelInput格式传入。Model 层不需要关心上下文的来源——它只需要理解上下文的内容。 -
输出结构化: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 层的设计原则:
-
关注点分离:每个组件有明确的职责边界。Context Manager 不关心工具执行,Safety Guard 不关心推理策略。
-
可插拔性:每个组件定义清晰的接口,具体实现可以替换。你可以用自定义的 Memory System 替换默认实现,而不需要改动其他组件。
-
中间件架构:Harness 组件以中间件链的方式工作,请求依次通过各组件处理。每个组件既能为下游组件提供输入,也能对上游的输出做处理。
-
配置驱动: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 架构的核心精髓:
- 推理-行动循环:Model 推理 → 判断要不要工具 → 执行工具 → 结果回去 → 再推理
- 对话即状态:messages 数组就是 Agent 的状态。每一轮推理都在此基础上进行。
- 工具抽象:工具以 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 本章小结
本章是全书最核心的章节之一,我们完成了从"认知框架"到"可执行代码架构"的关键跨越:
-
Model 层:推理引擎的统一抽象接口。无论底层是什么模型,Agent 框架都通过
ModelProvider接口与之交互。Model 只负责"想",一切"做"的事情交给 Harness。 -
Harness 层:六大组件的职责边界和交互协议。Context Manager 负责信息输入,Tool Executor 负责行动执行,Reasoning Controller 负责过程控制,Safety Guard 负责边界约束,Memory System 负责信息持久化,Observability 负责全链路可见。
-
Agent 主循环:Perceive → Reason → Act → Observe 的信息闭环。这是所有 Agent 系统最核心的设计模式。
-
中间件架构:六组件通过中间件管道协同工作,支持可插拔的组件替换和灵活的顺序编排。
-
模块化 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 | 通过配置而非硬编码控制组件行为的模式 |
思考与练习
-
Agent 主循环改造:将本章3.1.4节的最小 Agent 代码扩展,加入 Context Manager(简单的窗口管理)和 Safety Guard(输入过滤),感受 Harness 组件如何增强 Agent。
-
多模型切换:基于 3.1.1 节的
ModelProvider接口,实现一个OpenAIModelProvider,然后在不改动 Agent 核心代码的前提下,从 Claude 切换到 GPT。 -
Harness 组件评估:如果你正在构建一个 Agent 应用,对照本章3.2节评估你的六大 Harness 组件各处于什么成熟度:不存在 / 玩具级 / 可用 / 生产级?
-
配置设计:为你的 Agent 应用设计一份完整的配置文件(JSON 格式),覆盖 Model、Context、Tools、Reasoning、Safety、Memory、Observability 七个维度。
-
框架改造:运行本章3.3.3节的 Agent 框架骨架代码,尝试:
- 注册一个新工具
- 修改配置(如改变 maxSteps、strategy)
- 观察 Observability 输出的追踪日志
更多推荐
所有评论(0)