sales-assistant-py-new/app/chat_qa/deduplication_qa/d_qa.py

123 lines
4.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
数据清洗,把提取的问题进行聚类,然后只提取一个
"""
import json
from pathlib import Path
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from sklearn.cluster import DBSCAN
import numpy as np
import asyncio
from app.client.embedding_client_manager import EmbeddingClientManager, embedding_client
from app.conf.app_config import app_config
from app.llm import llm_low
qa_file = Path(r"E:\qa_result.jsonl") # E:\qa_output\qa_result.jsonl
qa_list = []
with open(qa_file, 'r', encoding='utf-8') as f:
for line_num, line in enumerate(f, 1):
line = line.strip()
if not line:
continue
try:
data = json.loads(line)
if isinstance(data, list):
qa_list.extend(data)
elif isinstance(data, dict):
qa_list.append(data)
else:
print(f"警告: 第{line_num}行格式异常,跳过")
except json.JSONDecodeError as e:
print(f"警告: 第{line_num}行JSON解析失败: {e},跳过")
print(f"成功读取 {len(qa_list)} 条QA数据")
# qa_list = [
# {"question": "教学软件是否为全流程制作,课前需要准备哪些设备?", "answer": "需要准备手绘板与手绘笔,手绘笔通常为配套。"},
# {"question": "教学软件是否为全流程制作,课前需要准备哪些设备", "answer": "需要准备手绘板与手绘笔,手绘笔通常为配套"},
# # ... 更多数据
# ]
# 2. 对question进行向量化异步调用远程模型
questions = [item["question"] for item in qa_list]
async def build()->list:
try:
embedding_client.init()
# embeddings_batch = await embedding_client.aembed_documents(questions)
# embeddings = [np.array(emb) for emb in embeddings_batch]
embedding_batch_size = 20
embedding_ed_all: list[list[float]] = []
for i in range(0, len(questions), embedding_batch_size):
embedding_part_texts = questions[i:i + embedding_batch_size]
embedding_eds = await embedding_client.aembed_documents(embedding_part_texts)
embedding_ed_all.extend(embedding_eds)
# 3. DBSCAN聚类基于余弦相似度eps≈0.15-0.25
# 相似度 = 1 - cosine_distanceeps=0.2 约等于相似度>0.8
clustering = DBSCAN(eps=0.3, min_samples=1, metric='cosine').fit(embedding_ed_all)
# 3. 组内LLM择优
results = []
unique_labels = set(clustering.labels_)
for label in unique_labels:
group = [qa_list[i] for i, l in enumerate(clustering.labels_) if l == label]
if len(group) == 1:
results.append(group[0])
else:
best = await select_best_qa(group)
results.append(best)
print(f"组内去重:从 {len(group)} 条中保留最优,舍弃 {len(group) - 1}")
except Exception as e:
print(f"处理失败: {str(e)}")
finally:
await embedding_client.close()
print("-----------------------------------------")
for item in results:
print(item)
return results
async def select_best_qa(qa_group: list) -> str:
"""
从一组相似QA中选出最完整、信息最丰富的一个
"""
# 构建对比文本
# qa_text = "\n\n".join([
# f"【选项{i + 1}】\n问题{item['question']}\n回答{item['answer']}"
# for i, item in enumerate(qa_group)
# ])
qa_text = "\n\n".join([
f"question: {item['question']},answer: {item['answer']}"
for i, item in enumerate(qa_group)
])
prompt = """你是一位数据清洗专家。以下是一组语义相似的QA请从中选出**最完整、信息最丰富、对用户最有价值**的一个。
评判标准(按优先级排序):
1. **回答完整性**:回答内容是否详尽,覆盖的信息点更多
2. **问题明确度**:问题表述是否清晰、无歧义
3. **信息密度**:单位字数内有效信息更多,无冗余废话
4. **实用性**:对用户实际解决问题的帮助程度
{qa_text}
请直接输出最完整结果。格式:{"question": "xxxxx", "answer": "bbbb"}
"""
prompt = PromptTemplate(template=prompt, input_variables=["qa_text"])
chain = prompt | llm_low | StrOutputParser()
response = await chain.ainvoke({"qa_text": qa_text})
print(response)
return response
# # 解析结果返回对应QA
# content = response.choices[0].message.content
# # 简单解析逻辑...
# import re
# match = re.search(r'选项(\d+)', content)
# best_idx = int(match.group(1)) - 1 if match else 0
#
# return qa_group[best_idx]
if __name__ == '__main__':
asyncio.run(build())