feat: improve context memory and fix auto-draw gemini/upload chain
Some checks failed
Pre-commit / run (ubuntu-latest) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_en (ubuntu-latest, 3.10) (push) Has been cancelled
Deploy Sphinx documentation to Pages / build_zh (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (macos-15, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (ubuntu-latest, 3.12) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.10) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.11) (push) Has been cancelled
Python Unittest Coverage / test (windows-latest, 3.12) (push) Has been cancelled

This commit is contained in:
2026-03-03 10:18:02 +08:00
parent 382581b9bc
commit 00166d7ebf
4 changed files with 223 additions and 109 deletions

View File

@@ -3,6 +3,7 @@ import json
import re
import time
from collections import defaultdict
from datetime import datetime
import websockets
@@ -35,7 +36,9 @@ class QingjianClient:
self.pending_images: dict[str, list[str]] = defaultdict(list)
self.auto_quote_tasks: dict[str, asyncio.Task] = {}
self.last_reply_key: dict[str, str] = {}
self.first_msg_replied: set[str] = set()
self.recent_outbound: list[tuple[str, str, str, float]] = []
self.recent_dialogue: dict[str, list[dict]] = defaultdict(list)
@staticmethod
def _customer_key(data: dict) -> str:
@@ -45,6 +48,38 @@ class QingjianClient:
def _msg_text(data: dict) -> str:
return str(data.get("msg", "") or "").strip()
def _append_dialogue(self, key: str, role: str, text: str) -> None:
t = str(text or "").strip()
if not t:
return
self.recent_dialogue[key].append({"role": role, "text": t})
if len(self.recent_dialogue[key]) > 24:
self.recent_dialogue[key] = self.recent_dialogue[key][-24:]
@staticmethod
def _parse_msg_ts(data: dict) -> float:
# 兼容常见时间字段解析失败时返回0后续按入队顺序兜底
for key in ("timestamp", "msg_time", "send_time", "create_time", "time"):
v = data.get(key)
if v is None:
continue
if isinstance(v, (int, float)):
return float(v)
s = str(v).strip()
if not s:
continue
# 纯数字时间戳
if re.fullmatch(r"\d{10,13}", s):
n = float(s)
return n / 1000.0 if len(s) == 13 else n
# 常见日期格式
for fmt in ("%Y-%m-%d %H:%M:%S", "%Y/%m/%d %H:%M:%S", "%Y-%m-%d %H:%M", "%Y/%m/%d %H:%M"):
try:
return datetime.strptime(s, fmt).timestamp()
except Exception:
pass
return 0.0
def _debounce_seconds(self, msg: str) -> float:
if extract_image_urls(msg):
return 2.5
@@ -61,6 +96,7 @@ class QingjianClient:
if not text:
return
text = self._shorten_reply(text)
key = self._customer_key(data)
msg = {
"msg_id": "",
"acc_id": data.get("acc_id", ""),
@@ -74,6 +110,7 @@ class QingjianClient:
}
activity_event(self.logger, "send_reply_attempt", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=text)
await self.send_message(msg)
self._append_dialogue(key, "assistant", text)
self.recent_outbound.append((str(data.get("acc_id", "")), str(data.get("from_id", "")), text, time.monotonic()))
if len(self.recent_outbound) > 200:
self.recent_outbound = self.recent_outbound[-200:]
@@ -83,6 +120,7 @@ class QingjianClient:
image_url = str(image_url or "").strip()
if not image_url:
return
key = self._customer_key(data)
msg = {
"msg_id": "",
"acc_id": data.get("acc_id", ""),
@@ -96,6 +134,7 @@ class QingjianClient:
}
activity_event(self.logger, "send_image_attempt", trace_id=trace_id, customer_id=data.get("from_id", "-"), msg=image_url)
await self.send_message(msg)
self._append_dialogue(key, "assistant", f"[image]{image_url}")
self.recent_outbound.append((str(data.get("acc_id", "")), str(data.get("from_id", "")), image_url, time.monotonic()))
if len(self.recent_outbound) > 200:
self.recent_outbound = self.recent_outbound[-200:]
@@ -196,6 +235,7 @@ class QingjianClient:
"pending_images": len(self.pending_images[key]),
"auto_quote_trigger": auto_quote,
"last_reply": self.last_reply_key.get(key, ""),
"recent_dialogue": self.recent_dialogue.get(key, [])[-12:],
}
activity_event(self.logger, "agent_process_start", trace_id=trace_id, customer_id=context["customer_id"], acc_id=context["acc_id"], intent=context["intent"])
@@ -300,8 +340,11 @@ class QingjianClient:
queue = self.pending_msgs.get(key, [])
if not queue:
return
merged = "".join([self._msg_text(x) for x in queue if self._msg_text(x)])
data = queue[-1]
indexed = list(enumerate(queue))
indexed.sort(key=lambda it: (self._parse_msg_ts(it[1]), it[0]))
ordered = [x for _, x in indexed]
merged = "".join([self._msg_text(x) for x in ordered if self._msg_text(x)])
data = ordered[-1]
self.pending_msgs[key].clear()
await self._handle_decision(data, merged)
@@ -356,6 +399,14 @@ class QingjianClient:
patched = dict(data)
patched["msg"] = rule.normalized_msg or msg
key = self._customer_key(patched)
self._append_dialogue(key, "user", patched["msg"])
# 硬编码:每个客户首条消息先快速回复“在的”
if key not in self.first_msg_replied:
await self.send_reply(patched, "在的")
self.last_reply_key[key] = "在的"
self.first_msg_replied.add(key)
if msg_type == 1:
await self._handle_decision(patched, patched["msg"])