实现Agent的评估器-TS版本
一、Agent 评估器核心设计
文件链接:https://ai.feishu.cn/file/SQpqbUWqdoTnwkxNYmRc2wqQnIg
.DZiWyMRb.png)
在上面这张思维设计图中可以看到,有以下几个关键的模块在 Agent 评估器中
- 评估测试数据集:我们要准备好“合适的问题”和“标准答案”,当然这个可以是大模型生成的,也可以是人工手动写的,最好是人工的方式,这种数据集评估效果最佳
- Agent:就是我们要评估的那个 Agent,评估该 Agent 的设计如何?,输出的结果如何?
- 事件系统:这个是用来收集 Agent 的执行状态使用的
- 评估函数:根据“测试数据集”和“实际收到的数据”进行判断和计算
二、评估测试数据集
评估测试数据集的格式类型:
- id:测试用例的 ID
- description:测试用例的描述
- input:用户的输入,准备输入到 Agent 中执行的
- expected:期望的 Agent 的结果
正常的评估器需要评估的是 Agent 两个关键的地方
- 单 Agent:对于单 Agent 来说,最重要的评估 Agent 是否能挣正确调用工具,所以需要收集工具调用的状态
- 多 Agent:对于多 Agent 来说,除了子 Agent 调用工具的情况,还有主 Agent 是否能正确协调子 Agent
以此我们可以得到 expected 中的属性值:一个是 Agent 的执行列表、一个是工具调用列表
typescript
/**
* 测试用例定义
*/
export interface TestCase {
id: string; // 测试用例ID
description: string; // 用例描述
input: string; // 用户输入
expected: ExpectedBehavior; // 期望结果
}
/**
* 期望行为定义
*/
export interface ExpectedBehavior {
// 期望调用的子Agent列表
agents: string[];
// 每个子Agent期望调用的工具
tools: {
[agentName: string]: string[];
};
}
/**
* 测试用例
*/
export const TEST_CASES: TestCase[] = [
// 1. 单工具测试 - 天气查询
{
id: 'T1',
description: '单工具调用 - 天气查询',
input: '北京今天天气怎么样?',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['get_weather'] },
},
},
// 2. 单工具测试 - 翻译
{
id: 'T2',
description: '单工具调用 - 翻译',
input: '把"你好世界"翻译成英文',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['translate'] },
},
},
];三、事件系统
在简单的 Agent 中,可以不需要事件系统来收集 Agent 的执行情况,可以让 Agent 函数直接返回执行情况
但是如果是多于复杂的 Agent 的设计,例如:大量的工具调用、多智能体的架构设计,在这种情况下,采用事件系统,也就是发布-订阅的方式来收集 Agent 的执行情况就非常有效了
对于事件系统的实现,最关键的是定义到事件类型,下面是完整的事件系统实现
typescript
/**
* 事件总线 + 数据收集器(合并版本)
* 负责事件的发射、监听,以及收集执行过程中的数据
*/
import { EventEmitter } from 'events';
import { CollectedData } from './types.js';
/**
* 事件类型
*/
export type EventType =
| 'agent:call' // 子Agent被调用
| 'tool:call' // 工具被调用
| 'edit:complete'; // 编辑节点完成
/**
* 事件数据类型
*/
export interface AgentCallEvent {
agentName: string;
}
export interface ToolCallEvent {
agentName: string;
toolName: string;
}
export interface EditCompleteEvent {
successCount: number;
failCount: number;
}
/**
* 事件总线 - 单例模式
* 合并了事件发射和数据收集功能
*/
class EventBus {
private emitter = new EventEmitter();
private static instance: EventBus;
// 数据收集(原 Collector 功能)
private agents: string[] = [];
private tools: Map<string, string[]> = new Map();
private editResult: { success: number; fail: number } | null = null;
constructor() {
this.setupListeners();
}
/**
* 获取单例实例
*/
static getInstance(): EventBus {
if (!this.instance) {
this.instance = new EventBus();
}
return this.instance;
}
/**
* 设置内部事件监听器(用于数据收集)
*/
private setupListeners() {
// 监听子Agent调用事件
this.emitter.on('agent:call', (data: AgentCallEvent) => {
this.agents.push(data.agentName);
// 为该Agent初始化工具列表
if (!this.tools.has(data.agentName)) {
this.tools.set(data.agentName, []);
}
});
// 监听工具调用事件
this.emitter.on('tool:call', (data: ToolCallEvent) => {
const agentTools = this.tools.get(data.agentName);
if (agentTools) {
// 去重添加
if (!agentTools.includes(data.toolName)) {
agentTools.push(data.toolName);
}
} else {
this.tools.set(data.agentName, [data.toolName]);
}
});
// 监听编辑完成事件
this.emitter.on('edit:complete', (data: EditCompleteEvent) => {
this.editResult = {
success: data.successCount,
fail: data.failCount,
};
});
}
/**
* 发射事件
*/
emit(event: EventType, data: AgentCallEvent | ToolCallEvent | EditCompleteEvent) {
this.emitter.emit(event, data);
}
/**
* 监听事件(供外部使用)
*/
on(event: EventType, handler: (data: any) => void) {
this.emitter.on(event, handler);
}
/**
* 获取收集到的数据
*/
getData(): CollectedData {
return {
agents: [...new Set(this.agents)], // 去重
tools: Object.fromEntries(this.tools),
editResult: this.editResult,
};
}
/**
* 重置收集器(每次测试前调用)
*/
reset() {
this.agents = [];
this.tools = new Map();
this.editResult = null;
}
/**
* 重置实例(用于测试)
*/
static resetInstance() {
if (this.instance) {
this.instance.emitter.removeAllListeners();
this.instance = new EventBus();
}
}
}
// 导出单例
export const eventBus = EventBus.getInstance();四、Agent 的执行情况收集
在 Agent 中,我们使用事件系统来收集 Agent 的执行情况,在需要收集的地方,使用 emit来触发相应的事件函数
typescript
/**
* 简单Agent实现
* 使用OpenAI SDK调用DeepSeek,包含工具定义、执行函数和调用循环
* 通过事件系统收集执行数据
*/
import OpenAI from 'openai';
import { DeepSeekBaseURL } from '../../constant/modelConstant.js';
import { eventBus } from './EventBus.js';
// 初始化OpenAI客户端(指向DeepSeek)
const client = new OpenAI({
apiKey: process.env.deepseekAPI,
baseURL: DeepSeekBaseURL,
});
const tools: OpenAI.ChatCompletionTool[] = [
{
type: 'function',
function: {
name: 'get_weather',
description: '获取指定城市的天气信息',
parameters: {
type: 'object',
properties: {
city: { type: 'string', description: '城市名称' },
},
required: ['city'],
},
},
},
{
type: 'function',
function: {
name: 'translate',
description: '将文本翻译成指定语言',
parameters: {
type: 'object',
properties: {
text: { type: 'string', description: '要翻译的文本' },
targetLang: { type: 'string', description: '目标语言,如 english、chinese、japanese' },
},
required: ['text', 'targetLang'],
},
},
},
];
function executeTool(name: string, args: Record<string, any>): string {
switch (name) {
case 'get_weather':
return JSON.stringify({
city: args.city,
temperature: '25°C',
weather: '晴',
humidity: '60%',
});
case 'translate':
return JSON.stringify({
original: args.text,
translated: `[${args.targetLang}] ${args.text}`,
targetLang: args.targetLang,
});
default:
return JSON.stringify({ error: `未知工具: ${name}` });
}
}
type Message = OpenAI.ChatCompletionMessageParam;
// ============ Agent主函数 ============
export interface AgentResult {
agents: string[];
tools: Record<string, string[]>;
finalResponse: string;
}
const AGENT_NAME = 'simple_agent';
const MAX_ITERATIONS = 10;
export async function simpleAgent(userInput: string): Promise<AgentResult> {
// 发射Agent调用事件
eventBus.emit('agent:call', { agentName: AGENT_NAME });
// 初始化消息
const messages: Message[] = [
{ role: 'system', content: '你是一个有用的助手,可以使用工具来帮助用户完成任务。' },
{ role: 'user', content: userInput },
];
let iterations = 0;
let finalResponse = '';
// 工具调用循环
while (iterations < MAX_ITERATIONS) {
iterations++;
const response = await client.chat.completions.create({
model: 'deepseek-chat',
messages,
tools,
tool_choice: 'auto',
});
const assistantMessage = response.choices[0].message;
// 添加助手消息到历史
messages.push(assistantMessage);
// 如果没有工具调用,结束循环
if (!assistantMessage.tool_calls || assistantMessage.tool_calls.length === 0) {
finalResponse = assistantMessage.content || '';
break;
}
// 执行所有工具调用
for (const toolCall of assistantMessage.tool_calls) {
const toolName = toolCall.function.name;
const toolArgs = JSON.parse(toolCall.function.arguments);
// 发射工具调用事件
eventBus.emit('tool:call', { agentName: AGENT_NAME, toolName });
// 执行工具
const toolResult = executeTool(toolName, toolArgs);
// 添加工具结果到消息
messages.push({
role: 'tool',
content: toolResult,
tool_call_id: toolCall.id,
});
}
}
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse,
};
}五、评估函数
评估函数就是根据事件系统收集到的实际的 Agent 执行数据,参考测试数据集中的“标准输入”和“标准输出”进行计算和判断
简单的一些的评估函数如下,只是做了一层简单的判断
typescript
/**
* 评估函数 - 简化版本
* 比较期望行为和实际执行数据
*/
import { TestCase, CollectedData, EvaluateResult } from './types.js';
/**
* 评估函数
* @param testCase 测试用例
* @param actual 实际收集到的数据
* @returns 评估结果
*/
export function evaluate(testCase: TestCase, actual: CollectedData): EvaluateResult {
const expected = testCase.expected;
// 1. 评估Agent调用
const missedAgents = expected.agents.filter((a) => !actual.agents.includes(a));
const extraAgents = actual.agents.filter((a) => !expected.agents.includes(a));
const agentMatch = missedAgents.length === 0 && extraAgents.length === 0;
// 2. 评估工具调用
const missedTools: { agent: string; tool: string }[] = [];
const extraTools: { agent: string; tool: string }[] = [];
// 检查遗漏的工具
for (const [agent, tools] of Object.entries(expected.tools)) {
const actualTools = actual.tools[agent] || [];
for (const tool of tools) {
if (!actualTools.includes(tool)) {
missedTools.push({ agent, tool });
}
}
}
// 检查多余的工具
for (const [agent, tools] of Object.entries(actual.tools)) {
const expectedTools = expected.tools[agent] || [];
for (const tool of tools) {
if (!expectedTools.includes(tool)) {
extraTools.push({ agent, tool });
}
}
}
const toolMatch = missedTools.length === 0 && extraTools.length === 0;
// 3. 综合判断
const passed = agentMatch && toolMatch;
return {
passed,
agentMatch,
toolMatch,
details: {
agents: {
expected: expected.agents,
actual: actual.agents,
missed: missedAgents,
extra: extraAgents,
},
tools: {
expected: expected.tools,
actual: actual.tools,
missed: missedTools,
extra: extraTools,
},
},
};
}
/**
* 格式化评估结果为可读字符串
*/
export function formatResult(result: EvaluateResult): string {
let output = '';
// Agent评估
if (result.agentMatch) {
output += `✅ Agent调用正确\n`;
} else {
output += `❌ Agent调用错误\n`;
output += ` 期望: ${result.details.agents.expected.join(', ') || '无'}\n`;
output += ` 实际: ${result.details.agents.actual.join(', ') || '无'}\n`;
if (result.details.agents.missed.length > 0) {
output += ` 遗漏: ${result.details.agents.missed.join(', ')}\n`;
}
if (result.details.agents.extra.length > 0) {
output += ` 多余: ${result.details.agents.extra.join(', ')}\n`;
}
}
// 工具评估
if (result.toolMatch) {
output += `✅ 工具调用正确\n`;
} else {
output += `❌ 工具调用错误\n`;
if (result.details.tools.missed.length > 0) {
const missed = result.details.tools.missed.map((t) => `${t.agent}.${t.tool}`).join(', ');
output += ` 遗漏: ${missed}\n`;
}
if (result.details.tools.extra.length > 0) {
const extra = result.details.tools.extra.map((t) => `${t.agent}.${t.tool}`).join(', ');
output += ` 多余: ${extra}\n`;
}
}
return output;
}六、完整的代码文件链接:
https://ai.feishu.cn/drive/folder/HLAWfyjKLlWkJedR5Yycm4ZhnLe