从“玩具对话框”到“企业级 SaaS”:我的多智能体法律大模型架构演进实录

6 阅读9分钟

引言

  • 痛点切入: 为什么市面上充斥着各种大模型套壳应用,但在严谨的“法律垂类”却难以落地?(算数差、容易遗忘案卷细节、缺乏专业合规性、容易被注入攻击)。
  • 项目愿景: 介绍 L-ERAP (企业级劳动法律研判引擎)。我的目标不是写一个 Chatbot,而是打造一个包含前端 3D 动效、底层超长记忆、高并发队列与多模态解析的商业级 SaaS。

核心架构演进

1. 交互升维:抛弃纯聊天框,构建“苹果级”工作台

  • 设计理念: 采用 Dark-mode Tech Minimalism (暗黑科技极简主义)。

  • 前端选型: Next.js + Tailwind CSS + Framer Motion,打造丝滑的响应式体验。

  • 界面解构:

    • 三段式设计:左侧历史卷宗、中控多模态立案(结构化要素提取卡片)、右侧可视化胜诉率与判例溯源。
    • 微交互微创新:引入划词悬浮菜单、无损格式的一键复制,拒绝反人类操作。

2. 大脑重构:LangGraph 与多 Agent 降维调度

  • 痛点: 单体大模型无法处理复杂的“事实解构 -> 检索 -> 推理 -> 审计”全流程。
  • 解法 (Coordinator-Worker 模式): 将系统拆分为 4 个 Node。在核心的 Node 3(裁判大脑)内部采用 Subgraph(子图)模式,让“原告 Agent”与“被告 Agent”在沙盒中互相对抗推演。
  • 对抗大模型“理科差”: 放弃沉重的 MCP Server,手写纯 Python 的 Skills(工具箱),将“加班费计算”等绝对客观的逻辑交给代码,大模型只负责调度。

3. 记忆引擎:攻克“案卷级”长文本的遗忘

  • 痛点: 滑动窗口机制会导致大模型遗忘几十页前的合同细节。

  • 三重记忆引擎(致敬 Claude Code):

    1. 工作记忆: 强制使用 128k/200k 超长上下文模型,对话全量追加。
    2. 物理记忆 (Checkpointer): 利用 Postgres 固化 LangGraph 状态,支持断点续传与 Human-in-the-Loop(人工复核)。
    3. 语义记忆 (KAIROS 机制): 编写后台 Cron Job,在凌晨自动将几十页的废话压缩成极其精炼的 JSON 要素表(做梦机制),大幅降低 Token 成本。

4. 安全与合规:企业 SaaS 的真正护城河

  • 零成本防注入 (Dual-Channel):

    • 短文本框: 采用 Pydantic 严格限制字数,正则抹除 <|im_start|> 等控制符。
    • 长文书解析: 走独立通道,提取纯文本后强制使用 <document> XML 标签进行物理隔离,防御间接注入 (Indirect Prompt Injection)。
  • 防破产与数据隐私: 引入 Tenant_Billing 租户账本(防止 API 被刷爆);建立不可篡改的 Audit_Log 审计日志;引入基于 Celery 的定时“阅后即焚”垃圾回收机制。


实战记录:打下后端的第一根桩 (环境与目录)

创建目录

mkdir api, agents, db, skills, services, tasks, rag, core, prompts, tests

创建依赖 pyproject.toml 、安装 poetry install、更新poetry update

[tool.poetry]
name = "l-erap-backend"
version = "0.1.0"
description = "L-ERAP (Legal Element-based Retrieval & Analysis Platform) Enterprise Backend"
authors = ["Your Name <your.email@example.com>"]
readme = "README.md"

# 🚀 [新增防断连黑科技] 配置清华大学镜像源,秒杀几十 GB 的大模型库下载超时!
[[tool.poetry.source]]
name = "aliyun"
url = "https://mirrors.aliyun.com/pypi/simple/"
priority = "primary"

[tool.poetry.dependencies]
python = "^3.11" 

# 🌐 1. Web 网关与鉴权层
fastapi = "^0.110.0"
uvicorn = {extras = ["standard"], version = "^0.29.0"}
pydantic = "^2.6.0"
pydantic-settings = "^2.2.0"
python-jose = {extras = ["cryptography"], version = "^3.3.0"}

# 🧠 2. 核心大模型与 Agent 编排层
langchain = "^0.3.0"
langchain-core = "^0.3.0"
langchain-openai = "^0.2.0" 
langchain-community = "^0.3.0"
langgraph = "^0.2.20" 

# 💾 3. 数据库与记忆持久化
sqlalchemy = "^2.0.29"
alembic = "^1.13.1"
psycopg2-binary = "^2.9.9" 
langgraph-checkpoint-postgres = "^1.0.0" 
langgraph-checkpoint-sqlite = "^1.0.0"   

# 📚 4. 向量检索与 RAG 
# (已移除 chromadb 规避 Windows C++ 编译问题,直接使用企业级 pymilvus)
pymilvus = "^2.3.6"
sentence-transformers = "^2.6.0" 

# ⏳ 5. 异步高并发任务队列
celery = "^5.3.6"
redis = "^5.0.3"

# 👁️‍🗨️ 6. 多模态预处理
pymupdf = "^1.24.1" 

[tool.poetry.group.dev.dependencies]
pytest = "^8.1.1"
pytest-asyncio = "^0.23.6"
ruff = "^0.3.4" 

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

编写环境变量.env

# --- 基础配置 ---
PROJECT_NAME="L-ERAP-Enterprise"
DEBUG=True

# --- 大模型 API 配置 ---
# 这里以 DeepSeek 为例,它完全兼容 OpenAI 协议
DEEPSEEK_API_KEY="你的_DEEPSEEK_API_KEY"
DEEPSEEK_BASE_URL="https://api.deepseek.com"

# --- 数据库配置 ---
# 初期测试我们先用本地 SQLite,后期一键切换 Postgres
DATABASE_URL="sqlite+aiosqlite:///./lerap_db.sqlite"

# --- 向量库配置 (Milvus) ---
MILVUS_HOST="localhost"
MILVUS_PORT="19530"

# --- 安全与鉴权 ---
SECRET_KEY="用一串随机长字符串替换这里_用于JWT签名"
ACCESS_TOKEN_EXPIRE_MINUTES=60

编写配置中心读取环境变量core/config.py

from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field

class Settings(BaseSettings):
    """
    使用 Pydantic 自动读取 .env 环境变量
    """
    # 基础配置
    PROJECT_NAME: str = "L-ERAP-Backend"
    DEBUG: bool = True
    
    # LLM 配置
    DEEPSEEK_API_KEY: str
    DEEPSEEK_BASE_URL: str = "https://api.deepseek.com"
    
    # 数据库
    DATABASE_URL: str
    
    # 安全
    SECRET_KEY: str
    ACCESS_TOKEN_EXPIRE_MINUTES: int = 60

    # 自动加载 .env 文件
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")

# 全局单例对象,其他模块直接导入这个 settings 即可
settings = Settings()

编写双隔离通道防御 api/schemas.py
代码不仅是前后端的数据契约,它还承载了我们设计的 双通道隔离 和 输入清洗 逻辑
物理隔离:把“指令”和“文书”分在两个字段。即使 PDF 里藏了恶意代码,它也只是 里的一个 URL 字符串,file_urls 没有执行权限。
正则清洗:在进入业务逻辑前,所有system:等劫持词汇会被直接抹除。
强类型约束: 保证了用户传进来的HttpUrl必须是合法的地址,从源头上堵住了 SSRF 等传统 Web 攻击。

from pydantic import BaseModel, Field, field_validator, HttpUrl
from typing import List, Optional
import re

# ==========================================
# 🛡️ 核心防御:双通道立案请求契约
# ==========================================

class CaseAnalysisRequest(BaseModel):
    """
    立案请求:物理分离“用户指令”与“参考文书”
    """
    
    # 通道 A: 严格限长的对话框 (防御重点)
    user_query: str = Field(
        ..., 
        min_length=2,
        max_length=2000, 
        description="用户手工输入的诉求或描述,严格限长以防溢出攻击"
    )
    
    # 通道 B: 不受字数限制的文书通道 (仅传递文件引用)
    # 文书由 services/doc_parser 处理,此处只接收已上传的文件地址
    file_urls: List[HttpUrl] = Field(
        default_factory=list, 
        max_items=10, 
        description="经过预处理的证据文书 URL 列表"
    )
    
    # 业务多租户上下文
    tenant_id: str = Field(default="default_corp", description="租户ID,实现多租户数据隔离")
    user_id: str = Field(..., description="操作人ID")

    @field_validator("user_query")
    @classmethod
    def sanitize_prompt(cls, v: str) -> str:
        """
        [第一层防御:清洗引擎] 
        自动检测并抹除任何试图劫持模型控制权的特殊 Token 或关键词
        """
        # 定义需要过滤的“黑名单”正则表达式
        forbidden_patterns = [
            r"<\|.*?\|>",                    # 拦截 LLM 特殊角色 Token
            r"system:",                      # 拦截伪造系统指令
            r"assistant:",                   # 拦截伪造助手响应
            r"ignore all previous instructions", # 拦截英文越权指令
            r"忽略(之前|前面)的(所有)?指令"   # 拦截中文越权指令
        ]
        
        cleaned = v
        for pattern in forbidden_patterns:
            # 执行不区分大小写的暴力替换
            cleaned = re.sub(pattern, "", cleaned, flags=re.IGNORECASE)
        
        return cleaned.strip()

# ==========================================
# 🔄 异步状态响应契约
# ==========================================

class TaskResponse(BaseModel):
    """
    标准异步响应:遵循“先响应任务ID,后台慢慢算”的商业标准
    """
    task_id: str = Field(..., description="用于轮询进度的任务 ID")
    thread_id: str = Field(..., description="LangGraph 记忆链条的持久化会话 ID")
    status: str = Field(default="processing")
    message: str = Field(default="司法大脑已受理,正在启动多维度研判程序...")

class AuditActionRequest(BaseModel):
    """
    人工复核契约:当 Node 4 审计员发现高风险或不确定性时触发
    """
    thread_id: str = Field(..., description="被挂起的会话 ID")
    action: str = Field(..., description="approve(放行) / reject(打回) / modify(修改后放行)")
    legal_opinion_override: Optional[str] = Field(default=None, description="律师手动覆盖的研判意见")

💣 踩坑与避坑指南 (The Pitfalls)

坑位 1:PowerShell 的 mkdir 陷阱

  • 现象: 习惯了 Linux 环境,在 PowerShell 运行 mkdir api agents db... 试图批量建文件夹时,直接报错 A positional parameter cannot be found
  • 解法: PowerShell 的 mkdir 不认空格,必须用逗号隔开:mkdir api, agents, db, skills...

坑位 2:Poetry 安装后的“查无此人”

  • 现象: 按照官方命令安装了 Poetry,但在终端运行 poetry install 时,系统无情提示 The term 'poetry' is not recognized。这是 Windows 极其经典的 PATH 环境变量丢失问题,且如果存在多个 Conda 环境,极易引发套娃灾难。
  • 终极解法(隔离打法): 与其去修改系统的环境变量,不如利用 Python 原生的 venv 加上局部安装的黑科技,实现绝对清爽的隔离:
# 1. 退出当前的复杂环境
deactivate
# 2. 创建原生虚拟环境
python -m venv .venv
# 3. 激活环境 (注意 PowerShell 的执行策略)
.\.venv\Scripts\Activate.ps1
# 4. 在该环境内部强制安装 poetry,绕过系统限制
python -m pip install --upgrade pip
pip install poetry
# 5. 享受丝滑安装
poetry install

坑位 3:LangChain 生态的“代差割裂” (Version Solving Failed)

  • 现象: 在使用 Poetry 安装依赖时,爆出红色的 version solving failed,提示 langchain-corelanggraph-checkpoint-sqlite 版本冲突。
  • 复盘分析: 很多网上的老教程还在教你用 LangChain 0.1.x。但当我们引入企业级状态持久化(langgraph-checkpoint)时,这些新模块是伴随 LangChain 0.2.x 诞生的,强制要求底层 Core 版本 >= 0.2.22。如果你不统一升级整个 AI 编排层的依赖,就会出现“旧底座挂不上新引擎”的窘境。
  • 架构师经验: 永远不要用 requirements.txt 去强行 pip install!如果不用 Poetry 提前暴露出这种底层的版本约束冲突,代码跑到生产环境里发生神秘的崩溃,查都不好查。升级 pyproject.toml 中 LangChain 家族全员至 ^0.3.0 后,完美解决。

坑点 4:网络刺客与 PyTorch 巨兽

  • 现象:安装 torch 时频繁出现 IncompleteReadTimeoutError
  • 真相:PyTorch 体积高达 2.5GB+,海外 PyPI 官方源在没有特殊加速的情况下,极易因为网络波动导致下载到一半断开。
  • 解法:在配置文件中注入国内阿里云 (Aliyun)清华 (Tsinghua) 镜像源,利用国内 CDN 的高速带宽实现“秒下”。

坑点 5:Windows 缺失的 C++ 编译链 (The Build Trap)

  • 现象:安装 chromadb (hnswlib) 时提示 Microsoft Visual C++ 14.0 or greater is required
  • 真相:Python 3.13 太新了,很多库还没准备好预编译的二进制包(Whl)。系统试图现场编译 C++ 代码,但你的 Windows 缺乏昂贵的 Visual Studio 编译环境。
  • 解法(架构师思维) :不要为了一个小依赖去装 5GB 的编译器!果断移除“玩具级”的 ChromaDB,直接全面拥抱纯 Python 驱动、高性能的 Milvus (pymilvus) ,从源头上消灭 C++ 编译报错。