1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
122 lines
4.5 KiB
Python
122 lines
4.5 KiB
Python
"""记忆系统状态探针(只读):一次看清「库里有没有」「事件消费到哪了」。
|
|
|
|
用途:排查"记忆系统是否真的在工作"时,先跑这个脚本拿到事实基线,
|
|
再去看日志定位是哪一段断了。全部是 SELECT,不写任何数据。
|
|
|
|
用法:
|
|
D:/conda/envs/jr_py313/python.exe tools/probe_memory_state.py [customer_id]
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sys
|
|
from typing import Any
|
|
|
|
from sqlalchemy import text
|
|
|
|
from app.infrastructure.db import SessionFactory
|
|
|
|
TABLES = (
|
|
"memory_unit",
|
|
"memory_evidence",
|
|
"memory_conflict",
|
|
"memory_sync_outbox",
|
|
"user_facts",
|
|
"profile_snapshots",
|
|
)
|
|
|
|
|
|
async def _scalar(session: Any, sql: str, **params: Any) -> Any:
|
|
try:
|
|
return await session.scalar(text(sql), params)
|
|
except Exception as exc: # 表不存在/连不上库都要能看,而不是整个脚本崩掉
|
|
return f"ERROR {type(exc).__name__}: {exc}"
|
|
|
|
|
|
async def main() -> None:
|
|
customer_id = int(sys.argv[1]) if len(sys.argv) > 1 else None
|
|
async with SessionFactory() as session:
|
|
print("=" * 60)
|
|
print("1) 记忆相关表的总行数")
|
|
print("=" * 60)
|
|
for table in TABLES:
|
|
print(f" {table:<22} {await _scalar(session, f'SELECT COUNT(*) FROM {table}')}")
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print("2) memory_unit 按状态分组")
|
|
print("=" * 60)
|
|
try:
|
|
rows = (await session.execute(
|
|
text("SELECT status, COUNT(*) c FROM memory_unit GROUP BY status"))).all()
|
|
for row in rows:
|
|
print(f" {row[0]:<12} {row[1]}")
|
|
except Exception as exc:
|
|
print(f" ERROR {type(exc).__name__}: {exc}")
|
|
|
|
print()
|
|
print("=" * 60)
|
|
print("3) domain_event_outbox 按 (event_type, status) 分组")
|
|
print(" —— 这里能看到事件是不是堆在 pending(= Worker 没跑 / 处理失败)")
|
|
print("=" * 60)
|
|
try:
|
|
rows = (await session.execute(text(
|
|
"SELECT event_type, status, COUNT(*) c, MAX(occurred_at) latest "
|
|
"FROM domain_event_outbox GROUP BY event_type, status ORDER BY c DESC"
|
|
))).all()
|
|
if not rows:
|
|
print(" (空)")
|
|
for row in rows:
|
|
print(f" {row[0]:<38} {row[1]:<10} {row[2]:<6} latest={row[3]}")
|
|
except Exception as exc:
|
|
print(f" ERROR {type(exc).__name__}: {exc}")
|
|
|
|
if customer_id is not None:
|
|
print()
|
|
print("=" * 60)
|
|
print(f"4) 客户 {customer_id} 的记忆明细")
|
|
print("=" * 60)
|
|
try:
|
|
rows = (await session.execute(text(
|
|
"SELECT memory_uuid, memory_key, content, memory_type, status, "
|
|
"confidence, source_type, updated_at "
|
|
"FROM memory_unit WHERE customer_id=:cid ORDER BY updated_at DESC LIMIT 20"
|
|
), {"cid": customer_id})).all()
|
|
if not rows:
|
|
print(" (该客户没有任何 memory_unit 行)")
|
|
for row in rows:
|
|
print(f" [{row[4]}] {row[1]} = {row[2]!r}")
|
|
print(f" type={row[3]} conf={row[5]} src={row[6]} at={row[7]}")
|
|
except Exception as exc:
|
|
print(f" ERROR {type(exc).__name__}: {exc}")
|
|
|
|
print()
|
|
print(f" user_facts:")
|
|
try:
|
|
rows = (await session.execute(text(
|
|
"SELECT fact_key, confidence FROM user_facts "
|
|
"WHERE customer_id=:cid LIMIT 20"), {"cid": customer_id})).all()
|
|
for row in rows:
|
|
print(f" {row[0]} (conf={row[1]})")
|
|
if not rows:
|
|
print(" (无)")
|
|
except Exception as exc:
|
|
print(f" ERROR {type(exc).__name__}: {exc}")
|
|
|
|
print(f" profile_snapshots:")
|
|
try:
|
|
rows = (await session.execute(text(
|
|
"SELECT version, is_current, generated_at FROM profile_snapshots "
|
|
"WHERE customer_id=:cid ORDER BY id DESC LIMIT 5"), {"cid": customer_id})).all()
|
|
for row in rows:
|
|
print(f" v{row[0]} current={row[1]} at={row[2]}")
|
|
if not rows:
|
|
print(" (无)")
|
|
except Exception as exc:
|
|
print(f" ERROR {type(exc).__name__}: {exc}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main())
|