import json from langchain_core.messages import SystemMessage, HumanMessage from langchain_core.output_parsers import JsonOutputParser from langchain_core.prompts import PromptTemplate from langgraph.runtime import Runtime from app.chat_qa.context import QAContext from app.chat_qa.qa_state import QAState from app.dto.quality_score import QualityScore from app.llm import llm from app.core.log import logger #检查消息质量 async def quality_check(chat_history_text:str)->int: prompt = """ 你是一位培训质量评估专家。请分析以下客服/讲师与学员的对话记录,判断是否值得生成标准化的问答对(QA Pair)。 ## 对话记录 {chat_history_text} ## 说明 - [用户]:表示用户的聊天 - [销售]:表示客服/销售的回复/提问 ## 评估任务 请从以下5个维度打分(1-10分),并给出综合判断。 ## 核心评分原则(优先级从高到低) **第一原则:相关性优先** - 对话是否属于培训咨询场景?(课程价格、报名条件、学习内容、就业方向、学习周期等) - 如果是,基础分不低于40分 **第二原则:问答对应关系** - 用户是否提出了明确的问题? - 销售/客服是否给出了直接对应的答案? - 如果"问题明确 + 答案对应",基础分不低于60分 **第三原则:信息完整性(在已回答问题的前提下评估)** - 不要苛求答案必须覆盖用户所有潜在需求 - 只要答案直接回应了用户提出的问题,即视为完整 --- ## 五个评分维度(调整后权重) ### 【相关性】(权重30%,1-10分) - 是否属于培训咨询场景? - 课程价格、费用、优惠 → 10分 - 报名条件、适用人群、学历要求 → 10分 - 课程内容、学习周期、上课方式 → 10分 - 就业方向、证书、师资 → 10分 - 其他培训相关 → 7-9分 - 与培训无关(如闲聊、技术支持) → 0-3分 ### 【问答对应度】(权重25%,1-10分) - 用户问题是否明确?(3分) - 销售答案是否直接对应问题?(4分) - 答案是否存在明显错误或答非所问?(3分,无错误即满分) - 例如:[用户] XXXXXX [销售] XXXXX 表示销售回答了用户的问题 **评分细则:** - 用户问A,销售答A = 10分 - 用户问A,销售答A+B(额外信息) = 10分 - 用户问A,销售答了一半A,又问了B = 7分 - 用户问A,销售答非所问 = 0-3分 ### 【答案准确性】(权重20%,1-10分) - 答案事实是否正确?(5分) - 答案是否无歧义、不会误导用户?(5分) ### 【可复用价值】(权重15%,1-10分) - 该问题是否是学员常问的问题?(5分) - 该答案是否可以直接用于未来同类咨询?(5分) **重要:只要问题和答案对应,且属于培训场景,此项默认不低于6分** ### 【表达清晰度】(权重10%,1-10分) - 答案是否能让普通学员看懂?(5分) - 答案是否有明显语病或混乱?(5分,无明显问题即满分) --- ## 硬性否决规则(仅以下情况否决,其他一律不否决) 1. **非培训场景**:对话内容与培训咨询完全无关(如技术支持、物流查询、闲聊) 2. **答案错误**:销售给出了错误信息且可能误导学员 3. **答非所问**:用户问的是A,销售回答的是完全不相关的B **以下情况不再作为否决条件:** - 答案只有数字/简单事实 - 对话只有1轮 - 销售抛出了新问题未闭环 - 答案没有覆盖用户的所有潜在需求 - 答案缺少"下一步行动建议" --- ## 输出格式(必须严格遵循JSON格式) ```json {{ "relevance": 10, "qa_alignment": 10, "accuracy": 10, "reusability": 8, "clarity": 8 }} """ prompt = PromptTemplate(template=prompt, input_variables=["chat_history_text"]) output = JsonOutputParser() chain = prompt | llm | output result = {} try: result = await chain.ainvoke({"chat_history_text": chat_history_text}) except Exception as e: logger.error(f"QA质量检查prompt模板错误:{e}") logger.info(f"消息质量评估:{result}") score_obj = QualityScore( relevance=result.get("relevance", 0), qa_alignment=result.get("qa_alignment", 0), accuracy=result.get("accuracy", 0), reusability=result.get("reusability", 0), clarity=result.get("clarity", 0) ) total = score_obj.total return total """ 生成QA """ async def generate_qa(state: QAState, runtime: Runtime[QAContext]): retry_count = state.get("retry_count", 0) chat_history = state.get("chat_history", 0) # 生成QA的系统提示 system_prompt = """你是一个专业的QA提取专家。请分析以下聊天记录,提取或生成1-2个高质量的QA对。 规则: 1. 优先提取用户明确提出的问题及其对应的回答 2. 如果对话中没有明确的问题,根据讨论内容生成一个总结性问题 3. 答案必须基于聊天记录中的实际内容,不能编造 4. 如果某个问题在聊天记录中没有明确答案,则不生成QA 5. 最多生成2个QA对,优先选择最有价值的信息 例如: 聊天内容:[用户] 表明自己为准大学生,高考完,非在校大学生。询问线上课程价格是否为1800。[销售] 价格为11800 输出:{"question": "询问线上课程价格", "answer": "价格为11800"} JSON规则(按优先级): 1. 将中文引号 "" '' 替换为英文引号 "" '' 2. 删除所有注释(// 和 /* */) 3. 删除 markdown 代码块标记(```json ```) 4. 删除 JSON 前后多余的文本说明 5. 修复末尾多余的逗号(如 },] 或 },}) 6. 确保所有字符串用双引号包裹 7. 确保布尔值是小写的 true/false,null 是小写的 8. 如果键名没有引号,给键名加上双引号 只输出修复后的纯 JSON,不要任何解释、不要 markdown 标记、不要注释。 输出格式必须是JSON,不要任何解释、不要 markdown 标记、不要注释: { "qa_pairs": [ {"question": "...", "answer": "..."}, {"question": "...", "answer": "..."} ] } """ current_qas_list = [] logger.info(f"聊天内容:{chat_history}") total = await quality_check(chat_history) if total <= 75: return {"retry_count" : (retry_count+1)} # 调用LLM messages = [ SystemMessage(content=system_prompt), HumanMessage(content=f"聊天记录:\n{chat_history}") ] chain = llm result = await chain.ainvoke(messages) try: res = json.loads(result.content) # 提取生成的问答对 new_qa = res.get("qa_pairs", []) except Exception : new_qa = is_valid_json(result.content) # 解析失败赋值字符串,后续调用 .get() 崩溃 logger.info(f"生成QA内容:{new_qa}") current_qas_list.append(new_qa) return {"retry_count" : (retry_count+1), "current_qas": current_qas_list} # 创建一个校验json格式的函数 async def is_valid_json(json_str: str)->str: prompt = """你是一个 JSON 修复专家。请修复以下损坏的 JSON 文本,使其成为合法的标准 JSON。 修复规则(按优先级): 1. 将中文引号 "" '' 替换为英文引号 "" '' 2. 删除所有注释(// 和 /* */) 3. 删除 markdown 代码块标记(```json ```) 4. 删除 JSON 前后多余的文本说明 5. 修复末尾多余的逗号(如 },] 或 },}) 6. 确保所有字符串用双引号包裹 7. 确保布尔值是小写的 true/false,null 是小写的 8. 如果键名没有引号,给键名加上双引号 只输出修复后的纯 JSON,不要任何解释、不要 markdown 标记、不要注释。 待修复的 JSON: {input_json} """ prompt = PromptTemplate(template=prompt, input_variables=["chat_history_text"]) output = JsonOutputParser() chain = prompt | llm | output result = await chain.ainvoke({"input_json": json_str}) return result