AI 专利分析多智能体系统:chat-first 对话入口 + 工作台可视化,支持专利无效性检索与侵权取证(EOU)两种分析。产出为律师工作底稿,每条结论带逐字引用、可溯源、可核验。
无效检索
全景检索状态机
检索过程建模为状态机,状态包括:INIT(解析完成,生成初始计划)→ PLAN(查询计划 k 版)→ EXECUTE(并行多通道召回)→ ASSESS(要素级评估矩阵更新)→ GAP(缺口识别)→ 循环回到 PLAN,直至满足终止条件。终止条件三选一:全部独立权利要求都存在至少一个 §102 候选或完整 §103 组合;迭代收益递减(新增覆盖要素数连续两轮为 0);预算耗尽(时间/ token / 调用数)。状态机每轮更新黑板上的覆盖矩阵 M[element, reference],该矩阵既是算法状态也是向用户展示检索范围的可视化数据源(对应官网:可以看到已经检索了什么)
覆盖率保障机制
全景是渐近目标,工程上用四个机制逼近。机制一,多视角查询:同一要素至少从术语、功能、问题—解决方案三个视角各生成一组查询,防止单一表述造成的检索盲区。机制二,跨域类比通道:对每个要素抽取功能语义向量(what it does,剥离领域名词),在去掉本领域 CPC 过滤的条件下做向量检索,专门召回类比文献——针对 Stilta 披露的第一类失败模式。机制三,组合优先搜索:组合求解器优先选择能覆盖最多未覆盖要素的文献(集合覆盖贪心 + 局部替换),使 §103 路径尽早闭合。机制四,不确定性上报:当某要素在 N 轮后仍无覆盖,将其标记为需要专家判断的硬缺口并在报告中显式披露,而非静默放弃——与 Stilta 的透明性原则一致。
可引用结果对象(CitableFinding)
系统一切对外输出均以 CitableFinding 为原子:结构为(结论文本,要素引用,证据引用列表,论证类型 §102/§103/DoE,置信区间标注,生成模型与版本,核验状态)。每条证据引用(EvidenceCitation)包含:文献 ID 与类型、定位(页码 + 段落 + 字符级 span 的 PDF 坐标)、逐字引文(verbatim quote)、公开日与合规说明、存档链接、核验结果(逐字匹配通过/警告)。核验规则:引文必须与源文件 OCR/文本层完全一致(允许空白差异),不一致即打回重新生成;日期不合规(晚于优先权日且非 §102(b) 类例外)的引用不得进入 §102/§103 论证。该对象设计直接对标官网承诺:引文逐字、直链源 PDF 页面、数秒核验。
侵权分析
第一,检索目标从文献域转向产品域:Product Discovery Agent 以要素关键词 + 技术领域在网页/电商/文档/视频/代码域检索竞品,输出候选公司-产品对,并按与全部要素的联合相关性排序(对应官网:为单个专利或整个组合发现谁在使用你的专利)。第二,证据采集器增加网页存档时间戳与版本追踪,产品页变化触发增量重评。第三,映射矩阵按三档解释分别计算:M_interpretation[element, product, evidence],claim chart 视图允许切换解释档并高亮结论翻转的要素(这是 Stilta 2026-06 功能的扩展设计)。输出物包含:逐产品 claim chart、缺口清单、证据充分性评级、监控订阅建议。
图表索引
| # | 图表 | 位置 |
|---|---|---|
| 图 1-1 | 系统分层架构图 | §1.1 |
| 图 1-2 | 黑板中心数据流图 | §1.2 |
| 图 1-3 | 部署视图 | §1.3 |
| 图 2-1 | Run 运行状态机 | §2.1 |
| 图 2-2 | 事件总线扇出 | §2.5 |
| 图 2-3 | LLM 调用容错链 | §2.4.1 |
| 图 2-4 | 熔断器状态机 | §2.8.1 |
| 图 3-1 | 端到端时序图(对话发起分析) | §3.1 |
| 图 3-2 | 无效性分析主流程 | §3.2 |
| 图 3-3 | 侵权分析主流程 | §3.3 |
| 图 3-4 | Chat 意图路由流程 | §3.4 |
| 图 3-5 | 引用核验与降级流程 | §3.5 |
| 图 3-6 | 检查点恢复流程 | §3.6 |
| 图 3-7 | 前端 SSE 事件处理流 | §3.7 |
第一部分 架构设计
1.1 系统分层架构
五层架构:前端展现层 → API 网关层 → 编排层 → 智能体层 → 资源层,横切企业级关注点。
graph TB
subgraph FE["前端展现层 · Vue3 SPA (Vite 构建, 后端 :8000 托管 dist)"]
Shell["ShellView 双栏外壳<br/>路由 / 与 /run/:id"]
ChatP["ChatPanel 左1/3对话流<br/>消息卡/智能体直播/会话抽屉/📎上传"]
WB["工作台 右2/3<br/>Overview · PriorArt · ClaimChart · Findings"]
i18n["i18n 中英双语"]
APIJ["api.js<br/>fetch封装(X-User-Id) + SSE消费"]
Shell --> ChatP
Shell --> WB
ChatP --> APIJ
WB --> APIJ
end
subgraph GW["API 网关层 · FastAPI"]
MW["中间件链<br/>RequestContext(请求ID/用户,最外层) → RateLimit(令牌桶) → CORS"]
R1["routers/chat 对话SSE"]
R2["routers/runs 分析CRUD+steer+resume"]
R3["routers/conversations 会话+上传"]
R4["routers/figures 附图代理"]
R5["routers/stream 运行SSE + health/metrics/probe"]
MW --> R1 & R2 & R3 & R4 & R5
end
subgraph ORCH["编排层"]
ORC["Orchestrator 黑板状态机<br/>invalidity / infringement 两条流水线"]
EVT["EventBus 进程内发布订阅<br/>+2000条/run事件历史"]
CTX["_active_ctx 运行态上下文<br/>(steering 收件箱)"]
ORC --> EVT
end
subgraph AGENTS["智能体层 · BaseAgent(预算护栏+步骤事件+法域注入)"]
A1["IntentAgent"]
A2["ClaimParser"]
A3["SearchStrategist"]
A4["EvidenceHunter"]
A5["ElementMapper"]
A6["CitationVerifier"]
A7["InvalidityReasoner"]
A8["ProductDiscovery"]
A9["DeepSearchAgent"]
A10["ReportGenerator"]
end
subgraph RES["资源层"]
C1["GLMClient glm-5.3-flashx<br/>chat/chat_json/chat_stream"]
C2["WebSearchClient GLM web_search"]
C3["ZhihuiyaClient 智慧芽<br/>P002/P008/P069/P018/P012/P015/P021"]
DB[("SQLite aiosqlite<br/>runs/agent_steps/conversations<br/>uploads/chat_messages/findings")]
REDIS[("Redis Stream<br/>wf:llm_tokens")]
end
subgraph ENT["企业级横切 · enterprise/"]
E1["resilience 重试+熔断"]
E2["metrics Prometheus"]
E3["middleware 限流/请求上下文"]
E4["token_metrics token计量"]
E5["logging_config JSON日志+contextvars"]
end
subgraph EXT["外部服务"]
GLM["智谱 bigmodel API"]
ZHY["智慧芽 connect.zhihuiya.com"]
end
APIJ -->|"REST + SSE"| MW
R1 & R2 --> ORC
ORC --> AGENTS
AGENTS --> C1 & C2 & C3
C1 & C2 -->|"resilient_call"| GLM
C3 -->|"resilient_call"| ZHY
ORC --> DB
E4 --> REDIS
R5 --> EVT
EVT -->|"SSE /runs/:id/events"| APIJ
E1 -.包裹.-> C1 & C2 & C3
E2 -.采集.-> C1 & C2 & C3 & GW
要点
- 中间件执行顺序(Starlette 后添加者最外层):
RequestContextMiddleware(request_id/user_id contextvars)→RateLimitMiddleware(仅/api路径,按客户端 IP 令牌桶)→CORS→ 路由。 - 单实例进程内架构:EventBus、
_active_ctx、token 内存聚合均为内存态;水平扩展需引入 Redis Pub/Sub(见 §4 DESIGN.md 限制)。
1.2 核心架构模式:黑板 + 事件溯源
智能体之间不直接调用,全部通过黑板(Run.blackboard JSON 列)读写协作;每次可观察状态迁移发布具名事件,前端凭事件流驱动 UI——事件既是实时通道,也是可回放的执行日志。
graph LR
subgraph AG["智能体(只读写黑板)"]
CP["ClaimParser"]
SS["SearchStrategist"]
EH["EvidenceHunter"]
EM["ElementMapper"]
CV["CitationVerifier"]
IR["InvalidityReasoner"]
PD["ProductDiscovery"]
RG["ReportGenerator"]
end
BB[("黑板 Blackboard<br/>elements / queries / references<br/>mappings / coverage / gaps<br/>combinations / findings / report")]
CP -->|"写 elements"| BB
SS -->|"读 elements 写 queries"| BB
EH -->|"读 queries 写 references"| BB
EM -->|"读 elements×references 写 mappings"| BB
CV -->|"改写 mappings.verify"| BB
IR -->|"读 mappings 写 coverage/gaps/combinations"| BB
PD -->|"写 product_leads"| BB
RG -->|"写 report"| BB
BB -->|"检查点 _checkpoint()"| DB[("SQLite runs.blackboard")]
BB -->|"emit()"| EVT["EventBus → SSE"]
黑板带来的架构收益
- 可恢复:黑板逐文献落库即检查点;恢复时以
mapped_ref_ids差集跳过已完成工作。 - 可观测:任一时刻 GET
/runs/{id}/blackboard得到全量中间态。 - 可插拔:流水线步骤是编排器里的普通 await 调用,增删步骤不改智能体。
四条架构决策记录(ADR 摘要)
| 决策 | 选择 | 备选与否决理由 |
|---|---|---|
| 智能体协作模型 | 共享黑板 | 消息传递:中间态难持久化、难整体恢复 |
| 映射调用粒度 | 每文献 1 次 LLM 覆盖全部要素 | 每要素 1 次:丢失全文上下文、调用数 = 要素×文献 |
| §103 组合搜索 | 确定性贪心集合覆盖 + LLM 仅写动机论证 | 纯 LLM 搜索:不可复现不可测试 |
| 实时通道 | 进程内 EventBus + SSE(历史回放) | WebSocket:双向能力冗余,SSE 足够且自动重连 |
1.3 部署视图
graph TB
subgraph HOST["单机部署(演示形态)"]
UV["uvicorn :8000<br/>app.main:app"]
subgraph APP["FastAPI 进程"]
API["/api/* 路由"]
SSE["SSE 长连接"]
STATIC["StaticFiles 托管 frontend/dist<br/>+ SPA 404 回退 index.html"]
BG["asyncio.create_task / BackgroundTasks<br/>后台运行 Orchestrator"]
end
SQLITE[("lawsight.db")]
LOG[("logs/app.log JSON")]
end
subgraph OPT["可选组件"]
R[("Redis :6379<br/>不可用时自动降级为日志告警")]
PROM["Prometheus 抓取 /api/metrics"]
end
BROWSER["浏览器"] -->|"HTTP + SSE"| UV
UV --> SQLITE
UV --> LOG
UV -.XADD token流水.-> R
PROM -.抓取.-> UV
UV -->|"出网 HTTPS"| BIGMODEL["open.bigmodel.cn"]
UV -->|"出网 HTTPS"| ZHY["connect.zhihuiya.com"]
开发态:前端 bun run dev(:5173) 直连 :8000(CORS 白名单含 5173);生产态:单服务 :8000 同时供 API 与静态资源,深链 /run/{id} 由 SPA 回退处理。
第二部分 详细设计
2.1 编排器与运行状态机(orchestrator.py)
Run 状态机:
stateDiagram-v2
[*] --> created: POST /api/runs 或对话自动创建
created --> running: /start 或对话槽位齐自动启动
running --> awaiting_review: 流水线正常完成(99%)
running --> stopped: steer stop → ctx.stop_requested
running --> failed: 异常(bb.error + run_failed事件)
stopped --> running: POST /resume 检查点恢复
failed --> running: POST /resume 检查点恢复
awaiting_review --> [*]: 律师审阅 / steer 引导
执行入口 execute() 的统一框架:
register_ctx登记 steering 收件箱 +matter_var设 run_id(token 计量按 matter 归集)。- 置
running、发run_started事件。 - 按 module 分派
run_invalidity()/run_infringement()。 - finally:写终态(
stopped/awaiting_review/failed)、llm_calls、token_usage、黑板整体落库、发run_completed、drop_ctx。
构造函数的恢复语义:黑板 setdefault 全部键;resolve_domain(jurisdictions, params.law_domain) 决定法域(US 默认,CN/EP 可选),law_prompt_block 生成注入所有系统提示的法定依据块。
进度上报协议 _progress(pct, stage, detail):写黑板 progress → _checkpoint() 落库 → 发 progress 事件。进度规划:
| 阶段 | 无效性 | 侵权 |
|---|---|---|
| 启动/专利加载/权利要求解析 | 5 / 10 / 20 | 5 / 10 / 20 |
| 检索计划(逐要素 30→44)/ 文献抓取 | 30~44 / 55 | —(产品发现 35) |
| 网页证据采集 | — | 55 |
| 逐要素比对(逐文献/证据 55→88) | 55~88 | 55~88 |
| 引用核验 / 覆盖与组合(缺口分析) | 88 / 92 | 88 / 92 |
| 报告生成 / 收尾 | 95 / 99 | 95 / 99 |
检查点策略:恢复场景跳过解析(黑板已有 elements);映射循环内每文献 _checkpoint();检索异常仅发 log 事件不中断;_checkpoint 自身失败静默(不中断分析)。
_backfill_why_relevant:映射完成后为每篇文献生成「公开权N·要素M…:首个 rationale」的证据表说明列,无命中时回退标题。
2.2 黑板数据结构(事实 Schema)
无强校验,靠编排器
setdefault与智能体约定维护。字段名即 API 契约。
{
"module": "invalidity|infringement",
"law_domain": "US|CN|EP",
"round": 1, // 当前 gap 轮次
"patent": { // load_patent 产物
"pn": "US12003836B2", "title": "", "abstract": "",
"patent_id": "智慧芽ID", "assignee": "", "pbdt": "公开日(检索截止)", "claims": [{"num":"1","text":"..."}]
},
"elements": [{ // ClaimParser 产物
"id": "md5(claim:order)[:12]", "claim_num": "1", "order": 1,
"text": "要素原文 verbatim",
"interp_broad": "…", "interp_narrow": "…", "interp_proposed": "…", // 三档解释
"key_terms": ["中英关键词≤8"], "function_summary": "剥离领域名词的功能语义",
"is_112f": false
}],
"queries": [{ "element_id": "…", "perspective": "terms|functional|problem_solution|eou",
"query": "…", "rationale": "…", "round": 1 }],
"references": [{ // 统一文献模型(三来源)
"id": "md5(pn|url)[:12]", "source": "zhihuiya_patent|web|user_added|user_upload",
"pn": "", "url": "", "title": "", "assignee": "", "pbdt": "", "apdt": "",
"relevancy": "", "note": "", "text": "全文(claims≤9000|abstract≤4000|web≤6000)",
"text_kind": "claims|abstract|web_page|user_supplied|user_upload",
"why_relevant": "回填", "figure": {"url": "签名URL", "ts": 0}
}],
"mappings": [{ "element_id": "", "ref_id": "", "verdict": "met|not_met|uncertain",
"quote": "逐字摘录", "locus": "权利要求1|网页正文",
"rationale": "", "verify": "pass|warn|fail", "verify_reason": "" }],
"coverage": { "<element_id>": [{"ref_id":"","quote":"","locus":""}] }, // 仅 met 且 verify∈{pass,warn}
"gaps": ["<element_id>"],
"combinations": [{ "primary_ref": "", "secondary_refs": [], "covered_elements": [],
"missing_elements": [], "motivation": "", "weakness": "",
"confidence": "low|medium|high" }],
"s102_ref": "<ref_id>|null",
"product_leads": [{ "company": "", "product": "", "evidence_hint": "",
"keywords_matched": [], "confidence": "" }],
"findings": [{ "element_id": "", "reasoning_type": "s102|s103|gap|evidence_of_use",
"statement": "", "confidence": "high|medium|low", "verified": true,
"citations": [{"ref_id":"","quote":"","pn":"","url":"","locus":""}], "score": 0.0 }],
"report": { "summary": "", "conclusions": [{"claim":"","basis":"","confidence":""}],
"risks": [], "next_actions": [] },
"progress": { "pct": 0, "stage": "", "detail": "" },
"token_usage": { "calls": 0, "in": 0, "out": 0, "total": 0 },
"active_interpretation": "interp_proposed", // steer 写入
"focused_elements": [] // steer 写入
}
2.3 智能体详细设计
BaseAgent 公共契约(agents/base.py):
llm_json / llm_text:先查budget_left()(params.llm_max_calls_per_run,默认 60,超限抛RuntimeError)→ 计数 + 发log事件(purpose + 第 N 次调用)→stage_var注入计量 → 系统提示前缀law_block。step(action, detail, status):发agent_step事件 + 结构化日志。AgentContext:run_id / module / bb / params / llm_calls / stop_requested / notes。
| 智能体 | 输入 | 输出 | 提示词要点 | 容错 |
|---|---|---|---|---|
| IntentAgent | 近 8 轮对话 + 当前消息 | {intent, module, patent_number, target_company, jurisdictions, invention_disclosure, notes, law_domain, missing_slots⊆[module,patent_input], conversational_reply} |
五意图;槽位跨轮累积;一次只追问一项 | LLM 失败→_rule_intent 关键词规则;专利号正则 (US\|CN\|EP\|WO\|JP\|KR\|DE\|GB\|FR)[\s-]?\d{5,}[\s-]?[A-Z]\d? 兜底;module 关键词规则兜底 |
| ClaimParser | 独权(≤max_independent_claims=2)+ 标题 | elements[](≤max_elements=8/项) | 一要素=主语+动作+对象;原文 verbatim;三档解释无前缀 | 空要素→加强指令重试 1 次;无效 claim/element 剔除 |
| SearchStrategist | elements(缺口轮仅 gaps) | plans[]:每要素 ≤3 视角查询 | terms/functional/problem_solution 三视角;缺口轮换表述 | element_id 白名单过滤 |
| EvidenceHunter.run_patents | 候选(pn 去重、剔自身) | references(带全文) | — | P018 批量(100/块)→逐条降级;P012 补齐无权要求文献;仍无文本则丢弃 |
| EvidenceHunter.run_web | 查询列表 | web references | — | 单查询失败跳过;URL 去重;content 为空丢弃 |
| ElementMapper.map_reference | elements × 单篇文献全文(≤9000) | mappings[](每要素恰一条) | 反幻觉硬约束:quote 逐字连续片段;无原文→not_met/uncertain | 未知 element_id 剔除;verdict 非法→uncertain;缺失补 not_met |
| CitationVerifier | mappings + refs_by_id | 打写 verify/verify_reason | 纯算法无 LLM | 详见 §3.5 |
| InvalidityReasoner | elements + mappings + refs | coverage/gaps/combinations/s102_ref | 动机论证 Graham/KSR + teaching-away 弱点,仅可引给定材料 | 动机 LLM 失败→degraded,组合保留空 motivation |
| ProductDiscovery | elements + 标题 + 目标公司 | leads ≤6 | 仅保留有真实公开信息支撑的线索 | LLM 失败→检索结果直出 4 条低置信线索 |
| DeepSearchAgent | question | {answer, sources≤14} | plan 2~4 查询;综合时标注 [n] 不编造 | 第2轮只补缺口;单查询失败继续 |
| ReportGenerator | 分析结果 | report{summary/conclusions/risks/next_actions} | 结论必须标注支撑文献;区分引用支撑 vs 推断 | — |
InvalidityReasoner 组合搜索算法(确定性,测试锁定):
build_coverage:仅verdict=met 且 verify∈{pass,warn}的映射计入覆盖。search_combinations:构建 ref→覆盖要素集合;按覆盖数排序取前 2 个主参考;束搜索(beam=24,agenda 安全阀 400)扩展次要文献;得分len(covered) − 0.15×(refs−1);max_refs=3;找到首个全覆盖组合即止。- §102 快查:任一单篇覆盖全部要素 →
s102_ref。 - 最优组合再调 LLM 写 motivation/weakness/confidence。
2.4 客户端层详细设计
2.4.1 GLMClient:推理模型适配 + JSON 容错链
flowchart TD
A["agent.llm_json(system, user)"] --> B{"budget_left()?"}
B -->|"否"| B1["抛 RuntimeError 预算耗尽"]
B -->|"是"| C["llm_calls+=1<br/>发log事件 / stage_var注入"]
C --> D["chat(): _chat_once"]
D --> E["resilient_call<br/>重试3次+退避+熔断(glm)"]
E --> F{"空内容 或<br/>finish_reason=length?"}
F -->|"是(思考耗尽预算)"| G["2× max_tokens 重试一次"]
F -->|"否"| H["parse_json_block"]
G --> H
H --> I{"解析成功?"}
I -->|"是"| J["返回结构化结果"]
I -->|"否"| K["4× max_tokens 再调一次"]
K --> L["parse_json_block 容错链"]
L --> M{"成功?"}
M -->|"是"| J
M -->|"否"| N["抛 ValueError"]
subgraph PCB["parse_json_block 容错链(顺序)"]
P1["① ```json 围栏提取"] --> P2["② 整体 json.loads"]
P2 --> P3["③ 首个平衡对象/数组截取"]
P3 --> P4["④ 内层裸引号重转义<br/>(引号后非结构符则视为内容)"]
P4 --> P5["⑤ 截断修复:回退裁切边界≤64<br/>去悬挂逗号/裸key(仅对象)→补全括号"]
end
L -.-> PCB
测试 test_core.py 锁定:围栏/前缀包裹/纯截断/裸引号/引号+截断叠加/数组尾元素保留/2×重试预算序列 [100,200]。
2.4.2 WebSearchClient:查询降级梯
search_std 对全词 AND——LLM 生成的 7~9 词长查询常 0 结果。0 结果时依次重试:去引号取前4词 → 前2词(源码注释标注 2026-11-02 实测验证)。已试集合去重。
2.4.3 ZhihuiyaClient:API 映射与批量
| 端点 | 用途 | 关键实现 |
|---|---|---|
P069 POST /search/patent/patent-search-pn |
pn→patent_id | 不支持逗号批量(实测),只取首条 |
P002 POST …/query-search-patent/v2 |
检索式通道 | query_text/limit/offset |
P008 POST …/semantic-search-patent/v2 |
语义通道(要素级稠密检索) | text + pbd_to 公开日截止(防 EPO 后公开) |
P018 GET /basic-patent-data/claim-data |
权利要求 HTML | 批量逗号连接(100/块);indep-clm class + num 属性拼接独立权利要求→纯文本 |
P012 GET /basic-patent-data/bibliography |
题录 | 嵌套 bibliographic_data 解析,EN 优先,扁平字段兜底 |
P015 GET …/forward-citation/v3 |
引用图扩展 | (客户端已备,流水线未用) |
P021 GET …/abstract-image |
摘要附图签名 URL | figures 路由专用 |
统一响应约定:status=false → ZhihuiyaError(error_msg, error_code)(如余额不足→运行快速失败)。
2.5 事件系统与 SSE 协议(events.py / stream.py)
graph LR
subgraph SRC["发布方"]
ORC["Orchestrator<br/>progress/run_*/steering"]
AG["Agents<br/>agent_step/log/…"]
CHT["chat.py<br/>chat_message"]
end
EMIT["emit(run_id, type, **data)"]
BUS["EventBus<br/>history≤2000/run(超裁至1000)<br/>subscribers: Set<Queue>"]
SSE1["GET /runs/:id/events<br/>from_index 回放 + 15s 心跳"]
FE["前端 subscribeRunEvents<br/>EventSource 自动重连"]
SRC --> EMIT --> BUS
BUS --> SSE1 --> FE
BUS --> HIST["GET /runs/:id/events/history<br/>(运维排查)"]
事件清单与载荷:
| 事件 | data 关键字段 | 前端行为 |
|---|---|---|
run_started |
module, patent_number | — |
progress |
pct, stage, detail | 进度条;stage 变化→对话直播行 |
agent_step |
agent, action, status, detail | status≠ok→直播行 |
log |
agent, message | 直播行(每次 LLM 调用可见) |
patent_loaded / elements_ready |
patent / elements | 后者刷新面板+直播 |
reference_added |
reference(完整文献) | 刷新+直播 |
coverage_updated |
coverage, gaps, coverage_pct | 刷新+直播 |
citations_verified |
verified, failed | — |
combinations_ready |
combinations, s102_ref | — |
products_discovered |
leads | — |
steering_applied |
action, payload, applied | — |
run_resumed |
checkpoint{elements,references,mappings} | — |
run_completed / run_failed |
status/llm_calls/token_usage / error | 刷新+直播 |
chat_message |
role, content | 运行内问答广播 |
Chat SSE(POST /api/chat,fetch 手解析):intent(意图+缺失槽位)、status(深搜进行中/降级)、delta(增量文本)、run_created/run_link(运行卡片/跳转)、error、done。
2.6 会话与意图路由详细设计(routers/chat.py)
会话模型:Conversation(标题自动 = 首条消息前 24 字;绑定 run 后改为「模块+专利号」)↔ Run 一对一可选;ChatMessage 双外键(conversation_id 为主,run_id 兼容);Upload 文本抽取入库(见 §2.7)。
消息处理主干(详见流程图 §3.4):
- 加载会话与绑定 Run;绑定 Run → 系统提示注入
_blackboard_digest(专利/要素/文献/覆盖/组合/报告摘要)成为「运行内引导对话」;未绑定 → 走意图路由。 - 意图分支各自持久化 assistant 回复并
return;user 消息统一在路由后落库。 - 常规问答:命中时效关键词(
最新/新闻/2025/2026/官网/价格…,运行内追加搜索/查一下/联网)且use_deep_search→ DeepSearchAgent(1 轮 5 条)→ 摘要以 system 消息注入 →glm_client.chat_stream增量输出。 - 上传兜底:
create_analysis且无专利号/交底书 → 最新上传文本(>80 字)即交底书。
文件上传(conversations.py):15MB 上限;txt/md/csv/json/xml/html 直接 UTF-8;PDF pypdf 前 120 页;DOCX zip 解 word/document.xml 正则剥标签;兜底可打印率 >0.85。会话已绑定 Run 时:抽取文本(≤9000 字)直接追加为 user_added 文献入比对池 + 发 reference_added。
2.7 数据模型与迁移(models.py / db.py)
六表:runs(blackboard JSON 列为核心)、agent_steps(run_id+created_at 索引,步骤回放)、conversations、uploads、chat_messages(conversation_id/run_id 双索引)、findings(run_id+created_at 索引,score 排序输出)。全部 32 位 hex UUID 主键、UTC 时间戳、级联删除。
轻量迁移 _migrate(无 Alembic):PRAGMA 检列补 chat_messages.conversation_id、runs/conversations.user_id(回填 demo-user + 建索引);旧 run 消息按 run_id 归并成会话,全局散消息归并为「历史对话」。
Finding(CitableFinding)落库规则:逐要素×覆盖映射 → s103(有组合时)或 s102,score=verify pass?1.0:0.6;硬缺口 → gap score 0.0 low;全覆盖组合 → s103 score 0.8,citations 列出主+次全部文献;侵权侧 evidence_of_use/gap。
2.8 企业级横切详细设计
2.8.1 弹性(resilience.py)
stateDiagram-v2
[*] --> closed: 初始
closed --> open: 连续失败≥5 或 half_open 再失败
open --> half_open: 距 opened_at ≥ reset_seconds(30s)
half_open --> closed: 探测成功(计数清零)
half_open --> open: 探测失败
resilient_call(name, fn→(result, ok)):先 breaker.allow()(open 直接抛 CircuitOpenError);尝试 ≤3 次,ok=False 视为提供方级失败(计熔断+重试);异常经 default_retriable(Timeout/Transport/408/425/429/5xx)判定,不可重试立即抛;退避 0.8×2^(n-1) + U(0,0.3) 秒。按依赖独立熔断器:glm / glm_websearch / zhihuiya。
2.8.2 其余横切
| 模块 | 设计 |
|---|---|
| metrics.py | 自实现 Counter/Summary(Prometheus 文本格式):lawsight_requests_total{method,path,status}、lawsight_request_latency_ms、lawsight_llm_calls_total{purpose,status}、lawsight_zhihuiya_calls_total{endpoint,status}、lawsight_websearch_calls_total、lawsight_runs_total、tokens in/out |
| middleware.py | RequestContext:X-Request-Id(或生成 16 位)+ X-User-Id(清洗 [^A-Za-z0-9_-])注入 contextvars;未捕获异常→500+request_id;响应头回写请求 ID。RateLimit:令牌桶 per-IP,RPM 可配,429 + X-RateLimit-Remaining |
| token_metrics.py | record(usage):内存按 matter(=run_id/conversation)聚合 + Prometheus 计数 + Redis Stream XADD wf:llm_tokens MAXLEN≈100000(9 字段 ts/user_id/matter_id/stage/name/model/tokens_in/out/total);Redis 不可用一次性标记降级、仅日志告警不阻塞 |
| logging_config.py | JSON 行格式:ts/level/logger/msg/request_id + extras(agent/run_id/step/duration_ms/component/status/detail);console+file 双通道;httpx/httpcore/uvicorn.access 压 WARNING |
2.9 前端详细设计(frontend/src)
组件树:
App.vue → ShellView(唯一视图, 路由 / 与 /run/:id)
├─ ChatPanel 消息流(user/assistant/event/upload/runcard 五种消息形态)
│ 会话抽屉(列表/新建/切换/删除) 📎上传 深搜开关 发送/停止键
├─ WelcomePanel 无运行时欢迎页+最近运行
├─ 工作台(activeRunId 存在时)
│ ├─ 头部:面包屑(PN+状态点) 进度条 三级页签 Secure/Privileged 徽章 停止/恢复按钮
│ ├─ OverviewPanel 进度/统计/token 用量
│ ├─ Evidence & Chart 子页签
│ │ ├─ PriorArtPanel 六列证据表:PN/标题/受让人/日期/类型/Why relevant + C1..Cn 覆盖条 + 附图缩略
│ │ └─ ClaimChartMatrix 矩阵:行=Limitation(按 CLAIM 分组) 列=文献;三色(Strong绿/Partial琥珀/Gap灰)
│ │ 过滤(关键词/日期基准/来源/强度) + Broad|Proposed|Narrow 档位切换(→steer)
│ └─ FindingsPanel report 摘要/结论/风险/行动 + 组合论证 + §102 候选
状态管理:无 Pinia,ShellView 本地 ref 集中持有 runs/sessions/activeConversation/activeRunId/run/bb/progress/interp,props 下发 + emit 上抛。数据新鲜度 = SSE 事件触发 refresh() + 6s setInterval 兜底;EventSource 自动重连,from_index 支持断线续播。
双 SSE 消费:运行事件用 EventSource(subscribeRunEvents 显式注册 15 种事件);对话用 fetch reader 手解析帧(streamChat,POST 体无法用 EventSource)。
2.10 配置项清单(config.py,.env 覆盖)
| 组 | 键(默认) |
|---|---|
| GLM | glm_api_key / glm_base_url / glm_model(glm-5.3-flashx) / glm_timeout(120) / glm_search_engine(search_std) / websearch_timeout(30) |
| 智慧芽 | zhihuiya_api_key / zhihuiya_base_url / zhihuiya_timeout(45) |
| 编排预算 | max_independent_claims(2) max_elements(8) max_gap_rounds(1) max_combination_refs(3) llm_max_calls_per_run(60)——均可被 Run.params 逐运行覆盖 |
| 弹性/限流 | breaker_failure_threshold(5) breaker_reset_seconds(30) retry_max_attempts(3) retry_backoff_base(0.8) rate_limit_rpm(600) |
| 基础设施 | database_url(sqlite+aiosqlite) redis_url redis_stream(wf:llm_tokens) redis_stream_max(100000) cors_origins static_frontend_dir |
| 观测 | log_level(INFO) log_file(logs/app.log) |
第三部分 流程图汇总
3.1 端到端时序:对话发起分析 → 工作台实时展示
sequenceDiagram
autonumber
actor U as 用户
participant CP as ChatPanel
participant CHAT as POST /api/chat
participant IA as IntentAgent
participant GLM as GLM(智谱)
participant ORC as Orchestrator(后台task)
participant ZHY as 智慧芽
participant BUS as EventBus
participant SV as ShellView/工作台
participant DB as SQLite
U->>CP: "分析 US12003836B2 的无效性"
CP->>CHAT: SSE 请求
CHAT->>IA: history + message
IA->>GLM: 意图识别+槽位抽取(llm_json)
GLM-->>IA: intent=create_analysis 槽位齐
CHAT->>DB: 建 Run(created)
CHAT->>ORC: asyncio.create_task(execute)
CHAT-->>CP: event: run_created(运行卡片)
CP->>SV: openRun(run_id) → 路由 /run/:id
SV->>BUS: EventSource /runs/:id/events
Note over ORC: 黑板流水线(见 §3.2)
ORC->>ZHY: P069→P018 加载专利/权利要求
ORC->>GLM: ClaimParser 等各智能体调用
ORC->>DB: _checkpoint() 逐文献落库
ORC->>BUS: progress/agent_step/reference_added/coverage_updated…
BUS-->>SV: SSE 事件
SV-->>CP: pushAgentEvent(智能体直播)
SV->>SV: refresh() 拉取 blackboard 更新面板
ORC->>DB: 终态 awaiting_review + findings 落库
ORC->>BUS: run_completed(status, llm_calls, token_usage)
SV->>SV: 刷新工作台(Claim Chart/Findings 就绪)
U->>SV: 律师引导: 换解释档/聚焦/补文献(steer)
3.2 无效性分析主流程(run_invalidity)
flowchart TD
S(["开始 run_invalidity"]) --> RES{"黑板已有 elements?<br/>(检查点恢复)"}
RES -->|"是"| SKIP["跳过解析<br/>进度20% 恢复模式"]
RES -->|"否"| LP["load_patent: P069解析pn→P018权利要求<br/>(失败降级P012题录; 无pn=交底书模式)"]
LP --> CP["ClaimParser: 要素拆解+三档解释<br/>+key_terms/function_summary (空结果重试1次)"]
SKIP --> ROUND["gap轮循环 round=1..1+max_gap_rounds"]
CP --> ROUND
ROUND --> SS["SearchStrategist: 每要素三视角查询<br/>(缺口轮仅gaps且换表述)"]
SS --> SRC["双通道检索: 每要素<br/>P008语义(建议解释,pbd_to截止,limit4)<br/>+ P002检索式(每查询limit3)"]
SRC --> DEDUP["pn去重 + 剔除本专利"]
DEDUP --> EH["EvidenceHunter: P018批量取权要求<br/>→P012批量补题录→仍无文本丢弃"]
EH --> MAPLOOP["映射循环: todo_refs = 全部文献 - mapped_ref_ids"]
MAPLOOP --> EM["ElementMapper: 每文献1次LLM覆盖全部要素<br/>三态判定+逐字quote (异常仅记log)"]
EM --> CKP["_checkpoint() 落库"]
CKP --> MAPLOOP
MAPLOOP -->|"全部完成"| CV["CitationVerifier: 逐字核验<br/>fail的met→uncertain (§3.5)"]
CV --> WHY["_backfill_why_relevant 回填证据表说明"]
WHY --> IR["InvalidityReasoner:<br/>coverage矩阵+gaps识别<br/>+§102单篇全查 +§103贪心集合覆盖组合<br/>+LLM写motivation(Graham/KSR)"]
IR --> GAP{"仍有 gaps 且<br/>未到轮次上限?"}
GAP -->|"是"| ROUND
GAP -->|"否"| RG["ReportGenerator: 执行摘要/结论/风险/行动"]
RG --> FD["_build_findings: s102/s103/gap 落 Finding 表"]
FD --> DONE(["进度99% → awaiting_review"])
STOP["steer stop → ctx.stop_requested"] -.打断循环.-> MAPLOOP
STOP -.打断.-> SRC
3.3 侵权分析主流程(run_infringement)
flowchart TD
S(["开始 run_infringement"]) --> LP["load_patent: P069→P018(无pn则交底书伪claim1)"]
LP --> CP["ClaimParser 要素拆解+三档解释 (20%)"]
CP --> PD["ProductDiscovery (35%):<br/>深搜候选公司产品(1轮5条)→LLM结构化leads≤6<br/>(失败→检索结果直出低置信线索)"]
PD --> Q["取前4条线索构造EOU查询:<br/>公司+产品+首要素key_terms前3"]
Q --> WEB["EvidenceHunter.run_web (55%):<br/>每查询GLM web_search 4条→URL去重入库"]
WEB --> MAP["映射循环(前8条web文献):<br/>ElementMapper逐证据比对→CitationVerifier核验"]
MAP --> GAPA["缺口分析: verdict=met的要素为覆盖<br/>其余=决定性缺口(gaps) → coverage_updated"]
GAPA --> RG["ReportGenerator.run_infringement:<br/>claim chart 满足度摘要+建议行动"]
RG --> FD["_build_findings: evidence_of_use / gap"]
FD --> DONE(["进度99% → awaiting_review"])
3.4 Chat 意图路由流程(routers/chat.py)
flowchart TD
IN(["POST /api/chat message"]) --> BIND{"会话已绑定 Run?"}
BIND -->|"是"| DIGEST["系统提示注入黑板摘要<br/>→ 运行内引导对话"]
BIND -->|"否"| IA["IntentAgent(近8轮历史+当前消息)<br/>失败→关键词规则兜底"]
IA --> UP{"create_analysis 且<br/>无pn无交底书?"}
UP -->|"有最新上传>80字"| FILL["上传文本=交底书<br/>移除patent_input缺失"]
UP -->|"否"| INT
FILL --> INT{"intent?"}
INT -->|"create_analysis"| MS{"missing_slots?"}
MS -->|"空(齐)"| CREATE["建Run + create_task启动编排器<br/>绑定会话(标题=模块+PN)<br/>SSE: run_created → 对话卡片直达工作台"]
MS -->|"module/patent_input"| ASK["追问一项(missing_slots)<br/>SSE: delta → done"]
INT -->|"resume_run"| RES{"找到 stopped/failed 且<br/>有elements检查点<br/>且无活跃ctx?"}
RES -->|"是"| RSM["Orchestrator续跑 + 绑定会话<br/>SSE: run_created + 检查点摘要回复"]
RES -->|"否"| RNO["回复无可恢复分析"]
INT -->|"view_run"| VIEW["按pn/最近定位Run → 绑定会话<br/>SSE: run_link → 跳工作台"]
INT -->|"general_qa/help"| QA
QA["常规问答: 命中时效关键词且use_deep_search?<br/>→DeepSearchAgent(1轮)→摘要注入system消息"]
QA --> STREAM["glm_client.chat_stream 增量输出<br/>SSE: delta… → done; 落库assistant消息"]
DIGEST --> QA
CREATE --> PERSIST["user消息统一落库"]
ASK --> PERSIST
RSM --> PERSIST
RNO --> PERSIST
VIEW --> PERSIST
3.5 引用核验与降级流程(CitationVerifier.verify)
flowchart TD
A(["verify(quote, ref.text)"]) --> B{"quote 非空?"}
B -->|"否"| FAIL(["fail: empty quote"])
B -->|"是"| N["归一化: 空白折叠→弯引号/中文标点转ASCII→casefold"]
N --> C{"归一化quote ⊆ 归一化原文?"}
C -->|"是"| PASS(["pass"])
C -->|"否"| F["模糊匹配: quote前缀窗口≤120字符<br/>与原文最长公共匹配 / 窗口长"]
F --> G{"比值 ≥ 0.97?"}
G -->|"是"| WARN(["warn: 轻微空白/标点漂移"])
G -->|"否"| FAIL2(["fail: quote not found (匹配度%)"])
PASS & WARN & FAIL2 --> H["写回 mapping.verify / verify_reason"]
H --> I{"verdict=met 且 verify=fail?"}
I -->|"是"| J["降级: met→uncertain<br/>rationale前缀'[引用核验未通过]'<br/>步骥标记degraded"]
I -->|"否"| K["保持"]
J & K --> L["下游: 仅 met且verify∈{pass,warn} 计入coverage<br/>Finding.verified = verify∈{pass,warn}<br/>score: pass=1.0 / warn=0.6"]
3.6 检查点恢复流程
flowchart TD
TRG(["触发: 工作台恢复按钮 / POST /resume / 对话说'继续分析'"]) --> V1{"Run 存在?<br/>user_id 匹配?"}
V1 -->|"否"| E404["404/403"]
V1 -->|"是"| V2{"status?"}
V2 -->|"awaiting_review"| E409a["409 已完成,直接引导"]
V2 -->|"running 或活跃ctx"| E409b["409 正在运行"]
V2 -->|"stopped/failed"| V3{"黑板有 elements?"}
V3 -->|"否"| E422["422 无检查点保护<br/>(解析未完成,不可恢复)"]
V3 -->|"是"| GO["status=running + 发run_resumed事件<br/>+ 后台启动Orchestrator"]
GO --> R1["恢复语义①: 跳过ClaimParser<br/>(直接用黑板elements,进度20%)"]
R1 --> R2["恢复语义②: 检索去重<br/>seen_pn=已有文献pn集合"]
R2 --> R3["恢复语义③: 映射差集<br/>todo_refs=全部文献-mapped_ref_ids"]
R3 --> R4["续跑: 检索→抓取→未完成映射→核验→推理→报告"]
3.7 前端 SSE 事件处理流(ShellView.attachSSE)
flowchart TD
A(["EventSource /runs/:id/events?from_index=N"]) --> B{"event.type"}
B -->|"agent_step 且 status≠ok"| C["ChatPanel 直播行: agent action status"]
B -->|"log"| D["ChatPanel 直播行: message<br/>(每次LLM调用/检索失败可见)"]
B -->|"elements_ready"| E["直播'已解析N个要素' + refresh()"]
B -->|"reference_added"| F["直播'新增文献PN' + refresh()"]
B -->|"coverage_updated"| G["直播'覆盖X% 缺口N个' + refresh()"]
B -->|"progress"| H{"stage 变化?"}
H -->|"是"| I["直播'阶段 PCT% · detail' + 更新进度条"]
H -->|"否"| J["仅更新进度条"]
I & J --> K{"pct ≥ 99?"}
K -->|"是"| L["refresh()"]
B -->|"run_completed / run_failed"| M["直播终态 + refresh()<br/>(拉取最终黑板与findings)"]
B -->|"其他(patent_loaded/…) "| N["忽略"]
C & D & E & F & G & L & M --> O["refresh(): 并行拉 getRun + blackboard<br/>→ 驱动 Overview/PriorArt/ClaimChart/Findings"]
P["6s setInterval 兜底轮询"] --> O
Q["EventSource onerror 自动重连<br/>from_index 断线续播"] --> A
附:可靠性设计速查
4. 系统概述
定位:用户在左侧对话中用自然语言发起分析(如"分析 US12003836B2 的无效性"),系统自动完成意图识别 → 槽位抽取 → 创建并启动多智能体分析流水线;右侧工作台实时展示证据表、Claim Chart 矩阵与可引用发现(Findings)。
核心设计理念(三条主线):
| 理念 | 落地方式 |
|---|---|
| 黑板状态机 | 一次分析(Run)的全部中间产物(要素/文献/映射/覆盖/组合/发现)收敛在一个 JSON 黑板中,既是智能体间的通信介质,也是检查点与持久化投影 |
| 两阶段人机协同 | 阶段一自主运行至 awaiting_review;阶段二律师通过 steer(换解释档/聚焦要素/补文献/停止)引导定向重跑 |
| 反幻觉硬约束 | LLM 摘录的引用必须能在文献原文中逐字核验(归一化包含匹配 + 前缀模糊匹配),核验失败自动降级 met → uncertain 并标注原因 |
技术栈:FastAPI + SQLAlchemy(async) + httpx(后端);Vue3 + Vite + Element Plus + ECharts(前端);GLM glm-5.3-flashx(推理模型)、GLM web_search(联网)、智慧芽开放平台(专利数据)。
4. 核心机制
4.1 黑板(Blackboard)
Run.blackboard(JSON 列)是系统的中枢数据结构:
patent{pn,title,claims} elements[](要素:text + 三档解释 + key_terms + function_summary + is_112f)
queries[](三视角检索计划) references[](文献:patent/web/user_added,含全文 text)
mappings[](要素×文献:verdict 三态 + quote + locus) coverage{element_id→[met 映射]}
gaps[](硬缺口要素) combinations[](§103 组合 + motivation 论证) s102_ref
product_leads[](侵权侧线索) findings[] report{} progress{pct,stage} token_usage law_domain
- 智能体只读写黑板,彼此不直接调用 → 流水线步骤可插拔、可恢复。
_checkpoint()在关键节点(逐文献映射后、进度更新时)整体落库,配合mapped_ref_ids跳过已完成文献 → 停止/失败后可从检查点续跑(POST /runs/{id}/resume或对话说"继续分析")。
4.2 事件总线与实时性
EventBus(进程内):emit(run_id, type, **data)→ 订阅队列 + 每 run 最多 2000 条历史(支持 SSE 断线from_index回放)。- 每个可观察状态迁移都是具名事件:
run_started / agent_step / progress / patent_loaded / elements_ready / reference_added / coverage_updated / combinations_ready / citations_verified / steering_applied / run_completed / run_failed等。 - SSE 两条通道:
GET /runs/{id}/events(运行事件,15s 心跳保活)与POST /chat(对话增量 delta/intent/run_created/run_link)。前端 EventSource 自动重连 + 6s 轮询兜底。
4.3 Chat-first 意图路由(routers/chat.py)
会话未绑定 Run 时,每条消息先过 IntentAgent:
| 意图 | 动作 |
|---|---|
create_analysis(槽位齐) |
建 Run + 后台启动 → SSE run_created → 前端卡片直达工作台,会话绑定 Run |
create_analysis(缺槽位) |
对话追问(一次只问一项),槽位跨轮累积 |
resume_run |
定位可恢复 Run → 检查点续跑 |
view_run |
按专利号/最近定位 Run → run_link 跳工作台 |
| 其他 | 常规问答(Run 内注入黑板摘要;命中时效关键词走 DeepSearchAgent 联网) |
鲁棒性:LLM 意图失败降级为关键词规则(模块词/视图词/恢复词表 + 专利号正则 _PN_RE 兜底);上传文件(txt/md/pdf/docx 解析文本)可作为交底书替代专利号;会话绑定 Run 后上传自动入对照文献池。
5. 智能体流水线
所有智能体继承 BaseAgent:llm_json/llm_text 强制走预算护栏(llm_max_calls_per_run,默认 60 次)、每次调用发 log 事件、系统提示统一前缀注入法域块(US §102/§103+Phillips / CN 专利法 22 条三步法 / EP Art.54/56,law_domains.py)。
5.1 无效性(invalidity)
load_patent(P069 解析 pn → P018 权利要求,失败降级 P012 题录)
→ ClaimParser 权利要求 → 要素拆解 + 三档解释(broad/proposed/narrow)+ key_terms/function_summary;空结果自动强指令重试
→ [gap 轮循环 ≤ 1+max_gap_rounds]
→ SearchStrategist 每要素三视角查询(terms/functional/problem_solution),缺口轮换表述
→ 双通道检索 P008 语义(要素建议解释为查询)+ P002 检索式;按 pn 去重、剔除本专利
→ EvidenceHunter P018/P012 批量抓取全文(2 次往返替代 N×2),批量失败降级逐条
→ ElementMapper 每文献一次 LLM 调用覆盖全部要素(保全文上下文),三态判定 + 逐字 quote;缺失要素补 not_met
→ CitationVerifier 逐字核验:归一化(空白/中英标点/casefold)包含匹配;前缀 97% 模糊 → warn;fail 则 met→uncertain
→ InvalidityReasoner 覆盖矩阵 + 缺口识别 + §102 单篇全查;§103 组合 = 带束界的贪心集合覆盖(score = 覆盖数 − 0.15×文献数)
最优组合再让 LLM 写 motivation to combine(Graham/KSR,含 teaching-away 弱点)
→ ReportGenerator 执行摘要/结论/风险/建议行动(仅可引用给定材料)
→ _build_findings 逐要素发现落库(s102/s103/gap,带 citations、score、verified)
5.2 侵权(infringement)
load_patent → ClaimParser
→ ProductDiscovery 深搜发现候选公司/产品线索(LLM 结构化,失败降级检索结果直出)
→ EvidenceHunter.run_web 目标公司×产品×要素关键词 → 网页证据(EOU)
→ ElementMapper + CitationVerifier(同上,对 web 证据)
→ 缺口分析(verdict=met 的要素为覆盖,其余为决定性缺口)
→ ReportGenerator(run_infringement)+ Findings(evidence_of_use / gap)
5.3 DeepSearchAgent(通用工具)
plan → search → reflect 循环(第 2 轮针对缺口补查、避免重复查询),供侵权产品发现、公开出版物的补充现有技术与对话联网问答使用。
6. 工程质量设计(enterprise/)
| 关注点 | 实现 |
|---|---|
| 弹性 | resilient_call:指数退避 + 抖动重试(默认 3 次);按依赖熔断器(closed→open→half_open,5 次失败/30s 复位),重试判定 default_retriable(超时/传输/408/425/429/5xx) |
| 限流 | 令牌桶中间件(按客户端 IP,RPM 可配),X-RateLimit-Remaining 响应头 |
| 观测 | JSON 结构化日志 + contextvars(request_id/user_id/matter_id/stage 全链路透传);Prometheus 文本指标(HTTP/LLM/智慧芽/检索调用、延迟);token 用量按 matter 聚合 + Redis Stream(wf:llm_tokens,不可用降级不阻塞) |
| 推理模型适配 | glm-5.3-flashx 常开思考、思考共享 max_tokens:空内容或 finish_reason=length → 2 倍预算重试;JSON 解析失败 → 4 倍再试;parse_json_block 容错链(围栏提取 → 平衡括号截取 → 内层引号重转义 → 截断修复裁切) |
| 检索适配 | search_std 对长查询 AND 全词易 0 结果 → 自动降级查询(去引号取前 4 词 → 前 2 词) |
| 预算护栏 | 每 run LLM 调用上限(默认 60);要素/独权/组合文献/缺口轮数均可 params 覆盖 |
7. 关键设计决策与权衡
- 黑板而非消息传递:智能体间解耦、天然可检查点、JSON 单列即可持久化(SQL 无 schema 演进负担);代价是黑板结构靠
setdefault约定维护,无强 schema 校验。 - 每文献一次映射调用(而非每要素):模型看到文献全文上下文,判定更准、调用数 = 文献数;配合逐字核验兜住幻觉。
- 集合覆盖用确定性算法(贪心+束界),动机论证用 LLM:组合搜索可复现可测试,法律修辞交给模型——各用所长。
- 降级链贯穿始终:意图→规则、批量→逐条、检索长查询→短查询、met→uncertain、Redis→日志。外部依赖故障表现为快速失败 + 明确错误,不阻塞服务。
- 进程内 EventBus + 单实例假设:简单可靠,但水平扩展需换 Redis Pub/Sub;运行态 ctx(steering inbox)也是内存态,重启丢失(靠黑板检查点补偿)。
- 单模型多角色:所有智能体共用 GLM 一个模型,靠系统提示(+法域块)分化角色;未做模型路由/分级。