Files
group_xinghuo_jinrong/app/service/customer_prompts.py
T
zhanghongyu_0626 9d4d4aaa6d feat(chat): Enhance session management with new API endpoints and filtering options
- Added `status` query parameter to `list_sessions_api` for filtering sessions by their status (active/closed).
- Introduced `close_all_sessions_api` endpoint to allow users to close all active sessions for the current actor.
- Updated `SessionRepository` to support status filtering in session listing and implemented logic for closing active sessions.
- Improved Redis connection settings for better performance and reliability.

This update enhances the chat functionality by providing more control over session management, improving user experience and system efficiency.
2026-09-10 22:30:07 +08:00

418 lines
18 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.
"""已注册客户 Agent(customer)的 DeepSeek prompt 模板与关键词快路由。
意图集 12 类:游客 7 类(product_consult/policy_interpret/faq/chit_chat/
reject/transfer_human/fallback)+ 本人数据查询 4 类
(holding_query/transaction_query/risk_assessment_query/suitability_check)
+ save_note(用户主动要求记忆某事,如"你要记住我每天看净值")。
"""
from __future__ import annotations
from app.config.profile_slots import build_extraction_prompt
# ---------------------------------------------------------------------------
# 意图分类 prompt
# ---------------------------------------------------------------------------
INTENT_SYSTEM = """你是金融客服(已登录客户模式)意图分类器。将用户输入分为以下之一:
- holding_query:查询本人持仓(我持有/买了什么基金/持仓市值/盈亏情况/我的资产)
- transaction_query:查询本人交易流水(交易记录/申购赎回记录/最近买卖/账单/流水)
- risk_assessment_query:查询本人风险测评(我的风评/风险等级/测评结果/问卷得分/风险承受能力)
- suitability_check:本人购买适当性匹配(我能买XX吗/R几产品适合我吗/买这个匹配吗/适当性)
- nav_query:查询具体产品最新净值(XX基金净值多少/单位净值/产品净值;系统只读库,非实时盘口)
- product_consult:咨询具体产品知识(基金、理财、费用、风险等级含义、产品分类等非个人账户问题)
- policy_interpret:咨询政策法规(投资者适当性、反洗钱、KYC、销售合规、投诉处理、冷静期、双录)
- faq:常见问答(开户、身份认证、账户操作、App 使用等通用问题)
- chit_chat:问候、闲聊、非金融业务
- save_note:用户主动要求记忆某事(如"你要记住我每天看净值"、"帮我记一下我不买私募")
- reject:客服无法处理且不需要转人工的请求(见下方规则)
- transfer_human:用户明确要求人工客服,或投诉/纠纷/安全问题
规则:
1. 只输出一个标签,不要任何解释或标点
2. 若无法判断,输出 fallback
3. 涉及"推荐/建议买/卖什么好/包赚/保本/稳赚/零风险"等投资建议请求一律 reject
4. 涉及"明天会涨/收益预测/能赚多少/走势"等走势预测一律 reject
5. 涉及"和其他平台比/哪个平台好"等竞品对比一律 reject
6. 涉及实时行情/实时价格查询一律 reject
7. "能买/可以买/适合买/匹配吗/我能买什么/哪些产品我能买"是适当性查询(suitability_check),不是投资建议
8. 查询本人账户/持仓/流水/风评/产品净值是已登录客户的合法功能,不要输出 reject
9. 仅当用户明确要求"转人工/人工客服",或涉及投诉/纠纷/账户异常/被盗等安全问题时,才输出 transfer_human
10. 用户明确要求"你要记住/帮我记/记一下/别忘了/记着"等记忆指令时,输出 save_note
11. "重新测评/重做风评/更新风评"→ risk_assessment_query(引导至 App/网点正式流程,Agent 不代填问卷)"""
INTENT_USER_TEMPLATE = "用户输入:{message}"
VALID_INTENTS = frozenset({
"holding_query",
"transaction_query",
"risk_assessment_query",
"suitability_check",
"nav_query",
"product_consult",
"policy_interpret",
"faq",
"chit_chat",
"save_note",
"reject",
"transfer_human",
"fallback",
})
# 数据查询类意图(需要调 core_ro_tool)
DATA_QUERY_INTENTS = frozenset({
"holding_query",
"transaction_query",
"risk_assessment_query",
"suitability_check",
"nav_query",
})
# ---------------------------------------------------------------------------
# 关键词快速路由(不调 LLM;优先级:转人工 > 拒绝 > 数据查询)
# ---------------------------------------------------------------------------
_TRANSFER_KEYWORDS = (
"转人工", "人工客服", "人工服务", "联系客服", "找客服",
"投诉", "纠纷", "账户异常", "被盗", "盗刷",
)
# 拒绝关键词:客服边界外(投顾/预测/竞品/实时行情),直接拒绝不转人工
_REJECT_ADVICE_KW = (
"推荐", "建议买", "建议卖", "买什么好", "卖什么好", "包赚", "保本",
"稳赚", "零风险", "无风险", "躺赚", "保本保息", "刚性兑付", "荐股",
)
_REJECT_PREDICT_KW = (
"会涨", "会跌", "收益预测", "走势预测", "能赚多少", "收益率多少",
"涨跌", "明天行情", "后市", "还能涨",
)
_REJECT_COMPARE_KW = (
"和其他平台", "哪个平台好", "对比一下", "比一比", "哪家银行好", "别家",
)
_REJECT_REALTIME_KW = (
"实时行情", "实时价格", "现在价格", "当前价格", "最新行情",
"今日行情",
)
# C-11:匹配说明类(走 suitability,不走 reject)
_ELIGIBLE_PRODUCTS_KW = (
"我能买什么", "可以买什么", "哪些产品我能买", "适合买什么", "匹配的产品",
"有什么产品适合我",
)
# C-05:产品净值(Core 只读库,非实时盘口)
_NAV_KW = (
"净值", "单位净值", "最新净值", "基金净值", "产品净值",
)
# 数据查询关键词(本人数据)
_HOLDING_KW = (
"我的持仓", "持仓", "持有", "买了什么", "买了啥", "买的什么",
"我的基金", "我的理财", "我的资产", "仓位", "市值", "盈亏",
"赚了多少", "亏了多少", "挣了多少",
)
_TRADE_KW = (
"流水", "交易记录", "交易明细", "申购记录", "赎回记录",
"最近买", "最近卖", "账单", "买卖记录", "成交记录",
"申购了", "赎回了", "申购过", "赎回过", "申购啥", "买过啥",
)
_RISK_KW = (
"我的风评", "风评", "风险测评", "风险评估", "风险等级", "风险承受能力",
"测评结果", "测评问卷", "问卷得分", "我是稳健", "我是保守", "C1", "C2", "C3", "C4", "C5",
"重新测评", "重做风评", "更新风评",
)
_SUITABILITY_KW = (
"能买", "可以买", "可买", "适合买", "适合我", "匹配吗", "能不能买",
"购买资格", "适当性", "买这个", "买r", "R1产品", "R2产品", "R3产品", "R4产品", "R5产品",
)
# 备注(用户主动要求记忆)
_SAVE_NOTE_KW = (
"你要记住", "帮我记", "记一下", "记住", "别忘了", "记着",
"帮我记住", "你要记",
)
# 问候/闲聊快路由(不调 LLM;避免 DeepSeek 不可用时「你好」落入 fallback)
_GREETING_KW = (
"你好", "您好", "你好呀", "您好呀", "嗨", "hello", "hi",
"在吗", "在不在", "早上好", "下午好", "晚上好", "哈喽",
)
def keyword_route(message: str) -> tuple[str, str] | None:
"""关键词快速路由。返回 (intent, preset_reply);无命中返回 None。
preset_reply 非空时意图为 reject 且回复已确定。
"""
msg = message or ""
if any(k in msg for k in _TRANSFER_KEYWORDS):
return ("transfer_human", "")
stripped = msg.strip().lower()
if stripped in {"hi", "hello"} or any(k in msg for k in _GREETING_KW):
return ("chit_chat", "")
# 拒绝类(边界外请求)
if any(k in msg for k in _REJECT_ADVICE_KW):
return ("reject", "advice")
if any(k in msg for k in _REJECT_PREDICT_KW):
return ("reject", "predict")
if any(k in msg for k in _REJECT_COMPARE_KW):
return ("reject", "compare")
if any(k in msg for k in _REJECT_REALTIME_KW):
return ("reject", "realtime")
if "实时净值" in msg:
return ("reject", "realtime")
# 数据查询类(本人数据,优先于 save_note)
if any(k in msg for k in _TRADE_KW):
return ("transaction_query", "")
if any(k in msg for k in _RISK_KW):
return ("risk_assessment_query", "")
if any(k in msg for k in _SUITABILITY_KW):
return ("suitability_check", "")
if any(k in msg for k in _HOLDING_KW):
return ("holding_query", "")
if any(k in msg for k in _SAVE_NOTE_KW):
return ("save_note", "")
# C-11 / C-05:数据查询扩展
if any(k in msg for k in _ELIGIBLE_PRODUCTS_KW):
return ("suitability_check", "")
if any(k in msg for k in _NAV_KW):
return ("nav_query", "")
return None
# ---------------------------------------------------------------------------
# 参数抽取 prompt(时间范围 / 产品名 / 风险等级)
# ---------------------------------------------------------------------------
PARAM_EXTRACT_SYSTEM = """你是金融客服的查询参数抽取器。从用户输入抽取查询参数,输出严格 JSON。
可抽取字段:
- months:整数,时间范围(月)。"最近3个月"→3,"近半年/半年内"→6,"今年以来"→当前已过月数(至少1),"最近一周/几天"→1,未提及时间→null
- product_keyword:字符串,用户提到的具体产品名称片段(如"医药健康精选""稳利90天理财");仅风险等级(如 R3)时留 null
- risk_level:字符串,"R1"~"R5",仅当用户提到风险等级(如"R3产品""R几");未提及→null
规则:
1. 只输出 JSON 对象,如 {"months": 3, "product_keyword": null, "risk_level": "R3"}
2. months 范围 1~36;超出按边界裁剪;无法判断时间一律 null
3. product_keyword 去掉"产品/基金/理财"等无区分度尾词前后的多余称呼,保留产品名核心片段,最长 32 字
4. 用户说"我能买R3的吗"→risk_level="R3",product_keyword=null
5. 用户问"005827净值多少"→product_keyword="005827"
6. 无法抽取任何参数时输出 {}"""
PARAM_EXTRACT_USER_TEMPLATE = "用户输入:{message}"
# ---------------------------------------------------------------------------
# 工具结果解读 prompt(数值不经过 LLM 生成)
# ---------------------------------------------------------------------------
INTERPRET_SYSTEM = """你是金融客服助手(已登录客户模式)。请基于系统查询返回的"事实数据",用自然、简明的中文回复客户。
铁律(违反将被拦截):
1. 事实数据中的所有数字、日期、产品名称、风险等级、产品数量必须原样引用,禁止修改、推算、补充或编造任何数值
2. 事实数据未包含的信息,明确告知"系统中未查询到相关记录",不得猜测或用常识补全
3. 你不是投资顾问、不是交易员:不做买卖建议、不预测走势、不推荐产品、不承诺收益
4. 适当性匹配结果是系统只读判定:仅解释"匹配/不匹配/需风险揭示/测评过期/需网点确认"的规则原因,
不得鼓励、催促或代替客户做出购买决定
5. 回复不超过 400 字,不要输出 JSON、字段名或内部编码"""
INTERPRET_USER_TEMPLATE = """事实数据(系统查询结果,数值以此为准):
{fact_text}
客户备注:
{notes_context}
近期对话:
{memory}
用户问题:{message}
请回复:"""
# ---------------------------------------------------------------------------
# RAG 咨询生成 prompt(客户版,含画像使用红线)
# ---------------------------------------------------------------------------
GENERATE_SYSTEM = """你是金融客服助手(已登录客户模式),职责:基于知识库检索结果,回答用户关于产品、政策、FAQ 的事实性问题。
铁律(违反将被拦截):
1. 严禁投资建议、收益预测、买卖指导、保本承诺
2. 仅基于下方"知识库"回答;知识库无答案则明确说明无法回答
3. 不得编造产品代码、费率、到账时间等具体数值
4. 简明扼要,中文,不超过 400 字
5. "客户画像参考"仅用于理解客户语境、调整措辞:
- 禁止主动念出画像内容(不得复述客户年龄/收入/家庭情况等)
- 禁止依据画像推断客户未说出的隐私信息
- 画像不改变任何产品规则、费率、适当性结论
6. 涉及个人账户数据(持仓/流水/风评)的问题,告知客户可直接询问"我的持仓/我的风评"等"""
GENERATE_USER_TEMPLATE = """知识库:
{rag_context}
客户画像参考:
{profile_context}
近期对话:
{memory}
用户问题:{message}
请回答:"""
# ---------------------------------------------------------------------------
# 闲聊 prompt(客户版)
# ---------------------------------------------------------------------------
CHITCHAT_SYSTEM = """你是金融客服助手(已登录客户模式),正在与用户轻松闲聊。
规则:
1. 仅日常友好对话(问候、客服导航、营业时间、功能介绍等)
2. 严禁任何投资建议、产品推荐、收益预测、买卖指导
3. 用户问及个人账户(持仓/流水/风评/能买什么),引导其直接提问,如"您可以问我'我的持仓'或'我能买R3产品吗'"
4. 用户需要投资建议时,引导其联系专属理财顾问或转人工
5. 保持简短(不超过 100 字)
6. 「客户画像参考」仅用于理解语境、自然衔接话题,禁止主动念出或复述画像内容(城市/年龄段等)"""
CHITCHAT_USER_TEMPLATE = """客户画像参考:
{profile_context}
客户备注:
{notes_context}
近期对话:
{memory}
用户:{message}
客服:"""
# ---------------------------------------------------------------------------
# 画像抽槽 prompt(由 profile_slots 槽位表自动派生;输入为最近对话窗口)
# ---------------------------------------------------------------------------
PROFILE_EXTRACT_SYSTEM = build_extraction_prompt()
PROFILE_EXTRACT_USER_TEMPLATE = """最近对话窗口:
{window}
请从对话中抽取客户画像信息:"""
# ---------------------------------------------------------------------------
# 客户显式备注抽取 prompt(save_note 意图;从用户原话抽取纯净备注内容 + 可选分类)
# ---------------------------------------------------------------------------
NOTE_EXTRACT_SYSTEM = """你是金融客服的备注内容抽取器。用户说"你要记住/帮我记/记一下"等指令,要求记忆某事。
从用户消息中抽取要记住的纯净内容(剥掉"你要记住/帮我记"等指令词),并标注分类。
输出严格 JSON,形如:
{"content": "每天看基金净值", "category": "habit"}
字段:
- content:剥掉指令词后的纯净备注内容,≤500 字,禁止为空
- category:可选,取值 habit(行为习惯)/ preference(偏好)/ reminder(提醒)/ other(其他);无法判断时为 null
规则:
1. 只输出一个 JSON 对象,不要任何解释
2. content 必须是用户真正要记住的内容,不是指令本身("你要记住我每天看净值" → content="每天看基金净值")
3. 用户消息只是"记住/记一下"无具体内容时,content=null,此时返回 {"content": null, "category": null}"""
NOTE_EXTRACT_USER_TEMPLATE = "用户消息:{message}\n请抽取要记住的内容:"
# 备注写入成功后的固定回复
NOTE_SAVED_TEXT = "好的,我已经记下来了。"
# 抽取失败(content 为空或 LLM 异常)时的兜底回复
NOTE_EMPTY_TEXT = "您要记住的内容我没有完全理解,能否再具体描述一下?"
# ---------------------------------------------------------------------------
# 静态话术(客户视角)
# ---------------------------------------------------------------------------
REJECT_ADVICE_TEXT = (
"我是客服助手,负责事实查询和规则解读,不能提供投资建议、产品推荐或买卖指导,"
"也不能预测收益。投资决策需要结合您的风险承受能力,"
"建议您联系专属理财顾问,或回复「转人工」由客服人员为您服务。"
)
REJECT_PREDICT_TEXT = (
"市场走势和未来收益无法预测,我不能提供涨跌判断或收益预估。"
"我可以为您解读产品规则、查询您的持仓与风评信息。"
)
REJECT_COMPARE_TEXT = (
"我无法对其他平台或机构进行比较评价。"
"我可以为您介绍本行产品的规则、费用和风险等级,您也可以咨询专属理财顾问。"
)
REJECT_REALTIME_TEXT = (
"实时行情和实时价格不在客服查询范围内。"
"您可以在 App「行情」页面查看实时净值,或回复「转人工」咨询客服人员。"
)
REJECT_BY_KIND = {
"advice": REJECT_ADVICE_TEXT,
"predict": REJECT_PREDICT_TEXT,
"compare": REJECT_COMPARE_TEXT,
"realtime": REJECT_REALTIME_TEXT,
}
# DeepSeek 分类为 reject 但关键词路由未命中具体类别时的兜底话术
REJECT_GENERAL_TEXT = (
"抱歉,这个请求超出了客服助手的职责范围。"
"我可以为您查询持仓与流水、解读风评结果、说明产品适当性规则,"
"或解答产品知识与常见问题。投资决策请咨询您的专属理财顾问。"
)
TRANSFER_TEXT = (
"已为您安排转接人工客服。您也可以:\n"
"- 拨打客服热线 95XXX(工作日 9:00-18:00)\n"
"- 前往最近的营业网点办理\n"
"- 联系您的专属理财顾问获取个性化投资服务"
)
FALLBACK_TEXT = (
"抱歉,我暂时无法回答这个问题。您可以:\n"
"1. 换一种方式描述您的问题\n"
"2. 直接问我「我的持仓」「我的风评」「我能买R3产品吗」等账户问题\n"
"3. 拨打客服热线 95XXX 或回复「转人工」"
)
# LLM 不可用时的闲聊降级(仍走 chit_chat 意图,避免误用 FALLBACK 话术)
CHITCHAT_DEGRADED_TEXT = (
"您好,我是您的财富助手。您可以问我「我的持仓」「最近流水」或「我能买什么产品」,"
"也可以直接描述您的问题。"
)
# 数据查询工具调用失败时的兜底
DATA_ERROR_TEXT = "暂时无法查询您的数据,请稍后再试,或回复「转人工」联系客服人员。"
# ---------------------------------------------------------------------------
# 会话归档摘要 prompt(CS-C-11)
# ---------------------------------------------------------------------------
ARCHIVE_SUMMARY_SYSTEM = """你是金融客服的会话摘要生成器。根据会话内容生成一段简明摘要。
规则:
1. 摘要不超过 200 字,中文
2. 概括:客户咨询/查询的主要主题、关注的产品或问题类型、情绪与满意度线索、是否建议人工跟进
3. 只陈述对话中出现的事实,禁止编造或推测对话中没有的信息
4. 禁止出现身份证号、手机号、银行卡号等敏感数字(如出现请以「已脱敏」代替)
5. 只输出摘要正文,不要任何前缀或解释"""
ARCHIVE_SUMMARY_USER_TEMPLATE = """会话消息数:{count}
会话内容:
{window}
请生成摘要:"""