用智能体与RAG把科研文献处理工程化:本地部署与批量任务实践
读研读博真正拖慢进度的,往往不是想不出 idea,而是那些永远做不完的杂活:查文献、下 PDF、读摘要、整理对比表、调实验代码、改参考文献格式、写组会周报。这些事单件都不难,但加起来能吞掉一天里 80% 的时间,而且它们几乎不产生任何学术价值。
这篇文章讲的不是某一个具体软件,而是一套把研究流程拆成智能体任务的方法:用本地或云端的模型服务做底座,配合 RAG 检索、工具调用和批处理脚本,把检索、解析、整理、初稿这几个环节交出去,人只守住判断、创新和最终复核。核心特点可以概括成五条——可以在本地部署、数据不出内网;覆盖从文献发现到写作辅助的全流程;支持结构化抽取,直接产出可用的表格;提供接口 API 和批量任务能力;每一步都留痕、可回溯、可复核。
下面会从环境准备开始,一路讲到四个典型智能体怎么搭、接口怎么调、批量任务怎么排队、显存怎么观察、报错怎么排查。全文不承诺「一键起飞」,只讲能落地执行的部分。
适合读这篇文章的,是需要长期处理大量文献和实验数据的硕博生、科研助理,以及想把重复劳动工程化掉的工程同学。如果你期待的是「让 AI 帮我写完整篇论文」,那这篇会让你失望,因为那条路既不合规也不好用。
1. 核心能力速览:把「80%」拆成可执行清单
先把口号翻译成任务。所谓「把 80% 的事情交给智能体」,指的是把研究流程里那些输入明确、输出可校验、重复度高的环节交出去,而不是把「提出假设」和「解释结果」也交出去。这两件事交给模型,得到的只会是看起来通顺的废话。
| 能力项 | 说明 |
|---|---|
| 项目类型 | 研究方法论 + 智能体工程实践,不绑定单一软件 |
| 核心底座 | 支持 OpenAI 兼容接口的模型服务,可本地可云端 |
| 主要功能 | 文献检索与去重、PDF 解析、结构化抽取、知识库问答、代码草稿、写作辅助 |
| 推荐硬件 | 本地跑 7B 级量化模型通常需要数 GB 级显存,具体以实际模型和推理后端为准 |
| 是否支持 CPU 推理 | 支持,但速度明显慢于 GPU,适合夜间批处理 |
| 启动方式 | Docker Compose 启动服务 / Python 脚本直跑 / 低代码平台导入 |
| 是否支持 API | 支持,OpenAI 兼容格式为主流做法 |
| 是否支持批量任务 | 支持,靠脚本循环 + 队列 + 日志实现 |
| 适合场景 | 文献综述准备、实验数据整理、组会材料生成、代码草稿与调试 |
| 不适合场景 | 代写论文正文、编造实验数据、替代导师判断 |
再把研究全流程按「谁负责」拆一遍,这张表决定了你后面的智能体该怎么设计:
| 流程环节 | 可以交给智能体 | 必须由人负责 |
|---|---|---|
| 选题 | 汇总领域热点、梳理已有工作分布 | 判断问题价值、确定研究边界 |
| 文献检索 | 多源检索、去重、按相关性排序 | 判断哪篇真正值得精读 |
| 文献精读 | 抽取方法、数据集、指标、结论 | 判断结论是否可信、是否有漏洞 |
| 实验设计 | 生成对照方案候选、检查遗漏变量 | 决定最终方案、承担学术责任 |
| 代码实现 | 生成草稿、写测试、解释报错 | 审查逻辑正确性、保证可复现 |
| 数据处理 | 清洗、格式转换、批量统计 | 确认数据来源合法、无篡改 |
| 写作 | 提纲、翻译、润色、格式检查 | 核心论点、创新点表述、最终定稿 |
| 投稿 | 检查格式、整理回复意见 | 回复审稿人的实质性质疑 |
这张表建议直接打印贴在工位上。任何时候你想让 AI 做表右侧的事,都先停三秒。
1.1 为什么强调「结构化输出」而不是「聊天」
新手最容易踩的坑,是把智能体当聊天窗口用:问一句、复制一段、粘贴到 Word。这种方式看起来省事,实际上没法复用,也积累不下任何资产。真正有效的做法是让智能体每次都输出固定结构的 JSON 或 Markdown 表格,然后由脚本自动汇总。这样处理 10 篇和处理 500 篇的工作量几乎一样,你只需要在最后抽查。
1.2 一个务实的预期管理
先说清楚能做到什么程度。文献摘要抽取、格式转换、代码草稿、英文润色,这些任务的正确率在人工复核的前提下足够可用。而「判断两篇论文的方法哪个更优」「这个实验结果是否支持假设」这类需要领域判断的任务,模型给出的答案只能当参考,不能当结论。把这条线划清楚,后面的工程才不会白做。
2. 适用场景与使用边界
这套方法最适合的场景有三个。第一是综述前期准备:一个方向动辄几百篇文献,人工筛选要几周,用检索 + 解析 + 结构化抽取的流水线,一天能出一版初筛表。第二是实验数据整理:跑完一批实验,日志散在几十个文件里,用脚本 + 智能体做汇总统计和异常标注,比人肉翻文件靠谱。第三是工程性写作:周报、组会 PPT 大纲、代码注释、实验记录,这些不需要灵感的文本,交出去最划算。
不适合的场景同样要说清楚。需要原创观点的正文写作不能交,因为模型的「原创」本质是高频模式的重新组合,写出来的东西看起来像论文,细看没有新意,投出去风险极高。涉及未发表数据的分析不能直接上传到第三方服务,尤其是临床试验、人体被试、企业合作数据,一旦泄露后果不可控。涉及保密项目的内容,连本地部署都要先确认单位的信息安全规定。
2.1 学术诚信是硬红线
必须把这条单独拎出来讲。用 AI 做文献检索、语言润色、代码辅助,在多数院校属于允许范围,但具体规定各校不同,投稿时期刊要求也不同,动手前务必查清楚自己单位、目标期刊、目标会议的最新政策。有几条是普遍共识:不能把 AI 生成的文字当作自己原创的学术观点提交;不能使用 AI 编造或篡改实验数据;不能伪造参考文献,模型「幻觉」出来的文献条目在形式上和真的几乎一样,必须逐条核对 DOI 或原文。参考文献造假是学术不端里最容易被抓、也最难解释的一类。
2.2 数据合规与隐私
本地部署的最大价值就在这里。凡是有保密要求的数据,一律走内网模型,不走云端接口。如果是人体被试数据、医疗影像、访谈录音,还要额外做脱敏处理,把姓名、身份证号、联系方式、机构信息替换掉再入库。团队协作时,知识库要设置权限,避免所有人都能检索到全部原始材料。这部分工作看起来繁琐,但它是整套流程能不能长期用下去的前提。
3. 环境准备与前置条件
先把地基打好。不同人的机器条件差别很大,下面给的是通用检查清单,具体版本号需要按你选用的框架文档来定,不要照抄。
首先是硬件判断。如果你的主要任务是文献解析和文本处理,一张入门级显卡甚至纯 CPU 就能跑,缺点是慢,适合挂夜间批处理。如果要用本地模型做长文档理解、代码生成,显存会明显吃紧,实测时重点关注加载模型后的剩余显存和长上下文下的峰值。磁盘空间容易被忽略:一个方向的 PDF 库加上向量库、模型权重、实验输出,几百 GB 是常态,建议单独挂一块盘。
其次是软件栈。常见组合是 Python 3.10 及以上、conda 或 uv 做环境隔离、Docker 跑服务、Git 管代码和提示词。CUDA 和显卡驱动版本必须和推理框架匹配,这是新手报错率最高的地方,装之前先看框架文档的版本对照表,不要凭感觉升级驱动。
3.1 环境检查清单
# 1. 确认显卡与驱动 nvidia-smi # 2. 确认 Python 版本 python --version # 3. 确认 Docker 可用 docker --version docker compose version # 4. 确认端口未被占用(示例端口,按实际项目替换) ss -lntp | grep 8000 || echo "8000 端口空闲"3.2 目录结构先定好
这一步很多人跳过,后面一定后悔。建议从第一天就按职责分目录,模型、输入、输出、日志、提示词各归各位:
research-agent/ ├── models/ # 本地模型权重,体积大,不进 Git ├── inputs/ │ ├── pdf/ # 原始文献 │ └── data/ # 实验原始数据 ├── outputs/ │ ├── extracted/ # 结构化抽取结果 │ └── reports/ # 汇总报告 ├── prompts/ # 提示词模板,进 Git,带版本号 ├── logs/ # 运行日志,按日期切分 ├── scripts/ # 批处理脚本 └── docker-compose.yaml提示词一定要进 Git。很多人调提示词靠改代码里的字符串,改到第十版发现还是第三版效果好,却已经找不回来了。把提示词当代码管理,是最低成本的工程习惯。
4. 智能体框架选型与本地部署启动
选型这件事,先问自己一个问题:你是想快速验证流程,还是想长期维护一套系统。前者用低代码平台,拖节点、连流程,一两个小时能跑通;后者用代码框架,可控、可测试、可版本化,但前期投入大。两条路没有优劣,只有阶段匹配。
第三条路是「本地一体机」式的整合部署:把模型服务、向量库、前端界面打包成一套 Docker Compose,一条命令起来。下面给一个通用模板,镜像名、端口、环境变量都需要按你实际选用的组件替换,不要直接复制运行。
# docker-compose.yaml # 说明:镜像与端口均为示例,请替换为实际使用的组件 services: llm: image: your-llm-server:latest ports: - "8000:8000" volumes: - ./models:/models environment: - MODEL_PATH=/models/your-model - MAX_CONTEXT=8192 deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] restart: unless-stopped vector-db: image: your-vector-db:latest ports: - "6333:6333" volumes: - ./data/vector:/data restart: unless-stopped agent-app: image: your-agent-app:latest ports: - "3000:3000" environment: - LLM_BASE_URL=http://llm:8000/v1 - VECTOR_DB_URL=http://vector-db:6333 - API_KEY=change-me depends_on: - llm - vector-db restart: unless-stopped启动与验证:
# 启动全部服务 docker compose up -d # 查看日志,确认模型加载完成 docker compose logs -f llm # 验证模型服务是否响应 curl http://127.0.0.1:8000/v1/models \ -H "Authorization: Bearer change-me"判断成功的标准很简单:/v1/models能返回模型列表,前端页面能打开,发一句测试输入能在合理时间内返回结果。如果页面打不开,先看容器状态,再看端口映射,最后看日志里的报错关键词,不要一上来就重装。
4.1 云端接口的备选方案
如果机器条件不够,用云端 OpenAI 兼容接口是最省事的路。只需要把LLM_BASE_URL和API_KEY换成服务商提供的值,上层脚本一行不用改。代价是数据要出本地,所以务必遵守第 2 节的合规要求:脱敏、分级、只用允许外传的材料。
4.2 第一次跑通不要贪多
建议第一次只跑通「读一个 PDF,输出一段结构化摘要」这一个最小闭环。跑通了再加向量库,再加批处理,再加前端。见过太多人一上来就搭全流程,卡在某个依赖冲突上两天,然后放弃。工程上的渐进式推进,比一次性设计完美方案有效得多。
5. 文献发现与结构化抽取智能体
这是整套流程里性价比最高的一个智能体。它的任务很明确:给定一个研究主题,产出一张可以直接用于综述的表格。
先说检索。不要只依赖单一来源,至少组合两到三个学术数据库,加上预印本平台。检索式要写清楚,把关键词、时间范围、文献类型都固化到配置文件里,方便复现。检索结果去重是个技术活,标题完全匹配容易漏,建议用标题归一化(去标点、转小写)+ DOI 匹配双重策略。
再说解析。PDF 解析是整个链条里最脏的一环,双栏排版、公式、表格、页眉页脚都会干扰。常见做法是先用专门的解析工具把 PDF 转成结构化文本,再交给模型抽取。解析质量直接决定下游效果,这一步偷懒,后面全白搭。
5.1 结构化抽取的实现
下面是通用模板,模型接口按 OpenAI 兼容格式调用,字段定义需要按你的研究方向调整:
import json import re from pathlib import Path import requests LLM_BASE_URL = "http://127.0.0.1:8000/v1" API_KEY = "change-me" MODEL_NAME = "your-model" EXTRACT_PROMPT = """你是一名科研助理。请从下面的论文文本中抽取信息, 严格按 JSON 输出,不要输出任何解释文字。 缺失的信息填 null,不要编造。 字段定义: - method: 论文提出的核心方法,一句话 - dataset: 使用的数据集名称,列表 - metrics: 评价指标,列表 - baseline: 对比的基线方法,列表 - limitation: 作者自述的局限性,一句话 - year: 发表年份 论文文本: {text} """ def call_llm(prompt: str) -> str: resp = requests.post( f"{LLM_BASE_URL}/chat/completions", headers={"Authorization": f"Bearer {API_KEY}"}, json={ "model": MODEL_NAME, "messages": [{"role": "user", "content": prompt}], "temperature": 0, }, timeout=180, ) resp.raise_for_status() return resp.json()["choices"][0]["message"]["content"] def extract_fields(text: str) -> dict: # 长文本截断策略:优先保留摘要 + 方法 + 结论段 text = text[:12000] raw = call_llm(EXTRACT_PROMPT.format(text=text)) # 容错:剥掉可能出现的代码块标记 raw = re.sub(r"^```(?:json)?|```$", "", raw.strip(), flags=re.MULTILINE) try: return json.loads(raw) except json.JSONDecodeError: return {"error": "parse_failed", "raw": raw[:500]} def main(): out_dir = Path("./outputs/extracted") out_dir.mkdir(parents=True, exist_ok=True) for txt_file in Path("./inputs/pdf_txt").glob("*.txt"): text = txt_file.read_text(encoding="utf-8", errors="ignore") result = extract_fields(text) result["source"] = txt_file.stem (out_dir / f"{txt_file.stem}.json").write_text( json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8", ) print(f"done: {txt_file.name}") if __name__ == "__main__": main()5.2 怎么验证抽取质量
抽完不要直接信。随机抽 20 条,人工核对method、dataset、metrics三个字段,统计准确率。低于 80% 就说明提示词或解析环节有问题,先回去调,不要拿脏数据去做综述。另一个必查项是幻觉:baseline里经常出现论文根本没提过的方法名,这类错误在综述里引用出去非常致命,务必逐条回原文确认。
5.3 输出格式统一成 Markdown 表格
JSON 适合程序处理,但你需要看的时候还是表格舒服。加一个转换脚本,把 JSON 汇总成 Markdown 表格,直接贴进笔记软件:
import json from pathlib import Path COLS = ["source", "year", "method", "dataset", "metrics", "baseline", "limitation"] rows = [] for f in sorted(Path("./outputs/extracted").glob("*.json")): d = json.loads(f.read_text(encoding="utf-8")) rows.append([str(d.get(c, "")) for c in COLS]) header = "| " + " | ".join(COLS) + " |" sep = "| " + " | ".join(["---"] * len(COLS)) + " |" lines = [header, sep] + ["| " + " | ".join(r) + " |" for r in rows] Path("./outputs/reports/summary.md").write_text("\n".join(lines), encoding="utf-8") print(f"生成 {len(rows)} 行")这套流程跑通之后,处理 50 篇和处理 500 篇的边际成本极低,这才是「把 80% 交出去」的真实含义。
6. 精读问答:本地知识库 RAG 智能体
抽取表格解决的是「横向对比」,RAG 解决的是「纵向深挖」。你想知道「这个方向过去三年在某某指标上的演进路线」,靠翻 PDF 效率极低,靠语义检索加生成回答可以几分钟出结果。
搭建分四步:文档解析成分块、分块向量化入库、查询时召回相关分块、把分块和问题一起交给模型生成带引用的回答。每一步都有坑。
6.1 分块策略决定成败
最常见的错误是固定长度切分,切到一半把实验设置和结论分到两个块里,检索时永远只能捞到半截信息。更稳妥的做法是按结构切:先按章节标题切,再在章节内按段落切,块大小控制在几百字量级,块之间保留一定重叠。表格和公式单独处理,不要和正文混在一起。
6.2 一定要带引用回链
生成的回答必须标出「这句话来自哪篇文献第几页」,否则你没法核实,也没法在写作时引用。实现上就是在每个分块的元数据里存paper_id、page、section,生成时要求模型输出引用标记。下面是一个通用的检索接口调用模板:
import requests # 说明:接口路径与参数需按实际向量库服务调整 resp = requests.post( "http://127.0.0.1:6333/collections/papers/points/search", json={ "vector": query_vector, # 由嵌入模型生成的向量 "limit": 8, # 召回条数 "with_payload": True, # 带回元数据用于引用 }, timeout=30, ) hits = resp.json()["result"] context = "\n\n".join( f"[{h['payload']['paper_id']} p.{h['payload']['page']}]\n{h['payload']['text']}" for h in hits )把context拼进提示词,明确要求「只依据给定材料回答,材料里没有就说没有」,这句话能挡掉大量幻觉。
6.3 验证 RAG 效果的三个测试
第一,问一个你已知答案的细节问题,看它是否答对且引用正确页码。第二,问一个材料里完全没有的问题,看它是否老实说「材料中没有」。第三,问一个需要跨两篇文献对比的问题,看它能否正确区分两篇的观点。三个测试都过,这个知识库才算能用。第三个测试最容易失败,因为跨文档推理对模型的指令遵循能力要求比较高。
6.4 显存与速度的现实考量
本地跑 RAG,瓶颈通常不在生成而在嵌入。首次入库时要把几百篇文献全部分块向量化,这个过程很吃算力,建议夜里挂批处理。日常查询阶段压力小很多,因为每次只处理几个分块。如果用的是 CPU 推理,单次问答等几十秒是正常的,别以为是卡死了。
7. 实验代码与写作辅助智能体
这两个放在一起讲,因为它们的共同点是「产出物必须由人审查」。
7.1 代码智能体怎么用才不出事
有效的用法是让它做三件事:写样板代码、解释报错、生成单元测试。无效的用法是让它一次性写完整个实验框架然后直接跑。前者的产出你能看懂、能验证;后者的产出你只能祈祷它是对的。
一个务实的约束是代码必须在隔离环境里执行。不要让智能体直接在你的主环境里跑它生成的脚本,尤其是涉及文件删除、网络请求、批量重命名的操作。用一个 Docker 容器或临时目录做沙箱,跑完检查输出再决定要不要合进主工程。
# 要求智能体输出可测试的最小单元,并附带断言 TEST_PROMPT = """为下面的函数生成 pytest 测试,覆盖: 1. 正常输入 2. 空输入 3. 边界值 4. 异常输入 只输出测试代码,不要解释。 函数: {code} """生成测试之后,先看测试本身对不对,再看被测函数过不过。很多人跳过第一步,结果测试和函数一起错,还以为是环境问题。
7.2 写作辅助的正确姿势
写作环节能交出去的部分是有明确边界的:把中文草稿翻译成学术英文、把口语化表述改成书面语、检查术语一致性、调整参考文献格式、生成组会汇报提纲。这些任务的共同点是不产生新观点,只做形式转换。
中文草稿永远是第一步。直接让模型「写一段关于某某的引言」,得到的多半是四平八稳的空话,全是「近年来,随着……的快速发展」这类句式,删起来比自己写还累。正确顺序是:你先写一版逻辑完整的烂中文,让模型做语言层改造,你再逐句核对技术表述有没有被改错。术语被替换错是高频问题,尤其是同一个中文词在不同子领域对应不同英文术语的情况。
7.3 参考文献核验必须自动化
模型生成的参考文献,格式往往完美,内容可能完全是编的。写完一定要跑一遍核验:提取所有 DOI,逐个请求官方接口确认存在;没有 DOI 的,用标题加作者在学术搜索引擎里查。这一步写成脚本,几十条文献几分钟跑完:
import requests def check_doi(doi: str) -> bool: """通用示例:请求 DOI 解析服务确认条目存在,具体接口按实际情况调整""" try: r = requests.get(f"https://doi.org/api/handles/{doi}", timeout=15) return r.status_code == 200 except requests.RequestException: return False核验不通过的条目,一律回原始文献手动补全,不要靠猜。
8. 接口 API 调用与批量任务编排
单次对话解决不了科研场景的问题,因为科研任务的本质是「大量重复 + 少量特殊」。所以接口和批处理才是这套方法的核心生产力。
8.1 统一接口调用
不管你用本地模型还是云端服务,尽量统一走 OpenAI 兼容格式,这样换模型只改配置不改代码。下面是最小调用示例:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer change-me" \ -d '{ "model": "your-model", "messages": [ {"role": "system", "content": "你是科研助理,只输出结构化结果"}, {"role": "user", "content": "总结这段摘要的方法与结论:..."} ], "temperature": 0 }'temperature设成 0 很重要。抽取类和转换类任务需要稳定输出,随机性只会让结果不可复现。
8.2 批量任务的工程要点
批量任务最容易出的问题不是模型出错,而是跑到第 137 个任务时整个脚本崩了,前面的结果全丢。所以批处理脚本必须做到三点:每个任务独立落盘、失败记录单独存、支持断点续跑。
import json import time from pathlib import Path import requests IN_DIR = Path("./inputs/pdf_txt") OUT_DIR = Path("./outputs/extracted") FAIL_LOG = Path("./logs/failed.jsonl") OUT_DIR.mkdir(parents=True, exist_ok=True) FAIL_LOG.parent.mkdir(parents=True, exist_ok=True) MAX_RETRY = 3 SLEEP_BETWEEN = 1.0 def process_one(path: Path) -> bool: out_file = OUT_DIR / f"{path.stem}.json" if out_file.exists(): return True # 断点续跑:已完成的直接跳过 for attempt in range(1, MAX_RETRY + 1): try: text = path.read_text(encoding="utf-8", errors="ignore")[:12000] resp = requests.post( "http://127.0.0.1:8000/v1/chat/completions", headers={"Authorization": "Bearer change-me"}, json={ "model": "your-model", "messages": [{"role": "user", "content": text}], "temperature": 0, }, timeout=180, ) resp.raise_for_status() content = resp.json()["choices"][0]["message"]["content"] out_file.write_text(content, encoding="utf-8") return True except Exception as exc: # noqa: BLE001 if attempt == MAX_RETRY: with FAIL_LOG.open("a", encoding="utf-8") as f: f.write(json.dumps({"file": path.name, "error": str(exc)}, ensure_ascii=False) + "\n") return False time.sleep(SLEEP_BETWEEN * attempt) # 退避重试 return False def main(): files = sorted(IN_DIR.glob("*.txt")) ok = sum(process_one(f) for f in files) print(f"完成 {ok}/{len(files)},失败详见 {FAIL_LOG}") if __name__ == "__main__": main()这个脚本的几个细节值得注意:已完成的任务直接跳过,重跑不会重复烧算力;失败任务写 jsonl,方便事后单独重跑;重试间隔递增,避免服务端被打爆。
8.3 并发要克制
看到批处理跑得慢,第一反应往往是开并发。但本地模型服务的并发能力有限,盲目开 8 个线程的结果通常是全部超时。稳妥做法是先测单请求耗时,再开 2 到 4 个并发观察是否有收益,没有收益就老老实实串行。串行跑一夜能完成的任务,不值得为了省两小时去调并发。
9. 资源占用、性能观察与常见问题排查
这一节是实战里最常被跳过、也最容易卡住人的部分。
9.1 显存怎么看
观察显存最直接的方式是nvidia-smi,但不要只看一眼,要在模型加载完成、处理长文本、批处理并发时分别看。峰值出现在哪一步,决定了你该优化什么。
# 每秒刷新一次,观察峰值 nvidia-smi --query-gpu=memory.used,memory.total,utilization.gpu \ --format=csv -l 1如果显存不够,调整顺序建议是:先降上下文长度,再降批量大小,再考虑量化版本,最后才换更小的模型。很多人一上来就换模型,结果效果掉得厉害,其实只是上下文开太大了。CPU 推理可以作为兜底方案,适合夜间跑批,但要接受单条任务耗时成倍增加的现实。
9.2 影响性能的几个变量
上下文长度对显存和耗时的影响是超线性的,长文档处理要格外小心。批量大小主要影响吞吐,不影响单条质量。输出长度直接影响耗时,抽取任务要明确限制输出字段数,别让模型自由发挥写一大段。向量库的检索条数也会影响生成阶段的上下文长度,召回 20 条和召回 5 条的差别很明显。
9.3 常见问题排查表
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 服务启动后页面打不开 | 端口被占用或容器未起来 | docker compose ps+ 查端口 | 换端口或重启服务 |
| 模型加载报显存不足 | 上下文设太大或模型太大 | nvidia-smi看峰值 | 降上下文、降批量、用量化版 |
| PDF 解析出来是乱码 | 编码或字体嵌入问题 | 打开原始 PDF 对比 | 换解析工具,或先转图片再 OCR |
| 抽取结果字段大量为 null | 提示词字段定义不清或文本被截断 | 打印送入模型的文本 | 优化提示词,调整截断策略 |
| 参考文献查不到 | 模型幻觉编造 | 逐条查 DOI | 删除并回原文补全 |
| 批处理跑一半崩了 | 无断点续跑或网络抖动 | 看failed.jsonl | 加已完成跳过和重试机制 |
| API 调用 401 | 密钥错误或未带 Header | 检查请求头 | 修正Authorization |
| 回答总是很空泛 | 检索没召回相关内容 | 打印召回分块 | 调分块策略和召回条数 |
| 同一输入两次结果不同 | temperature未设 0 | 检查请求参数 | 抽取类任务固定为 0 |
| 批处理越跑越慢 | 内存泄漏或日志膨胀 | 看进程内存曲线 | 分批重启,日志按日切分 |
排查的基本原则是从下往上:先确认服务活着,再确认接口通,再确认输入正确,最后才怀疑模型能力。绝大多数「模型不行」的问题,实际是输入喂错了。
10. 最佳实践与执行节奏
最后讲怎么把这件事长期跑下去,而不是新鲜三天就放弃。
第一周只做一件事:跑通「一个 PDF 进,一段结构化 JSON 出」。这一个闭环打通,你就理解了整条链路的每个环节,后面加什么都是在这个骨架上长肉。第二周加向量库,做文献问答。第三周把批处理脚本写好,让它能整夜跑。第四周才开始碰写作辅助。这个节奏看起来慢,但每一步都可用,比一次性搭全套然后全部半途而废快得多。
配置管理上,提示词进 Git 并打版本号,模型参数写进配置文件而不是硬编码,输出目录按日期切分,日志保留至少一个月。这些是工程习惯,不是洁癖——当你三周后想复现某次结果,能不能快速找到当时的提示词和参数,决定了你要花十分钟还是两天。
成本控制上,给批量任务设上限。跑之前先拿 10 个样本试,估算总耗时和费用,确认没问题再全量跑。云端接口尤其要注意,一个不小心跑出几百块账单的事故并不罕见。本地部署则要算电费和机器占用,夜间跑批是最经济的安排。
人工复核环节不能省。抽取结果抽检、引用回链核对、代码测试审查、参考文献 DOI 验证,这四道关卡每一道都要保留。AI 负责把量做上去,人负责把质量守住,这个分工不能反过来。
合规上再强调一次:涉及未发表数据、人体被试、企业合作材料的,一律本地部署并做脱敏;AI 生成的内容在提交前必须经过你自己的判断和改写;投稿前查清楚目标期刊对 AI 使用的披露要求,该声明就声明。这些事情做在前面,比出了问题再解释省事得多。
如果只能记住一句话,那就是:把检索、解析、整理、初稿交给智能体,把判断、创新、责任留给自己。前者是 80%,后者才是读研读博真正要练的东西。建议把第 1 节那张职责表保存在笔记里,每次想让 AI 代劳之前先对一眼。