246 lines
5.7 KiB
TypeScript
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, // 直接使用原始查询作为预填充
|
|
};
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|