💻 源码
# 简历 · 大模型应用开发工程师(法律行业版)

> 由中医知识图谱项目迁移。同一套架构(爬取 → LLM 抽取 → 图谱 → Agent → 服务 → 前端 → 微调),
> 换成法律法规检索场景。带 `<>` 的请按实际情况替换。

---

## 基本信息

| | |
|---|---|
| 姓名 | `<姓名>` |
| 岗位 | 大模型应用开发工程师 / 法律 AI |
| 经验 | 4 年(其中大模型应用方向 2 年) |
| 电话 / 邮箱 | `<电话>` / `<邮箱>` |
| 学历 | `<学校>` `<专业>` `<本科/硕士>` |

---

## 技能栏

**大模型应用**
- LangGraph 有状态编排:多节点条件路由、带上限的重试环、多轮对话记忆、流式增量产出
- LangChain 链式调用与结构化输出,OpenAI 兼容端点(DeepSeek / vLLM 自部署)
- Prompt 工程:schema 注入、否定式约束、多级降级解析;法条引用的强制溯源约束

**知识图谱与检索**
- Neo4j:Cypher 读写、批量事务导入、唯一约束、`EXPLAIN` 静态校验、schema 元数据导出
- FAISS + bge-large-zh-v1.5 向量召回,按实体类型过滤
- 法律术语标准化:口语诉求到法定概念的映射("老板不给钱" → "拖欠劳动报酬")

**模型微调**:Alpaca 数据集构建、LoRA 微调、vLLM multi-LoRA 部署

**工程能力**:Python 3.10、FastAPI(异步 / 流式 / 线程池)、Streamlit、并发爬虫、
`asyncio.Queue` 跨线程反压、Git / Black

---

## 项目经验

### 法律法规智能问答与检索系统 `<公司/独立项目>`

**角色**:技术负责人,负责整体方案与全链路实现,带 1 名实习生做法条校对

**背景**:用户咨询法律问题时用的是生活语言("公司让我加班不给加班费怎么办"),而法律检索
需要的是法定概念和精确的法条编号。通用大模型回答法律问题时最危险的行为是**编造法条**——
凭空生成一个"《劳动法》第某条",条号和内容都似真非真,用户无从辨别。目标是把法律条文变成
可查询的知识图谱,答案中的每一条引用都能回溯到真实条文。

**技术栈**:Python 3.10 / LangGraph / LangChain / Neo4j / FAISS / sentence-transformers /
FastAPI / Streamlit / vLLM

**七阶段流水线:**

**1. 数据采集**(法律法规全文 + 司法解释 + 指导案例)
- 从国家法律法规数据库、裁判文书网采集,按**文档顺序**遍历 DOM 而非按已知字段名查找 ——
  法规的层级结构(编/章/节/条/款/项)因法而异,按顺序走才能保住条文的从属关系。
  这一点在法律场景比在其他领域更关键:条文脱离所属章节,含义会变
- 断点续传做在文件系统层:目标文件存在即完成;写入走 `.part` 再原子替换,
  中断不留半截文件。法规修订后需要重采,续传让增量更新只做缺失部分
- 并发采集,Session 按线程隔离;对政府站点保留请求间隔,避免触发封禁

**2. 条文结构化抽取**(LLM 结构化输出)
- Pydantic 定义 schema 作为全链路契约:实体为**法律法规 / 条文 / 法律概念 / 主体 /
  行为 / 法律后果**;关系为**规定 / 适用于 / 违反则 / 引用 / 上位法 / 例外**
- 属性挂在实体上:条文带条号、生效日期、失效日期、修订版本;法规带效力层级
  (法律 > 行政法规 > 部门规章 > 地方性法规)
- Prompt 显式要求"单条文无关联时返回空结果" —— 法律场景下模型最容易犯的错就是
  为了填满格式而虚构条文之间的引用关系,空结果是正确输出
- **抽取时不允许改写条文原文**:`条文内容` 字段必须逐字保留,任何摘要或改写都会
  让后续的引用失去法律意义
- 成本约束:单次调用耗时取决于**输出**规模,长条文(如民法典分则)显著更慢;
  失败不写文件因此天然留在待办列表,重跑即重试;空结果要写,否则重跑在重复付费

**3. 图谱构建**
- **先在内存聚合再写库**:同一个法律概念(如"不可抗力")出现在数百个条文里,
  逐条 MERGE 等于把同样的工作重做几百遍
- **节点主键是 `(类型, 名称)` 而非名称**:法律领域同名不同义极其常见 ——
  "定金"作为法律概念与作为合同条款行为是两类节点,"第十条"必须绑定所属法规才唯一。
  只按名称去重会把不同法规的同号条文错误合并,这是致命错误
- 标签、关系类型、属性键先过白名单再拼接;值一律走参数化,防注入
- 属性用 `MERGE ... SET n += $attrs`:法规修订后属性变化,写进 MERGE 模式会匹配失败
  并创建重复节点,`ON CREATE SET` 又永远不会回填已有节点
- **时效性建模**:条文节点带生效/失效区间,查询时按时间点过滤 ——
  法律问答必须回答"当时适用哪一版",而不是只给最新版

**4. 检索层**
- Neo4j schema 元数据导出后注入 Cypher 生成 prompt;关系的中文描述人工补齐
  (含效力层级说明和适用范围),这段说明直接影响生成质量
- FAISS 索引覆盖全部实体类型,**索引本身不带类型**,召回后必须按类型过滤 ——
  否则"欺诈"可能召回一个法规名并落进"法律概念"槽位,生成出错误的 Cypher。
  超额取 `top_k * N` 再过滤,保证过滤后数量足够
- 分级健康检查守住索引与图谱的一致性:自检索(已知实体必须以距离≈0 返回 top-1)
  证明向量与映射未错位;与 Neo4j 对账防止法规更新后索引陈旧

**5. LangGraph Agent**(9 节点、2 条件边、1 个环、3 个出口)

```
START → 语义转写 → 法律意图识别
          ├─ 非法律 ─→ 直接回答 ──────────────────────→ END
          └─ 法律  ─→ 概念抽取 → 概念匹配 → cypher生成 ←──┐
                          → cypher检查                    │
                               ├─ 全部合法 ─→ 执行 → 引注答案 → END
                               ├─ 不合法且未超 3 次 ────────┘
                               └─ 额度用尽 ─→ 直接回答 → END
```

- **语义转写排在最前**:法律咨询天然是多轮的 —— 用户先问"加班费怎么算",再问"那不给呢"。
  后一句单看没有任何信息,意图识别会判成无关。转写节点结合历史补全成完整问题,
  下游才有东西可用。全图只有转写节点读原始输入,其余节点一律读转写结果
- **重试环的刹车靠状态计数**:路由函数只能读 state 不能记账,重试次数必须由生成节点自增,
  上限 3 次。重试时把上一轮不合法的语句回喂给模型,否则模型输入不变会原样再生成一遍
- **生成的 Cypher 直连生产库,两个独立位置设闸**,prompt 里的禁止写操作不算其中之一:
  ① 关键词词边界扫描 + 必须有 RETURN 且以 MATCH/WITH 开头;② `EXPLAIN` 逐条静态校验
- **绝不把用户原话放进 WHERE**:用户说"老板不给钱",图里存的是"拖欠劳动报酬"。
  概念匹配节点的存在意义就是弥合这个差距
- **答案生成节点强制引注**:回答必须携带法规全称 + 条号,且这些字段只能来自图谱查询结果,
  不允许模型自行补充。这是整个项目最核心的约束 —— 宁可回答"未检索到相关条文",
  也不能给出一个编造的条号
- JSON 解析多级降级;Cypher 逐条执行而非批量事务,出错补空列表保持下标对齐

**6. 服务化**
- FastAPI 三接口:健康检查、同步问答、流式问答
- 图是懒加载的模块级单例,`lifespan` 启动预热(FAISS 索引 + embedding 模型约 50 秒);
  预热失败只记日志不阻止启动
- `graph.invoke` 同步阻塞,走 `run_in_threadpool`,否则事件循环被占死、服务退化成单并发
- 流式接口用生产者-消费者:节点在工作线程产出,有界 `asyncio.Queue`(32,形成反压)
  跨回事件循环。推给前端的是**状态增量**而非全量状态
- 法律咨询的等待容忍度低,流式让"正在检索《劳动合同法》"这类进度可见

**7. 前端与微调**
- Streamlit 多页:常规问答 + 流式页。流式页展示检索过程(命中的法规、生成的查询、
  引用的条文),法律用户尤其需要看到检索依据而非只有结论
- 抽取结果顺带产出 Alpaca 数据集用于微调;两条写入路径 —— 抽取时追加 JSONL
  (并发追加整行不会破坏性交错),另有不调 LLM 的幂等重建脚本覆盖续传跳过的历史条目
- LoRA 微调后 vLLM 部署,multi-LoRA 单服务挂多个适配器(法律 / 中医 / 通用)按需切换

**成果**
- 覆盖 `<N>` 部法律法规、`<M>` 万条条文,schema 6 类实体 6 类关系
- **零编造条号**:所有引用均来自图谱边,检索不到时明确告知而非猜测
- 支持按时间点查询历史版本条文
- 接口层 27 条离线用例(替换假图,不触达 LLM/Neo4j/FAISS,2 秒内跑完)+ 15 条 live;
  前端 49 条离线 + 11 条 live

---

## 行业适配要点(面试可展开)

1. **效力层级入图**:法律 > 行政法规 > 部门规章 > 地方性法规,冲突时按层级取舍
2. **时效区间建模**:条文带生效/失效日期,回答"当时适用哪一版"
3. **同号条文的主键设计**:`第十条` 必须绑定所属法规才唯一,只按名称去重是致命错误
4. **强制引注**:答案里的条号只能来自图谱,宁可说"未检索到"也不编造
5. **条文原文逐字保留**:抽取时禁止改写,改写会让引用失去法律意义
6. 免责声明:系统输出为法律信息检索结果,不构成法律意见,需执业律师复核