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

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

---

## 基本信息

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

---

## 技能栏

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

**知识图谱与检索**
- Neo4j:Cypher 读写、批量事务导入、唯一约束、`EXPLAIN` 静态校验、schema 元数据导出
- FAISS + bge-large-zh-v1.5 向量召回,按实体类型过滤
- 医学术语标准化:主诉口语到标准术语的映射("肚子疼" → "腹痛"),对接 ICD-10 / SNOMED CT

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

**工程能力**:Python 3.10、FastAPI(异步 / 流式 / 线程池)、Streamlit、并发采集、
`asyncio.Queue` 跨线程反压、Git / Black;数据脱敏与院内私有化部署

---

## 项目经验

### 临床知识图谱辅助问答系统 `<公司/独立项目>`

**角色**:技术负责人,负责整体方案与全链路实现,与 `<N>` 名临床医师协作做术语与内容审核

**背景**:临床问答对准确性的要求高于任何其他领域。通用大模型会编造不存在的药物相互作用、
给出错误剂量、把禁忌症说成适应症 —— 这类错误在医疗场景直接对应伤害。目标是把临床指南、
药品说明书、疾病知识变成可查询的知识图谱,让答案的每一条结论都能回溯到具体来源,
并且**在证据不足时明确拒答**而非编造。

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

**七阶段流水线:**

**1. 数据采集**(临床指南 + 药品说明书 + 疾病知识库)
- 按**文档顺序**遍历 DOM 而非按已知字段名查找 —— 说明书的章节(适应症/用法用量/不良反应/
  禁忌/注意事项/相互作用)在不同厂家的排布和命名都不统一,按顺序走开新章节才能完整保留。
  漏掉"禁忌"章节的后果不是数据缺失,是安全事故
- 断点续传做在文件系统层:目标文件存在即完成;写入走 `.part` 再原子替换。
  指南更新版本后重采,续传让增量更新只做缺失部分
- 并发采集 + Session 按线程隔离;**采集内容全程不含患者信息**,只处理公开的药品与指南文本

**2. 医学实体关系抽取**(LLM 结构化输出)
- Pydantic 定义 schema 作为全链路契约:实体为**疾病 / 症状 / 药物 / 检查 / 治疗方案 /
  证据来源**;关系为**治疗 / 缓解 / 适应症 / 禁忌 / 药物相互作用 / 需做检查 / 来源**
- 属性挂在实体上:药物带剂量、给药途径、半衰期、妊娠分级、储存条件;
  疾病带 ICD-10 编码、分期分级
- **禁忌与适应症必须严格区分**:prompt 中作为最高优先级约束反复强调,
  两者混淆是本项目定义的 P0 错误。抽取结果按此维度做专项抽样复核
- 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 直连生产库,两个独立位置设闸**:① 关键词词边界扫描 + 必须有 RETURN
  且以 MATCH/WITH 开头;② `EXPLAIN` 逐条静态校验
- **绝不把用户原话放进 WHERE**:用户说"肚子疼",图里存的是"腹痛"。术语匹配节点弥合这个差距
- **答案生成节点的三条硬约束**(本项目最核心的部分):
  ① 结论必须携带来源(指南名称 / 说明书章节),来源只能来自图谱查询结果;
  ② 检索不到证据时明确回答"未检索到相关证据",绝不由模型补充;
  ③ 输出附免责声明,明确不构成诊疗建议,需执业医师判断
- JSON 解析多级降级;Cypher 逐条执行而非批量事务,出错补空列表保持下标对齐

**6. 服务化**
- FastAPI 三接口:健康检查、同步问答、流式问答
- 图是懒加载的模块级单例,`lifespan` 启动预热(FAISS 索引 + embedding 模型约 50 秒);
  预热失败只记日志不阻止启动,健康检查照样可用 —— 院内系统对可用性要求高
- `graph.invoke` 同步阻塞,走 `run_in_threadpool`,否则事件循环被占死、服务退化成单并发
- 流式接口用生产者-消费者:节点在工作线程产出,有界 `asyncio.Queue`(32,形成反压)
  跨回事件循环。推给前端的是**状态增量**而非全量状态
- 全套可离线私有化部署(vLLM 自托管模型 + 本地 embedding + 院内 Neo4j),
  不依赖外部 API —— 医疗数据不出院是硬性要求

**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. **禁忌 vs 适应症**:定义为 P0 错误,prompt 最高优先级约束 + 专项抽样复核
2. **跨类型同名**:高血压既是疾病也是症状,主键必须是 `(类型, 名称)`
3. **别名映射**:通用名/商品名指向同一节点,否则禁忌查询会漏
4. **证据等级入图**:答案表明推荐强度,而非把所有结论等同对待
5. **拒答优于猜测**:检索不到证据时明确说明,这是医疗场景不可让渡的底线
6. **私有化部署**:模型、向量库、图谱全部院内自托管,数据不出院
7. 合规边界:系统定位为医师辅助工具,输出不构成诊疗建议,须执业医师复核