diff --git a/main.py b/main.py index 6b835e4..d98db7c 100644 --- a/main.py +++ b/main.py @@ -20,6 +20,13 @@ httpx_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别 # 可选:禁用传播(防止被根logger处理) httpx_logger.propagate = False + +# 获取logger并设置级别 +openai_logger = logging.getLogger("openai") +openai_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别 +# 可选:禁用传播(防止被根logger处理) +openai_logger.propagate = False + # 获取logger并设置级别 neo4j_logger = logging.getLogger("neo4j") neo4j_logger.setLevel(logging.WARNING) # 设置httpcore及其子模块的级别 @@ -69,7 +76,7 @@ def main(): prompt_manager, ) - pre_input_question = "查找一下名称中包含基础工程的项目划分" + pre_input_question = "查找名称中包含“工程”的项目划分项,并返回其人工费乘以1000的值。" asyncio.run(dialog_manager.run_async(pre_input=pre_input_question)) diff --git a/project_implementation.py b/project_implementation.py index 49e12fa..dcf8cfb 100644 --- a/project_implementation.py +++ b/project_implementation.py @@ -24,9 +24,9 @@ class ProjectTookiItNeo4j(ProjectTookiIt): user (str): 用户名 password (str): 密码 """ - uri = config.get("uri") - user = config.get("username") - password = config.get("password") + uri = config.neo4j_conf.get("uri") + user = config.neo4j_conf.get("username") + password = config.neo4j_conf.get("password") super().__init__() self.driver = GraphDatabase.driver(uri, auth=(user, password)) diff --git a/src/code_executor.py b/src/code_executor.py index 366d16f..0e9240b 100644 --- a/src/code_executor.py +++ b/src/code_executor.py @@ -39,7 +39,7 @@ class CodeExecutor: def execute_code(self, code_str): """封装代码执行逻辑""" - logger.debug(f"开始执行代码: {code_str[:50]}...") + logger.debug(f"开始执行代码: {code_str}") try: namespace = { "ProjectBuilder": ProjectBuilder, @@ -54,10 +54,10 @@ class CodeExecutor: exec(code_str, namespace) # 确保neo4j_find_function存在 - if "neo4j_find_function" not in namespace: - raise ValueError("代码中未定义neo4j_find_function函数") + if "project_get_calculate_function" not in namespace: + raise ValueError("代码中未定义project_get_calculate_function函数") - result_tuple = namespace["neo4j_find_function"]() + result_tuple = namespace["project_get_calculate_function"]() sys.stdout = old_stdout output = redirected_output.getvalue().strip() @@ -92,23 +92,29 @@ class CodeExecutor: def generate_and_run_code(self, user_request: str, context: str = "", bowei_api_docs: str = "") -> str: code = self.generate_code(user_request, context, bowei_api_docs) - pre_code = code.content logger.info("开始执行生成的代码") + pre_code = "" + error_msg = "" + prev_happend_error = False for attempt in range(self.max_retries): try: + if prev_happend_error: + logger.error(f"代码执行失败,尝试第 {attempt+1} 次修复。错误信息:{error_msg}") + code = self.fix_code(pre_code, error_msg) + + import re + pre_code = re.sub(r'^```python\s*|\s*```$', '', code.content, flags=re.MULTILINE) result = self.execute_code(pre_code) if result["status"] == "success": - logger.info(f"代码执行成功,返回结果长度:{len(result['data'])}") + logger.info(f"代码执行成功,结果: {result['data']}") return result["data"] else: error_msg = result.get("error", "未知错误") - logger.error(f"代码执行失败,尝试第 {attempt+1} 次修复。错误信息:{error_msg}") - pre_code = self.fix_code(pre_code, error_msg) + prev_happend_error = True except Exception as e: error_msg = str(e) - logger.error(f"代码执行异常,尝试第 {attempt+1} 次修复。异常信息:{error_msg}") - pre_code = self.fix_code(pre_code, error_msg) + prev_happend_error = True logger.error(f"代码执行失败,超过最大重试次数 {self.max_retries}") return f"代码执行失败,超过最大重试次数 {self.max_retries}。\n最后一次错误信息:\n{error_msg}" diff --git a/src/dialog_manager.py b/src/dialog_manager.py index e235396..ed1a2d9 100644 --- a/src/dialog_manager.py +++ b/src/dialog_manager.py @@ -51,6 +51,8 @@ class DialogManager: messages = prompt.to_messages() messages.append(HumanMessage(content=user_input)) + logger.debug(f"重写提示词:{messages}") + result = "" async for chunk in self.llm_client.stream(messages): print(chunk.content, end="", flush=True) @@ -78,14 +80,14 @@ class DialogManager: messages = prompt.to_messages() result = "" async for chunk in self.llm_client.stream(messages): - print(chunk.content, end="", flush=True) + #print(chunk.content, end="", flush=True) result += chunk.content - print() + #print() return [(result.strip(), "")] rewritten_list = [] for idx, doc in enumerate(docs, start=1): - print(f"\n第{idx}条相关文档改写结果(流式):") + logger.debug(f"\n第{idx}条相关文档改写结果(流式):") rewritten = await self.rewrite_question_with_query_result(user_input, doc.page_content) rewritten_list.append((rewritten, doc.page_content)) return rewritten_list diff --git a/src/prompt_manager.py b/src/prompt_manager.py index e629029..35c7117 100644 --- a/src/prompt_manager.py +++ b/src/prompt_manager.py @@ -23,7 +23,7 @@ class PromptManager: """ 你是一名电力造价业务专家,请基于以下工程文件业务结构,将用户自然语言问题改写成专业查询语句: -**工程文件业务结构**: +**工程文件业务结构(示例,请勿当真实数据)**: {business_structure} **改写规则**: @@ -55,10 +55,10 @@ class PromptManager: # 工作流程 1. 从用户问题中提取关键信息(节点路径、节点类型、节点名称等) 2. 根据"用户问题"和"上下文信息"选择最匹配的"工程数据访问库"中的方法 -3. 生成可直接执行的Python代码 +3. 只能生成一个可直接执行的Python函数代码 -# 代码模板(必须严格遵循) -def neo4j_find_function(): +# 输出格式(必须严格遵循) +def project_get_calculate_function(): project = ProjectBuilder.build() status, data, error, helper_info = project.[SELECTED_METHOD]([PARAMETERS]) return status, data, error, helper_info @@ -66,14 +66,7 @@ def neo4j_find_function(): # 执行规则 - 参数必须从用户问题或上下文信息中提取 - 必须确保生成的代码可以直接执行 -- 禁止修改代码模板结构 - 禁止添加任何注释或解释 - -# 输出格式 -def neo4j_find_function(): - project = ProjectBuilder.build() - status, data, error, helper_info = project.[SELECTED_METHOD]([PARAMETERS]) - return status, data, error, helper_info """ ) @@ -82,8 +75,14 @@ def neo4j_find_function(): 你是一个专业的Python工程师。我会给你一段错误python代码和错误信息,你需要帮我修复这段出错的代码 +已执行代码: +{code} + +代码执行报错信息: +{error} + 你的任务是: -1. 根据需要修改的代码{original_code}和代码的错误信息{error_info}来对代码和参数进行修改 +1. 根据"已执行代码"和"代码执行报错信息"来对“已执行代码”和函数调用参数进行修改,修复执行错误 2. 如果错误信息中是代码的逻辑出现错误,那么就需要对代码本身整体结构进行修改 3. 如果是代码中参数出现问题了,那么就需要结合错误信息中的帮助信息(helper_info)来对代码总的参数进行修改 4. 修复后的代码应该完整,可以直接执行,并且能够返回查询结果 @@ -102,29 +101,62 @@ def neo4j_find_function(): ) rewrite_prompt_template = ChatPromptTemplate.from_template( - """你是一个专业的工程业务助理,结合以下工程业务结构信息和相关知识: -{business_structure} + """ +你是一个AI助手,负责将模糊的用户问题改写成明确的查询。给定一个模糊的用户问题、一条从知识库获取的具体上下文知识以及相关业务背景信息,你需要执行以下任务: -相关知识内容: -{context} +1. **理解输入**: + - 原始问题:用户输入的模糊性问题(字符串) + - 上下文知识:从知识库查询获取的一条具体知识(字符串) + - 业务背景:当前问题所属的业务场景信息(字符串,如行业、产品类型、业务规则等) -请根据用户的问题,结合上述信息,理解并改写成一个针对工程数据的访问请求(简洁明了的描述)。 -请只输出改写后的访问请求文本,不要多余解释。""" - ) +2. **改写要求**: + - 基于**上下文知识**和**业务背景**,将原始问题改写成针对该知识的明确性问题 + - 严格保留原始问题的核心语义(意图和关键信息不变) + - 输出应是一个完整的自然语言问题,需同时满足: + ✅ 如果是一个查找语句则改写成一个获取语句 + ✅ 直接关联上下文知识的具体内容 + ✅ 符合业务背景的专业表述(如使用行业术语) + - 禁止添加原始问题未提及的额外假设 + +3. **输出格式**: + - 仅输出改写后的明确性问题(单个字符串) + +4. **示例**: +用户输入:查找名称中包含“工程”的项目划分项,并返回其人工费乘以1000的值。 +上下文知识: +改写输出:获取[安装/架空输电线路本体工程/基础工程/基础工程材料工地运输]项目划分项的人工费,并乘以1000的值 + +现在请处理以下输入: +- 上下文知识:"{context}" +- 业务背景(示例,请勿当真实数据):"{business_structure}" +""") cypher_conversion_prompt = ChatPromptTemplate.from_template( """ -你是一个Neo4j专家。请将用户的自然语言问题转换成一个有效的Cypher查询语句,查询知识图谱中相关信息。 -只返回Cypher语句,不要任何解释,最多返回5条。 +你是一名电力造价业务专家,负责将用户自然语言问题中需要访问的对象识别出来,并生成针对该对象的NEO4J知识图谱的Cypher查询语句,获取该对象的全部信息。知识图谱基于以下工程文件业务结构构建。 -业务结构信息: +业务结构(示例,请勿当真实数据): {business_structure} +改写规则: +识别目标对象:从业务结构中识别用户问题中需要获取的核心对象类型(如 ProjectDivisionItem、ProjectQuantity、Fee、FeeCollection 等)。对象类型必须精确映射到结构中的节点标签(例如,使用 ProjectDivisionItem 而非“项目划分项”)。 +提取查询条件:从用户输入中解析关键条件(如名称、量、类型、值、层级等),条件应基于对象属性(如 name、quantity、type)。 +如果用户指定层级(如“叶节点”),需在条件中体现(例如,添加 WHERE item.isLeaf = true)。 +忽略任何计算、转换或后处理要求(如“乘以1000”),只关注获取原始数据对象或属性。 +构建Cypher查询:生成一个Cypher查询语句,格式为: + +MATCH 子句:匹配目标对象节点,并应用条件,不带变量。 +WHERE 子句:包含提取的条件(使用变量或具体值)。 +RETURN 子句:必须返回对象(如 RETURN item),不能包含对象属性、函数(如乘法、SUM)。 +LIMIT 子句: 最多返回5条。 +使用业务术语在节点标签和属性中(例如,ProjectDivisionItem 而不是“项目”)。 +查询应简洁,只获取数据,不执行计算。 +输出格式:直接输出最终Cypher查询语句,不添加解释或额外文本。 + 用户问题: {user_input} Cypher查询语句: -""" - ) +""") return CodeExecutorPrompts( understand_prompt=understand_prompt,