From 048f57a89f8ce67a4aa06253971b1715ad26eace Mon Sep 17 00:00:00 2001 From: root <953994191@qq.com> Date: Mon, 27 Apr 2026 15:23:50 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9B=86=E6=88=90=E4=B8=89=E4=B8=AA=E5=AD=90?= =?UTF-8?q?=E5=9B=BE=E5=88=B0=E4=B8=BBAgent=E6=9E=B6=E6=9E=84=20+=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=89=8D=E5=90=8E=E7=AB=AF=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E4=B8=8D=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要变更: 1. 创建 subgraph_tools.py - 将三个子图包装为 LangChain 工具 2. 更新 graph_tools.py - 删除旧工具,添加子图工具 3. 更新系统提示词 - 介绍三个子系统 + RAG 能力 4. 简化 backend.py - 删除独立子图 API 端点 5. 修复 service.py 字段名不匹配问题 - content -> token 6. 前端界面优化 - 移动子图测试到侧边栏、删除测试审核按钮 7. 添加 pyjwt 依赖到 requirements.txt 8. 更新 docker-compose.yml - 添加前端代码挂载 --- backend/app/agent/prompts.py | 10 +- backend/app/agent/service.py | 15 +- .../app/agent_subgraphs/common/__init__.py | 34 +-- .../app/agent_subgraphs/dictionary/nodes.py | 13 +- backend/app/backend.py | 232 ------------------ backend/app/graph/graph_tools.py | 102 +------- backend/app/graph/subgraph_tools.py | 193 +++++++++++++++ backend/app/rag/__init__.py | 5 +- backend/requirements.txt | 1 + docker/docker-compose.yml | 2 + frontend/src/components/chat_area.py | 26 -- frontend/src/components/sidebar.py | 11 + frontend/src/frontend_main.py | 6 - 13 files changed, 244 insertions(+), 406 deletions(-) create mode 100644 backend/app/graph/subgraph_tools.py diff --git a/backend/app/agent/prompts.py b/backend/app/agent/prompts.py index 8b05050..848b8c6 100644 --- a/backend/app/agent/prompts.py +++ b/backend/app/agent/prompts.py @@ -9,14 +9,18 @@ def create_system_prompt(tools: list = None) -> ChatPromptTemplate: if tools: tool_descs = [] for tool in tools: - # 提取工具名称和描述的第一行 name = getattr(tool, 'name', None) or getattr(tool, '__name__', 'unknown_tool') desc = (tool.description or "").split('\n')[0] tool_descs.append(f"- {name}: {desc}") tools_section = "\n".join(tool_descs) system_template = ( - "你是一个个人生活助手和数据分析助手,请使用中文交流。\n\n" + "你是一个智能助手,具有三个专业子系统和RAG检索能力,请使用中文交流。\n\n" + "【核心功能】\n" + "1. 📚 词典/翻译子系统 - 查询单词、翻译文本、提取术语、每日一词\n" + "2. 📰 资讯分析子系统 - 查询新闻、分析URL、提取关键词、生成报告\n" + "3. 📇 通讯录子系统 - 查询联系人、添加联系人、管理通讯录\n" + "4. 🔍 RAG检索 - 从知识库中检索相关信息回答问题\n\n" "【用户背景信息】\n" "以下是对当前用户的已知信息和长期记忆,你必须优先采纳并在回答中体现:\n" "{memory_context}\n" @@ -34,4 +38,4 @@ def create_system_prompt(tools: list = None) -> ChatPromptTemplate: return ChatPromptTemplate.from_messages([ ("system", system_template), MessagesPlaceholder(variable_name="messages") - ]) \ No newline at end of file + ]) diff --git a/backend/app/agent/service.py b/backend/app/agent/service.py index 4ac79e6..51a3091 100644 --- a/backend/app/agent/service.py +++ b/backend/app/agent/service.py @@ -187,9 +187,9 @@ class AIAgentService: # 处理思考过程 if reasoning_token: processed_event = { - "type": "reasoning", + "type": "llm_token", "node": node_name, - "content": reasoning_token + "reasoning_token": reasoning_token } # 处理工具调用 elif hasattr(message_chunk, 'tool_calls') and message_chunk.tool_calls: @@ -215,7 +215,8 @@ class AIAgentService: processed_event = { "type": "llm_token", "node": node_name, - "content": token_content + "token": token_content, # ✅ 改为 token + "reasoning_token": reasoning_token } elif chunk_type == "updates": @@ -289,7 +290,7 @@ class AIAgentService: yield { "type": "llm_token", "node": "fast_path", - "content": char + "token": char # ✅ 改为 token } await asyncio.sleep(0.03) @@ -303,7 +304,7 @@ class AIAgentService: yield { "type": "llm_token", "node": "fast_path", - "content": char + "token": char # ✅ 改为 token } await asyncio.sleep(0.03) @@ -334,7 +335,7 @@ class AIAgentService: yield { "type": "llm_token", "node": "fast_path", - "content": char + "token": char # ✅ 改为 token } await asyncio.sleep(0.03) @@ -348,7 +349,7 @@ class AIAgentService: yield { "type": "llm_token", "node": "fast_path", - "content": char + "token": char # ✅ 改为 token } await asyncio.sleep(0.03) diff --git a/backend/app/agent_subgraphs/common/__init__.py b/backend/app/agent_subgraphs/common/__init__.py index 2a5e3cf..c66cecd 100644 --- a/backend/app/agent_subgraphs/common/__init__.py +++ b/backend/app/agent_subgraphs/common/__init__.py @@ -17,24 +17,13 @@ from .formatter import ( ) from .intent import ( - # 旧版 API(保持向后兼容) - IntentType, - Intent, - Entity, - IntentParser, - RuleBasedIntentClassifier, - RuleBasedEntityExtractor, - IntentRegistry, - create_default_intent_parser, - # 新版 React 模式 API + # React 模式 API ReasoningAction, RetrievalConfig, ReasoningResult, - BaseIntentReasoner, - RuleBasedReactReasoner, - LLMReactReasoner, - create_react_reasoner, + ReactIntentReasoner, react_reason, + react_reason_async, get_route_by_reasoning ) @@ -60,24 +49,13 @@ __all__ = [ "TemplateManager", "OutputRenderer", "PresetTemplates", - # intent - 旧版 - "IntentType", - "Intent", - "Entity", - "IntentParser", - "RuleBasedIntentClassifier", - "RuleBasedEntityExtractor", - "IntentRegistry", - "create_default_intent_parser", - # intent - 新版 React 模式 + # intent - React 模式 "ReasoningAction", "RetrievalConfig", "ReasoningResult", - "BaseIntentReasoner", - "RuleBasedReactReasoner", - "LLMReactReasoner", - "create_react_reasoner", + "ReactIntentReasoner", "react_reason", + "react_reason_async", "get_route_by_reasoning", # human_review "ReviewStatus", diff --git a/backend/app/agent_subgraphs/dictionary/nodes.py b/backend/app/agent_subgraphs/dictionary/nodes.py index f804a38..faf06c3 100644 --- a/backend/app/agent_subgraphs/dictionary/nodes.py +++ b/backend/app/agent_subgraphs/dictionary/nodes.py @@ -9,8 +9,6 @@ import random # 公共工具 from ..common import ( - IntentParser, - RuleBasedIntentClassifier, MarkdownFormatter ) @@ -29,18 +27,9 @@ WORD_BOOK_DB: Dict[str, List[Dict]] = {} # user_id -> [word_entries] def parse_intent(state: DictionaryState) -> DictionaryState: """ - 解析用户意图节点(使用公共工具) + 解析用户意图节点(使用规则匹配) 确定用户想做什么操作 """ - # 使用公共意图解析器 - parser = IntentParser() - - # 为词典子图添加特定规则 - if isinstance(parser.classifier, RuleBasedIntentClassifier): - # 为了复用公共工具,我们在内部继续使用关键词匹配 - # 但可以通过扩展 IntentParser 来支持子图特定的意图 - pass - # 子图特定的意图解析 query_lower = state.user_query.lower() diff --git a/backend/app/backend.py b/backend/app/backend.py index 2ab49de..ceee392 100644 --- a/backend/app/backend.py +++ b/backend/app/backend.py @@ -350,235 +350,3 @@ if __name__ == "__main__": # 使用环境变量或默认端口 8079(避免与 llama.cpp 的 8081 端口冲突) port = int(BACKEND_PORT) uvicorn.run(app, host="0.0.0.0", port=port) - - -# ==================== 子图专用 API 端点 ==================== -# 简化版本,直接调用各个子图,无需完整 agent_service -# 注意:这些是独立测试用的简化端点,方便前端直接调用 - -@app.get("/subgraph/dictionary/{action}") -async def dictionary_subgraph_api( - action: str, - query: str = "", - user_id: str = "default" -): - """词典子图简化 API""" - from backend.app.agent_subgraphs.dictionary import ( - DictionaryState, - DictionaryAction, - parse_intent, - format_result - ) - from backend.app.agent_subgraphs.dictionary.nodes import ( - query_word, translate_text, extract_terms, get_daily_word - ) - - # 创建初始状态 - state = DictionaryState(user_query=query, user_id=user_id) - - # 处理 action - try: - if action == "query": - state.action = DictionaryAction.QUERY - state.action_params = {"word": query} - state = query_word(state) - elif action == "translate": - state.action = DictionaryAction.TRANSLATE - state.source_text = query - state = translate_text(state) - elif action == "daily": - state.action = DictionaryAction.DAILY_WORD - state = get_daily_word(state) - elif action == "extract": - state.action = DictionaryAction.EXTRACT - state.action_params = {"text": query} - state = extract_terms(state) - else: - # 自动解析意图 - state = parse_intent(state) - # 根据解析后的 action 调用 - if state.action == DictionaryAction.QUERY: - state = query_word(state) - elif state.action == DictionaryAction.TRANSLATE: - state = translate_text(state) - elif state.action == DictionaryAction.DAILY_WORD: - state = get_daily_word(state) - elif state.action == DictionaryAction.EXTRACT: - state = extract_terms(state) - - # 格式化结果 - state = format_result(state) - - return { - "success": True, - "action": str(state.action), - "result": state.final_result, - "raw_data": { - "word_entry": vars(state.word_entry) if state.word_entry else None, - "translated_text": state.translated_text, - "extracted_terms": [vars(t) for t in state.extracted_terms], - "daily_word": vars(state.daily_word) if state.daily_word else None - } - } - except Exception as e: - return {"success": False, "error": str(e)} - - -@app.get("/subgraph/news/{action}") -async def news_subgraph_api( - action: str, - query: str = "", - user_id: str = "default" -): - """资讯子图简化 API""" - from backend.app.agent_subgraphs.news_analysis import ( - NewsAnalysisState, - NewsAction, - parse_intent, - format_result - ) - from backend.app.agent_subgraphs.news_analysis.nodes import ( - query_news, analyze_url, extract_keywords, generate_report - ) - - # 创建初始状态 - state = NewsAnalysisState(user_query=query, user_id=user_id) - - # 处理 action - try: - if action == "query": - state.action = NewsAction.QUERY_NEWS - state = query_news(state) - elif action == "analyze": - state.action = NewsAction.ANALYZE_URL - state.custom_urls = [query] - state = analyze_url(state) - elif action == "keywords": - state.action = NewsAction.EXTRACT_KEYWORDS - state = extract_keywords(state) - elif action == "report": - state.action = NewsAction.GENERATE_REPORT - state = generate_report(state) - else: - # 自动解析意图 - state = parse_intent(state) - # 根据解析后的 action 调用 - if state.action == NewsAction.QUERY_NEWS: - state = query_news(state) - elif state.action == NewsAction.ANALYZE_URL: - state.custom_urls = [query] - state = analyze_url(state) - elif state.action == NewsAction.EXTRACT_KEYWORDS: - state = extract_keywords(state) - elif state.action == NewsAction.GENERATE_REPORT: - state = generate_report(state) - - # 格式化结果 - state = format_result(state) - - return { - "success": True, - "action": str(state.action), - "result": state.final_result, - "raw_data": { - "news_items": [vars(item) for item in state.news_items], - "extracted_keywords": state.extracted_keywords, - "report_content": state.report_content - } - } - except Exception as e: - return {"success": False, "error": str(e)} - - -@app.get("/subgraph/contact/{action}") -async def contact_subgraph_api( - action: str, - query: str = "", - user_id: str = "default" -): - """通讯录子图简化 API""" - from backend.app.agent_subgraphs.contact import ( - ContactState, - ContactAction, - parse_intent, - format_result - ) - from backend.app.agent_subgraphs.contact.nodes import ( - list_contacts, add_contact, list_emails, generate_email_draft, sniff_contacts - ) - - # 创建初始状态 - state = ContactState(user_query=query, user_id=user_id) - - # 处理 action - try: - if action == "list": - state.action = ContactAction.CONTACT_LIST - state = list_contacts(state) - elif action == "add": - state.action = ContactAction.CONTACT_ADD - state = add_contact(state) - elif action == "emails": - state.action = ContactAction.EMAIL_LIST - state = list_emails(state) - elif action == "draft": - state.action = ContactAction.EMAIL_SEND - state = generate_email_draft(state) - elif action == "sniff": - state.action = ContactAction.SNIFF_CONTACTS - state = sniff_contacts(state) - else: - # 自动解析意图 - state = parse_intent(state) - # 根据解析后的 action 调用 - if state.action == ContactAction.CONTACT_LIST: - state = list_contacts(state) - elif state.action == ContactAction.CONTACT_ADD: - state = add_contact(state) - elif state.action == ContactAction.EMAIL_LIST: - state = list_emails(state) - elif state.action == ContactAction.EMAIL_SEND: - state = generate_email_draft(state) - elif state.action == ContactAction.SNIFF_CONTACTS: - state = sniff_contacts(state) - - # 格式化结果 - state = format_result(state) - - return { - "success": True, - "action": str(state.action), - "result": state.final_result, - "raw_data": { - "contacts": [vars(c) for c in state.contacts], - "emails": [vars(e) for e in state.emails], - "current_contact": vars(state.current_contact) if state.current_contact else None, - "draft": { - "subject": state.draft_subject, - "recipient": state.draft_recipient, - "body": state.draft_body - }, - "sniffed": [vars(c) for c in state.sniffed_contacts] - } - } - except Exception as e: - return {"success": False, "error": str(e)} - - -@app.get("/subgraph/help") -async def subgraph_help_api(): - """子图 API 使用帮助""" - return { - "dictionary": { - "actions": ["query", "translate", "daily", "extract", "auto"], - "endpoint": "/subgraph/dictionary/{action}" - }, - "news": { - "actions": ["query", "analyze", "keywords", "report", "auto"], - "endpoint": "/subgraph/news/{action}" - }, - "contact": { - "actions": ["list", "add", "emails", "draft", "sniff", "auto"], - "endpoint": "/subgraph/contact/{action}" - } - } diff --git a/backend/app/graph/graph_tools.py b/backend/app/graph/graph_tools.py index 1cc1e17..e87352a 100644 --- a/backend/app/graph/graph_tools.py +++ b/backend/app/graph/graph_tools.py @@ -1,95 +1,17 @@ """ -工具定义模块 - 纯函数工具,无依赖 AIAgent 类 +工具定义模块 - 子图工具 + RAG 工具 +Subgraph Tools + RAG Tools """ -# 标准库 -from pathlib import Path - -# 第三方库 -import pandas as pd -import pypdf -import requests -from bs4 import BeautifulSoup -from langchain_core.tools import tool - -def _file_allow_check(filename: str) -> Path: - """检查用户文件名是否位于允许目录 './user_docs' 下,防止路径遍历攻击。""" - allowed_dir = Path("./user_docs").resolve() - allowed_dir.mkdir(exist_ok=True) - - file_path = (allowed_dir / filename).resolve() - if not str(file_path).startswith(str(allowed_dir)): - raise ValueError("错误:非法文件路径。") - - if not file_path.exists(): - raise FileNotFoundError(f"错误:文件 '{filename}' 不存在。") - - return file_path - -@tool -def get_current_temperature(location: str) -> str: - """获取指定地点的当前温度。""" - return f'当前{location}的温度为25℃' - -@tool -def read_local_file(filename: str) -> str: - """读取用户指定名称的本地文本文件内容并返回摘要。""" - try: - file_path = _file_allow_check(filename) - with open(file_path, 'r', encoding='utf-8') as f: - content = f.read() - return f"文件 '{filename}' 的内容开头:\n{content[:1000]}..." - except Exception as e: - return f"读取文件时出错:{str(e)}" - -@tool -def read_pdf_summary(filename: str) -> str: - """读取PDF文件并返回内容文本摘要。""" - try: - file_path = _file_allow_check(filename) - text = "" - with open(file_path, 'rb') as f: - reader = pypdf.PdfReader(f) - for page in reader.pages[:3]: - text += page.extract_text() - return f"PDF文件 '{filename}' 的前几页内容:\n{text[:2000]}..." - except Exception as e: - return f"读取PDF出错:{e}" - -@tool -def read_excel_as_markdown(filename: str) -> str: - """读取Excel文件,并将其主要数据转换为Markdown表格格式。""" - try: - file_path = _file_allow_check(filename) - df = pd.read_excel(file_path) - markdown_table = df.head(10).to_markdown(index=False) - return f"Excel文件 '{filename}' 的数据预览(前10行):\n{markdown_table}" - except Exception as e: - return f"读取Excel出错:{e}" - -@tool -def fetch_webpage_content(url: str) -> str: - """抓取给定URL的网页正文内容,并返回清晰的纯文本。""" - try: - response = requests.get(url, timeout=10) - response.raise_for_status() - soup = BeautifulSoup(response.text, 'html.parser') - for script in soup(["script", "style"]): - script.decompose() - text = soup.get_text() - lines = (line.strip() for line in text.splitlines()) - chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) - text = '\n'.join(chunk for chunk in chunks if chunk) - return f"成功抓取网页 {url},正文内容开头:\n{text[:1500]}..." - except Exception as e: - return f"抓取网页时出错:{str(e)}" +# 子图工具 +from .subgraph_tools import ( + SUBGRAPH_TOOLS, + SUBGRAPH_TOOLS_BY_NAME, + dictionary_tool, + news_analysis_tool, + contact_tool +) # 工具列表和映射(全局常量) -AVAILABLE_TOOLS = [ - get_current_temperature, - read_local_file, - fetch_webpage_content, - read_pdf_summary, - read_excel_as_markdown -] -TOOLS_BY_NAME = {tool.name: tool for tool in AVAILABLE_TOOLS} +AVAILABLE_TOOLS = SUBGRAPH_TOOLS.copy() +TOOLS_BY_NAME = SUBGRAPH_TOOLS_BY_NAME.copy() diff --git a/backend/app/graph/subgraph_tools.py b/backend/app/graph/subgraph_tools.py new file mode 100644 index 0000000..f675508 --- /dev/null +++ b/backend/app/graph/subgraph_tools.py @@ -0,0 +1,193 @@ +""" +子图工具模块 - 将三个子图包装成 LangChain 工具 +Subgraph Tools Module - Wrap three subgraphs as LangChain tools +""" + +from langchain_core.tools import tool +from typing import Optional + + +# ============== 词典子图工具 ============== +@tool +def dictionary_tool(query: str, action: Optional[str] = None) -> str: + """ + 词典/翻译工具 - 查询单词、翻译文本、提取术语、获取每日一词 + + Args: + query: 用户查询内容(单词、句子、文本等) + action: 可选,指定操作类型("query" 查询单词,"translate" 翻译, + "extract" 提取术语,"daily" 每日一词,不指定则自动识别) + + Returns: + 格式化的结果文本 + """ + try: + from backend.app.agent_subgraphs.dictionary import ( + DictionaryState, + DictionaryAction, + parse_intent, + format_result + ) + from backend.app.agent_subgraphs.dictionary.nodes import ( + query_word, translate_text, extract_terms, get_daily_word + ) + + # 创建初始状态 + state = DictionaryState(user_query=query, user_id="default") + + # 处理 action + if action == "query": + state.action = DictionaryAction.QUERY + state.action_params = {"word": query} + state = query_word(state) + elif action == "translate": + state.action = DictionaryAction.TRANSLATE + state.source_text = query + state = translate_text(state) + elif action == "daily": + state.action = DictionaryAction.DAILY_WORD + state = get_daily_word(state) + elif action == "extract": + state.action = DictionaryAction.EXTRACT + state.action_params = {"text": query} + state = extract_terms(state) + else: + # 自动解析意图 + state = parse_intent(state) + if state.action == DictionaryAction.QUERY: + state = query_word(state) + elif state.action == DictionaryAction.TRANSLATE: + state = translate_text(state) + elif state.action == DictionaryAction.DAILY_WORD: + state = get_daily_word(state) + elif state.action == DictionaryAction.EXTRACT: + state = extract_terms(state) + + # 格式化结果 + state = format_result(state) + + return state.final_result or "操作完成" + + except Exception as e: + return f"词典工具执行出错:{str(e)}" + + +# ============== 资讯分析子图工具 ============== +@tool +def news_analysis_tool(query: str, action: Optional[str] = None) -> str: + """ + 资讯分析工具 - 查询新闻、分析URL、提取关键词、生成报告 + + Args: + query: 用户查询内容(关键词、URL、文本等) + action: 可选,指定操作类型("query" 查询新闻,"analyze" 分析URL, + "keywords" 提取关键词,"report" 生成报告,不指定则自动识别) + + Returns: + 格式化的结果文本 + """ + try: + from backend.app.agent_subgraphs.news_analysis import ( + NewsAnalysisState, + NewsAction, + parse_intent, + format_result + ) + from backend.app.agent_subgraphs.news_analysis.nodes import ( + query_news, analyze_url, extract_keywords, generate_report + ) + + # 创建初始状态 + state = NewsAnalysisState(user_query=query, user_id="default") + + # 处理 action + if action == "query": + state.action = NewsAction.QUERY_NEWS + state = query_news(state) + elif action == "analyze": + state.action = NewsAction.ANALYZE_URL + state.custom_urls = [query] + state = analyze_url(state) + elif action == "keywords": + state.action = NewsAction.EXTRACT_KEYWORDS + state = extract_keywords(state) + elif action == "report": + state.action = NewsAction.GENERATE_REPORT + state = generate_report(state) + else: + # 自动解析意图 + state = parse_intent(state) + if state.action == NewsAction.QUERY_NEWS: + state = query_news(state) + elif state.action == NewsAction.ANALYZE_URL: + state.custom_urls = [query] + state = analyze_url(state) + elif state.action == NewsAction.EXTRACT_KEYWORDS: + state = extract_keywords(state) + elif state.action == NewsAction.GENERATE_REPORT: + state = generate_report(state) + + # 格式化结果 + state = format_result(state) + + return state.final_result or "操作完成" + + except Exception as e: + return f"资讯分析工具执行出错:{str(e)}" + + +# ============== 通讯录子图工具 ============== +@tool +def contact_tool(query: str, action: Optional[str] = None) -> str: + """ + 通讯录工具 - 查询联系人、添加联系人、管理通讯录 + + Args: + query: 用户查询内容(姓名、电话、信息等) + action: 可选,指定操作类型(不指定则自动识别) + + Returns: + 格式化的结果文本 + """ + try: + from backend.app.agent_subgraphs.contact import ( + ContactState, + ContactAction, + parse_intent, + format_result + ) + from backend.app.agent_subgraphs.contact.nodes import ( + query_contact, add_contact, list_contacts + ) + + # 创建初始状态 + state = ContactState(user_query=query, user_id="default") + + # 自动解析意图 + state = parse_intent(state) + + # 根据解析结果执行操作 + if state.action == ContactAction.QUERY: + state = query_contact(state) + elif state.action == ContactAction.ADD: + state = add_contact(state) + elif state.action == ContactAction.LIST: + state = list_contacts(state) + + # 格式化结果 + state = format_result(state) + + return state.final_result or "操作完成" + + except Exception as e: + return f"通讯录工具执行出错:{str(e)}" + + +# ============== 工具列表 ============== +SUBGRAPH_TOOLS = [ + dictionary_tool, + news_analysis_tool, + contact_tool +] + +SUBGRAPH_TOOLS_BY_NAME = {tool.name: tool for tool in SUBGRAPH_TOOLS} diff --git a/backend/app/rag/__init__.py b/backend/app/rag/__init__.py index 5499d7d..ca1911f 100644 --- a/backend/app/rag/__init__.py +++ b/backend/app/rag/__init__.py @@ -38,7 +38,7 @@ from .retriever import ( create_base_retriever, create_hybrid_retriever, ) -from .reranker import LLaMaCPPReranker +from .rerank import DocumentReranker, create_document_reranker from .query_transform import MultiQueryGenerator from .fusion import reciprocal_rank_fusion from .pipeline import RAGPipeline @@ -51,7 +51,8 @@ __all__ = [ "create_hybrid_retriever", # 重排序器 - "LLaMaCPPReranker", + "DocumentReranker", + "create_document_reranker", # 查询改写生成器 "MultiQueryGenerator", diff --git a/backend/requirements.txt b/backend/requirements.txt index e648caa..55435f8 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -36,6 +36,7 @@ tenacity==9.1.4 rich==15.0.0 PyYAML==6.0.3 numpy>=1.26.2 +pyjwt==2.10.1 # Document Processing unstructured==0.22.21 diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c3ab2ea..a1f8681 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -87,6 +87,8 @@ services: environment: # Docker 内部网络使用服务名 'backend' 解析后端服务 - API_URL=http://backend:8079/chat + volumes: + - ../frontend/src:/app/src # 挂载源代码目录,修改立即生效 ports: - "8501:8501" networks: diff --git a/frontend/src/components/chat_area.py b/frontend/src/components/chat_area.py index 18defb3..620311f 100644 --- a/frontend/src/components/chat_area.py +++ b/frontend/src/components/chat_area.py @@ -393,32 +393,6 @@ def _render_review_confirmation(): except Exception as e: st.session_state.review_error = str(e) - # 测试按钮 - 用于演示审核功能 - with st.container(): - st.markdown("
", unsafe_allow_html=True) - col_test, col_info = st.columns([1, 3]) - with col_test: - if st.button("🔧 测试审核", key="test_review_chat"): - # 创建一个测试审核请求 - test_content = "这是一条待审核的测试内容。\n\n您可以选择:\n✅ 确定 - 批准此内容\n✏️ 修改 - 修改后批准\n❌ 拒绝 - 拒绝此内容" - review_id = api_client.request_review(thread_id, user_id, test_content) - if review_id: - st.session_state.pending_review = { - "review_id": review_id, - "content_to_review": test_content, - "created_at": "2024-01-01T12:00:00", - "user_id": user_id - } - st.success("✅ 已创建测试审核") - st.rerun() - else: - st.error("❌ 创建测试审核失败") - with col_info: - if st.session_state.get("review_error"): - st.warning(f"⚠️ {st.session_state.review_error}") - elif st.session_state.pending_review: - st.info("📋 有待审核内容") - # 显示审核确认界面 if st.session_state.pending_review: review = st.session_state.pending_review diff --git a/frontend/src/components/sidebar.py b/frontend/src/components/sidebar.py index 14ca9cd..7c570f0 100644 --- a/frontend/src/components/sidebar.py +++ b/frontend/src/components/sidebar.py @@ -21,6 +21,17 @@ def render_sidebar(): # 底部放用户部分 st.divider() _render_user_section() + + # 子图测试面板:放在最底部的角落里 + st.divider() + with st.expander("🔧 测试工具", expanded=False): + from components.subgraph_panel import _render_dictionary_panel, _render_news_panel, _render_contact_panel + st.caption("📚 词典子图") + _render_dictionary_panel() + st.caption("📰 资讯子图") + _render_news_panel() + st.caption("📇 通讯录子图") + _render_contact_panel() def _render_user_section(): """渲染用户登录区域""" diff --git a/frontend/src/frontend_main.py b/frontend/src/frontend_main.py index 9018fff..c3fcfce 100644 --- a/frontend/src/frontend_main.py +++ b/frontend/src/frontend_main.py @@ -19,14 +19,12 @@ if __name__ == '__main__': from components.sidebar import render_sidebar from components.chat_area import render_chat_area from components.info_panel import render_info_panel - from components.subgraph_panel import render_subgraph_panel else: from .config import config from .state import AppState from .components.sidebar import render_sidebar from .components.chat_area import render_chat_area from .components.info_panel import render_info_panel - from .components.subgraph_panel import render_subgraph_panel # ============================================================================= @@ -129,10 +127,6 @@ def main(): # 中间主区域:全宽的聊天区域 render_chat_area() - - # 底部:子图测试面板 - st.divider() - render_subgraph_panel() if __name__ == "__main__":