diff --git a/rag/intent.py b/rag/intent.py index a10c44c..2637a49 100644 --- a/rag/intent.py +++ b/rag/intent.py @@ -2,6 +2,7 @@ from __future__ import annotations import logging +import re from enum import StrEnum @@ -12,32 +13,51 @@ class Intent(StrEnum): GUIDE_PURCHASE = "guide_purchase" WANT_ADVISOR = "want_advisor" NL2SQL_REQUEST = "nl2sql_request" - COMPLAIN = "complain" KNOWLEDGE_QA = "knowledge_qa" + COMPANY_INFO = "company_info" + CHITCHAT = "chitchat" + OFF_TOPIC = "off_topic" NO_MATCH = "no_match" INTENT_VALUES = frozenset(item.value for item in Intent) +# 按长度降序匹配,避免短标签误命中长标签的一部分 +_INTENT_PATTERN = re.compile( + "|".join(re.escape(value) for value in sorted(INTENT_VALUES, key=len, reverse=True)) +) + +INTENT_SYSTEM_PROMPT = ( + "你是华夏科技(一家基金代销金融机构)智能客服的意图分类器。" + "请根据用户输入,从以下选项中选择最匹配的意图,并仅输出对应的英文标签(不输出任何其他内容):\n" + "- guide_purchase: 用户询问如何购买基金、开户、注册等引导类问题\n" + "- want_advisor: 用户希望获得个性化基金推荐或投资顾问服务\n" + "- knowledge_qa: 用户询问基金相关的知识性问题,如净值、费率、风险、申赎规则等\n" + "- company_info: 用户询问华夏科技公司本身的信息,如公司全称、成立时间、牌照、总部地址、" + "客服电话、服务时间、官网、投诉渠道等\n" + "- nl2sql_request: 用户要求查询具体数据或账户信息\n" + "- chitchat: 普通寒暄,如问候、致谢、告别、询问你是谁/你能做什么、在吗等一两句话的闲聊\n" + "- off_topic: 用户要求你实质性地处理与金融、基金、公司业务无关的事情," + "如写代码、讲笑话、写作文、问天气、聊政治、情感咨询、做数学题等\n" + "- no_match: 无法归入以上任何一类\n" + "注意:寒暄性质的一两句话归为 chitchat;一旦用户提出金融之外的实质性请求,归为 off_topic。\n" + "仅输出一个小写英文标签,不要输出解释、标点或换行。" +) + + +def parse_intent(raw: str | None) -> Intent: + """从模型原始输出中提取第一个合法标签,提取不到则返回 NO_MATCH。""" + if not raw: + return Intent.NO_MATCH + match = _INTENT_PATTERN.search(raw.lower()) + return Intent(match.group(0)) if match else Intent.NO_MATCH + async def intent_recognize(query: str, *, llm_client) -> Intent: if not query or not query.strip(): return Intent.NO_MATCH messages = [ - { - "role": "system", - "content": ( - "你是一个意图分类器。请根据用户输入,从以下选项中选择最匹配的意图," - "并仅输出对应的英文标签(不输出任何其他内容):\n" - "- guide_purchase: 用户询问如何购买基金、开户、注册等引导类问题\n" - "- want_advisor: 用户希望获得个性化基金推荐或投资顾问服务\n" - "- knowledge_qa: 用户询问基金相关的知识性问题,如净值、费率、风险等\n" - "- nl2sql_request: 用户要求查询具体数据或账户信息\n" - "- complain: 用户表达不满或投诉\n" - "- no_match: 以上都不匹配\n" - "仅输出一个小写英文标签,不要输出解释、标点或换行。" - ), - }, + {"role": "system", "content": INTENT_SYSTEM_PROMPT}, {"role": "user", "content": query}, ] try: @@ -45,5 +65,4 @@ async def intent_recognize(query: str, *, llm_client) -> Intent: except Exception: logger.exception("intent recognition failed") return Intent.NO_MATCH - value = raw.strip().strip('`').lower() - return Intent(value) if value in INTENT_VALUES else Intent.NO_MATCH \ No newline at end of file + return parse_intent(raw) diff --git a/service/customer_agent/chat.py b/service/customer_agent/chat.py index bb5d289..0138830 100644 --- a/service/customer_agent/chat.py +++ b/service/customer_agent/chat.py @@ -54,19 +54,28 @@ class AnonymousCustomerAgent: intent = await _maybe_await(self.intent_recognize(query)) sources = [] - if intent is Intent.GUIDE_PURCHASE: + if intent == Intent.GUIDE_PURCHASE: answer = await _config( self.config_getter, "agent.customer.template.guide_purchase", "请前往开户页面办理。", ) - elif intent is Intent.WANT_ADVISOR: + elif intent == Intent.WANT_ADVISOR: answer = await _config( self.config_getter, "agent.customer.template.guide_advisor", "如需基金推荐,请联系投资顾问。", ) - elif intent is Intent.KNOWLEDGE_QA: + elif intent == Intent.OFF_TOPIC: + answer = await _config( + self.config_getter, + "agent.customer.template.off_topic", + "我是华夏科技的智能客服,只能解答基金与公司业务相关的问题," + "您可以问我基金知识、开户流程或公司信息~", + ) + elif intent == Intent.CHITCHAT: + answer = await self._chitchat(session_id) + elif intent in (Intent.KNOWLEDGE_QA, Intent.COMPANY_INFO): try: sources = await _maybe_await(self.rag_retrieve(query, None)) except Exception: @@ -112,6 +121,31 @@ class AnonymousCustomerAgent: "trace_id": trace_id, } + async def _chitchat(self, session_id: str) -> str: + """带对话历史调用 LLM 做受限闲聊,失败时退回固定话术。""" + messages = await self.context.get(session_id) + prompt = [ + { + "role": "system", + "content": ( + "你是华夏科技(基金代销金融机构)的智能客服助手。" + "用户正在与你寒暄,请用一两句话简短、友好地回应," + "并自然地引导用户咨询基金知识、开户流程或公司信息。" + "不得推荐任何基金产品,不得谈论具体收益,不得回答金融之外的实质性问题。" + ), + }, + *messages, + ] + try: + return await _maybe_await(self.generate_answer(prompt)) + except Exception: + return await _config( + self.config_getter, + "agent.customer.template.chitchat_fallback", + "您好,我是华夏科技的智能客服,很高兴为您服务!" + "您可以问我基金知识、开户流程或公司信息~", + ) + @staticmethod def _contains_sensitive_input(query: str) -> bool: return bool(