🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

create-context-template

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

create-context-template - npm Package Compare versions

Comparing version
1.0.1
to
1.0.2
+142
template/src/config/env.ts
/**
* 环境变量配置管理
*
* 自动加载 .env 文件:
* - 使用 dotenv 库自动加载环境变量
* - 支持 Node.js 所有版本
*
* 优先级(从高到低):
* 1. .env.local (本地覆盖,不提交到 git)
* 2. .env.{NODE_ENV} (环境特定配置,如 .env.production)
* 3. .env (默认配置)
*
* 使用方式:
* ```typescript
* import { config } from './config/env.js';
* console.log(config.llm.deepseek.apiKey);
* ```
*/
import { config as dotenvConfig } from "dotenv";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
//加载环境变量文件
loadEnv();
/** 应用配置接口 */
export interface AppConfig {
/** Node 环境 */
nodeEnv: "development" | "production" | "test";
/** 日志配置 */
logging: {
level: "debug" | "info" | "warn" | "error";
enableConsole: boolean;
};
/** LLM 提供商配置 */
llm: {
/** DeepSeek 配置 */
deepseek: {
apiKey: string;
baseURL?: string;
};
};
/** 默认 LLM 配置 */
defaultProvider: string;
}
/** 获取环境变量,支持默认值 */
function getEnvVar(name: string, defaultValue?: string): string {
const value = process.env[name];
if (!value && defaultValue === undefined) {
throw new Error(`Environment variable ${name} is required but not set`);
}
return value || defaultValue!;
}
/** 获取环境变量布尔值 */
function getEnvBoolean(name: string, defaultValue: boolean = false): boolean {
const value = process.env[name];
if (!value) return defaultValue;
return value.toLowerCase() === "true" || value === "1";
}
/** 验证 NODE_ENV 值 */
function validateNodeEnv(env: string): "development" | "production" | "test" {
if (["development", "production", "test"].includes(env)) {
return env as "development" | "production" | "test";
}
console.warn(`Invalid NODE_ENV: ${env}, falling back to 'development'`);
return "development";
}
/** 验证日志级别 */
function validateLogLevel(level: string): "debug" | "info" | "warn" | "error" {
if (["debug", "info", "warn", "error"].includes(level)) {
return level as "debug" | "info" | "warn" | "error";
}
console.warn(`Invalid LOG_LEVEL: ${level}, falling back to 'info'`);
return "info";
}
/** 导出类型安全的配置对象 */
export const config: AppConfig = {
nodeEnv: validateNodeEnv(getEnvVar("NODE_ENV", "development")),
logging: {
level: validateLogLevel(getEnvVar("LOG_LEVEL", "info")),
enableConsole: getEnvBoolean("ENABLE_CONSOLE_LOG", true),
},
llm: {
deepseek: {
apiKey: getEnvVar("DEEPSEEK_API_KEY", ""),
baseURL: getEnvVar("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
},
},
defaultProvider: getEnvVar("DEFAULT_LLM_PROVIDER", "deepseek"),
};
/**
* 获取指定提供商的配置
* @param provider - LLM 提供商名称
* @returns 提供商配置对象
*/
export function getLLMKeyByProvider(provider: string) {
const providerKey = provider.toLowerCase();
let apiKey = config.llm[providerKey].apiKey;
if (!apiKey) {
throw new Error(`API key for provider "${provider}" not found. `);
}
return apiKey;
}
export function loadEnv() {
// 获取当前文件所在目录
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = resolve(__dirname, "../..");
// 加载环境变量文件(按优先级)
const nodeEnv = process.env.NODE_ENV || "development";
// 1. 加载 .env.local(最高优先级)
dotenvConfig({ path: resolve(projectRoot, ".env.local"), override: false });
// 2. 加载 .env.{NODE_ENV}
if (nodeEnv !== "development") {
dotenvConfig({
path: resolve(projectRoot, `.env.${nodeEnv}`),
override: false,
});
}
// 3. 加载 .env(默认配置)
dotenvConfig({ path: resolve(projectRoot, ".env"), override: false });
}
/**
* Agent 模块统一导出
*/
export { SimpleAgent } from './SimpleAgent.js';
export type { AgentResult, AgentConfig } from './SimpleAgent.js';
export { MainAgent, SubAgent, createMultiAgentSystem } from './MultiAgent.js';
export type { MainAgentResult, SubAgentResult } from './MultiAgent.js';
/**
* 多智能体系统实现
* 演示主Agent协调多个子Agent完成任务的架构
*/
import { ContextManager, ContextType, Message } from '../context/index.js';
import { ToolManager } from '../tool/ToolManager.js';
import { ILLMService } from '../llm/types/index.js';
import { executeToolLoop } from '../llm/utils/executeToolLoop.js';
import { ExecutionHistoryContext } from '../context/modules/ExecutionHistoryContext.js';
import { eventBus } from '../../evaluation/EventBus.js';
import {
MAIN_AGENT_PROMPT,
SUB_AGENT_A_PROMPT,
SUB_AGENT_B_PROMPT,
} from '../promptManager/index.js';
/**
* 子Agent执行结果
*/
export interface SubAgentResult {
/** 子Agent名称 */
agentName: string;
/** 执行结果 */
result: string;
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* 主Agent执行结果
*/
export interface MainAgentResult {
/** 收集到的 Agent 名称列表 */
agents: string[];
/** 每个 Agent 调用的工具记录 */
tools: Record<string, string[]>;
/** 最终响应 */
finalResponse: string;
/** 子Agent执行记录 */
subAgentResults: SubAgentResult[];
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* 子Agent类
* 使用 executeToolLoop 执行任务(工具可为空)
*/
export class SubAgent {
private name: string;
private llmService: ILLMService;
private systemPrompt: string;
private maxLoops: number;
constructor(
name: string,
llmService: ILLMService,
systemPrompt: string,
maxLoops: number = 5
) {
this.name = name;
this.llmService = llmService;
this.systemPrompt = systemPrompt;
this.maxLoops = maxLoops;
}
/**
* 执行子Agent任务
* @param instruction - 主Agent下发的指令
*/
async run(instruction: string): Promise<SubAgentResult> {
console.log(`\n🤖 子Agent [${this.name}] 开始执行...`);
console.log(`📋 指令: ${instruction}`);
// 发射子Agent调用事件
eventBus.emit('agent:call', { agentName: this.name });
try {
// 初始化上下文管理器
const contextManager = new ContextManager();
await contextManager.init();
// 设置系统提示词
contextManager.add(
this.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置用户输入(来自主Agent的指令)
contextManager.setUserInput(instruction);
// 初始化工具管理器(工具为空,但仍使用 executeToolLoop)
const toolManager = new ToolManager();
// 清空默认工具,使子Agent无工具可用
toolManager.clear();
// 执行工具循环
const loopResult = await executeToolLoop(
this.llmService,
contextManager,
toolManager,
{
maxLoops: this.maxLoops,
agentName: this.name,
}
);
console.log(`✅ 子Agent [${this.name}] 执行完成`);
return {
agentName: this.name,
result: loopResult.result || '',
success: loopResult.success,
error: loopResult.error,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`❌ 子Agent [${this.name}] 执行失败: ${errorMessage}`);
return {
agentName: this.name,
result: '',
success: false,
error: errorMessage,
};
}
}
getName(): string {
return this.name;
}
}
/**
* 主Agent类
* 协调者角色,负责任务分配和结果汇总
* 不直接使用工具,而是通过调用子Agent完成任务
*/
export class MainAgent {
private name: string;
private llmService: ILLMService;
private systemPrompt: string;
private subAgentA: SubAgent;
private subAgentB: SubAgent;
private contextManager: ContextManager;
constructor(llmService: ILLMService, name: string = 'main_agent') {
this.name = name;
this.llmService = llmService;
this.systemPrompt = MAIN_AGENT_PROMPT;
this.contextManager = new ContextManager();
// 初始化两个子Agent
this.subAgentA = new SubAgent(
'researcher',
llmService,
SUB_AGENT_A_PROMPT
);
this.subAgentB = new SubAgent(
'executor',
llmService,
SUB_AGENT_B_PROMPT
);
}
/**
* 执行主Agent
* @param userInput - 用户输入
*/
async run(userInput: string): Promise<MainAgentResult> {
// 初始化上下文管理器
this.contextManager.init();
// 重置事件收集器
eventBus.reset();
// 发射主Agent调用事件
eventBus.emit('agent:call', { agentName: this.name });
console.log(`\n🎯 主Agent [${this.name}] 开始处理任务...`);
console.log(`📝 用户输入: ${userInput}`);
const subAgentResults: SubAgentResult[] = [];
try {
// 1. 调用子Agent A(研究者)
const instructionA = `请针对以下用户需求进行研究分析:\n${userInput}`;
const resultA = await this.subAgentA.run(instructionA);
subAgentResults.push(resultA);
let executionHistoryA = `
子Agent执行完成-${this.subAgentA.getName()}:
主Agent指令: ${instructionA}
输出: ${resultA.result}
`;
this.contextManager.add(executionHistoryA, ContextType.EXECUTION_HISTORY);
// 2. 调用子Agent B(执行者)
const instructionB = `基于以下用户需求,请提供具体的执行方案:\n${userInput}`;
const resultB = await this.subAgentB.run(instructionB);
subAgentResults.push(resultB);
// 记录子Agent B的执行结果
let executionHistoryB = `
子Agent执行完成-${this.subAgentB.getName()}:
主Agent指令: ${instructionB}
输出: ${resultB.result}
`;
this.contextManager.add(executionHistoryB, ContextType.EXECUTION_HISTORY);
// 3. 主Agent汇总结果
const finalResponse = await this.summarizeResults(userInput);
console.log(`\n✅ 主Agent [${this.name}] 任务完成`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse,
subAgentResults,
success: true,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`❌ 主Agent [${this.name}] 执行失败: ${errorMessage}`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: '',
subAgentResults,
success: false,
error: errorMessage,
};
}
}
/**
* 汇总子Agent结果
* 主Agent不使用工具,直接调用LLM进行汇总
*/
private async summarizeResults(userInput: string): Promise<string> {
console.log(`\n📊 主Agent 正在汇总子Agent结果...`);
// 构建汇总上下文
const contextManager = new ContextManager();
await contextManager.init();
// 设置系统提示词
contextManager.add(
this.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置汇总指令
const summarizeInstruction = `基于上述子Agent的研究分析和执行方案,请为用户提供一个综合性的最终答复。
用户原始需求:${userInput}
请整合各子Agent的输出,给出完整、清晰的最终响应。`;
contextManager.setUserInput(summarizeInstruction);
// 获取上下文并调用LLM(不使用工具)
const messages = contextManager.getContext();
const response = await this.llmService.complete(messages, []);
return response.content || '';
}
/**
* 获取执行历史
*/
getExecutionHistory(): Message[] {
return this.contextManager.get(ContextType.EXECUTION_HISTORY);
}
getName(): string {
return this.name;
}
}
/**
* 创建多智能体系统的便捷函数
*/
export function createMultiAgentSystem(llmService: ILLMService): MainAgent {
return new MainAgent(llmService);
}
/**
* 简单 Agent 实现
* 使用 ContextManager + ToolManager + LLMService 实现基本的工具调用循环
*/
import { ContextManager, ContextType } from '../context/index.js';
import { ToolManager } from '../tool/ToolManager.js';
import { ILLMService, ToolLoopResult } from '../llm/types/index.js';
import { executeToolLoop } from '../llm/utils/executeToolLoop.js';
import { eventBus } from '../../evaluation/EventBus.js';
import { SIMPLE_AGENT_PROMPT } from '../promptManager/index.js';
/**
* Agent 执行结果
*/
export interface AgentResult {
/** 收集到的 Agent 名称列表 */
agents: string[];
/** 每个 Agent 调用的工具记录 */
tools: Record<string, string[]>;
/** 最终响应内容 */
finalResponse: string;
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* Agent 配置
*/
export interface AgentConfig {
/** Agent 名称 */
name?: string;
/** 最大工具调用循环次数 */
maxLoops?: number;
/** 系统提示词 */
systemPrompt?: string;
}
/**
* 简单 Agent 类
*
* 使用方式:
* ```typescript
* const agent = new SimpleAgent(llmService, { name: 'my_agent' });
* const result = await agent.run('列出当前目录的文件');
* ```
*/
export class SimpleAgent {
private llmService: ILLMService;
private contextManager: ContextManager;
private toolManager: ToolManager;
private config: Required<AgentConfig>;
constructor(llmService: ILLMService, config?: AgentConfig) {
this.llmService = llmService;
this.contextManager = new ContextManager();
this.toolManager = new ToolManager();
this.config = {
name: config?.name ?? 'simple_agent',
maxLoops: config?.maxLoops ?? 10,
systemPrompt: config?.systemPrompt ?? SIMPLE_AGENT_PROMPT,
};
}
/**
* 执行 Agent
* @param userInput - 用户输入
* @returns Agent 执行结果
*/
async run(userInput: string): Promise<AgentResult> {
// 重置事件收集器
eventBus.reset();
// 发射 Agent 调用事件
eventBus.emit('agent:call', { agentName: this.config.name });
try {
// 初始化上下文管理器
await this.contextManager.init();
// 设置系统提示词
this.contextManager.add(
this.config.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置用户输入
this.contextManager.setUserInput(userInput);
// 执行工具循环
const loopResult = await executeToolLoop(
this.llmService,
this.contextManager,
this.toolManager,
{
maxLoops: this.config.maxLoops,
agentName: this.config.name,
}
);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: loopResult.result || '',
success: loopResult.success,
error: loopResult.error,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`Agent 执行失败: ${errorMessage}`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: '',
success: false,
error: errorMessage,
};
}
}
/**
* 获取 Agent 名称
*/
getName(): string {
return this.config.name;
}
/**
* 获取工具管理器
*/
getToolManager(): ToolManager {
return this.toolManager;
}
/**
* 获取上下文管理器
*/
getContextManager(): ContextManager {
return this.contextManager;
}
}
import { describe, it, expect, beforeEach } from 'vitest';
import { ContextManager } from '../ContextManager.js';
import { ContextType } from '../types.js';
describe('ContextManager 测试', () => {
let contextManager: ContextManager;
beforeEach(async () => {
contextManager = new ContextManager();
await contextManager.init();
});
describe('初始化', () => {
it('应该正确初始化', async () => {
const manager = new ContextManager();
expect(manager.isInitialized()).toBe(false);
await manager.init();
expect(manager.isInitialized()).toBe(true);
});
it('不应该重复初始化', async () => {
await contextManager.init(); // 第二次调用
expect(contextManager.isInitialized()).toBe(true);
});
it('未初始化时应该抛出错误', () => {
const manager = new ContextManager();
expect(() => manager.add('test', ContextType.CONVERSATION)).toThrow(
'ContextManager 未初始化'
);
});
});
describe('添加上下文', () => {
it('应该能添加会话上下文', () => {
const message = { role: 'user', content: '你好' };
contextManager.add(message, ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(1);
});
it('应该能添加工具上下文', () => {
const toolCall = {
id: '1',
name: 'test_tool',
arguments: {},
result: 'success',
};
contextManager.add(toolCall, ContextType.TOOL);
expect(contextManager.getCount(ContextType.TOOL)).toBe(1);
});
it('应该能添加记忆上下文', () => {
const memory = { key: 'user_name', value: '张三' };
contextManager.add(memory, ContextType.MEMORY);
expect(contextManager.getCount(ContextType.MEMORY)).toBe(1);
});
});
describe('获取上下文', () => {
it('应该能获取指定类型的上下文', () => {
const message = { role: 'user', content: '测试消息' };
contextManager.add(message, ContextType.CONVERSATION);
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts.length).toBe(1);
});
it('应该能获取所有上下文', () => {
contextManager.add(
{ role: 'user', content: '你好' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'preference', value: 'dark_mode' },
ContextType.MEMORY
);
const allContexts = contextManager.getAll();
expect(allContexts.length).toBeGreaterThan(0);
});
it('空上下文应该返回空数组', () => {
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts).toEqual([]);
});
});
describe('统计信息', () => {
it('应该正确统计上下文数量', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'test', value: 'value' },
ContextType.MEMORY
);
const stats = contextManager.getStats();
expect(stats.total).toBe(3);
expect(stats.byType[ContextType.CONVERSATION]).toBe(2);
expect(stats.byType[ContextType.MEMORY]).toBe(1);
});
it('应该正确检查上下文是否存在', () => {
expect(contextManager.hasContext(ContextType.CONVERSATION)).toBe(false);
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
expect(contextManager.hasContext(ContextType.CONVERSATION)).toBe(true);
});
it('应该正确检查是否为空', () => {
expect(contextManager.isEmpty()).toBe(true);
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
expect(contextManager.isEmpty()).toBe(false);
});
});
describe('更新和删除', () => {
it('应该能更新指定上下文项', () => {
contextManager.add(
{ role: 'user', content: '原始消息' },
ContextType.CONVERSATION
);
contextManager.update(ContextType.CONVERSATION, 0, {
role: 'user',
content: '更新后的消息',
});
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts[0].content).toContain('更新后的消息');
});
it('应该能删除最后一项', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(2);
contextManager.removeLast(ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(1);
});
it('应该能清空指定类型的上下文', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
contextManager.clear(ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(0);
});
it('应该能重置所有上下文', () => {
contextManager.add(
{ role: 'user', content: '消息' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'test', value: 'value' },
ContextType.MEMORY
);
contextManager.reset();
expect(contextManager.isEmpty()).toBe(true);
});
});
describe('模块访问', () => {
it('应该能获取指定类型的模块实例', () => {
const module = contextManager.getModule(ContextType.CONVERSATION);
expect(module).toBeDefined();
expect(module.type).toBe(ContextType.CONVERSATION);
});
it('应该能获取所有模块', () => {
const modules = contextManager.getAllModules();
expect(modules.size).toBe(5); // 5 种上下文类型
});
});
describe('验证', () => {
it('应该通过验证', () => {
expect(contextManager.validate()).toBe(true);
});
it('未初始化的管理器不应通过验证', () => {
const manager = new ContextManager();
expect(manager.validate()).toBe(false);
});
});
describe('预留接口', () => {
it('压缩检查应该返回 false(未实现)', () => {
expect(contextManager.needsCompression()).toBe(false);
});
it('token 计数应该返回 0(未实现)', async () => {
const count = await contextManager.getTokenCount();
expect(count).toBe(0);
});
it('应该能导出为 JSON', () => {
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
const json = contextManager.toJSON();
expect(json).toBeDefined();
expect(typeof json).toBe('string');
});
});
});
import { describe, it, expect, beforeEach } from 'vitest';
import { ConversationContext } from '../../modules/ConversationContext.js';
import { ContextType } from '../../types.js';
describe('ConversationContext 测试', () => {
let context: ConversationContext;
beforeEach(() => {
context = new ConversationContext();
});
it('应该正确初始化', () => {
expect(context.type).toBe(ContextType.CONVERSATION);
expect(context.isEmpty()).toBe(true);
expect(context.getCount()).toBe(0);
});
describe('添加消息', () => {
it('应该能添加用户消息', () => {
context.addUserMessage('你好');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('user');
expect(messages[0].content).toBe('你好');
});
it('应该能添加助手消息', () => {
context.addAssistantMessage('你好!有什么可以帮助你的吗?');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('assistant');
});
it('应该能添加系统消息', () => {
context.addSystemMessage('你是一个有帮助的助手');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('system');
});
it('应该能添加带图片的消息', () => {
context.addUserMessage('这是什么?', {
url: 'https://example.com/image.jpg',
});
const messages = context.format();
expect(messages[0].content).toBeDefined();
expect(Array.isArray(messages[0].content)).toBe(true);
});
it('应该能添加带工具调用的助手消息', () => {
const toolCalls = [
{
id: 'call_1',
type: 'function',
function: { name: 'get_weather', arguments: '{"city": "北京"}' },
},
];
context.addAssistantMessage('让我查一下天气', toolCalls);
const messages = context.format();
expect(messages[0].tool_calls).toBeDefined();
expect(messages[0].tool_calls?.length).toBe(1);
});
});
describe('格式化', () => {
it('应该正确格式化为 LLM 消息格式', () => {
context.addUserMessage('你好');
context.addAssistantMessage('你好!');
const formatted = context.format();
expect(formatted).toHaveLength(2);
expect(formatted[0].role).toBe('user');
expect(formatted[1].role).toBe('assistant');
});
it('应该正确格式化带 base64 图片的消息', () => {
context.addUserMessage('分析这张图片', {
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
mimeType: 'image/png',
});
const formatted = context.format();
expect(Array.isArray(formatted[0].content)).toBe(true);
});
});
describe('查询方法', () => {
it('应该能获取最后一条消息', () => {
context.addUserMessage('第一条消息');
context.addUserMessage('第二条消息');
const lastMessage = context.getLastMessage();
expect(lastMessage?.content).toBe('第二条消息');
});
it('应该能按角色统计消息数量', () => {
context.addUserMessage('用户消息1');
context.addUserMessage('用户消息2');
context.addAssistantMessage('助手消息');
expect(context.getCountByRole('user')).toBe(2);
expect(context.getCountByRole('assistant')).toBe(1);
});
it('应该能获取所有用户消息', () => {
context.addUserMessage('用户消息1');
context.addAssistantMessage('助手消息');
context.addUserMessage('用户消息2');
const userMessages = context.getUserMessages();
expect(userMessages.length).toBe(2);
expect(userMessages[0].content).toBe('用户消息1');
expect(userMessages[1].content).toBe('用户消息2');
});
it('应该能获取所有助手消息', () => {
context.addUserMessage('用户消息');
context.addAssistantMessage('助手消息1');
context.addAssistantMessage('助手消息2');
const assistantMessages = context.getAssistantMessages();
expect(assistantMessages.length).toBe(2);
});
});
describe('基础操作', () => {
it('应该能清空所有消息', () => {
context.addUserMessage('测试消息');
expect(context.getCount()).toBe(1);
context.clear();
expect(context.getCount()).toBe(0);
expect(context.isEmpty()).toBe(true);
});
it('应该能删除最后一条消息', () => {
context.addUserMessage('消息1');
context.addUserMessage('消息2');
expect(context.getCount()).toBe(2);
context.removeLast();
expect(context.getCount()).toBe(1);
});
it('应该能更新指定消息', () => {
context.addUserMessage('原始消息');
context.update(0, {
role: 'user',
content: '更新后的消息',
});
const messages = context.format();
expect(messages[0].content).toBe('更新后的消息');
});
});
});
import { ContextType, ContextItem, IContext } from '../types.js';
import { logger } from '../../../utils/logger.js';
/**
* 基础上下文抽象类
* 提供所有上下文模块的通用功能
*/
export abstract class BaseContext<T = any> implements IContext<T> {
/** 上下文类型 */
public readonly type: ContextType;
/** 存储上下文项的数组 */
protected items: ContextItem<T>[] = [];
constructor(type: ContextType) {
this.type = type;
logger.debug(`初始化上下文模块: ${type}`);
}
/**
* 添加上下文项
*/
add(content: T, metadata?: Record<string, any>): void {
const item: ContextItem<T> = {
content,
type: this.type,
metadata,
timestamp: Date.now(),
id: this.generateId(),
};
this.items.push(item);
logger.debug(`添加上下文项到 ${this.type}: ${this.items.length} 项`);
}
/**
* 获取所有上下文项
*/
getAll(): ContextItem<T>[] {
return [...this.items];
}
/**
* 获取指定索引的上下文项
*/
get(index: number): ContextItem<T> | undefined {
if (index < 0 || index >= this.items.length) {
logger.warn(`索引 ${index} 超出范围 (0-${this.items.length - 1})`);
return undefined;
}
return this.items[index];
}
/**
* 清空所有上下文
*/
clear(): void {
const count = this.items.length;
this.items = [];
logger.debug(`清空 ${this.type} 上下文: 移除了 ${count} 项`);
}
/**
* 获取上下文数量
*/
getCount(): number {
return this.items.length;
}
/**
* 检查是否为空
*/
isEmpty(): boolean {
return this.items.length === 0;
}
/**
* 移除最后一项
*/
removeLast(): void {
if (this.items.length > 0) {
this.items.pop();
logger.debug(`从 ${this.type} 移除最后一项`);
} else {
logger.warn(`尝试从空的 ${this.type} 上下文移除项`);
}
}
/**
* 更新指定索引的上下文项
*/
update(index: number, content: T, metadata?: Record<string, any>): void {
if (index < 0 || index >= this.items.length) {
logger.error(`无法更新: 索引 ${index} 超出范围`);
throw new Error(`索引 ${index} 超出范围 (0-${this.items.length - 1})`);
}
const oldItem = this.items[index];
this.items[index] = {
...oldItem,
content,
metadata: metadata || oldItem.metadata,
timestamp: Date.now(),
};
logger.debug(`更新 ${this.type} 上下文项 [${index}]`);
}
/**
* 格式化为特定格式(由子类实现)
*/
abstract format(): any[];
/**
* TODO: 后续实现序列化
* 转换为 JSON
*/
toJSON(): string {
// TODO: 实现序列化逻辑
return JSON.stringify({
type: this.type,
items: this.items,
});
}
/**
* TODO: 后续实现序列化
* 从 JSON 恢复
*/
fromJSON(json: string): void {
// TODO: 实现反序列化逻辑
try {
const data = JSON.parse(json);
if (data.type === this.type && Array.isArray(data.items)) {
this.items = data.items;
logger.debug(`从 JSON 恢复 ${this.type} 上下文: ${this.items.length} 项`);
} else {
throw new Error('无效的 JSON 数据格式');
}
} catch (error: any) {
logger.error(`从 JSON 恢复失败: ${error.message}`);
throw error;
}
}
/**
* 生成唯一 ID
*/
protected generateId(): string {
return `${this.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
import { ContextType, ContextStats, IContext, Message } from "./types.js";
import { ConversationContext } from "./modules/ConversationContext.js";
import { ToolMessageSequenceContext } from "./modules/ToolMessageSequenceContext.js";
import { MemoryContext } from "./modules/MemoryContext.js";
import { SystemPromptContext } from "./modules/SystemPromptContext.js";
import { StructuredOutputContext } from "./modules/StructuredOutputContext.js";
import { RelevantContext } from "./modules/RelevantContext.js";
import { ExecutionHistoryContext } from "./modules/ExecutionHistoryContext.js";
/**
* 上下文管理器
* 负责统一管理所有类型的上下文模块
*/
export class ContextManager {
/** 存储所有上下文模块的映射 */
private contexts: Map<ContextType, IContext> = new Map();
/** 用户输入 */
private userInput: string = "";
/** 是否已初始化 */
private initialized: boolean = false;
constructor() {}
/**
* 初始化所有上下文模块
*/
async init(): Promise<void> {
if (this.initialized) {
return;
}
// 初始化所有上下文模块
this.contexts.set(
ContextType.CONVERSATION_HISTORY,
new ConversationContext()
);
this.contexts.set(
ContextType.TOOL_MESSAGE_SEQUENCE,
new ToolMessageSequenceContext()
);
this.contexts.set(ContextType.MEMORY, new MemoryContext());
this.contexts.set(ContextType.SYSTEM_PROMPT, new SystemPromptContext());
this.contexts.set(
ContextType.STRUCTURED_OUTPUT,
new StructuredOutputContext()
);
this.contexts.set(ContextType.RELEVANT_CONTEXT, new RelevantContext());
this.contexts.set(ContextType.EXECUTION_HISTORY, new ExecutionHistoryContext());
this.initialized = true;
}
/**
* 检查是否已初始化
*/
isInitialized(): boolean {
return this.initialized;
}
/** 设置用户输入 */
setUserInput(userInput: string): void {
this.userInput = userInput;
}
/**
* 统一的添加上下文方法
* @param content - 上下文内容
* @param type - 上下文类型
* @param metadata - 可选的元数据
*
* @example
* // SYSTEM_PROMPT - 字符串
* contextManager.add('你是一个助手', ContextType.SYSTEM_PROMPT);
*
* // MEMORY - { key, value } 格式
* contextManager.add({ key: 'user_name', value: '张三' }, ContextType.MEMORY);
*
* // CONVERSATION_HISTORY - { role, content } 格式
* contextManager.add({ role: 'user', content: '你好' }, ContextType.CONVERSATION_HISTORY);
*
* // TOOL_MESSAGE_SEQUENCE - Message 对象
* contextManager.add({ role: 'assistant', content: '...', tool_calls: [...] }, ContextType.TOOL_MESSAGE_SEQUENCE);
*
* // STRUCTURED_OUTPUT - 字符串
* contextManager.add('json', ContextType.STRUCTURED_OUTPUT);
*
* // RELEVANT_CONTEXT - { key, value } 格式
* contextManager.add({ key: 'scene', value: '客服场景' }, ContextType.RELEVANT_CONTEXT);
*/
add(content: any, type: ContextType, metadata?: Record<string, any>): void {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
this.validateContent(content, type);
context.add(content, metadata);
}
/**
* 校验 content 格式是否匹配 type
*/
private validateContent(content: any, type: ContextType): void {
switch (type) {
case ContextType.SYSTEM_PROMPT:
case ContextType.STRUCTURED_OUTPUT:
case ContextType.EXECUTION_HISTORY:
if (typeof content !== "string") {
throw new Error(`${type} 需要字符串类型`);
}
break;
case ContextType.TOOL_MESSAGE_SEQUENCE:
case ContextType.CONVERSATION_HISTORY:
if (!content?.role || content?.content === undefined) {
throw new Error(`${type} 需要 { role, content } 格式的 Message 对象`);
}
break;
case ContextType.MEMORY:
case ContextType.RELEVANT_CONTEXT:
if (!content?.key || content?.value === undefined) {
throw new Error(`${type} 需要 { key, value } 格式`);
}
break;
}
}
/**
* 获取指定类型的上下文
* @param type - 上下文类型
* @returns 格式化的上下文数据
*/
get(type: ContextType): any[] {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context.format();
}
/**
* 获取完整上下文(供 LLM 调用使用)
*
* 自动检测并组装所有可用的上下文类型:
* - system消息(systemPrompt + structuredOutput + relevantContext + memory + 会话历史摘要)
* - 用户输入
* - 工具消息序列
*
* @returns Message[] 格式的上下文数组
*/
getContext(): Message[] {
this.ensureInitialized();
const messages: Message[] = [];
// 1. system消息(包含历史摘要)
const systemMessage = this.buildSystemMessage();
if (systemMessage) {
messages.push(systemMessage);
}
// 2. 用户输入(当前请求)
if (this.userInput) {
messages.push({ role: "user", content: this.userInput });
}
// 3. 工具消息序列
const toolContext = this.contexts.get(ContextType.TOOL_MESSAGE_SEQUENCE);
if (toolContext && !toolContext.isEmpty()) {
const toolMessages = toolContext.format();
if (toolMessages && toolMessages.length > 0) {
messages.push(...toolMessages);
}
}
// 4. 执行历史
const executionHistory = this.getModule<ExecutionHistoryContext>(
ContextType.EXECUTION_HISTORY
);
const executionHistoryMessages = executionHistory.format();
if (executionHistoryMessages.length > 0) {
messages.push(...executionHistoryMessages);
}
return messages;
}
/**
* 构建system消息(私有方法)
* 自动拼接: systemPrompt + structuredOutput + relevantContext + memory + 会话历史摘要
*
* @returns Message 对象或 null
*/
private buildSystemMessage(): Message | null {
const parts: string[] = [];
// 1. 系统提示词(必需)
const systemPromptContext = this.getModule<SystemPromptContext>(
ContextType.SYSTEM_PROMPT
);
const systemPrompts = systemPromptContext.formatNormal();
if (systemPrompts) {
parts.push(systemPrompts);
}
// 2. 结构化输出要求
const structuredOutputContext = this.getModule<StructuredOutputContext>(
ContextType.STRUCTURED_OUTPUT
);
const structuredOutput = structuredOutputContext.format();
if (structuredOutput.length > 0) {
parts.push("\n【结构化输出要求】");
parts.push(structuredOutput[0]);
}
// 3. 相关上下文
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
const relevantInfo = relevantContext.format();
if (relevantInfo.length > 0) {
parts.push("\n【相关上下文】");
parts.push(relevantInfo.join("\n"));
}
// 4. 用户记忆
const memoryContext = this.getModule<MemoryContext>(ContextType.MEMORY);
const memories = memoryContext.format();
if (memories.length > 0) {
parts.push("\n【用户记忆】");
parts.push(memories.join("\n"));
}
// 5. 会话历史摘要
const historySummary = this.formatConversationHistoryForPrompt();
if (historySummary) {
parts.push(historySummary);
}
// 如果没有任何内容,返回null
if (parts.length === 0) {
return null;
}
return {
role: "system",
content: parts.join("\n"),
};
}
/**
* 将会话历史格式化为简洁列表(用于系统提示词)
* 只取最近15条,以简洁形式呈现
*
* @returns 格式化的历史摘要字符串,或 null
*/
private formatConversationHistoryForPrompt(): string | null {
const conversationContext = this.contexts.get(
ContextType.CONVERSATION_HISTORY
);
if (!conversationContext || conversationContext.isEmpty()) {
return null;
}
const history = conversationContext.format();
if (!history || history.length === 0) {
return null;
}
// 取最近15条
const recentHistory = history.slice(-15);
const lines: string[] = [];
lines.push("\n## 【历史对话参考】");
lines.push("");
lines.push("最近对话记录:");
recentHistory.forEach((msg, idx) => {
const role = msg.role === "user" ? "[用户]" : "[助手]";
let summary = "";
try {
const content = JSON.parse(msg.content as string);
if (msg.role === "user") {
summary = content.text || msg.content;
} else {
summary =
content.action === "finish"
? `已完成: ${(content.result || "").slice(0, 50)}...`
: content.action || (msg.content as string).slice(0, 50);
}
} catch {
summary =
typeof msg.content === "string"
? msg.content.slice(0, 50)
: String(msg.content);
}
lines.push(`${idx + 1}. ${role} ${summary}`);
});
return lines.join("\n");
}
/**
* 添加相关上下文
*/
addRelevantContext(key: string, value: any, description?: string): void {
this.ensureInitialized();
this.add({ key, value, description }, ContextType.RELEVANT_CONTEXT);
}
/**
* 获取相关上下文值
*/
getRelevantContextValue(key: string): any {
this.ensureInitialized();
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
return relevantContext.getValue(key);
}
/**
* 更新相关上下文
*/
updateRelevantContext(key: string, value: any): void {
this.ensureInitialized();
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
relevantContext.updateValue(key, value);
}
/**
* 获取指定类型上下文的数量
*/
getCount(type: ContextType): number {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context.getCount();
}
/**
* 获取所有上下文的统计信息
*/
getStats(): ContextStats {
this.ensureInitialized();
let total = 0;
const byType: Record<string, number> = {};
this.contexts.forEach((context, type) => {
const count = context.getCount();
total += count;
byType[type] = count;
});
return {
total,
byType,
tokenCount: undefined,
};
}
/**
* 检查指定类型上下文是否存在
*/
hasContext(type: ContextType): boolean {
this.ensureInitialized();
const context = this.contexts.get(type);
return context ? !context.isEmpty() : false;
}
/**
* 检查上下文是否为空
*/
isEmpty(): boolean {
this.ensureInitialized();
return Array.from(this.contexts.values()).every((context) =>
context.isEmpty()
);
}
/**
* 清空指定类型的上下文
*/
clear(type: ContextType): void {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
context.clear();
}
/**
* 重置所有上下文(清空状态)
*/
reset(): void {
this.ensureInitialized();
this.contexts.forEach((context) => {
context.clear();
});
this.userInput = "";
}
/**
* 获取指定类型的上下文模块实例
*/
getModule<T extends IContext>(type: ContextType): T {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context as T;
}
/**
* 打印当前上下文状态(调试用)
*/
debug(): void {
this.ensureInitialized();
console.log("=== ContextManager 状态 ===");
console.log(`已初始化: ${this.initialized}`);
console.log(`总计: ${this.getStats().total} 项`);
console.log("\n各类型统计:");
this.contexts.forEach((context, type) => {
console.log(` ${type}: ${context.getCount()} 项`);
});
console.log("========================\n");
}
/**
* 确保已初始化
*/
private ensureInitialized(): void {
if (!this.initialized) {
throw new Error("ContextManager 未初始化,请先调用 init() 方法");
}
}
}
/**
* 上下文管理模块统一导出
*/
// 类型定义
export * from './types.js';
// 基础类
export { BaseContext } from './base/BaseContext.js';
// 核心管理器
export { ContextManager } from './ContextManager.js';
// 具体上下文模块
export { ConversationContext } from './modules/ConversationContext.js';
export { ToolMessageSequenceContext } from './modules/ToolMessageSequenceContext.js';
export { MemoryContext } from './modules/MemoryContext.js';
export { SystemPromptContext } from './modules/SystemPromptContext.js';
export { StructuredOutputContext } from './modules/StructuredOutputContext.js';
export { RelevantContext } from './modules/RelevantContext.js';
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, ConversationMessage } from '../types.js';
/**
* 会话上下文管理类
* 负责管理用户和助手之间的对话历史
*/
export class ConversationContext extends BaseContext<ConversationMessage> {
constructor() {
super(ContextType.CONVERSATION_HISTORY);
}
/**
* 格式化为 LLM 消息格式
* 转换为标准的 OpenAI 消息格式
*/
format(): any[] {
return this.items.map((item) => {
const message = item.content;
const formatted: any = {
role: message.role,
content: message.content,
};
// 添加图片数据(如果存在)
if (message.imageData) {
if (message.imageData.url) {
formatted.content = [
{ type: 'text', text: message.content },
{
type: 'image_url',
image_url: { url: message.imageData.url },
},
];
} else if (message.imageData.base64) {
formatted.content = [
{ type: 'text', text: message.content },
{
type: 'image_url',
image_url: {
url: `data:${message.imageData.mimeType || 'image/png'};base64,${message.imageData.base64}`,
},
},
];
}
}
// 添加工具调用(如果存在)
if (message.toolCalls && message.toolCalls.length > 0) {
formatted.tool_calls = message.toolCalls;
}
// 添加工具调用 ID(如果是工具消息)
if (message.role === 'tool' && item.metadata?.toolCallId) {
formatted.tool_call_id = item.metadata.toolCallId;
}
return formatted;
});
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, Message } from '../types.js';
/**
* 执行历史上下文管理类
* 专门用于主Agent记录子Agent的执行结果
* 存储字符串,格式化时转换为 user 消息
*/
export class ExecutionHistoryContext extends BaseContext<string> {
constructor() {
super(ContextType.EXECUTION_HISTORY);
}
/**
* 格式化为 Message 数组
* 将存储的字符串转换为 user 角色的消息
*/
format(): Message[] {
return this.items.map(item => ({
role: 'user' as const,
content: item.content,
}));
}
/**
* 获取最后一次执行记录(字符串形式)
*/
getLastExecutionRecord(): string | undefined {
if (this.items.length === 0) return undefined;
return this.items[this.items.length - 1].content;
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, MemoryItem } from '../types.js';
/**
* 用户记忆上下文管理类
* 负责管理用户偏好、历史信息等长期记忆
*/
export class MemoryContext extends BaseContext<MemoryItem> {
/** 用于快速查找的键值映射 */
private keyMap: Map<string, number> = new Map();
constructor() {
super(ContextType.MEMORY);
}
/**
* 重写 add 方法以维护 keyMap
*/
add(content: MemoryItem, metadata?: Record<string, any>): void {
// 检查是否已存在
const existingIndex = this.keyMap.get(content.key);
if (existingIndex !== undefined) {
// 更新已存在的记忆
this.update(existingIndex, content);
} else {
// 添加新记忆
super.add(content, metadata);
this.keyMap.set(content.key, this.items.length - 1);
}
}
/**
* 获取指定键的记忆值
*/
getMemory(key: string): any | undefined {
const index = this.keyMap.get(key);
if (index !== undefined) {
return this.items[index]?.content.value;
}
return undefined;
}
/**
* 检查是否存在指定键的记忆
*/
hasMemory(key: string): boolean {
return this.keyMap.has(key);
}
/**
* 格式化为自然语言描述
*/
format(): string[] {
// 按优先级排序(优先级越小越靠前)
const sortedItems = [...this.items].sort((a, b) => {
const priorityA = a.content.priority ?? 999;
const priorityB = b.content.priority ?? 999;
return priorityA - priorityB;
});
return sortedItems.map((item) => {
const memory = item.content;
let text = `${memory.key}: ${JSON.stringify(memory.value)}`;
if (memory.description) {
text += ` (${memory.description})`;
}
return text;
});
}
/**
* 清空所有记忆
*/
clear(): void {
super.clear();
this.keyMap.clear();
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType } from '../types.js';
/**
* 相关上下文项
*/
export interface RelevantContextItem {
/** 上下文键(如 "known_roles", "scene_info") */
key: string;
/** 上下文值(灵活类型) */
value: any;
/** 描述 */
description?: string;
}
/**
* 相关上下文管理类
*
* 用于存储问题相关的背景知识,这些知识:
* - 不属于用户记忆(非用户个人信息)
* - 不属于会话历史(非对话记录)
* - 不属于工具输出(非工具执行结果)
* - 是为了解决当前问题而需要的临时背景信息
*/
export class RelevantContext extends BaseContext<RelevantContextItem> {
constructor() {
super(ContextType.RELEVANT_CONTEXT);
}
/**
* 获取指定键的值
*/
getValue(key: string): any | undefined {
const item = this.items.find((item) => item.content.key === key);
return item?.content.value;
}
/**
* 更新指定键的值
*/
updateValue(key: string, value: any): void {
const index = this.items.findIndex((item) => item.content.key === key);
if (index !== -1) {
const existingItem = this.items[index].content;
this.update(index, { ...existingItem, value });
}
}
/**
* 检查是否存在指定键
*/
hasKey(key: string): boolean {
return this.items.some((item) => item.content.key === key);
}
/**
* 格式化为文本数组(用于拼接到 prompt)
*/
format(): string[] {
return this.items.map((item) => {
const { key, value, description } = item.content;
const desc = description ? ` (${description})` : '';
// 根据值类型格式化
if (Array.isArray(value)) {
return `${key}${desc}: ${value.join('、')}`;
} else if (typeof value === 'object' && value !== null) {
return `${key}${desc}: ${JSON.stringify(value)}`;
} else {
return `${key}${desc}: ${value}`;
}
});
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, StructuredOutputSchema } from '../types.js';
/**
* 结构化输出上下文管理类
* 负责管理结构化输出的 Schema 定义
*/
export class StructuredOutputContext extends BaseContext<StructuredOutputSchema> {
constructor() {
super(ContextType.STRUCTURED_OUTPUT);
}
/**
* 格式化为 LLM 理解的格式
* 转换为 OpenAI 的 response_format 格式
*/
format(): any[] {
if (this.isEmpty()) {
return [];
}
// item.content 就是字符串(如 'json')
const latestItem = this.items[this.items.length - 1];
const type = latestItem.content;
return [
{
type: type,
},
];
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, SystemPromptItem } from '../types.js';
/**
* 系统提示词上下文管理类
* 负责管理系统级提示词,支持多段提示词和优先级
*/
export class SystemPromptContext extends BaseContext<SystemPromptItem> {
constructor() {
super(ContextType.SYSTEM_PROMPT);
}
/**
* 格式化为单一系统消息
* 合并所有启用的提示词,按优先级排序
*/
format(): any[] {
if (this.items.length === 0) {
return [];
}
// item.content 就是字符串
const combinedContent = this.items.map((item) => item.content).join('\n\n');
return [
{
role: 'system',
content: combinedContent,
},
];
}
/**
* 格式化为普通字符串(用于合并到系统消息中)
* @returns 合并后的提示词文本,或 null
*/
formatNormal(): string | null {
if (this.items.length === 0) {
return null;
}
return this.items.map((item) => item.content).join('\n\n');
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, Message } from '../types.js';
/**
* 工具消息序列上下文管理类
* 用于存储工具调用循环中的消息序列
* 顺序: assistant (含tool_calls) → tool → assistant → tool → ...
*/
export class ToolMessageSequenceContext extends BaseContext<Message> {
constructor() {
super(ContextType.TOOL_MESSAGE_SEQUENCE);
}
/**
* 格式化为 Message 数组 (API 格式)
*/
format(): Message[] {
return this.items.map((item) => item.content);
}
}
/**
* 上下文管理系统的核心类型定义
*/
/**
* 上下文类型枚举
*/
export enum ContextType {
/** 会话历史上下文 */
CONVERSATION_HISTORY = 'conversation_history',
/** 工具消息序列上下文 */
TOOL_MESSAGE_SEQUENCE = 'tool_message_sequence',
/** 用户记忆上下文 */
MEMORY = 'memory',
/** 系统提示词上下文 */
SYSTEM_PROMPT = 'system_prompt',
/** 结构化输出上下文 */
STRUCTURED_OUTPUT = 'structured_output',
/** 相关上下文 */
RELEVANT_CONTEXT = 'relevant_context',
/** 执行历史上下文 */
EXECUTION_HISTORY = 'execution_history',
}
/**
* 单个上下文项的结构
*/
export interface ContextItem<T = any> {
/** 上下文内容 */
content: T;
/** 上下文类型 */
type: ContextType;
/** 元数据 */
metadata?: Record<string, any>;
/** 时间戳 */
timestamp: number;
/** 唯一标识(可选) */
id?: string;
}
/**
* 统计信息结构
*/
export interface ContextStats {
/** 总计数量 */
total: number;
/** 按类型分组的数量 */
byType: Record<string, number>;
/** 总 token 数(预留) */
tokenCount?: number;
}
/**
* 图片数据
*/
export interface ImageData {
url?: string;
base64?: string;
mimeType?: string;
}
/**
* 消息角色
*/
export type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
/**
* 标准消息结构(用于 LLM API 调用)
*/
export interface Message {
role: MessageRole;
content: string;
tool_calls?: any[];
tool_call_id?: string;
name?: string;
}
/**
* 会话消息结构
*/
export interface ConversationMessage {
role: MessageRole;
content: string;
imageData?: ImageData;
toolCalls?: any[];
}
/**
* 用户记忆项结构
*/
export interface MemoryItem {
/** 记忆键 */
key: string;
/** 记忆值 */
value: any;
/** 描述 */
description?: string;
/** 优先级 */
priority?: number;
}
/**
* 系统提示词项结构
*/
export interface SystemPromptItem {
/** 提示词内容 */
content: string;
/** 优先级(数字越小优先级越高) */
priority?: number;
/** 是否启用 */
enabled?: boolean;
}
/**
* 结构化输出 Schema
*/
export interface StructuredOutputSchema {
/** Schema 类型(如 json_schema) */
type: string;
/** Schema 定义 */
schema: Record<string, any>;
/** 输出格式(json、yaml 等) */
format?: string;
/** 是否严格模式 */
strict?: boolean;
}
/**
* 基础上下文接口
* 所有具体上下文模块必须实现的方法
*/
export interface IContext<T = any> {
/** 上下文类型 */
readonly type: ContextType;
/**
* 添加上下文项
* @param content - 内容
* @param metadata - 元数据
*/
add(content: T, metadata?: Record<string, any>): void;
/**
* 获取所有上下文项
*/
getAll(): ContextItem<T>[];
/**
* 获取指定索引的上下文项
* @param index - 索引
*/
get(index: number): ContextItem<T> | undefined;
/**
* 清空所有上下文
*/
clear(): void;
/**
* 获取上下文数量
*/
getCount(): number;
/**
* 检查是否为空
*/
isEmpty(): boolean;
/**
* 格式化为特定格式(由子类实现)
*/
format(): any[];
/**
* 移除最后一项
*/
removeLast(): void;
/**
* 更新指定索引的上下文项
* @param index - 索引
* @param content - 新内容
* @param metadata - 新元数据
*/
update(index: number, content: T, metadata?: Record<string, any>): void;
/**
* 转换为 JSON
*/
toJSON(): string;
/**
* 从 JSON 恢复
*/
fromJSON(json: string): void;
}
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
extractApiKey,
getBaseURL,
getDefaultContextWindow,
} from '../utils/helpers.js';
import { LLMConfig } from '../types/index.js';
describe('辅助函数测试', () => {
describe('extractApiKey', () => {
it('应该优先使用用户传递的 API Key', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
apiKey: 'user-provided-key',
};
expect(extractApiKey(config)).toBe('user-provided-key');
});
it('应该从环境变量获取 API Key(当用户未传递时)', () => {
// 注意:这个测试依赖于实际的环境变量加载
// 如果 .env 中有 DEEPSEEK_API_KEY,则会返回该值
const config: LLMConfig = {
provider: 'deepseek',
model: 'deepseek-chat',
};
// 由于依赖环境变量,我们只测试不抛出错误即可
// 或者可以 mock getLLMConfig 函数
const result = extractApiKey(config);
expect(typeof result).toBe('string');
});
it('无需 API Key 的提供商应该返回 not-required', () => {
const config: LLMConfig = {
provider: 'ollama',
model: 'llama2',
};
expect(extractApiKey(config)).toBe('not-required');
});
it('缺少 API Key 时应该抛出清晰的错误信息', () => {
const config: LLMConfig = {
provider: 'unknown-provider',
model: 'some-model',
};
expect(() => extractApiKey(config)).toThrow(
/API key for provider "unknown-provider" not found/
);
});
});
describe('getBaseURL', () => {
it('应该优先使用用户传递的 baseURL', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
baseURL: 'https://custom.api.com',
};
expect(getBaseURL(config)).toBe('https://custom.api.com');
});
it('应该从环境变量获取 baseURL(当用户未传递时)', () => {
const config: LLMConfig = {
provider: 'deepseek',
model: 'deepseek-chat',
};
// 会返回环境变量或默认值
const result = getBaseURL(config);
expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
it('应该返回默认 Base URL', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
};
expect(getBaseURL(config)).toBe('https://api.openai.com/v1');
});
it('应该返回 OpenRouter 的默认 URL', () => {
const config: LLMConfig = {
provider: 'openrouter',
model: 'gpt-4',
};
expect(getBaseURL(config)).toBe('https://openrouter.ai/api/v1');
});
it('应该返回 Ollama 的默认 URL', () => {
const config: LLMConfig = {
provider: 'ollama',
model: 'llama2',
};
expect(getBaseURL(config)).toBe('http://localhost:11434/v1');
});
it('未知提供商应该抛出错误', () => {
const config: LLMConfig = {
provider: 'unknown-provider',
model: 'some-model',
};
expect(() => getBaseURL(config)).toThrow(
/No base URL found for provider "unknown-provider"/
);
});
});
describe('getDefaultContextWindow', () => {
it('应该返回 GPT-4o 的上下文窗口', () => {
expect(getDefaultContextWindow('openai', 'gpt-4o')).toBe(128000);
});
it('应该返回默认上下文窗口', () => {
expect(getDefaultContextWindow('openai', 'unknown-model')).toBe(8192);
});
it('应该返回提供商的默认值', () => {
expect(getDefaultContextWindow('anthropic')).toBe(200000);
});
it('未知提供商应该返回默认值 8192', () => {
expect(getDefaultContextWindow('unknown-provider')).toBe(8192);
});
});
});
import { ILLMService, LLMConfig, UnifiedToolManager } from './types/index.js';
import { DeepSeekService } from './services/DeepSeekService.js';
import { extractApiKey, getBaseURL } from './utils/helpers.js';
/**
* 公共工厂函数:创建 LLM 服务实例(支持可选的工具管理器)
*
* @param config - LLM 配置(包括 provider、model、apiKey 等)
* @param toolManager - 可选的工具管理器实例
* @param eventManager - 可选的事件管理器实例
* @returns Promise<ILLMService> 实例
*
* @example
* ```typescript
* import { ToolManager } from '../tool/ToolManager.js';
*
* const toolManager = new ToolManager();
* const service = await createLLMService(
* {
* provider: 'deepseek',
* model: 'deepseek-chat',
* apiKey: 'your-api-key',
* maxIterations: 10
* },
* toolManager
* );
*
* // 使用服务
* const response = await service.complete(messages, tools);
* ```
*/
export async function createLLMService(
config: LLMConfig,
toolManager?: UnifiedToolManager,
eventManager?: any
): Promise<ILLMService> {
// 1. 创建服务实例
const service = await _createLLMService(config, toolManager);
// 2. 设置事件管理器(如果提供且服务支持)
if (eventManager && typeof (service as any).setEventManager === 'function') {
(service as any).setEventManager(eventManager);
}
return service;
}
/**
* 内部函数:创建 LLM 服务实例
*/
async function _createLLMService(
config: LLMConfig,
toolManager?: UnifiedToolManager
): Promise<ILLMService> {
// 1. 提取和验证 API Key
const apiKey = extractApiKey(config);
// 2. 获取 Base URL
const baseURL = getBaseURL(config);
// 3. 根据 provider 创建服务
switch (config.provider.toLowerCase()) {
case 'deepseek': {
// 使用 ES 动态导入 OpenAI SDK
const { default: OpenAI } = await import('openai');
const openai = new OpenAI({ apiKey, baseURL });
return new DeepSeekService(
openai,
config.model || 'deepseek-chat',
{
baseURL,
maxRetries: 3,
toolManager,
maxIterations: config.maxIterations || 5,
}
);
}
// 🟡 可扩展:其他提供商
// case 'openai':
// case 'anthropic':
// case 'qwen':
// case 'siliconflow':
// case 'openrouter':
default:
throw new Error(`Unsupported LLM provider: ${config.provider}`);
}
}
// 导出核心类型
export * from './types/index.js';
// 导出服务实现
export { DeepSeekService } from './services/DeepSeekService.js';
// 导出工厂方法
export { createLLMService } from './factory.js';
// 导出辅助函数
export {
extractApiKey,
getBaseURL,
getDefaultContextWindow,
sleep,
} from './utils/helpers.js';
import OpenAI from 'openai';
import {
ILLMService,
LLMResponse,
LLMChatOptions,
ImageData,
ToolSet,
UnifiedToolManager,
} from '../types/index.js';
import { logger } from '../../../utils/logger.js';
import { sleep } from '../utils/helpers.js';
/**
* DeepSeek LLM 服务
* 使用 OpenAI SDK(DeepSeek 兼容 OpenAI API)
*
* 提供两种使用方式:
* 1. complete() - 上下文补全(推荐)
* 2. generate() - 内置工具调用循环(可选)
*/
export class DeepSeekService implements ILLMService {
private client: OpenAI;
private model: string;
private maxRetries: number;
private toolManager?: UnifiedToolManager;
private maxIterations: number;
constructor(
openai: OpenAI,
model: string,
options?: {
baseURL?: string;
maxRetries?: number;
toolManager?: UnifiedToolManager;
maxIterations?: number;
}
) {
this.client = openai;
this.model = model;
this.maxRetries = options?.maxRetries || 3;
this.toolManager = options?.toolManager;
this.maxIterations = options?.maxIterations || 5;
logger.debug(`初始化 DeepSeekService: model=${model}`);
}
/**
* 核心方法:上下文补全
* 接收格式化的上下文(消息历史),返回模型响应
* @param messages - 上下文消息列表
* @param tools - 可用的工具定义列表
* @param options - 生成参数(temperature, maxTokens 等)
* @returns 模型响应(包含内容、工具调用、使用统计)
*/
async complete(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<LLMResponse> {
let attempt = 0;
while (attempt < this.maxRetries) {
attempt++;
try {
logger.debug(
`调用 DeepSeek API (尝试 ${attempt}/${this.maxRetries}): ${messages.length} 条消息, ${tools?.length || 0} 个工具`
);
const response = await this.client.chat.completions.create({
model: this.model,
messages,
tools: tools && tools.length > 0 ? tools : undefined,
tool_choice: options?.toolChoice,
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
response_format: options?.responseFormat,
});
const message = response.choices[0]?.message;
if (!message) {
throw new Error('DeepSeek API 返回空响应');
}
const result: LLMResponse = {
content: message.content || '',
toolCalls: message.tool_calls as any,
finishReason: response.choices[0]?.finish_reason,
usage: response.usage
? {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens,
}
: undefined,
};
logger.debug(
`DeepSeek 响应: ${result.content.slice(0, 100)}${result.content.length > 100 ? '...' : ''}, 工具调用: ${result.toolCalls?.length || 0}`
);
return result;
} catch (error: any) {
logger.error(
`DeepSeek API 调用失败 (${attempt}/${this.maxRetries}): ${error.message}`
);
if (attempt >= this.maxRetries) {
throw new Error(`DeepSeek API 调用失败: ${error.message}`);
}
// 指数退避
const delay = 500 * attempt;
logger.debug(`等待 ${delay}ms 后重试...`);
await sleep(delay);
}
}
throw new Error('Unreachable');
}
/**
* 简单对话:无工具,单次调用
* @param userInput - 用户输入
* @param systemPrompt - 可选的系统提示词
*/
async simpleChat(userInput: string, systemPrompt?: string): Promise<string> {
const messages: any[] = [];
if (systemPrompt) {
messages.push({ role: 'system', content: systemPrompt });
}
messages.push({ role: 'user', content: userInput });
const response = await this.complete(messages);
return response.content;
}
/**
* 获取配置信息
*/
getConfig(): { provider: string; model: string } {
return {
provider: 'deepseek',
model: this.model,
};
}
/**
* 完整方法:支持工具调用循环
* 需要在构造时传入 toolManager
* @param userInput - 用户输入
* @param imageData - 可选的图片数据
* @param _stream - 是否流式输出(暂未实现)
*/
async generate(
userInput: string,
imageData?: ImageData,
_stream?: boolean
): Promise<string> {
if (!this.toolManager) {
throw new Error(
'generate() 方法需要 toolManager,请在构造时传入或使用 chat() 方法'
);
}
// 初始化消息列表
const messages: any[] = [
{
role: 'system',
content: '你是一个有帮助的 AI 助手,可以使用工具来完成任务。',
},
];
// 添加用户消息
const userMessage: any = { role: 'user', content: userInput };
if (imageData) {
userMessage.content = [
{ type: 'text', text: userInput },
imageData.url
? { type: 'image_url', image_url: { url: imageData.url } }
: {
type: 'image_url',
image_url: {
url: `data:${imageData.mimeType || 'image/png'};base64,${imageData.base64}`,
},
},
];
}
messages.push(userMessage);
// 获取可用工具
const availableTools = await this.toolManager.getToolsForProvider(
'deepseek'
);
// 工具调用循环
let iteration = 0;
while (iteration < this.maxIterations) {
iteration++;
logger.debug(`工具调用迭代: ${iteration}/${this.maxIterations}`);
// 调用 LLM
const response = await this.complete(messages, availableTools, {
toolChoice: iteration === 1 ? 'auto' : undefined,
});
// 无工具调用,返回结果
if (!response.toolCalls || response.toolCalls.length === 0) {
logger.debug('无工具调用,返回最终结果');
return response.content;
}
// 记录思考内容
if (response.content) {
logger.info(`💭 助手思考: ${response.content}`);
}
// 添加助手消息(包含工具调用)
messages.push({
role: 'assistant',
content: response.content || null,
tool_calls: response.toolCalls,
});
// 执行所有工具调用
for (const toolCall of response.toolCalls) {
if (toolCall.type !== 'function' || !toolCall.function) {
continue;
}
logger.info(`🔧 使用工具: ${toolCall.function.name}`);
try {
const args = JSON.parse(toolCall.function.arguments);
const result = await this.toolManager.executeTool(
toolCall.function.name,
args
);
logger.info(`✅ 工具执行成功: ${JSON.stringify(result).slice(0, 200)}`);
// 添加工具执行结果
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
} catch (error: any) {
logger.error(`❌ 工具执行失败: ${error.message}`);
// 添加错误结果
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify({ error: error.message }),
});
}
}
}
throw new Error(`达到最大迭代次数 (${this.maxIterations})`);
}
/**
* 获取所有可用工具
*/
async getAllTools(): Promise<ToolSet> {
if (!this.toolManager) {
return {};
}
return this.toolManager.getAllTools();
}
/**
* 流式对话(预留接口)
* TODO: 实现流式响应
*/
async chatStream(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<AsyncIterable<LLMResponse>> {
throw new Error('流式响应暂未实现');
}
}
/** 图片数据 */
export interface ImageData {
url?: string;
base64?: string;
mimeType?: string;
}
/** 工具参数定义 */
export interface ToolParameters {
type: 'object';
properties: Record<string, any>;
required?: string[];
}
/** 工具定义 */
export interface Tool {
name: string;
description: string;
parameters: ToolParameters;
}
/** 工具集合 */
export type ToolSet = Record<string, Tool>;
/** 工具调用 */
export interface ToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string;
};
}
/** LLM 配置 */
export interface LLMConfig {
provider: string;
model: string;
apiKey?: string;
maxIterations?: number;
baseURL?: string;
qwenOptions?: Record<string, any>;
aws?: Record<string, any>;
azure?: Record<string, any>;
}
/** MCP Manager 接口 (占位符) */
export interface MCPManager {
getAllTools(): Promise<ToolSet>;
executeTool(name: string, args: any): Promise<any>;
}
/** Context Manager - 导入真实实现 */
export { ContextManager } from '../../context/index.js';
/** Unified Tool Manager 接口 (占位符) */
export interface UnifiedToolManager {
getToolsForProvider(provider: string): Promise<any[]>;
getAllTools(): Promise<ToolSet>;
executeTool(name: string, args: any): Promise<any>;
}
/** Event Manager 接口 (占位符) */
export interface EventManager {
emit(event: string, data: any): void;
}
/**
* LLM 响应结构
*/
export interface LLMResponse {
/** 响应内容 */
content: string;
/** 工具调用列表 */
toolCalls?: ToolCall[];
/** 结束原因 */
finishReason?: string;
/** Token 使用统计 */
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
}
/**
* LLM 调用选项
*/
export interface LLMChatOptions {
/** 温度参数 (0-2) */
temperature?: number;
/** 最大 token 数 */
maxTokens?: number;
/** 工具选择策略 */
toolChoice?: 'auto' | 'none' | 'required' | { type: 'function'; function: { name: string } };
/** Top P 采样 */
topP?: number;
/** 频率惩罚 */
frequencyPenalty?: number;
/** 存在惩罚 */
presencePenalty?: number;
/** 停止词 */
stop?: string | string[];
/** 结构化输出格式 */
responseFormat?: any;
}
/**
* 工具循环执行结果
*/
export interface ToolLoopResult {
/** 是否成功 */
success: boolean;
/** 最终结果 */
result?: string;
/** 错误信息 */
error?: string;
/** 循环次数 */
loopCount: number;
}
/**
* 工具循环配置
*/
export interface ToolLoopConfig {
/** 最大循环次数 */
maxLoops?: number;
/** Agent 名称 */
agentName?: string;
}
/** LLM Service 核心接口 */
export interface ILLMService {
/**
* 核心方法:上下文补全
* 接收格式化的上下文(消息历史),返回模型响应
* @param messages - 上下文消息列表
* @param tools - 可用的工具定义列表
* @param options - 生成参数(temperature, maxTokens 等)
* @returns 模型响应(包含内容、工具调用、使用统计)
*/
complete(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<LLMResponse>;
/**
* 简单对话:无工具,单次调用
* @param userInput - 用户输入
* @param systemPrompt - 可选的系统提示词
*/
simpleChat(userInput: string, systemPrompt?: string): Promise<string>;
/**
* 完整方法:支持工具调用循环(可选实现)
* 内置工具调用、上下文管理、迭代执行
* @param userInput - 用户输入
* @param imageData - 可选的图片数据
* @param stream - 是否流式输出
*/
generate?(
userInput: string,
imageData?: ImageData,
stream?: boolean
): Promise<string>;
/** 获取配置 */
getConfig(): { provider: string; model: string };
/** 可选:事件管理器 */
setEventManager?(eventManager: EventManager): void;
/** @deprecated 由 Agent 管理工具 */
getAllTools?(): Promise<ToolSet>;
}
/**
* 工具循环执行器
* 简化版本的工具调用循环逻辑,供 Agent 使用
*/
import { ILLMService, ToolLoopResult, ToolLoopConfig } from '../types/index.js';
import { ContextManager } from '../../context/index.js';
import { ToolManager } from '../../tool/ToolManager.js';
import { ContextType, Message } from '../../context/types.js';
import { eventBus } from '../../../evaluation/EventBus.js';
import { deepParseArgs, sleep } from './helpers.js';
/**
* 执行工具循环
*
* 循环逻辑:
* 1. 从 ContextManager 获取当前上下文
* 2. 调用 LLM 完成
* 3. 如果返回工具调用,执行工具并更新上下文
* 4. 重复直到 LLM 返回最终结果或达到最大循环次数
*
* @param llmService - LLM 服务实例
* @param contextManager - 上下文管理器
* @param toolManager - 工具管理器
* @param config - 可选配置
* @returns 工具循环执行结果
*/
export async function executeToolLoop(
llmService: ILLMService,
contextManager: ContextManager,
toolManager: ToolManager,
config?: ToolLoopConfig
): Promise<ToolLoopResult> {
const maxLoops = config?.maxLoops ?? 10;
const agentName = config?.agentName ?? 'simple_agent';
let loopCount = 0;
console.log(`开始工具循环,最大循环次数: ${maxLoops}`);
while (loopCount < maxLoops) {
loopCount++;
console.log(`🔄 工具循环 ${loopCount}/${maxLoops}`);
try {
// 1. 获取当前上下文
const messages = contextManager.getContext();
// 2. 获取格式化的工具定义
const tools = toolManager.getFormattedTools();
console.log(`调用 LLM: ${messages.length} 条消息, ${tools.length} 个工具`);
// 3. 调用 LLM
const response = await llmService.complete(messages, tools);
// 4. 判断是否有工具调用
if (
response.finishReason === 'tool_calls' &&
response.toolCalls &&
response.toolCalls.length > 0
) {
console.log(`检测到 ${response.toolCalls.length} 个工具调用`);
// 记录 LLM 的思考内容(如果有)
if (response.content) {
console.log(`💭 LLM 思考: ${response.content.slice(0, 100)}...`);
}
// 5. 构建 assistant 消息(包含工具调用)
const assistantMessage: Message = {
role: 'assistant',
content: response.content || '',
tool_calls: response.toolCalls,
};
// 6. 添加到 TOOL_MESSAGE_SEQUENCE
contextManager.add(assistantMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
// 7. 执行所有工具调用
for (const toolCall of response.toolCalls) {
const toolName = toolCall.function.name;
try {
// 解析参数
const rawArgs = toolCall.function.arguments
? JSON.parse(toolCall.function.arguments)
: {};
const args = deepParseArgs(rawArgs);
console.log(`🔧 执行工具: ${toolName}`);
// 触发工具调用事件(用于评估系统)
eventBus.emit('tool:call', {
agentName,
toolName,
});
// 执行工具
const result = await toolManager.execute(toolName, args);
const resultString = JSON.stringify(result);
console.log(`✅ 工具结果: ${resultString.slice(0, 200)}...`);
// 8. 构建 tool 消息
const toolMessage: Message = {
role: 'tool',
tool_call_id: toolCall.id,
name: toolName,
content: resultString,
};
// 9. 添加到 TOOL_MESSAGE_SEQUENCE
contextManager.add(toolMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
// 等待一下避免请求过快
await sleep(500);
} catch (error) {
console.error(`❌ 工具执行失败: ${toolName}`, error);
// 将错误信息作为工具结果返回
const errorMessage: Message = {
role: 'tool',
tool_call_id: toolCall.id,
name: toolName,
content: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
}),
};
contextManager.add(errorMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
}
}
// 继续循环
continue;
}
// 10. 没有工具调用,返回最终结果
console.log(`✅ 工具循环完成,循环次数: ${loopCount}`);
console.log(`最终结果: ${response.content?.slice(0, 200)}...`);
// 添加最终的 assistant 消息
const finalMessage: Message = {
role: 'assistant',
content: response.content,
};
contextManager.add(finalMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
return {
success: true,
result: response.content,
loopCount,
};
} catch (error) {
console.error(`❌ LLM 调用失败 (循环 ${loopCount}):`, error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
loopCount,
};
}
}
// 超过最大循环次数
console.warn(`⚠️ 超过最大循环次数 (${maxLoops})`);
return {
success: false,
error: `超过最大循环次数 (${maxLoops})`,
loopCount,
};
}
import { LLMConfig } from "../types/index.js";
import {
config as envConfig,
getLLMKeyByProvider,
} from "../../../config/env.js";
/**
* 提取 API Key
*
* 优先级(从高到低):
* 1. 用户传递的 config.apiKey(显式配置)
* 2. 环境变量中的配置(通过 provider 自动查找)
* 3. 如果都没有且该 provider 需要 API Key,则抛出错误
*
* @param config - LLM 配置
* @returns API Key 字符串
*/
export function extractApiKey(config: LLMConfig): string {
const provider = config.provider.toLowerCase();
// 无需 API Key 的提供商
if (["ollama", "lmstudio", "aws"].includes(provider)) {
return "not-required";
}
// 1. 优先使用用户传递的 API Key
if (config.apiKey) {
return config.apiKey;
}
// 2. 尝试从环境变量配置中获取
const providerConfigKey = getLLMKeyByProvider(provider);
return providerConfigKey;
}
/**
* 获取提供商的 Base URL
*
* 优先级(从高到低):
* 1. 用户传递的 config.baseURL(显式配置)
* 2. 环境变量中的配置(通过 provider 自动查找)
* 3. 硬编码的默认 Base URL
*
* @param config - LLM 配置
* @returns Base URL 字符串
*
*/
export function getBaseURL(config: LLMConfig): string {
// 1. 优先使用用户传递的 baseURL
if (config.baseURL) {
return config.baseURL;
}
const provider = config.provider.toLowerCase();
// 3. 硬编码的默认 Base URL(兜底)
const defaultBaseURLs: Record<string, string> = {
deepseek: "https://api.deepseek.com",
openai: "https://api.openai.com/v1",
anthropic: "https://api.anthropic.com",
siliconflow: "https://api.siliconflow.cn/v1",
qwen: "https://dashscope.aliyuncs.com/compatible-mode/v1",
openrouter: "https://openrouter.ai/api/v1",
ollama: "http://localhost:11434/v1",
lmstudio: "http://localhost:1234/v1",
};
const baseURL = defaultBaseURLs[provider];
if (!baseURL) {
throw new Error(
`No base URL found for provider "${provider}". ` +
`Please pass baseURL in config: { provider: '${provider}', model: '...', baseURL: 'https://...' }`
);
}
return baseURL;
}
/** 获取默认上下文窗口大小 */
export function getDefaultContextWindow(
provider: string,
model?: string
): number {
const defaults: Record<string, Record<string, number>> = {
openai: {
"gpt-4o": 128000,
"gpt-4": 8192,
"gpt-3.5-turbo": 16385,
default: 8192,
},
anthropic: { default: 200000 },
gemini: { default: 1000000 },
deepseek: { default: 128000 },
ollama: { default: 8192 },
};
const providerDefaults = defaults[provider.toLowerCase()] || {
default: 8192,
};
return providerDefaults[model || "default"] || providerDefaults.default;
}
/** 睡眠函数 */
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 深度解析参数:递归检查字符串类型的参数值,尝试 JSON 解析
* 用于处理 Claude 等模型返回嵌套 JSON 字符串的情况
*
* 例如:{ bgmRequests: "[{...}]" } → { bgmRequests: [{...}] }
*/
export function deepParseArgs(args: any): any {
if (typeof args === "string") {
// 尝试解析看起来像 JSON 数组或对象的字符串
const trimmed = args.trim();
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
// 第一次尝试:直接解析
try {
const parsed = JSON.parse(trimmed);
console.log("[deepParseArgs] ✅ 第一次尝试解析成功");
return deepParseArgs(parsed);
} catch (e) {
console.log(
"[deepParseArgs] ❌ 第一次尝试解析失败:",
(e as Error).message
);
console.log(
"[deepParseArgs] 字符串前100字符:",
trimmed.substring(0, 100)
);
// 第二次尝试:处理 Claude 模型返回的双重转义问题
// 将 \\n 转换为 \n,\\\" 转换为 \"
try {
const unescaped = trimmed
.replace(/\\\\n/g, "\\n")
.replace(/\\\\"/g, '\\"');
console.log("[deepParseArgs] 尝试处理双重转义后解析...");
const parsed = JSON.parse(unescaped);
console.log("[deepParseArgs] ✅ 第二次尝试(处理转义后)解析成功");
return deepParseArgs(parsed);
} catch (e2) {
console.log(
"[deepParseArgs] ❌ 第二次尝试也失败:",
(e2 as Error).message
);
// 都失败了,返回原始字符串
return args;
}
}
}
return args;
}
if (Array.isArray(args)) {
return args.map(deepParseArgs);
}
if (args && typeof args === "object") {
const result: any = {};
for (const [key, value] of Object.entries(args)) {
result[key] = deepParseArgs(value);
}
return result;
}
return args;
}
/**
* 提示词管理模块
*/
export {
SIMPLE_AGENT_PROMPT,
MAIN_AGENT_PROMPT,
SUB_AGENT_A_PROMPT,
SUB_AGENT_B_PROMPT,
} from './prompts.js';
/**
* 提示词常量定义
* 集中管理各类 Agent 的系统提示词
*/
/**
* SimpleAgent 默认提示词
*/
export const SIMPLE_AGENT_PROMPT = `你是一个有用的 AI 助手,可以使用工具来帮助用户完成任务。
你可以使用以下工具:
- list_files: 列出指定目录下的文件和文件夹
- read_file: 读取指定文件的内容
请根据用户的需求,选择合适的工具来完成任务。`;
/**
* 主 Agent 提示词(协调者)
* 用于多智能体系统中的主控 Agent
*/
export const MAIN_AGENT_PROMPT = `你是一个任务协调者,负责分析用户需求并协调子Agent完成任务。
你的职责:
1. 分析用户的任务需求
2. 将任务分配给合适的子Agent
3. 汇总子Agent的执行结果
4. 向用户提供最终的综合答复
你不直接执行具体任务,而是通过协调子Agent来完成工作。`;
/**
* 子 Agent A 提示词(研究者)
*/
export const SUB_AGENT_A_PROMPT = `你是一个研究分析专家,擅长:
- 收集和整理信息
- 分析问题的各个方面
- 提供详细的背景知识
请根据主Agent的指令,提供专业的研究分析结果。`;
/**
* 子 Agent B 提示词(执行者)
*/
export const SUB_AGENT_B_PROMPT = `你是一个执行专家,擅长:
- 制定具体的执行方案
- 提供实用的建议和步骤
- 给出可操作的解决方案
请根据主Agent的指令,提供具体的执行方案。`;
import { describe, it, expect, beforeEach } from 'vitest';
import { ToolManager } from '../ToolManager.js';
describe('ToolManager 测试', () => {
let toolManager: ToolManager;
beforeEach(() => {
toolManager = new ToolManager();
});
describe('基础功能', () => {
it('应该成功创建 ToolManager 实例', () => {
expect(toolManager).toBeDefined();
expect(toolManager).toBeInstanceOf(ToolManager);
});
it('应该自动注册所有工具', () => {
const toolNames = toolManager.getToolNames();
expect(toolNames).toContain('read_file');
expect(toolNames).toContain('grep_search');
});
it('应该返回正确的工具数量', () => {
const tools = toolManager.getTools();
expect(tools.length).toBeGreaterThan(0);
});
});
describe('工具查询', () => {
it('应该能够通过名称获取工具', () => {
const tool = toolManager.getTool('read_file');
expect(tool).toBeDefined();
expect(tool?.name).toBe('read_file');
expect(tool?.category).toBe('filesystem');
});
it('应该在工具不存在时返回 undefined', () => {
const tool = toolManager.getTool('non_existent_tool');
expect(tool).toBeUndefined();
});
it('应该正确检查工具是否存在', () => {
expect(toolManager.hasTool('read_file')).toBe(true);
expect(toolManager.hasTool('non_existent_tool')).toBe(false);
});
it('应该返回所有工具名称', () => {
const toolNames = toolManager.getToolNames();
expect(toolNames).toBeInstanceOf(Array);
expect(toolNames.length).toBeGreaterThan(0);
expect(toolNames).toContain('read_file');
});
});
describe('工具统计', () => {
it('应该返回正确的统计信息', () => {
const stats = toolManager.getStats();
expect(stats).toHaveProperty('totalTools');
expect(stats).toHaveProperty('categories');
expect(stats).toHaveProperty('toolNames');
expect(stats.totalTools).toBeGreaterThan(0);
});
it('应该按分类统计工具数量', () => {
const stats = toolManager.getStats();
expect(stats.categories).toHaveProperty('filesystem');
expect(stats.categories).toHaveProperty('search');
});
});
describe('工具执行', () => {
it('应该在工具不存在时抛出错误', async () => {
await expect(
toolManager.execute('non_existent_tool', {})
).rejects.toThrow(/not found/);
});
it('应该在错误信息中列出可用工具', async () => {
try {
await toolManager.execute('non_existent_tool', {});
} catch (error: any) {
expect(error.message).toContain('Available:');
expect(error.message).toContain('read_file');
}
});
});
describe('工具结构验证', () => {
it('每个工具应该有必需的属性', () => {
const tools = toolManager.getTools();
tools.forEach((tool) => {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('category');
expect(tool).toHaveProperty('internal');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('version');
expect(tool).toHaveProperty('parameters');
expect(tool).toHaveProperty('handler');
expect(typeof tool.handler).toBe('function');
});
});
it('每个工具的参数定义应该是有效的 JSON Schema', () => {
const tools = toolManager.getTools();
tools.forEach((tool) => {
expect(tool.parameters).toHaveProperty('type');
expect(tool.parameters.type).toBe('object');
expect(tool.parameters).toHaveProperty('properties');
});
});
});
});
import { describe, it, expect } from 'vitest';
import { formatToolForLLM, InternalTool } from '../types.js';
describe('types 测试', () => {
describe('formatToolForLLM', () => {
it('应该正确格式化工具定义', () => {
const mockTool: InternalTool = {
name: 'test_tool',
category: 'test',
internal: true,
description: 'Test tool description',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
arg1: {
type: 'string',
description: 'Test argument',
},
},
required: ['arg1'],
},
handler: async () => ({ success: true }),
};
const formatted = formatToolForLLM(mockTool);
expect(formatted).toEqual({
name: 'test_tool',
category: 'test',
description: 'Test tool description',
version: '1.0.0',
parameters: mockTool.parameters,
});
});
it('应该不包含 handler 和其他内部属性', () => {
const mockTool: InternalTool = {
name: 'test_tool',
category: 'test',
internal: true,
description: 'Test tool',
version: '1.0.0',
parameters: {
type: 'object',
properties: {},
},
handler: async () => ({ success: true }),
renderResultForAssistant: (result) => JSON.stringify(result),
needsPermissions: () => false,
};
const formatted = formatToolForLLM(mockTool);
expect(formatted).not.toHaveProperty('handler');
expect(formatted).not.toHaveProperty('internal');
expect(formatted).not.toHaveProperty('renderResultForAssistant');
expect(formatted).not.toHaveProperty('needsPermissions');
});
it('应该保留完整的参数 Schema 结构', () => {
const complexParameters = {
type: 'object' as const,
properties: {
nested: {
type: 'object' as const,
properties: {
deep: {
type: 'string' as const,
},
},
},
array: {
type: 'array' as const,
items: {
type: 'number' as const,
},
},
},
required: ['nested'],
};
const mockTool: InternalTool = {
name: 'complex_tool',
category: 'test',
internal: true,
description: 'Complex tool',
version: '1.0.0',
parameters: complexParameters,
handler: async () => ({}),
};
const formatted = formatToolForLLM(mockTool);
expect(formatted.parameters).toEqual(complexParameters);
});
});
});
/**
* 工具模块统一导出
*/
// 类型定义
export * from './types.js';
// 工具管理器
export { ToolManager } from './ToolManager.js';
// 具体工具
export { ListFilesTool } from './ListFiles/definitions.js';
export type { ListFilesArgs, ListFilesResult } from './ListFiles/executors.js';
export { ReadFileTool } from './ReadFile/definitions.js';
export type { ReadFileArgs, ReadFileResult } from './ReadFile/executors.js';
import { InternalTool } from "../types";
import { listFilesExecutor } from "./executors";
import { renderResultForAssistant } from "./executors";
import { ListFilesArgs, ListFilesResult } from "./executors";
export const ListFilesTool: InternalTool<ListFilesArgs, ListFilesResult> = {
name: 'list_files',
category: 'filesystem',
internal: true,
description: '列出指定目录下的文件和文件夹。如果不指定目录,则列出当前工作目录。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
directory: {
type: 'string',
description: '要列出内容的目录路径,默认为当前工作目录',
},
},
required: [],
},
handler: listFilesExecutor,
renderResultForAssistant: renderResultForAssistant,
};
/**
* 列出目录文件工具
* 列出指定目录下的文件和文件夹
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ListFilesArgs {
/** 目录路径,默认为当前工作目录 */
directory?: string;
}
export interface ListFilesResult {
/** 目录路径 */
directory: string;
/** 文件列表 */
files: Array<{
name: string;
type: 'file' | 'directory';
size?: number;
}>;
/** 文件总数 */
totalCount: number;
}
/**
* 列出文件执行器
* @param args - 列出文件参数
* @param config - 配置
* @returns - 列出文件结果
*/
export async function listFilesExecutor(args: ListFilesArgs, config: any): Promise<ListFilesResult> {
const cwd = config?.cwd || process.cwd();
const targetDir = args.directory ? path.resolve(cwd, args.directory) : cwd;
// 检查目录是否存在
if (!fs.existsSync(targetDir)) {
throw new Error(`目录不存在: ${targetDir}`);
}
// 检查是否是目录
const stats = fs.statSync(targetDir);
if (!stats.isDirectory()) {
throw new Error(`路径不是目录: ${targetDir}`);
}
// 读取目录内容
const entries = fs.readdirSync(targetDir, { withFileTypes: true });
const files = entries
.filter((entry) => !entry.name.startsWith('.')) // 过滤隐藏文件
.map((entry) => {
const fullPath = path.join(targetDir, entry.name);
const result: {
name: string;
type: 'file' | 'directory';
size?: number;
} = {
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
};
// 对于文件,获取大小
if (entry.isFile()) {
try {
const fileStats = fs.statSync(fullPath);
result.size = fileStats.size;
} catch {
// 忽略权限错误
}
}
return result;
})
.sort((a, b) => {
// 目录在前,文件在后
if (a.type !== b.type) {
return a.type === 'directory' ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
return {
directory: targetDir,
files,
totalCount: files.length,
};
}
/**
* 格式化工具结果
* @param result - 列表文件结果
* @returns
*/
export function renderResultForAssistant(result: ListFilesResult): string {
const lines = [`目录: ${result.directory}`, `共 ${result.totalCount} 个项目:`, ''];
for (const file of result.files) {
const icon = file.type === 'directory' ? '📁' : '📄';
const size = file.size !== undefined ? ` (${formatSize(file.size)})` : '';
lines.push(`${icon} ${file.name}${size}`);
}
return lines.join('\n');
}
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
import { InternalTool } from "../types";
import { readFileExecutor } from "./executors";
import { renderResultForAssistant } from "./executors";
import { ReadFileArgs, ReadFileResult } from "./executors";
export const ReadFileTool: InternalTool<ReadFileArgs, ReadFileResult> = {
name: 'read_file',
category: 'filesystem',
internal: true,
description: '读取指定文件的内容。支持文本文件,返回文件内容、大小和行数。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: '要读取的文件路径',
},
encoding: {
type: 'string',
description: '文件编码,默认为 utf-8',
},
},
required: ['filePath'],
},
handler: readFileExecutor,
renderResultForAssistant: renderResultForAssistant,
};
/**
* 读取文件内容工具
* 读取指定文件的内容
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ReadFileArgs {
/** 文件路径 */
filePath: string;
/** 编码,默认 utf-8 */
encoding?: BufferEncoding;
}
export interface ReadFileResult {
/** 文件路径 */
filePath: string;
/** 文件内容 */
content: string;
/** 文件大小(字节) */
size: number;
/** 行数 */
lineCount: number;
}
/**
* 读取文件执行器
* @param args - 读取文件参数
* @param config - 配置
* @returns - 读取文件结果
*/
export async function readFileExecutor(args: ReadFileArgs, config: any): Promise<ReadFileResult> {
const cwd = config?.cwd || process.cwd();
const targetPath = path.resolve(cwd, args.filePath);
const encoding = args.encoding || 'utf-8';
// 检查文件是否存在
if (!fs.existsSync(targetPath)) {
throw new Error(`文件不存在: ${targetPath}`);
}
// 检查是否是文件
const stats = fs.statSync(targetPath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${targetPath}`);
}
// 检查文件大小,避免读取过大的文件
const maxSize = 1024 * 1024; // 1MB
if (stats.size > maxSize) {
throw new Error(`文件太大 (${formatSize(stats.size)}),最大支持 1MB`);
}
// 读取文件内容
const content = fs.readFileSync(targetPath, encoding);
const lineCount = content.split('\n').length;
return {
filePath: targetPath,
content,
size: stats.size,
lineCount,
};
}
/**
* 格式化工具结果
* @param result - 读取文件结果
* @returns - 读取文件结果
*/
export function renderResultForAssistant(result: ReadFileResult): string {
return [
`文件: ${result.filePath}`,
`大小: ${formatSize(result.size)}`,
`行数: ${result.lineCount}`,
'',
'--- 内容 ---',
result.content,
].join('\n');
}
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
/**
* 读取文件内容工具
* 读取指定文件的内容
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ReadFileArgs {
/** 文件路径 */
filePath: string;
/** 编码,默认 utf-8 */
encoding?: BufferEncoding;
}
export interface ReadFileResult {
/** 文件路径 */
filePath: string;
/** 文件内容 */
content: string;
/** 文件大小(字节) */
size: number;
/** 行数 */
lineCount: number;
}
export const ReadFileTool: InternalTool<ReadFileArgs, ReadFileResult> = {
name: 'read_file',
category: 'filesystem',
internal: true,
description: '读取指定文件的内容。支持文本文件,返回文件内容、大小和行数。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: '要读取的文件路径',
},
encoding: {
type: 'string',
description: '文件编码,默认为 utf-8',
},
},
required: ['filePath'],
},
async handler(args, context) {
const cwd = context?.cwd || process.cwd();
const targetPath = path.resolve(cwd, args.filePath);
const encoding = args.encoding || 'utf-8';
// 检查文件是否存在
if (!fs.existsSync(targetPath)) {
throw new Error(`文件不存在: ${targetPath}`);
}
// 检查是否是文件
const stats = fs.statSync(targetPath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${targetPath}`);
}
// 检查文件大小,避免读取过大的文件
const maxSize = 1024 * 1024; // 1MB
if (stats.size > maxSize) {
throw new Error(`文件太大 (${formatSize(stats.size)}),最大支持 1MB`);
}
// 读取文件内容
const content = fs.readFileSync(targetPath, encoding);
const lineCount = content.split('\n').length;
return {
filePath: targetPath,
content,
size: stats.size,
lineCount,
};
},
renderResultForAssistant(result) {
return [
`文件: ${result.filePath}`,
`大小: ${formatSize(result.size)}`,
`行数: ${result.lineCount}`,
'',
'--- 内容 ---',
result.content,
].join('\n');
},
};
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
import { InternalTool, InternalToolContext } from './types.js';
import { ListFilesTool } from './ListFiles/definitions.js';
import { ReadFileTool } from './ReadFile/definitions.js';
// 注册的工具列表
const toolsList: InternalTool[] = [ListFilesTool, ReadFileTool];
/**
* OpenAI 格式的工具定义
*/
interface OpenAITool {
type: 'function';
function: {
name: string;
description: string;
parameters: any;
};
}
/**
* 工具管理类
* 负责工具的注册、查询和执行
*/
export class ToolManager {
private tools: Map<string, InternalTool> = new Map();
constructor() {
this.registerAllTools();
}
/**
* 注册所有工具
*/
private registerAllTools() {
toolsList.forEach((tool) => {
this.tools.set(tool.name, tool);
});
}
/**
* 执行指定工具
*/
async execute<TArgs = any, TResult = any>(
name: string,
args: TArgs,
context?: InternalToolContext
): Promise<TResult> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool '${name}' not found. Available: ${this.getToolNames().join(', ')}`);
}
try {
return await tool.handler(args, context);
} catch (error: any) {
console.error(`Tool '${name}' failed:`, error);
throw error;
}
}
/**
* 获取格式化的工具定义(OpenAI 格式)
* 供 LLM API 调用使用
*/
getFormattedTools(): OpenAITool[] {
return Array.from(this.tools.values()).map((tool) => ({
type: 'function' as const,
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
},
}));
}
/**
* 获取所有工具
*/
getTools(): InternalTool[] {
return Array.from(this.tools.values());
}
/**
* 获取指定工具
*/
getTool(name: string): InternalTool | undefined {
return this.tools.get(name);
}
/**
* 获取所有工具名称
*/
getToolNames(): string[] {
return Array.from(this.tools.keys());
}
/**
* 检查工具是否存在
*/
hasTool(name: string): boolean {
return this.tools.has(name);
}
/**
* 清空所有工具
* 用于创建无工具的Agent场景
*/
clear(): void {
this.tools.clear();
}
/**
* 获取工具统计信息
*/
getStats() {
const tools = this.getTools();
const categories = new Map<string, number>();
tools.forEach((tool) => {
const count = categories.get(tool.category) || 0;
categories.set(tool.category, count + 1);
});
return {
totalTools: tools.length,
categories: Object.fromEntries(categories),
toolNames: this.getToolNames(),
};
}
}
/**
* 工具定义类型约束
* 用于定义所有工具的统一结构,便于格式化并作为提示词传给大模型
*/
/**
* JSON Schema 参数定义
*/
export interface ToolParameterSchema {
type: 'object' | 'array' | 'string' | 'number' | 'boolean';
description?: string;
properties?: Record<string, ToolParameterSchema>;
items?: ToolParameterSchema;
required?: string[];
enum?: string[];
default?: any;
}
/**
* 工具上下文
*/
export interface InternalToolContext {
abortSignal?: AbortSignal;
cwd?: string;
[key: string]: any; // 扩展字段
}
/**
* 工具定义基础接口
*/
export interface InternalTool<TArgs = any, TResult = any> {
/** 工具名称(唯一标识) */
name: string;
/** 工具分类(如 filesystem、search、network) */
category: string;
/** 是否为内部工具 */
internal: boolean;
/** 工具描述(简短,详细描述在 prompt 中) */
description: string;
/** 版本号 */
version: string;
/** 参数定义(JSON Schema 格式) */
parameters: ToolParameterSchema;
/** 工具处理函数 */
handler: (args: TArgs, context?: InternalToolContext) => Promise<TResult>;
/** 可选:格式化结果给 AI */
renderResultForAssistant?: (result: TResult) => string;
/** 可选:权限控制 */
needsPermissions?: (input?: TArgs) => boolean; // 是否需要权限
isEnabled?: () => Promise<boolean>; // 是否启用
isReadOnly?: () => boolean; // 是否只读
isConcurrencySafe?: () => boolean; // 是否并发安全
}
/**
* 格式化工具定义为大模型可读格式
*/
export interface FormattedToolDefinition {
name: string;
category: string;
description: string;
version: string;
parameters: ToolParameterSchema;
}
/**
* 将工具定义格式化为大模型输入格式
*/
export function formatToolForLLM(tool: InternalTool): FormattedToolDefinition {
return {
name: tool.name,
category: tool.category,
description: tool.description,
version: tool.version,
parameters: tool.parameters,
};
}
/**
* 通用测试数据集
* 包含单 Agent 和多 Agent 的测试用例
*/
import { TestCase } from './types.js';
/**
* 单 Agent 测试用例 (SimpleAgent)
*/
export const SIMPLE_AGENT_TESTS: TestCase[] = [
// S1. 单工具测试 - 列出文件
{
id: 'S1',
description: '单工具调用 - 列出当前目录文件',
input: '列出当前目录下的所有文件和文件夹',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['list_files'] },
},
},
// S2. 多工具测试 - 先列出再读取
{
id: 'S2',
description: '多工具调用 - 列出文件后读取',
input: '先列出当前目录的文件,然后读取 README.md 的内容',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['list_files', 'read_file'] },
},
},
];
/**
* 多 Agent 测试用例 (MultiAgent: MainAgent + SubAgents)
*/
export const MULTI_AGENT_TESTS: TestCase[] = [
// M1. 简单任务分发 - 主Agent协调子Agent
{
id: 'M1',
description: '多Agent协调 - 简单任务分发',
input: '帮我分析一下如何提升代码质量',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
// M2. 研究+执行 - 调用 researcher 和 executor
{
id: 'M2',
description: '多Agent协调 - 研究与执行',
input: '请研究并提供一个实现用户认证系统的方案',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
// M3. 复杂任务 - 多轮子Agent协作
{
id: 'M3',
description: '多Agent协调 - 复杂任务处理',
input: '帮我设计一个完整的电商系统架构,包括技术选型和实施计划',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
];
/**
* 所有测试用例
*/
export const TEST_CASES: TestCase[] = [...SIMPLE_AGENT_TESTS, ...MULTI_AGENT_TESTS];
/**
* 根据ID获取测试用例
*/
export function getTestById(id: string): TestCase | undefined {
return TEST_CASES.find((test) => test.id === id);
}
/**
* 获取单Agent测试用例
*/
export function getSimpleAgentTests(): TestCase[] {
return SIMPLE_AGENT_TESTS;
}
/**
* 获取多Agent测试用例
*/
export function getMultiAgentTests(): TestCase[] {
return MULTI_AGENT_TESTS;
}
/**
* 评估函数 - 简化版本
* 比较期望行为和实际执行数据
*/
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;
}
/**
* 事件总线 + 数据收集器(合并版本)
* 负责事件的发射、监听,以及收集执行过程中的数据
*/
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();
/**
* 评估模块使用示例
* 展示如何使用 SimpleAgent 和 MultiAgent 进行测试
*/
import { eventBus } from './EventBus.js';
import { evaluate, formatResult } from './evaluate.js';
import {
TEST_CASES,
getTestById,
getSimpleAgentTests,
getMultiAgentTests,
} from './dataset.js';
import { TestCase, EvaluateResult } from './types.js';
import { SimpleAgent, MainAgent } from '../core/agent/index.js';
import { createLLMService } from '../core/llm/index.js';
import { ILLMService } from '../core/llm/types/index.js';
// 缓存 LLM 服务和 Agent 实例
let llmService: ILLMService | null = null;
let simpleAgent: SimpleAgent | null = null;
let multiAgent: MainAgent | null = null;
/**
* 获取 LLM 服务(延迟初始化)
*/
async function getLLMService(): Promise<ILLMService> {
if (!llmService) {
llmService = await createLLMService({
provider: 'deepseek',
model: "deepseek-chat",
});
}
return llmService;
}
/**
* 获取 SimpleAgent 实例
*/
async function getSimpleAgent(): Promise<SimpleAgent> {
if (!simpleAgent) {
const service = await getLLMService();
simpleAgent = new SimpleAgent(service, { name: 'simple_agent' });
}
return simpleAgent;
}
/**
* 获取 MultiAgent (MainAgent) 实例
*/
async function getMultiAgent(): Promise<MainAgent> {
if (!multiAgent) {
const service = await getLLMService();
multiAgent = new MainAgent(service, 'main_agent');
}
return multiAgent;
}
/**
* 判断测试用例是否为多 Agent 测试
*/
function isMultiAgentTest(testCase: TestCase): boolean {
return testCase.id.startsWith('M');
}
/**
* 运行单个测试用例
*/
async function runTest(testCase: TestCase): Promise<EvaluateResult | null> {
console.log(`\n${'='.repeat(50)}`);
console.log(`🧪 测试: ${testCase.id} - ${testCase.description}`);
console.log(`${'='.repeat(50)}`);
const startTime = Date.now();
try {
let agents: string[];
let tools: Record<string, string[]>;
let finalResponse: string;
let success: boolean;
let error: string | undefined;
if (isMultiAgentTest(testCase)) {
// 多 Agent 测试
const agent = await getMultiAgent();
const result = await agent.run(testCase.input);
agents = result.agents;
tools = result.tools;
finalResponse = result.finalResponse;
success = result.success;
error = result.error;
} else {
// 单 Agent 测试
const agent = await getSimpleAgent();
const result = await agent.run(testCase.input);
agents = result.agents;
tools = result.tools;
finalResponse = result.finalResponse;
success = result.success;
error = result.error;
}
// 评估
const evalResult = evaluate(testCase, {
agents,
tools,
editResult: null,
});
// 输出结果
const status = evalResult.passed ? '✅ PASS' : '❌ FAIL';
const time = Date.now() - startTime;
console.log(`\n${status} (${time}ms)`);
console.log(formatResult(evalResult));
const responsePreview = finalResponse.slice(0, 200);
console.log(`\n📝 Agent回复: ${responsePreview}${finalResponse.length > 200 ? '...' : ''}`);
if (!success) {
console.log(`\n⚠️ Agent 执行失败: ${error}`);
}
return evalResult;
} catch (err) {
console.error(`❌ 执行失败:`, err);
return null;
}
}
/**
* 运行测试集
*/
async function runTests(testCases: TestCase[], title: string) {
console.log(`\n${'#'.repeat(60)}`);
console.log(`# ${title}: ${testCases.length} 个用例`);
console.log(`${'#'.repeat(60)}`);
const results: Array<{ testCase: TestCase; result: EvaluateResult }> = [];
const startTime = Date.now();
for (const testCase of testCases) {
const result = await runTest(testCase);
if (result) {
results.push({ testCase, result });
}
}
// 汇总
const passed = results.filter((r) => r.result.passed).length;
const failed = results.length - passed;
const totalTime = Date.now() - startTime;
console.log(`\n${'='.repeat(60)}`);
console.log(`${title} 完成`);
console.log(`${'='.repeat(60)}`);
console.log(`总数: ${results.length}`);
console.log(`✅ 通过: ${passed}`);
console.log(`❌ 失败: ${failed}`);
console.log(`⏱️ 耗时: ${(totalTime / 1000).toFixed(2)}s`);
return results;
}
/**
* 运行所有测试用例
*/
async function runAllTests() {
console.log(`\n${'#'.repeat(60)}`);
console.log(`# 开始全量测试: ${TEST_CASES.length} 个用例`);
console.log(`${'#'.repeat(60)}`);
const startTime = Date.now();
// 运行单 Agent 测试
const simpleResults = await runTests(getSimpleAgentTests(), '单 Agent 测试');
// 运行多 Agent 测试
const multiResults = await runTests(getMultiAgentTests(), '多 Agent 测试');
// 总汇总
const allResults = [...simpleResults, ...multiResults];
const passed = allResults.filter((r) => r.result.passed).length;
const failed = allResults.length - passed;
const totalTime = Date.now() - startTime;
console.log(`\n${'#'.repeat(60)}`);
console.log(`# 全部测试完成`);
console.log(`${'#'.repeat(60)}`);
console.log(`总数: ${allResults.length}`);
console.log(`✅ 通过: ${passed}`);
console.log(`❌ 失败: ${failed}`);
console.log(`⏱️ 总耗时: ${(totalTime / 1000).toFixed(2)}s`);
}
/**
* 打印帮助信息
*/
function printHelp() {
console.log(`
评估模块使用说明:
npx ts-node example.ts [选项]
选项:
--help 显示帮助信息
--test <id> 运行指定测试用例 (如: S1, M1)
--simple 运行单 Agent 测试集 (2 个用例)
--multi 运行多 Agent 测试集 (3 个用例)
(无参数) 运行所有测试用例
测试用例 ID:
S1, S2 单 Agent 测试 (SimpleAgent)
M1, M2, M3 多 Agent 测试 (MultiAgent)
`);
}
/**
* 主函数
*/
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help')) {
printHelp();
} else if (args.includes('--simple')) {
// 运行单 Agent 测试集
await runTests(getSimpleAgentTests(), '单 Agent 测试');
} else if (args.includes('--multi')) {
// 运行多 Agent 测试集
await runTests(getMultiAgentTests(), '多 Agent 测试');
} else if (args.includes('--test') && args[args.indexOf('--test') + 1]) {
// 运行指定测试用例
const testId = args[args.indexOf('--test') + 1];
const testCase = getTestById(testId);
if (testCase) {
await runTest(testCase);
} else {
console.error(`未找到测试用例: ${testId}`);
console.log('可用的测试用例 ID: S1, S2, M1, M2, M3');
}
} else {
// 运行所有测试
await runAllTests();
}
}
// 运行
main().catch(console.error);
/**
* 评估模块模板 - 类型定义
* 简化版本,专注核心功能
*/
/**
* 测试用例定义
*/
export interface TestCase {
id: string; // 测试用例ID
description: string; // 用例描述
input: string; // 用户输入
expected: ExpectedBehavior; // 期望结果
}
/**
* 期望行为定义
*/
export interface ExpectedBehavior {
// 期望调用的子Agent列表
agents: string[];
// 每个子Agent期望调用的工具
tools: {
[agentName: string]: string[];
};
}
/**
* 事件收集器收集到的实际执行数据
*/
export interface CollectedData {
// 实际调用的子Agent列表
agents: string[];
// 每个Agent实际调用的工具
tools: {
[agentName: string]: string[];
};
// 编辑节点执行结果
editResult: {
success: number;
fail: number;
} | null;
}
/**
* 评估结果
*/
export interface EvaluateResult {
passed: boolean; // 是否通过
agentMatch: boolean; // Agent调用是否匹配
toolMatch: boolean; // 工具调用是否匹配
// 详细信息
details: {
agents: {
expected: string[];
actual: string[];
missed: string[]; // 遗漏的Agent
extra: string[]; // 多余的Agent
};
tools: {
expected: { [agentName: string]: string[] };
actual: { [agentName: string]: string[] };
missed: { agent: string; tool: string }[];
extra: { agent: string; tool: string }[];
};
};
}
/**
* 多智能体对话示例
* 演示 MainAgent 协调多个 SubAgent 完成任务
*/
import { createLLMService } from '../core/llm/index.js';
import { MainAgent } from '../core/agent/index.js';
async function main() {
console.log('🚀 Multi-Agent Chat Example\n');
// 创建 LLM 服务
const service = await createLLMService({
provider: 'deepseek',
model: 'deepseek-chat',
});
// 创建多智能体系统 (MainAgent + SubAgents)
const multiAgent = new MainAgent(service, 'main_agent');
console.log('📋 任务: 分析如何提升代码质量\n');
// 执行多智能体协作
const result = await multiAgent.run('请帮我分析如何提升代码质量,并给出具体的改进建议');
// 输出结果
console.log('\n' + '='.repeat(60));
console.log('📊 执行结果汇总');
console.log('='.repeat(60));
console.log(`\n✅ 执行状态: ${result.success ? '成功' : '失败'}`);
console.log(`🤖 参与的 Agent: ${result.agents.join(' → ')}`);
console.log('\n📝 子 Agent 执行记录:');
for (const subResult of result.subAgentResults) {
console.log(`\n [${subResult.agentName}]`);
console.log(` 状态: ${subResult.success ? '✅ 成功' : '❌ 失败'}`);
console.log(` 输出: ${subResult.result.slice(0, 200)}${subResult.result.length > 200 ? '...' : ''}`);
}
console.log('\n' + '='.repeat(60));
console.log('🎯 最终响应');
console.log('='.repeat(60));
console.log(result.finalResponse);
}
main().catch(console.error);
/**
* 简单对话示例
*/
import { createLLMService } from "../core/llm/index.js";
async function main() {
// 创建 LLM 服务
const service = await createLLMService({
provider: "deepseek",
model: "deepseek-chat",
});
console.log("🚀 Simple Chat Example\n");
// 简单对话
const response = await service.simpleChat(
"你好!你能用一句话介绍一下自己吗?",
"你是一个有用的AI助手。"
);
console.log("Assistant:", response);
}
main().catch(console.error);
/**
* 简单的日志工具
*/
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
}
class Logger {
private level: LogLevel = LogLevel.INFO;
setLevel(level: LogLevel) {
this.level = level;
}
debug(...args: any[]) {
if (this.shouldLog(LogLevel.DEBUG)) {
console.log('[DEBUG]', ...args);
}
}
info(...args: any[]) {
if (this.shouldLog(LogLevel.INFO)) {
console.log('[INFO]', ...args);
}
}
warn(...args: any[]) {
if (this.shouldLog(LogLevel.WARN)) {
console.warn('[WARN]', ...args);
}
}
error(...args: any[]) {
if (this.shouldLog(LogLevel.ERROR)) {
console.error('[ERROR]', ...args);
}
}
private shouldLog(level: LogLevel): boolean {
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
return levels.indexOf(level) >= levels.indexOf(this.level);
}
}
export const logger = new Logger();
// 根据配置设置日志级别
import { config } from '../config/env.js';
logger.setLevel(config.logging.level as LogLevel);
+11
-10

@@ -75,12 +75,13 @@ #!/usr/bin/env node

.
├── core/ # 核心系统模块
│ ├── llm/ # LLM 服务层(多模型支持)
│ ├── context/ # 上下文管理系统
│ ├── tool/ # 工具管理系统
│ ├── agent/ # Agent 编排(预留)
│ └── promptManager/ # 提示词管理
├── evaluation/ # 测试与评估
├── utils/ # 工具函数(日志等)
├── config/ # 配置(环境变量加载)
├── examples/ # 使用示例
├── src/ # 源代码目录
│ ├── config/ # 配置(环境变量加载)
│ ├── utils/ # 工具函数(日志等)
│ ├── core/ # 核心系统模块
│ │ ├── llm/ # LLM 服务层(多模型支持)
│ │ ├── context/ # 上下文管理系统
│ │ ├── tool/ # 工具管理系统
│ │ ├── agent/ # Agent 编排
│ │ └── promptManager/ # 提示词管理
│ ├── evaluation/ # 测试与评估
│ └── examples/ # 使用示例
└── docs/ # 项目文档

@@ -87,0 +88,0 @@ └── ARCHITECTURE.md # 架构设计文档(必读!)

@@ -1,1 +0,1 @@

{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;GAEG;AACH,OAAO,KAAK,CAAC,MAAM,gBAAgB,CAAC;AACpC,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC1G,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAEnE,KAAK,UAAU,mBAAmB,CAAC,MAAW;IAC5C,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAE/D,kBAAkB;IAClB,MAAM,WAAW,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IAChD,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,WAAW,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAErF,mBAAmB;IACnB,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IAEnF,gBAAgB;IAChB,MAAM,SAAS,GAAG,iBAAiB,EAAE,CAAC;IACtC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAE3E,kBAAkB;IAClB,MAAM,UAAU,GAAG,kBAAkB,EAAE,CAAC;IACxC,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,cAAc,CAAC,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC;IAE9E,sBAAsB;IACtB,MAAM,YAAY,GAAG,oBAAoB,EAAE,CAAC;IAC5C,MAAM,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,kBAAkB,CAAC,EAAE,YAAY,EAAE,OAAO,CAAC,CAAC;IAEpF,uBAAuB;IACvB,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiFxB,CAAC;IAEA,MAAM,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,WAAW,CAAC,EAAE,cAAc,EAAE;QAC9E,YAAY,EAAE,MAAM,CAAC,WAAW;QAChC,eAAe,EAAE,MAAM,CAAC,cAAc;QACtC,WAAW,EAAE,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC;QACjD,eAAe,EAAE,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC;KAC1D,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,UAAU;QACV,MAAM,MAAM,GAAG,MAAM,kBAAkB,EAAE,CAAC;QAE1C,YAAY;QACZ,MAAM,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAC/C,MAAM,sBAAsB,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC;QAE3C,YAAY;QACZ,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACnD,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAE/C,YAAY;QACZ,OAAO,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC3C,MAAM,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QAEvC,UAAU;QACV,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO,CAAC,KAAK,CAAC,gCAAgC,MAAM,CAAC,cAAc,KAAK,CAAC,CAAC;YAC1E,IAAI,CAAC;gBACH,MAAM,mBAAmB,CAAC,MAAM,CAAC,CAAC;gBAClC,OAAO,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;YAC1C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;gBAC/C,CAAC,CAAC,IAAI,CACJ,gDAAgD,MAAM,CAAC,WAAW,OAAO,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,EACnH,MAAM,CACP,CAAC;YACJ,CAAC;QACH,CAAC;QAED,UAAU;QACV,MAAM,SAAS,GAAG,MAAM,CAAC,aAAa;YACpC,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW;;KAE9B,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM;YACzC,CAAC,CAAC,SAAS,MAAM,CAAC,WAAW;KAC9B,iBAAiB,CAAC,MAAM,CAAC,cAAc,CAAC;;KAExC,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC;QAE5C,CAAC,CAAC,KAAK,CAAC,sBAAsB,MAAM,CAAC,WAAW;;;EAGlD,SAAS;;oEAEyD,CAAC,CAAC;IACpE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,CAAC,CAAC,MAAM,CAAC,UAAU,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;QACpC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC"}

@@ -10,4 +10,4 @@ export function generatePackageJson(config) {

// 开发脚本
dev: 'tsx watch examples/simple-chat.ts',
'dev:multi-chat': 'tsx watch examples/multi-chat.ts',
dev: 'tsx watch src/examples/simple-chat.ts',
'dev:multi-chat': 'tsx watch src/examples/multi-chat.ts',
// 构建脚本

@@ -21,3 +21,3 @@ build: 'tsc',

// 评估脚本
eval: 'tsx evaluationTemplate/example.ts',
eval: 'tsx src/evaluation/example.ts',
// 清理脚本

@@ -24,0 +24,0 @@ clean: 'rm -rf dist',

@@ -1,1 +0,1 @@

{"version":3,"file":"packageJson.js","sourceRoot":"","sources":["../../src/templates/packageJson.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,mBAAmB,CAAC,MAAqB;IACvD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,iDAAiD;QAC9D,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE;YACP,OAAO;YACP,GAAG,EAAE,mCAAmC;YACxC,gBAAgB,EAAE,kCAAkC;YAEpD,OAAO;YACP,KAAK,EAAE,KAAK;YACZ,YAAY,EAAE,cAAc;YAE5B,OAAO;YACP,IAAI,EAAE,YAAY;YAClB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,aAAa;YAExB,OAAO;YACP,IAAI,EAAE,mCAAmC;YAEzC,OAAO;YACP,KAAK,EAAE,aAAa;SACrB;QACD,YAAY,EAAE;YACZ,MAAM,EAAE,SAAS,EAAE,2BAA2B;YAC9C,MAAM,EAAE,SAAS,EAAE,SAAS;SAC7B;QACD,eAAe,EAAE;YACf,aAAa,EAAE,UAAU,EAAE,YAAY;YACvC,GAAG,EAAE,SAAS,EAAE,iBAAiB;YACjC,UAAU,EAAE,QAAQ,EAAE,iBAAiB;YACvC,MAAM,EAAE,QAAQ,EAAE,OAAO;YACzB,YAAY,EAAE,QAAQ,EAAE,QAAQ;SACjC;QACD,OAAO,EAAE;YACP,IAAI,EAAE,UAAU;SACjB;QACD,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC;KACxE,CAAC;AACJ,CAAC"}
{"version":3,"file":"packageJson.js","sourceRoot":"","sources":["../../src/templates/packageJson.ts"],"names":[],"mappings":"AAKA,MAAM,UAAU,mBAAmB,CAAC,MAAqB;IACvD,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,WAAW;QACxB,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE,QAAQ;QACd,WAAW,EAAE,iDAAiD;QAC9D,IAAI,EAAE,eAAe;QACrB,OAAO,EAAE;YACP,OAAO;YACP,GAAG,EAAE,uCAAuC;YAC5C,gBAAgB,EAAE,sCAAsC;YAExD,OAAO;YACP,KAAK,EAAE,KAAK;YACZ,YAAY,EAAE,cAAc;YAE5B,OAAO;YACP,IAAI,EAAE,YAAY;YAClB,YAAY,EAAE,QAAQ;YACtB,SAAS,EAAE,aAAa;YAExB,OAAO;YACP,IAAI,EAAE,+BAA+B;YAErC,OAAO;YACP,KAAK,EAAE,aAAa;SACrB;QACD,YAAY,EAAE;YACZ,MAAM,EAAE,SAAS,EAAE,2BAA2B;YAC9C,MAAM,EAAE,SAAS,EAAE,SAAS;SAC7B;QACD,eAAe,EAAE;YACf,aAAa,EAAE,UAAU,EAAE,YAAY;YACvC,GAAG,EAAE,SAAS,EAAE,iBAAiB;YACjC,UAAU,EAAE,QAAQ,EAAE,iBAAiB;YACvC,MAAM,EAAE,QAAQ,EAAE,OAAO;YACzB,YAAY,EAAE,QAAQ,EAAE,QAAQ;SACjC;QACD,OAAO,EAAE;YACP,IAAI,EAAE,UAAU;SACjB;QACD,QAAQ,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,CAAC;KACxE,CAAC;AACJ,CAAC"}

@@ -1,1 +0,1 @@

{"version":3,"file":"tsconfig.d.ts","sourceRoot":"","sources":["../../src/templates/tsconfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,gBAAgB,IAAI,MAAM,CAkDzC"}
{"version":3,"file":"tsconfig.d.ts","sourceRoot":"","sources":["../../src/templates/tsconfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,wBAAgB,gBAAgB,IAAI,MAAM,CAyCzC"}

@@ -13,3 +13,3 @@ /**

outDir: './dist',
rootDir: './',
rootDir: './src',
// 严格模式

@@ -34,15 +34,6 @@ strict: true,

paths: {
'@/*': ['./*'],
'@/*': ['./src/*'],
},
},
include: [
'llm/**/*',
'context/**/*',
'tool/**/*',
'agent/**/*',
'evaluationTemplate/**/*',
'utils/**/*',
'config/**/*',
'examples/**/*',
],
include: ['src/**/*'],
exclude: ['node_modules', 'dist', '**/*.test.ts'],

@@ -49,0 +40,0 @@ };

@@ -1,1 +0,1 @@

{"version":3,"file":"tsconfig.js","sourceRoot":"","sources":["../../src/templates/tsconfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,eAAe,EAAE;YACf,OAAO;YACP,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,MAAM;YAExB,OAAO;YACP,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,IAAI;YAEb,OAAO;YACP,MAAM,EAAE,IAAI;YACZ,gBAAgB,EAAE,IAAI;YACtB,mBAAmB,EAAE,IAAI;YACzB,cAAc,EAAE,IAAI;YACpB,kBAAkB,EAAE,IAAI;YACxB,iBAAiB,EAAE,IAAI;YAEvB,gBAAgB;YAChB,eAAe,EAAE,IAAI;YACrB,4BAA4B,EAAE,IAAI;YAElC,OAAO;YACP,YAAY,EAAE,IAAI;YAClB,iBAAiB,EAAE,IAAI;YAEvB,MAAM;YACN,sBAAsB,EAAE,IAAI;YAC5B,qBAAqB,EAAE,IAAI;YAE3B,YAAY;YACZ,OAAO,EAAE,GAAG;YACZ,KAAK,EAAE;gBACL,KAAK,EAAE,CAAC,KAAK,CAAC;aACf;SACF;QACD,OAAO,EAAE;YACP,UAAU;YACV,cAAc;YACd,WAAW;YACX,YAAY;YACZ,yBAAyB;YACzB,YAAY;YACZ,aAAa;YACb,eAAe;SAChB;QACD,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,cAAc,CAAC;KAClD,CAAC;AACJ,CAAC"}
{"version":3,"file":"tsconfig.js","sourceRoot":"","sources":["../../src/templates/tsconfig.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,UAAU,gBAAgB;IAC9B,OAAO;QACL,eAAe,EAAE;YACf,OAAO;YACP,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,QAAQ;YAChB,gBAAgB,EAAE,MAAM;YAExB,OAAO;YACP,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,OAAO;YAEhB,OAAO;YACP,MAAM,EAAE,IAAI;YACZ,gBAAgB,EAAE,IAAI;YACtB,mBAAmB,EAAE,IAAI;YACzB,cAAc,EAAE,IAAI;YACpB,kBAAkB,EAAE,IAAI;YACxB,iBAAiB,EAAE,IAAI;YAEvB,gBAAgB;YAChB,eAAe,EAAE,IAAI;YACrB,4BAA4B,EAAE,IAAI;YAElC,OAAO;YACP,YAAY,EAAE,IAAI;YAClB,iBAAiB,EAAE,IAAI;YAEvB,MAAM;YACN,sBAAsB,EAAE,IAAI;YAC5B,qBAAqB,EAAE,IAAI;YAE3B,YAAY;YACZ,OAAO,EAAE,GAAG;YACZ,KAAK,EAAE;gBACL,KAAK,EAAE,CAAC,SAAS,CAAC;aACnB;SACF;QACD,OAAO,EAAE,CAAC,UAAU,CAAC;QACrB,OAAO,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,cAAc,CAAC;KAClD,CAAC;AACJ,CAAC"}

@@ -11,3 +11,3 @@ /**

environment: 'node',
include: ['**/__tests__/**/*.test.ts'],
include: ['src/**/__tests__/**/*.test.ts'],
coverage: {

@@ -14,0 +14,0 @@ provider: 'v8',

/**
* 类型定义
*
*/

@@ -4,0 +5,0 @@ export interface ProjectConfig {

@@ -1,1 +0,1 @@

{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IAGvB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IAChD,WAAW,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC"}
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,aAAa;IAC5B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,OAAO,CAAC;IAGvB,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,SAAS,CAAC;IAChD,WAAW,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAC;IAClD,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,MAAM,cAAc,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,CAAC"}
/**
* 类型定义
*
*/
export {};
//# sourceMappingURL=types.js.map

@@ -1,1 +0,1 @@

{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}

@@ -1,1 +0,1 @@

{"version":3,"file":"fileSystem.d.ts","sourceRoot":"","sources":["../../src/utils/fileSystem.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA4B5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAOtF;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAGf"}
{"version":3,"file":"fileSystem.d.ts","sourceRoot":"","sources":["../../src/utils/fileSystem.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAK5C;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAQjF;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC5E;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAOtF;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,CAGf"}

@@ -25,17 +25,25 @@ /**

const targetDir = path.join(process.cwd(), config.projectName);
// 要复制的目录列表
const dirs = ['core', 'docs', 'evaluation', 'utils', 'config', 'examples'];
for (const dir of dirs) {
const srcPath = path.join(templateDir, dir);
const destPath = path.join(targetDir, dir);
if (await fs.pathExists(srcPath)) {
await fs.copy(srcPath, destPath, {
filter: (src) => {
const basename = path.basename(src);
// 过滤掉 .DS_Store 等系统文件
return !basename.startsWith('.') || basename === '.gitignore';
},
});
}
// 复制 src 目录(包含所有源代码)
const srcPath = path.join(templateDir, 'src');
const srcDest = path.join(targetDir, 'src');
if (await fs.pathExists(srcPath)) {
await fs.copy(srcPath, srcDest, {
filter: (src) => {
const basename = path.basename(src);
// 过滤掉 .DS_Store 等系统文件
return !basename.startsWith('.') || basename === '.gitignore';
},
});
}
// 单独复制 docs 目录(保留在根目录)
const docsPath = path.join(templateDir, 'docs');
const docsDest = path.join(targetDir, 'docs');
if (await fs.pathExists(docsPath)) {
await fs.copy(docsPath, docsDest, {
filter: (src) => {
const basename = path.basename(src);
return !basename.startsWith('.') || basename === '.gitignore';
},
});
}
// 复制 .env.example

@@ -42,0 +50,0 @@ const envSrc = path.join(templateDir, '.env.example');

@@ -1,1 +0,1 @@

{"version":3,"file":"fileSystem.js","sourceRoot":"","sources":["../../src/utils/fileSystem.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"fileSystem.js","sourceRoot":"","sources":["../../src/utils/fileSystem.ts"],"names":[],"mappings":"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"}
{
"name": "create-context-template",
"version": "1.0.1",
"version": "1.0.2",
"description": "CLI tool to scaffold LLM projects with context engineering architecture",

@@ -5,0 +5,0 @@ "type": "module",

+35
-23

@@ -9,2 +9,4 @@ # create-context-template

> 📚 **相关资源**:[上下文工程实践指南](https://github.com/WakeUp-Jin/Practical-Guide-to-Context-Engineering) -深入学习上下文工程的设计理念和最佳实践
## ✨ 特性

@@ -144,2 +146,3 @@

**核心方法:**
- `complete(messages, tools)` - 完整的 LLM 调用,支持工具

@@ -153,10 +156,10 @@ - `simpleChat(userMessage, systemPrompt)` - 简单对话

| 上下文类型 | 说明 | 用途 |
|----------|------|------|
| **ConversationContext** | 会话历史记录 | 维护对话连续性 |
| 上下文类型 | 说明 | 用途 |
| ------------------------------ | ------------ | ---------------- |
| **ConversationContext** | 会话历史记录 | 维护对话连续性 |
| **ToolMessageSequenceContext** | 工具调用序列 | 追踪工具使用历史 |
| **MemoryContext** | 用户记忆 | 长期记忆存储 |
| **SystemPromptContext** | 系统提示词 | 定义 AI 行为 |
| **StructuredOutputContext** | 结构化输出 | JSON 格式化输出 |
| **RelevantContext** | 相关上下文 | 动态相关信息 |
| **MemoryContext** | 用户记忆 | 长期记忆存储 |
| **SystemPromptContext** | 系统提示词 | 定义 AI 行为 |
| **StructuredOutputContext** | 结构化输出 | JSON 格式化输出 |
| **RelevantContext** | 相关上下文 | 动态相关信息 |

@@ -168,2 +171,3 @@ ### 3. 工具系统

**内置工具:**
- **ReadFileTool** - 读取文件内容

@@ -173,2 +177,3 @@ - **ListFilesTool** - 列出目录文件

**工具定义规范:**
- 标准化的工具接口

@@ -204,2 +209,3 @@ - JSON Schema 参数定义

**核心思想:**
1. **LLM 是核心** - 保证核心是 LLM,随着模型能力提升,Agent 效果自动变好

@@ -216,4 +222,4 @@ 2. **开发重心是上下文** - 极大发挥应用开发者的能力和创造力

```typescript
import { createLLMService } from './core/llm/index.js';
import { loadEnv } from './config/env.js';
import { createLLMService } from "./core/llm/index.js";
import { loadEnv } from "./config/env.js";

@@ -223,4 +229,4 @@ loadEnv();

const service = await createLLMService({
provider: 'deepseek',
model: 'deepseek-chat',
provider: "deepseek",
model: "deepseek-chat",
apiKey: process.env.DEEPSEEK_API_KEY,

@@ -230,7 +236,7 @@ });

const response = await service.simpleChat(
'Hello! Can you introduce yourself?',
'You are a helpful AI assistant.'
"Hello! Can you introduce yourself?",
"You are a helpful AI assistant."
);
console.log('Assistant:', response);
console.log("Assistant:", response);
```

@@ -241,5 +247,5 @@

```typescript
import { createLLMService } from './core/llm/index.js';
import { ContextManager } from './core/context/index.js';
import { ToolManager } from './core/tool/index.js';
import { createLLMService } from "./core/llm/index.js";
import { ContextManager } from "./core/context/index.js";
import { ToolManager } from "./core/tool/index.js";

@@ -253,11 +259,14 @@ // 初始化上下文和工具

// 创建 LLM 服务
const service = await createLLMService({
provider: 'deepseek',
model: 'deepseek-chat',
apiKey: process.env.DEEPSEEK_API_KEY,
}, toolManager);
const service = await createLLMService(
{
provider: "deepseek",
model: "deepseek-chat",
apiKey: process.env.DEEPSEEK_API_KEY,
},
toolManager
);
// 使用 generate 方法自动处理工具调用
const answer = await service.generate(
'请帮我读取 package.json 文件,并告诉我项目名称是什么'
"请帮我读取 package.json 文件,并告诉我项目名称是什么"
);

@@ -321,2 +330,3 @@

### v1.0 - 核心功能 ✅
- [x] CLI 交互界面

@@ -330,2 +340,3 @@ - [x] 多包管理器支持

### v1.1 - 增强功能 🚧
- [ ] 模板类型选择(full/minimal)

@@ -337,2 +348,3 @@ - [ ] 更多 LLM 提供商模板

### v2.0 - 扩展功能 🔮
- [ ] Web 服务器集成(Hono/Koa/Express)

@@ -339,0 +351,0 @@ - [ ] 插件系统

/**
* 环境变量配置管理
*
* 自动加载 .env 文件:
* - 使用 dotenv 库自动加载环境变量
* - 支持 Node.js 所有版本
*
* 优先级(从高到低):
* 1. .env.local (本地覆盖,不提交到 git)
* 2. .env.{NODE_ENV} (环境特定配置,如 .env.production)
* 3. .env (默认配置)
*
* 使用方式:
* ```typescript
* import { config } from './config/env.js';
* console.log(config.llm.deepseek.apiKey);
* ```
*/
import { config as dotenvConfig } from "dotenv";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
//加载环境变量文件
loadEnv();
/** 应用配置接口 */
export interface AppConfig {
/** Node 环境 */
nodeEnv: "development" | "production" | "test";
/** 日志配置 */
logging: {
level: "debug" | "info" | "warn" | "error";
enableConsole: boolean;
};
/** LLM 提供商配置 */
llm: {
/** DeepSeek 配置 */
deepseek: {
apiKey: string;
baseURL?: string;
};
};
/** 默认 LLM 配置 */
defaultProvider: string;
}
/** 获取环境变量,支持默认值 */
function getEnvVar(name: string, defaultValue?: string): string {
const value = process.env[name];
if (!value && defaultValue === undefined) {
throw new Error(`Environment variable ${name} is required but not set`);
}
return value || defaultValue!;
}
/** 获取环境变量布尔值 */
function getEnvBoolean(name: string, defaultValue: boolean = false): boolean {
const value = process.env[name];
if (!value) return defaultValue;
return value.toLowerCase() === "true" || value === "1";
}
/** 验证 NODE_ENV 值 */
function validateNodeEnv(env: string): "development" | "production" | "test" {
if (["development", "production", "test"].includes(env)) {
return env as "development" | "production" | "test";
}
console.warn(`Invalid NODE_ENV: ${env}, falling back to 'development'`);
return "development";
}
/** 验证日志级别 */
function validateLogLevel(level: string): "debug" | "info" | "warn" | "error" {
if (["debug", "info", "warn", "error"].includes(level)) {
return level as "debug" | "info" | "warn" | "error";
}
console.warn(`Invalid LOG_LEVEL: ${level}, falling back to 'info'`);
return "info";
}
/** 导出类型安全的配置对象 */
export const config: AppConfig = {
nodeEnv: validateNodeEnv(getEnvVar("NODE_ENV", "development")),
logging: {
level: validateLogLevel(getEnvVar("LOG_LEVEL", "info")),
enableConsole: getEnvBoolean("ENABLE_CONSOLE_LOG", true),
},
llm: {
deepseek: {
apiKey: getEnvVar("DEEPSEEK_API_KEY", ""),
baseURL: getEnvVar("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
},
},
defaultProvider: getEnvVar("DEFAULT_LLM_PROVIDER", "deepseek"),
};
/**
* 获取指定提供商的配置
* @param provider - LLM 提供商名称
* @returns 提供商配置对象
*/
export function getLLMKeyByProvider(provider: string) {
const providerKey = provider.toLowerCase();
let apiKey = config.llm[providerKey].apiKey;
if (!apiKey) {
throw new Error(`API key for provider "${provider}" not found. `);
}
return apiKey;
}
export function loadEnv() {
// 获取当前文件所在目录
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = resolve(__dirname, "..");
// 加载环境变量文件(按优先级)
const nodeEnv = process.env.NODE_ENV || "development";
// 1. 加载 .env.local(最高优先级)
dotenvConfig({ path: resolve(projectRoot, ".env.local"), override: false });
// 2. 加载 .env.{NODE_ENV}
if (nodeEnv !== "development") {
dotenvConfig({
path: resolve(projectRoot, `.env.${nodeEnv}`),
override: false,
});
}
// 3. 加载 .env(默认配置)
dotenvConfig({ path: resolve(projectRoot, ".env"), override: false });
}
/**
* Agent 模块统一导出
*/
export { SimpleAgent } from './SimpleAgent.js';
export type { AgentResult, AgentConfig } from './SimpleAgent.js';
export { MainAgent, SubAgent, createMultiAgentSystem } from './MultiAgent.js';
export type { MainAgentResult, SubAgentResult } from './MultiAgent.js';
/**
* 多智能体系统实现
* 演示主Agent协调多个子Agent完成任务的架构
*/
import { ContextManager, ContextType, Message } from '../context/index.js';
import { ToolManager } from '../tool/ToolManager.js';
import { ILLMService } from '../llm/types/index.js';
import { executeToolLoop } from '../llm/utils/executeToolLoop.js';
import { ExecutionHistoryContext } from '../context/modules/ExecutionHistoryContext.js';
import { eventBus } from '../../evaluation/EventBus.js';
import {
MAIN_AGENT_PROMPT,
SUB_AGENT_A_PROMPT,
SUB_AGENT_B_PROMPT,
} from '../promptManager/index.js';
/**
* 子Agent执行结果
*/
export interface SubAgentResult {
/** 子Agent名称 */
agentName: string;
/** 执行结果 */
result: string;
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* 主Agent执行结果
*/
export interface MainAgentResult {
/** 收集到的 Agent 名称列表 */
agents: string[];
/** 每个 Agent 调用的工具记录 */
tools: Record<string, string[]>;
/** 最终响应 */
finalResponse: string;
/** 子Agent执行记录 */
subAgentResults: SubAgentResult[];
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* 子Agent类
* 使用 executeToolLoop 执行任务(工具可为空)
*/
export class SubAgent {
private name: string;
private llmService: ILLMService;
private systemPrompt: string;
private maxLoops: number;
constructor(
name: string,
llmService: ILLMService,
systemPrompt: string,
maxLoops: number = 5
) {
this.name = name;
this.llmService = llmService;
this.systemPrompt = systemPrompt;
this.maxLoops = maxLoops;
}
/**
* 执行子Agent任务
* @param instruction - 主Agent下发的指令
*/
async run(instruction: string): Promise<SubAgentResult> {
console.log(`\n🤖 子Agent [${this.name}] 开始执行...`);
console.log(`📋 指令: ${instruction}`);
// 发射子Agent调用事件
eventBus.emit('agent:call', { agentName: this.name });
try {
// 初始化上下文管理器
const contextManager = new ContextManager();
await contextManager.init();
// 设置系统提示词
contextManager.add(
this.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置用户输入(来自主Agent的指令)
contextManager.setUserInput(instruction);
// 初始化工具管理器(工具为空,但仍使用 executeToolLoop)
const toolManager = new ToolManager();
// 清空默认工具,使子Agent无工具可用
toolManager.clear();
// 执行工具循环
const loopResult = await executeToolLoop(
this.llmService,
contextManager,
toolManager,
{
maxLoops: this.maxLoops,
agentName: this.name,
}
);
console.log(`✅ 子Agent [${this.name}] 执行完成`);
return {
agentName: this.name,
result: loopResult.result || '',
success: loopResult.success,
error: loopResult.error,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`❌ 子Agent [${this.name}] 执行失败: ${errorMessage}`);
return {
agentName: this.name,
result: '',
success: false,
error: errorMessage,
};
}
}
getName(): string {
return this.name;
}
}
/**
* 主Agent类
* 协调者角色,负责任务分配和结果汇总
* 不直接使用工具,而是通过调用子Agent完成任务
*/
export class MainAgent {
private name: string;
private llmService: ILLMService;
private systemPrompt: string;
private subAgentA: SubAgent;
private subAgentB: SubAgent;
private contextManager: ContextManager;
constructor(llmService: ILLMService, name: string = 'main_agent') {
this.name = name;
this.llmService = llmService;
this.systemPrompt = MAIN_AGENT_PROMPT;
this.contextManager = new ContextManager();
// 初始化两个子Agent
this.subAgentA = new SubAgent(
'researcher',
llmService,
SUB_AGENT_A_PROMPT
);
this.subAgentB = new SubAgent(
'executor',
llmService,
SUB_AGENT_B_PROMPT
);
}
/**
* 执行主Agent
* @param userInput - 用户输入
*/
async run(userInput: string): Promise<MainAgentResult> {
// 初始化上下文管理器
this.contextManager.init();
// 重置事件收集器
eventBus.reset();
// 发射主Agent调用事件
eventBus.emit('agent:call', { agentName: this.name });
console.log(`\n🎯 主Agent [${this.name}] 开始处理任务...`);
console.log(`📝 用户输入: ${userInput}`);
const subAgentResults: SubAgentResult[] = [];
try {
// 1. 调用子Agent A(研究者)
const instructionA = `请针对以下用户需求进行研究分析:\n${userInput}`;
const resultA = await this.subAgentA.run(instructionA);
subAgentResults.push(resultA);
let executionHistoryA = `
子Agent执行完成-${this.subAgentA.getName()}:
主Agent指令: ${instructionA}
输出: ${resultA.result}
`;
this.contextManager.add(executionHistoryA, ContextType.EXECUTION_HISTORY);
// 2. 调用子Agent B(执行者)
const instructionB = `基于以下用户需求,请提供具体的执行方案:\n${userInput}`;
const resultB = await this.subAgentB.run(instructionB);
subAgentResults.push(resultB);
// 记录子Agent B的执行结果
let executionHistoryB = `
子Agent执行完成-${this.subAgentB.getName()}:
主Agent指令: ${instructionB}
输出: ${resultB.result}
`;
this.contextManager.add(executionHistoryB, ContextType.EXECUTION_HISTORY);
// 3. 主Agent汇总结果
const finalResponse = await this.summarizeResults(userInput);
console.log(`\n✅ 主Agent [${this.name}] 任务完成`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse,
subAgentResults,
success: true,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`❌ 主Agent [${this.name}] 执行失败: ${errorMessage}`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: '',
subAgentResults,
success: false,
error: errorMessage,
};
}
}
/**
* 汇总子Agent结果
* 主Agent不使用工具,直接调用LLM进行汇总
*/
private async summarizeResults(userInput: string): Promise<string> {
console.log(`\n📊 主Agent 正在汇总子Agent结果...`);
// 构建汇总上下文
const contextManager = new ContextManager();
await contextManager.init();
// 设置系统提示词
contextManager.add(
this.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置汇总指令
const summarizeInstruction = `基于上述子Agent的研究分析和执行方案,请为用户提供一个综合性的最终答复。
用户原始需求:${userInput}
请整合各子Agent的输出,给出完整、清晰的最终响应。`;
contextManager.setUserInput(summarizeInstruction);
// 获取上下文并调用LLM(不使用工具)
const messages = contextManager.getContext();
const response = await this.llmService.complete(messages, []);
return response.content || '';
}
/**
* 获取执行历史
*/
getExecutionHistory(): Message[] {
return this.contextManager.get(ContextType.EXECUTION_HISTORY);
}
getName(): string {
return this.name;
}
}
/**
* 创建多智能体系统的便捷函数
*/
export function createMultiAgentSystem(llmService: ILLMService): MainAgent {
return new MainAgent(llmService);
}
/**
* 简单 Agent 实现
* 使用 ContextManager + ToolManager + LLMService 实现基本的工具调用循环
*/
import { ContextManager, ContextType } from '../context/index.js';
import { ToolManager } from '../tool/ToolManager.js';
import { ILLMService, ToolLoopResult } from '../llm/types/index.js';
import { executeToolLoop } from '../llm/utils/executeToolLoop.js';
import { eventBus } from '../../evaluation/EventBus.js';
import { SIMPLE_AGENT_PROMPT } from '../promptManager/index.js';
/**
* Agent 执行结果
*/
export interface AgentResult {
/** 收集到的 Agent 名称列表 */
agents: string[];
/** 每个 Agent 调用的工具记录 */
tools: Record<string, string[]>;
/** 最终响应内容 */
finalResponse: string;
/** 是否成功 */
success: boolean;
/** 错误信息 */
error?: string;
}
/**
* Agent 配置
*/
export interface AgentConfig {
/** Agent 名称 */
name?: string;
/** 最大工具调用循环次数 */
maxLoops?: number;
/** 系统提示词 */
systemPrompt?: string;
}
/**
* 简单 Agent 类
*
* 使用方式:
* ```typescript
* const agent = new SimpleAgent(llmService, { name: 'my_agent' });
* const result = await agent.run('列出当前目录的文件');
* ```
*/
export class SimpleAgent {
private llmService: ILLMService;
private contextManager: ContextManager;
private toolManager: ToolManager;
private config: Required<AgentConfig>;
constructor(llmService: ILLMService, config?: AgentConfig) {
this.llmService = llmService;
this.contextManager = new ContextManager();
this.toolManager = new ToolManager();
this.config = {
name: config?.name ?? 'simple_agent',
maxLoops: config?.maxLoops ?? 10,
systemPrompt: config?.systemPrompt ?? SIMPLE_AGENT_PROMPT,
};
}
/**
* 执行 Agent
* @param userInput - 用户输入
* @returns Agent 执行结果
*/
async run(userInput: string): Promise<AgentResult> {
// 重置事件收集器
eventBus.reset();
// 发射 Agent 调用事件
eventBus.emit('agent:call', { agentName: this.config.name });
try {
// 初始化上下文管理器
await this.contextManager.init();
// 设置系统提示词
this.contextManager.add(
this.config.systemPrompt,
ContextType.SYSTEM_PROMPT
);
// 设置用户输入
this.contextManager.setUserInput(userInput);
// 执行工具循环
const loopResult = await executeToolLoop(
this.llmService,
this.contextManager,
this.toolManager,
{
maxLoops: this.config.maxLoops,
agentName: this.config.name,
}
);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: loopResult.result || '',
success: loopResult.success,
error: loopResult.error,
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error(`Agent 执行失败: ${errorMessage}`);
// 从事件系统获取收集的数据
const collected = eventBus.getData();
return {
agents: collected.agents,
tools: collected.tools,
finalResponse: '',
success: false,
error: errorMessage,
};
}
}
/**
* 获取 Agent 名称
*/
getName(): string {
return this.config.name;
}
/**
* 获取工具管理器
*/
getToolManager(): ToolManager {
return this.toolManager;
}
/**
* 获取上下文管理器
*/
getContextManager(): ContextManager {
return this.contextManager;
}
}
import { describe, it, expect, beforeEach } from 'vitest';
import { ContextManager } from '../ContextManager.js';
import { ContextType } from '../types.js';
describe('ContextManager 测试', () => {
let contextManager: ContextManager;
beforeEach(async () => {
contextManager = new ContextManager();
await contextManager.init();
});
describe('初始化', () => {
it('应该正确初始化', async () => {
const manager = new ContextManager();
expect(manager.isInitialized()).toBe(false);
await manager.init();
expect(manager.isInitialized()).toBe(true);
});
it('不应该重复初始化', async () => {
await contextManager.init(); // 第二次调用
expect(contextManager.isInitialized()).toBe(true);
});
it('未初始化时应该抛出错误', () => {
const manager = new ContextManager();
expect(() => manager.add('test', ContextType.CONVERSATION)).toThrow(
'ContextManager 未初始化'
);
});
});
describe('添加上下文', () => {
it('应该能添加会话上下文', () => {
const message = { role: 'user', content: '你好' };
contextManager.add(message, ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(1);
});
it('应该能添加工具上下文', () => {
const toolCall = {
id: '1',
name: 'test_tool',
arguments: {},
result: 'success',
};
contextManager.add(toolCall, ContextType.TOOL);
expect(contextManager.getCount(ContextType.TOOL)).toBe(1);
});
it('应该能添加记忆上下文', () => {
const memory = { key: 'user_name', value: '张三' };
contextManager.add(memory, ContextType.MEMORY);
expect(contextManager.getCount(ContextType.MEMORY)).toBe(1);
});
});
describe('获取上下文', () => {
it('应该能获取指定类型的上下文', () => {
const message = { role: 'user', content: '测试消息' };
contextManager.add(message, ContextType.CONVERSATION);
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts.length).toBe(1);
});
it('应该能获取所有上下文', () => {
contextManager.add(
{ role: 'user', content: '你好' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'preference', value: 'dark_mode' },
ContextType.MEMORY
);
const allContexts = contextManager.getAll();
expect(allContexts.length).toBeGreaterThan(0);
});
it('空上下文应该返回空数组', () => {
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts).toEqual([]);
});
});
describe('统计信息', () => {
it('应该正确统计上下文数量', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'test', value: 'value' },
ContextType.MEMORY
);
const stats = contextManager.getStats();
expect(stats.total).toBe(3);
expect(stats.byType[ContextType.CONVERSATION]).toBe(2);
expect(stats.byType[ContextType.MEMORY]).toBe(1);
});
it('应该正确检查上下文是否存在', () => {
expect(contextManager.hasContext(ContextType.CONVERSATION)).toBe(false);
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
expect(contextManager.hasContext(ContextType.CONVERSATION)).toBe(true);
});
it('应该正确检查是否为空', () => {
expect(contextManager.isEmpty()).toBe(true);
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
expect(contextManager.isEmpty()).toBe(false);
});
});
describe('更新和删除', () => {
it('应该能更新指定上下文项', () => {
contextManager.add(
{ role: 'user', content: '原始消息' },
ContextType.CONVERSATION
);
contextManager.update(ContextType.CONVERSATION, 0, {
role: 'user',
content: '更新后的消息',
});
const contexts = contextManager.get(ContextType.CONVERSATION);
expect(contexts[0].content).toContain('更新后的消息');
});
it('应该能删除最后一项', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(2);
contextManager.removeLast(ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(1);
});
it('应该能清空指定类型的上下文', () => {
contextManager.add(
{ role: 'user', content: '消息1' },
ContextType.CONVERSATION
);
contextManager.add(
{ role: 'user', content: '消息2' },
ContextType.CONVERSATION
);
contextManager.clear(ContextType.CONVERSATION);
expect(contextManager.getCount(ContextType.CONVERSATION)).toBe(0);
});
it('应该能重置所有上下文', () => {
contextManager.add(
{ role: 'user', content: '消息' },
ContextType.CONVERSATION
);
contextManager.add(
{ key: 'test', value: 'value' },
ContextType.MEMORY
);
contextManager.reset();
expect(contextManager.isEmpty()).toBe(true);
});
});
describe('模块访问', () => {
it('应该能获取指定类型的模块实例', () => {
const module = contextManager.getModule(ContextType.CONVERSATION);
expect(module).toBeDefined();
expect(module.type).toBe(ContextType.CONVERSATION);
});
it('应该能获取所有模块', () => {
const modules = contextManager.getAllModules();
expect(modules.size).toBe(5); // 5 种上下文类型
});
});
describe('验证', () => {
it('应该通过验证', () => {
expect(contextManager.validate()).toBe(true);
});
it('未初始化的管理器不应通过验证', () => {
const manager = new ContextManager();
expect(manager.validate()).toBe(false);
});
});
describe('预留接口', () => {
it('压缩检查应该返回 false(未实现)', () => {
expect(contextManager.needsCompression()).toBe(false);
});
it('token 计数应该返回 0(未实现)', async () => {
const count = await contextManager.getTokenCount();
expect(count).toBe(0);
});
it('应该能导出为 JSON', () => {
contextManager.add(
{ role: 'user', content: '测试' },
ContextType.CONVERSATION
);
const json = contextManager.toJSON();
expect(json).toBeDefined();
expect(typeof json).toBe('string');
});
});
});
import { describe, it, expect, beforeEach } from 'vitest';
import { ConversationContext } from '../../modules/ConversationContext.js';
import { ContextType } from '../../types.js';
describe('ConversationContext 测试', () => {
let context: ConversationContext;
beforeEach(() => {
context = new ConversationContext();
});
it('应该正确初始化', () => {
expect(context.type).toBe(ContextType.CONVERSATION);
expect(context.isEmpty()).toBe(true);
expect(context.getCount()).toBe(0);
});
describe('添加消息', () => {
it('应该能添加用户消息', () => {
context.addUserMessage('你好');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('user');
expect(messages[0].content).toBe('你好');
});
it('应该能添加助手消息', () => {
context.addAssistantMessage('你好!有什么可以帮助你的吗?');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('assistant');
});
it('应该能添加系统消息', () => {
context.addSystemMessage('你是一个有帮助的助手');
expect(context.getCount()).toBe(1);
const messages = context.format();
expect(messages[0].role).toBe('system');
});
it('应该能添加带图片的消息', () => {
context.addUserMessage('这是什么?', {
url: 'https://example.com/image.jpg',
});
const messages = context.format();
expect(messages[0].content).toBeDefined();
expect(Array.isArray(messages[0].content)).toBe(true);
});
it('应该能添加带工具调用的助手消息', () => {
const toolCalls = [
{
id: 'call_1',
type: 'function',
function: { name: 'get_weather', arguments: '{"city": "北京"}' },
},
];
context.addAssistantMessage('让我查一下天气', toolCalls);
const messages = context.format();
expect(messages[0].tool_calls).toBeDefined();
expect(messages[0].tool_calls?.length).toBe(1);
});
});
describe('格式化', () => {
it('应该正确格式化为 LLM 消息格式', () => {
context.addUserMessage('你好');
context.addAssistantMessage('你好!');
const formatted = context.format();
expect(formatted).toHaveLength(2);
expect(formatted[0].role).toBe('user');
expect(formatted[1].role).toBe('assistant');
});
it('应该正确格式化带 base64 图片的消息', () => {
context.addUserMessage('分析这张图片', {
base64: 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==',
mimeType: 'image/png',
});
const formatted = context.format();
expect(Array.isArray(formatted[0].content)).toBe(true);
});
});
describe('查询方法', () => {
it('应该能获取最后一条消息', () => {
context.addUserMessage('第一条消息');
context.addUserMessage('第二条消息');
const lastMessage = context.getLastMessage();
expect(lastMessage?.content).toBe('第二条消息');
});
it('应该能按角色统计消息数量', () => {
context.addUserMessage('用户消息1');
context.addUserMessage('用户消息2');
context.addAssistantMessage('助手消息');
expect(context.getCountByRole('user')).toBe(2);
expect(context.getCountByRole('assistant')).toBe(1);
});
it('应该能获取所有用户消息', () => {
context.addUserMessage('用户消息1');
context.addAssistantMessage('助手消息');
context.addUserMessage('用户消息2');
const userMessages = context.getUserMessages();
expect(userMessages.length).toBe(2);
expect(userMessages[0].content).toBe('用户消息1');
expect(userMessages[1].content).toBe('用户消息2');
});
it('应该能获取所有助手消息', () => {
context.addUserMessage('用户消息');
context.addAssistantMessage('助手消息1');
context.addAssistantMessage('助手消息2');
const assistantMessages = context.getAssistantMessages();
expect(assistantMessages.length).toBe(2);
});
});
describe('基础操作', () => {
it('应该能清空所有消息', () => {
context.addUserMessage('测试消息');
expect(context.getCount()).toBe(1);
context.clear();
expect(context.getCount()).toBe(0);
expect(context.isEmpty()).toBe(true);
});
it('应该能删除最后一条消息', () => {
context.addUserMessage('消息1');
context.addUserMessage('消息2');
expect(context.getCount()).toBe(2);
context.removeLast();
expect(context.getCount()).toBe(1);
});
it('应该能更新指定消息', () => {
context.addUserMessage('原始消息');
context.update(0, {
role: 'user',
content: '更新后的消息',
});
const messages = context.format();
expect(messages[0].content).toBe('更新后的消息');
});
});
});
import { ContextType, ContextItem, IContext } from '../types.js';
import { logger } from '../../../utils/logger.js';
/**
* 基础上下文抽象类
* 提供所有上下文模块的通用功能
*/
export abstract class BaseContext<T = any> implements IContext<T> {
/** 上下文类型 */
public readonly type: ContextType;
/** 存储上下文项的数组 */
protected items: ContextItem<T>[] = [];
constructor(type: ContextType) {
this.type = type;
logger.debug(`初始化上下文模块: ${type}`);
}
/**
* 添加上下文项
*/
add(content: T, metadata?: Record<string, any>): void {
const item: ContextItem<T> = {
content,
type: this.type,
metadata,
timestamp: Date.now(),
id: this.generateId(),
};
this.items.push(item);
logger.debug(`添加上下文项到 ${this.type}: ${this.items.length} 项`);
}
/**
* 获取所有上下文项
*/
getAll(): ContextItem<T>[] {
return [...this.items];
}
/**
* 获取指定索引的上下文项
*/
get(index: number): ContextItem<T> | undefined {
if (index < 0 || index >= this.items.length) {
logger.warn(`索引 ${index} 超出范围 (0-${this.items.length - 1})`);
return undefined;
}
return this.items[index];
}
/**
* 清空所有上下文
*/
clear(): void {
const count = this.items.length;
this.items = [];
logger.debug(`清空 ${this.type} 上下文: 移除了 ${count} 项`);
}
/**
* 获取上下文数量
*/
getCount(): number {
return this.items.length;
}
/**
* 检查是否为空
*/
isEmpty(): boolean {
return this.items.length === 0;
}
/**
* 移除最后一项
*/
removeLast(): void {
if (this.items.length > 0) {
this.items.pop();
logger.debug(`从 ${this.type} 移除最后一项`);
} else {
logger.warn(`尝试从空的 ${this.type} 上下文移除项`);
}
}
/**
* 更新指定索引的上下文项
*/
update(index: number, content: T, metadata?: Record<string, any>): void {
if (index < 0 || index >= this.items.length) {
logger.error(`无法更新: 索引 ${index} 超出范围`);
throw new Error(`索引 ${index} 超出范围 (0-${this.items.length - 1})`);
}
const oldItem = this.items[index];
this.items[index] = {
...oldItem,
content,
metadata: metadata || oldItem.metadata,
timestamp: Date.now(),
};
logger.debug(`更新 ${this.type} 上下文项 [${index}]`);
}
/**
* 格式化为特定格式(由子类实现)
*/
abstract format(): any[];
/**
* TODO: 后续实现序列化
* 转换为 JSON
*/
toJSON(): string {
// TODO: 实现序列化逻辑
return JSON.stringify({
type: this.type,
items: this.items,
});
}
/**
* TODO: 后续实现序列化
* 从 JSON 恢复
*/
fromJSON(json: string): void {
// TODO: 实现反序列化逻辑
try {
const data = JSON.parse(json);
if (data.type === this.type && Array.isArray(data.items)) {
this.items = data.items;
logger.debug(`从 JSON 恢复 ${this.type} 上下文: ${this.items.length} 项`);
} else {
throw new Error('无效的 JSON 数据格式');
}
} catch (error: any) {
logger.error(`从 JSON 恢复失败: ${error.message}`);
throw error;
}
}
/**
* 生成唯一 ID
*/
protected generateId(): string {
return `${this.type}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
}
import { ContextType, ContextStats, IContext, Message } from "./types.js";
import { ConversationContext } from "./modules/ConversationContext.js";
import { ToolMessageSequenceContext } from "./modules/ToolMessageSequenceContext.js";
import { MemoryContext } from "./modules/MemoryContext.js";
import { SystemPromptContext } from "./modules/SystemPromptContext.js";
import { StructuredOutputContext } from "./modules/StructuredOutputContext.js";
import { RelevantContext } from "./modules/RelevantContext.js";
import { ExecutionHistoryContext } from "./modules/ExecutionHistoryContext.js";
/**
* 上下文管理器
* 负责统一管理所有类型的上下文模块
*/
export class ContextManager {
/** 存储所有上下文模块的映射 */
private contexts: Map<ContextType, IContext> = new Map();
/** 用户输入 */
private userInput: string = "";
/** 是否已初始化 */
private initialized: boolean = false;
constructor() {}
/**
* 初始化所有上下文模块
*/
async init(): Promise<void> {
if (this.initialized) {
return;
}
// 初始化所有上下文模块
this.contexts.set(
ContextType.CONVERSATION_HISTORY,
new ConversationContext()
);
this.contexts.set(
ContextType.TOOL_MESSAGE_SEQUENCE,
new ToolMessageSequenceContext()
);
this.contexts.set(ContextType.MEMORY, new MemoryContext());
this.contexts.set(ContextType.SYSTEM_PROMPT, new SystemPromptContext());
this.contexts.set(
ContextType.STRUCTURED_OUTPUT,
new StructuredOutputContext()
);
this.contexts.set(ContextType.RELEVANT_CONTEXT, new RelevantContext());
this.contexts.set(ContextType.EXECUTION_HISTORY, new ExecutionHistoryContext());
this.initialized = true;
}
/**
* 检查是否已初始化
*/
isInitialized(): boolean {
return this.initialized;
}
/** 设置用户输入 */
setUserInput(userInput: string): void {
this.userInput = userInput;
}
/**
* 统一的添加上下文方法
* @param content - 上下文内容
* @param type - 上下文类型
* @param metadata - 可选的元数据
*
* @example
* // SYSTEM_PROMPT - 字符串
* contextManager.add('你是一个助手', ContextType.SYSTEM_PROMPT);
*
* // MEMORY - { key, value } 格式
* contextManager.add({ key: 'user_name', value: '张三' }, ContextType.MEMORY);
*
* // CONVERSATION_HISTORY - { role, content } 格式
* contextManager.add({ role: 'user', content: '你好' }, ContextType.CONVERSATION_HISTORY);
*
* // TOOL_MESSAGE_SEQUENCE - Message 对象
* contextManager.add({ role: 'assistant', content: '...', tool_calls: [...] }, ContextType.TOOL_MESSAGE_SEQUENCE);
*
* // STRUCTURED_OUTPUT - 字符串
* contextManager.add('json', ContextType.STRUCTURED_OUTPUT);
*
* // RELEVANT_CONTEXT - { key, value } 格式
* contextManager.add({ key: 'scene', value: '客服场景' }, ContextType.RELEVANT_CONTEXT);
*/
add(content: any, type: ContextType, metadata?: Record<string, any>): void {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
this.validateContent(content, type);
context.add(content, metadata);
}
/**
* 校验 content 格式是否匹配 type
*/
private validateContent(content: any, type: ContextType): void {
switch (type) {
case ContextType.SYSTEM_PROMPT:
case ContextType.STRUCTURED_OUTPUT:
case ContextType.EXECUTION_HISTORY:
if (typeof content !== "string") {
throw new Error(`${type} 需要字符串类型`);
}
break;
case ContextType.TOOL_MESSAGE_SEQUENCE:
case ContextType.CONVERSATION_HISTORY:
if (!content?.role || content?.content === undefined) {
throw new Error(`${type} 需要 { role, content } 格式的 Message 对象`);
}
break;
case ContextType.MEMORY:
case ContextType.RELEVANT_CONTEXT:
if (!content?.key || content?.value === undefined) {
throw new Error(`${type} 需要 { key, value } 格式`);
}
break;
}
}
/**
* 获取指定类型的上下文
* @param type - 上下文类型
* @returns 格式化的上下文数据
*/
get(type: ContextType): any[] {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context.format();
}
/**
* 获取完整上下文(供 LLM 调用使用)
*
* 自动检测并组装所有可用的上下文类型:
* - system消息(systemPrompt + structuredOutput + relevantContext + memory + 会话历史摘要)
* - 用户输入
* - 工具消息序列
*
* @returns Message[] 格式的上下文数组
*/
getContext(): Message[] {
this.ensureInitialized();
const messages: Message[] = [];
// 1. system消息(包含历史摘要)
const systemMessage = this.buildSystemMessage();
if (systemMessage) {
messages.push(systemMessage);
}
// 2. 用户输入(当前请求)
if (this.userInput) {
messages.push({ role: "user", content: this.userInput });
}
// 3. 工具消息序列
const toolContext = this.contexts.get(ContextType.TOOL_MESSAGE_SEQUENCE);
if (toolContext && !toolContext.isEmpty()) {
const toolMessages = toolContext.format();
if (toolMessages && toolMessages.length > 0) {
messages.push(...toolMessages);
}
}
// 4. 执行历史
const executionHistory = this.getModule<ExecutionHistoryContext>(
ContextType.EXECUTION_HISTORY
);
const executionHistoryMessages = executionHistory.format();
if (executionHistoryMessages.length > 0) {
messages.push(...executionHistoryMessages);
}
return messages;
}
/**
* 构建system消息(私有方法)
* 自动拼接: systemPrompt + structuredOutput + relevantContext + memory + 会话历史摘要
*
* @returns Message 对象或 null
*/
private buildSystemMessage(): Message | null {
const parts: string[] = [];
// 1. 系统提示词(必需)
const systemPromptContext = this.getModule<SystemPromptContext>(
ContextType.SYSTEM_PROMPT
);
const systemPrompts = systemPromptContext.formatNormal();
if (systemPrompts) {
parts.push(systemPrompts);
}
// 2. 结构化输出要求
const structuredOutputContext = this.getModule<StructuredOutputContext>(
ContextType.STRUCTURED_OUTPUT
);
const structuredOutput = structuredOutputContext.format();
if (structuredOutput.length > 0) {
parts.push("\n【结构化输出要求】");
parts.push(structuredOutput[0]);
}
// 3. 相关上下文
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
const relevantInfo = relevantContext.format();
if (relevantInfo.length > 0) {
parts.push("\n【相关上下文】");
parts.push(relevantInfo.join("\n"));
}
// 4. 用户记忆
const memoryContext = this.getModule<MemoryContext>(ContextType.MEMORY);
const memories = memoryContext.format();
if (memories.length > 0) {
parts.push("\n【用户记忆】");
parts.push(memories.join("\n"));
}
// 5. 会话历史摘要
const historySummary = this.formatConversationHistoryForPrompt();
if (historySummary) {
parts.push(historySummary);
}
// 如果没有任何内容,返回null
if (parts.length === 0) {
return null;
}
return {
role: "system",
content: parts.join("\n"),
};
}
/**
* 将会话历史格式化为简洁列表(用于系统提示词)
* 只取最近15条,以简洁形式呈现
*
* @returns 格式化的历史摘要字符串,或 null
*/
private formatConversationHistoryForPrompt(): string | null {
const conversationContext = this.contexts.get(
ContextType.CONVERSATION_HISTORY
);
if (!conversationContext || conversationContext.isEmpty()) {
return null;
}
const history = conversationContext.format();
if (!history || history.length === 0) {
return null;
}
// 取最近15条
const recentHistory = history.slice(-15);
const lines: string[] = [];
lines.push("\n## 【历史对话参考】");
lines.push("");
lines.push("最近对话记录:");
recentHistory.forEach((msg, idx) => {
const role = msg.role === "user" ? "[用户]" : "[助手]";
let summary = "";
try {
const content = JSON.parse(msg.content as string);
if (msg.role === "user") {
summary = content.text || msg.content;
} else {
summary =
content.action === "finish"
? `已完成: ${(content.result || "").slice(0, 50)}...`
: content.action || (msg.content as string).slice(0, 50);
}
} catch {
summary =
typeof msg.content === "string"
? msg.content.slice(0, 50)
: String(msg.content);
}
lines.push(`${idx + 1}. ${role} ${summary}`);
});
return lines.join("\n");
}
/**
* 添加相关上下文
*/
addRelevantContext(key: string, value: any, description?: string): void {
this.ensureInitialized();
this.add({ key, value, description }, ContextType.RELEVANT_CONTEXT);
}
/**
* 获取相关上下文值
*/
getRelevantContextValue(key: string): any {
this.ensureInitialized();
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
return relevantContext.getValue(key);
}
/**
* 更新相关上下文
*/
updateRelevantContext(key: string, value: any): void {
this.ensureInitialized();
const relevantContext = this.getModule<RelevantContext>(
ContextType.RELEVANT_CONTEXT
);
relevantContext.updateValue(key, value);
}
/**
* 获取指定类型上下文的数量
*/
getCount(type: ContextType): number {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context.getCount();
}
/**
* 获取所有上下文的统计信息
*/
getStats(): ContextStats {
this.ensureInitialized();
let total = 0;
const byType: Record<string, number> = {};
this.contexts.forEach((context, type) => {
const count = context.getCount();
total += count;
byType[type] = count;
});
return {
total,
byType,
tokenCount: undefined,
};
}
/**
* 检查指定类型上下文是否存在
*/
hasContext(type: ContextType): boolean {
this.ensureInitialized();
const context = this.contexts.get(type);
return context ? !context.isEmpty() : false;
}
/**
* 检查上下文是否为空
*/
isEmpty(): boolean {
this.ensureInitialized();
return Array.from(this.contexts.values()).every((context) =>
context.isEmpty()
);
}
/**
* 清空指定类型的上下文
*/
clear(type: ContextType): void {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
context.clear();
}
/**
* 重置所有上下文(清空状态)
*/
reset(): void {
this.ensureInitialized();
this.contexts.forEach((context) => {
context.clear();
});
this.userInput = "";
}
/**
* 获取指定类型的上下文模块实例
*/
getModule<T extends IContext>(type: ContextType): T {
this.ensureInitialized();
const context = this.contexts.get(type);
if (!context) {
throw new Error(`未知的上下文类型: ${type}`);
}
return context as T;
}
/**
* 打印当前上下文状态(调试用)
*/
debug(): void {
this.ensureInitialized();
console.log("=== ContextManager 状态 ===");
console.log(`已初始化: ${this.initialized}`);
console.log(`总计: ${this.getStats().total} 项`);
console.log("\n各类型统计:");
this.contexts.forEach((context, type) => {
console.log(` ${type}: ${context.getCount()} 项`);
});
console.log("========================\n");
}
/**
* 确保已初始化
*/
private ensureInitialized(): void {
if (!this.initialized) {
throw new Error("ContextManager 未初始化,请先调用 init() 方法");
}
}
}
/**
* 上下文管理模块统一导出
*/
// 类型定义
export * from './types.js';
// 基础类
export { BaseContext } from './base/BaseContext.js';
// 核心管理器
export { ContextManager } from './ContextManager.js';
// 具体上下文模块
export { ConversationContext } from './modules/ConversationContext.js';
export { ToolMessageSequenceContext } from './modules/ToolMessageSequenceContext.js';
export { MemoryContext } from './modules/MemoryContext.js';
export { SystemPromptContext } from './modules/SystemPromptContext.js';
export { StructuredOutputContext } from './modules/StructuredOutputContext.js';
export { RelevantContext } from './modules/RelevantContext.js';
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, ConversationMessage } from '../types.js';
/**
* 会话上下文管理类
* 负责管理用户和助手之间的对话历史
*/
export class ConversationContext extends BaseContext<ConversationMessage> {
constructor() {
super(ContextType.CONVERSATION_HISTORY);
}
/**
* 格式化为 LLM 消息格式
* 转换为标准的 OpenAI 消息格式
*/
format(): any[] {
return this.items.map((item) => {
const message = item.content;
const formatted: any = {
role: message.role,
content: message.content,
};
// 添加图片数据(如果存在)
if (message.imageData) {
if (message.imageData.url) {
formatted.content = [
{ type: 'text', text: message.content },
{
type: 'image_url',
image_url: { url: message.imageData.url },
},
];
} else if (message.imageData.base64) {
formatted.content = [
{ type: 'text', text: message.content },
{
type: 'image_url',
image_url: {
url: `data:${message.imageData.mimeType || 'image/png'};base64,${message.imageData.base64}`,
},
},
];
}
}
// 添加工具调用(如果存在)
if (message.toolCalls && message.toolCalls.length > 0) {
formatted.tool_calls = message.toolCalls;
}
// 添加工具调用 ID(如果是工具消息)
if (message.role === 'tool' && item.metadata?.toolCallId) {
formatted.tool_call_id = item.metadata.toolCallId;
}
return formatted;
});
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, Message } from '../types.js';
/**
* 执行历史上下文管理类
* 专门用于主Agent记录子Agent的执行结果
* 存储字符串,格式化时转换为 user 消息
*/
export class ExecutionHistoryContext extends BaseContext<string> {
constructor() {
super(ContextType.EXECUTION_HISTORY);
}
/**
* 格式化为 Message 数组
* 将存储的字符串转换为 user 角色的消息
*/
format(): Message[] {
return this.items.map(item => ({
role: 'user' as const,
content: item.content,
}));
}
/**
* 获取最后一次执行记录(字符串形式)
*/
getLastExecutionRecord(): string | undefined {
if (this.items.length === 0) return undefined;
return this.items[this.items.length - 1].content;
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, MemoryItem } from '../types.js';
/**
* 用户记忆上下文管理类
* 负责管理用户偏好、历史信息等长期记忆
*/
export class MemoryContext extends BaseContext<MemoryItem> {
/** 用于快速查找的键值映射 */
private keyMap: Map<string, number> = new Map();
constructor() {
super(ContextType.MEMORY);
}
/**
* 重写 add 方法以维护 keyMap
*/
add(content: MemoryItem, metadata?: Record<string, any>): void {
// 检查是否已存在
const existingIndex = this.keyMap.get(content.key);
if (existingIndex !== undefined) {
// 更新已存在的记忆
this.update(existingIndex, content);
} else {
// 添加新记忆
super.add(content, metadata);
this.keyMap.set(content.key, this.items.length - 1);
}
}
/**
* 获取指定键的记忆值
*/
getMemory(key: string): any | undefined {
const index = this.keyMap.get(key);
if (index !== undefined) {
return this.items[index]?.content.value;
}
return undefined;
}
/**
* 检查是否存在指定键的记忆
*/
hasMemory(key: string): boolean {
return this.keyMap.has(key);
}
/**
* 格式化为自然语言描述
*/
format(): string[] {
// 按优先级排序(优先级越小越靠前)
const sortedItems = [...this.items].sort((a, b) => {
const priorityA = a.content.priority ?? 999;
const priorityB = b.content.priority ?? 999;
return priorityA - priorityB;
});
return sortedItems.map((item) => {
const memory = item.content;
let text = `${memory.key}: ${JSON.stringify(memory.value)}`;
if (memory.description) {
text += ` (${memory.description})`;
}
return text;
});
}
/**
* 清空所有记忆
*/
clear(): void {
super.clear();
this.keyMap.clear();
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType } from '../types.js';
/**
* 相关上下文项
*/
export interface RelevantContextItem {
/** 上下文键(如 "known_roles", "scene_info") */
key: string;
/** 上下文值(灵活类型) */
value: any;
/** 描述 */
description?: string;
}
/**
* 相关上下文管理类
*
* 用于存储问题相关的背景知识,这些知识:
* - 不属于用户记忆(非用户个人信息)
* - 不属于会话历史(非对话记录)
* - 不属于工具输出(非工具执行结果)
* - 是为了解决当前问题而需要的临时背景信息
*/
export class RelevantContext extends BaseContext<RelevantContextItem> {
constructor() {
super(ContextType.RELEVANT_CONTEXT);
}
/**
* 获取指定键的值
*/
getValue(key: string): any | undefined {
const item = this.items.find((item) => item.content.key === key);
return item?.content.value;
}
/**
* 更新指定键的值
*/
updateValue(key: string, value: any): void {
const index = this.items.findIndex((item) => item.content.key === key);
if (index !== -1) {
const existingItem = this.items[index].content;
this.update(index, { ...existingItem, value });
}
}
/**
* 检查是否存在指定键
*/
hasKey(key: string): boolean {
return this.items.some((item) => item.content.key === key);
}
/**
* 格式化为文本数组(用于拼接到 prompt)
*/
format(): string[] {
return this.items.map((item) => {
const { key, value, description } = item.content;
const desc = description ? ` (${description})` : '';
// 根据值类型格式化
if (Array.isArray(value)) {
return `${key}${desc}: ${value.join('、')}`;
} else if (typeof value === 'object' && value !== null) {
return `${key}${desc}: ${JSON.stringify(value)}`;
} else {
return `${key}${desc}: ${value}`;
}
});
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, StructuredOutputSchema } from '../types.js';
/**
* 结构化输出上下文管理类
* 负责管理结构化输出的 Schema 定义
*/
export class StructuredOutputContext extends BaseContext<StructuredOutputSchema> {
constructor() {
super(ContextType.STRUCTURED_OUTPUT);
}
/**
* 格式化为 LLM 理解的格式
* 转换为 OpenAI 的 response_format 格式
*/
format(): any[] {
if (this.isEmpty()) {
return [];
}
// item.content 就是字符串(如 'json')
const latestItem = this.items[this.items.length - 1];
const type = latestItem.content;
return [
{
type: type,
},
];
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, SystemPromptItem } from '../types.js';
/**
* 系统提示词上下文管理类
* 负责管理系统级提示词,支持多段提示词和优先级
*/
export class SystemPromptContext extends BaseContext<SystemPromptItem> {
constructor() {
super(ContextType.SYSTEM_PROMPT);
}
/**
* 格式化为单一系统消息
* 合并所有启用的提示词,按优先级排序
*/
format(): any[] {
if (this.items.length === 0) {
return [];
}
// item.content 就是字符串
const combinedContent = this.items.map((item) => item.content).join('\n\n');
return [
{
role: 'system',
content: combinedContent,
},
];
}
/**
* 格式化为普通字符串(用于合并到系统消息中)
* @returns 合并后的提示词文本,或 null
*/
formatNormal(): string | null {
if (this.items.length === 0) {
return null;
}
return this.items.map((item) => item.content).join('\n\n');
}
}
import { BaseContext } from '../base/BaseContext.js';
import { ContextType, Message } from '../types.js';
/**
* 工具消息序列上下文管理类
* 用于存储工具调用循环中的消息序列
* 顺序: assistant (含tool_calls) → tool → assistant → tool → ...
*/
export class ToolMessageSequenceContext extends BaseContext<Message> {
constructor() {
super(ContextType.TOOL_MESSAGE_SEQUENCE);
}
/**
* 格式化为 Message 数组 (API 格式)
*/
format(): Message[] {
return this.items.map((item) => item.content);
}
}
/**
* 上下文管理系统的核心类型定义
*/
/**
* 上下文类型枚举
*/
export enum ContextType {
/** 会话历史上下文 */
CONVERSATION_HISTORY = 'conversation_history',
/** 工具消息序列上下文 */
TOOL_MESSAGE_SEQUENCE = 'tool_message_sequence',
/** 用户记忆上下文 */
MEMORY = 'memory',
/** 系统提示词上下文 */
SYSTEM_PROMPT = 'system_prompt',
/** 结构化输出上下文 */
STRUCTURED_OUTPUT = 'structured_output',
/** 相关上下文 */
RELEVANT_CONTEXT = 'relevant_context',
/** 执行历史上下文 */
EXECUTION_HISTORY = 'execution_history',
}
/**
* 单个上下文项的结构
*/
export interface ContextItem<T = any> {
/** 上下文内容 */
content: T;
/** 上下文类型 */
type: ContextType;
/** 元数据 */
metadata?: Record<string, any>;
/** 时间戳 */
timestamp: number;
/** 唯一标识(可选) */
id?: string;
}
/**
* 统计信息结构
*/
export interface ContextStats {
/** 总计数量 */
total: number;
/** 按类型分组的数量 */
byType: Record<string, number>;
/** 总 token 数(预留) */
tokenCount?: number;
}
/**
* 图片数据
*/
export interface ImageData {
url?: string;
base64?: string;
mimeType?: string;
}
/**
* 消息角色
*/
export type MessageRole = 'user' | 'assistant' | 'system' | 'tool';
/**
* 标准消息结构(用于 LLM API 调用)
*/
export interface Message {
role: MessageRole;
content: string;
tool_calls?: any[];
tool_call_id?: string;
name?: string;
}
/**
* 会话消息结构
*/
export interface ConversationMessage {
role: MessageRole;
content: string;
imageData?: ImageData;
toolCalls?: any[];
}
/**
* 用户记忆项结构
*/
export interface MemoryItem {
/** 记忆键 */
key: string;
/** 记忆值 */
value: any;
/** 描述 */
description?: string;
/** 优先级 */
priority?: number;
}
/**
* 系统提示词项结构
*/
export interface SystemPromptItem {
/** 提示词内容 */
content: string;
/** 优先级(数字越小优先级越高) */
priority?: number;
/** 是否启用 */
enabled?: boolean;
}
/**
* 结构化输出 Schema
*/
export interface StructuredOutputSchema {
/** Schema 类型(如 json_schema) */
type: string;
/** Schema 定义 */
schema: Record<string, any>;
/** 输出格式(json、yaml 等) */
format?: string;
/** 是否严格模式 */
strict?: boolean;
}
/**
* 基础上下文接口
* 所有具体上下文模块必须实现的方法
*/
export interface IContext<T = any> {
/** 上下文类型 */
readonly type: ContextType;
/**
* 添加上下文项
* @param content - 内容
* @param metadata - 元数据
*/
add(content: T, metadata?: Record<string, any>): void;
/**
* 获取所有上下文项
*/
getAll(): ContextItem<T>[];
/**
* 获取指定索引的上下文项
* @param index - 索引
*/
get(index: number): ContextItem<T> | undefined;
/**
* 清空所有上下文
*/
clear(): void;
/**
* 获取上下文数量
*/
getCount(): number;
/**
* 检查是否为空
*/
isEmpty(): boolean;
/**
* 格式化为特定格式(由子类实现)
*/
format(): any[];
/**
* 移除最后一项
*/
removeLast(): void;
/**
* 更新指定索引的上下文项
* @param index - 索引
* @param content - 新内容
* @param metadata - 新元数据
*/
update(index: number, content: T, metadata?: Record<string, any>): void;
/**
* 转换为 JSON
*/
toJSON(): string;
/**
* 从 JSON 恢复
*/
fromJSON(json: string): void;
}
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import {
extractApiKey,
getBaseURL,
getDefaultContextWindow,
} from '../utils/helpers.js';
import { LLMConfig } from '../types/index.js';
describe('辅助函数测试', () => {
describe('extractApiKey', () => {
it('应该优先使用用户传递的 API Key', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
apiKey: 'user-provided-key',
};
expect(extractApiKey(config)).toBe('user-provided-key');
});
it('应该从环境变量获取 API Key(当用户未传递时)', () => {
// 注意:这个测试依赖于实际的环境变量加载
// 如果 .env 中有 DEEPSEEK_API_KEY,则会返回该值
const config: LLMConfig = {
provider: 'deepseek',
model: 'deepseek-chat',
};
// 由于依赖环境变量,我们只测试不抛出错误即可
// 或者可以 mock getLLMConfig 函数
const result = extractApiKey(config);
expect(typeof result).toBe('string');
});
it('无需 API Key 的提供商应该返回 not-required', () => {
const config: LLMConfig = {
provider: 'ollama',
model: 'llama2',
};
expect(extractApiKey(config)).toBe('not-required');
});
it('缺少 API Key 时应该抛出清晰的错误信息', () => {
const config: LLMConfig = {
provider: 'unknown-provider',
model: 'some-model',
};
expect(() => extractApiKey(config)).toThrow(
/API key for provider "unknown-provider" not found/
);
});
});
describe('getBaseURL', () => {
it('应该优先使用用户传递的 baseURL', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
baseURL: 'https://custom.api.com',
};
expect(getBaseURL(config)).toBe('https://custom.api.com');
});
it('应该从环境变量获取 baseURL(当用户未传递时)', () => {
const config: LLMConfig = {
provider: 'deepseek',
model: 'deepseek-chat',
};
// 会返回环境变量或默认值
const result = getBaseURL(config);
expect(result).toBeTruthy();
expect(typeof result).toBe('string');
});
it('应该返回默认 Base URL', () => {
const config: LLMConfig = {
provider: 'openai',
model: 'gpt-4',
};
expect(getBaseURL(config)).toBe('https://api.openai.com/v1');
});
it('应该返回 OpenRouter 的默认 URL', () => {
const config: LLMConfig = {
provider: 'openrouter',
model: 'gpt-4',
};
expect(getBaseURL(config)).toBe('https://openrouter.ai/api/v1');
});
it('应该返回 Ollama 的默认 URL', () => {
const config: LLMConfig = {
provider: 'ollama',
model: 'llama2',
};
expect(getBaseURL(config)).toBe('http://localhost:11434/v1');
});
it('未知提供商应该抛出错误', () => {
const config: LLMConfig = {
provider: 'unknown-provider',
model: 'some-model',
};
expect(() => getBaseURL(config)).toThrow(
/No base URL found for provider "unknown-provider"/
);
});
});
describe('getDefaultContextWindow', () => {
it('应该返回 GPT-4o 的上下文窗口', () => {
expect(getDefaultContextWindow('openai', 'gpt-4o')).toBe(128000);
});
it('应该返回默认上下文窗口', () => {
expect(getDefaultContextWindow('openai', 'unknown-model')).toBe(8192);
});
it('应该返回提供商的默认值', () => {
expect(getDefaultContextWindow('anthropic')).toBe(200000);
});
it('未知提供商应该返回默认值 8192', () => {
expect(getDefaultContextWindow('unknown-provider')).toBe(8192);
});
});
});
import { ILLMService, LLMConfig, UnifiedToolManager } from './types/index.js';
import { DeepSeekService } from './services/DeepSeekService.js';
import { extractApiKey, getBaseURL } from './utils/helpers.js';
/**
* 公共工厂函数:创建 LLM 服务实例(支持可选的工具管理器)
*
* @param config - LLM 配置(包括 provider、model、apiKey 等)
* @param toolManager - 可选的工具管理器实例
* @param eventManager - 可选的事件管理器实例
* @returns Promise<ILLMService> 实例
*
* @example
* ```typescript
* import { ToolManager } from '../tool/ToolManager.js';
*
* const toolManager = new ToolManager();
* const service = await createLLMService(
* {
* provider: 'deepseek',
* model: 'deepseek-chat',
* apiKey: 'your-api-key',
* maxIterations: 10
* },
* toolManager
* );
*
* // 使用服务
* const response = await service.complete(messages, tools);
* ```
*/
export async function createLLMService(
config: LLMConfig,
toolManager?: UnifiedToolManager,
eventManager?: any
): Promise<ILLMService> {
// 1. 创建服务实例
const service = await _createLLMService(config, toolManager);
// 2. 设置事件管理器(如果提供且服务支持)
if (eventManager && typeof (service as any).setEventManager === 'function') {
(service as any).setEventManager(eventManager);
}
return service;
}
/**
* 内部函数:创建 LLM 服务实例
*/
async function _createLLMService(
config: LLMConfig,
toolManager?: UnifiedToolManager
): Promise<ILLMService> {
// 1. 提取和验证 API Key
const apiKey = extractApiKey(config);
// 2. 获取 Base URL
const baseURL = getBaseURL(config);
// 3. 根据 provider 创建服务
switch (config.provider.toLowerCase()) {
case 'deepseek': {
// 使用 ES 动态导入 OpenAI SDK
const { default: OpenAI } = await import('openai');
const openai = new OpenAI({ apiKey, baseURL });
return new DeepSeekService(
openai,
config.model || 'deepseek-chat',
{
baseURL,
maxRetries: 3,
toolManager,
maxIterations: config.maxIterations || 5,
}
);
}
// 🟡 可扩展:其他提供商
// case 'openai':
// case 'anthropic':
// case 'qwen':
// case 'siliconflow':
// case 'openrouter':
default:
throw new Error(`Unsupported LLM provider: ${config.provider}`);
}
}
// 导出核心类型
export * from './types/index.js';
// 导出服务实现
export { DeepSeekService } from './services/DeepSeekService.js';
// 导出工厂方法
export { createLLMService } from './factory.js';
// 导出辅助函数
export {
extractApiKey,
getBaseURL,
getDefaultContextWindow,
sleep,
} from './utils/helpers.js';
import OpenAI from 'openai';
import {
ILLMService,
LLMResponse,
LLMChatOptions,
ImageData,
ToolSet,
UnifiedToolManager,
} from '../types/index.js';
import { logger } from '../../../utils/logger.js';
import { sleep } from '../utils/helpers.js';
/**
* DeepSeek LLM 服务
* 使用 OpenAI SDK(DeepSeek 兼容 OpenAI API)
*
* 提供两种使用方式:
* 1. complete() - 上下文补全(推荐)
* 2. generate() - 内置工具调用循环(可选)
*/
export class DeepSeekService implements ILLMService {
private client: OpenAI;
private model: string;
private maxRetries: number;
private toolManager?: UnifiedToolManager;
private maxIterations: number;
constructor(
openai: OpenAI,
model: string,
options?: {
baseURL?: string;
maxRetries?: number;
toolManager?: UnifiedToolManager;
maxIterations?: number;
}
) {
this.client = openai;
this.model = model;
this.maxRetries = options?.maxRetries || 3;
this.toolManager = options?.toolManager;
this.maxIterations = options?.maxIterations || 5;
logger.debug(`初始化 DeepSeekService: model=${model}`);
}
/**
* 核心方法:上下文补全
* 接收格式化的上下文(消息历史),返回模型响应
* @param messages - 上下文消息列表
* @param tools - 可用的工具定义列表
* @param options - 生成参数(temperature, maxTokens 等)
* @returns 模型响应(包含内容、工具调用、使用统计)
*/
async complete(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<LLMResponse> {
let attempt = 0;
while (attempt < this.maxRetries) {
attempt++;
try {
logger.debug(
`调用 DeepSeek API (尝试 ${attempt}/${this.maxRetries}): ${messages.length} 条消息, ${tools?.length || 0} 个工具`
);
const response = await this.client.chat.completions.create({
model: this.model,
messages,
tools: tools && tools.length > 0 ? tools : undefined,
tool_choice: options?.toolChoice,
temperature: options?.temperature,
max_tokens: options?.maxTokens,
top_p: options?.topP,
frequency_penalty: options?.frequencyPenalty,
presence_penalty: options?.presencePenalty,
stop: options?.stop,
response_format: options?.responseFormat,
});
const message = response.choices[0]?.message;
if (!message) {
throw new Error('DeepSeek API 返回空响应');
}
const result: LLMResponse = {
content: message.content || '',
toolCalls: message.tool_calls as any,
finishReason: response.choices[0]?.finish_reason,
usage: response.usage
? {
promptTokens: response.usage.prompt_tokens,
completionTokens: response.usage.completion_tokens,
totalTokens: response.usage.total_tokens,
}
: undefined,
};
logger.debug(
`DeepSeek 响应: ${result.content.slice(0, 100)}${result.content.length > 100 ? '...' : ''}, 工具调用: ${result.toolCalls?.length || 0}`
);
return result;
} catch (error: any) {
logger.error(
`DeepSeek API 调用失败 (${attempt}/${this.maxRetries}): ${error.message}`
);
if (attempt >= this.maxRetries) {
throw new Error(`DeepSeek API 调用失败: ${error.message}`);
}
// 指数退避
const delay = 500 * attempt;
logger.debug(`等待 ${delay}ms 后重试...`);
await sleep(delay);
}
}
throw new Error('Unreachable');
}
/**
* 简单对话:无工具,单次调用
* @param userInput - 用户输入
* @param systemPrompt - 可选的系统提示词
*/
async simpleChat(userInput: string, systemPrompt?: string): Promise<string> {
const messages: any[] = [];
if (systemPrompt) {
messages.push({ role: 'system', content: systemPrompt });
}
messages.push({ role: 'user', content: userInput });
const response = await this.complete(messages);
return response.content;
}
/**
* 获取配置信息
*/
getConfig(): { provider: string; model: string } {
return {
provider: 'deepseek',
model: this.model,
};
}
/**
* 完整方法:支持工具调用循环
* 需要在构造时传入 toolManager
* @param userInput - 用户输入
* @param imageData - 可选的图片数据
* @param _stream - 是否流式输出(暂未实现)
*/
async generate(
userInput: string,
imageData?: ImageData,
_stream?: boolean
): Promise<string> {
if (!this.toolManager) {
throw new Error(
'generate() 方法需要 toolManager,请在构造时传入或使用 chat() 方法'
);
}
// 初始化消息列表
const messages: any[] = [
{
role: 'system',
content: '你是一个有帮助的 AI 助手,可以使用工具来完成任务。',
},
];
// 添加用户消息
const userMessage: any = { role: 'user', content: userInput };
if (imageData) {
userMessage.content = [
{ type: 'text', text: userInput },
imageData.url
? { type: 'image_url', image_url: { url: imageData.url } }
: {
type: 'image_url',
image_url: {
url: `data:${imageData.mimeType || 'image/png'};base64,${imageData.base64}`,
},
},
];
}
messages.push(userMessage);
// 获取可用工具
const availableTools = await this.toolManager.getToolsForProvider(
'deepseek'
);
// 工具调用循环
let iteration = 0;
while (iteration < this.maxIterations) {
iteration++;
logger.debug(`工具调用迭代: ${iteration}/${this.maxIterations}`);
// 调用 LLM
const response = await this.complete(messages, availableTools, {
toolChoice: iteration === 1 ? 'auto' : undefined,
});
// 无工具调用,返回结果
if (!response.toolCalls || response.toolCalls.length === 0) {
logger.debug('无工具调用,返回最终结果');
return response.content;
}
// 记录思考内容
if (response.content) {
logger.info(`💭 助手思考: ${response.content}`);
}
// 添加助手消息(包含工具调用)
messages.push({
role: 'assistant',
content: response.content || null,
tool_calls: response.toolCalls,
});
// 执行所有工具调用
for (const toolCall of response.toolCalls) {
if (toolCall.type !== 'function' || !toolCall.function) {
continue;
}
logger.info(`🔧 使用工具: ${toolCall.function.name}`);
try {
const args = JSON.parse(toolCall.function.arguments);
const result = await this.toolManager.executeTool(
toolCall.function.name,
args
);
logger.info(`✅ 工具执行成功: ${JSON.stringify(result).slice(0, 200)}`);
// 添加工具执行结果
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify(result),
});
} catch (error: any) {
logger.error(`❌ 工具执行失败: ${error.message}`);
// 添加错误结果
messages.push({
role: 'tool',
tool_call_id: toolCall.id,
content: JSON.stringify({ error: error.message }),
});
}
}
}
throw new Error(`达到最大迭代次数 (${this.maxIterations})`);
}
/**
* 获取所有可用工具
*/
async getAllTools(): Promise<ToolSet> {
if (!this.toolManager) {
return {};
}
return this.toolManager.getAllTools();
}
/**
* 流式对话(预留接口)
* TODO: 实现流式响应
*/
async chatStream(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<AsyncIterable<LLMResponse>> {
throw new Error('流式响应暂未实现');
}
}
/** 图片数据 */
export interface ImageData {
url?: string;
base64?: string;
mimeType?: string;
}
/** 工具参数定义 */
export interface ToolParameters {
type: 'object';
properties: Record<string, any>;
required?: string[];
}
/** 工具定义 */
export interface Tool {
name: string;
description: string;
parameters: ToolParameters;
}
/** 工具集合 */
export type ToolSet = Record<string, Tool>;
/** 工具调用 */
export interface ToolCall {
id: string;
type: 'function';
function: {
name: string;
arguments: string;
};
}
/** LLM 配置 */
export interface LLMConfig {
provider: string;
model: string;
apiKey?: string;
maxIterations?: number;
baseURL?: string;
qwenOptions?: Record<string, any>;
aws?: Record<string, any>;
azure?: Record<string, any>;
}
/** MCP Manager 接口 (占位符) */
export interface MCPManager {
getAllTools(): Promise<ToolSet>;
executeTool(name: string, args: any): Promise<any>;
}
/** Context Manager - 导入真实实现 */
export { ContextManager } from '../../context/index.js';
/** Unified Tool Manager 接口 (占位符) */
export interface UnifiedToolManager {
getToolsForProvider(provider: string): Promise<any[]>;
getAllTools(): Promise<ToolSet>;
executeTool(name: string, args: any): Promise<any>;
}
/** Event Manager 接口 (占位符) */
export interface EventManager {
emit(event: string, data: any): void;
}
/**
* LLM 响应结构
*/
export interface LLMResponse {
/** 响应内容 */
content: string;
/** 工具调用列表 */
toolCalls?: ToolCall[];
/** 结束原因 */
finishReason?: string;
/** Token 使用统计 */
usage?: {
promptTokens: number;
completionTokens: number;
totalTokens: number;
};
}
/**
* LLM 调用选项
*/
export interface LLMChatOptions {
/** 温度参数 (0-2) */
temperature?: number;
/** 最大 token 数 */
maxTokens?: number;
/** 工具选择策略 */
toolChoice?: 'auto' | 'none' | 'required' | { type: 'function'; function: { name: string } };
/** Top P 采样 */
topP?: number;
/** 频率惩罚 */
frequencyPenalty?: number;
/** 存在惩罚 */
presencePenalty?: number;
/** 停止词 */
stop?: string | string[];
/** 结构化输出格式 */
responseFormat?: any;
}
/**
* 工具循环执行结果
*/
export interface ToolLoopResult {
/** 是否成功 */
success: boolean;
/** 最终结果 */
result?: string;
/** 错误信息 */
error?: string;
/** 循环次数 */
loopCount: number;
}
/**
* 工具循环配置
*/
export interface ToolLoopConfig {
/** 最大循环次数 */
maxLoops?: number;
/** Agent 名称 */
agentName?: string;
}
/** LLM Service 核心接口 */
export interface ILLMService {
/**
* 核心方法:上下文补全
* 接收格式化的上下文(消息历史),返回模型响应
* @param messages - 上下文消息列表
* @param tools - 可用的工具定义列表
* @param options - 生成参数(temperature, maxTokens 等)
* @returns 模型响应(包含内容、工具调用、使用统计)
*/
complete(
messages: any[],
tools?: any[],
options?: LLMChatOptions
): Promise<LLMResponse>;
/**
* 简单对话:无工具,单次调用
* @param userInput - 用户输入
* @param systemPrompt - 可选的系统提示词
*/
simpleChat(userInput: string, systemPrompt?: string): Promise<string>;
/**
* 完整方法:支持工具调用循环(可选实现)
* 内置工具调用、上下文管理、迭代执行
* @param userInput - 用户输入
* @param imageData - 可选的图片数据
* @param stream - 是否流式输出
*/
generate?(
userInput: string,
imageData?: ImageData,
stream?: boolean
): Promise<string>;
/** 获取配置 */
getConfig(): { provider: string; model: string };
/** 可选:事件管理器 */
setEventManager?(eventManager: EventManager): void;
/** @deprecated 由 Agent 管理工具 */
getAllTools?(): Promise<ToolSet>;
}
/**
* 工具循环执行器
* 简化版本的工具调用循环逻辑,供 Agent 使用
*/
import { ILLMService, ToolLoopResult, ToolLoopConfig } from '../types/index.js';
import { ContextManager } from '../../context/index.js';
import { ToolManager } from '../../tool/ToolManager.js';
import { ContextType, Message } from '../../context/types.js';
import { eventBus } from '../../../evaluation/EventBus.js';
import { deepParseArgs, sleep } from './helpers.js';
/**
* 执行工具循环
*
* 循环逻辑:
* 1. 从 ContextManager 获取当前上下文
* 2. 调用 LLM 完成
* 3. 如果返回工具调用,执行工具并更新上下文
* 4. 重复直到 LLM 返回最终结果或达到最大循环次数
*
* @param llmService - LLM 服务实例
* @param contextManager - 上下文管理器
* @param toolManager - 工具管理器
* @param config - 可选配置
* @returns 工具循环执行结果
*/
export async function executeToolLoop(
llmService: ILLMService,
contextManager: ContextManager,
toolManager: ToolManager,
config?: ToolLoopConfig
): Promise<ToolLoopResult> {
const maxLoops = config?.maxLoops ?? 10;
const agentName = config?.agentName ?? 'simple_agent';
let loopCount = 0;
console.log(`开始工具循环,最大循环次数: ${maxLoops}`);
while (loopCount < maxLoops) {
loopCount++;
console.log(`🔄 工具循环 ${loopCount}/${maxLoops}`);
try {
// 1. 获取当前上下文
const messages = contextManager.getContext();
// 2. 获取格式化的工具定义
const tools = toolManager.getFormattedTools();
console.log(`调用 LLM: ${messages.length} 条消息, ${tools.length} 个工具`);
// 3. 调用 LLM
const response = await llmService.complete(messages, tools);
// 4. 判断是否有工具调用
if (
response.finishReason === 'tool_calls' &&
response.toolCalls &&
response.toolCalls.length > 0
) {
console.log(`检测到 ${response.toolCalls.length} 个工具调用`);
// 记录 LLM 的思考内容(如果有)
if (response.content) {
console.log(`💭 LLM 思考: ${response.content.slice(0, 100)}...`);
}
// 5. 构建 assistant 消息(包含工具调用)
const assistantMessage: Message = {
role: 'assistant',
content: response.content || '',
tool_calls: response.toolCalls,
};
// 6. 添加到 TOOL_MESSAGE_SEQUENCE
contextManager.add(assistantMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
// 7. 执行所有工具调用
for (const toolCall of response.toolCalls) {
const toolName = toolCall.function.name;
try {
// 解析参数
const rawArgs = toolCall.function.arguments
? JSON.parse(toolCall.function.arguments)
: {};
const args = deepParseArgs(rawArgs);
console.log(`🔧 执行工具: ${toolName}`);
// 触发工具调用事件(用于评估系统)
eventBus.emit('tool:call', {
agentName,
toolName,
});
// 执行工具
const result = await toolManager.execute(toolName, args);
const resultString = JSON.stringify(result);
console.log(`✅ 工具结果: ${resultString.slice(0, 200)}...`);
// 8. 构建 tool 消息
const toolMessage: Message = {
role: 'tool',
tool_call_id: toolCall.id,
name: toolName,
content: resultString,
};
// 9. 添加到 TOOL_MESSAGE_SEQUENCE
contextManager.add(toolMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
// 等待一下避免请求过快
await sleep(500);
} catch (error) {
console.error(`❌ 工具执行失败: ${toolName}`, error);
// 将错误信息作为工具结果返回
const errorMessage: Message = {
role: 'tool',
tool_call_id: toolCall.id,
name: toolName,
content: JSON.stringify({
error: error instanceof Error ? error.message : String(error),
}),
};
contextManager.add(errorMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
}
}
// 继续循环
continue;
}
// 10. 没有工具调用,返回最终结果
console.log(`✅ 工具循环完成,循环次数: ${loopCount}`);
console.log(`最终结果: ${response.content?.slice(0, 200)}...`);
// 添加最终的 assistant 消息
const finalMessage: Message = {
role: 'assistant',
content: response.content,
};
contextManager.add(finalMessage, ContextType.TOOL_MESSAGE_SEQUENCE);
return {
success: true,
result: response.content,
loopCount,
};
} catch (error) {
console.error(`❌ LLM 调用失败 (循环 ${loopCount}):`, error);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
loopCount,
};
}
}
// 超过最大循环次数
console.warn(`⚠️ 超过最大循环次数 (${maxLoops})`);
return {
success: false,
error: `超过最大循环次数 (${maxLoops})`,
loopCount,
};
}
import { LLMConfig } from "../types/index.js";
import {
config as envConfig,
getLLMKeyByProvider,
} from "../../../config/env.js";
/**
* 提取 API Key
*
* 优先级(从高到低):
* 1. 用户传递的 config.apiKey(显式配置)
* 2. 环境变量中的配置(通过 provider 自动查找)
* 3. 如果都没有且该 provider 需要 API Key,则抛出错误
*
* @param config - LLM 配置
* @returns API Key 字符串
*/
export function extractApiKey(config: LLMConfig): string {
const provider = config.provider.toLowerCase();
// 无需 API Key 的提供商
if (["ollama", "lmstudio", "aws"].includes(provider)) {
return "not-required";
}
// 1. 优先使用用户传递的 API Key
if (config.apiKey) {
return config.apiKey;
}
// 2. 尝试从环境变量配置中获取
const providerConfigKey = getLLMKeyByProvider(provider);
return providerConfigKey;
}
/**
* 获取提供商的 Base URL
*
* 优先级(从高到低):
* 1. 用户传递的 config.baseURL(显式配置)
* 2. 环境变量中的配置(通过 provider 自动查找)
* 3. 硬编码的默认 Base URL
*
* @param config - LLM 配置
* @returns Base URL 字符串
*
*/
export function getBaseURL(config: LLMConfig): string {
// 1. 优先使用用户传递的 baseURL
if (config.baseURL) {
return config.baseURL;
}
const provider = config.provider.toLowerCase();
// 3. 硬编码的默认 Base URL(兜底)
const defaultBaseURLs: Record<string, string> = {
deepseek: "https://api.deepseek.com",
openai: "https://api.openai.com/v1",
anthropic: "https://api.anthropic.com",
siliconflow: "https://api.siliconflow.cn/v1",
qwen: "https://dashscope.aliyuncs.com/compatible-mode/v1",
openrouter: "https://openrouter.ai/api/v1",
ollama: "http://localhost:11434/v1",
lmstudio: "http://localhost:1234/v1",
};
const baseURL = defaultBaseURLs[provider];
if (!baseURL) {
throw new Error(
`No base URL found for provider "${provider}". ` +
`Please pass baseURL in config: { provider: '${provider}', model: '...', baseURL: 'https://...' }`
);
}
return baseURL;
}
/** 获取默认上下文窗口大小 */
export function getDefaultContextWindow(
provider: string,
model?: string
): number {
const defaults: Record<string, Record<string, number>> = {
openai: {
"gpt-4o": 128000,
"gpt-4": 8192,
"gpt-3.5-turbo": 16385,
default: 8192,
},
anthropic: { default: 200000 },
gemini: { default: 1000000 },
deepseek: { default: 128000 },
ollama: { default: 8192 },
};
const providerDefaults = defaults[provider.toLowerCase()] || {
default: 8192,
};
return providerDefaults[model || "default"] || providerDefaults.default;
}
/** 睡眠函数 */
export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
/**
* 深度解析参数:递归检查字符串类型的参数值,尝试 JSON 解析
* 用于处理 Claude 等模型返回嵌套 JSON 字符串的情况
*
* 例如:{ bgmRequests: "[{...}]" } → { bgmRequests: [{...}] }
*/
export function deepParseArgs(args: any): any {
if (typeof args === "string") {
// 尝试解析看起来像 JSON 数组或对象的字符串
const trimmed = args.trim();
if (trimmed.startsWith("[") || trimmed.startsWith("{")) {
// 第一次尝试:直接解析
try {
const parsed = JSON.parse(trimmed);
console.log("[deepParseArgs] ✅ 第一次尝试解析成功");
return deepParseArgs(parsed);
} catch (e) {
console.log(
"[deepParseArgs] ❌ 第一次尝试解析失败:",
(e as Error).message
);
console.log(
"[deepParseArgs] 字符串前100字符:",
trimmed.substring(0, 100)
);
// 第二次尝试:处理 Claude 模型返回的双重转义问题
// 将 \\n 转换为 \n,\\\" 转换为 \"
try {
const unescaped = trimmed
.replace(/\\\\n/g, "\\n")
.replace(/\\\\"/g, '\\"');
console.log("[deepParseArgs] 尝试处理双重转义后解析...");
const parsed = JSON.parse(unescaped);
console.log("[deepParseArgs] ✅ 第二次尝试(处理转义后)解析成功");
return deepParseArgs(parsed);
} catch (e2) {
console.log(
"[deepParseArgs] ❌ 第二次尝试也失败:",
(e2 as Error).message
);
// 都失败了,返回原始字符串
return args;
}
}
}
return args;
}
if (Array.isArray(args)) {
return args.map(deepParseArgs);
}
if (args && typeof args === "object") {
const result: any = {};
for (const [key, value] of Object.entries(args)) {
result[key] = deepParseArgs(value);
}
return result;
}
return args;
}
/**
* 提示词管理模块
*/
export {
SIMPLE_AGENT_PROMPT,
MAIN_AGENT_PROMPT,
SUB_AGENT_A_PROMPT,
SUB_AGENT_B_PROMPT,
} from './prompts.js';
/**
* 提示词常量定义
* 集中管理各类 Agent 的系统提示词
*/
/**
* SimpleAgent 默认提示词
*/
export const SIMPLE_AGENT_PROMPT = `你是一个有用的 AI 助手,可以使用工具来帮助用户完成任务。
你可以使用以下工具:
- list_files: 列出指定目录下的文件和文件夹
- read_file: 读取指定文件的内容
请根据用户的需求,选择合适的工具来完成任务。`;
/**
* 主 Agent 提示词(协调者)
* 用于多智能体系统中的主控 Agent
*/
export const MAIN_AGENT_PROMPT = `你是一个任务协调者,负责分析用户需求并协调子Agent完成任务。
你的职责:
1. 分析用户的任务需求
2. 将任务分配给合适的子Agent
3. 汇总子Agent的执行结果
4. 向用户提供最终的综合答复
你不直接执行具体任务,而是通过协调子Agent来完成工作。`;
/**
* 子 Agent A 提示词(研究者)
*/
export const SUB_AGENT_A_PROMPT = `你是一个研究分析专家,擅长:
- 收集和整理信息
- 分析问题的各个方面
- 提供详细的背景知识
请根据主Agent的指令,提供专业的研究分析结果。`;
/**
* 子 Agent B 提示词(执行者)
*/
export const SUB_AGENT_B_PROMPT = `你是一个执行专家,擅长:
- 制定具体的执行方案
- 提供实用的建议和步骤
- 给出可操作的解决方案
请根据主Agent的指令,提供具体的执行方案。`;
import { describe, it, expect, beforeEach } from 'vitest';
import { ToolManager } from '../ToolManager.js';
describe('ToolManager 测试', () => {
let toolManager: ToolManager;
beforeEach(() => {
toolManager = new ToolManager();
});
describe('基础功能', () => {
it('应该成功创建 ToolManager 实例', () => {
expect(toolManager).toBeDefined();
expect(toolManager).toBeInstanceOf(ToolManager);
});
it('应该自动注册所有工具', () => {
const toolNames = toolManager.getToolNames();
expect(toolNames).toContain('read_file');
expect(toolNames).toContain('grep_search');
});
it('应该返回正确的工具数量', () => {
const tools = toolManager.getTools();
expect(tools.length).toBeGreaterThan(0);
});
});
describe('工具查询', () => {
it('应该能够通过名称获取工具', () => {
const tool = toolManager.getTool('read_file');
expect(tool).toBeDefined();
expect(tool?.name).toBe('read_file');
expect(tool?.category).toBe('filesystem');
});
it('应该在工具不存在时返回 undefined', () => {
const tool = toolManager.getTool('non_existent_tool');
expect(tool).toBeUndefined();
});
it('应该正确检查工具是否存在', () => {
expect(toolManager.hasTool('read_file')).toBe(true);
expect(toolManager.hasTool('non_existent_tool')).toBe(false);
});
it('应该返回所有工具名称', () => {
const toolNames = toolManager.getToolNames();
expect(toolNames).toBeInstanceOf(Array);
expect(toolNames.length).toBeGreaterThan(0);
expect(toolNames).toContain('read_file');
});
});
describe('工具统计', () => {
it('应该返回正确的统计信息', () => {
const stats = toolManager.getStats();
expect(stats).toHaveProperty('totalTools');
expect(stats).toHaveProperty('categories');
expect(stats).toHaveProperty('toolNames');
expect(stats.totalTools).toBeGreaterThan(0);
});
it('应该按分类统计工具数量', () => {
const stats = toolManager.getStats();
expect(stats.categories).toHaveProperty('filesystem');
expect(stats.categories).toHaveProperty('search');
});
});
describe('工具执行', () => {
it('应该在工具不存在时抛出错误', async () => {
await expect(
toolManager.execute('non_existent_tool', {})
).rejects.toThrow(/not found/);
});
it('应该在错误信息中列出可用工具', async () => {
try {
await toolManager.execute('non_existent_tool', {});
} catch (error: any) {
expect(error.message).toContain('Available:');
expect(error.message).toContain('read_file');
}
});
});
describe('工具结构验证', () => {
it('每个工具应该有必需的属性', () => {
const tools = toolManager.getTools();
tools.forEach((tool) => {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('category');
expect(tool).toHaveProperty('internal');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('version');
expect(tool).toHaveProperty('parameters');
expect(tool).toHaveProperty('handler');
expect(typeof tool.handler).toBe('function');
});
});
it('每个工具的参数定义应该是有效的 JSON Schema', () => {
const tools = toolManager.getTools();
tools.forEach((tool) => {
expect(tool.parameters).toHaveProperty('type');
expect(tool.parameters.type).toBe('object');
expect(tool.parameters).toHaveProperty('properties');
});
});
});
});
import { describe, it, expect } from 'vitest';
import { formatToolForLLM, InternalTool } from '../types.js';
describe('types 测试', () => {
describe('formatToolForLLM', () => {
it('应该正确格式化工具定义', () => {
const mockTool: InternalTool = {
name: 'test_tool',
category: 'test',
internal: true,
description: 'Test tool description',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
arg1: {
type: 'string',
description: 'Test argument',
},
},
required: ['arg1'],
},
handler: async () => ({ success: true }),
};
const formatted = formatToolForLLM(mockTool);
expect(formatted).toEqual({
name: 'test_tool',
category: 'test',
description: 'Test tool description',
version: '1.0.0',
parameters: mockTool.parameters,
});
});
it('应该不包含 handler 和其他内部属性', () => {
const mockTool: InternalTool = {
name: 'test_tool',
category: 'test',
internal: true,
description: 'Test tool',
version: '1.0.0',
parameters: {
type: 'object',
properties: {},
},
handler: async () => ({ success: true }),
renderResultForAssistant: (result) => JSON.stringify(result),
needsPermissions: () => false,
};
const formatted = formatToolForLLM(mockTool);
expect(formatted).not.toHaveProperty('handler');
expect(formatted).not.toHaveProperty('internal');
expect(formatted).not.toHaveProperty('renderResultForAssistant');
expect(formatted).not.toHaveProperty('needsPermissions');
});
it('应该保留完整的参数 Schema 结构', () => {
const complexParameters = {
type: 'object' as const,
properties: {
nested: {
type: 'object' as const,
properties: {
deep: {
type: 'string' as const,
},
},
},
array: {
type: 'array' as const,
items: {
type: 'number' as const,
},
},
},
required: ['nested'],
};
const mockTool: InternalTool = {
name: 'complex_tool',
category: 'test',
internal: true,
description: 'Complex tool',
version: '1.0.0',
parameters: complexParameters,
handler: async () => ({}),
};
const formatted = formatToolForLLM(mockTool);
expect(formatted.parameters).toEqual(complexParameters);
});
});
});
/**
* 工具模块统一导出
*/
// 类型定义
export * from './types.js';
// 工具管理器
export { ToolManager } from './ToolManager.js';
// 具体工具
export { ListFilesTool } from './ListFiles/definitions.js';
export type { ListFilesArgs, ListFilesResult } from './ListFiles/executors.js';
export { ReadFileTool } from './ReadFile/definitions.js';
export type { ReadFileArgs, ReadFileResult } from './ReadFile/executors.js';
import { InternalTool } from "../types";
import { listFilesExecutor } from "./executors";
import { renderResultForAssistant } from "./executors";
import { ListFilesArgs, ListFilesResult } from "./executors";
export const ListFilesTool: InternalTool<ListFilesArgs, ListFilesResult> = {
name: 'list_files',
category: 'filesystem',
internal: true,
description: '列出指定目录下的文件和文件夹。如果不指定目录,则列出当前工作目录。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
directory: {
type: 'string',
description: '要列出内容的目录路径,默认为当前工作目录',
},
},
required: [],
},
handler: listFilesExecutor,
renderResultForAssistant: renderResultForAssistant,
};
/**
* 列出目录文件工具
* 列出指定目录下的文件和文件夹
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ListFilesArgs {
/** 目录路径,默认为当前工作目录 */
directory?: string;
}
export interface ListFilesResult {
/** 目录路径 */
directory: string;
/** 文件列表 */
files: Array<{
name: string;
type: 'file' | 'directory';
size?: number;
}>;
/** 文件总数 */
totalCount: number;
}
/**
* 列出文件执行器
* @param args - 列出文件参数
* @param config - 配置
* @returns - 列出文件结果
*/
export async function listFilesExecutor(args: ListFilesArgs, config: any): Promise<ListFilesResult> {
const cwd = config?.cwd || process.cwd();
const targetDir = args.directory ? path.resolve(cwd, args.directory) : cwd;
// 检查目录是否存在
if (!fs.existsSync(targetDir)) {
throw new Error(`目录不存在: ${targetDir}`);
}
// 检查是否是目录
const stats = fs.statSync(targetDir);
if (!stats.isDirectory()) {
throw new Error(`路径不是目录: ${targetDir}`);
}
// 读取目录内容
const entries = fs.readdirSync(targetDir, { withFileTypes: true });
const files = entries
.filter((entry) => !entry.name.startsWith('.')) // 过滤隐藏文件
.map((entry) => {
const fullPath = path.join(targetDir, entry.name);
const result: {
name: string;
type: 'file' | 'directory';
size?: number;
} = {
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
};
// 对于文件,获取大小
if (entry.isFile()) {
try {
const fileStats = fs.statSync(fullPath);
result.size = fileStats.size;
} catch {
// 忽略权限错误
}
}
return result;
})
.sort((a, b) => {
// 目录在前,文件在后
if (a.type !== b.type) {
return a.type === 'directory' ? -1 : 1;
}
return a.name.localeCompare(b.name);
});
return {
directory: targetDir,
files,
totalCount: files.length,
};
}
/**
* 格式化工具结果
* @param result - 列表文件结果
* @returns
*/
export function renderResultForAssistant(result: ListFilesResult): string {
const lines = [`目录: ${result.directory}`, `共 ${result.totalCount} 个项目:`, ''];
for (const file of result.files) {
const icon = file.type === 'directory' ? '📁' : '📄';
const size = file.size !== undefined ? ` (${formatSize(file.size)})` : '';
lines.push(`${icon} ${file.name}${size}`);
}
return lines.join('\n');
}
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
import { InternalTool } from "../types";
import { readFileExecutor } from "./executors";
import { renderResultForAssistant } from "./executors";
import { ReadFileArgs, ReadFileResult } from "./executors";
export const ReadFileTool: InternalTool<ReadFileArgs, ReadFileResult> = {
name: 'read_file',
category: 'filesystem',
internal: true,
description: '读取指定文件的内容。支持文本文件,返回文件内容、大小和行数。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: '要读取的文件路径',
},
encoding: {
type: 'string',
description: '文件编码,默认为 utf-8',
},
},
required: ['filePath'],
},
handler: readFileExecutor,
renderResultForAssistant: renderResultForAssistant,
};
/**
* 读取文件内容工具
* 读取指定文件的内容
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ReadFileArgs {
/** 文件路径 */
filePath: string;
/** 编码,默认 utf-8 */
encoding?: BufferEncoding;
}
export interface ReadFileResult {
/** 文件路径 */
filePath: string;
/** 文件内容 */
content: string;
/** 文件大小(字节) */
size: number;
/** 行数 */
lineCount: number;
}
/**
* 读取文件执行器
* @param args - 读取文件参数
* @param config - 配置
* @returns - 读取文件结果
*/
export async function readFileExecutor(args: ReadFileArgs, config: any): Promise<ReadFileResult> {
const cwd = config?.cwd || process.cwd();
const targetPath = path.resolve(cwd, args.filePath);
const encoding = args.encoding || 'utf-8';
// 检查文件是否存在
if (!fs.existsSync(targetPath)) {
throw new Error(`文件不存在: ${targetPath}`);
}
// 检查是否是文件
const stats = fs.statSync(targetPath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${targetPath}`);
}
// 检查文件大小,避免读取过大的文件
const maxSize = 1024 * 1024; // 1MB
if (stats.size > maxSize) {
throw new Error(`文件太大 (${formatSize(stats.size)}),最大支持 1MB`);
}
// 读取文件内容
const content = fs.readFileSync(targetPath, encoding);
const lineCount = content.split('\n').length;
return {
filePath: targetPath,
content,
size: stats.size,
lineCount,
};
}
/**
* 格式化工具结果
* @param result - 读取文件结果
* @returns - 读取文件结果
*/
export function renderResultForAssistant(result: ReadFileResult): string {
return [
`文件: ${result.filePath}`,
`大小: ${formatSize(result.size)}`,
`行数: ${result.lineCount}`,
'',
'--- 内容 ---',
result.content,
].join('\n');
}
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
/**
* 读取文件内容工具
* 读取指定文件的内容
*/
import * as fs from 'fs';
import * as path from 'path';
import { InternalTool } from '../types.js';
export interface ReadFileArgs {
/** 文件路径 */
filePath: string;
/** 编码,默认 utf-8 */
encoding?: BufferEncoding;
}
export interface ReadFileResult {
/** 文件路径 */
filePath: string;
/** 文件内容 */
content: string;
/** 文件大小(字节) */
size: number;
/** 行数 */
lineCount: number;
}
export const ReadFileTool: InternalTool<ReadFileArgs, ReadFileResult> = {
name: 'read_file',
category: 'filesystem',
internal: true,
description: '读取指定文件的内容。支持文本文件,返回文件内容、大小和行数。',
version: '1.0.0',
parameters: {
type: 'object',
properties: {
filePath: {
type: 'string',
description: '要读取的文件路径',
},
encoding: {
type: 'string',
description: '文件编码,默认为 utf-8',
},
},
required: ['filePath'],
},
async handler(args, context) {
const cwd = context?.cwd || process.cwd();
const targetPath = path.resolve(cwd, args.filePath);
const encoding = args.encoding || 'utf-8';
// 检查文件是否存在
if (!fs.existsSync(targetPath)) {
throw new Error(`文件不存在: ${targetPath}`);
}
// 检查是否是文件
const stats = fs.statSync(targetPath);
if (!stats.isFile()) {
throw new Error(`路径不是文件: ${targetPath}`);
}
// 检查文件大小,避免读取过大的文件
const maxSize = 1024 * 1024; // 1MB
if (stats.size > maxSize) {
throw new Error(`文件太大 (${formatSize(stats.size)}),最大支持 1MB`);
}
// 读取文件内容
const content = fs.readFileSync(targetPath, encoding);
const lineCount = content.split('\n').length;
return {
filePath: targetPath,
content,
size: stats.size,
lineCount,
};
},
renderResultForAssistant(result) {
return [
`文件: ${result.filePath}`,
`大小: ${formatSize(result.size)}`,
`行数: ${result.lineCount}`,
'',
'--- 内容 ---',
result.content,
].join('\n');
},
};
/**
* 格式化文件大小
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}
import { InternalTool, InternalToolContext } from './types.js';
import { ListFilesTool } from './ListFiles/definitions.js';
import { ReadFileTool } from './ReadFile/definitions.js';
// 注册的工具列表
const toolsList: InternalTool[] = [ListFilesTool, ReadFileTool];
/**
* OpenAI 格式的工具定义
*/
interface OpenAITool {
type: 'function';
function: {
name: string;
description: string;
parameters: any;
};
}
/**
* 工具管理类
* 负责工具的注册、查询和执行
*/
export class ToolManager {
private tools: Map<string, InternalTool> = new Map();
constructor() {
this.registerAllTools();
}
/**
* 注册所有工具
*/
private registerAllTools() {
toolsList.forEach((tool) => {
this.tools.set(tool.name, tool);
});
}
/**
* 执行指定工具
*/
async execute<TArgs = any, TResult = any>(
name: string,
args: TArgs,
context?: InternalToolContext
): Promise<TResult> {
const tool = this.tools.get(name);
if (!tool) {
throw new Error(`Tool '${name}' not found. Available: ${this.getToolNames().join(', ')}`);
}
try {
return await tool.handler(args, context);
} catch (error: any) {
console.error(`Tool '${name}' failed:`, error);
throw error;
}
}
/**
* 获取格式化的工具定义(OpenAI 格式)
* 供 LLM API 调用使用
*/
getFormattedTools(): OpenAITool[] {
return Array.from(this.tools.values()).map((tool) => ({
type: 'function' as const,
function: {
name: tool.name,
description: tool.description,
parameters: tool.parameters,
},
}));
}
/**
* 获取所有工具
*/
getTools(): InternalTool[] {
return Array.from(this.tools.values());
}
/**
* 获取指定工具
*/
getTool(name: string): InternalTool | undefined {
return this.tools.get(name);
}
/**
* 获取所有工具名称
*/
getToolNames(): string[] {
return Array.from(this.tools.keys());
}
/**
* 检查工具是否存在
*/
hasTool(name: string): boolean {
return this.tools.has(name);
}
/**
* 清空所有工具
* 用于创建无工具的Agent场景
*/
clear(): void {
this.tools.clear();
}
/**
* 获取工具统计信息
*/
getStats() {
const tools = this.getTools();
const categories = new Map<string, number>();
tools.forEach((tool) => {
const count = categories.get(tool.category) || 0;
categories.set(tool.category, count + 1);
});
return {
totalTools: tools.length,
categories: Object.fromEntries(categories),
toolNames: this.getToolNames(),
};
}
}
/**
* 工具定义类型约束
* 用于定义所有工具的统一结构,便于格式化并作为提示词传给大模型
*/
/**
* JSON Schema 参数定义
*/
export interface ToolParameterSchema {
type: 'object' | 'array' | 'string' | 'number' | 'boolean';
description?: string;
properties?: Record<string, ToolParameterSchema>;
items?: ToolParameterSchema;
required?: string[];
enum?: string[];
default?: any;
}
/**
* 工具上下文
*/
export interface InternalToolContext {
abortSignal?: AbortSignal;
cwd?: string;
[key: string]: any; // 扩展字段
}
/**
* 工具定义基础接口
*/
export interface InternalTool<TArgs = any, TResult = any> {
/** 工具名称(唯一标识) */
name: string;
/** 工具分类(如 filesystem、search、network) */
category: string;
/** 是否为内部工具 */
internal: boolean;
/** 工具描述(简短,详细描述在 prompt 中) */
description: string;
/** 版本号 */
version: string;
/** 参数定义(JSON Schema 格式) */
parameters: ToolParameterSchema;
/** 工具处理函数 */
handler: (args: TArgs, context?: InternalToolContext) => Promise<TResult>;
/** 可选:格式化结果给 AI */
renderResultForAssistant?: (result: TResult) => string;
/** 可选:权限控制 */
needsPermissions?: (input?: TArgs) => boolean; // 是否需要权限
isEnabled?: () => Promise<boolean>; // 是否启用
isReadOnly?: () => boolean; // 是否只读
isConcurrencySafe?: () => boolean; // 是否并发安全
}
/**
* 格式化工具定义为大模型可读格式
*/
export interface FormattedToolDefinition {
name: string;
category: string;
description: string;
version: string;
parameters: ToolParameterSchema;
}
/**
* 将工具定义格式化为大模型输入格式
*/
export function formatToolForLLM(tool: InternalTool): FormattedToolDefinition {
return {
name: tool.name,
category: tool.category,
description: tool.description,
version: tool.version,
parameters: tool.parameters,
};
}
/**
* 通用测试数据集
* 包含单 Agent 和多 Agent 的测试用例
*/
import { TestCase } from './types.js';
/**
* 单 Agent 测试用例 (SimpleAgent)
*/
export const SIMPLE_AGENT_TESTS: TestCase[] = [
// S1. 单工具测试 - 列出文件
{
id: 'S1',
description: '单工具调用 - 列出当前目录文件',
input: '列出当前目录下的所有文件和文件夹',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['list_files'] },
},
},
// S2. 多工具测试 - 先列出再读取
{
id: 'S2',
description: '多工具调用 - 列出文件后读取',
input: '先列出当前目录的文件,然后读取 README.md 的内容',
expected: {
agents: ['simple_agent'],
tools: { simple_agent: ['list_files', 'read_file'] },
},
},
];
/**
* 多 Agent 测试用例 (MultiAgent: MainAgent + SubAgents)
*/
export const MULTI_AGENT_TESTS: TestCase[] = [
// M1. 简单任务分发 - 主Agent协调子Agent
{
id: 'M1',
description: '多Agent协调 - 简单任务分发',
input: '帮我分析一下如何提升代码质量',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
// M2. 研究+执行 - 调用 researcher 和 executor
{
id: 'M2',
description: '多Agent协调 - 研究与执行',
input: '请研究并提供一个实现用户认证系统的方案',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
// M3. 复杂任务 - 多轮子Agent协作
{
id: 'M3',
description: '多Agent协调 - 复杂任务处理',
input: '帮我设计一个完整的电商系统架构,包括技术选型和实施计划',
expected: {
agents: ['main_agent', 'researcher', 'executor'],
tools: {
main_agent: [],
researcher: [],
executor: [],
},
},
},
];
/**
* 所有测试用例
*/
export const TEST_CASES: TestCase[] = [...SIMPLE_AGENT_TESTS, ...MULTI_AGENT_TESTS];
/**
* 根据ID获取测试用例
*/
export function getTestById(id: string): TestCase | undefined {
return TEST_CASES.find((test) => test.id === id);
}
/**
* 获取单Agent测试用例
*/
export function getSimpleAgentTests(): TestCase[] {
return SIMPLE_AGENT_TESTS;
}
/**
* 获取多Agent测试用例
*/
export function getMultiAgentTests(): TestCase[] {
return MULTI_AGENT_TESTS;
}
/**
* 评估函数 - 简化版本
* 比较期望行为和实际执行数据
*/
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;
}
/**
* 事件总线 + 数据收集器(合并版本)
* 负责事件的发射、监听,以及收集执行过程中的数据
*/
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();
/**
* 评估模块使用示例
* 展示如何使用 SimpleAgent 和 MultiAgent 进行测试
*/
import { eventBus } from './EventBus.js';
import { evaluate, formatResult } from './evaluate.js';
import {
TEST_CASES,
getTestById,
getSimpleAgentTests,
getMultiAgentTests,
} from './dataset.js';
import { TestCase, EvaluateResult } from './types.js';
import { SimpleAgent, MainAgent } from '../core/agent/index.js';
import { createLLMService } from '../core/llm/index.js';
import { ILLMService } from '../core/llm/types/index.js';
// 缓存 LLM 服务和 Agent 实例
let llmService: ILLMService | null = null;
let simpleAgent: SimpleAgent | null = null;
let multiAgent: MainAgent | null = null;
/**
* 获取 LLM 服务(延迟初始化)
*/
async function getLLMService(): Promise<ILLMService> {
if (!llmService) {
llmService = await createLLMService({
provider: 'deepseek',
model: "deepseek-chat",
});
}
return llmService;
}
/**
* 获取 SimpleAgent 实例
*/
async function getSimpleAgent(): Promise<SimpleAgent> {
if (!simpleAgent) {
const service = await getLLMService();
simpleAgent = new SimpleAgent(service, { name: 'simple_agent' });
}
return simpleAgent;
}
/**
* 获取 MultiAgent (MainAgent) 实例
*/
async function getMultiAgent(): Promise<MainAgent> {
if (!multiAgent) {
const service = await getLLMService();
multiAgent = new MainAgent(service, 'main_agent');
}
return multiAgent;
}
/**
* 判断测试用例是否为多 Agent 测试
*/
function isMultiAgentTest(testCase: TestCase): boolean {
return testCase.id.startsWith('M');
}
/**
* 运行单个测试用例
*/
async function runTest(testCase: TestCase): Promise<EvaluateResult | null> {
console.log(`\n${'='.repeat(50)}`);
console.log(`🧪 测试: ${testCase.id} - ${testCase.description}`);
console.log(`${'='.repeat(50)}`);
const startTime = Date.now();
try {
let agents: string[];
let tools: Record<string, string[]>;
let finalResponse: string;
let success: boolean;
let error: string | undefined;
if (isMultiAgentTest(testCase)) {
// 多 Agent 测试
const agent = await getMultiAgent();
const result = await agent.run(testCase.input);
agents = result.agents;
tools = result.tools;
finalResponse = result.finalResponse;
success = result.success;
error = result.error;
} else {
// 单 Agent 测试
const agent = await getSimpleAgent();
const result = await agent.run(testCase.input);
agents = result.agents;
tools = result.tools;
finalResponse = result.finalResponse;
success = result.success;
error = result.error;
}
// 评估
const evalResult = evaluate(testCase, {
agents,
tools,
editResult: null,
});
// 输出结果
const status = evalResult.passed ? '✅ PASS' : '❌ FAIL';
const time = Date.now() - startTime;
console.log(`\n${status} (${time}ms)`);
console.log(formatResult(evalResult));
const responsePreview = finalResponse.slice(0, 200);
console.log(`\n📝 Agent回复: ${responsePreview}${finalResponse.length > 200 ? '...' : ''}`);
if (!success) {
console.log(`\n⚠️ Agent 执行失败: ${error}`);
}
return evalResult;
} catch (err) {
console.error(`❌ 执行失败:`, err);
return null;
}
}
/**
* 运行测试集
*/
async function runTests(testCases: TestCase[], title: string) {
console.log(`\n${'#'.repeat(60)}`);
console.log(`# ${title}: ${testCases.length} 个用例`);
console.log(`${'#'.repeat(60)}`);
const results: Array<{ testCase: TestCase; result: EvaluateResult }> = [];
const startTime = Date.now();
for (const testCase of testCases) {
const result = await runTest(testCase);
if (result) {
results.push({ testCase, result });
}
}
// 汇总
const passed = results.filter((r) => r.result.passed).length;
const failed = results.length - passed;
const totalTime = Date.now() - startTime;
console.log(`\n${'='.repeat(60)}`);
console.log(`${title} 完成`);
console.log(`${'='.repeat(60)}`);
console.log(`总数: ${results.length}`);
console.log(`✅ 通过: ${passed}`);
console.log(`❌ 失败: ${failed}`);
console.log(`⏱️ 耗时: ${(totalTime / 1000).toFixed(2)}s`);
return results;
}
/**
* 运行所有测试用例
*/
async function runAllTests() {
console.log(`\n${'#'.repeat(60)}`);
console.log(`# 开始全量测试: ${TEST_CASES.length} 个用例`);
console.log(`${'#'.repeat(60)}`);
const startTime = Date.now();
// 运行单 Agent 测试
const simpleResults = await runTests(getSimpleAgentTests(), '单 Agent 测试');
// 运行多 Agent 测试
const multiResults = await runTests(getMultiAgentTests(), '多 Agent 测试');
// 总汇总
const allResults = [...simpleResults, ...multiResults];
const passed = allResults.filter((r) => r.result.passed).length;
const failed = allResults.length - passed;
const totalTime = Date.now() - startTime;
console.log(`\n${'#'.repeat(60)}`);
console.log(`# 全部测试完成`);
console.log(`${'#'.repeat(60)}`);
console.log(`总数: ${allResults.length}`);
console.log(`✅ 通过: ${passed}`);
console.log(`❌ 失败: ${failed}`);
console.log(`⏱️ 总耗时: ${(totalTime / 1000).toFixed(2)}s`);
}
/**
* 打印帮助信息
*/
function printHelp() {
console.log(`
评估模块使用说明:
npx ts-node example.ts [选项]
选项:
--help 显示帮助信息
--test <id> 运行指定测试用例 (如: S1, M1)
--simple 运行单 Agent 测试集 (2 个用例)
--multi 运行多 Agent 测试集 (3 个用例)
(无参数) 运行所有测试用例
测试用例 ID:
S1, S2 单 Agent 测试 (SimpleAgent)
M1, M2, M3 多 Agent 测试 (MultiAgent)
`);
}
/**
* 主函数
*/
async function main() {
const args = process.argv.slice(2);
if (args.includes('--help')) {
printHelp();
} else if (args.includes('--simple')) {
// 运行单 Agent 测试集
await runTests(getSimpleAgentTests(), '单 Agent 测试');
} else if (args.includes('--multi')) {
// 运行多 Agent 测试集
await runTests(getMultiAgentTests(), '多 Agent 测试');
} else if (args.includes('--test') && args[args.indexOf('--test') + 1]) {
// 运行指定测试用例
const testId = args[args.indexOf('--test') + 1];
const testCase = getTestById(testId);
if (testCase) {
await runTest(testCase);
} else {
console.error(`未找到测试用例: ${testId}`);
console.log('可用的测试用例 ID: S1, S2, M1, M2, M3');
}
} else {
// 运行所有测试
await runAllTests();
}
}
// 运行
main().catch(console.error);
/**
* 评估模块模板 - 类型定义
* 简化版本,专注核心功能
*/
/**
* 测试用例定义
*/
export interface TestCase {
id: string; // 测试用例ID
description: string; // 用例描述
input: string; // 用户输入
expected: ExpectedBehavior; // 期望结果
}
/**
* 期望行为定义
*/
export interface ExpectedBehavior {
// 期望调用的子Agent列表
agents: string[];
// 每个子Agent期望调用的工具
tools: {
[agentName: string]: string[];
};
}
/**
* 事件收集器收集到的实际执行数据
*/
export interface CollectedData {
// 实际调用的子Agent列表
agents: string[];
// 每个Agent实际调用的工具
tools: {
[agentName: string]: string[];
};
// 编辑节点执行结果
editResult: {
success: number;
fail: number;
} | null;
}
/**
* 评估结果
*/
export interface EvaluateResult {
passed: boolean; // 是否通过
agentMatch: boolean; // Agent调用是否匹配
toolMatch: boolean; // 工具调用是否匹配
// 详细信息
details: {
agents: {
expected: string[];
actual: string[];
missed: string[]; // 遗漏的Agent
extra: string[]; // 多余的Agent
};
tools: {
expected: { [agentName: string]: string[] };
actual: { [agentName: string]: string[] };
missed: { agent: string; tool: string }[];
extra: { agent: string; tool: string }[];
};
};
}
/**
* 多智能体对话示例
* 演示 MainAgent 协调多个 SubAgent 完成任务
*/
import { createLLMService } from '../core/llm/index.js';
import { MainAgent } from '../core/agent/index.js';
async function main() {
console.log('🚀 Multi-Agent Chat Example\n');
// 创建 LLM 服务
const service = await createLLMService({
provider: 'deepseek',
model: 'deepseek-chat',
});
// 创建多智能体系统 (MainAgent + SubAgents)
const multiAgent = new MainAgent(service, 'main_agent');
console.log('📋 任务: 分析如何提升代码质量\n');
// 执行多智能体协作
const result = await multiAgent.run('请帮我分析如何提升代码质量,并给出具体的改进建议');
// 输出结果
console.log('\n' + '='.repeat(60));
console.log('📊 执行结果汇总');
console.log('='.repeat(60));
console.log(`\n✅ 执行状态: ${result.success ? '成功' : '失败'}`);
console.log(`🤖 参与的 Agent: ${result.agents.join(' → ')}`);
console.log('\n📝 子 Agent 执行记录:');
for (const subResult of result.subAgentResults) {
console.log(`\n [${subResult.agentName}]`);
console.log(` 状态: ${subResult.success ? '✅ 成功' : '❌ 失败'}`);
console.log(` 输出: ${subResult.result.slice(0, 200)}${subResult.result.length > 200 ? '...' : ''}`);
}
console.log('\n' + '='.repeat(60));
console.log('🎯 最终响应');
console.log('='.repeat(60));
console.log(result.finalResponse);
}
main().catch(console.error);
/**
* 简单对话示例
*/
import { createLLMService } from "../core/llm/index.js";
async function main() {
// 创建 LLM 服务
const service = await createLLMService({
provider: "deepseek",
model: "deepseek-chat",
});
console.log("🚀 Simple Chat Example\n");
// 简单对话
const response = await service.simpleChat(
"你好!你能用一句话介绍一下自己吗?",
"你是一个有用的AI助手。"
);
console.log("Assistant:", response);
}
main().catch(console.error);
/**
* 简单的日志工具
*/
export enum LogLevel {
DEBUG = 'debug',
INFO = 'info',
WARN = 'warn',
ERROR = 'error',
}
class Logger {
private level: LogLevel = LogLevel.INFO;
setLevel(level: LogLevel) {
this.level = level;
}
debug(...args: any[]) {
if (this.shouldLog(LogLevel.DEBUG)) {
console.log('[DEBUG]', ...args);
}
}
info(...args: any[]) {
if (this.shouldLog(LogLevel.INFO)) {
console.log('[INFO]', ...args);
}
}
warn(...args: any[]) {
if (this.shouldLog(LogLevel.WARN)) {
console.warn('[WARN]', ...args);
}
}
error(...args: any[]) {
if (this.shouldLog(LogLevel.ERROR)) {
console.error('[ERROR]', ...args);
}
}
private shouldLog(level: LogLevel): boolean {
const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR];
return levels.indexOf(level) >= levels.indexOf(this.level);
}
}
export const logger = new Logger();
// 根据配置设置日志级别
import { config } from '../config/env.js';
logger.setLevel(config.logging.level as LogLevel);