# 合同分析检索 **Repository Path**: tgh621/contract-analysis-retrieval ## Basic Information - **Project Name**: 合同分析检索 - **Description**: 这是一个智能体(agent)项目,基于langchain4j框架的实现。 旨在实现“合同”的智能分析,预期目标为:合同分析、合同检索、合同知识库建设、合同审核。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: develop - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-08-10 - **Last Updated**: 2026-08-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 合同分析检索智能体 (Contract Analysis Retrieval Agent) 基于 RAG(检索增强生成)的合同智能分析系统,支持 PDF 合同解析、语义检索、多合同对比、知识库构建及联合分析。 --- ## 一、架构设计 ### 1.1 整体架构 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 前端 (Vue 3) │ │ 聊天面板 │ 合同上传 │ 知识库管理 │ 多合同对比 │ 流式渲染 │ └──────────────────────────────┬──────────────────────────────────┘ │ HTTP/SSE (localhost:3000 → 8080) ┌──────────────────────────────┴──────────────────────────────────┐ │ 后端 (Spring Boot 3.2) │ │ │ │ ┌──────────┐ ┌─────────────────────────────────────────────┐ │ │ │Controller│ │ 工作流编排层 │ │ │ │ Chat │ │ Router ─→ Intent ─→ Workflow │ │ │ │ Contract │ │ │ │ ├─ ChatWorkflow │ │ │ │ Session │ │ │ LLM分类 ├─ SingleContractWorkflow│ │ │ └──────────┘ │ │ │ ├─ MultiContractWf │ │ │ │ │ │ ├─ KnowledgeBaseWf │ │ │ │ │ │ ├─ RagContractWf │ │ │ │ │ │ └─ DocumentUploadWf │ │ │ │ │ │ │ │ │ │ │ ┌──────┴──────┐ │ │ │ │ │ │ 节点链 │ │ │ │ │ │ │ RetrieveNode │ │ │ │ │ │ │ AnalyzeNode │ │ │ │ │ │ │ CompareNode │ │ │ │ │ │ │ ChatNode │ │ │ │ │ │ │ MergeContextNode│ │ │ │ │ │ └─────────────┘ │ │ │ │ └──────────────────────────────────────────┘ │ │ │ │ ┌─────────────────────────────────────────────────────────────┐ │ │ │ 服务层 │ │ │ │ ChatService │ PdfParsingService │ TextSplitterService │ │ │ │ SessionService │ VectorStoreService │ DocumentReconstruction│ │ └─────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ │ │ LLM 模型 │ │Embedding 模型│ │ Milvus 向量数据库 │ │ │ │ MiniMax M2 │ │ BGE-M3 │ │ (localhost:19530) │ │ │ │ (OpenAI API) │ │ (本地 ONNX) │ │ │ │ │ └──────────────┘ └──────────────┘ └──────────────────────┘ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 1.2 分层设计 | 层 | 职责 | 核心组件 | |---|---|---| | **控制器层** | 接收 HTTP 请求,参数校验,返回响应 | `ChatController`, `ContractController`, `SessionController` | | **编排层** | 意图分类、工作流路由、节点串联 | `Router`, `Workflow`, `WorkflowContext`, `SSEEmitterHelper` | | **节点层** | 独立的功能单元,可被多个工作流复用 | `RetrieveNode`, `AnalyzeNode`, `CompareNode`, `ChatNode`, `MergeContextNode` | | **服务层** | 提供原子能力:PDF解析、切分、向量化、存储、对话编排 | `ChatService`, `PdfParsingService`, `TextSplitterService`, `VectorStoreService`, `SessionService`, `DocumentReconstructionService` | | **模型层** | LLM 对话与 Embedding 向量化 | `ChatModelConfig`, `EmbeddingConfig` | --- ## 二、业务内容与功能说明 ### 2.1 功能概览 | 功能 | 说明 | 模式 | |---|---|---| | **闲聊对话** | 自由对话,支持上下文记忆 | 默认模式 | | **单合同分析** | 上传 PDF 合同,提问条款、风险、关键信息 | 合同模式 | | **多合同对比** | 上传多份合同,对比条款差异 | 合同模式(多文件) | | **知识库管理** | 上传 PDF 构建 RAG 知识库(如法律法规),支持多知识库 | RAG 模式 | | **RAG+合同联合分析** | 上传合同同时勾选知识库,结合两者进行分析 | RAG 模式 | ### 2.2 工作流一览 | 意图 | 工作流 | 编排链 | |---|---|---| | `CHAT` | `ChatWorkflow` | ChatNode → LLM | | `SINGLE_CONTRACT` | `SingleContractWorkflow` | RetrieveNode → AnalyzeNode → LLM | | `MULTI_CONTRACT` | `MultiContractWorkflow` | RetrieveNode → AnalyzeNode → CompareNode → LLM | | `KNOWLEDGE_BASE` | `KnowledgeBaseWorkflow` | RetrieveNode → AnalyzeNode → LLM | | `RAG_CONTRACT` | `RagContractWorkflow` | 检索合同 → 检索知识库 → 合并上下文 → AnalyzeNode → LLM | ### 2.3 使用方式 #### 单合同分析 1. 切换到 **合同分析** 标签 2. 上传 PDF 合同 → 自动解析、切分、向量化入库 3. 输入问题(如"租金条款是什么?") 4. 系统检索合同相关内容,LLM 基于检索结果回答 #### 知识库构建 1. 切换到 **RAG知识库** 标签 2. 输入知识库名称(如"个人租赁法规")→ 点击创建 3. 勾选目标知识库 → 展开上传区域 → 上传 PDF 4. 上传成功后面板自动折叠 #### RAG + 合同联合分析 1. 勾选知识库(可多选) 2. 展开合同上传区域 → 上传 PDF 合同 3. 提问(如"结合租赁法规,分析这份合同的风险") 4. 系统同时检索合同内容和知识库,合并后分析 --- ## 三、业务流程设计 ### 3.1 文档上传 ``` [用户上传 PDF] │ ▼ ┌──────────────┐ │ UPLOAD │ 接收 MultipartFile,生成临时 Collection 名 │ │ → 格式:s_<时间戳>_<随机数>(独立隔离) └──────┬───────┘ │ ▼ ┌──────────────┐ │ PARSE │ Apache PDFBox 提取文本 │ │ → setSortByPosition(true) 按渲染顺序 │ │ → 保留段落结构 └──────┬───────┘ │ ▼ ┌──────────────┐ │ SPLIT │ TextSplitter 语义切分 │ │ ① 标题边界(## / ###) │ │ ② 段落边界(空行) │ │ ③ 列表项保持完整 │ │ ④ 长段落按句子切分 └──────┬───────┘ │ ▼ ┌──────────────┐ │ VECTORIZE │ BGE-M3 向量化 │ │ → 分批处理(每批 5 个 chunk,避免 OOM) │ │ → 写入 Milvus └──────┬───────┘ │ ▼ ┌──────────────┐ │ DONE │ 返回 chunkCount 和文本长度 └──────────────┘ ``` ### 3.2 对话检索 ``` [用户输入问题] │ ▼ ┌──────────────┐ │ Router │ LLM 意图分类(3 种基础意图) │ │ → CHAT / SINGLE_CONTRACT / MULTI_CONTRACT │ │ ChatService 根据上下文扩展为 5 种意图: │ │ → KNOWLEDGE_BASE(有 RAG 无合同) │ │ → RAG_CONTRACT(有 RAG 有合同) └──────┬───────┘ │ ▼ ┌──────────────┐ │ RetrieveNode│ 向量检索 Milvus │ │ → 用户问题向量化 │ │ → 余弦相似度 Top-K 检索 │ │ → 拼接上下文 └──────┬───────┘ │ ▼ ┌──────────────┐ │ AnalyzeNode │ 构建 System/User Prompt │ │ → 格式约束(标题/列表/代码块) │ │ → 仅基于检索内容回答 └──────┬───────┘ │ ▼ ┌──────────────┐ │ LLM (SSE) │ 流式返回 │ │ → Spring MVC SseEmitter │ │ → 前端按 type 字段区分渲染 └──────────────┘ ``` ### 3.3 RAG+合同联合检索 ``` [用户提问] → ChatService │ ┌─────────────┴─────────────┐ │ hasContract? hasRag? │ └─────────────┬─────────────┘ │ 两者都满足 ▼ ┌────────────────────────────┐ │ RagContractWorkflow │ │ │ │ ① 检索合同 (sessionId) │ → Top-5 │ ② 逐库检索知识库 │ → 每库 Top-3 │ ③ 合并上下文 │ │ 【合同内容】+【参考知识库】│ │ ④ AnalyzeNode 构建Prompt │ │ ⑤ LLM 流式输出 │ └────────────────────────────┘ ``` ### 3.4 Session 管理 - 合同上传后数据存入临时 Collection(命名格式:`s_<时间戳>_<随机数>`) - 同一 session 可上传多份合同,每份合同独立 Collection,会话超时 10 分钟自动清理: - 清除对话历史 - 删除 Milvus 临时 Collection - 定时任务每 5 分钟检查 - 知识库数据永久存储,不受 session 影响 --- ## 四、代码结构 ``` contract-analysis-retrieval/ │ ├── backend/ # Spring Boot 后端 │ ├── pom.xml # Maven 依赖配置 │ └── src/main/ │ ├── resources/ │ │ └── application.yml # 应用配置(LLM/Milvus/Session) │ └── java/com/contract/retrieval/ │ │ │ ├── ContractRetrievalApplication.java # Spring Boot 启动类 │ │ │ ├── config/ # 配置层 │ │ ├── ChatModelConfig.java # LLM:MiniMax M2 (OpenAI 兼容) │ │ ├── EmbeddingConfig.java # Embedding:BGE-M3 (本地 ONNX) │ │ │ ├── controller/ # 控制器层 │ │ ├── ChatController.java # /api/chat/stream, /message │ │ ├── ContractController.java # /api/contract/upload/**, /collections │ │ └── SessionController.java # /api/session/** │ │ │ ├── service/ # 服务层 │ │ ├── ChatService.java # 对话编排:意图路由 + 工作流执行 │ │ ├── DocumentReconstructionService.java # 文档重建(LLM 语义拼接) │ │ ├── PdfParsingService.java # PDF 解析(Apache PDFBox) │ │ ├── SessionService.java # 会话管理 + 超时清理 │ │ ├── TextSplitterService.java # 语义切分(标题/段落/列表/句子) │ │ └── VectorStoreService.java # 向量存储(Milvus CRUD) │ │ │ └── workflow/ # 工作流编排层 │ ├── Workflow.java # 工作流接口 │ ├── WorkflowContext.java # 工作流共享上下文 │ ├── Router.java # 意图路由(LLM 分类 + 工作流分发,支持 5 种意图) │ ├── ContractWorkflow.java # 工作流状态/意图枚举定义 │ ├── DocumentUploadWorkflow.java # 文档上传编排(UPLOAD→PARSE→SPLIT→VECTORIZE) │ ├── ChatWorkflow.java # 闲聊工作流 │ ├── SingleContractWorkflow.java # 单合同工作流 │ ├── MultiContractWorkflow.java # 多合同对比工作流 │ ├── KnowledgeBaseWorkflow.java # 知识库检索工作流 │ ├── RagContractWorkflow.java # RAG+合同联合检索 │ ├── SSEEmitterHelper.java # SSE 流式输出辅助器(缓冲、标签解析、事件推送) │ └── node/ # 可复用工作流节点 │ ├── Node.java # 节点接口 │ ├── RetrieveNode.java # 向量检索节点 │ ├── AnalyzeNode.java # 分析节点(Prompt 构建) │ ├── CompareNode.java # 多合同对比节点 │ ├── ChatNode.java # 聊天节点(历史管理) │ └── MergeContextNode.java # 上下文合并节点 │ ├── frontend/ # Vue 3 前端 │ ├── index.html # HTML 入口 │ ├── package.json # 前端依赖 │ ├── vite.config.js # Vite 配置(代理 /api → localhost:8080) │ └── src/ │ ├── main.js # Vue 应用入口 │ ├── App.vue # 主界面:聊天/上传/知识库管理 │ └── api/ │ └── index.js # API 封装(Axios + Fetch SSE) │ └── README.md # 本文档 ``` --- ## 五、技术栈及核心原理 ### 5.1 技术选型 | 类别 | 技术 | 版本 | 说明 | |---|---|---|---| | **语言** | Java | 17 | | | **框架** | Spring Boot | 3.2.0 | Web(MVC)+ WebFlux + Validation(SSE 使用 SseEmitter,非 WebFlux) | | **AI 编排** | LangChain4j | 1.14.1 | 统一 LLM/Embedding/VectorStore 接口 | | **LLM** | MiniMax M2 | - | 通过 OpenAI 兼容 API 调用 | | **Embedding** | BGE-M3 | - | 本地 ONNX 模型,1024 维向量,ONNX Runtime + DJL Tokenizer | | **向量数据库** | Milvus | - | 独立部署,gRPC 通信 | | **PDF 解析** | Apache PDFBox | 3.0.4 | 纯 Java,按渲染顺序提取文本 | | **文件类型检测** | Apache Tika | 2.9.1 | 文件类型识别 | | **数据库** | PostgreSQL | - | 运行时依赖 | | **工具库** | Lombok | - | 简化 Java 代码 | | **JSON** | Jackson | - | 含 jsr310 日期模块 | | **MCP 协议** | LangChain4j MCP | 1.14.1 | Model Context Protocol 支持 | | **前端** | Vue 3 + Vite | 3.4 / 5.0 | 组合式 API | | **状态管理** | Pinia | 2.1 | Vue 3 状态管理 | | **路由** | Vue Router | 4.2 | 前端路由 | | **Markdown 渲染** | marked | 18.0 | 客户端渲染 LLM 输出 | | **代码高亮** | highlight.js | 11.9 | | | **HTTP 客户端** | Axios | 1.6 | | | **构建** | Maven | - | | ### 5.2 RAG(检索增强生成)原理 ``` 用户提问 ──→ Embedding 模型 ──→ 查询向量 │ ▼ Milvus 向量检索 (余弦相似度 Top-K) │ ▼ 相关文档片段 │ ▼ ┌──────────────────────────────────────────────────────┐ │ System Prompt: "你是一个文档分析助手,请根据 │ │ 提供的文档内容直接回答用户问题..." │ │ │ │ User Prompt: "【检索到的上下文】\n...\n\n用户问题:..."│ └──────────────────────────────────────────────────────┘ │ ▼ LLM 生成回答 ``` **核心要点**: - **Embedding**:将文本转换为高维向量,语义相近的文本向量距离近 - **向量检索**:用余弦相似度在向量空间中找最相似的 Top-K 文档 - **上下文增强**:将检索到的文档片段作为 Prompt 上下文注入 LLM - **幻觉抑制**:通过 System Prompt 约束"仅基于检索内容回答",低 temperature(0.1) ### 5.3 BGE Embedding 模型 - **模型**:BAAI/bge-m3(北京智源人工智能研究院) - **运行方式**:本地 ONNX Runtime,无需 GPU,无需 API 调用 - **向量维度**:1024 维 - **支持语言**:多语言(中文、英文等 100+ 语言) - **支持功能**:密集检索(Dense)、稀疏检索(Sparse)、多向量检索(ColBERT) - **适用场景**:多语言文本的语义相似度计算 - **优势**:零成本、低延迟、隐私安全、多语言支持 ### 5.4 Milvus 向量数据库 - **类型**:开源的分布式向量数据库 - **索引**:支持 IVF_FLAT、IVF_SQ8、HNSW 等多种索引 - **相似度**:默认余弦相似度(Cosine Similarity) - **通信**:gRPC 协议,端口 19530 - **Collection 命名**:只允许字母、数字、下划线,中文自动 MD5 哈希 ### 5.5 文本语义切分 TextSplitter 采用多层级切分策略,确保每个 chunk 都是语义完整的单元: ``` 原始文本 │ ├─ ① 标题边界(## / ###)→ 按章节切分 │ ├─ ② 段落边界(连续空行)→ 按段落切分 │ ├─ ③ 列表项识别 → 保持列表块完整,不拆散 │ (以 - 或数字序号开头) │ └─ ④ 长段落(>512字符)→ 按中文句子切分 (断句标记:。!?;) 最小 chunk 256 字符 ``` ### 5.6 LangChain4j - **统一抽象**:`ChatModel`、`StreamingChatModel`、`EmbeddingModel`、`EmbeddingStore` 接口 - **Milvus 适配器**:`langchain4j-milvus` 直接集成 - **BGE-M3 模型**:通过 `langchain4j-embeddings` 通用模块,自定义 `BgeM3EmbeddingModel` 加载 ONNX 模型,直接使用 ONNX Runtime + DJL Tokenizer 推理 - **OpenAI 兼容**:`langchain4j-open-ai` 支持 MiniMax 等兼容 API - **MCP 协议**:`langchain4j-mcp` 支持 Model Context Protocol - **Spring Boot 集成**:`langchain4j-spring-boot-starter` 自动配置 ### 5.7 SSE(Server-Sent Events)流式输出 - 后端使用 Spring MVC 的 `SseEmitter` 推送流式响应(非 WebFlux `Flux`) - 统一封装在 `SSEEmitterHelper.createStreamHandler(...)`,5 个工作流(Chat / SingleContract / MultiContract / RagContract / KnowledgeBase)共用 - 事件类型: - `{"type":"text","content":"..."}` 正式回复,按标点缓冲避免重复 chunk - `{"type":"thinking","content":"..."}` 思维链,从 `` / `` 标签解析 - `{"type":"error","content":"..."}` 错误 - `[DONE]` 流结束 - 跨 chunk 标签被切断时由 `ThoughtTagParser` 状态机处理,避免误识别 - 前端使用 `fetch` + `ReadableStream`,按 `\r?\n\r?\n` 切分完整事件,解析 `type` 字段分别渲染到"思考中"灰色区域和正式回复区 - 流式过程中修复 Markdown 格式(`**bold**` 被换行打断的问题) --- ## 六、部署方式 ### 6.1 环境要求 | 依赖 | 版本 | 说明 | |---|---|---| | JDK | 17+ | | | Maven | 3.6+ | | | Node.js | 18+ | 前端开发 | | Milvus | 2.x | 向量数据库 | ### 6.2 部署 Milvus 推荐使用 Docker 部署 Milvus Standalone: ```bash # 下载 docker-compose 文件 wget https://github.com/milvus-io/milvus/releases/download/v2.4.0/milvus-standalone-docker-compose.yml -O docker-compose.yml # 启动 Milvus docker compose up -d # 验证 docker compose ps ``` Milvus 默认监听 `localhost:19530`。 ### 6.3 配置 LLM API 编辑 `backend/src/main/resources/application.yml` 或通过环境变量: ```yaml llm: base-url: ${AGENT_LLM_BASE_URL:https://api.minimax.chat/v1} api-key: ${AGENT_LLM_API_KEY:your-api-key} model-name: ${AGENT_LLM_MODEL:MiniMax-M2.7} temperature: 0.1 max-tokens: 4096 ``` 支持通过环境变量覆盖: ```bash export AGENT_LLM_BASE_URL="https://your-llm-endpoint/v1" export AGENT_LLM_API_KEY="sk-xxx" export AGENT_LLM_MODEL="your-model-name" ``` ### 6.4 启动后端 ```bash cd backend # 设置 JAVA_HOME(Windows) set JAVA_HOME=D:\Software\jdk-17 # 编译 mvn clean compile -DskipTests # 启动 mvn spring-boot:run ``` 后端启动在 `http://localhost:8080`。 ### 6.5 启动前端 ```bash cd frontend # 安装依赖 npm install # 开发模式 npm run dev ``` 前端启动在 `http://localhost:3000`,Vite 自动代理 `/api` 请求到后端 `http://127.0.0.1:8080`。 ### 6.6 验证 1. 打开 `http://localhost:3000` 2. 在闲聊模式下输入"你好" → 应收到流式回复 3. 切换到合同分析模式 → 上传 PDF → 提问 4. 切换到 RAG 模式 → 创建知识库 → 上传文档 → 勾选知识库 → 提问 ### 6.7 外部依赖清单 | 服务 | 端口 | 用途 | 必需 | |---|---|---|---| | Milvus | 19530 | 向量存储与检索 | 是 | | MiniMax API | 443 (HTTPS) | LLM 推理 | 是 | | BGE-M3 ONNX | 本地 | Embedding 向量化 | 是(本地运行) | --- ## 七、开发说明 ### 7.1 新增工作流 1. 在 `ContractWorkflow.Intent` 添加新意图 2. 创建实现 `Workflow` 接口的工作流类 3. 在 `Router.route()` 添加路由分支 4. 在 `ChatService` 添加意图判断逻辑 ### 7.2 新增工作流节点 1. 创建实现 `Node` 接口的节点类 2. 实现 `process(WorkflowContext ctx)` 方法 3. 在目标工作流中按顺序调用节点 ### 7.3 配置说明 - `session.timeout-minutes`: 会话超时时间(默认 10 分钟) - `session.cleanup-interval-minutes`: 清理检查间隔(默认 5 分钟) - `milvus.collection.prefix`: Collection 名称前缀 - `milvus.collection.dimension`: 向量维度(与 Embedding 模型一致,1024) - `llm.temperature`: LLM 温度(0.1 以减少幻觉) --- ## 八、近期更新记录 ### 2026-06-29 多线程与异步处理优化 - **后端异步线程池** - 使用 `ThreadPoolTaskExecutor` 处理文件解析与向量化任务 - 核心参数:核心线程数 4、最大线程数 8、队列容量 50 - 任务状态通过 SSE 实时推送到前端,避免轮询 - **前端多文件并发上传** - 支持同时上传多份合同,每份合同独立生成 SSE 连接 - 每个上传任务拥有独立的 `AbortController`,可单独取消 - 修复 `AbortController` 被置空后异步循环访问 `signal` 的 `TypeError` - **MultipartFile 临时文件处理** - 在异步处理前将文件内容读取到内存(`file.getBytes()`),避免 Tomcat 在 HTTP 请求结束后删除临时文件导致 PDF 解析失败 - **SSE 连接生命周期优化** - 后端任务完成后延迟 500ms 关闭连接,确保最终状态被前端接收 - 前端收到 `{"done":true}` 后延迟 200ms 关闭连接 - 用户断开连接或页面关闭时,后端在 SSE 超时/错误回调中调用 `sessionService.deleteSession()` 清理临时数据 --- ### 2026-06-30 第一步:RAG 知识库映射持久化到 H2 - 新增 `KnowledgeBase` 实体与 `KnowledgeBaseRepository` - RAG 知识库的显示名与实际 Milvus Collection 名的映射关系持久化到 H2 - 知识库在 Milvus 中的 Collection 名统一使用 `rag_` 前缀,中文名自动 MD5 处理 - 前端拉取知识库列表时仅返回显示名,不暴露 Milvus 实际 Collection 名 --- ### 2026-06-30 第二步:Session 临时文档管理迁移到 H2 - 新增 `SessionDocument` 实体与 `SessionDocumentRepository` - 临时文档的元数据(sessionId、fileName、collectionName、status、errorMsg、过期时间等)持久化到 H2 - 临时文档 Collection 名统一使用 `s_` 前缀,格式为 `s_<时间戳>_<随机数>` - `UploadTaskService` 在任务创建和状态变更时同步更新 H2 记录 - `SessionService` 基于 H2 中的 `expire_at` 字段清理过期 session - `VectorStoreService` 删除 Collection 时同步清理 H2 中的 `SessionDocument` 记录 --- ### 2026-06-30 第三步:过期 Session 定时清理机制完善 - 定时清理周期改为可配置:`session.cleanup-interval-minutes`(默认 5 分钟) - 新增手动触发清理接口:`POST /api/contract/admin/sessions/cleanup-expired` - 清理时检查 session 是否还有未完成的异步上传任务,避免误删正在处理的数据 - 新增 Milvus 孤立临时集合兜底清理:扫描所有 `s_` 前缀 Collection,若 H2 中无对应记录则删除 - 修复 JPA 删除操作因缺少 `@Transactional` 而失败的问题