diff --git a/backend/src/rag/chains.py b/backend/src/rag/chains.py index ba3a95d..8fbd760 100644 --- a/backend/src/rag/chains.py +++ b/backend/src/rag/chains.py @@ -133,37 +133,34 @@ class RAGChain: """流式调用(返回答案流和文档,包含思考过程)""" import time - # 0. 思考阶段开始 start_time = time.time() - yield {"type": "thinking", "stage": "understanding", "message": "正在理解问题..."} # 1. 检索文档 - yield {"type": "thinking", "stage": "retrieving", "message": "正在检索相关知识..."} retrieval_start = time.time() docs = await self.retriever.ainvoke(question) retrieval_time = time.time() - retrieval_start - # 发送检索结果 — 包含文档标题和摘要 - doc_details = [] - for i, doc in enumerate(docs[:5]): - metadata = doc.metadata if hasattr(doc, 'metadata') else {} - title = metadata.get("title", metadata.get("filename", f"文档 {i+1}")) - preview = doc.page_content[:100].replace('\n', ' ') - doc_details.append(f"**{title}**: {preview}...") + # 发送检索结果(只在有文档时) + if docs: + doc_details = [] + for i, doc in enumerate(docs[:5]): + metadata = doc.metadata if hasattr(doc, 'metadata') else {} + title = metadata.get("title", metadata.get("filename", f"文档 {i+1}")) + preview = doc.page_content[:100].replace('\n', ' ') + doc_details.append(f"**{title}**: {preview}...") - yield { - "type": "thinking", - "stage": "retrieved", - "message": f"检索到 {len(docs)} 篇相关文档", - "doc_count": len(docs), - "time": round(retrieval_time, 2), - "details": doc_details - } + yield { + "type": "thinking", + "stage": "retrieved", + "message": f"检索到 {len(docs)} 篇相关文档", + "doc_count": len(docs), + "time": round(retrieval_time, 2), + "details": doc_details + } context = "\n\n".join(doc.page_content for doc in docs) - # 2. 构建prompt - yield {"type": "thinking", "stage": "generating", "message": f"基于 {len(docs)} 篇文档生成回答..."} + # 2. 构建prompt并流式生成 prompt_value = await self.prompt.ainvoke({"context": context, "question": question}) messages = prompt_value.to_messages() diff --git a/backend/src/rag/conversation_chains.py b/backend/src/rag/conversation_chains.py index 06f8bd0..f684361 100644 --- a/backend/src/rag/conversation_chains.py +++ b/backend/src/rag/conversation_chains.py @@ -85,16 +85,8 @@ class ConversationChain: """流式调用(包含思考过程,捕获推理模型的真实推理内容)""" import time - # 思考阶段 start_time = time.time() - yield {"type": "thinking", "stage": "understanding", "message": "正在理解问题..."} - - # 准备历史 history_messages = self._format_history(chat_history or []) - history_count = len([m for m in (chat_history or []) if m["role"] == "user"]) - yield {"type": "thinking", "stage": "preparing", "message": f"加载对话上下文({history_count} 轮历史)..." if history_count > 0 else "准备生成回答..."} - - yield {"type": "thinking", "stage": "generating", "message": "正在生成回答..."} # 直接使用原始 OpenAI SDK 捕获推理内容 prompt_value = await self.prompt.ainvoke({ diff --git a/web/src/components/chat/message-item.tsx b/web/src/components/chat/message-item.tsx index 092107f..8fc455f 100644 --- a/web/src/components/chat/message-item.tsx +++ b/web/src/components/chat/message-item.tsx @@ -1,7 +1,7 @@ "use client"; import { ChatMessage, ThinkingStep } from "@/types"; -import { User, Bot, Copy, Edit, RotateCcw, ThumbsUp, ThumbsDown, Loader2, CheckCircle2, FileSearch, Brain, Sparkles } from "lucide-react"; +import { User, Bot, Copy, Edit, RotateCcw, ThumbsUp, ThumbsDown, Loader2, Brain } from "lucide-react"; import { cn } from "@/lib/utils"; import ReactMarkdown from "react-markdown"; import remarkGfm from "remark-gfm"; @@ -23,30 +23,29 @@ const ThinkingProcess = ({ thinking, isStreaming }: { thinking: ThinkingStep[]; const [expanded, setExpanded] = useState(false); if (!thinking || thinking.length === 0) return null; - // 分离状态步骤和推理内容 - const statusSteps = thinking.filter(s => s.stage !== 'reasoning'); + // 只提取有实际内容的步骤:推理内容和检索文档 const reasoningSteps = thinking.filter(s => s.stage === 'reasoning'); + const retrievedStep = thinking.find(s => s.stage === 'retrieved'); const hasReasoning = reasoningSteps.length > 0; + const hasDocs = retrievedStep?.details && retrievedStep.details.length > 0; + + // 没有推理内容也没有文档详情时不显示 + if (!hasReasoning && !hasDocs && !isStreaming) return null; // 合并推理文本 const reasoningText = reasoningSteps.map(s => s.message).join(''); - // 汇总信息 - const retrievedStep = statusSteps.find(s => s.stage === 'retrieved'); - const totalTime = statusSteps.reduce((sum, s) => sum + (s.time || 0), 0); - - // 流式时显示最后状态 - const lastStatusStep = statusSteps[statusSteps.length - 1]; + // 流式时是否正在推理 const isReasoningNow = isStreaming && thinking[thinking.length - 1]?.stage === 'reasoning'; // 折叠标题 const collapsedTitle = isStreaming - ? isReasoningNow - ? '深度思考中...' - : lastStatusStep?.message || '思考中...' + ? isReasoningNow ? '思考中...' : '检索中...' : hasReasoning ? `思考过程 (${reasoningText.length} 字)` - : `思考过程${totalTime > 0 ? ` (${totalTime.toFixed(1)}s)` : ''}`; + : retrievedStep + ? `检索到 ${retrievedStep.doc_count} 篇相关文档` + : '思考过程'; return (
@@ -62,37 +61,14 @@ const ThinkingProcess = ({ thinking, isStreaming }: { thinking: ThinkingStep[]; {collapsedTitle} - {retrievedStep?.doc_count != null && ( - · {retrievedStep.doc_count} 篇文档 - )} - {totalTime > 0 && !isStreaming && ( - · {totalTime.toFixed(1)}s - )} {expanded && (
- {/* 状态步骤 */} - {statusSteps.map((step, index) => ( -
- {step.stage === 'retrieving' ? ( - - ) : step.stage === 'retrieved' ? ( - - ) : step.stage === 'generating' ? ( - - ) : ( -
- )} - {step.message} - {step.time != null && {step.time.toFixed(1)}s} -
- ))} - {/* 检索到的文档详情 */} - {retrievedStep?.details && retrievedStep.details.length > 0 && ( -
+ {hasDocs && ( +
参考文档:
- {retrievedStep.details.map((detail: string, i: number) => ( + {retrievedStep?.details?.map((detail: string, i: number) => (
{detail}
@@ -103,9 +79,6 @@ const ThinkingProcess = ({ thinking, isStreaming }: { thinking: ThinkingStep[]; {/* 推理内容 */} {hasReasoning && (
-
- {isReasoningNow ? '推理进行中...' : '推理过程:'} -
{reasoningText}