跳到主要内容

AI 法务合同智能审核与管理平台:从 0 到 1 的工程实践

· 阅读需 36 分钟
Laby
全栈开发工程师

本文按 解决什么问题 → 用什么技术 → 怎么串 展开,与 Agent 四大工程Cursor Skill 流水线 同属一套工程化思维的不同战场。

这是我一个人从 0 到 1 搭建的企业级应用:Spring Boot 3 + Vue3,自带多租户、权限、文件、工作流等基础能力,业务上聚焦 AI 大模型平台法务合同智能审核与管理

它不是 Chat 套壳,而是把 文档进库、向量检索、Rerank 精排、Agent 工具调用、合同审核 Pipeline、Flowable 审批 串成可上线、可审计、可回归的系统。

全文按 解决什么问题 → 用什么技术 → 怎么串 展开,与 PDF 结构化入库、Universal RAG 编排、AgentScope Harness 三条主线对齐;不堆配置项和类名清单,但保留可落地的工程细节。


一、整体架构

1.1 业务闭环

上传 Word 合同 → POI 拆段 → AI 结合规则和标准条款审核 → 法务采纳/忽略 → Flowable 审批 → 导出 Word 报告。

并行还有:AI 知识库对话合同页 Agent 问答全局对话里编排建单

1.2 模块怎么分

模块干什么关键技术
system / infra用户、权限、租户、文件Spring Boot 基础模块
bpm人工审批Flowable,BPMN 自动部署
ai模型、知识库、对话、RAGAgentScope 2Qdrant、Embedding/Rerank HTTP
legal合同、意见、审核、AgentApache POIRedis Stream、AgentScope Harness
web-ele管理端页面Vue3 + Vben + Element Plus,SSE 流式

1.3 三条原则

  1. MySQL 是业务真相,Qdrant 只是检索索引。
  2. 机器活放应用 Pipeline 异步跑,Flowable 只从「法务处置意见」开始。
  3. AI 改库必须等人确认(提案 + Confirm)。

二、知识库:文档怎么结构化进库(用什么、怎么做)

2.1 要解决什么

RAG 入库若把 PDF 糊成一大段纯文本,会有三个典型问题:

  1. 结构丢失 — 标题层级、表格行列、图片说明全混在一起,向量语义模糊。
  2. 粒度不对 — 一块太大搜不准,一块太小断章取义。
  3. 检索单腿 — 只靠向量,法条编号、英文专有名词、表格单元格值容易漏召回。

所以入库不是「抽文本 → 切几段 → embed」,而是 解析成结构化元素 → 按块类型分片 → 三列文本分离 → 异步写 MySQL + Qdrant

2.2 入库整条链路(从上传到可检索)

用户上传文件 URL 后,后端走四步:

  1. 解析 — 解析路由器读文件字节,选引擎,得到统一 Markdown 全文 + elements 元素列表
  2. 文档落库 — 写文档表:全文、token 估算、解析引擎(MinerU/Docling/Tika…)、解析质量(high/low/degraded)、文档类型(PDF/Office/HTML/表格/邮件…)、入库状态 PENDING
  3. 异步分片 — 后台任务按解析质量选 层级分片器语义切分器,批量写分段表,状态变 EMBEDDING
  4. 向量化 — 对每个需要 embed 的分段调 Embedding HTTP,Qdrant upsert,回写 vectorId;全部成功则 SUCCESS

管理端能看到:用了哪个引擎、有没有降级、当前卡在解析还是向量化。向量和分段 以 MySQL 为准,Qdrant 只是索引,后面有健康对账可修。

2.3 解析路由:用什么引擎、怎么决策

我做了一层 DocumentParseRouter,所有引擎输出统一协议,内部映射成 结构化文档模型(元素 type、text、markdown、caption、页码、标题 level 等)。

AUTO 模式下的优先级(决策树):

文件类型优先引擎说明
html/htmJsoup 专用解析抽标题、正文块,直接产 elements
xlsx/xls/csv表格专用解析按 sheet/行产 table 元素
eml/msg邮件专用解析邮件头 + 正文结构化
pdfMinerU(可用时)版面分析,质量 high
pdf / docx/pptxDocling(MinerU 不可用或 Office)同样 HTTP JSON 协议
其它 / 全失败Apache Tika纯文本,质量 low,无 elements

还可 按扩展名覆盖引擎(例如强制某类走 Docling),或 关掉结构化总开关 全程 Tika,方便本地没 MinerU 时开发。

PDF → MinerU(主路径)

  • MinerU:开源 PDF 版面分析,识标题、段落、表格、公式,不是简单 OCR 抽字。
  • 部署:Docker 独立 HTTP 服务(项目提供 compose 模板),Java 把文件 Base64 POST 到 /api/v1/parse,超时约 5 分钟(大 PDF 可接受)。
  • 返回:JSON,含 markdownelements[];每个元素有 type(title/text/table/image/formula)、页码、层级、表格 Markdown 等。
  • 映射后标记引擎 MinerU、质量 high

Office docx/pptx → Docling

  • 同样 HTTP + 统一 JSON 协议,超时约 2 分钟
  • 适合 Office 原生结构;PDF 在 MinerU 挂了时也可走 Docling。

兜底与降级

  • MinerU/Docling 超时、报错、或 markdown 为空 → 自动 降级 Tika
  • 降级时在结果上标记 degraded,文档表仍记录原引擎尝试,方便运维知道「这份库质量打折了」。
  • Tika 只有纯文本 → 后面只能走 语义切分,没有 Parent-Child 和表格三索引。

专用解析(不全扔 Tika)

  • HTML:Jsoup 按 DOM 抽块。
  • Excel/CSV:按行列生成 table 元素,不丢表头。
  • 邮件:结构化头+正文,适合制度邮件、通知类知识。

2.4 分片:怎么切成可检索的块

分片器看解析结果是否 supportsStructuredChunking(高质量 elements):

  • 有 elementsHierarchicalKnowledgeChunker(层级分片器)
  • 无 elements(Tika 降级)SemanticTextSplitter(语义切分器),默认子块约 512 token、重叠 80 token,只有 child 没有 parent。

2.4.1 正文:Parent-Child + 标题路径

遍历 elements 时按 type 分支:

  • TITLE:刷新上一节的 parent;维护 headingStack(类似 DOM 大纲,level 深则 pop);新开一节 parent,buffer 清空。
  • TEXT:正文 append 进当前 parent buffer;同时把正文再切成多个 child 子块。
  • 一节结束时 flushParent:parent 存整节正文;child 存细粒度片段。

向量化文本和展示文本分离:

  • content — 给用户/LLM 看的原文。
  • embedText — 送进 Embedding 的文本,前面会拼 标题路径(如 第三章 保密义务 > 违约责任\n正文…),让向量带上章节语义。
  • sparseText — 去掉 Markdown 符号后的纯文本,专供 MySQL FULLTEXT;入库时对 embedText 做 同一套归一化,保证检索词和库内格式一致。

Parent 默认 不向量化(只作上下文回填),child 一定 embed;需要也可打开 parent embed。

2.4.2 表格:三索引(查单元格的关键)

一张 table 元素会拆成最多三类块(可配置开关):

块类型生成方式典型问法
table_wholecaption + 整表 Markdown「这张表讲什么」「列有哪些」
table_row每一数据行 → 一行 JSON(列名: 值)「张三工资多少」「Age 列谁最大」
table_summary规则生成:「表格摘要:{caption},列字段包括 A、B、C,共 N 行」「概况一下这张表」

行级 JSON 的好处:用户问 某一格 时,向量落在单行上;FULLTEXT 也能命中列名和数值。

2.4.3 图片 / 公式

  • IMAGE:caption + description + 附带 text 拼成一块,blockType=image。
  • FORMULA:前缀 [公式] + 文本,独立块,避免和正文混向量。

每块还带 pageStart/pageEnd、headingPath,方便溯源和表格整表回填。

2.4.4 落库与向量 metadata

分段先 batch 插 parent,再插 child 并写 parentId 外键。

写 Qdrant 时 payload 带:knowledgeId、documentId、segmentId、tenantId、blockType、chunkLevel、headingPath 等;检索时用 knowledgeId + tenantId 过滤,避免跨库串结果。

2.5 Embedding 与 Qdrant 写入

  1. Embedding — 独立 HTTP 客户端,OpenAI 兼容 /embeddings;后台模型表配通义、智谱、Ollama 等,和 Chat 模型解耦。
  2. Qdrant — 官方 Java 客户端直连,Cosine 相似度;每个分段 embed 后 upsert,vectorId 回写分段表。
  3. 健康对账 — 对比「DB 有段但 Qdrant 没 point」「vectorId 空」「sparseText 空」;支持 dryRun 或 补 embed + 补 sparse 文本,不必整库重建。

三、RAG 检索与 Rerank(用什么、怎么做)

3.1 要解决什么

单向量 topK 在真实业务里会踩坑:

  • 用户 口语化、省略主语,双塔向量相似度排偏。
  • 法条号、英文实体、表格数字 向量弱、关键词强,只走 Dense 会漏。
  • Dense 和 Sparse 分数尺度不同,简单加权不稳定。
  • 排第一的块 未必真相关,直接喂 LLM 会 编造

所以我做了一套 Universal RAG 检索编排:意图 → 多查询 → Dense + Sparse → RRF → 块类型加权 → 宽召回 → Rerank → 上下文回填 → 无引用守卫。

3.2 一次提问的完整编排(逐步说)

假设角色绑了 knowledgeId,用户问一句话,后端大致走下面 pipeline(管理端召回诊断接口能看到每一步命中数):

Step 1 — 意图识别(规则优先,不调 LLM)

用正则把问题分成五类,后面 块类型加权Multi-Query 都依赖它:

意图触发特征(举例)检索侧效果
SECTION「第×章」「条款」「幻灯片」偏章节 parent/带 headingPath 的块
TABLE_OVERVIEW「总结」「有哪些」「讲什么」抬高 table_summary 分数
TABLE_CELL「多少」「Age」「工资」「哪一行」抬高 table_row,略抬 table_whole
ENTITY英文专名、长数字编号配合 Sparse 补召回
GENERAL其它默认融合

Step 2 — Multi-Query 扩展

  • 默认 规则扩展(classpath 中英对照词典 + 表格意图自动加「表格 / table」后缀)。
  • 例如问「保密期限」可能同时搜原句 + 英文替换变体;问「利润多少」会多出带 table 后缀的变体。
  • 最多 3 个 查询变体,控制成本。

Step 3 — 每个变体并行两路召回

  • Dense(Qdrant)

    • 变体 embed → 向量检索,metadata 过滤 knowledgeId + tenantId
    • 命中 vectorId 反查分段 id,得到有序 segmentId 列表。
  • Sparse(MySQL FULLTEXT)

    • 查询和入库共用 SparseTextNormalizer(去 #*|[] 等 Markdown 噪声,保留中英数字)。
    • 优先 MATCH(sparse_text, embed_text, content);索引不可用则降级 单列 sparse_text
    • 专补 条文编号、英文术语、表格里的数字和列名

Step 4 — RRF 融合(Reciprocal Rank Fusion)

  • 每个变体、每一路召回都是一个 带权排名列表
  • 默认 RRF k=60;Dense 权重 1.0,Sparse 权重 1.2(略偏向关键词,专有名词更稳)。
  • 多列表按 segmentId 倒数排名融合,不要求两路分数同尺度,比简单加权平均稳。

Step 5 — 块类型加权(Block Route Boost)

融合分算完后、Rerank 前,按 Step1 意图对 blockType 乘系数,例如:

  • TABLE_CELL 意图 → table_row ×1.3,table_whole ×1.1
  • TABLE_OVERVIEW 意图 → **table_summary ×1.4

这是 规则层精排,把「问表格却召回大段正文」压下去。

Step 6 — 宽召回截断

  • 用户最终要 topK(默认 8),先取 topK × 4、上限 40 条候选进 Rerank。
  • 相似度阈值默认 0.60(Rerank 失败降级时用)。

Step 7 — DashScope Rerank 交叉编码

  • 问题:Dense 是双塔,问题和文档分开 embed,口语问容易排偏。
  • 做法:用 阿里 DashScope 文本 Rerank HTTP(如 gte-rerank-v2),把 用户原句 与每条候选的 embedText 成对打分,取 topK。
  • 降级:Rerank 未配置或调用失败 → 用 RRF 分 + 阈值过滤,诊断里标记 degraded,不 silently 假装精排成功。

Step 8 — 无引用守卫(NoAnswerGuard)

  • 召回为空,或 最高分 < minAnswerScore(默认 0.45)不调 LLM
  • 策略可 strict(固定「知识库未找到相关内容」)/ hint(提示换问法)/ relaxed(仍让模型答)。
  • 角色 没绑知识库 时不拦截,避免误伤纯聊天。

Step 9 — 上下文回填(给 LLM 的不仅是命中那一小块)

结构化入库的价值在检索后兑现:

  • 命中 child → 把 parent 整节正文 拼进 expandedContent--- 分隔)。
  • 命中 table_row → 再附 同 headingPath 下的 table_whole 整表,模型看到列头+全表+命中行。
  • 最终 Reference 进 Prompt 用扩展后的内容,引用仍指向命中 segmentId,UI 可展示来源。

命中分段还会 retrievalCount++,方便分析哪些块常被用到。

3.3 和对话、法务审核怎么衔接

  • AI 对话:角色绑 knowledgeId → 走上述编排 → 片段包成 Reference 引用块 进 Prompt;SSE 响应可带 召回诊断(意图、变体、Dense/Sparse 命中数、是否 Rerank 降级、top 分数)。
  • 法务审核 Kernel:引标准条款库、规则库 复用同一套检索服务,不是法务再写一套搜索;Playbook 确定性规则 + RAG 引条款 + LLM 批审在同一条质量链上。

3.4 质量怎么守(不是上线后靠感觉)

  • 离线黄金集:Hit@K、MRR 等指标,AiRagEvalGate 卡门槛。
  • GitHub Actions CI:改分片、改 RRF 权重、改 Rerank、改意图规则 必须先过 Gate
  • 向量健康检查:Sparse 开了但历史数据 sparseText 空 → 体检可批量回填。
  • 把 RAG 当 可测工程,不是 prompt 玄学。

四、AI 大模型平台:对话与模型(用什么、怎么做)

4.1 要解决什么

AI 平台不是「调一个 Chat API」就完事,至少要同时满足:

  1. 多厂商 — 通义、DeepSeek、智谱、OpenAI、Claude、Gemini、Ollama 等,业务代码不能每个平台写一套 SDK。
  2. 多形态 — 同步/流式 SSE、推理模型的 thinking 流、带 Tool 的 Agent 循环。
  3. 多能力叠加 — 角色设定 + 多知识库 RAG + 联网搜索 + 附件,还要 无引用守卫 防幻觉。
  4. 可运维 — 多实例 Session 不丢、同一会话不能并发两条 SSE、模型 Key 可轮换。

所以我做了 「模型服务层 + 统一 LLM 客户端 + AgentScope Harness 双路径」:纯聊天走 LLM Client;角色挂了 Tool/MCP 走 Harness,底层仍是同一套 Model 工厂。

4.2 平台四层:怎么配、怎么串

存什么干什么
API Key平台、密钥、Base URL通义/DeepSeek/OpenAI 等接入点
模型模型名、temperature、maxTokens、类型(Chat/Embedding/Image)业务选 modelId,不碰密钥
角色systemMessage、knowledgeIds、toolIds、mcpClientNames能力包:知识库 + Tool + MCP
对话用户、roleId、modelId、maxContexts、温度等每次聊天实例;消息落 MySQL

对话创建时 快照 角色设定和模型参数,后面改角色不会 retroactive 改历史对话。

4.3 模型层:AgentScope 2 统一 Chat

用什么

  • 业务只依赖 AiLlmClient 接口call 同步 / stream 流式)。
  • 实现是 AgentScopeLlmClient,内部 AgentScopeModelFactory 按平台构造 AgentScope 原生 Model。
  • AiModelService 按 modelId 取客户端,Singleton 缓存(modelId + apiKey + 模型名),避免每次 new 连接。

支持的平台

  • 原生 SDK 路径:通义 DashScopeChatModel、Gemini、Anthropic Claude。
  • OpenAI 兼容路径:OpenAI、Azure、DeepSeek(专用 DeepSeekFormatter 抽 reasoning)、智谱(GLMFormatter)、硅基流动、月之暗面、百川、豆包、混元、星火、Grok 等 —— 统一 OpenAIChatModel + 不同 baseUrl/formatter。
  • 本地:Ollama。

怎么做(一次模型调用)

  1. 校验模型启用 + API Key 有效。
  2. 把业务 AiLlmRequest(messages、temperature、maxTokens)转成 AgentScope Msg 列表
  3. 同步model.stream(..., stream=false).blockLast() 取完整回复。
  4. 流式stream=true,逐 chunk 映射为 CONTENT / REASONING / DONE / ERROR 事件;DeepSeek 等推理内容走 REASONING 通道,前端可折叠展示。
  5. 失败带 重试(Harness 默认最多约 2 次);401 映射为「模型鉴权失败」类错误码。

Chat、写作、脑图、法务审核等 所有 LLM 调用 都走这条链,换平台只改后台配置。

4.4 Embedding / Rerank / 向量库:与 Chat 解耦

三类模型 分开配 modelId,互不绑死:

能力实现用途
EmbeddingHttpAiEmbeddingClient,OpenAI 兼容 /embeddings知识库入库、Dense 检索、合同段落语义搜
RerankDashScopeRerankClient HTTP第三节宽召回后的交叉编码精排
VectorStoreQdrantVectorStoreClient + 上面 Embedding向量 upsert/search,Cosine

知识库绑 embeddingModelId;Chat 模型可以另选更强的推理模型 —— 典型组合:小 embedding + 大 Chat。

4.5 一次「发消息」的完整链路(流式为主)

前端 POST /ai/chat/message/send-stream(SSE,text/event-stream),后端顺序:

  1. 鉴权 — 校验 conversation 归属当前用户。
  2. 并发锁conversationId 级 Redis 互斥,同一会话 同时只允许一条流(防连点/双 Tab 把 Agent 跑乱)。
  3. 拉历史 — MySQL 消息列表(后面按路径决定是否注入)。
  4. RAG — 角色若绑了多个 knowledgeId,逐个库 走第三节 Universal RAG,结果合并;可选写 召回诊断 进 assistant 消息。
  5. 联网搜索(可选) — 用户勾选 useSearch 时调博查等 HTTP 客户端,摘要若干网页。
  6. 落库 user 消息 — 内容、附件 URL、是否带上下文等。
  7. 预建 assistant 消息 — 空 content,先占 id,流式结束再回填。
  8. 无引用守卫 — 角色绑了知识库但 RAG 空/低分 → 直接写固定回复,不调 LLM(见第三节)。
  9. 选路径调模型 — 见 4.6 / 4.7。
  10. 流式回写 — 累加 content/reasoning buffer,首包可带 知识库引用列表 + 搜索来源;结束时 update assistant 行。

同步接口 /send 逻辑相同,只是最后一次性返回。

4.6 纯 LLM 路径(角色 没有 Tool / MCP)

什么时候走toolIdsmcpClientNames 都为空。

Prompt 怎么拼(includeHistory=true

  1. System — 对话快照的 systemMessage(来自角色)。
  2. History — 用户勾选「携带上下文」且对话配了 maxContexts 时:从后往前取 N 组 user+assistant 对;跳过 type=summary 的 Compaction 摘要行(避免和 Agent Session 重复)。
  3. Token 裁剪 — 在组数限制之上,再用 historyTokenBudget(默认约 32k token) 从尾部保留,交给 AgentMemoryPolicy 裁切。
  4. 当前 user 句 — 本次输入。
  5. Reference 块 — RAG 命中片段包成 <Reference>...</Reference>,以 额外 User 消息 注入(不污染 system)。
  6. WebSearch 块 — 同理 <WebSearch title url>...</WebSearch>
  7. Attachment 块 — 附件 URL 下载:文本直读;图片转 Base64,包进 <Attachment>

调用llmClient.stream(request) → SSE 推前端。

适合:普通知识库问答、写作助手、无法具身改系统的场景。

4.7 Agent 路径(角色 挂了 Tool 或 MCP)

什么时候走:角色配置了 toolIds(Spring Bean 名)或 mcpClientNames

和纯 LLM 的关键区别 — Session 优先

  • 构建 HarnessAgentstreamEvents 流式。
  • 拼 Prompt 时 includeHistory=false —— 不再把 MySQL 全量 history 灌进本次请求
  • ReAct 轨迹(多轮 Tool 调用、中间推理)存在 AgentScope Session;每轮只发:system(Harness 内)+ 当前 user + Reference/WebSearch/Attachment
  • 这样避免 DB history + Session 双轨 导致 token 翻倍、Tool 轮次错乱。

Agent 构建时做了什么

  1. Toolkit — 按 toolIds 从 Spring 容器取 Bean 注册;MCP 名通过 AgentScopeMcpToolRegistrar 连远程服务挂 Tool。
  2. Middleware — 默认 Trace(记录 Tool 名、耗时);可选 Compaction 摘要落库 Middleware。
  3. Session — Key = sessionKeyPrefix + aiChat + conversationId;见 4.8。
  4. 公共选项 — maxIters 约 12;Compaction 阈值约 12 万 token关闭 Sub-agent / Dynamic Skills(Chat 场景控复杂度)。
  5. 业务扩展 — 实现 AiChatHarnessSupport 接口可注入额外 Middleware 和 RuntimeContext(法务编排 Agent 走这条扩展缝,不用 fork Chat 代码)。

事件映射TEXT_BLOCK_DELTA → 正文 SSE;THINKING_BLOCK_DELTA → reasoning SSE;Tool 事件目前以日志/后续 SSE 扩展为主。

Agent 仍 先 RAG 再进循环;无引用守卫在 Agent 启动前拦截,避免 Tool+幻觉双风险。

4.8 Session 存储与多实例

AgentScope 2 RC 没有官方 Redis Session,我实现了 RedisBackedJsonSession

  • 本地 JsonSession 缓存 + 变更 增量 flush 到 Redis;TTL 默认 24 小时
  • SessionKey 带 workspace 哈希前缀,Chat / 合同 Agent / 编排 命名空间隔离
  • 生产 session-store=redis;开发可 workspace 或 memory。
  • AgentScopeSessionLock:conversationId / sessionId 分布式锁,配合流式守卫。

重启后:MySQL 仍有用户可见 transcript;Agent 运行时 从 Redis 恢复,可继续 Tool 多轮(Compaction 后更轻)。

4.9 和知识库、法务的边界

  • RAG:只在 Chat 发送前召回一次,片段 id 写入 assistant 消息,前端展示「引用了哪几段」。
  • 法务合同 Agent / 编排 Agent:独立 Harness 构建(第五节),但 Model 工厂、Session 工厂、Embedding 客户端 与 AI 平台共用。
  • 写作 / 脑图 / 图像:同样 getLlmClient / getImageClient,平台能力复用。

4.10 生产注意点

  • SSE 网关 关缓冲(Nginx proxy_buffering off),否则流式假死。
  • Chat 流式接口 用户级限流(如 60 秒 10 次),防刷模型配额。
  • 模型 Key 401 → 明确提示检查密钥,别让用户以为是业务 bug。
  • 角色绑多个知识库时,单库不存在会 skip 并 warn,不拖垮整次对话。

五、Agent 智能体(全章 · 用什么、怎么做)

这是项目另一核心。要讲清:三条 Agent 线各自解决什么、共用哪些 Harness 能力、写操作为什么安全

5.1 要解决什么

法务场景里「Agent」不是单一聊天机器人,而是 三种不同入口、三种 Tool 集、三种 SessionKey

线入口用户目标
合同页 Agent审核页侧栏 SSE对着 已落库的一份合同 问答、查段落、处置意见
编排 AgentAI 对话页 + 编排角色对话里 登记附件 → 分类 → 建单 → 跟踪
审核 Orchestrator(试点)审核 Pipeline 内长合同 批审顺序规划(Sub-agent),不是用户聊天

另外还有 Pipeline 自动审核(第五节末尾):用户不和 Agent 说话,走 Redis Stream + Kernel——和对话 Agent 并列,不要混为一谈。

共同约束:AI 改库必须可审计;探索模式下不能误触写 Tool;多 Tab / 多实例下 Session 不串、Confirm 可续。

5.2 用什么运行时(AgentScope 2 Harness)

AgentScope 2 Java Harnessagentscope-harness 2.0.0-RC1),和第四节 AI Chat 共用工厂:

  • ReAct 循环:Thought → Action(Tool) → Observation → …,默认 maxIters ≈ 12,防死循环。
  • 流式streamEvents 推 TextDelta、ThinkingDelta、RequireUserConfirm 等。
  • Compaction:Session token 超 约 12 万 自动压缩;可选 Middleware 把摘要落 MySQL。
  • PermissionEngine:按 Tool 名 ALLOW / DENY / ASK(法务有专门规则,见 5.6)。
  • Session:RedisBackedJsonSession(生产 redis),TTL 24h;Key 按业务命名空间隔离。
  • Pending Tool Recovery:合同 Agent 开启,实例重启后可恢复卡在 Tool 前的状态。

Chat / 合同 / 编排 / 审核 Orchestrator 同一套 AgentScopeSessionFactory + AgentScopeModelFactory,差异在 Tool、Prompt、Middleware、SessionKey。

5.3 合同页 Agent:一次问答的完整链路

入口:审核页侧栏,SSE 流式;请求带 contractId、sessionId(多 Tab)、message、answerMode、allowProposal

Step 1 — 门禁

  • 若该 session 还在 AwaitingConfirm → 拒绝新消息,必须先处理确认。
  • sessionId 级互斥锁 → 同时只跑一条 Agent 流(连点/双 Tab 保护)。
  • 空 sessionId 时后端生成 UUID。

Step 2 — 解析 SkillPack 与 Prompt

  • 合同类型 + CHAT 场景 解析 SkillPack:额外 Tool 名 + 技能 Markdown。
  • System Prompt 拼装(事实来源,避免模型瞎说「审核不存在」):
    • 合同类型绑定的 QA 角色设定(可读 ai_chat_role 配置);
    • 审核快照块:合同 id/标题/状态/轮次、意见总数(待处置/已采纳/已忽略)、高风险统计;
    • 情节记忆 <ContractMemory>(milestone/risk/decision/fact/compaction_summary);
    • 用户事实 <UserFacts>
    • 回答模式(BRIEF/STANDARD/DETAILED 指令 + maxTokens);
    • allowProposal=true,追加写操作规则(批量采纳、单条采纳、跳过段落等)。

Step 3 — 构建 HarnessAgent

  • Model:合同可指定 modelId,否则默认 Chat 模型。
  • Toolkit:只读 Tool 始终注册;提案模式再注册写 Tool + SkillPack 额外 Tool。
  • ToolExecutionContext:注入 contractId、tenantId、userId、sessionId、allowProposal;Tool 只从上下文取 id,禁止模型 hallucinate。
  • SessionKey = prefix + legalContract + contractId + sessionId(多 Tab 不串线)。
  • Workspace.../legal/{contractId};有 SkillPack 则 sync 技能文件并 开 Dynamic Skills
  • 安全开关disableSubagentsdisableShellTooldisableFilesystemTools
  • Middleware 顺序(有意义):
    1. Tool 上下文注入
    2. 租户 Middleware
    3. Trace → 步骤日志(DB/审计:调了哪些 Tool)
    4. SSE Middleware → tool_start / tool_end 旁路事件
    5. Compaction 摘要落库(可选)

Step 4 — 只发当前用户句

  • buildAgentMessages 仅一条 UserMessage(本轮问题);ReAct 历史在 Redis Session。
  • 同步接口 runSyncagent.call;流式 runStreamagent.streamEvents

Step 5 — SSE 合并输出

主流映射 Agent 事件 → 前端协议;旁路队列 500ms 心跳合并:

  • 正文 / reasoning delta
  • tool_start / tool_end(Middleware 写入 LegalAgentSseEventHolder
  • proposal 卡片(提案号、标题、payload)
  • confirm_required(Permission ASK 时)
  • 空心跳包保活连接

主流结束后再 tail drain;若进入 AwaitingConfirm,confirmResumeDrain 继续 poll 直到会话释放。
整流 超时约 90 秒;Confirm 注入 observe 超时约 30 秒

5.4 合同页 Tool 能力(读 vs 写)

只读 Tool(探索/提案模式均可)

Tool干什么
legal_get_contract_meta合同元数据、待处置意见数等
legal_search_paragraphs语义搜段落(Qdrant metadata 限 contractId,失败降级关键词)
legal_get_audit_opinions按轮次/风险/段落/状态查意见
legal_get_audit_report六章报告摘要
legal_search_knowledge同一 Universal RAG 搜标准条款库
legal_compare_audit_rounds对比多轮审核差异

写 Tool(仅 allowProposal=true,且 Permission 允许)

Tool行为
legal_adopt_opinion / legal_batch_adopt_pending_opinions用户对话 即授权,Tool 内 直接调业务 Service 采纳,不再弹二次 Confirm
legal_propose_adopt_opinion兼容旧名,行为同直接采纳
legal_propose_skip_paragraph仍走 提案表:生成 proposalNo → 前端卡片 → 用户 execute 才改段落跳过标记

探索模式(allowProposal=false)

  • Permission EXPLORE:写 Tool DENY;只读 Tool ALLOW。
  • 适合「只问合同、不改状态」。

提案模式(allowProposal=true)

  • Permission DEFAULT + 对只读/写 Tool 显式 ALLOW(含 Harness 内置 session_search,避免误 ASK)。
  • 采纳类:对话指令 = 授权,Prompt 里写死规则,减少 SSE 打断。
  • 跳过段落类:提案表审计仍保留(5 分钟过期 + 定时清理)。

5.5 Permission Confirm + 续流(工程难点,编排仍重度使用)

即使合同采纳多为直接执行,Harness Confirm 链路仍完整实现,用于 Permission ASK、跨实例恢复、编排确认:

框架层 — AgentScope Confirm

  • Tool 触发 ASK → RequireUserConfirmEvent → SSE confirm_required + confirmId。
  • 用户 POST confirm → 构造 ConfirmResultagent.observe(resumeMsg) 注回 仍在等待的 Harness(首条 SSE 不能断)。
  • 运行时注册表(JVM 内 PendingRun)+ Redis Pending 快照(跨实例 rebuild Agent)。

业务层 — 提案表

  • legal_agent_proposal:PENDING → execute/cancel/expired;编排批量建单、类型包、分类等 走这条
  • execute 仍经 LegalAgentProposalService,和 Permission 职责分开:Harness 管推理续流,提案表管审计与写库

断线续流三档

  1. 原 SSE 还活着observe 注入 Confirm,主流继续推。
  2. SSE 已断confirm-stream:先 observe;失败则 streamEvents 降级 重建 Agent 续推。
  3. 实例换了 → 从 Redis 快照 rebuild Agent(同 contractId/sessionId/allowProposal/answerMode)再 observe。

会话锁:AwaitingConfirm 期间 不 release sessionGuard,防止双开流。

5.6 记忆:四层 + Compaction

存什么技术谁读/谁写
WorkingReAct、Tool 中间态、Compaction 前上下文Redis SessionHarness 自动
Transcript用户可见问答MySQL 合同聊天表前端列表
Episodicmilestone / risk / decision / fact / compaction_summaryMySQL 情节记忆表Prompt <ContractMemory>;去重 hash
User Facts用户级可复用事实MySQL 用户事实表Prompt <UserFacts>

写入:每轮问答后 异步抽取(可配置 LLM 或启发式;默认启发式);定时任务 backfill 漏网;管理端可 CRUD。

读取:构建 System Prompt 时按 contractId(+ 可选 sessionId)取最近 最多 10 条 附录。

Compaction 落库:Middleware 在 Harness 压缩完成后写 type=compaction_summary 或聊天 summary 行;纯 LLM 读 history 时 跳过 summary,避免和 Session 重复(第四节已述)。

5.7 SkillPack:按合同类型动态能力

  • 后台配置:合同类型 × 场景(CHAT / AUDIT 等)→ 额外 Tool Bean 名 + 多份技能 Markdown。
  • 构建 Agent 时 LegalSkillPackSkillWriter 同步到 workspace/skills,Harness Dynamic Skills 加载。
  • 例:劳动合同多「保密/竞业」说明 + 专用 Tool;采购合同多「付款/验收」——不改 Java 发版

合同 Chat 与审核 Orchestrator 各读各场景 的 SkillPack(CHAT vs AUDIT)。

5.8 编排 Agent(第三条线 · AI 对话页)

入口:AI 模块对话页,角色 Tool 名以 legal_orchestration_ 开头 → 触发 LegalOrchestrationAiChatHarnessSupport 扩展第四节 Chat Harness(不 fork 一套 Chat 代码)。

解决:上传合同附件 不是上传即建单,而是可控状态机。

阶段(编排会话表 + 字典)

INIT → FILE_REGISTERED → CLASSIFY_PENDING → CLASSIFY_CONFIRMED → TYPE_PACKAGE_* → CREATE_PENDING → CONTRACTS_CREATED → TRACKING → CLOSED

主要 Tool

Tool作用
register_latest_attachments / register_files登记对话附件
propose_file_classification语义分类 → 提案
list_contract_types查可用合同类型
propose_type_package缺类型时提案条款包草稿
propose_create_contracts批量建合同 提案(须分类已确认)
list_user_contracts / get_contract_summary跟踪进度
preview_audit / get_audit_preview预览审核

Checkpoint JSON(LangGraph 思路):每阶段推进写 checkpointJson(phase、policy、preview 快照等);异常可 resumeSession 恢复,不必从头 LLM。

Tool 上下文LegalOrchestrationToolRuntimeContext 绑 conversationId/userId/tenantId;Holder 可放 Redis,多实例可见

写库规则:编排 Tool 只 propose;用户在前端 execute → LegalAgentProposalService / 编排 Executor 写库 → 落库合同走 同一 Pipelinecreate_source=对话)。

5.9 审核 Orchestrator 与 Pipeline(第四条「非对话」线)

Orchestrator(Harness + Sub-agent 试点)

  • 配置 LegalAuditOrchestratorAgentScopeConfig:只读 Tool 子集 + clause-batch-reviewer Sub-agent(maxIters 更小,3 个只读 Tool)。
  • 主 Agent 委派子 Agent 输出 条款类型 + 批审优先级 JSON;Feature Flag auditSubagentPilot 控制。
  • 合同 Chat Agent 仍 disableSubagents——权限和复杂度可控,Sub-agent 只在审核链试点。

Pipeline 自动审核(用户不和 Agent 说话)

  • 列表建单 → Redis Stream 投递 LegalContractAuditMessage
  • Consumer 集群消费 + LegalAuditConcurrencyGuard 租户并发上限(默认约 5 路),满则重试/失败。
  • TenantUtils.execute 保证租户上下文;进度写 Redis,前端轮询 + reasoning 展示。
  • 内核 LegalAuditKernel:Playbook 确定性规则 + 批调 LLM + 第三节 RAG 引条款 → JSON 意见 + 六章报告(Playbook 黄金集 CI 锁回归)。

边界:解析 + 首轮 AI 不在 BPMN;Flowable 从「法务处置意见」人工节点开始(第六节)。

5.10 三条线对照

维度合同 Chat Agent编排 AgentPipeline 审核
交互侧栏 SSE 多轮AI 对话页 SSE无对话,异步进度
SessionKeycontractId + sessionIdconversationId无 Harness 会话
写操作采纳直接 / 跳过提案全 propose + executeKernel 写意见表
Sub-agent关闭关闭Orchestrator 试点
RAGTool 搜知识库预览审核等Kernel 内嵌 RAG
记忆四层 + 快照CheckpointTrace 复用预览

5.11 和第四节的关系(一句话)

第四节管 模型与 Chat 运行时;本节管 法务三条业务 Agent 线如何在 Harness 上挂 Tool、Permission、记忆、提案与 Pipeline。Model/Session/Embedding 工厂共用,业务 Prompt + Tool + 门控 分离,换合同类型主要靠 SkillPack + 角色配置,不靠改核心代码。


六、法务合同业务(用什么、怎么做)

6.1 Word 解析:和知识库 PDF 是两条线

审合同 Word(docx)不用 MinerU,用 Apache POI(XWPF)

  • 读段落,稳定编号 p-1、p-2…
  • 条款树构建 + 表格块抽取
  • 段落表,供审核和 Agent 查

知识库 PDF 走 MinerU;合同 Word 走 POI——职责分开,避免把在审合同混进公司知识库。

6.2 审核 Pipeline

LegalContractProcessStarter 异步:

  1. 解析(POI)
  2. 首轮 AI(Kernel + RAG + LLM)
  3. 成功 → 启动 Flowable 人工阶段

** deliberately** 解析和 AI 不在 BPMN 图里;流程从「法务处置意见」开始。

6.3 Flowable BPM

  • 流程 Key:legal_contract_review
  • BPMN:开始 → 法务处置意见 → 审批 → 导出
  • 启动时自动部署 BPMN,旧定义可升级
  • 业务表单嵌入 Vue 审核页;Delegate 可 自动导出 Word

6.4 意见、报告、导出

  • AI 出 JSON 结构化意见(段落锚点、风险等级)
  • 六章 Markdown 报告 统一模板生成
  • 法务 采纳/忽略/撤销/批量;可 二轮 AI(仍走 Stream)
  • POI 或模板 导出 Word 报告

6.5 合同内语义搜段落

解析后对段落 Embedding 写 Qdrant(metadata 限 contractId),Agent Tool search_paragraphs 和用户问答共用,失败降级关键词。


七、前端(用什么、怎么做)

  • Vue3 + Vben + Element Plus,动态菜单与 RBAC 权限。
  • 审核页 review.vue:段落、意见、处置、BPM 时间线、侧栏 Agent SSE
  • SSEEventSource / fetch stream 收 delta、thinking、Confirm 卡片、提案卡片。
  • 多 Tab:每 Tab 独立 sessionId,和后端 SessionKey 一致。
  • AI 对话页:流式 + 知识库引用展示 + 编排 Confirm。

八、工程化与质量

  • Playbook 黄金集(法务审核 10 条)+ RAG 黄金集,Maven + GitHub Actions CI。
  • 生产 checklist:Session Redis、SSE 网关、Stream 租户上下文、连接池。
  • MinerU Docker、OnlyOffice 等部署文档。

九、我 0→1 交付范围

需求/架构/SDD、ai+legal 全量后端、BPMN、web-ele 页面、SQL 增量、评测 CI、部署文档。自研价值:解析路由+结构化分片、Universal RAG+Rerank、AgentScope 工程化、Pipeline 与 BPM 边界


十、收尾

以上是 AI 法务合同智能审核与管理平台 全模块介绍。

  • 文档/RAG:MinerU/Docling/Tika → 层级分片+表格三索引 → Embedding HTTP → Qdrant → Dense+FULLTEXT+RRF → DashScope Rerank → 无引用守卫。
  • AgentAgentScope 2 Harness + Redis Session + Tool/Middleware + 提案+Confirm 续流 + 记忆四层 + 三条线。
  • 法务POI 解析 + Kernel 审核 + Redis Stream + Flowable 审批。

下文按模块展开,便于按图索骥。


附录:模块 × 技术 × 一句话

模块用什么怎么做(一句话)
PDF 知识库MinerU Docker HTTPJSON elements → 层级分片 → Embedding → Qdrant
Office/HTML/表格/邮件Docling / Jsoup / 专用解析同协议或专用元素 → 分片
降级Apache Tika纯文本语义切分,质量 low
Embedding通义/智谱/OpenAI 兼容 HTTP批量 embed → Qdrant Cosine
Dense 检索Qdrant Java 客户端问题 embed + metadata 过滤
Sparse 检索MySQL FULLTEXT关键词列归一化查询
融合RRF多路排名合并
RerankDashScope gte-rerank-v2 HTTP宽召回后交叉编码精排
防幻觉无引用守卫低分/空召回不调 LLM
ChatAgentScope Model + SSE纯 LLM 裁 history;带 Tool 走 Harness
SessionRedis JsonSession 适配增量 flush + 锁 + TTL
合同 AgentHarness + Toolkit只读/提案 Tool,SkillPack,Middleware
Confirmobserve + 续流Pending 注册表 + Redis
记忆MySQL + Redis四层 + Compaction 摘要落库
编排 Agent独立 Tool + 状态机propose → execute,Checkpoint
合同 WordApache POI段落+条款+表格,不进 MinerU
审核异步Redis Stream租户并发上限,进度 Redis
审核内核Playbook + LLM + RAGJSON 意见 + 六章报告
BPMFlowable BPMN自动部署,仅人工节点
前端Vue3 SSEsessionId 多 Tab,Confirm/提案 UI

书面材料:架构设计 / 系统设计 / 实现索引见 delivery 目录。


十一、亮点补充(正文点到为止的细节补充)

下面这些是正文里点到为止、但值得单独展开的细节。讲法统一:业务问题 → 用什么 → 怎么做

11.1 合同问答「三种回答模式」

  • 问题:同一合同,法务有时要一句话结论,有时要逐条引用段落做详审。
  • 用什么:前端选 BRIEF / STANDARD / DETAILED,后端枚举绑不同 System 指令 + maxTokens(约 1k / 2k / 4k)。
  • 怎么做:Harness 和同步 LLM 两条路共用同一套 Prompt 助手;模式进 Session 快照,Confirm 续流后模式不丢

11.2 探索模式 vs 提案模式(Permission 分层)

  • 问题:用户只想查合同,不希望 Agent 误触「写意见/改段落」。
  • 用什么:AgentScope PermissionEngine + 自研规则:探索模式 EXPLORE + DENY 写 Tool;提案模式 DEFAULT + ALLOW 只读/写 Tool(用户开提案开关即授权采纳类操作)。
  • 怎么做allowProposal 进 Tool 上下文;跳过段落仍走提案表审计;Confirm/observe 链路保留给编排与跨实例续流。

11.3 预览审核复用(省钱又一致)

  • 问题:用户先「预览审核」再「正式落库」,不希望 LLM 对同一段落打两遍分。
  • 用什么LegalAuditPreviewReuseService + 段落级 dedupeKey(段落 id + 风险标题)。
  • 怎么做:正式审核前扫预览结果,命中则 跳过 Kernel 调用;Trace 里记 previewReuseCount,可量化节省。

11.4 审核全链路 Trace

  • 问题:Playbook + LLM + RAG 混跑,线上出问题很难复盘。
  • 用什么LegalAiTraceRecorder 结构化落库(阶段、Tool、RAG 命中、复用次数)。
  • 怎么做:Kernel 每个阶段写 Span;和 Playbook 黄金集 CI 对齐,失败用例可对照 Trace 定位

11.5 向量库健康检查 + 自愈

  • 问题:MySQL 有分片、Qdrant 有点位,运维误删或重建后容易 数量不一致
  • 用什么AiVectorStoreHealthService 对比 DB segment 与 Qdrant point,支持 dryRun / repair。
  • 怎么做:管理端一键体检;repair 按缺失向量 补 embed 再 upsert,比全量重建便宜。

11.6 双黄金集 CI(RAG + Playbook)

  • 问题:改检索或改 Playbook 不能靠手感。
  • 用什么:Maven 集成测试 + GitHub Actions;RAG 用 AiRagEvalGate,法务 Playbook 用 LegalPlaybookEvalGate(如 10 条、100% 门槛)。
  • 怎么做:改 prompt / 分片 / Playbook 规则 必须先过 Gate 才能合并,把「AI 质量」当工程指标。

11.7 异步审核租户并发闸

  • 问题:大合同审核耗时长,多租户同时跑会把 LLM 打满。
  • 用什么:Redis Stream 消费 + 租户级并发上限(默认约 5)+ 进度写 Redis。
  • 怎么做:超限任务排队或快速失败;SSE/轮询只看进度,不和 Harness Session 抢同一把锁

11.8 编排 Agent Checkpoint

  • 问题:多步法务编排(立案 → 审核 → 报告)中途失败要从断点续,不能从头 LLM。
  • 用什么:编排状态机 + JSON Checkpoint 持久化。
  • 怎么做:每步 propose → 用户 Confirm → execute 后写 Checkpoint;恢复时 只重放未完成步

11.9 查询意图驱动的 RAG

  • 问题:法条检索、表格问答、闲聊混在一个知识库里,单一 TopK 不准。
  • 用什么:意图枚举(如法条 / 表格 / 通用)→ 不同 召回路径、TopK、是否走 Rerank
  • 怎么做:意图识别后再 multi-query;表格块走 表格三索引;低置信走 NoAnswerGuard 直接拒答。

11.10 SSE 限流(生产防护)

  • 问题:流式接口被刷会拖垮 LLM 和 Redis Session。
  • 用什么:接口级 @RateLimiter,合同 Agent / Chat 按 用户 限流,审核触发按 租户 限流。
  • 怎么做:超限快速 429,保护 Session 锁和外部 Embedding 配额。

11.11 审核 Sub-agent 试点(只读子 Agent)

  • 问题:长合同一次塞满上下文,主 Agent 批审顺序不稳定。
  • 用什么:AgentScope SubagentDeclarationclause-batch-reviewer),子 Agent 仅 3 个只读 Tool,maxIters 更小。
  • 怎么做:主 Orchestrator 按段落批次 delegate 子 Agent 输出条款类型与优先级 JSON;Feature Flag 控制,合同 Chat Agent 仍禁用 Sub-agent(控复杂度和权限)。

十二、AgentScope 2 Java 能力地图(已用 / 可扩展 / 刻意不用)

版本:agentscope-harness 2.0.0-RC1。建议说法:「我们吃透了 Harness 工程化能力,RAG 自研因为官方 RAG API 在 RC 已 deprecated。」

12.1 已经在生产用的

能力我们怎么用的
HarnessAgent + ReAct合同 Agent、编排 Agent、带 Tool 的 AI Chat;streamEvents 推 SSE
Toolkit 注册Spring Bean 法务 Tool + HTTP 知识 Tool;按角色/SkillPack 动态装配
Permission + HITL探索 DENY 写 Tool;编排/ASK 场景 Confirm → observe 续推理;采纳类对话即授权
Middleware 链Trace、租户、Tool 上下文、Compaction 摘要落库、Confirm→SSE 桥接
Redis SessionJsonSession 增量 flush + TTL + 分布式锁;多 Tab sessionId 对齐
Context Compaction上下文超约 120k token 触发摘要压缩,摘要异步写 MySQL 聊天表
Dynamic SkillsSkillPack 同步到 workspace/skills,Harness 按场景加载
Model 工厂统一 DashScope/兼容 OpenAI;可配 重试 + fallback 模型
Sub-agent(试点)仅审核 Orchestrator:clause-batch-reviewer 只读批审
MCP 注册器AgentScopeMcpToolRegistrar 已写好;角色/SkillPack 配 mcpClientNames 即可挂远程 Tool

12.2 代码里能接、但默认没全开(可讲「下一步」)

能力状态简要说明
MCP Client基础设施就绪,需 laby.ai.mcp.servers.* 配置「Tool 不必全写 Java,Filesystem/Linear 等 MCP 服务配置即用」
Sub-agent 扩展审核试点 1 个;Chat Agent subagentsEnabled=false「长文档可拆条款分类/报告子 Agent,权限仍只读子集」
多模态 DataBlockSpec P2-3,Chat 附件图片「合同扫描件问答可走 Vision,当前以文本段落为主」
Pending Tool Recovery配置项 enablePendingToolRecovery「实例重启后恢复卡在 Confirm 前的 Tool 状态」
AgentScope 原生 RAG刻意不用(RC deprecated)「向量用 Qdrant Java Client + 自研 RRF/Rerank,更可控」

12.3 刻意不用的(体现架构判断)

  • Shell / Filesystem 内置 Tool:法务场景写盘风险高,全部关闭。
  • Sandbox 代码执行:非目标,合规与隔离成本大。
  • 合同 Chat 默认 Sub-agent:避免权限边界模糊;审核链单独试点。
  • Spring AI 栈:已全量 AgentScope + 自研 RAG,减少双栈维护。

12.4 与 Spring AI 的对比(架构选型)

一句话:「Spring AI 偏组件拼装;AgentScope 2 给的是 Agent 运行时——Session、Permission、Middleware、Compaction、Sub-agent。我们把 RAG 和向量_store 自研握在手里,Agent 交互和续流交给 Harness。」


十三、常见追问与简答

  1. 为什么不用 AgentScope 自带 RAG? — RC 已 deprecated;我们要 Parent-Child、表格三索引、RRF+Rerank+NoAnswerGuard,自研 Qdrant 链路更可测。
  2. Confirm 和提案表会不会重复? — Harness 管交互续流;提案表管审计与 execute 写库,职责分开。
  3. Redis Session 和 Chat 历史表关系? — Session 存 Agent 运行时(Tool 轮次、Compaction);MySQL 存用户可见消息与摘要,重启可重建。
  4. Playbook 和 LLM 谁说了算? — Playbook 确定性规则先跑;Kernel 对复杂段落 LLM+ RAG;黄金集 CI 锁回归。
  5. 多实例怎么保证一个合同只有一个 Agent 流? — SessionKey + Redis 分布式锁 + 前端 sessionId;超限 RateLimiter 防刷。
forum

评论区 / Comments