# 简历 · 大模型应用开发工程师（医疗行业版）

> 由中医知识图谱项目迁移。同一套架构（采集 → 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. 合规边界：系统定位为医师辅助工具，输出不构成诊疗建议，须执业医师复核
