refactor: add rule engine, risk service, quote state machine, and replay tests
This commit is contained in:
@@ -11,6 +11,7 @@ from collections import deque
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Optional, Dict, Any, List
|
||||
from utils.observability import emit_activity, build_trace_id
|
||||
|
||||
# ========== 转接分组映射 ==========
|
||||
def _get_transfer_group(acc_id: str) -> str:
|
||||
@@ -134,16 +135,14 @@ class QingjianAPIClient:
|
||||
|
||||
def _activity_log(self, event: str, **kwargs):
|
||||
"""统一活动日志,便于按 event 检索完整链路。"""
|
||||
safe = {}
|
||||
for k, v in kwargs.items():
|
||||
if isinstance(v, str):
|
||||
safe[k] = v[:200]
|
||||
else:
|
||||
safe[k] = v
|
||||
try:
|
||||
logger.info(f"[ACTIVITY] event={event} data={json.dumps(safe, ensure_ascii=False)}")
|
||||
except Exception:
|
||||
logger.info(f"[ACTIVITY] event={event} data={safe}")
|
||||
emit_activity(
|
||||
logger,
|
||||
event=event,
|
||||
trace_id=str(kwargs.pop("trace_id", "")),
|
||||
customer_id=str(kwargs.pop("customer_id", "")),
|
||||
result=str(kwargs.pop("result", "ok")),
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
async def connect(self):
|
||||
@@ -721,6 +720,8 @@ class QingjianAPIClient:
|
||||
try:
|
||||
msg_text = self.to_chinese(data.get('msg', ''))
|
||||
_cid = data.get('from_id', '')
|
||||
trace_id = build_trace_id(data.get("acc_id", ""), _cid, data.get("msg_id", ""), msg_text[:64])
|
||||
data["_trace_id"] = trace_id
|
||||
_name = self.to_chinese(data.get('from_name', '') or data.get('cy_name', ''))
|
||||
_plat = data.get('acc_type', '')
|
||||
_shop_type = _get_shop_type(data.get('acc_id', ''), self.to_chinese(data.get('goods_name', '') or ''))
|
||||
@@ -864,19 +865,32 @@ class QingjianAPIClient:
|
||||
logger.info("Agent 正在处理消息...")
|
||||
self._activity_log(
|
||||
"agent_process_start",
|
||||
trace_id=trace_id,
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
msg=msg_text,
|
||||
)
|
||||
|
||||
# 调用 Agent
|
||||
_t0 = time.monotonic()
|
||||
response = await self.agent.process_message(customer_msg)
|
||||
self._activity_log(
|
||||
"agent_process_done",
|
||||
trace_id=trace_id,
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
result="ok",
|
||||
latency_ms=int((time.monotonic() - _t0) * 1000),
|
||||
should_reply=bool(response.should_reply),
|
||||
need_transfer=bool(response.need_transfer),
|
||||
)
|
||||
|
||||
# 检查是否需要转接人工
|
||||
if response.need_transfer:
|
||||
logger.info("Agent 决定转接人工")
|
||||
self._activity_log(
|
||||
"agent_transfer",
|
||||
trace_id=trace_id,
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
transfer_msg=response.transfer_msg,
|
||||
@@ -932,6 +946,7 @@ class QingjianAPIClient:
|
||||
logger.info(f"Agent 回复: {response.reply}")
|
||||
self._activity_log(
|
||||
"agent_reply",
|
||||
trace_id=trace_id,
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
reply=response.reply,
|
||||
@@ -955,6 +970,7 @@ class QingjianAPIClient:
|
||||
logger.info("Agent 决定不回复此消息")
|
||||
self._activity_log(
|
||||
"agent_no_reply",
|
||||
trace_id=trace_id,
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
)
|
||||
@@ -963,6 +979,7 @@ class QingjianAPIClient:
|
||||
logger.error(f"Agent 处理失败: {e}")
|
||||
self._activity_log(
|
||||
"agent_process_error",
|
||||
trace_id=data.get("_trace_id", ""),
|
||||
acc_id=data.get("acc_id", ""),
|
||||
customer_id=data.get("from_id", ""),
|
||||
error=str(e),
|
||||
@@ -1659,10 +1676,12 @@ class QingjianAPIClient:
|
||||
original_msg: 收到的原始消息字典
|
||||
reply_content: 回复内容(文本或本地文件路径/http地址)
|
||||
"""
|
||||
trace_id = original_msg.get("_trace_id", "")
|
||||
if not self.websocket:
|
||||
print(f"[{self.get_time()}] 错误: 未连接到服务器")
|
||||
self._activity_log(
|
||||
"send_reply_skipped",
|
||||
trace_id=trace_id,
|
||||
reason="websocket_not_connected",
|
||||
acc_id=original_msg.get("acc_id", ""),
|
||||
customer_id=original_msg.get("from_id", ""),
|
||||
@@ -1687,6 +1706,7 @@ class QingjianAPIClient:
|
||||
)
|
||||
self._activity_log(
|
||||
"send_reply_throttled",
|
||||
trace_id=trace_id,
|
||||
key=ckey,
|
||||
cooldown_s=cooldown,
|
||||
msg=str(reply_content),
|
||||
@@ -1716,10 +1736,12 @@ class QingjianAPIClient:
|
||||
self._log_outbound_once(original_msg, str(reply_content))
|
||||
self._activity_log(
|
||||
"send_reply_attempt",
|
||||
trace_id=trace_id,
|
||||
acc_id=shop_id,
|
||||
customer_id=customer_id,
|
||||
msg=str(reply_content),
|
||||
)
|
||||
reply["_trace_id"] = trace_id
|
||||
await self.send_message(reply)
|
||||
|
||||
def _colloquialize_outbound_reply(self, text: Any) -> Any:
|
||||
@@ -1820,6 +1842,7 @@ class QingjianAPIClient:
|
||||
print(f"[{self.get_time()}] 发送成功:\n{pretty}")
|
||||
self._activity_log(
|
||||
"send_message_success",
|
||||
trace_id=message.get("_trace_id", ""),
|
||||
acc_id=message.get("acc_id", ""),
|
||||
customer_id=message.get("from_id", ""),
|
||||
msg_type=message.get("msg_type", 0),
|
||||
@@ -1829,6 +1852,7 @@ class QingjianAPIClient:
|
||||
print(f"[{self.get_time()}] 发送失败: {e}")
|
||||
self._activity_log(
|
||||
"send_message_error",
|
||||
trace_id=message.get("_trace_id", ""),
|
||||
acc_id=message.get("acc_id", ""),
|
||||
customer_id=message.get("from_id", ""),
|
||||
error=str(e),
|
||||
@@ -1837,6 +1861,7 @@ class QingjianAPIClient:
|
||||
print(f"[{self.get_time()}] 错误: 连接未打开")
|
||||
self._activity_log(
|
||||
"send_message_skipped",
|
||||
trace_id=message.get("_trace_id", ""),
|
||||
reason="socket_not_open",
|
||||
acc_id=message.get("acc_id", ""),
|
||||
customer_id=message.get("from_id", ""),
|
||||
|
||||
Reference in New Issue
Block a user