首页 › AI Agent › Agent100道面试题 › 框架选型-frameworks/ › 如何设计可测试、可扩展的 Agent 框架抽象层?

如何设计可测试、可扩展的 Agent 框架抽象层?

难度:高级 分类:Frameworks​

简短回答​

设计可测试、可扩展的 Agent 框架抽象层,核心是将 Agent 系统分解为职责清晰、边界明确、可独立替换的模块,使每个模块可以被单独测试和扩展。关键架构原则:(1) 端口-适配器架构(Hexagonal Architecture)——业务逻辑(Agent Loop、推理策略)在核心层,外部依赖(LLM API、工具、存储)通过接口(Port)隔离,具体实现(Adapter)可插拔替换;(2) 依赖注入(DI)——LLM Client、Tool Registry、State Store 等通过构造函数注入,测试时可注入 Mock;(3) 中间件模式——日志、追踪、安全检查等横切关注点通过可组合的中间件实现,不污染核心逻辑;(4) 策略模式——推理策略(ReAct/Plan-Execute/CoT)、路由策略(模型选择)、重试策略等可插拔;(5) 事件驱动——Agent 执行过程发出事件(on_llm_call、on_tool_use、on_step_complete),观测和扩展通过事件监听实现。测试策略:单元测试 Mock LLM 响应 → 集成测试用真实 API → 端到端测试验证完整工作流。OpenAI Agents SDK 的"最小抽象"和 LangChain 1.0 的"中间件架构"都是这些原则的实际体现。​

详细解析​

分层架构设计​

​

飞书文档 - 图片

​

可测试性来自依赖方向:核心只认 Port 接口,LLM / 工具 / 存储全是可替换适配器——测试即换 Mock。​

关键原则:​

•

依赖方向:外层依赖内层,内层不依赖外层​

•

核心层只定义接口(Protocol),不包含实现​

•

适配器可独立替换,不影响其他层​

核心接口定义(Port)​

​

代码块​

Python

from abc import ABC, abstractmethod​

from typing import Protocol, AsyncIterator​

from dataclasses import dataclass​

​

# ===== 数据模型 =====​

@dataclass​

class Message:​

role: str # "system" | "user" | "assistant" | "tool"​

content: str​

tool_calls: list["ToolCall"] | None = None​

​

​

可插拔的适配器实现​

​

代码块​

Python

# ===== LLM 适配器 =====​

​

class OpenAIAdapter:​

"""OpenAI LLM 适配器"""​

def __init__(self, model: str = "gpt-4o", api_key: str = None):​

from openai import AsyncOpenAI​

self.client = AsyncOpenAI(api_key=api_key)​

self.model = model​

​

async def complete(self, messages, tools=None):​

response = await self.client.chat.completions.create(​

model=self.model,​

messages=[m.to_openai_format() for m in messages],​

​

Agent Loop 与依赖注入​

​

代码块​

Python

​

中间件模式​

​

代码块​

Python

# 中间件:横切关注点的可组合实现​

​

测试策略​

​

代码块​

Python

​

常见误区 / 面试追问​

误区:"可测试性意味着 100% 覆盖率" — Agent 系统中,LLM 输出的非确定性使得 100% 确定性测试不现实。正确的测试策略是分层的:单元测试 Mock LLM(验证逻辑)→ 集成测试用真实 API(验证集成)→ 评估测试用 LLM-as-Judge(验证质量)。每层覆盖不同的风险。​

误区:"接口越多越好" — 过度抽象和不够抽象一样有害。只为真正需要替换的组件定义接口。如果你的项目永远不会换 LLM 提供商,一个简单的封装函数就够了,不需要 Port/Adapter 全套架构。YAGNI 原则同样适用于框架设计。​

追问:"如何处理 LLM 响应的非确定性测试?" — 三种方法:(1) Mock——固定 LLM 输出,测试围绕它的逻辑;(2) 快照测试——记录真实 LLM 输出作为基线,后续对比偏差;(3) 属性测试——不测具体输出内容,测输出的属性(如"返回的 JSON 必须包含 name 字段"、"不包含 PII")。​

追问:"事件驱动 vs 回调 vs 中间件,怎么选?" — (1) **回调(Callback)**适合简单的钩子(on_start/on_end);(2) **中间件(Middleware)**适合可组合的请求/响应处理管线;(3) **事件驱动(Event Bus)**适合松耦合的观测和扩展,多个监听器互不影响。生产框架通常三者结合:中间件处理核心管线,事件驱动处理观测,回调提供简单的用户扩展点。​