优化对话转工单功能,添加重试机制以提高稳定性,限制处理会话数量为前2000个,更新示例查询和文件路径,增强代码可读性和维护性。同时新增数据库客户端功能,支持批量处理会话数据并导出至Excel。
This commit is contained in:
@@ -14,6 +14,8 @@ import json
|
||||
from typing import List, Tuple, Dict, Any, Optional
|
||||
import re
|
||||
import jieba
|
||||
import time
|
||||
|
||||
from .PromptTemplates import (classification_prompt, query_rewrite_prompt,
|
||||
extract_nouns_prompt, classification_info,
|
||||
slot_filling_prompt)
|
||||
@@ -95,7 +97,9 @@ class IntentRecognizer:
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"加载后缀关键词失败: {e}") from e
|
||||
|
||||
def _classify_intent(self, query: str) -> Classification:
|
||||
def _classify_intent(self, query: str, conversation_context: str = "",
|
||||
chat_history: List[Dict[str, str]] = None,
|
||||
previous_slots: Dict[str, Any] = None) -> Classification:
|
||||
"""
|
||||
对用户输入进行意图分类
|
||||
|
||||
@@ -109,7 +113,9 @@ class IntentRecognizer:
|
||||
classification_parser = PydanticOutputParser(pydantic_object=Classification)
|
||||
formatted_prompt = classification_prompt.format(user_input=query,
|
||||
classification_info=classification_info,
|
||||
output_format=classification_parser.get_format_instructions())
|
||||
output_format=classification_parser.get_format_instructions(),
|
||||
conversation_context=conversation_context,
|
||||
chat_history=json.dumps(chat_history, ensure_ascii=False))
|
||||
|
||||
# 调用LLM
|
||||
response = self._llm.invoke(formatted_prompt, False)
|
||||
@@ -208,7 +214,7 @@ class IntentRecognizer:
|
||||
term_texts = ["名称:" + term.name + "|" + "同义词:" + ";".join(term.synonymous) for term in matched_terms]
|
||||
|
||||
# 使用重排序模型
|
||||
xinference_reranker = SiliconFlowReRankerModel()
|
||||
xinference_reranker = XinferenceReRankerModel()
|
||||
rerank_results = xinference_reranker.rerank(query_key, term_texts, top_k=top_k)
|
||||
|
||||
# 将matched_terms转换为列表以便按索引访问
|
||||
@@ -220,7 +226,7 @@ class IntentRecognizer:
|
||||
return reranked_terms
|
||||
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"SiliconFlowReRankerModel重排失败:{e}") from e
|
||||
raise RuntimeError(f"_rerank_matched_terms重排失败:{e}") from e
|
||||
|
||||
def _match_keywords(self, query: str, use_jieba: bool = False) -> Tuple[TermList, List[str]]:
|
||||
"""
|
||||
@@ -233,18 +239,23 @@ class IntentRecognizer:
|
||||
Returns:
|
||||
匹配到的关键词列表
|
||||
"""
|
||||
start_time = time.time()
|
||||
query_keys=[]
|
||||
# 步骤1: 使用LLM提取查询中的关键词
|
||||
try:
|
||||
llm_start_time = time.time()
|
||||
extracted_terms = self._extract_keywords_with_llm(query, use_jieba)
|
||||
for term in extracted_terms:
|
||||
query_keys.append(term.name)
|
||||
llm_end_time = time.time()
|
||||
llm_time = llm_end_time - llm_start_time
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"LLM关键词提取失败: {e}") from e
|
||||
|
||||
matched_terms = [] # 存储匹配到的Term对象
|
||||
# 步骤2: 使用向量检索找到相似的专业名词
|
||||
try:
|
||||
vector_start_time = time.time()
|
||||
# 对matched_terms中的每个关键字进行向量检索
|
||||
for current_key in query_keys:
|
||||
vector_results = self._noun_retriever.query(current_key, top_k=5, use_intersection=False)
|
||||
@@ -262,12 +273,20 @@ class IntentRecognizer:
|
||||
if len(current_key_terms) > 0:
|
||||
reranked_terms = self._rerank_matched_terms(current_key, current_key_terms)
|
||||
matched_terms.extend(reranked_terms)
|
||||
vector_end_time = time.time()
|
||||
vector_time = vector_end_time - vector_start_time
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"向量检索关键词时出错: {e}") from e
|
||||
|
||||
# 提取所有Term对象的名称并排序
|
||||
# 将set类型的matched_terms转换为TermList类型
|
||||
term_list = TermList(terms=list(matched_terms))
|
||||
end_time = time.time()
|
||||
total_time = end_time - start_time
|
||||
|
||||
# 输出整合的时间日志
|
||||
logging.info(f"关键词匹配耗时统计 - 总耗时: {total_time:.2f}秒, 问题关键词提取: {llm_time:.2f}秒, 向量检索+重排序: {vector_time:.2f}秒")
|
||||
|
||||
return term_list, query_keys
|
||||
|
||||
def _rewrite_query(self, query: str, keywords: TermList, query_keys:List[str], chat_history: List[Dict[str, str]] = None, context: str = "") -> QueryRewrite:
|
||||
@@ -282,6 +301,8 @@ class IntentRecognizer:
|
||||
Returns:
|
||||
改写结果
|
||||
"""
|
||||
|
||||
rewrite_start_time = time.time()
|
||||
# 准备问题改写提示
|
||||
# terms_dict = [term.model_dump(exclude={"description"}) for term in keywords.terms]
|
||||
terms_dict = [term.model_dump() for term in keywords.terms]
|
||||
@@ -295,7 +316,7 @@ class IntentRecognizer:
|
||||
keywords=keywords_str,
|
||||
chat_history=chat_history,
|
||||
context=context)
|
||||
|
||||
|
||||
# 调用LLM
|
||||
response = self._llm.invoke(formatted_prompt, False)
|
||||
|
||||
@@ -303,6 +324,9 @@ class IntentRecognizer:
|
||||
try:
|
||||
# 尝试直接解析JSON响应
|
||||
parsed_output = query_rewrite_parser.parse(response.content)
|
||||
rewrite_end_time = time.time()
|
||||
rewrite_time = rewrite_end_time - rewrite_start_time
|
||||
logging.info(f"问题改写耗时统计 - 总耗时: {rewrite_time:.2f}秒")
|
||||
return parsed_output
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"解析问题改写结果时出错: {e}") from e
|
||||
@@ -360,7 +384,10 @@ class IntentRecognizer:
|
||||
# suffix_terms.append(suffix_term)
|
||||
|
||||
# return Classification(vertical_classification="安装下载", sub_classification="查询"), TermList(terms=suffix_terms), QueryRewrite(rewrite=query), matched_suffixes
|
||||
|
||||
if chat_history is None:
|
||||
chat_history = []
|
||||
if previous_slots is None:
|
||||
previous_slots = {}
|
||||
# 步骤1: 匹配关键词
|
||||
keywords_terms, query_keys = self._match_keywords(query, use_jieba)
|
||||
|
||||
@@ -397,7 +424,9 @@ class IntentRecognizer:
|
||||
# }
|
||||
|
||||
|
||||
def _fill_slots(self, query: str, classification: Classification) -> Dict[str, Any]:
|
||||
def _fill_slots(self, query: str, classification: Classification, conversation_context: str = "",
|
||||
chat_history: List[Dict[str, str]] = None,
|
||||
previous_slots: Dict[str, Any] = None,) -> Dict[str, Any]:
|
||||
"""
|
||||
根据分类结果对问题进行槽位填充
|
||||
|
||||
@@ -415,7 +444,7 @@ class IntentRecognizer:
|
||||
raise RuntimeError("未找到匹配的槽位模型")
|
||||
|
||||
# 使用LLM进行槽位填充
|
||||
filled_slots = self._fill_slots_with_llm(query, classification, slot_model)
|
||||
filled_slots = self._fill_slots_with_llm(query, classification, slot_model, conversation_context, chat_history, previous_slots)
|
||||
|
||||
# 检查必填槽位是否都已填充
|
||||
is_complete, missing_slots = filled_slots.check_required_slots()
|
||||
@@ -467,7 +496,12 @@ class IntentRecognizer:
|
||||
|
||||
return None
|
||||
|
||||
def _fill_slots_with_llm(self, query: str, classification: Classification, slot_model_class: type) -> Any:
|
||||
def _fill_slots_with_llm(self, query: str,
|
||||
classification: Classification,
|
||||
slot_model_class: type,
|
||||
conversation_context: str = "",
|
||||
chat_history: List[Dict[str, str]] = None,
|
||||
previous_slots: Dict[str, Any] = None) -> Any:
|
||||
"""
|
||||
使用LLM进行槽位填充
|
||||
|
||||
@@ -486,7 +520,10 @@ class IntentRecognizer:
|
||||
query=query,
|
||||
vertical_classification=classification.vertical_classification,
|
||||
sub_classification=classification.sub_classification,
|
||||
output_format=slot_parser.get_format_instructions()
|
||||
output_format=slot_parser.get_format_instructions(),
|
||||
conversation_context=conversation_context,
|
||||
chat_history=json.dumps(chat_history,ensure_ascii=False),
|
||||
previous_slots=json.dumps(previous_slots,ensure_ascii=False),
|
||||
)
|
||||
|
||||
# 调用LLM
|
||||
@@ -537,9 +574,14 @@ class IntentRecognizer:
|
||||
output_format=parser.get_format_instructions(),
|
||||
classification_info=classification_info
|
||||
)
|
||||
|
||||
# 调用LLM
|
||||
llm_start_time = time.time()
|
||||
response = self._llm.invoke(formatted_prompt + output_example, False)
|
||||
llm_end_time = time.time()
|
||||
llm_time = llm_end_time - llm_start_time
|
||||
|
||||
|
||||
try:
|
||||
# 解析LLM响应为JSON
|
||||
result_json = parser.parse(response.content)
|
||||
@@ -552,8 +594,19 @@ class IntentRecognizer:
|
||||
if expected_slot_model is None:
|
||||
# 添加容错处理,应对LLM返回错误分类信息,一级分类跟二级分类错乱
|
||||
# 重新分类
|
||||
classification = self._classify_intent(user_input)
|
||||
fill_slots = self._fill_slots(user_input, classification)
|
||||
classify_start_time = time.time()
|
||||
classification = self._classify_intent(user_input, conversation_context, chat_history, previous_slots)
|
||||
classify_end_time = time.time()
|
||||
classify_time = classify_end_time - classify_start_time
|
||||
# logging.info(f"重新分类耗时: {classify_time:.2f}秒")
|
||||
|
||||
fill_start_time = time.time()
|
||||
fill_slots = self._fill_slots(user_input, classification, conversation_context, chat_history, previous_slots)
|
||||
fill_end_time = time.time()
|
||||
fill_time = fill_end_time - fill_start_time
|
||||
all_time=fill_end_time-llm_start_time
|
||||
logging.info(f"总耗时:{all_time:.2f}秒,首次槽位+分类:{llm_time:.2f}秒, 重新分类耗时: {classify_time:.2f}秒, 重新槽位填充耗时: {fill_time:.2f}秒")
|
||||
|
||||
result = {
|
||||
"classification": classification.model_dump(),
|
||||
"slot_filling": fill_slots
|
||||
@@ -562,13 +615,21 @@ class IntentRecognizer:
|
||||
return result
|
||||
elif expected_slot_model.__name__ != type(slot_filling).__name__:
|
||||
# 添加容错处理,应对LLM槽位与分类不匹配。重新填充槽位
|
||||
fill_start_time = time.time()
|
||||
slot_filling = self._fill_slots(user_input, classification)
|
||||
fill_end_time = time.time()
|
||||
fill_time = fill_end_time - fill_start_time
|
||||
all_time=fill_end_time-llm_start_time
|
||||
logging.info(f"总耗时:{all_time:.2f}秒,首次槽位+分类:{llm_time:.2f}秒, 重新槽位填充耗时: {fill_time:.2f}秒")
|
||||
|
||||
result = {
|
||||
"classification": classification.model_dump(),
|
||||
"slot_filling": slot_filling
|
||||
}
|
||||
logging.warning(f"重新填充槽点")
|
||||
return result
|
||||
|
||||
logging.info(f"意图识别+槽位LLM调用耗时: {llm_time:.2f}秒")
|
||||
|
||||
# 构建最终结果
|
||||
result = {
|
||||
|
||||
Reference in New Issue
Block a user