更新多个模块,优化API密钥管理,增强意图识别和Excel数据验证功能,添加置信度评分,改进日志记录和错误处理机制,支持调试模式,提升代码可读性和灵活性。

This commit is contained in:
2025-06-23 16:17:06 +08:00
parent a1739cb703
commit eaf39e7bae
7 changed files with 373 additions and 168 deletions
+10 -7
View File
@@ -1,11 +1,7 @@
sk-kvgfuqeqvpmfsccykyoohheshclcrtvjlnewratvrjpkpbkc sk-kvgfuqeqvpmfsccykyoohheshclcrtvjlnewratvrjpkpbkc
sk-mxbapcczwjsyrictwgigxgcvdgptyfrlynrewqioegqwrggv
sk-dujjzxrknevesbagqgqmuffxsosjoueviubnmodoormlmlzt
sk-lsukfggzghmdhtfhqcbmlfqabbtapwpuxnvtwshqqqlaesie
sk-aulumxzhvaladchcwgmsxidtdsvzytbpvzqgfuvcxlwbwcgl sk-aulumxzhvaladchcwgmsxidtdsvzytbpvzqgfuvcxlwbwcgl
sk-otxxemniwhxkdvroszmmkitswwuykosnqoldrkzdoflqpgvw sk-otxxemniwhxkdvroszmmkitswwuykosnqoldrkzdoflqpgvw
sk-zlruqobfdbjebyyvkmehakpcvfgnlfbdlbfrepusazzckbnv sk-zlruqobfdbjebyyvkmehakpcvfgnlfbdlbfrepusazzckbnv
sk-zryimztrlkgvcaiolarhvbcewmhwruhqfcndbylonzlqvdox
sk-rczjqufgdisqplkrmvhaxmdgcboluvxympvzljlreuqeeviq sk-rczjqufgdisqplkrmvhaxmdgcboluvxympvzljlreuqeeviq
sk-xfnvcksdgwufsktvmhpqrwpgovsxxtaeehtxnaqjtxmubqzl sk-xfnvcksdgwufsktvmhpqrwpgovsxxtaeehtxnaqjtxmubqzl
sk-wwonvjnowbcxmoyoluynnkjwerghspzdulyidskunkordaft sk-wwonvjnowbcxmoyoluynnkjwerghspzdulyidskunkordaft
@@ -16,10 +12,8 @@ sk-lvtdgodiaurqyiwdxtdrgxifguychhccqlqkhqctscvqbfgi
sk-aedlbtlmqcttxwnvlfmxzaysamocamqxjceoyqjfgpcowybw sk-aedlbtlmqcttxwnvlfmxzaysamocamqxjceoyqjfgpcowybw
sk-fahdvndjblyjlizamvwcrxnilsgmbgbvwssxgquhkezgpqne sk-fahdvndjblyjlizamvwcrxnilsgmbgbvwssxgquhkezgpqne
sk-tzludgttzxvpvwayazdbppbauvathdtccafjrhojpemucgyi sk-tzludgttzxvpvwayazdbppbauvathdtccafjrhojpemucgyi
sk-hrbroidbfusidwnsmxenuzljxgdzzxiimlezygxplavnxjik
sk-ylgoiqxmtxeojdnonthxtweungyzldaqarvjxlqyztlvyrff sk-ylgoiqxmtxeojdnonthxtweungyzldaqarvjxlqyztlvyrff
sk-asuqbqwdhjcqnvtjlwufyrkrwkobnrbmukzarvcctsgjipdp sk-asuqbqwdhjcqnvtjlwufyrkrwkobnrbmukzarvcctsgjipdp
sk-dpgpymiydutoexgvkajwgahagnfmcqzafwulccudnzvleifz
sk-nbksjgcngsayoumnsdbkcpnqivnvxjenwpzuazzrkhnsgeoo sk-nbksjgcngsayoumnsdbkcpnqivnvxjenwpzuazzrkhnsgeoo
sk-jgybgyayxlwoxeijgrjcneqlyusleohgbliuwpsuhocrjsmk sk-jgybgyayxlwoxeijgrjcneqlyusleohgbliuwpsuhocrjsmk
sk-wzjsmwxcbbpcrqivqfzjwufqqjtlwejtncnvbpeicznkwiuh sk-wzjsmwxcbbpcrqivqfzjwufqqjtlwejtncnvbpeicznkwiuh
@@ -28,7 +22,6 @@ sk-fcsfmyivfuojsqsditvobfqprdpeunukycpcfnoxkraqevpx
sk-szyjgyxrcvyxpvzfwgmbxnflxngxvcplitcctsdvvrqjgftk sk-szyjgyxrcvyxpvzfwgmbxnflxngxvcplitcctsdvvrqjgftk
sk-jzbodthsnvjwbyrnynsxrudtqfnbdbrcxebjwjgajocnzqse sk-jzbodthsnvjwbyrnynsxrudtqfnbdbrcxebjwjgajocnzqse
sk-fxepossfzpmccibfwqpkluorzqlbtcaplepeugtfzfsctcbl sk-fxepossfzpmccibfwqpkluorzqlbtcaplepeugtfzfsctcbl
sk-ympnflocrkxjrbubsxqdjqwicuyavvvysctlpfhunkcrzxjx
sk-flhqvziknntednkcgjaxlyzzsrfzjhrzrmteqonajpbiinni sk-flhqvziknntednkcgjaxlyzzsrfzjhrzrmteqonajpbiinni
sk-xfregpbbquqbxpiobjzanydsjivrjrnbokzxcqtnhxhyghhe sk-xfregpbbquqbxpiobjzanydsjivrjrnbokzxcqtnhxhyghhe
sk-jrdzerhmvrtvzawkksowbgkggkubwfquplmrxbdhespqgtis sk-jrdzerhmvrtvzawkksowbgkggkubwfquplmrxbdhespqgtis
@@ -113,3 +106,13 @@ sk-yyqtojpqfrtkyvmhbjvnzhuujzgvitqpuxkytgxspwqscptz
sk-vcwenoaiegiwqhdvalxrvttwqmcrudttpfqhlvtdocsxvvob sk-vcwenoaiegiwqhdvalxrvttwqmcrudttpfqhlvtdocsxvvob
sk-npnyztbrdaqtbnldlvfelkfbfozykuwykkkfsrsisytusymj sk-npnyztbrdaqtbnldlvfelkfbfozykuwykkkfsrsisytusymj
sk-rjaadeqsgoskclkuqkkyjxdknngrdsjwlgucqutiskroprzk sk-rjaadeqsgoskclkuqkkyjxdknngrdsjwlgucqutiskroprzk
sk-uollmeyatyiwfzszvxkpyndmzfrbqjpyixewmrastbmaqbhy
sk-xdlsjytiwilvodadkjxvwdgulhhdytkqvfpyrcnllclgzqkb
sk-ffkltifkylutornjhwmnmfjsqsywrjibvujhjtjctzgnkvlp
sk-vmwocqqjqxnsvzmeyvqskahjaclifpmsbhywvnrvwygkfyuj
sk-gzwkmzxeeunaywrdrgirdatqhdtqdgvzqpesvprwbbjhcchn
sk-duchutcxmygrnkhzmmlykvtzwaylqtdxfbbuhvfvzuapazii
sk-nlddwexmjxqtgdvahwvlotnomrzcgskxeakxkxauicknzfkp
sk-lopwluipwvilwpwztvaxfebueeyilefwgncgpeprqvwazxom
sk-rgwrklpvhhrluokkbgavzukuhhpfhqzmozpjzoezfhkxyorc
sk-cdrpglnfmyeeqyhtvxvkpcpwscsbfouwkagjpphuksfzeipy
+40 -21
View File
@@ -8,13 +8,17 @@ import concurrent.futures
from functools import wraps from functools import wraps
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser from langchain.output_parsers import PydanticOutputParser
from rag2_0.tool.ModelTool import OpenAiLLM import sys
from dotenv import load_dotenv from dotenv import load_dotenv
import httpx import httpx
import traceback import traceback
import re import re
import logging import logging
# 将项目根目录添加到Python路径
sys.path.append(os.getcwd())
from rag2_0.tool.ModelTool import OpenAiLLM
load_dotenv() load_dotenv()
# 配置日志 # 配置日志
@@ -31,7 +35,7 @@ logger = logging.getLogger("dialogue_to_workorder")
# ================ 模型定义 ================ # ================ 模型定义 ================
class UserQuestionAndSolution(BaseModel): class UserQuestionAndSolution(BaseModel):
user_question: str = Field(description="用户的核心问题") user_question: str = Field(description="用户的核心问题")
solution: str = Field(description="坐席提供的解决方案") solution: str = Field(description="坐席提供的解决方案,解决方案如果存在多个步骤,使用中文分号隔开")
class UserQuestionAndSolutionList(BaseModel): class UserQuestionAndSolutionList(BaseModel):
user_question_list: list[UserQuestionAndSolution] = Field(description="客户问题列表") user_question_list: list[UserQuestionAndSolution] = Field(description="客户问题列表")
@@ -236,7 +240,7 @@ class DialogueToWorkorder:
```json ```json
{{ {{
"user_question": "技改软件打开报错", "user_question": "技改软件打开报错",
"solution": "1、告知报错原因 2、通过远程辅助解决" "solution": "1、告知报错原因2、通过远程辅助解决"
}} }}
``` ```
=======对话记录如下所示======= =======对话记录如下所示=======
@@ -426,14 +430,15 @@ class DialogueToWorkorder:
# 分析用户问题和解决方案 # 分析用户问题和解决方案
user_question_list = self.get_user_question_and_solution(conversation_rows) user_question_list = self.get_user_question_and_solution(conversation_rows)
# 获取第一个问题和解决方案,用于后续分析 user_question_str=""
if user_question_list and len(user_question_list) > 0: for user_question in user_question_list:
first_question = user_question_list[0] user_question_str = user_question_str + user_question.user_question.strip() + "\n"
user_question_str = first_question.user_question user_question_str = user_question_str.strip()
solution_str = first_question.solution
else: solution_str=""
user_question_str = "" for user_question in user_question_list:
solution_str = "" solution_str = solution_str + user_question.solution.strip() + "\n"
solution_str = solution_str.strip()
# 分析是否抱怨、是否投诉、抱怨级别 # 分析是否抱怨、是否投诉、抱怨级别
is_dissatisfaction, dissatisfaction_level, dissatisfaction_reasoning, is_complaint = ( is_dissatisfaction, dissatisfaction_level, dissatisfaction_reasoning, is_complaint = (
@@ -455,15 +460,9 @@ class DialogueToWorkorder:
# 创建工单列表 # 创建工单列表
workorder_list = [] workorder_list = []
for user_question in user_question_list:
user_question_str = user_question.user_question
solution_str = user_question.solution
# 创建新的工单字典,复制基本信息 # 更新工单字典
workorder_dict = base_workorder_dict.copy() base_workorder_dict.update({
# 更新工单字典
workorder_dict.update({
"产品线": product_line, "产品线": product_line,
"产品名称": product_name, "产品名称": product_name,
"模块名称": module_name, "模块名称": module_name,
@@ -474,9 +473,29 @@ class DialogueToWorkorder:
"是否投诉": "" if is_complaint else '', "是否投诉": "" if is_complaint else '',
"解决方案": (solution_str + '\n存在抱怨:' + dissatisfaction_reasoning) if is_dissatisfaction else solution_str "解决方案": (solution_str + '\n存在抱怨:' + dissatisfaction_reasoning) if is_dissatisfaction else solution_str
}) })
workorder_list.append(base_workorder_dict)
# for user_question in user_question_list:
# user_question_str = user_question.user_question
# solution_str = user_question.solution
# 将工单添加到列表中 # # 创建新的工单字典,复制基本信息
workorder_list.append(workorder_dict) # workorder_dict = base_workorder_dict.copy()
# # 更新工单字典
# workorder_dict.update({
# "产品线": product_line,
# "产品名称": product_name,
# "模块名称": module_name,
# "客户问题": user_question_str,
# "问题类型": problem_type,
# "是否抱怨": "是" if is_dissatisfaction else '否',
# "抱怨级别": dissatisfaction_level if is_dissatisfaction else '',
# "是否投诉": "是" if is_complaint else '否',
# "解决方案": (solution_str + '\n存在抱怨:' + dissatisfaction_reasoning) if is_dissatisfaction else solution_str
# })
# # 将工单添加到列表中
# workorder_list.append(workorder_dict)
return workorder_list return workorder_list
+3 -3
View File
@@ -8,8 +8,6 @@ Description: 意图识别和问题改写示例
import os import os
from dotenv import load_dotenv from dotenv import load_dotenv
from regex import F
from rag2_0.intent_recognition import IntentRecognizer
import pandas as pd import pandas as pd
import logging import logging
import json import json
@@ -19,6 +17,8 @@ import time
import sys import sys
import argparse import argparse
from typing import List, Dict from typing import List, Dict
sys.path.append(os.getcwd())
from rag2_0.intent_recognition import IntentRecognizer
# 加载环境变量 # 加载环境变量
load_dotenv() load_dotenv()
@@ -176,7 +176,7 @@ def save_results_to_excel(results, output_file, is_final=False):
logging.info(f"已保存{len(valid_results)}条结果至: {temp_output_file}") logging.info(f"已保存{len(valid_results)}条结果至: {temp_output_file}")
# 示例查询 # 示例查询
examples_query = """.BDD3是哪款软件编制的""" examples_query = """D3软件结算工程怎么解锁清单"""
conversation_context="" conversation_context=""
chat_history=[ chat_history=[
{ {
+313 -132
View File
@@ -6,6 +6,7 @@ Description: 使用LLM批量验证Excel数据中的问题分类、问题拆解
""" """
import os import os
import sys
import pandas as pd import pandas as pd
import json import json
import argparse import argparse
@@ -14,13 +15,25 @@ import concurrent.futures
from tqdm import tqdm from tqdm import tqdm
from dotenv import load_dotenv from dotenv import load_dotenv
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from rag2_0.intent_recognition.PromptTemplates import classification from pydantic import BaseModel, Field
from langchain.output_parsers import PydanticOutputParser
sys.path.append(os.getcwd())
from rag2_0.intent_recognition.PromptTemplates import classification_info
from rag2_0.intent_recognition.DataModels import *
from rag2_0.tool.ModelTool import OpenAiLLM from rag2_0.tool.ModelTool import OpenAiLLM
# 定义验证结果的Pydantic模型
class ValidationResult(BaseModel):
is_correct: bool = Field(description="验证是否通过")
confidence_score: float = Field(description="置信度得分")
reason: str = Field(default="", description="得出结论的原因")
class ExcelDataValidator: class ExcelDataValidator:
"""Excel数据验证类,用于批量验证Excel数据中的问题分类、问题拆解、检索关键词和问题改写""" """Excel数据验证类,用于批量验证Excel数据中的问题分类、问题拆解、检索关键词和问题改写"""
def __init__(self, input_file=None, output_file=None, workers=4, batch_size=10): def __init__(self, input_file=None, output_file=None, workers=4, batch_size=10, debug=False):
""" """
初始化验证器 初始化验证器
@@ -29,6 +42,7 @@ class ExcelDataValidator:
output_file: 输出结果Excel文件路径 output_file: 输出结果Excel文件路径
workers: 并行工作线程数 workers: 并行工作线程数
batch_size: 每批处理的行数 batch_size: 每批处理的行数
debug: 是否启用调试模式(串行处理)
""" """
# 加载环境变量 # 加载环境变量
load_dotenv() load_dotenv()
@@ -37,6 +51,7 @@ class ExcelDataValidator:
self.output_file = output_file self.output_file = output_file
self.workers = workers self.workers = workers
self.batch_size = batch_size self.batch_size = batch_size
self.debug = debug
self.df = None self.df = None
# 设置日志 # 设置日志
@@ -71,7 +86,7 @@ class ExcelDataValidator:
try: try:
df = pd.read_excel(file_path) df = pd.read_excel(file_path)
required_columns = ["", "问题拆解", "一级分类", "二级分类", "问题改写", "检索的关键词"] required_columns = ["", "问题分类", "问题改写", "槽点信息", "检索的内容"]
for col in required_columns: for col in required_columns:
if col not in df.columns: if col not in df.columns:
logging.error(f"缺少必要的列: {col}") logging.error(f"缺少必要的列: {col}")
@@ -94,109 +109,167 @@ class ExcelDataValidator:
sub_class: 二级分类 sub_class: 二级分类
Returns: Returns:
(bool, str): 是否正确,错误原因(如果有) (bool, str, float): 是否正确,错误原因(如果有),置信度
""" """
parser = self.create_validation_parser()
format_instructions = parser.get_format_instructions()
prompt = f""" prompt = f"""
背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用,也可能涉及电力造价专业知识。我对用户问题进行了分类,请评估以下问题分类是否正确。 背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用,也可能涉及电力造价专业知识。我对用户问题进行了分类,请评估以下问题分类是否正确。
我目前总共有以下分类: 我目前总共有以下分类:
{classification} {classification_info}
问题的分类情况如下: 问题的分类情况如下:
原始问题: {query} 原始问题: {query}
一级分类: {vertical_class} 一级分类: {vertical_class}
二级分类: {sub_class} 二级分类: {sub_class}
请从专业角度分析这个分类是否准确。只需返回"正确""错误:原因",不需要其他解释。""" 请从专业角度分析这个分类是否准确,并以JSON格式返回结果。请提供一个0到1之间的置信度得分,表示你对判断的确信程度。
{format_instructions}
"""
try: try:
response = llm.invoke(prompt) response = llm.invoke(prompt)
result = response.content.strip() result = parser.parse(response.content)
return result.is_correct, result.reason, result.confidence_score
if result.startswith("正确"):
return True, ""
else:
error_reason = result.replace("错误:", "").strip() if "错误:" in result else result
return False, error_reason
except Exception as e: except Exception as e:
logging.warning(f"验证问题分类时出错: {e}") logging.warning(f"验证问题分类时出错: {e}")
return False, f"验证过程出错: {str(e)}" return False, f"验证过程出错: {str(e)}", 0.0
def validate_query_keys(self, llm, query, query_keys): def _get_slot_model(self, classification: Classification) -> Optional[type]:
""" """
验证问题拆解是否正确 根据分类结果获取对应的槽位模型类,用于统一提示词处理
Args:
classification: 意图分类结果
Returns:
对应的槽位模型类
"""
# 软件问题
if classification.vertical_classification == "软件问题":
if classification.sub_classification == "软件功能":
return SoftwareFunctionSlots
elif classification.sub_classification == "故障排查":
return SoftwareTroubleShootingSlots
# 业务问题
elif classification.vertical_classification == "业务问题":
if classification.sub_classification == "专业咨询":
return ProfessionalConsultingSlots
elif classification.sub_classification == "数据问题":
return DataProblemSlots
# 安装下载注册
elif classification.vertical_classification == "安装下载注册":
if classification.sub_classification == "后缀名咨询":
return FileExtensionConsultingSlots
elif classification.sub_classification == "软件锁类":
return SoftwareLockSlots
elif classification.sub_classification == "安装下载类":
return InstallationDownloadSlots
elif classification.sub_classification == "问题排查类":
return ProblemDiagnosisSlots
# 其他
elif classification.vertical_classification == "其他":
return OtherSlots
return None
def validate_slot(self, llm, rewrite, slot_info, vertical_class, sub_class):
"""
验证槽位填充是否正确
Args: Args:
llm: LLM模型 llm: LLM模型
query: 原始问题 rewrite: 问题改写
query_keys: 问题拆解 slot_info: 槽位信息(JSON字符串)
Returns: Returns:
(bool, str): 是否正确,错误原因(如果有) (bool, str, float): 是否正确,错误原因(如果有),置信度
""" """
prompt = f""" # 解析槽位信息JSON
背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。我对用户问题进行了拆解,请评估以下问题拆解是否正确。
原始问题: {query}
问题拆解: {query_keys}
问题拆解应该准确提取原始问题中的关键词和信息。请分析这个拆解是否准确。只需返回"正确""错误:原因",不需要其他解释。"""
try: try:
response = llm.invoke(prompt) if isinstance(slot_info, str) and slot_info.strip():
result = response.content.strip() slots = json.loads(slot_info)
if result.startswith("正确"):
return True, ""
else: else:
error_reason = result.replace("错误:", "").strip() if "错误:" in result else result slots = slot_info
return False, error_reason
except Exception as e:
logging.warning(f"验证问题拆解时出错: {e}")
return False, f"验证过程出错: {str(e)}"
def validate_keywords(self, llm, query, query_keys, keywords_str):
"""
验证检索关键词是否准确
Args:
llm: LLM模型
query: 原始问题
query_keys: 问题拆解
keywords_str: 检索关键词(JSON字符串)
Returns:
(bool, str): 是否正确,错误原因(如果有)
"""
# 解析关键词JSON
try:
if isinstance(keywords_str, str) and keywords_str.strip():
keywords = json.loads(keywords_str)
else:
keywords = []
except: except:
keywords = keywords_str slots = slot_info
parser = self.create_validation_parser()
format_instructions = parser.get_format_instructions()
slot_info_prompt = self._get_slot_model(Classification(vertical_classification=vertical_class, sub_classification=sub_class)).model_json_schema()
slot_info_prompt = json.dumps(slot_info_prompt, ensure_ascii=False)
prompt = f""" prompt = f"""
背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。通过问题检索出了一些关键词,请评估这些关键词是否准确,是否与问题相关 背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。我从用户问题中提取了槽位信息,请评估这些槽位信息是否准确、完整。
原始问题: {query}
问题拆解: {query_keys}
检索关键词: {keywords}
检索关键词应该准确反映问题中需要检索的关键概念和术语。请分析这些关键词是否准确、完整。只需返回"正确""错误:原因",不需要其他解释。""" 问题改写: {rewrite}
槽位模板:{slot_info_prompt}
填充的槽位信息: {slots}
槽位信息应该准确提取问题中的关键实体和属性,如软件名称、功能名称、错误信息等。请分析这些槽位是否准确填充,并以JSON格式返回结果。请提供一个0到1之间的置信度得分,表示你对判断的确信程度。
{format_instructions}
"""
try: try:
response = llm.invoke(prompt) response = llm.invoke(prompt)
result = response.content.strip() result = parser.parse(response.content)
return result.is_correct, result.reason, result.confidence_score
if result.startswith("正确"):
return True, ""
else:
error_reason = result.replace("错误:", "").strip() if "错误:" in result else result
return False, error_reason
except Exception as e: except Exception as e:
logging.warning(f"验证检索关键词时出错: {e}") logging.warning(f"验证槽位填充时出错: {e}")
return False, f"验证过程出错: {str(e)}" return False, f"验证过程出错: {str(e)}", 0.0
def validate_retrieve_content(self, llm, rewrite, retrieve_content):
"""
验证检索内容是否正确
Args:
llm: LLM模型
rewrite: 问题改写
retrieve_content: 检索内容(可能是JSON字符串或文本)
Returns:
(bool, str, float): 是否正确,错误原因(如果有),置信度
"""
# 解析检索内容
try:
if isinstance(retrieve_content, str) and retrieve_content.strip():
if retrieve_content.startswith('{') or retrieve_content.startswith('['):
content = json.loads(retrieve_content)
else:
content = retrieve_content
else:
content = retrieve_content
except:
content = retrieve_content
parser = self.create_validation_parser()
format_instructions = parser.get_format_instructions()
prompt = f"""
背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。我针对用户问题检索了相关内容,请评估这些检索内容是否与问题相关、是否准确。
问题改写: {rewrite}
检索内容: {content}
检索内容应该与问题主题相关,能够提供有用的信息来回答问题。请分析检索内容是否相关、准确,并以JSON格式返回结果。请提供一个0到1之间的置信度得分,表示你对判断的确信程度。
{format_instructions}
"""
try:
response = llm.invoke(prompt)
result = parser.parse(response.content)
return result.is_correct, result.reason, result.confidence_score
except Exception as e:
logging.warning(f"验证检索内容时出错: {e}")
return False, f"验证过程出错: {str(e)}", 0.0
def validate_rewrite(self, llm, query, rewrite): def validate_rewrite(self, llm, query, rewrite):
""" """
@@ -208,28 +281,29 @@ class ExcelDataValidator:
rewrite: 问题改写 rewrite: 问题改写
Returns: Returns:
(bool, str): 是否正确,错误原因(如果有) (bool, str, float): 是否正确,错误原因(如果有),置信度
""" """
parser = self.create_validation_parser()
format_instructions = parser.get_format_instructions()
prompt = f""" prompt = f"""
背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。我对用户问题进行了改写,请评估以下问题改写是否正确。 背景:用户正在使用电力造价软件,提出的问题可能涉及电力造价软件的使用帮助,也可能涉及电力造价专业知识。我对用户问题进行了改写,请评估以下问题改写是否正确。
原始问题: {query} 原始问题: {query}
问题改写: {rewrite} 问题改写: {rewrite}
问题改写应该保持原问题的核心意图,同时使表达更加清晰、完整。请分析改写是否准确。只需返回"正确""错误:原因",不需要其他解释。""" 问题改写应该保持原问题的核心意图,同时使表达更加清晰、完整。请分析改写是否准确,并以JSON格式返回结果。请提供一个0到1之间的置信度得分,表示你对判断的确信程度。
{format_instructions}
"""
try: try:
response = llm.invoke(prompt) response = llm.invoke(prompt)
result = response.content.strip() result = parser.parse(response.content)
return result.is_correct, result.reason, result.confidence_score
if result.startswith("正确"):
return True, ""
else:
error_reason = result.replace("错误:", "").strip() if "错误:" in result else result
return False, error_reason
except Exception as e: except Exception as e:
logging.warning(f"验证问题改写时出错: {e}") logging.warning(f"验证问题改写时出错: {e}")
return False, f"验证过程出错: {str(e)}" return False, f"验证过程出错: {str(e)}", 0.0
def validate_row(self, llm, row_data): def validate_row(self, llm, row_data):
""" """
@@ -240,42 +314,110 @@ class ExcelDataValidator:
row_data: (index, row)元组 row_data: (index, row)元组
Returns: Returns:
(index, is_all_correct, error_phase, error_reason): 行索引,是否全部正确,错误环节,错误原因 (index, is_all_correct, error_phase, error_reason, confidence_score): 行索引,是否全部正确,错误环节,错误原因,置信度
""" """
index, row = row_data index, row = row_data
query = row[""] query = row[""]
query_keys = row["问题拆解"] query_class = row.get("问题分类", "")
vertical_class = row["一级分类"] rewrite = row.get("问题改写", "")
sub_class = row["二级分类"] slot_info = row.get("槽点信息", "")
rewrite = row["问题改写"] retrieve_content = row.get("检索的内容", "")
keywords_str = row["检索的关键词"]
if self.debug:
logging.info(f"开始验证行 {index}:")
logging.info(f" 问题: {query}")
logging.info(f" 问题分类: {query_class}")
logging.info(f" 问题改写: {rewrite}")
try: try:
# 1. 验证问题分类
is_correct, error_reason = self.validate_classification(llm, query, vertical_class, sub_class)
if not is_correct:
return index, False, "问题分类", error_reason
# 2. 验证问题拆解 confidence_score = 0.0
is_correct, error_reason = self.validate_query_keys(llm, query, query_keys) # 1. 验证问题改写
if not is_correct: if rewrite:
return index, False, "问题拆解", error_reason if self.debug:
logging.info(f" 验证问题改写...")
# 3. 验证检索关键词 result = self.validate_rewrite(llm, query, rewrite)
is_correct, error_reason = self.validate_keywords(llm, query, query_keys, keywords_str) if isinstance(result, tuple) and len(result) >= 3:
if not is_correct: is_correct, error_reason, rewrite_confidence = result[:3]
return index, False, "关键词检索", error_reason confidence_score = max(confidence_score, rewrite_confidence)
# 4. 验证问题改写 if self.debug:
is_correct, error_reason = self.validate_rewrite(llm, query, rewrite) logging.info(f" 问题改写验证结果: {'通过' if is_correct else '不通过'}, 置信度: {rewrite_confidence:.2f}")
if not is_correct: if not is_correct:
return index, False, "问题改写", error_reason logging.info(f" 错误原因: {error_reason}")
return index, True, "", "" if not is_correct:
return index, False, "问题改写", error_reason, rewrite_confidence
# 2. 验证问题分类
if query_class:
if self.debug:
logging.info(f" 验证问题分类...")
query_class_list = query_class.split(" - ")
if len(query_class_list) >= 2:
result = self.validate_classification(llm, rewrite, query_class_list[0], query_class_list[1])
if isinstance(result, tuple) and len(result) >= 3:
is_correct, error_reason, confidence_score = result[:3]
if self.debug:
logging.info(f" 问题分类验证结果: {'通过' if is_correct else '不通过'}, 置信度: {confidence_score:.2f}")
if not is_correct:
logging.info(f" 错误原因: {error_reason}")
if not is_correct:
return index, False, "问题分类", error_reason, confidence_score
# 3. 验证槽位填充
if slot_info:
if self.debug:
logging.info(f" 验证槽位填充...")
result = self.validate_slot(llm, rewrite, slot_info, query_class_list[0], query_class_list[1])
if isinstance(result, tuple) and len(result) >= 3:
is_correct, error_reason, slot_confidence = result[:3]
confidence_score = max(confidence_score, slot_confidence)
if self.debug:
logging.info(f" 槽位填充验证结果: {'通过' if is_correct else '不通过'}, 置信度: {slot_confidence:.2f}")
if not is_correct:
logging.info(f" 错误原因: {error_reason}")
if not is_correct:
return index, False, "槽位填充", error_reason, slot_confidence
# 4. 验证检索内容
if retrieve_content:
if self.debug:
logging.info(f" 验证检索内容...")
result = self.validate_retrieve_content(llm, rewrite, retrieve_content)
if isinstance(result, tuple) and len(result) >= 3:
is_correct, error_reason, retrieve_confidence = result[:3]
confidence_score = max(confidence_score, retrieve_confidence)
if self.debug:
logging.info(f" 检索内容验证结果: {'通过' if is_correct else '不通过'}, 置信度: {retrieve_confidence:.2f}")
if not is_correct:
logging.info(f" 错误原因: {error_reason}")
if not is_correct:
return index, False, "检索内容", error_reason, retrieve_confidence
if self.debug:
logging.info(f"{index} 验证完成: 通过, 总置信度: {confidence_score:.2f}")
return index, True, "", "", confidence_score
except Exception as e: except Exception as e:
error_msg = f"处理行 {index} 时发生错误: {str(e)}" error_msg = f"处理行 {index} 时发生错误: {str(e)}"
logging.error(error_msg) logging.error(error_msg)
return index, False, "处理错误", error_msg if self.debug:
import traceback
logging.error(traceback.format_exc())
return index, False, "处理错误", error_msg, 0.0
def process_batch(self, llm, batch_data): def process_batch(self, llm, batch_data):
"""处理一批数据""" """处理一批数据"""
@@ -288,7 +430,7 @@ class ExcelDataValidator:
"""创建多个LLM实例""" """创建多个LLM实例"""
api_key = os.getenv("OPENAI_API_KEY") api_key = os.getenv("OPENAI_API_KEY")
base_url = os.getenv("OPENAI_API_BASE") base_url = os.getenv("OPENAI_API_BASE")
model_name = os.getenv("LLM_MODEL_NAME", "gpt-3.5-turbo") model_name = "deepseek-ai/DeepSeek-R1"
llm_params = {"temperature": 0.7, "model": model_name} llm_params = {"temperature": 0.7, "model": model_name}
if api_key: if api_key:
@@ -298,7 +440,7 @@ class ExcelDataValidator:
return [OpenAiLLM(**llm_params) for _ in range(count)] return [OpenAiLLM(**llm_params) for _ in range(count)]
def validate(self, input_file=None, output_file=None, workers=None, batch_size=None): def validate(self, input_file=None, output_file=None, workers=None, batch_size=None, debug=None):
""" """
执行验证过程 执行验证过程
@@ -307,6 +449,7 @@ class ExcelDataValidator:
output_file: 输出结果Excel文件路径 output_file: 输出结果Excel文件路径
workers: 并行工作线程数 workers: 并行工作线程数
batch_size: 每批处理的行数 batch_size: 每批处理的行数
debug: 是否启用调试模式(串行处理)
Returns: Returns:
验证后的DataFrame 验证后的DataFrame
@@ -315,6 +458,7 @@ class ExcelDataValidator:
output_file = output_file or self.output_file output_file = output_file or self.output_file
workers = workers or self.workers workers = workers or self.workers
batch_size = batch_size or self.batch_size batch_size = batch_size or self.batch_size
debug = debug if debug is not None else self.debug
# 读取数据 # 读取数据
df = self.load_data_from_excel(input_file) df = self.load_data_from_excel(input_file)
@@ -325,36 +469,64 @@ class ExcelDataValidator:
df["验证结果"] = "" df["验证结果"] = ""
df["错误环节"] = "" df["错误环节"] = ""
df["错误原因"] = "" df["错误原因"] = ""
df["置信度"] = 0.0
# 准备数据批次 # 准备数据
all_rows = list(df.iterrows()) all_rows = list(df.iterrows())
batches = [all_rows[i:i+batch_size] for i in range(0, len(all_rows), batch_size)]
# 创建多个LLM实例 # 创建LLM实例
llm_instances = self.create_llm_instances(min(workers, len(batches))) llm = self.create_llm_instances(1)[0]
# 使用线程池处理数据 # 根据模式选择处理方式
all_results = [] all_results = []
with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor: if debug:
# 为每个批次分配一个LLM实例 # 调试模式:串行处理
future_to_batch = { logging.info("启用调试模式,使用串行处理...")
executor.submit(self.process_batch, llm_instances[i % len(llm_instances)], batch): for i, row_data in enumerate(all_rows):
i for i, batch in enumerate(batches) logging.info(f"处理第 {i+1}/{len(all_rows)} 行...")
} result = self.validate_row(llm, row_data)
all_results.append(result)
# 实时更新DataFrame
index, is_correct, error_phase, error_reason, confidence_score = result
df.at[index, "验证结果"] = "通过" if is_correct else "不通过"
df.at[index, "错误环节"] = error_phase
df.at[index, "错误原因"] = error_reason
df.at[index, "置信度"] = confidence_score
# 输出当前结果
logging.info(f"{index} 验证结果: {'通过' if is_correct else '不通过'}, 错误环节: {error_phase}, 错误原因: {error_reason}, 置信度: {confidence_score:.2f}")
else:
# 正常模式:并行处理
batches = [all_rows[i:i+batch_size] for i in range(0, len(all_rows), batch_size)]
llm_instances = self.create_llm_instances(min(workers, len(batches)))
# 使用tqdm显示进度条 with concurrent.futures.ThreadPoolExecutor(max_workers=workers) as executor:
for future in tqdm(concurrent.futures.as_completed(future_to_batch), total=len(batches), desc="批次处理进度"): # 为每个批次分配一个LLM实例
batch_results = future.result() future_to_batch = {
all_results.extend(batch_results) executor.submit(self.process_batch, llm_instances[i % len(llm_instances)], batch):
i for i, batch in enumerate(batches)
}
# 使用tqdm显示进度条
for future in tqdm(concurrent.futures.as_completed(future_to_batch), total=len(batches), desc="批次处理进度"):
batch_results = future.result()
all_results.extend(batch_results)
# 按行索引排序结果,确保与原始数据顺序一致 # 按行索引排序结果,确保与原始数据顺序一致
all_results.sort(key=lambda x: x[0]) all_results.sort(key=lambda x: x[0])
# 将结果填充到DataFrame # 将结果填充到DataFrame
for index, is_correct, error_phase, error_reason in all_results: for result in all_results:
df.at[index, "验证结果"] = "通过" if is_correct else "不通过" if len(result) >= 5:
df.at[index, "错误环节"] = error_phase index, is_correct, error_phase, error_reason, confidence_score = result
df.at[index, "错误原因"] = error_reason df.at[index, "验证结果"] = "通过" if is_correct else "不通过"
df.at[index, "错误环节"] = error_phase
df.at[index, "错误原因"] = error_reason
df.at[index, "置信度"] = confidence_score
else:
index, is_correct, error_phase, error_reason = result
df.at[index, "验证结果"] = "通过" if is_correct else "不通过"
df.at[index, "错误环节"] = error_phase
df.at[index, "错误原因"] = error_reason
# 保存结果 # 保存结果
if output_file is None: if output_file is None:
@@ -381,25 +553,34 @@ class ExcelDataValidator:
for phase, count in error_stats.items(): for phase, count in error_stats.items():
logging.info(f"- {phase}: {count}") logging.info(f"- {phase}: {count}")
def create_validation_parser(self):
"""创建验证结果解析器"""
return PydanticOutputParser(pydantic_object=ValidationResult)
def main(): def main():
"""主函数""" """主函数"""
# 解析命令行参数 # 解析命令行参数
input_excel = os.path.join(os.path.dirname(__file__), "..", "..", "data", "excel", "问题分类重写结果") input_excel = os.path.join(os.path.dirname(__file__), "..", "..", "data", "excel", "1500条点踩软件问题测试_检索结果.xlsx")
output_excel = os.path.join(os.path.dirname(__file__), "..", "..", "data", "excel", "自动验证_问题分类重写结果.xlsx") output_excel = os.path.join(os.path.dirname(__file__), "..", "..", "data", "excel", "自动验证_问题分类重写结果.xlsx")
parser = argparse.ArgumentParser(description="验证Excel数据中的问题分类、问题拆解、检索关键词和问题改写") parser = argparse.ArgumentParser(description="验证Excel数据中的问题分类、问题拆解、检索关键词和问题改写")
parser.add_argument("--input", "-i", type=str, required=True, help="输入Excel文件路径", default=input_excel) parser.add_argument("--input", "-i", type=str, help="输入Excel文件路径", default=input_excel)
parser.add_argument("--output", "-o", type=str, help="输出结果Excel文件路径", default=output_excel) parser.add_argument("--output", "-o", type=str, help="输出结果Excel文件路径", default=output_excel)
parser.add_argument("--workers", "-w", type=int, default=2, help="并行工作线程数") parser.add_argument("--workers", "-w", type=int, default=20, help="并行工作线程数")
parser.add_argument("--batch-size", "-b", type=int, default=5, help="每批处理的行数") parser.add_argument("--batch-size", "-b", type=int, default=5, help="每批处理的行数")
parser.add_argument("--debug", "-d", action="store_true", help="启用调试模式(串行处理)")
args = parser.parse_args() args = parser.parse_args()
is_debug = hasattr(sys, 'gettrace') and sys.gettrace() is not None
# 创建验证器实例并执行验证 # 创建验证器实例并执行验证
validator = ExcelDataValidator( validator = ExcelDataValidator(
input_file=args.input, input_file=args.input,
output_file=args.output, output_file=args.output,
workers=args.workers, workers=args.workers,
batch_size=args.batch_size batch_size=args.batch_size,
debug=is_debug
) )
validator.validate() validator.validate()
+2
View File
@@ -9,6 +9,8 @@ Description: 专业名词向量化和保存的示例程序
import os import os
import json import json
from dotenv import load_dotenv from dotenv import load_dotenv
import sys
sys.path.append(os.getcwd())
from rag2_0.intent_recognition import ProfessionalNounVectorizer from rag2_0.intent_recognition import ProfessionalNounVectorizer
import logging import logging
+1 -1
View File
@@ -149,7 +149,7 @@ class SlotBase(BaseModel):
class SoftwareFunctionSlots(SlotBase): class SoftwareFunctionSlots(SlotBase):
software_name: str = Field(default="", description="软件名称") software_name: str = Field(default="", description="软件名称")
function_name: str = Field(default="", description="具体功能名称") function_name: str = Field(default="", description="具体功能名称")
operation: str = Field(default="", description="用户操作意图(如何使用功能、功能入口、功能使用场景)") operation: str = Field(default="", description="用户操作意图(如何使用功能、功能入口、功能使用场景、是否支持该功能)")
project_type: Optional[str] = Field(default="单工程", description="工程类型(单工程、多工程、批次工程), 未明确提及则默认下是(单工程)") project_type: Optional[str] = Field(default="单工程", description="工程类型(单工程、多工程、批次工程), 未明确提及则默认下是(单工程)")
software_version: Optional[str] = Field(default="", description="软件版本") software_version: Optional[str] = Field(default="", description="软件版本")
operation_steps: Optional[str] = Field(default="", description="操作步骤描述") operation_steps: Optional[str] = Field(default="", description="操作步骤描述")
+1 -1
View File
@@ -271,7 +271,7 @@ if __name__ == "__main__":
instance = APIKeyManager.get_instance() instance = APIKeyManager.get_instance()
stats = instance.get_usage_stats() stats = instance.get_usage_stats()
all_balance=0.0 all_balance=0.0
buy_balance=16 * 10 * 14 # 购买16次,一次10条api_key,每个api_key有14元 buy_balance=17 * 10 * 14 # 购买16次,一次10条api_key,每个api_key有14元
invalid_api_keys = [] invalid_api_keys = []
for key, data in stats.items(): for key, data in stats.items():
usage_stats = APIKeyManager.get_key_usage_stats(key) usage_stats = APIKeyManager.get_key_usage_stats(key)