引言
- 痛点切入: 为什么市面上充斥着各种大模型套壳应用,但在严谨的“法律垂类”却难以落地?(算数差、容易遗忘案卷细节、缺乏专业合规性、容易被注入攻击)。
- 项目愿景: 介绍 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):
- 工作记忆: 强制使用 128k/200k 超长上下文模型,对话全量追加。
- 物理记忆 (Checkpointer): 利用 Postgres 固化 LangGraph 状态,支持断点续传与 Human-in-the-Loop(人工复核)。
- 语义记忆 (KAIROS 机制): 编写后台 Cron Job,在凌晨自动将几十页的废话压缩成极其精炼的 JSON 要素表(做梦机制),大幅降低 Token 成本。
4. 安全与合规:企业 SaaS 的真正护城河
-
零成本防注入 (Dual-Channel):
- 短文本框: 采用 Pydantic 严格限制字数,正则抹除
<|im_start|>等控制符。 - 长文书解析: 走独立通道,提取纯文本后强制使用
<document>XML 标签进行物理隔离,防御间接注入 (Indirect Prompt Injection)。
- 短文本框: 采用 Pydantic 严格限制字数,正则抹除
-
防破产与数据隐私: 引入
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-core和langgraph-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时频繁出现IncompleteRead或TimeoutError。 - 真相: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++ 编译报错。