freud-zero-mvp / strategy_visualizer.py
Feng Chike
Freud Zero MVP: 心理咨询AI系统(清洁部署)
408f650
"""战略推理可视化:将每次战略决策的完整路径渲染为可读的文本树。"""
from datetime import datetime
from pathlib import Path
class StrategyVisualizer:
"""生成战略推理的可视化报告,保存到 sessions/strategy_vis/ 目录。"""
def __init__(self, session_dir="sessions/strategy_vis"):
self.dir = Path(session_dir)
self.dir.mkdir(parents=True, exist_ok=True)
self.report_count = 0
def render(self, trace, turn_number=0):
"""将一次战略推理 trace 渲染为可视化文本并保存。"""
if not trace:
return
self.report_count += 1
lines = []
ts = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
lines.append(f"{'=' * 70}")
lines.append(f"战略推理报告 | 第{turn_number}轮 | {ts}")
lines.append(f"{'=' * 70}")
# 总结
lines.append(f"\n📋 会话总结:")
lines.append(f" {trace.get('summary', '?')}")
# 当前揭露度
current = trace.get("current_disclosure", "?")
lines.append(f"\n📊 当前揭露水平: {current}/10")
# 种子
seeds = trace.get("seeds", {})
lines.append(f"\n🌱 种子视角:")
for sid, seed in seeds.items():
lines.append(f" {sid}: {seed}")
# 路径树
candidates = trace.get("candidates", [])
if not candidates:
lines.append("\n⚠️ 无候选路径")
else:
# 按种子分组
from collections import defaultdict
groups = defaultdict(list)
for c in candidates:
groups[c["id"]].append(c)
# 标记有效路径(delta>0)
effective_ids = set()
for c in candidates:
if c.get("delta", 0) > 0:
effective_ids.add((c["id"], c["branch"]))
lines.append(f"\n🌳 路径树 (共{len(candidates)}条,{len(effective_ids)}条有效):")
lines.append(f" {'─' * 66}")
for sid in sorted(groups.keys()):
items = groups[sid]
seed_text = seeds.get(sid, "?")
max_score = max(i["score"] for i in items)
lines.append(f"")
lines.append(f" ┌─ 种子{sid}: {seed_text[:50]}")
lines.append(f" │ L1咨询师: {items[0].get('l1_reply', '?')[:55]}")
lines.append(f" │ L2来访者: {items[0].get('l2_client', '?')[:55]}")
lines.append(f" │")
for item in sorted(items, key=lambda x: x["branch"]):
bid = item["branch"]
score = item["score"]
delta = item.get("delta", 0)
is_effective = (sid, bid) in effective_ids
is_selected = f"{sid}.{bid}" == trace.get("selected", "")
# 标记符号
if is_selected:
marker = "★"
elif is_effective:
marker = "✓"
else:
marker = "·"
delta_str = f"+{delta}" if delta > 0 else str(delta)
lines.append(f" │ {marker} 分叉{bid}: score={score}/10 (Δ{delta_str}) | {item.get('reason', '')[:30]}")
lines.append(f" │ L3咨询师: {item.get('l3_reply', '?')[:50]}")
lines.append(f" │ L4来访者: {item.get('l4_client', '?')[:50]}")
lines.append(f" │ ── 种子{sid}最高分: {max_score}/10")
lines.append(f" └{'─' * 65}")
# 蒸馏结果
guidance = trace.get("guidance", {})
distill_count = guidance.get("_distill_count", len(effective_ids))
distill_ids = guidance.get("_distill_ids", [])
distill_label = f"{distill_count}条路径(前30%)" + (f" [{', '.join(distill_ids)}]" if distill_ids else "")
lines.append(f"\n🎯 蒸馏结果 (从{distill_label}):")
lines.append(f" 方向: {guidance.get('direction', '?')}")
for p in guidance.get("principles", []):
lines.append(f" • {p}")
lines.append(f" 证据: {guidance.get('evidence', '?')}")
# 计时
timing = trace.get("timing", {})
if timing:
lines.append(f"\n⏱ 计时:")
for k, v in timing.items():
lines.append(f" {k}: {v}s")
# 图例
lines.append(f"\n图例: ★=最终选中 ✓=有效路径(Δ>0) ·=未提升 | 蒸馏仅用前30%有效路径")
lines.append(f"{'=' * 70}\n")
report_text = "\n".join(lines)
# 保存
filename = f"turn{turn_number:03d}_{datetime.now().strftime('%H%M%S')}.txt"
filepath = self.dir / filename
with open(filepath, "w", encoding="utf-8") as f:
f.write(report_text)
# 同时打印到终端
print(report_text)
return str(filepath)