From 6356f53ef738e001f0f9e49ffbc5187c28bba3c6 Mon Sep 17 00:00:00 2001 From: paituo <330435863@qq.com> Date: Thu, 10 Apr 2025 07:18:52 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E6=95=B4=E4=B8=AA=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=EF=BC=8C=E5=B9=B6=E5=A2=9E=E5=8A=A0=E6=9F=A5=E8=AF=A2?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E6=A8=A1=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agentic_rag.py | 243 ++++++++++++++++++++++++++++++------------------- app.py | 23 +++-- main.py | 28 +++++- query.py | 49 ++++++++++ 4 files changed, 238 insertions(+), 105 deletions(-) create mode 100644 query.py diff --git a/agentic_rag.py b/agentic_rag.py index 908e87d..d60cfd5 100644 --- a/agentic_rag.py +++ b/agentic_rag.py @@ -99,10 +99,10 @@ def get_reader(file_type: str): } return readers.get(file_type.lower(), None) -def get_model_by_provider(provider: str, model_name: str): +def get_model_by_provider(provider: str, model_name: str, temperature: float = None): """根据提供商获取对应的模型实例""" if provider == "openai": - model = OpenAIChat(id=model_name, base_url=model_baseUrl, api_key=api_key) + model = OpenAIChat(id=model_name, base_url=model_baseUrl, api_key=api_key, temperature=temperature) model.role_map = { "system": "system", "user": "user", @@ -150,7 +150,7 @@ def initialize_mingci_vector_db() -> LanceDb: return LanceDb( table_name="mingci", uri=os.getenv("MINGCI_VECTOR_DB_PATH", "tmp/mingcidb"), - search_type=SearchType.keyword, + search_type=SearchType.vector, embedder=OpenAIEmbedder(id=embedding_model, base_url=embedding_baseUrl, api_key=api_key) ) @@ -171,7 +171,7 @@ def initialize_mingci_knowledge_base() -> AgentKnowledge: """初始化并返回配置好的AgentKnowledge实例""" return AgentKnowledge( vector_db=initialize_mingci_vector_db(), - num_documents=3, # 检索3个最相关的文档 + num_documents=1, # 检索1个最相关的文档 chunking_strategy=DocumentChunking( chunk_size=500, overlap=50, @@ -191,40 +191,68 @@ def get_question_agent( """获取一个带有记忆功能的Agentic RAG代理。""" # 解析模型提供商和名称 provider, model_name = model_id.split(":") - model = get_model_by_provider(provider, model_name) + model = get_model_by_provider(provider, model_name, temperature=0) # 初始化记忆系统 - memory = initialize_memory(model) + #memory = initialize_memory(model) # 初始化知识库 - knowledge_base = initialize_knowledge_base() + knowledge_base = initialize_mingci_knowledge_base() description = """ - 你是一个智能助手,专门为电力造价软件[博微配网计价通D3软件]提供使用支持。你的任务是与理解用户的问题, - 将用户口语化、简化、省略化的电力造价的专业问题改写成对电力造价软件[博微配网计价通D3软件]的书面化、完整的电力专业问题的问句。 + # 电力造价软件操作问题转换工具(严格模式) + ## 核心指令 + 你只能输出与**电力造价软件操作**直接相关的专业问题改写结果,禁止任何解释或中间步骤。 + ## 角色定义 + 你是一个**严格的问题转换工具**,仅将用户问题转换为电力造价专业表述,**禁止任何额外输出**。 + ## 绝对禁止事项 + - ❌ 禁止强调软件 + - ❌ 禁止添加解释性文字(如“注:”“提示:”等) + - ❌ 禁止反问句(如“请问”“您想了解什么?”) + - ❌ 禁止分步过程说明(如“下一步需要...”) + - ❌ 禁止使用人称代词(你/我/您) + ## 必须执行的操作流程 + 1. **关键词提取** + - 立即识别问题中的**基础词语**(动词/名词/形容词) + - 格式示例:`"项目" "划分" "cost" "control"` + - **不输出**,直接进入下一步 + 2. **专业术语查询** + - 强制自动执行 `search_knowledge_base` 工具 + - 等待返回结果后继续 + 3. **问题改写** + - 严格使用知识库返回的**专业术语**(带引号,如`"工程量清单"`) + - 如果问题仅是一个**专业术语**,则用户是想知道该**专业术语**的操作入口 + - 仅输出最终改写结果 + ## 输出规范 + - **唯一允许格式**: + `[改写后的专业问题]` + - **错误示例**(将触发终止): + ❌ `"涉及...管理"` + ❌ `"请问您需要..."` + ❌ `"注:已提取关键词..."` + ## 违规处罚 + 若违反上述规则,立即终止并输出: + `[ERROR_STRICT_MODE: 检测到禁止内容]` + ## 正确案例 + ✅ `"在哪里查看'项目划分'?"` + ✅ `"如何编辑'项目划分'的'取费表'属性?"` + --- + **立即执行**:输入问题后,自动完成`提取→查询→改写`,**仅输出最终结果**。 """ instructions = """ - 1. 提取关键词 - 请提取问题中的关键词,需要中英文均有,可以适量补充不在问题中但相关的关键词。 + 1. 识别关键词 + 请识别问题中的关键词,需要中英文均有,可以适量补充不在问题中但相关的关键词。 关键词尽量切分为动词、名词、或形容词等单独的词,不要长词组(目的是更好的匹配检索到语义相关但表述不同的相关资料)。 关键词间以空格分割,比如: "关键词1" "关键词2" "keyword3" "keyword4" 2.搜索知识库中的电力造价专业专有名词 必须始终使用工具 search_knowledge_base 来搜索出所有和关键词相关的电力造价专业及软件业务对象、业务属性的专有名词。 3. 改写用户问题 - 请结合知识库中返回的电力造价专业专有名词改写用户的输入问题,将问题中的简写、缩写、口语转化成专业词汇后,电力造价专业描述方式输出完整清晰的问句。 - 4. 结构化问题 - 解析改写后的用户问题,判断用户咨询问题的类型(功能入口、操作步骤、错误处理等) - 然后将用户问题转化为JSON格式的标准查询 - 例如:"如何设置取费费率" 转化后: - { - "问题类型":"操作步骤", - "操作方法":"设置", - "业务对象":"取费", - "业务属性":"费率", - } - "问题类型"、"操作方法"、"业务对象"、"业务属性"如果在用户问题中识别不出来则为空。 - """ + 请结合知识库中返回的电力造价专业专有名词改写用户的输入问题,将问题中的同义词替换成专业词汇,然后将用户问题改写成用电力造价专业描述方式输出完整清晰的问句。 + 4. 注意事项 + 不需要反问,不需要补充背景,仅输出第3步中最终改写后的问句。 + 注意:如果用户问题中有知识库返回的电力造价专业专有名词,请务必将该专业名词用引号包起来,以保留该专业名词完整,不要被拆分成多个基础词语。 + """ # 创建代理 rag_agent: Agent = Agent( @@ -232,11 +260,11 @@ def get_question_agent( session_id=session_id, # 跟踪会话ID以实现持久对话 user_id=user_id, model=model, - storage=JsonStorage(dir_path=os.getenv("SESSION_STORAGE_PATH", "tmp/answer_agent_sessions_json")), # 持久化会话数据 - memory=memory, # 为代理添加记忆功能 + storage=JsonStorage(dir_path=os.getenv("SESSION_STORAGE_PATH", "tmp/question_agent_sessions_json")), # 持久化会话数据 + #memory=memory, # 为代理添加记忆功能 knowledge=knowledge_base, # 添加知识库 description=description, - instructions=instructions, + #instructions=instructions, search_knowledge=True, # 此设置赋予模型搜索知识库信息的工具 markdown=True, # 此设置告诉模型以markdown格式格式化消息 show_tool_calls=True, @@ -244,7 +272,7 @@ def get_question_agent( debug_mode=debug_mode, read_tool_call_history=True, num_history_responses=3, - save_response_to_file=str(tmp.joinpath("msg/answer_{message}_{run_id}.md")), + save_response_to_file=str(tmp.joinpath("msg/question_{message}_{run_id}.md")), ) return rag_agent @@ -259,13 +287,13 @@ def get_answer_agent( """获取一个带有记忆功能的Agentic RAG代理。""" # 解析模型提供商和名称 provider, model_name = model_id.split(":") - model = get_model_by_provider(provider, model_name) + model = get_model_by_provider(provider, model_name, 0.3) # 初始化记忆系统 - #memory = initialize_memory(model) + memory = initialize_memory(model) # 初始化知识库 - knowledge_base = initialize_mingci_knowledge_base() + knowledge_base = initialize_knowledge_base() description = """ 你是一个智能助手,专门为[博微配网计价通D3软件]提供使用支持。你的任务是帮助用户理解和使用这个复杂的配电网工程造价软件系统。 @@ -278,36 +306,39 @@ def get_answer_agent( instructions = """ 1. 理解用户问题 - 用户正在使用软件过程中遇到问题,向您请求帮助 - 用户所处环境如下: - {sofeware_work_context} - 只从用户问题识别中提到的业务对象(如"如何设置取费费率"→"取费表") - 只从用户问题识别业务对象的属性字段(如"如何设置取费费率"→"费率") - 只从用户问题识别用户想要执行的操作(如"如何设置取费费率"→"设置") - 判断问题类型(功能入口、操作步骤、错误处理等) + - 用户正在使用软件过程中遇到问题,向您请求帮助 + - 只从用户问题识别中提到的业务对象(如"如何设置取费费率"→"取费表") + - 只从用户问题识别业务对象的属性字段(如"如何设置取费费率"→"费率") + - 只从用户问题识别用户想要执行的操作(如"如何设置取费费率"→"设置") + - 判断问题类型(功能入口、操作步骤、错误处理等) + - **不输出**,直接进入下一步 2. 改写问题 - 将用户问题改写为包含以下要素的标准查询: - [问题类型] : [操作类型] + [业务对象] + [属性] - [属性]只有用户问题中明确包含才改写,否则为未知。 - [问题类型]、[操作类型]、[业务对象]为必须输入,如果缺少任一个都需追问用户补全才能进入下一步。 - 示例: - 原始问题:"如何设置取费费率?" - 改写后:"操作步骤 : 设置 - 取费 - 费率" + - 将用户问题改写为包含以下要素的标准查询: + - [问题类型] : [操作类型] + [业务对象] + [属性] + - **不输出** + - [属性]只有用户问题中明确包含才改写,否则为未知。 + - [问题类型]、[操作类型]、[业务对象]为必须输入,如果缺少任一个都需追问用户补全才能进入下一步。 + 示例: + 原始问题:"如何设置取费费率?" + 改写后:"操作步骤 : 设置 - 取费 - 费率" 3.搜索知识库 - 必须始终使用工具 search_knowledge_base 来搜索知识库 - 在回应前彻底分析所有返回的文档 - 如果返回多个文档,需连贯地综合信息 + - 必须始终使用工具 search_knowledge_base 来搜索知识库 + - 在回应前彻底分析所有返回的文档 + - 如果返回多个文档,需连贯地综合信息 + - **不输出**,直接进入下一步 4. 上下文管理: - 使用工具 get_chat_history 保持对话连续性 - 相关时引用之前的交互 - 记录用户偏好和之前的澄清 + - 使用工具 get_chat_history 保持对话连续性 + - 相关时引用之前的交互 + - 记录用户偏好和之前的澄清 5. 结果呈现要求 - 以 makedown 格式输出,注意换行和排版 - 避免使用'根据我的知识'或'取决于信息'等模糊表述 + - 用户所处环境如下: + {sofeware_work_context} + - 以 makedown 格式输出,注意换行和排版 + - 避免使用'根据我的知识'或'取决于信息'等模糊表述 7. 特殊情况处理 - 如果问题不明确,可以反问请求澄清 - 如果知识库搜索无结果,则直接明确回复不知道 - 对于错误提示,先解释含义再直接回复无法解决 + - 如果问题不明确,可以反问请求澄清 + - 如果知识库搜索无结果,则直接明确回复不知道 + - 对于错误提示,先解释含义再直接回复无法解决 """ # 创建代理 @@ -316,8 +347,8 @@ def get_answer_agent( session_id=session_id, # 跟踪会话ID以实现持久对话 user_id=user_id, model=model, - storage=JsonStorage(dir_path=os.getenv("SESSION_STORAGE_PATH", "tmp/question_agent_sessions_json")), # 持久化会话数据 - #memory=memory, # 为代理添加记忆功能 + storage=JsonStorage(dir_path=os.getenv("SESSION_STORAGE_PATH", "tmp/answer_agent_sessions_json")), # 持久化会话数据 + memory=memory, # 为代理添加记忆功能 knowledge=knowledge_base, # 添加知识库 description=description, instructions=instructions, @@ -434,6 +465,24 @@ def get_workflow( return qa_workflow +mingci_knowledge_base = initialize_mingci_knowledge_base() + +def search_mingci_knowledge(query: str) -> str: + """Use this function to search the mingci knowledge for information about a query. + Args: + query: The query to search for. + Returns: + str: A string containing the response from the mingci knowledge. + """ + global mingci_knowledge_base + from agno.document import Document + + docs: List[Document] = mingci_knowledge_base.search(query=query, num_documents=1) + if len(docs) == 0: + return "No documents found" + + return "\n\n".join([doc.content for doc in docs]) + def get_agentic_rag_agent( model_id: str = "openai:gpt-4o", user_id: Optional[str] = None, @@ -443,7 +492,7 @@ def get_agentic_rag_agent( """获取一个带有记忆功能的Agentic RAG代理。""" # 解析模型提供商和名称 provider, model_name = model_id.split(":") - model = get_model_by_provider(provider, model_name) + model = get_model_by_provider(provider, model_name, 0.3) # 初始化记忆系统 memory = initialize_memory(model) @@ -462,37 +511,45 @@ def get_agentic_rag_agent( """ instructions=""" - 1. 理解用户问题 - 用户正在使用软件过程中遇到问题,向您请求帮助 - 用户所处环境如下: + 1. **关键词提取** + - 立即识别问题中的**基础词语**(动词/名词/形容词) + - 格式示例:`"项目" "划分" "cost" "control"` + - **不输出**,直接进入下一步 + 2. **专业术语查询** + - 强制自动执行 `search_mingci_knowledge` 工具 + - 等待返回结果后继续 + 3. **问题改写** + - 严格使用知识库返回的**专业术语**和**同义词**,必须完全匹配才能替换(带引号,如`"工程量清单"`) + - 如果问题仅是一个**专业术语**,则用户是想知道该**专业术语**的操作入口 + - **以向用户确认的口吻输出改写后的完整问句** (如 您是想询问:`改写后的问句`) + 4. **问题结构化** + - 判断问题类型(功能入口、操作步骤、错误处理等) + - 将第三步问题改写的问句解析为包含以下要素的标准查询: + - [问题类型] : [操作类型] + [业务对象] + [业务属性] + - **不输出** + - [业务属性]只有用户问题中明确包含才改写,否则为未知。 + - [问题类型]、[操作类型]、[业务对象]为必须输入,如果缺少任一个都需追问用户补全才能进入下一步。 + 示例: + 原始问题:"如何设置取费费率?" + 改写后:"操作步骤 : 设置 - 取费 - 费率" + 5.**搜索知识库** + - 必须始终使用工具 search_knowledge_base 来搜索知识库 + - 在回应前彻底分析所有返回的文档 + - 如果返回多个文档,需连贯地综合信息 + - **不输出**,直接进入下一步 + 6. **上下文管理** + - 使用工具 get_chat_history 保持对话连续性 + - 相关时引用之前的交互 + - 记录用户偏好和之前的澄清 + 7. **结果呈现要求** + - 用户所处环境如下: {sofeware_work_context} - 只从用户问题识别中提到的业务对象(如"如何设置取费费率"→"取费表") - 只从用户问题识别业务对象的属性字段(如"如何设置取费费率"→"费率") - 只从用户问题识别用户想要执行的操作(如"如何设置取费费率"→"设置") - 判断问题类型(功能入口、操作步骤、错误处理等) - 2. 改写问题 - 将用户问题改写为包含以下要素的标准查询: - [问题类型] : [操作类型] + [业务对象] + [属性] - [属性]只有用户问题中明确包含才改写,否则为未知。 - [问题类型]、[操作类型]、[业务对象]为必须输入,如果缺少任一个都需追问用户补全才能进入下一步。 - 示例: - 原始问题:"如何设置取费费率?" - 改写后:"操作步骤 : 设置 - 取费 - 费率" - 3.搜索知识库 - 必须始终使用工具 search_knowledge_base 来搜索知识库 - 在回应前彻底分析所有返回的文档 - 如果返回多个文档,需连贯地综合信息 - 4. 上下文管理: - 使用工具 get_chat_history 保持对话连续性 - 相关时引用之前的交互 - 记录用户偏好和之前的澄清 - 5. 结果呈现要求 - 以 makedown 格式输出,注意换行和排版 - 避免使用'根据我的知识'或'取决于信息'等模糊表述 - 7. 特殊情况处理 - 如果问题不明确,可以反问请求澄清 - 如果知识库搜索无结果,则直接明确回复不知道 - 对于错误提示,先解释含义再直接回复无法解决 + - 以 makedown 格式输出,注意不要丢失换行和排版,不要丢失换行,不要丢失换行 + - 避免使用'根据我的知识'或'取决于信息'等模糊表述 + 8. 特殊情况处理 + - 如果问题不明确,可以反问请求澄清 + - 如果搜索知识库返回"No documents found"无结果,则直接明确回复当前知识库中不存在该知识 + - 对于错误提示,直接告知错误原因即可 """ # 创建代理 @@ -510,17 +567,17 @@ def get_agentic_rag_agent( add_context=True, search_knowledge=True, # 此设置赋予模型搜索知识库信息的工具 read_chat_history=True, # 此设置赋予模型获取聊天历史的工具 - #tools=[DuckDuckGoTools()], + tools=[search_mingci_knowledge], markdown=True, # 此设置告诉模型以markdown格式格式化消息 # add_chat_history_to_messages=True, show_tool_calls=True, - add_history_to_messages=True, # 将聊天历史添加到消息中 + #add_history_to_messages=True, # 将聊天历史添加到消息中 add_datetime_to_instructions=True, add_name_to_instructions=True, debug_mode=debug_mode, - read_tool_call_history=True, + #read_tool_call_history=True, num_history_responses=3, - save_response_to_file=str(tmp.joinpath("{message}.md")), + save_response_to_file=str(tmp.joinpath("msg/answer_{message}_{run_id}.md")), ) return agentic_rag_agent diff --git a/app.py b/app.py index 671168e..8c56613 100644 --- a/app.py +++ b/app.py @@ -1,3 +1,4 @@ +import re from typing import Optional from dotenv import load_dotenv @@ -32,21 +33,22 @@ def initialize_agent(model_id: str, session_id: Optional[str] = None): if session_id is None: session_id = st.session_state.get("agentic_rag_agent_session_id") + agent = st.session_state.get("agentic_rag_agent") if "agentic_rag_agent" in st.session_state else None + try: - if ( - not "agentic_rag_agent" in st.session_state - or st.session_state.get("agentic_rag_agent") is None - or st.session_state.get("current_model") != model_id + if (st.session_state.get("current_model") != model_id + or agent is None + or agent.session_id != session_id ): logger.info(f"---*--- Creating {model_id} Agent ---*---") - #agent = get_agentic_rag_agent( - # model_id=model_id, - # session_id=session_id, - #) - agent = get_workflow( + agent = get_agentic_rag_agent( model_id=model_id, session_id=session_id, ) + #agent = get_workflow( + # model_id=model_id, + # session_id=session_id, + #) st.session_state["agentic_rag_agent"] = agent st.session_state["current_model"] = model_id else: @@ -120,6 +122,7 @@ def main(): with st.chat_message(message["role"]): #if "tool_calls" in message and message["tool_calls"]: # display_tool_calls(st.empty(), message["tool_calls"]) + _content = re.sub(r'\s*{.*?}\s*', '', _content, flags=re.DOTALL) st.markdown(_content) with lastMsgContainer: @@ -136,7 +139,7 @@ def main(): response = "" try: # Run the agent and stream the response - run_response = agentic_rag_agent.run(message=question, stream=False) + run_response = agentic_rag_agent.run(message=question, stream=True) for _resp_chunk in run_response: # Display tool calls if available #if _resp_chunk.tools and len(_resp_chunk.tools) > 0: diff --git a/main.py b/main.py index 3e11d35..b030e4a 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +import json from pathlib import Path from typing import List @@ -19,7 +20,7 @@ def main(): # 初始化知识库 knowledge_base = initialize_mingci_knowledge_base() - LoadKnowledgeToDatabase(knowledge_base, mingci_knowledge_source_dir) + LoadMingCiKnowledgeToDatabase(knowledge_base, mingci_knowledge_source_dir) knowledge_source_dir = os.getenv("KNOWLEDGE_SOURCE_DIR") if knowledge_source_dir and os.path.exists(knowledge_source_dir): @@ -28,6 +29,30 @@ def main(): LoadKnowledgeToDatabase(knowledge_base, knowledge_source_dir) +def LoadMingCiKnowledgeToDatabase(knowledge_base, knowledge_source_dir): + logger.info(f"加载知识库: {knowledge_source_dir}") + for root, _, files in os.walk(knowledge_source_dir): + for file in files: + file_path = os.path.join(root, file) + file_ext = os.path.splitext(file)[1][1:] # 获取文件扩展名 + reader = get_reader(file_ext) + if reader: + try: + docs = [] + with open(file_path, 'r', encoding='utf-8') as f: + json_obj = json.load(f) + for item in json_obj: + file_contents = "{}\n 同义词: {}".format(item["name"], item["synonymous"]) + + docs.append(Document( + name=item["name"], + id=item["name"], + content=file_contents, + )) + knowledge_base.load_documents(docs, upsert=True) + except Exception as e: + logger.warning(f"无法加载文档 {file_path}: {str(e)}") + def LoadKnowledgeToDatabase(knowledge_base, knowledge_source_dir): logger.info(f"加载知识库: {knowledge_source_dir}") for root, _, files in os.walk(knowledge_source_dir): @@ -43,6 +68,5 @@ def LoadKnowledgeToDatabase(knowledge_base, knowledge_source_dir): except Exception as e: logger.warning(f"无法加载文档 {file_path}: {str(e)}") - if __name__ == "__main__": main() diff --git a/query.py b/query.py new file mode 100644 index 0000000..63a15df --- /dev/null +++ b/query.py @@ -0,0 +1,49 @@ +from pathlib import Path +from typing import List + +from agno.document import Document +from agno.utils.log import logger +from dotenv import load_dotenv + +from agentic_rag import initialize_knowledge_base, get_reader, initialize_mingci_knowledge_base, get_question_agent, \ + get_answer_agent, get_agentic_rag_agent +from app import initialize_agent +from ui import get_modul_option + +# 加载.env文件 +load_dotenv() +import os + +def main(): + print("Hello from agno-agentic-rag!") + + model_id = get_modul_option(0) + + query = "修改取费表名称" + + gent = get_agentic_rag_agent(model_id) + response = gent.run(query) + print(response) + # 初始化知识库 + #mingci_knowledge_base = initialize_mingci_knowledge_base() + + #mingci_result = mingci_knowledge_base.search(query, num_documents=10) + #print(mingci_result) + + question_agent = get_question_agent(model_id) + question_response = question_agent.run(query) + print(question_response) + + #knowledge_base = initialize_knowledge_base() + + #result = knowledge_base.search(query, num_documents=10) + #print(result) + + answer_agent = get_answer_agent(model_id) + answer_response = answer_agent.run(question_response.content) + print(answer_response) + print(answer_response) + + +if __name__ == "__main__": + main()