fix(admin): Fix Prompt management list not showing version info and add debug diagnostics
Summary: - Fix Prompt list API response schema missing activeVersion and draftVersion fields - Fastify was filtering out undefined schema fields, causing version columns to show empty - Add detailed diagnostic logging for Prompt debug mode troubleshooting - Verify debug mode works correctly (DRAFT version is used when debug enabled) Changes: - backend/src/common/prompt/prompt.routes.ts: Add activeVersion and draftVersion to response schema - backend/src/common/prompt/prompt.service.ts: Add diagnostic logs for setDebugMode and get methods - PKB module: Various authentication and document handling fixes from previous session Tested: Debug mode verified working - v2 DRAFT version correctly loaded when debug enabled
This commit is contained in:
@@ -76,3 +76,5 @@ export async function moduleRoutes(fastify: FastifyInstance) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -106,3 +106,5 @@ export interface PaginatedResponse<T> {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -352,6 +352,8 @@ runTests().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -293,6 +293,8 @@ runTest()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -331,6 +331,8 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -267,6 +267,8 @@ export const conflictDetectionService = new ConflictDetectionService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -217,6 +217,8 @@ curl -X POST http://localhost:3000/api/v1/dc/tool-c/test/execute \
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -271,6 +271,8 @@ export const streamAIController = new StreamAIController();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -183,3 +183,5 @@ logger.info('[SessionMemory] 会话记忆管理器已启动', {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -117,3 +117,5 @@ checkTableStructure();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -104,3 +104,5 @@ checkProjectConfig().catch(console.error);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -86,3 +86,5 @@ main();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -543,3 +543,5 @@ URL: https://iit.xunzhengyixue.com/api/v1/iit/patient-wechat/callback
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -178,3 +178,5 @@ console.log('');
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -495,3 +495,5 @@ export const patientWechatService = new PatientWechatService();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -140,3 +140,5 @@ testDifyIntegration().catch(error => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -167,5 +167,7 @@ testIitDatabase()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -155,3 +155,5 @@ if (hasError) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -181,3 +181,5 @@ async function testUrlVerification() {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -262,3 +262,5 @@ main().catch((error) => {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -146,3 +146,5 @@ Write-Host ""
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -237,5 +237,7 @@ export interface CachedProtocolRules {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
281
backend/src/modules/pkb/controllers/chatController.ts
Normal file
281
backend/src/modules/pkb/controllers/chatController.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
/**
|
||||
* PKB模块 - Chat控制器
|
||||
*
|
||||
* 知识库问答功能(全文阅读、逐篇精读模式)
|
||||
* 强制要求用户认证
|
||||
*
|
||||
* 简化版:不保存对话历史(PKB的全文阅读/逐篇精读是一次性问答)
|
||||
*/
|
||||
|
||||
import { FastifyRequest, FastifyReply } from 'fastify';
|
||||
import { ModelType } from '../../../common/llm/adapters/types.js';
|
||||
import { LLMFactory } from '../../../common/llm/adapters/LLMFactory.js';
|
||||
import { prisma } from '../../../config/database.js';
|
||||
import { logger } from '../../../common/logging/index.js';
|
||||
|
||||
/**
|
||||
* 引用信息接口
|
||||
*/
|
||||
interface Citation {
|
||||
id: number;
|
||||
fileName: string;
|
||||
position: number;
|
||||
score: number;
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取文本片段(用于引用上下文)
|
||||
*/
|
||||
function extractContextPreview(text: string, maxLength: number = 100): string {
|
||||
if (!text) return '';
|
||||
|
||||
const cleaned = text.replace(/\s+/g, ' ').trim();
|
||||
if (cleaned.length <= maxLength) {
|
||||
return cleaned;
|
||||
}
|
||||
|
||||
const truncated = cleaned.substring(0, maxLength);
|
||||
const lastPunctuation = Math.max(
|
||||
truncated.lastIndexOf('。'),
|
||||
truncated.lastIndexOf('!'),
|
||||
truncated.lastIndexOf('?'),
|
||||
truncated.lastIndexOf('.'),
|
||||
truncated.lastIndexOf('!'),
|
||||
truncated.lastIndexOf('?')
|
||||
);
|
||||
|
||||
if (lastPunctuation > maxLength * 0.5) {
|
||||
return truncated.substring(0, lastPunctuation + 1);
|
||||
}
|
||||
|
||||
return truncated + '...';
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化引用清单
|
||||
*/
|
||||
function formatCitations(citations: Citation[]): string {
|
||||
if (citations.length === 0) return '';
|
||||
|
||||
let result = '\n\n---\n\n📚 **参考文献**\n\n';
|
||||
|
||||
for (const cite of citations) {
|
||||
const scorePercent = (cite.score * 100).toFixed(0);
|
||||
const preview = extractContextPreview(cite.content, 100);
|
||||
|
||||
result += `<span id="citation-detail-${cite.id}">[${cite.id}]</span> 📄 **${cite.fileName}** - 第${cite.position}段 (相关度${scorePercent}%)\n`;
|
||||
result += ` "${preview}"\n\n`;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
interface SendChatMessageBody {
|
||||
content: string;
|
||||
modelType: ModelType;
|
||||
knowledgeBaseIds?: string[];
|
||||
documentIds?: string[];
|
||||
fullTextDocumentIds?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息(流式输出)
|
||||
*
|
||||
* POST /api/v2/pkb/chat/stream
|
||||
*/
|
||||
export async function sendMessageStream(
|
||||
request: FastifyRequest<{ Body: SendChatMessageBody }>,
|
||||
reply: FastifyReply
|
||||
) {
|
||||
try {
|
||||
// 从认证中获取userId(已由authenticate中间件验证)
|
||||
const userId = request.user!.userId;
|
||||
|
||||
const { content, modelType, knowledgeBaseIds, fullTextDocumentIds } = request.body;
|
||||
|
||||
logger.info('[PKB Chat] 收到对话请求', {
|
||||
userId,
|
||||
modelType,
|
||||
knowledgeBaseIds: knowledgeBaseIds || [],
|
||||
fullTextDocumentIds: fullTextDocumentIds || [],
|
||||
});
|
||||
|
||||
// 验证modelType
|
||||
const validModels = ['deepseek-v3', 'qwen3-72b', 'qwen-long', 'gemini-pro'];
|
||||
if (!validModels.includes(modelType)) {
|
||||
reply.code(400).send({
|
||||
success: false,
|
||||
message: `不支持的模型类型: ${modelType}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// 检索知识库上下文
|
||||
let knowledgeBaseContext = '';
|
||||
const allCitations: Citation[] = [];
|
||||
|
||||
// 全文阅读模式
|
||||
if (fullTextDocumentIds && fullTextDocumentIds.length > 0) {
|
||||
logger.info('[PKB Chat] 全文阅读模式', { documentCount: fullTextDocumentIds.length });
|
||||
|
||||
try {
|
||||
const documents = await prisma.document.findMany({
|
||||
where: {
|
||||
id: { in: fullTextDocumentIds },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
filename: true,
|
||||
extractedText: true,
|
||||
tokensCount: true,
|
||||
},
|
||||
orderBy: {
|
||||
filename: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
const validDocuments = documents.filter(doc => doc.extractedText && doc.extractedText.trim().length > 0);
|
||||
|
||||
if (validDocuments.length === 0) {
|
||||
logger.warn('[PKB Chat] 所有文档都没有提取文本');
|
||||
}
|
||||
|
||||
const fullTextParts: string[] = [];
|
||||
|
||||
for (let i = 0; i < validDocuments.length; i++) {
|
||||
const doc = validDocuments[i];
|
||||
const docNumber = i + 1;
|
||||
|
||||
allCitations.push({
|
||||
id: docNumber,
|
||||
fileName: doc.filename,
|
||||
position: 0,
|
||||
score: 1.0,
|
||||
content: doc.extractedText?.substring(0, 200) || '(无内容)',
|
||||
});
|
||||
|
||||
fullTextParts.push(
|
||||
`【文献${docNumber}:${doc.filename}】\n\n${doc.extractedText}`
|
||||
);
|
||||
}
|
||||
|
||||
knowledgeBaseContext = fullTextParts.join('\n\n---\n\n');
|
||||
|
||||
const totalTokens = validDocuments.reduce((sum, doc) => sum + (doc.tokensCount || 0), 0);
|
||||
|
||||
logger.info('[PKB Chat] 全文上下文已组装', {
|
||||
totalDocuments: validDocuments.length,
|
||||
totalCharacters: knowledgeBaseContext.length,
|
||||
totalTokens,
|
||||
});
|
||||
|
||||
// Token限制检查
|
||||
const QWEN_LONG_INPUT_LIMIT = 1000000;
|
||||
const SYSTEM_OVERHEAD = 10000;
|
||||
const SAFE_INPUT_LIMIT = QWEN_LONG_INPUT_LIMIT - SYSTEM_OVERHEAD;
|
||||
|
||||
if (totalTokens > SAFE_INPUT_LIMIT) {
|
||||
const errorMsg = `输入Token数量 (${totalTokens}) 超出限制 (${SAFE_INPUT_LIMIT})。请减少文献数量。`;
|
||||
logger.error('[PKB Chat] Token超限', { totalTokens, limit: SAFE_INPUT_LIMIT });
|
||||
|
||||
reply.raw.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
});
|
||||
reply.raw.write(`data: ${JSON.stringify({
|
||||
content: `\n\n⚠️ **Token数量超限**\n\n${errorMsg}`,
|
||||
role: 'assistant',
|
||||
error: true,
|
||||
})}\n\n`);
|
||||
reply.raw.write('data: [DONE]\n\n');
|
||||
return reply.raw.end();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
logger.error('[PKB Chat] 加载文献全文失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 组装消息
|
||||
let systemPrompt = '你是一个专业、友好的AI助手。';
|
||||
|
||||
if (fullTextDocumentIds && fullTextDocumentIds.length > 0) {
|
||||
systemPrompt = '你是一个专业的学术文献分析助手。用户会提供多篇文献的完整全文,每篇文献用【文献N:文件名】标记。请认真阅读所有文献,进行深入的综合分析。在回答时请引用具体文献,使用【文献N】格式。';
|
||||
}
|
||||
|
||||
const messages: any[] = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
];
|
||||
|
||||
let userContent = content;
|
||||
if (knowledgeBaseContext) {
|
||||
if (fullTextDocumentIds && fullTextDocumentIds.length > 0) {
|
||||
userContent = `${content}\n\n## 参考资料(文献全文)\n\n${knowledgeBaseContext}`;
|
||||
} else {
|
||||
userContent = `${content}\n\n## 参考资料\n\n${knowledgeBaseContext}`;
|
||||
}
|
||||
}
|
||||
messages.push({ role: 'user', content: userContent });
|
||||
|
||||
// 设置SSE响应头
|
||||
reply.raw.writeHead(200, {
|
||||
'Content-Type': 'text/event-stream',
|
||||
'Cache-Control': 'no-cache',
|
||||
'Connection': 'keep-alive',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
});
|
||||
|
||||
// 流式输出
|
||||
const adapter = LLMFactory.getAdapter(modelType);
|
||||
let fullContent = '';
|
||||
|
||||
const maxOutputTokens = fullTextDocumentIds && fullTextDocumentIds.length > 0 ? 6000 : 2000;
|
||||
|
||||
logger.info('[PKB Chat] 开始LLM调用', { model: modelType, maxOutputTokens });
|
||||
|
||||
for await (const chunk of adapter.chatStream(messages, {
|
||||
temperature: 0.7,
|
||||
maxTokens: maxOutputTokens,
|
||||
})) {
|
||||
fullContent += chunk.content;
|
||||
reply.raw.write(`data: ${JSON.stringify(chunk)}\n\n`);
|
||||
}
|
||||
|
||||
// 追加引用清单
|
||||
if (allCitations.length > 0) {
|
||||
const citationsText = formatCitations(allCitations);
|
||||
fullContent += citationsText;
|
||||
reply.raw.write(`data: ${JSON.stringify({ content: citationsText, role: 'assistant' })}\n\n`);
|
||||
}
|
||||
|
||||
reply.raw.write('data: [DONE]\n\n');
|
||||
reply.raw.end();
|
||||
|
||||
logger.info('[PKB Chat] 对话完成', {
|
||||
userId,
|
||||
responseLength: fullContent.length,
|
||||
citationsCount: allCitations.length,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error('[PKB Chat] 错误:', error);
|
||||
|
||||
// 如果还没有发送响应头,返回JSON错误
|
||||
if (!reply.raw.headersSent) {
|
||||
reply.code(500).send({
|
||||
success: false,
|
||||
message: error.message || '服务器错误',
|
||||
});
|
||||
} else {
|
||||
// 已经发送了SSE头,尝试发送错误信息
|
||||
try {
|
||||
reply.raw.write(`data: ${JSON.stringify({ error: true, message: error.message })}\n\n`);
|
||||
reply.raw.write('data: [DONE]\n\n');
|
||||
reply.raw.end();
|
||||
} catch (e) {
|
||||
// 忽略
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
backend/src/modules/pkb/routes/chatRoutes.ts
Normal file
17
backend/src/modules/pkb/routes/chatRoutes.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* PKB模块 - Chat路由
|
||||
*
|
||||
* 知识库问答功能(全文阅读、逐篇精读模式)
|
||||
* 所有路由都需要认证
|
||||
*/
|
||||
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import { sendMessageStream } from '../controllers/chatController.js';
|
||||
import { authenticate, requireModule } from '../../../common/auth/auth.middleware.js';
|
||||
|
||||
export default async function chatRoutes(fastify: FastifyInstance) {
|
||||
// 发送消息(流式输出)
|
||||
fastify.post('/stream', {
|
||||
preHandler: [authenticate, requireModule('PKB')]
|
||||
}, sendMessageStream as any);
|
||||
}
|
||||
@@ -52,3 +52,5 @@ export default async function healthRoutes(fastify: FastifyInstance) {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import knowledgeBaseRoutes from './knowledgeBases.js';
|
||||
import batchRoutes from './batchRoutes.js';
|
||||
import chatRoutes from './chatRoutes.js';
|
||||
import healthRoutes from './health.js';
|
||||
|
||||
export default async function pkbRoutes(fastify: FastifyInstance) {
|
||||
@@ -15,5 +16,8 @@ export default async function pkbRoutes(fastify: FastifyInstance) {
|
||||
|
||||
// 注册批处理路由
|
||||
fastify.register(batchRoutes, { prefix: '/batch-tasks' });
|
||||
|
||||
// 注册Chat路由(全文阅读、逐篇精读)
|
||||
fastify.register(chatRoutes, { prefix: '/chat' });
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,18 @@ export async function uploadDocument(
|
||||
throw new Error('Document limit exceeded. Maximum 50 documents per knowledge base');
|
||||
}
|
||||
|
||||
// 3. 检查文档是否已存在(同名文件查重)
|
||||
const existingDoc = await prisma.document.findFirst({
|
||||
where: {
|
||||
kbId,
|
||||
filename: filename,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingDoc) {
|
||||
throw new Error(`文档 "${filename}" 已存在,请勿重复上传`);
|
||||
}
|
||||
|
||||
// 3. 在数据库中创建文档记录(状态:uploading)
|
||||
const document = await prisma.document.create({
|
||||
data: {
|
||||
|
||||
@@ -130,3 +130,5 @@ Content-Type: application/json
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -115,3 +115,5 @@ Write-Host " - 删除任务: DELETE $BaseUrl/api/v2/rvw/tasks/{taskId}" -Foregr
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -29,3 +29,5 @@ export * from './services/utils.js';
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -120,3 +120,5 @@ export function validateAgentSelection(agents: string[]): void {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user