"""Customer-service intent recognition contract.""" from __future__ import annotations import logging import re from enum import StrEnum logger = logging.getLogger("rag.intent") class Intent(StrEnum): GUIDE_PURCHASE = "guide_purchase" WANT_ADVISOR = "want_advisor" NL2SQL_REQUEST = "nl2sql_request" 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": INTENT_SYSTEM_PROMPT}, {"role": "user", "content": query}, ] try: raw = await llm_client.chat(messages) except Exception: logger.exception("intent recognition failed") return Intent.NO_MATCH return parse_intent(raw)