Files
AIclinicalresearch/backend/src/modules/aia/controllers/agentController.ts

246 lines
5.7 KiB
TypeScript

/**
* AIA 智能问答模块 - 智能体控制器
* @module aia/controllers/agentController
*
* API 端点:
* - GET /api/v1/aia/agents 获取智能体列表
* - GET /api/v1/aia/agents/:id 获取智能体详情
* - POST /api/v1/aia/intent/route 意图路由
*/
import type { FastifyRequest, FastifyReply } from 'fastify';
import { logger } from '../../../common/logging/index.js';
import * as agentService from '../services/agentService.js';
import type { AgentStage, IntentRouteRequest } from '../types/index.js';
/**
* 从 JWT Token 获取用户 ID
*/
function getUserId(request: FastifyRequest): string {
const userId = (request as any).user?.userId;
if (!userId) {
throw new Error('User not authenticated');
}
return userId;
}
// ==================== 智能体管理 ====================
/**
* 获取智能体列表
* GET /api/v1/aia/agents
*/
export async function getAgents(
request: FastifyRequest<{
Querystring: { stage?: AgentStage };
}>,
reply: FastifyReply
) {
try {
const { stage } = request.query;
logger.info('[AIA:Controller] 获取智能体列表', { stage });
const agents = await agentService.getAgents(stage);
return reply.send({
code: 0,
data: { agents },
});
} catch (error) {
logger.error('[AIA:Controller] 获取智能体列表失败', { error });
return reply.status(500).send({
code: -1,
error: {
code: 'INTERNAL_ERROR',
message: error instanceof Error ? error.message : '服务器内部错误',
},
});
}
}
/**
* 获取智能体详情
* GET /api/v1/aia/agents/:id
*/
export async function getAgentById(
request: FastifyRequest<{
Params: { id: string };
}>,
reply: FastifyReply
) {
try {
const { id } = request.params;
logger.info('[AIA:Controller] 获取智能体详情', { agentId: id });
const agent = await agentService.getAgentById(id);
if (!agent) {
return reply.status(404).send({
code: -1,
error: {
code: 'NOT_FOUND',
message: '智能体不存在',
},
});
}
return reply.send({
code: 0,
data: agent,
});
} catch (error) {
logger.error('[AIA:Controller] 获取智能体详情失败', { error });
return reply.status(500).send({
code: -1,
error: {
code: 'INTERNAL_ERROR',
message: error instanceof Error ? error.message : '服务器内部错误',
},
});
}
}
// ==================== 意图路由 ====================
/**
* 意图路由
* POST /api/v1/aia/intent/route
*
* 根据用户输入识别意图,推荐合适的智能体
*/
export async function routeIntent(
request: FastifyRequest<{
Body: IntentRouteRequest;
}>,
reply: FastifyReply
) {
try {
const userId = getUserId(request);
const { query } = request.body;
logger.info('[AIA:Controller] 意图路由', { userId, queryLength: query?.length });
if (!query || query.trim().length < 2) {
return reply.status(400).send({
code: -1,
error: {
code: 'VALIDATION_ERROR',
message: '查询内容至少需要2个字符',
},
});
}
// 简单的关键词匹配意图识别
// TODO: 后续可以使用 LLM 或专门的意图识别模型
const intent = await matchIntent(query);
return reply.send({
code: 0,
data: intent,
});
} catch (error) {
logger.error('[AIA:Controller] 意图路由失败', { error });
return reply.status(500).send({
code: -1,
error: {
code: 'INTERNAL_ERROR',
message: error instanceof Error ? error.message : '服务器内部错误',
},
});
}
}
/**
* 简单的关键词匹配意图识别
*/
async function matchIntent(query: string): Promise<{
agentId: string;
agentName: string;
confidence: number;
prefillPrompt: string;
}> {
const queryLower = query.toLowerCase();
// 关键词映射
const intentPatterns = [
{
patterns: ['研究设计', '方案设计', 'rct', '队列', '病例对照', '样本量'],
agentId: 'research-design',
agentName: '科研设计小助手',
},
{
patterns: ['文献', '检索', 'pubmed', '筛选', 'meta', '系统综述'],
agentId: 'literature-assistant',
agentName: '文献检索助手',
},
{
patterns: ['数据', '清洗', '缺失值', '异常值', '整理'],
agentId: 'data-assistant',
agentName: '数据管理助手',
},
{
patterns: ['统计', '分析', 'spss', 't检验', '卡方', '回归', 'p值'],
agentId: 'stat-assistant',
agentName: '统计分析小助手',
},
{
patterns: ['论文', '写作', '润色', '翻译', 'sci', '摘要', 'introduction'],
agentId: 'writing-assistant',
agentName: '论文撰写助手',
},
{
patterns: ['投稿', '期刊', '审稿', '发表', 'if', '影响因子'],
agentId: 'publish-assistant',
agentName: '投稿指导助手',
},
];
// 计算匹配度
let bestMatch = {
agentId: 'research-design',
agentName: '科研设计小助手',
confidence: 0.3, // 默认较低置信度
matchCount: 0,
};
for (const pattern of intentPatterns) {
let matchCount = 0;
for (const keyword of pattern.patterns) {
if (queryLower.includes(keyword)) {
matchCount++;
}
}
if (matchCount > bestMatch.matchCount) {
bestMatch = {
agentId: pattern.agentId,
agentName: pattern.agentName,
confidence: Math.min(0.5 + matchCount * 0.15, 0.95),
matchCount,
};
}
}
return {
agentId: bestMatch.agentId,
agentName: bestMatch.agentName,
confidence: bestMatch.confidence,
prefillPrompt: query, // 直接使用原始查询作为预填充
};
}