diff --git a/backend/config/agents.yaml b/backend/config/agents.yaml new file mode 100644 index 00000000..8e07a458 --- /dev/null +++ b/backend/config/agents.yaml @@ -0,0 +1,309 @@ +# AI临床研究平台 - 智能体配置文件 +# 版本: 1.0 +# 更新日期: 2025-10-10 + +agents: + # ==================== 选题阶段 ==================== + + - id: topic-evaluation + name: 选题评价智能体 + nameEn: Topic Evaluation Agent + description: 从创新性、临床价值、科学性和可行性等维度评估研究选题 + category: 选题阶段 + icon: 🎯 + enabled: true + systemPromptFile: topic_evaluation_system.txt + userPromptTemplateFile: topic_evaluation_user.txt + models: + deepseek-v3: + temperature: 0.4 + maxTokens: 2000 + topP: 0.9 + qwen3-72b: + temperature: 0.5 + maxTokens: 2000 + topP: 0.9 + ragEnabled: true + requiresProject: true + outputFormat: structured + tags: + - 选题 + - 评估 + - 创新性 + + - id: scientific-question + name: 科学问题梳理智能体 + nameEn: Scientific Question Agent + description: 将模糊的研究想法提炼成清晰、具体、可验证的科学问题 + category: 选题阶段 + icon: 🔬 + enabled: false + systemPromptFile: scientific_question_system.txt + userPromptTemplateFile: scientific_question_user.txt + models: + deepseek-v3: + temperature: 0.5 + maxTokens: 1500 + qwen3-72b: + temperature: 0.6 + maxTokens: 1500 + ragEnabled: true + requiresProject: true + outputFormat: structured + tags: + - 科学问题 + - PICOS + + - id: picos-construction + name: PICOS构建智能体 + nameEn: PICOS Construction Agent + description: 按照PICOS原则结构化定义临床研究的核心要素 + category: 选题阶段 + icon: 📋 + enabled: false + systemPromptFile: picos_construction_system.txt + userPromptTemplateFile: picos_construction_user.txt + models: + deepseek-v3: + temperature: 0.3 + maxTokens: 1500 + qwen3-72b: + temperature: 0.4 + maxTokens: 1500 + ragEnabled: true + requiresProject: true + outputFormat: structured + tags: + - PICOS + - 研究设计 + + # ==================== 研究设计阶段 ==================== + + - id: observation-design + name: 观察指标设计智能体 + nameEn: Observation Design Agent + description: 推荐合适的主要、次要及安全性观察指标集 + category: 研究设计阶段 + icon: 📊 + enabled: false + systemPromptFile: observation_design_system.txt + userPromptTemplateFile: observation_design_user.txt + models: + deepseek-v3: + temperature: 0.4 + maxTokens: 2000 + qwen3-72b: + temperature: 0.5 + maxTokens: 2000 + ragEnabled: true + requiresProject: true + outputFormat: structured + tags: + - 观察指标 + - 终点事件 + + - id: crf-development + name: CRF制定智能体 + nameEn: CRF Development Agent + description: 自动生成结构化、符合规范的病例报告表(CRF)框架 + category: 研究设计阶段 + icon: 📝 + enabled: false + systemPromptFile: crf_development_system.txt + userPromptTemplateFile: crf_development_user.txt + models: + deepseek-v3: + temperature: 0.3 + maxTokens: 3000 + qwen3-72b: + temperature: 0.4 + maxTokens: 3000 + ragEnabled: true + requiresProject: true + outputFormat: document + tags: + - CRF + - 病例报告表 + + - id: sample-size + name: 样本量计算智能体 + nameEn: Sample Size Calculation Agent + description: 根据研究参数提供科学合理的样本量估算结果 + category: 研究设计阶段 + icon: 🔢 + enabled: false + systemPromptFile: sample_size_system.txt + userPromptTemplateFile: sample_size_user.txt + models: + deepseek-v3: + temperature: 0.2 + maxTokens: 1500 + qwen3-72b: + temperature: 0.3 + maxTokens: 1500 + ragEnabled: false + requiresProject: true + outputFormat: structured + tags: + - 样本量 + - 统计学 + + - id: protocol-writing + name: 临床研究方案撰写智能体 + nameEn: Protocol Writing Agent + description: 自动生成结构完整的临床研究设计方案 + category: 研究设计阶段 + icon: 📄 + enabled: false + systemPromptFile: protocol_writing_system.txt + userPromptTemplateFile: protocol_writing_user.txt + models: + deepseek-v3: + temperature: 0.5 + maxTokens: 4000 + qwen3-72b: + temperature: 0.6 + maxTokens: 4000 + ragEnabled: true + requiresProject: true + outputFormat: document + tags: + - 研究方案 + - 文档生成 + + # ==================== 论文撰写阶段 ==================== + + - id: paper-polishing + name: 论文润色智能体 + nameEn: Paper Polishing Agent + description: 提供专业级的语言润色,修正语法、拼写和表达方式 + category: 论文撰写阶段 + icon: ✨ + enabled: false + systemPromptFile: paper_polishing_system.txt + userPromptTemplateFile: paper_polishing_user.txt + models: + deepseek-v3: + temperature: 0.4 + maxTokens: 3000 + qwen3-72b: + temperature: 0.5 + maxTokens: 3000 + ragEnabled: false + requiresProject: false + outputFormat: text + tags: + - 润色 + - 语言优化 + + - id: paper-translation + name: 论文翻译智能体 + nameEn: Paper Translation Agent + description: 提供专业、精准的中英互译服务 + category: 论文撰写阶段 + icon: 🌐 + enabled: false + systemPromptFile: paper_translation_system.txt + userPromptTemplateFile: paper_translation_user.txt + models: + deepseek-v3: + temperature: 0.3 + maxTokens: 3000 + qwen3-72b: + temperature: 0.4 + maxTokens: 3000 + ragEnabled: false + requiresProject: false + outputFormat: text + tags: + - 翻译 + - 中英互译 + + - id: methodology-review + name: 方法学评审智能体 + nameEn: Methodology Review Agent + description: 对研究方案或论文进行全面的方法学评审 + category: 论文撰写阶段 + icon: 🔍 + enabled: false + systemPromptFile: methodology_review_system.txt + userPromptTemplateFile: methodology_review_user.txt + models: + deepseek-v3: + temperature: 0.5 + maxTokens: 2500 + qwen3-72b: + temperature: 0.6 + maxTokens: 2500 + ragEnabled: true + requiresProject: false + outputFormat: structured + tags: + - 方法学评审 + - 质量控制 + + - id: journal-methodology-review + name: 期刊方法学评审智能体 + nameEn: Journal Methodology Review Agent + description: 模拟期刊审稿人视角,进行方法学挑战 + category: 论文撰写阶段 + icon: 📑 + enabled: false + systemPromptFile: journal_methodology_review_system.txt + userPromptTemplateFile: journal_methodology_review_user.txt + models: + deepseek-v3: + temperature: 0.6 + maxTokens: 2500 + qwen3-72b: + temperature: 0.7 + maxTokens: 2500 + ragEnabled: true + requiresProject: false + outputFormat: structured + tags: + - 期刊审稿 + - 方法学挑战 + + - id: journal-guidelines-review + name: 期刊稿约评审智能体 + nameEn: Journal Guidelines Review Agent + description: 检查文章格式、字数、参考文献规范等是否符合投稿要求 + category: 论文撰写阶段 + icon: ✅ + enabled: false + systemPromptFile: journal_guidelines_review_system.txt + userPromptTemplateFile: journal_guidelines_review_user.txt + models: + deepseek-v3: + temperature: 0.3 + maxTokens: 2000 + qwen3-72b: + temperature: 0.4 + maxTokens: 2000 + ragEnabled: false + requiresProject: false + outputFormat: structured + tags: + - 期刊投稿 + - 格式检查 + +# 配置说明: +# - id: 智能体唯一标识符 +# - name: 显示名称 +# - nameEn: 英文名称 +# - description: 功能描述 +# - category: 所属阶段 +# - icon: 显示图标 +# - enabled: 是否启用(true/false) +# - systemPromptFile: 系统Prompt文件名 +# - userPromptTemplateFile: 用户Prompt模板文件名 +# - models: 支持的模型及参数配置 +# - temperature: 温度参数(0-1) +# - maxTokens: 最大token数 +# - topP: Top-p采样参数 +# - ragEnabled: 是否支持知识库检索 +# - requiresProject: 是否需要项目上下文 +# - outputFormat: 输出格式(text/structured/document) +# - tags: 标签列表 + diff --git a/backend/package-lock.json b/backend/package-lock.json index f8e2d45e..d1e50978 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -17,6 +17,7 @@ "prisma": "^6.17.0" }, "devDependencies": { + "@types/js-yaml": "^4.0.9", "@types/node": "^24.7.1", "nodemon": "^3.1.10", "pino-pretty": "^13.1.1", @@ -784,6 +785,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.7.1", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.7.1.tgz", diff --git a/backend/package.json b/backend/package.json index d6874239..8ad9e0e6 100644 --- a/backend/package.json +++ b/backend/package.json @@ -30,6 +30,7 @@ "prisma": "^6.17.0" }, "devDependencies": { + "@types/js-yaml": "^4.0.9", "@types/node": "^24.7.1", "nodemon": "^3.1.10", "pino-pretty": "^13.1.1", diff --git a/backend/prompts/topic_evaluation_system.txt b/backend/prompts/topic_evaluation_system.txt new file mode 100644 index 00000000..785c9a31 --- /dev/null +++ b/backend/prompts/topic_evaluation_system.txt @@ -0,0 +1,119 @@ +你是一位经验丰富的临床研究专家,擅长评估研究选题的质量和可行性。你的任务是从多个维度对研究选题进行专业、客观的评价,并给出建设性的改进建议。 + +## 你的职责 + +1. **全面评估**:从创新性、临床价值、科学性和可行性四个维度评价研究选题 +2. **指出问题**:客观指出选题存在的不足和潜在风险 +3. **提供建议**:给出具体的改进方向和优化思路 +4. **评分量化**:对每个维度给出1-10分的评分,并说明理由 + +## 评价维度 + +### 1. 创新性(Innovation) +- 研究问题是否新颖?是否填补了现有知识的空白? +- 研究方法或角度是否具有创新性? +- 与已有研究相比有哪些突破? +- 评分标准: + * 9-10分:开创性研究,填补重大空白 + * 7-8分:显著创新,有新的发现或方法 + * 5-6分:一定创新性,但突破有限 + * 3-4分:创新性较弱,主要是验证性研究 + * 1-2分:缺乏创新,重复已有研究 + +### 2. 临床价值(Clinical Value) +- 研究结果对临床实践有何指导意义? +- 能否改善患者预后或生活质量? +- 是否解决了临床中的实际问题? +- 潜在的临床应用前景如何? +- 评分标准: + * 9-10分:重大临床价值,可能改变临床实践 + * 7-8分:明确临床价值,有较大应用潜力 + * 5-6分:一定临床价值,但应用范围有限 + * 3-4分:临床价值不明显 + * 1-2分:临床价值很低或无价值 + +### 3. 科学性(Scientific Rigor) +- 研究假设是否合理?理论基础是否扎实? +- 研究设计是否科学?方法学是否严谨? +- 样本量是否合理?统计方法是否恰当? +- 是否考虑了混杂因素和偏倚控制? +- 评分标准: + * 9-10分:研究设计严谨,方法学无懈可击 + * 7-8分:研究设计合理,方法学较为严谨 + * 5-6分:研究设计基本合理,但存在一些缺陷 + * 3-4分:研究设计有明显缺陷,方法学有问题 + * 1-2分:研究设计不科学,方法学严重缺陷 + +### 4. 可行性(Feasibility) +- 研究所需资源(时间、经费、设备、人力)是否可获得? +- 样本招募是否困难?数据收集是否可行? +- 伦理审查是否容易通过? +- 研究周期是否合理? +- 是否存在重大技术或操作障碍? +- 评分标准: + * 9-10分:可行性非常高,资源充足,易于实施 + * 7-8分:可行性较高,资源基本可获得 + * 5-6分:可行性一般,存在一些困难但可克服 + * 3-4分:可行性较低,存在较多障碍 + * 1-2分:可行性很低,难以实施 + +## 评价流程 + +1. **理解选题**:仔细阅读用户提供的研究选题描述,包括研究背景、目的、方法等 +2. **维度评分**:对每个维度进行1-10分的评分,并详细说明理由 +3. **综合评价**:给出选题的总体评价和优先级建议 +4. **改进建议**:针对不足之处,给出3-5条具体的改进建议 + +## 输出格式 + +你的评价应该包含以下结构: + +### 📊 评分总览 +- 创新性:X/10分 +- 临床价值:X/10分 +- 科学性:X/10分 +- 可行性:X/10分 +- **综合得分:XX/40分** + +### 🎯 详细评价 + +#### 1. 创新性评价(X/10分) +[详细说明为什么给出这个分数,指出创新点或不足] + +#### 2. 临床价值评价(X/10分) +[详细说明临床价值体现在哪里,或为什么价值有限] + +#### 3. 科学性评价(X/10分) +[分析研究设计和方法学的优缺点] + +#### 4. 可行性评价(X/10分) +[分析实施难度和资源需求] + +### 💡 改进建议 + +1. **建议一**:[具体的改进方向] +2. **建议二**:[具体的改进方向] +3. **建议三**:[具体的改进方向] +[如有必要,可以提供更多建议] + +### ✅ 总体评价 + +[用1-2段话总结选题的整体质量,给出是否建议推进的明确意见,以及优先级建议(高/中/低)] + +## 注意事项 + +- 评价要**客观公正**,既要肯定优点,也要指出不足 +- 评分要**有理有据**,不能主观臆断 +- 建议要**具体可行**,不能空泛 +- 语气要**专业友好**,鼓励研究者改进 +- 如果用户提供的信息不足,要明确指出需要补充哪些信息 +- 如果选题存在严重缺陷,要明确指出,但也要给出挽救方案(如果有) + +## 特殊情况处理 + +- 如果用户提供的选题描述过于简略,要求其补充关键信息(研究目的、方法、预期结果等) +- 如果选题涉及伦理敏感问题,要特别提醒伦理审查的注意事项 +- 如果选题超出你的专业范围(如纯基础研究),要说明你的评价可能有局限性 + +记住:你的评价将直接影响研究者的决策,因此要认真负责、专业严谨。 + diff --git a/backend/prompts/topic_evaluation_user.txt b/backend/prompts/topic_evaluation_user.txt new file mode 100644 index 00000000..9b4ef2d1 --- /dev/null +++ b/backend/prompts/topic_evaluation_user.txt @@ -0,0 +1,15 @@ +请对以下研究选题进行评价: + +## 项目背景 +{{projectBackground}} + +## 研究选题 +{{userInput}} + +{{#if knowledgeBaseContext}} +## 参考文献(来自知识库) +{{knowledgeBaseContext}} +{{/if}} + +请根据创新性、临床价值、科学性和可行性四个维度,对上述选题进行全面评价,并给出改进建议。 + diff --git a/backend/src/controllers/agentController.ts b/backend/src/controllers/agentController.ts new file mode 100644 index 00000000..a766a086 --- /dev/null +++ b/backend/src/controllers/agentController.ts @@ -0,0 +1,215 @@ +import { FastifyRequest, FastifyReply } from 'fastify'; +import { agentService } from '../services/agentService.js'; + +interface AgentParams { + id: string; +} + +interface RenderPromptBody { + projectBackground?: string; + userInput: string; + knowledgeBaseContext?: string; +} + +class AgentController { + // 获取所有智能体列表 + async getAllAgents(request: FastifyRequest, reply: FastifyReply) { + try { + const agents = agentService.getAllAgents(); + + return reply.code(200).send({ + success: true, + data: agents, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '获取智能体列表失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 获取启用的智能体列表 + async getEnabledAgents(request: FastifyRequest, reply: FastifyReply) { + try { + const agents = agentService.getEnabledAgents(); + + return reply.code(200).send({ + success: true, + data: agents, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '获取智能体列表失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 获取单个智能体详情 + async getAgentById( + request: FastifyRequest<{ Params: AgentParams }>, + reply: FastifyReply + ) { + try { + const { id } = request.params; + const agent = agentService.getAgentById(id); + + if (!agent) { + return reply.code(404).send({ + success: false, + message: '智能体不存在', + }); + } + + return reply.code(200).send({ + success: true, + data: agent, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '获取智能体详情失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 获取智能体的系统Prompt + async getSystemPrompt( + request: FastifyRequest<{ Params: AgentParams }>, + reply: FastifyReply + ) { + try { + const { id } = request.params; + + if (!agentService.agentExists(id)) { + return reply.code(404).send({ + success: false, + message: '智能体不存在', + }); + } + + const systemPrompt = agentService.getSystemPrompt(id); + + return reply.code(200).send({ + success: true, + data: { + agentId: id, + systemPrompt, + }, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '获取系统Prompt失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 渲染用户Prompt(用于预览或调试) + async renderPrompt( + request: FastifyRequest<{ Params: AgentParams }>, + reply: FastifyReply + ) { + try { + const { id } = request.params; + const body = request.body as RenderPromptBody; + + if (!agentService.agentExists(id)) { + return reply.code(404).send({ + success: false, + message: '智能体不存在', + }); + } + + if (!body.userInput) { + return reply.code(400).send({ + success: false, + message: 'userInput为必填项', + }); + } + + const renderedPrompt = agentService.renderUserPrompt(id, { + projectBackground: body.projectBackground, + userInput: body.userInput, + knowledgeBaseContext: body.knowledgeBaseContext, + }); + + return reply.code(200).send({ + success: true, + data: { + agentId: id, + renderedPrompt, + }, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '渲染Prompt失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 根据分类获取智能体 + async getAgentsByCategory( + request: FastifyRequest<{ Querystring: { category: string } }>, + reply: FastifyReply + ) { + try { + const { category } = request.query; + + if (!category) { + return reply.code(400).send({ + success: false, + message: 'category参数为必填项', + }); + } + + const agents = agentService.getAgentsByCategory(category); + + return reply.code(200).send({ + success: true, + data: agents, + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '获取智能体列表失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + // 重新加载配置(管理员功能) + async reloadConfig(request: FastifyRequest, reply: FastifyReply) { + try { + agentService.reloadConfig(); + + return reply.code(200).send({ + success: true, + message: '智能体配置已重新加载', + }); + } catch (error) { + request.log.error(error); + return reply.code(500).send({ + success: false, + message: '重新加载配置失败', + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } +} + +export const agentController = new AgentController(); + diff --git a/backend/src/index.ts b/backend/src/index.ts index b1ede82b..f0129cb8 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -3,6 +3,7 @@ import cors from '@fastify/cors'; import { config } from './config/env.js'; import { testDatabaseConnection, prisma } from './config/database.js'; import { projectRoutes } from './routes/projects.js'; +import { agentRoutes } from './routes/agents.js'; const fastify = Fastify({ logger: { @@ -55,6 +56,9 @@ fastify.get('/api/v1', async () => { // 注册项目管理路由 await fastify.register(projectRoutes, { prefix: '/api/v1' }); +// 注册智能体管理路由 +await fastify.register(agentRoutes, { prefix: '/api/v1' }); + // 启动服务器 const start = async () => { try { diff --git a/backend/src/routes/agents.ts b/backend/src/routes/agents.ts new file mode 100644 index 00000000..82e14dda --- /dev/null +++ b/backend/src/routes/agents.ts @@ -0,0 +1,56 @@ +import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; +import { agentController } from '../controllers/agentController.js'; + +interface AgentParams { + id: string; +} + +export async function agentRoutes(fastify: FastifyInstance) { + // 获取所有智能体列表 + fastify.get('/agents', async (request: FastifyRequest, reply: FastifyReply) => { + return agentController.getAllAgents(request, reply); + }); + + // 获取启用的智能体列表 + fastify.get('/agents/enabled', async (request: FastifyRequest, reply: FastifyReply) => { + return agentController.getEnabledAgents(request, reply); + }); + + // 根据分类获取智能体 + fastify.get<{ Querystring: { category: string } }>( + '/agents/by-category', + async (request: FastifyRequest<{ Querystring: { category: string } }>, reply: FastifyReply) => { + return agentController.getAgentsByCategory(request, reply); + } + ); + + // 获取单个智能体详情 + fastify.get<{ Params: AgentParams }>( + '/agents/:id', + async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => { + return agentController.getAgentById(request, reply); + } + ); + + // 获取智能体的系统Prompt + fastify.get<{ Params: AgentParams }>( + '/agents/:id/system-prompt', + async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => { + return agentController.getSystemPrompt(request, reply); + } + ); + + // 渲染用户Prompt(预览) + fastify.post<{ Params: AgentParams }>( + '/agents/:id/render-prompt', + async (request: FastifyRequest<{ Params: AgentParams }>, reply: FastifyReply) => { + return agentController.renderPrompt(request, reply); + } + ); + + // 重新加载配置(管理员功能) + fastify.post('/agents/reload-config', async (request: FastifyRequest, reply: FastifyReply) => { + return agentController.reloadConfig(request, reply); + }); +} + diff --git a/backend/src/services/agentService.ts b/backend/src/services/agentService.ts new file mode 100644 index 00000000..33b2eb99 --- /dev/null +++ b/backend/src/services/agentService.ts @@ -0,0 +1,212 @@ +import fs from 'fs'; +import path from 'path'; +import yaml from 'js-yaml'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +// 智能体配置接口 +export interface AgentConfig { + id: string; + name: string; + nameEn: string; + description: string; + category: string; + icon: string; + enabled: boolean; + systemPromptFile: string; + userPromptTemplateFile: string; + models: { + [modelName: string]: { + temperature: number; + maxTokens: number; + topP?: number; + }; + }; + ragEnabled: boolean; + requiresProject: boolean; + outputFormat: 'text' | 'structured' | 'document'; + tags: string[]; +} + +// 配置文件根结构 +interface AgentsConfigFile { + agents: AgentConfig[]; +} + +class AgentService { + private agents: Map = new Map(); + private prompts: Map = new Map(); + private configPath: string; + private promptsPath: string; + + constructor() { + // 配置文件路径 + this.configPath = path.resolve(__dirname, '../../config/agents.yaml'); + this.promptsPath = path.resolve(__dirname, '../../prompts'); + + // 初始化加载配置 + this.loadAgents(); + } + + // 加载智能体配置 + private loadAgents() { + try { + const fileContents = fs.readFileSync(this.configPath, 'utf8'); + const config = yaml.load(fileContents) as AgentsConfigFile; + + if (!config || !config.agents) { + throw new Error('Invalid agents configuration file'); + } + + // 存储到Map中 + config.agents.forEach((agent) => { + this.agents.set(agent.id, agent); + }); + + console.log(`✅ Loaded ${this.agents.size} agent configurations`); + } catch (error) { + console.error('❌ Failed to load agent configurations:', error); + throw error; + } + } + + // 加载Prompt模板 + private loadPrompt(filename: string): string { + const cacheKey = filename; + + // 检查缓存 + if (this.prompts.has(cacheKey)) { + return this.prompts.get(cacheKey)!; + } + + try { + const promptPath = path.join(this.promptsPath, filename); + const content = fs.readFileSync(promptPath, 'utf8'); + + // 缓存到内存 + this.prompts.set(cacheKey, content); + + return content; + } catch (error) { + console.error(`❌ Failed to load prompt file: ${filename}`, error); + throw new Error(`Prompt file not found: ${filename}`); + } + } + + // 获取所有智能体列表 + getAllAgents(): AgentConfig[] { + return Array.from(this.agents.values()); + } + + // 获取启用的智能体列表 + getEnabledAgents(): AgentConfig[] { + return Array.from(this.agents.values()).filter((agent) => agent.enabled); + } + + // 根据ID获取智能体配置 + getAgentById(agentId: string): AgentConfig | null { + return this.agents.get(agentId) || null; + } + + // 根据分类获取智能体列表 + getAgentsByCategory(category: string): AgentConfig[] { + return Array.from(this.agents.values()).filter( + (agent) => agent.category === category + ); + } + + // 获取智能体的系统Prompt + getSystemPrompt(agentId: string): string { + const agent = this.getAgentById(agentId); + if (!agent) { + throw new Error(`Agent not found: ${agentId}`); + } + + return this.loadPrompt(agent.systemPromptFile); + } + + // 获取智能体的用户Prompt模板 + getUserPromptTemplate(agentId: string): string { + const agent = this.getAgentById(agentId); + if (!agent) { + throw new Error(`Agent not found: ${agentId}`); + } + + return this.loadPrompt(agent.userPromptTemplateFile); + } + + // 渲染用户Prompt(替换模板变量) + renderUserPrompt( + agentId: string, + variables: { + projectBackground?: string; + userInput: string; + knowledgeBaseContext?: string; + } + ): string { + const template = this.getUserPromptTemplate(agentId); + + let rendered = template; + + // 替换变量 + rendered = rendered.replace(/\{\{projectBackground\}\}/g, variables.projectBackground || '未提供项目背景'); + rendered = rendered.replace(/\{\{userInput\}\}/g, variables.userInput); + + // 处理条件块(知识库上下文) + if (variables.knowledgeBaseContext) { + rendered = rendered.replace( + /\{\{#if knowledgeBaseContext\}\}([\s\S]*?)\{\{\/if\}\}/g, + '$1' + ); + rendered = rendered.replace(/\{\{knowledgeBaseContext\}\}/g, variables.knowledgeBaseContext); + } else { + // 移除条件块 + rendered = rendered.replace( + /\{\{#if knowledgeBaseContext\}\}[\s\S]*?\{\{\/if\}\}/g, + '' + ); + } + + return rendered.trim(); + } + + // 检查智能体是否存在 + agentExists(agentId: string): boolean { + return this.agents.has(agentId); + } + + // 检查智能体是否启用 + isAgentEnabled(agentId: string): boolean { + const agent = this.getAgentById(agentId); + return agent ? agent.enabled : false; + } + + // 获取智能体的模型配置 + getModelConfig(agentId: string, modelName: string) { + const agent = this.getAgentById(agentId); + if (!agent) { + throw new Error(`Agent not found: ${agentId}`); + } + + const modelConfig = agent.models[modelName]; + if (!modelConfig) { + throw new Error(`Model ${modelName} not configured for agent ${agentId}`); + } + + return modelConfig; + } + + // 重新加载配置(热更新) + reloadConfig() { + this.agents.clear(); + this.prompts.clear(); + this.loadAgents(); + console.log('✅ Agent configurations reloaded'); + } +} + +// 导出单例 +export const agentService = new AgentService(); + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 92689057..9ebd26de 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -9,8 +9,10 @@ "version": "1.0.0", "dependencies": { "@ant-design/icons": "^5.5.2", + "@types/js-yaml": "^4.0.9", "antd": "^5.22.5", "axios": "^1.7.9", + "js-yaml": "^4.1.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0" @@ -1788,6 +1790,12 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmmirror.com/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -2245,7 +2253,6 @@ "version": "2.0.1", "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/asynckit": { @@ -3577,7 +3584,6 @@ "version": "4.1.0", "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" diff --git a/frontend/package.json b/frontend/package.json index 56acd0a6..a6706975 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,12 +10,14 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0" }, "dependencies": { - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0", + "@ant-design/icons": "^5.5.2", + "@types/js-yaml": "^4.0.9", "antd": "^5.22.5", "axios": "^1.7.9", - "@ant-design/icons": "^5.5.2" + "js-yaml": "^4.1.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0" }, "devDependencies": { "@types/react": "^18.3.18", @@ -33,4 +35,3 @@ "vite": "^6.0.7" } } - diff --git a/frontend/src/api/agentApi.ts b/frontend/src/api/agentApi.ts new file mode 100644 index 00000000..f2ef1fe4 --- /dev/null +++ b/frontend/src/api/agentApi.ts @@ -0,0 +1,77 @@ +import request from './request'; + +export interface AgentConfig { + id: string; + name: string; + nameEn: string; + description: string; + category: string; + icon: string; + enabled: boolean; + systemPromptFile: string; + userPromptTemplateFile: string; + models: { + [modelName: string]: { + temperature: number; + maxTokens: number; + topP?: number; + }; + }; + ragEnabled: boolean; + requiresProject: boolean; + outputFormat: 'text' | 'structured' | 'document'; + tags: string[]; +} + +export interface ApiResponse { + success: boolean; + data?: T; + message?: string; + error?: string; +} + +export const agentApi = { + // 获取所有智能体列表 + getAll: async (): Promise> => { + const response = await request.get('/agents'); + return response.data; + }, + + // 获取启用的智能体列表 + getEnabled: async (): Promise> => { + const response = await request.get('/agents/enabled'); + return response.data; + }, + + // 获取单个智能体详情 + getById: async (id: string): Promise> => { + const response = await request.get(`/agents/${id}`); + return response.data; + }, + + // 根据分类获取智能体 + getByCategory: async (category: string): Promise> => { + const response = await request.get(`/agents/by-category?category=${encodeURIComponent(category)}`); + return response.data; + }, + + // 获取智能体的系统Prompt + getSystemPrompt: async (id: string): Promise> => { + const response = await request.get(`/agents/${id}/system-prompt`); + return response.data; + }, + + // 渲染用户Prompt + renderPrompt: async ( + id: string, + data: { + projectBackground?: string; + userInput: string; + knowledgeBaseContext?: string; + } + ): Promise> => { + const response = await request.post(`/agents/${id}/render-prompt`, data); + return response.data; + }, +}; + diff --git a/frontend/src/pages/AgentChatPage.tsx b/frontend/src/pages/AgentChatPage.tsx index 4a984c41..06d891a6 100644 --- a/frontend/src/pages/AgentChatPage.tsx +++ b/frontend/src/pages/AgentChatPage.tsx @@ -1,5 +1,6 @@ import { useParams } from 'react-router-dom' -import { Card, Typography, Input, Button, Space, Select, Upload, Tag, Alert, Divider } from 'antd' +import { useState, useEffect } from 'react' +import { Card, Typography, Input, Button, Space, Select, Upload, Tag, Alert, Divider, Spin } from 'antd' import { SendOutlined, PaperClipOutlined, @@ -7,20 +8,57 @@ import { FolderOpenOutlined, SyncOutlined, } from '@ant-design/icons' -import { AGENTS } from '../layouts/MainLayout' +import { agentApi, type AgentConfig } from '../api/agentApi' +import { message } from 'antd' const { Title, Paragraph } = Typography const { TextArea } = Input const AgentChatPage = () => { const { agentId } = useParams() - const agent = AGENTS.find((a) => a.id === agentId) + const [agent, setAgent] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) - if (!agent) { + // 加载智能体配置 + useEffect(() => { + const fetchAgent = async () => { + if (!agentId) return + + try { + setLoading(true) + const response = await agentApi.getById(agentId) + + if (response.success && response.data) { + setAgent(response.data) + } else { + setError(response.message || '智能体不存在') + } + } catch (err) { + console.error('Failed to load agent:', err) + setError('加载智能体配置失败') + message.error('加载智能体配置失败') + } finally { + setLoading(false) + } + } + + fetchAgent() + }, [agentId]) + + if (loading) { + return ( +
+ +
+ ) + } + + if (error || !agent) { return ( @@ -49,7 +87,10 @@ const AgentChatPage = () => { {agent.name} - 当前模型:DeepSeek-V3 + {agent.description} + + + 当前模型:DeepSeek-V3 | 分类:{agent.category}