Files
Mutual_Fund/service/customer_agent/chat.py
T

121 lines
4.0 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.
"""Anonymous customer-service orchestration without private customer access."""
from __future__ import annotations
import json
import re
from inspect import isawaitable
from rag.intent import Intent
class QueryTooLongError(ValueError):
pass
async def _config(config_getter, key: str, default: str):
value = config_getter(key, default)
if isawaitable(value):
value = await value
return value or default
async def _maybe_await(value):
return await value if isawaitable(value) else value
class AnonymousCustomerAgent:
def __init__(
self,
*,
context,
rag_retrieve,
intent_recognize,
generate_answer,
audit_writer,
config_getter,
):
self.context = context
self.rag_retrieve = rag_retrieve
self.intent_recognize = intent_recognize
self.generate_answer = generate_answer
self.audit_writer = audit_writer
self.config_getter = config_getter
async def handle(self, session_id: str, query: str, *, trace_id: str) -> dict:
if len(query) > 2000:
raise QueryTooLongError("query长度不能超过2000字符")
await self.context.append(session_id, "user", query)
if self._contains_sensitive_input(query):
await _maybe_await(self.audit_writer(
action="anon_sensitive_input",
trace_id=trace_id,
session_id=session_id,
))
intent = await _maybe_await(self.intent_recognize(query))
sources = []
if intent is Intent.GUIDE_PURCHASE:
answer = await _config(
self.config_getter,
"agent.customer.template.guide_purchase",
"请前往开户页面办理。",
)
elif intent is Intent.WANT_ADVISOR:
answer = await _config(
self.config_getter,
"agent.customer.template.guide_advisor",
"如需基金推荐,请联系投资顾问。",
)
elif intent is Intent.KNOWLEDGE_QA:
try:
sources = await _maybe_await(self.rag_retrieve(query, None))
except Exception:
sources = []
if not sources:
answer = await _config(
self.config_getter,
"agent.customer.template.fallback_human",
"当前未找到匹配信息,请转人工客服。",
)
else:
messages = await self.context.get(session_id)
prompt = messages + [
{
"role": "system",
"content": "仅根据提供的知识来源回答,不得编造基金推荐。",
},
{
"role": "system",
"content": f"知识来源:{json.dumps(sources, ensure_ascii=False)}",
},
]
try:
answer = await _maybe_await(self.generate_answer(prompt))
except Exception:
answer = await _config(
self.config_getter,
"agent.customer.template.fallback_human",
"当前服务繁忙,请转人工客服。",
)
else:
answer = await _config(
self.config_getter,
"agent.customer.template.fallback_human",
"当前未找到匹配信息,请转人工客服。",
)
await self.context.append(session_id, "assistant", answer)
return {
"answer": answer,
"sources": sources,
"intent": intent.value,
"trace_id": trace_id,
}
@staticmethod
def _contains_sensitive_input(query: str) -> bool:
return bool(
re.search(r"(?<!\d)1[3-9]\d{9}(?!\d)", query)
or re.search(r"(?:客户|customer)[_ -]?(?:id|号)?\s*[::]?\s*[A-Za-z0-9_-]{4,}", query, re.I)
)