"""Generate defense deck from docs/答辩/答辩知识点清单.md structure.""" from __future__ import annotations from pathlib import Path from pptx import Presentation from pptx.dml.color import RGBColor from pptx.enum.text import MSO_ANCHOR, PP_ALIGN from pptx.util import Inches, Pt ROOT = Path(__file__).resolve().parents[2] OUT = ROOT / "docs" / "答辩" / "JinRong-答辩.pptx" INK = RGBColor(0x13, 0x2B, 0x3A) TEXT = RGBColor(0x17, 0x21, 0x2B) MUTED = RGBColor(0x71, 0x80, 0x8A) GOLD = RGBColor(0xC9, 0xA6, 0x6B) WHITE = RGBColor(0xFF, 0xFF, 0xFF) BG_LIGHT = RGBColor(0xF6, 0xF5, 0xF1) def _set_slide_bg(slide, rgb: RGBColor) -> None: fill = slide.background.fill fill.solid() fill.fore_color.rgb = rgb def _add_title_slide(prs: Presentation, title: str, subtitle: str, notes: str) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) _set_slide_bg(slide, INK) box = slide.shapes.add_textbox(Inches(0.7), Inches(2.0), Inches(11.5), Inches(1.2)) tf = box.text_frame tf.text = title p = tf.paragraphs[0] p.font.size = Pt(40) p.font.bold = True p.font.color.rgb = WHITE sub = slide.shapes.add_textbox(Inches(0.7), Inches(3.3), Inches(11.5), Inches(1.0)) stf = sub.text_frame stf.text = subtitle sp = stf.paragraphs[0] sp.font.size = Pt(20) sp.font.color.rgb = GOLD slide.notes_slide.notes_text_frame.text = notes def _add_section_slide(prs: Presentation, title: str, bullets: list[str], notes: str) -> None: slide = prs.slides.add_slide(prs.slide_layouts[6]) _set_slide_bg(slide, BG_LIGHT) bar = slide.shapes.add_shape(1, Inches(0), Inches(0), Inches(13.33), Inches(0.12)) bar.fill.solid() bar.fill.fore_color.rgb = GOLD bar.line.fill.background() tbox = slide.shapes.add_textbox(Inches(0.7), Inches(0.45), Inches(12), Inches(0.8)) ttf = tbox.text_frame ttf.text = title tp = ttf.paragraphs[0] tp.font.size = Pt(32) tp.font.bold = True tp.font.color.rgb = INK body = slide.shapes.add_textbox(Inches(0.85), Inches(1.35), Inches(11.8), Inches(5.5)) btf = body.text_frame btf.word_wrap = True btf.vertical_anchor = MSO_ANCHOR.TOP for i, line in enumerate(bullets): p = btf.paragraphs[0] if i == 0 else btf.add_paragraph() p.text = line p.level = 0 p.font.size = Pt(18 if len(line) < 80 else 16) p.font.color.rgb = TEXT p.space_after = Pt(10) slide.notes_slide.notes_text_frame.text = notes def main() -> None: prs = Presentation() prs.slide_width = Inches(13.333) prs.slide_height = Inches(7.5) slides: list[tuple[str, list[str], str, bool]] = [ ( "金融四 Agent 智能代销平台", [], "", True, ), ] # Title handled separately _add_title_slide( prs, "金融四 Agent 智能代销平台", "答辩演示 · XX 科技综合财富 · merger 基线", "开场:代销四角色 + L0 与合规底座;Core 为模拟库;四 Agent 不互调 LLM。", ) deck: list[tuple[str, list[str], str]] = [ ( "我们要做什么(网站三句话)", [ "用于:持牌财富代销场景下的 AI 工作台(Web + FastAPI),不是营销落地页", "业务:XX 科技综合财富平台 · 前端视觉「静奢智能」", "用户:客户看资产与对话 · 分析员问数 · 风控处置 · 理财师看归属(顾问 Agent 未完整接入)", ], "对应用户模板:用途 / 业务名 / 用户能做什么。", ), ( "总架构(30 秒)", [ "四角色共用 L0 事实 + 合规底座;跨线靠画像表、预警表、代销 REST", "分层:浏览器 → FastAPI → Service → Tool/Repository → MySQL 双库 / Redis / Milvus / Core", "对话:通用 Chat/SSE · 客服 14 节点 · 问数独立(interpret 按需解读)", "铁律:L0 不可覆盖 · 仅 R-02 可阻断交易 · 不自动冻户 · 审计只 INSERT", ], "MEMORY §0 + 答辩清单 §一。", ), ( "数据怎么流", [ "L0 Core 模拟:Agent 只读;仅模拟交易网关 INSERT core_trade", "jinrong_agent 与 jinrong_core 物理隔离", "Redis 6380:会话窗口、限流、问数缓存世代;MySQL 永久落盘", "Milvus:产品规则 RAG(Ollama bge-m3 1024 维,不出内网)", ], "§二 数据分层表口述。", ), ( "共用底座 Wave 0", [ "JWT + RBAC + X-Agent-Type 准入 · dev token 与 gateway 双栈(AL-09)", "输入防护:限流 / 注入 / 超长 · 被拒 fail-fast 不建会话", "审计:trace_id 贯通 http_access + audit_log + agent_tool_call", "Tool:customer_id 会话注入,LLM 不能指定 · 归属校验 + 落库", ], "F-01~F-04 演示工程化。", ), ( "客服 Agent(主 Demo 之一)", [ "14 节点 LangGraph:Core Tool + fin_* RAG + 1B sanitize 出口", "C-04/C-05:阈值提醒、净值查询 · C-11 适当性 vs 营销 reject", "Demo:CUST-9527 持仓 + 阈值 · 游客 RAG", "SSE:Tool 同步完成后再推文本,避免半句不一致", ], "步骤 1 客户助手 + 持仓页。", ), ( "数据分析 Agent(主 Demo 之二)", [ "问数与 Chat 分离:默认 interpret=false 出表 · 按钮触发 /interpret", "五层 sql_guard + 模板命中 Tag + 结果缓存世代(D-06)", "D-10:解读数字与表逐字比对,失败降级只出表", "Demo:分析员问数 → 模板/缓存 Tag → 解读按钮", ], "DEMO-SOP-问数.md。", ), ( "风控 Agent(主 Demo 之三)", [ "事件线:模拟交易 → R-02 适当性 → 规则 R-01~R-05 → 聚合预警 → 人工处置", "对话线:6 只只读 Tool,无处置 Tool(状态不变)", "前端:预警台账 / 适当性 / AML / 模拟交易 + 风控 Chat", "Demo:prepare_all.ps1 · A-1/A-3 · 台账筛选处置", ], "演示SOP-风控模块.md。", ), ( "代销平台 API + 前端 web/", [ "v0.1 canonical REST:客户/产品/顾问/合规 · 重复能力以平台 API 为准", "React 19 + Vite + Ant Design · HashRouter · /api 代理 8000", "四角色 Dashboard + 产品行情(nav/history 图表)", "DevReadyBanner:前后端版本不一致先告警", ], "FRONTEND-HANDOFF + 登录四 Demo 账号。", ), ( "诚实边界(答辩加分)", [ "理财师 Agent:无 advisor_service 级编排 · A-01~A-05 未做 · 勿作主 Demo", "问数:D-09 多轮 / D-12 钻取仍 open", "看板:无钻取 · 进页全量拉平台 API", "测试:827 pytest · 22 Vitest(口播基线以当日 pytest 为准)", ], "§4.2 §4.3 §4.6 诚实缺口。", ), ( "技术选型(为什么这样)", [ "LangGraph 显式图 · MySQL 双库隔离 · Milvus Lite 本机 RAG", "DeepSeek 生成 · 无 Key 明确降级 · Redis RESP2 @6380", "FastAPI 一体栈 · React+AntD 四角色 Demo 快", "不用 Streamlit · 不用 Agent 互调 LLM", ], "§五 选型表。", ), ( "工程化亮点", [ "契约优先:代销 API v0.1 · AL-09 合并接缝", "双护栏:输入防护 + 客服 sanitize + 问数 guardrail", "RAG 溯源 source_refs · 问数 battery 本地跑分不入库", "docs/memory + docs/course 交互课 + 本套 SOP", ], "§六 加分项。", ), ( "Demo 动线(12~15 分钟标准)", [ "0 开场 30 秒铁律 + 架构句", "1 客户:登录 → 持仓/阈值 → 客户助手", "2 分析员:问数 → Tag → 解读", "3 风控:模拟交易 → 台账 → 处置", "4 可选:行情页 · /api/ready 探针", ], "DEMO-SOP-答辩全流程.md 路线 B。", ), ( "谢谢 · Q&A", [ "配套文档:docs/答辩/答辩知识点清单.md", "Demo:docs/答辩/DEMO-SOP-答辩全流程.md", "交互课:docs/course/jinrong-overview/index.html", "仓库:JinRong · 分支 merger", ], "预留 Q&A:适当性、审计、问数 guard、顾问线规划。", ), ] for title, bullets, notes in deck: _add_section_slide(prs, title, bullets, notes) OUT.parent.mkdir(parents=True, exist_ok=True) prs.save(str(OUT)) print(f"Wrote {OUT} ({len(prs.slides)} slides)") if __name__ == "__main__": main()