Files
inquiry_robot/inquiry-agent/agent/redis_coord/flow_bookmark.py
T

132 lines
3.9 KiB
Python

"""
询价当前书签短协调:HTTP 收字、Worker 识图后补问必须读同一份。
不当六态、不当 checkpoint。只存当前 sender 的补问/核对草稿。
禁止:写入现网 Redis 前缀;把已出号工单只放这里当主账。
"""
from __future__ import annotations
import json
import logging
from typing import Any, Optional, Protocol
from agent.redis_coord.keys import FLOW_BOOKMARK
from agent.redis_coord.runtime import get_redis_runtime
logger = logging.getLogger(__name__)
class BookmarkStore(Protocol):
"""单测可注入;生产走 Redis。"""
def save(self, kind: str, sender_id: str, payload: dict[str, Any]) -> None: ...
def load(self, kind: str, sender_id: str) -> Optional[dict[str, Any]]: ...
def delete(self, kind: str, sender_id: str) -> None: ...
class DictBookmarkStore:
"""两份 Flow 实例共用同一字典,模拟 HTTP/Worker。"""
def __init__(self) -> None:
self._d: dict[str, dict[str, Any]] = {}
def save(self, kind: str, sender_id: str, payload: dict[str, Any]) -> None:
self._d[f"{kind}:{sender_id}"] = json.loads(json.dumps(payload))
def load(self, kind: str, sender_id: str) -> Optional[dict[str, Any]]:
row = self._d.get(f"{kind}:{sender_id}")
return json.loads(json.dumps(row)) if row is not None else None
def delete(self, kind: str, sender_id: str) -> None:
self._d.pop(f"{kind}:{sender_id}", None)
def _redis_ok() -> bool:
try:
return get_redis_runtime().client.backend == "redis"
except Exception:
return False
def save_flow_bookmark(
kind: str,
sender_id: str,
payload: dict[str, Any],
*,
store: Optional[BookmarkStore] = None,
) -> None:
"""写下当前书签。store 优先;否则仅真 Redis。"""
sid = (sender_id or "").strip()
name = (kind or "").strip() or "air"
if not sid or not isinstance(payload, dict):
return
if store is not None:
store.save(name, sid, payload)
return
if not _redis_ok():
return
try:
client = get_redis_runtime().client
# 核对卡不设过期。确定前一直留着,离开核对或建单后由 delete 清掉。
client.raw.set(
client.key(FLOW_BOOKMARK, name, sid),
json.dumps(payload, ensure_ascii=False),
)
except Exception:
logger.exception("写询价书签失败 kind=%s sender=%s", name, sid)
def load_flow_bookmark(
kind: str,
sender_id: str,
*,
store: Optional[BookmarkStore] = None,
) -> dict[str, Any]:
"""取出当前书签。没有则空。"""
sid = (sender_id or "").strip()
name = (kind or "").strip() or "air"
if not sid:
return {}
if store is not None:
return dict(store.load(name, sid) or {})
if not _redis_ok():
return {}
try:
client = get_redis_runtime().client
raw = client.raw.get(client.key(FLOW_BOOKMARK, name, sid))
if not raw:
return {}
if isinstance(raw, bytes):
raw = raw.decode("utf-8")
data = json.loads(raw)
return dict(data) if isinstance(data, dict) else {}
except Exception:
logger.exception("读书签失败 kind=%s sender=%s", name, sid)
return {}
def delete_flow_bookmark(
kind: str,
sender_id: str,
*,
store: Optional[BookmarkStore] = None,
) -> None:
"""发图新开或结束当前草稿时删掉。已出号会话不靠这把键。"""
sid = (sender_id or "").strip()
name = (kind or "").strip() or "air"
if not sid:
return
if store is not None:
store.delete(name, sid)
return
if not _redis_ok():
return
try:
client = get_redis_runtime().client
client.raw.delete(client.key(FLOW_BOOKMARK, name, sid))
except Exception:
logger.exception("删书签失败 kind=%s sender=%s", name, sid)