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
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:
@@ -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"])
|
||||
|
||||
Reference in New Issue
Block a user