feat(aia): Complete AIA V2.0 with universal streaming capabilities
Major Updates: - Add StreamingService with OpenAI Compatible format (backend/common/streaming) - Upgrade Chat component V2 with Ant Design X integration - Implement AIA module with 12 intelligent agents - Create AgentHub with 100% prototype V11 restoration - Create ChatWorkspace with streaming response support - Add ThinkingBlock for deep thinking display - Add useAIStream Hook for OpenAI Compatible stream handling Backend Common Capabilities (~400 lines): - OpenAIStreamAdapter: SSE adapter with OpenAI format - StreamingService: unified streaming service - Support content and reasoning_content dual streams - Deep thinking tag processing (<think>...</think>) Frontend Common Capabilities (~2000 lines): - AIStreamChat: modern streaming chat component - ThinkingBlock: collapsible deep thinking display - ConversationList: conversation management with grouping - useAIStream: OpenAI Compatible stream handler Hook - useConversations: conversation state management Hook - Modern design styles (Ultramodern theme) AIA Module Frontend (~1500 lines): - AgentHub: 12 agent cards with timeline design - ChatWorkspace: fullscreen immersive chat interface - AgentCard: theme-colored cards (blue/yellow/teal/purple) - 5 phases, 12 agents configuration - Responsive layout (desktop + mobile) AIA Module Backend (~900 lines): - agentService: 12 agents config with system prompts - conversationService: refactored with StreamingService - attachmentService: file upload skeleton (30k token limit) - 12 API endpoints with authentication - Full CRUD for conversations and messages Documentation: - AIA module status and development guide - Universal capabilities catalog (11 services) - Quick reference card for developers - System overview updates Testing: - Stream response verified (HTTP 200) - Authentication working correctly - Auto conversation creation working - Deep thinking display working - Message input and send working Status: Core features completed (85%), attachment and history loading pending
This commit is contained in:
233
backend/src/modules/aia/controllers/agentController.ts
Normal file
233
backend/src/modules/aia/controllers/agentController.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
/**
|
||||
* 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, // 直接使用原始查询作为预填充
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user