这是"药企AI Agent落地三部曲"的第1篇。三篇分别解决:
本篇:文献调研与竞品情报——信息淹没,人工整理效率极低
第2篇:临床方案与注册文件——长文档修改联动,格式校验耗时
第3篇:跨部门数据孤岛——多部门数据不通,协作靠手工转格式
本三部曲完整记录了从单Agent逐步扩展到多Agent协作系统的过程。所有工具全线开源,可私有化部署。
1. 问题描述
先说痛点。
在药企做过竞品情报的人应该都有经历:每周花20多个小时在文献筛选和整理上,真正坐下来做分析判断的时间不到5小时。大部分时间都耗在"找"这个动作上——翻PubMed、刷ClinicalTrials.gov、盯竞品官网、查财报、看会议摘要……
几个数字比较说明问题:
竞品动态追踪靠人工,平均滞后2周。等你看到人家发了新闻稿,其实临床数据早就在会议摘要或者SEC filing里了
一份竞品周报花4小时制作,其中至少3小时是花在找数据、对齐口径、格式调整上
同一段数据在不同报告里重复整理。临床部的报告、注册部的报告、BD的报告,各写各的,数据来源都不一样
每周检索文献200+篇,真正有用的不超过20篇,但是你还得一篇篇点进去看
再说说硬约束,药企场景跟互联网场景最大的区别就在这儿:
数据不能出内网。患者数据、临床数据、商业分析——这些碰都不能碰外部API。用GPT-4o?数据过境的合规风险你扛不住
必须满足GxP合规。GxP不只是个标签,它意味着你得有审计追踪、版本控制、数据溯源
已有PostgreSQL基础设施。核心业务跑在PG上面(LIMS、CTMS、eTMF),不可能为了一个AI项目单独搞一套新的数据库集群,运维团队会把你骂死
全线需要开源可私有化部署。自主可控不是喊口号,是药企IT部门审批的红线。不开源?不部署在内网?直接否掉
说白了,你要在"数据不出内网 + GxP合规 + 复用现有基础设施 + 全线开源"这四个约束条件下,搭一套能干活的AI系统。
我之前帮另一家药企评估过一套商业化方案——某大厂的"智能知识管理平台",功能演示确实炫酷。但是一问到数据部署,对方说"可以部署私有云版本",再一问价格,光授权费就够买三台GPU服务器了。更关键的是,它的文档解析引擎不支持药企常用的双栏排版论文格式,改都没法改。这种"黑盒"方案在药企根本过不了合规审查。
所以我给自己定了个原则:所有组件必须开源、可审计、可二次开发。出了问题自己能改,不用等供应商排期。
2. 方案设计
先说一个我踩过的最大的坑:Agent的核心不是对话能力,是工作流能力。
很多人一上来就想"做一个AI聊天机器人,能回答医药问题就行了"。这是把Agent当搜索引擎用。真正有价值的Agent,是一条完整的工作链:检索→解析→结构化→生成→学习。每一步都能自动化,串起来才是"做情报",而不只是"搜信息"。
按这个思路,我把整个系统拆成了几个模块,每个模块选一个最合适的开源方案。
技术选型
组件 | 选型 | 开源协议 | 选择理由 | 考虑过但没选 |
|---|---|---|---|---|
Agent平台 | Dify.ai | 开源(GitHub 90k+ Star) | 原生支持多Agent编排和Workflow,支持私有化Docker部署 | Langflow(可视化好但是多Agent支持弱)、Flowise(轻量但不适合企业级) |
向量数据库 | PGVector | PostgreSQL扩展(开源) | 药企已有PostgreSQL基础设施,加个扩展即可 | Milvus(功能更强但是需要独立部署运维,对这个规模偏重) |
LLM底座 | Qwen3.5(35B-A3B) | Apache 2.0 | 最新开源旗舰,Apache 2.0,中文能力顶级,MoE架构推理高效,可私有化部署 | GPT-4o(数据出境合规风险)、GLM-4(中文略逊Qwen) |
记忆层 | Mem0(开源版) | Apache 2.0 | 分层记忆,Dify原生集成 | LangChain Memory(功能弱)、自建(开发成本高) |
重排序 | BGE-Reranker-v2-m3 | MIT | BAAI开源,中英文检索精度优秀 | Cohere Rerank(商业API,数据出境风险) |
文档解析 | pdfplumber + MinerU | 开源 | MinerU专攻学术论文双栏排版,上海AI Lab开源 | Unstructured.io(通用性强但是学术论文解析不如MinerU精准) |
文档存储 | RustFS | 开源 | 分布式对象存储,S3兼容,私有化部署 | MinIO(开源优秀但是核心维护团队在海外) |
报告生成 | Jinja2 + python-docx | 开源 | 模板引擎+Word输出 | — |
GPU预算升级建议:如果GPU资源充足,可以把Qwen3.5-35B-A3B升级到Qwen3.5-397B-A17B(总参数397B,激活参数17B),中文理解和长文本能力更好,不过需要4×A100 80G起步。初期建议先上35B-A3B,跑通流程再说。
为什么选Dify不选Langflow?Dify的Workflow编排能力是真的强,条件分支、并行节点、循环——这些在复杂的检索工作流里都是刚需。Langflow可视化确实好看,但是一旦涉及多Agent编排就不太够用了。
为什么用PGVector不单独上Milvus?因为药企已经有跑着的PostgreSQL了。加个扩展就能做向量检索,不需要额外部署一套新系统。800篇文献的向量规模,PGVector完全够用,杀鸡不用牛刀。
Qwen3.5-35B-A3B怎么样?MoE架构,总参数35B,但是激活参数只有3B。这意味着什么?推理的时候只需要跑3B的参数,速度很快,1-2张A100 40G就能跑得很流畅。显存占用大概在70GB左右(因为要加载全部权重,这个后面Step 2详细说)。初期建议先上这个,够用。
架构图
整个系统就一个Agent,干一件事:把散落在各处的文献和情报数据收集起来、解析、结构化、生成周报,然后记住用户喜欢什么。不复杂,但是每一步都跑通了。
3. 实施过程
下面按天拆解。所有命令和代码都可以直接复制跑。
Step 1:Dify环境搭建(Day 1)
Dify用Docker部署,十分钟就能跑起来。
# 克隆Dify仓库 git clone https://github.com/langgenius/dify.git cd dify/docker # 复制环境配置文件 cp .env.example .env # 编辑.env,改几个关键参数: # SECRET_KEY=你随机生成的密钥(建议用 openssl rand -hex 32) # DB_PASSWORD=设个强密码 # REDIS_PASSWORD=同上 # LOG_LEVEL=WARNING(默认INFO,日志量太大,排查问题的时候再改成DEBUG) # MAX_WORKERS=4(Celery Worker并发数,文献量大的时候可以调高到8) # WEB_API_RATE_LIMIT=60(API限流,防止被刷爆) # 启动所有服务 docker compose up -d启动完访问 http://你的内网IP/install,完成初始化设置,创建管理员账号。整个过程跟装个WordPress差不多。
踩坑记录
坑1:Docker镜像拉取很慢或者直接超时。内网环境没有外网访问权限,docker compose pull直接卡死。解决办法是提前在内网的镜像仓库(Harbor)里把Dify的几个镜像同步好,或者找运维要离线安装包(tar包),用 docker load 手动导入。别到了现场才发现拉不了镜像,那就尴尬了。
坑2:启动之后浏览器访问不了。最常见的原因有两个:一是防火墙没开端口,firewall-cmd --add-port=80/tcp --permanent 然后 firewall-cmd --reload;二是SELinux阻止了Docker的网络访问,临时测试可以用 setenforce 0 关掉,生产环境建议配置SELinux策略而不是直接关。
官方文档:[Dify Docker部署指南](https://docs.dify.ai/getting-started/install-self-hosted/docker-compose)
Step 2:Qwen3.5模型部署(Day 1-2)
这一步需要有GPU的服务器。我用的是vLLM做推理服务,部署完暴露一个OpenAI兼容的API接口,Dify直接对接。
# 安装vLLM(注意版本,必须>=0.6.0才支持Qwen3.5的MoE架构) pip install vllm>=0.6.0 # 启动Qwen3.5-35B-A3B推理服务(MoE架构,1-2张A100 40G即可) python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3.5-35B-A3B \ --host 0.0.0.0 \ --port 8000 \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9 \ --max-model-len 32768几个关键参数说明一下:
--gpu-memory-utilization 0.9:默认是0.9,意思是允许vLLM用90%的显存。如果你的GPU还要跑别的服务,需要调低这个值,比如设0.7
--max-model-len 32768:最大上下文长度。这个值要根据显存来调,32K在双卡A100 40G上是跑得动的。如果发现OOM,先试24576或者16384
--tensor-parallel-size 2:张量并行数,用几张卡就设几
跑起来之后,去Dify后台:设置→模型供应商→添加OpenAI-API兼容→填入地址 http://内网IP:8000/v1。模型名称填 Qwen/Qwen3.5-35B-A3B。
测一下连通性:
curl http://内网IP:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{"model":"Qwen/Qwen3.5-35B-A3B","messages":[{"role":"user","content":"你好"}]}'返回正常JSON就说明没问题了。
踩坑记录
坑1:MoE模型的显存占用跟你想的不一样。Qwen3.5-35B-A3B虽然激活参数只有3B,推理速度接近3B模型,但是显存占用不能按3B来算——全部35B的权重都得加载到显存里,实际占用大概70GB+。也就是说,如果你只有2×A100 40G(总共80GB),留给KV Cache的空间只有10GB左右,长文本场景可能会OOM。解决办法:要么降 --max-model-len,要么用3张卡分摊。
坑2:vLLM版本兼容性。确保vLLM版本 >= 0.6.0,老版本(比如0.4.x)不识别Qwen3.5的MoE架构,启动直接报 Model architecture not supported 的错误。安装完之后先用 pip show vllm 确认一下版本号。
官方文档:[vLLM文档](https://docs.vllm.ai/)
[Qwen模型文档](https://qwenlm.github.io/)
Step 3:PGVector向量数据库配置(Day 2)
不用额外部署任何东西,直接在你现有的PostgreSQL里加个扩展。
# 安装PGVector扩展(根据你的PG版本选对应包名,注意大版本要匹配) sudo apt install postgresql-15-pgvector # 连接你的数据库,执行以下SQL psql -U your_user -d your_database-- 启用PGVector扩展 CREATE EXTENSION IF NOT EXISTS vector; -- 创建文献向量表 -- 注意维度要跟embedding模型匹配!Qwen3.5配bge-m3的话维度是1024,不是1536 CREATE TABLE document_embeddings ( id SERIAL PRIMARY KEY, content TEXT, metadata JSONB, embedding vector(1024) -- bge-m3输出1024维向量 ); -- 创建向量索引,加速余弦相似度检索 -- 注意:ivfflat索引需要先有数据才能建!空表建索引会报错 -- 建议先灌一批数据再建索引,或者改用HNSW索引(支持空表建索引,但是构建更慢) CREATE INDEX ON document_embeddings USING hnsw (embedding vector_cosine_ops);然后在Dify后台配置外部知识库连接,填入你的PostgreSQL连接信息。这样Dify的Agent就能直接检索你库里的文献了。
踩坑记录
坑1:PGVector版本要跟PostgreSQL大版本匹配。PG15装PG14的vector扩展会报错 could not open extension control file,因为控制文件路径是按大版本分的。安装之前先 psql --version 确认PG版本,然后装对应的pgvector包。
坑2:ivfflat索引在空表上建不了。第一次跑建表SQL顺手建索引的话会直接报错。解决办法:先灌一批数据进去再建ivfflat索引,或者像上面SQL里写的,改用HNSW索引。HNSW支持空表建索引,好处是后面新增数据不需要重建索引,坏处是初次构建比ivfflat慢不少。800篇文献的规模,两种索引差别不大,看个人偏好。
官方文档:[PGVector](https://github.com/pgvector/pgvector)
Step 4:文档解析流水线搭建(Day 2-3)
这一步我踩了不小的坑。
一上来我用pdfplumber解析论文PDF,结果双栏排版的论文表格直接乱成一锅粥。测了50篇论文,表格解析准确率只有67%。后来换上了MinerU(上海AI Lab开源的项目,专攻学术论文解析),准确率直接拉到89%。
但是MinerU也不是万能的,大概5%的复杂表格(合并单元格特别多那种)还是会错位。最后定下来的方案是两个一起用:MinerU为主,pdfplumber补刀。
# 安装MinerU(注意full版本会拉很多依赖,包括paddleocr) pip install magic-pdf[full] # 安装pdfplumber作为补充 pip install pdfplumberfrom magic_pdf.data.data_reader_writer import FileBasedDataWriter, FileBasedDataReader from magic_pdf.data.dataset import PymuDocDataset # 读取PDF reader = FileBasedDataReader('') pdf_bytes = reader.read('paper.pdf') ds = PymuDocDataset(pdf_bytes) # 解析为Markdown(表格、公式结构都保留) md_content = ds.apply( PdfFlashParsePipeline(), ImageWriter('/tmp/images'), )解析完的Markdown不是直接就能用的。页眉页脚要清掉,引用格式要统一,参考文献要剥离——这些预处理做完再向量化入库。别问我怎么知道的,第一次入库的数据检索出来全是页眉和页码,差点以为自己搞了个假系统。
踩坑记录
坑1:MinerU需要额外的GPU显存。MinerU内部用了布局检测模型(基于PaddleOCR),需要额外的2-4GB显存。如果跟LLM推理服务跑在同一张卡上,需要预留这部分显存。建议把MinerU的任务放到非高峰时段批量跑,或者单独用一张卡跑解析,不要跟vLLM抢显存。另外 magic-pdf[full] 在离线环境安装的时候,需要提前准备好paddleocr等依赖的whl包,不然装到一半卡住就很烦。
坑2:解析速度比想象的慢。MinerU解析一篇10页的论文大概需要30-60秒(GPU模式),800篇文献的初始入库需要预留半天时间。别把初始入库安排在上线前一天,老老实实提前两三天开始跑。如果CPU模式的话更慢,一篇可能要5-10分钟。
官方文档:[MinerU](https://github.com/opendatalab/MinerU)
Step 5:Dify Workflow配置——检索+摘要(Day 4-5)
这是Agent的核心工作流。在Dify里用Workflow编排,搭了一条这样的流水线:
开始节点 → 接收用户输入(竞品名称/靶点/关键词)
并行检索节点(三路同时跑):
PubMed E-utilities API(通过HTTP请求节点)
Semantic Scholar API
内部知识库(PGVector)
重排序节点 → BGE-Reranker对合并结果排序
摘要生成节点 → Qwen3.5按竞品维度组织输出
结束节点 → 返回结构化摘要
BGE-Reranker的部署也在这一步完成。用Xinference起一个推理服务就行:
# 安装xinference(建议>=0.10.0,低版本可能找不到bge-reranker-v2-m3) pip install xinference>=0.10.0 # 启动Reranker服务 xinference-local --host 0.0.0.0 --port 8001 # 在xinference web UI中启动 bge-reranker-v2-m3 模型 # 或者直接命令行 xinference launch --model-name bge-reranker-v2-m3 --model-type rerankerDify的Workflow里用HTTP请求节点调用这个Reranker接口,对三路检索的合并结果做二次排序。实际跑下来效果很明显——不加重排的时候,Top 10结果里大概6-7篇是相关的;加上之后能到9篇以上。
有一个配置细节要注意:Dify Workflow里HTTP请求节点的超时时间要调大一些,默认值太短了。PubMed API响应慢的时候可能要10-20秒才返回,建议把超时设到30秒。不然高峰期检索直接超时失败,报错信息还不太明显,排查了半天才发现是超时问题。
这里有两个坑说一下:
第一个坑:PubMed API返回的摘要太短了,基本就是一两句话,根本不够做分析。解决办法是通过DOI去抓CrossRef的全文摘要,信息量翻了好几倍。如果CrossRef也没有全文摘要,退而求其次去Semantic Scholar拿 enriched abstract。两条路都试了,基本上90%以上的论文都能拿到足够的摘要信息。
第二个坑是多源检索的去重。PubMed和Semantic Scholar有重叠,同一个论文可能两边都检索到。我用DOI+标题双重匹配做去重,先匹配DOI(精确),DOI缺失的再用标题模糊匹配。标题匹配有个细节:要先把标题统一转成小写、去掉标点,不然"Phase III"和"Phase 3"会匹配不上。
另外还有一个容易忽略的问题:三路检索返回的结果格式不统一。PubMed返回的是XML格式,Semantic Scholar返回JSON,内部知识库返回的又是另一种结构。在做重排之前,必须先写一个适配层把格式统一掉——统一成 (title, abstract, source, doi) 这样的标准结构,然后再喂给Reranker。这个问题一开始没处理好,Reranker直接报错了,因为输入格式不对。
踩坑记录
坑1:Xinference版本太低找不到模型。如果xinference版本低于0.10.0,xinference launch 启动bge-reranker-v2-m3的时候会报模型不存在的错误。升级一下就好:pip install --upgrade xinference>=0.10.0。
坑2:三路检索结果格式不统一。这个问题上面提了,再多说一句——PubMed的XML解析用Python的 xml.etree.ElementTree 就行,不需要额外装库。关键是把所有来源的字段名对齐,不然后面重排和生成都会出问题。这个问题后面单独展开说,第2篇会讲到多数据源适配的通用做法。
官方文档:[Dify Workflow指南](https://docs.dify.ai/guides/workflow)
[FlagEmbedding(BGE-Reranker)](https://github.com/FlagOpen/FlagEmbedding)
Step 6:竞品周报自动生成(Day 6-8)
检索完了得输出。我一开始犯了一个经典错误:直接让Qwen3.5写"文献综述"。结果生成的周报又长又学术,根本没人看。
后来调整了prompt方向——"写给决策者看的简报,每个信息点不超过2行"。效果马上就出来了。
周报模板用Jinja2定义,最终用python-docx输出Word文档:
from docx import Document from docx.shared import Pt, Inches from docx.enum.text import WD_ALIGN_PARAGRAPH # ========== 样式预定义(很重要,不然默认样式很丑)========== def setup_styles(doc): """定义Word文档样式,这一步不能省""" # 设置正文字体 style = doc.styles['Normal'] font = style.font font.name = '微软雅黑' font.size = Pt(10.5) # 设置标题样式 for i in range(4): heading_style = doc.styles[f'Heading {i}'] heading_style.font.name = '微软雅黑' heading_style.font.color.rgb = None # 用默认黑色,别搞花里胡哨的 # 设置列表样式 list_style = doc.styles['List Bullet'] list_style.font.name = '微软雅黑' list_style.font.size = Pt(10.5) list_style.paragraph_format.space_after = Pt(4) # 段后间距,别挤在一起 # Jinja2周报模板 template = """ # 竞品情报周报 - {{ week_range }} ## 1. 管线进展 {% for item in pipeline_updates %} - **{{ item.company }}** {{ item.drug }}: {{ item.update }} {% endfor %} ## 2. 临床数据 {% for item in clinical_data %} - **{{ item.trial_id }}** {{ item.phase }}期 | {{ item.indication }} - 主要终点: {{ item.primary_endpoint }} - 结果: {{ item.result }} {% endfor %} ## 3. 专利动态 {% for item in patent_updates %} - **{{ item.company }}**: {{ item.detail }} {% endfor %} ## 4. 合作交易 {% for item in deals %} - **{{ item.company_a }}** × **{{ item.company_b }}**: {{ item.detail }} {% endfor %} """ # 用python-docx输出Word doc = Document() setup_styles(doc) # 先设样式 doc.add_heading('竞品情报周报', 0) for section in report_data: doc.add_heading(section['title'], level=1) for item in section['items']: doc.add_paragraph(f"{item['company']}: {item['detail']}", style='List Bullet') doc.save('weekly_report.docx')踩坑记录
坑1:Jinja2模板里的中文在python-docx里可能显示乱码。确保模板文件和docx的编码都统一为UTF-8。如果模板文件是用Windows记事本编辑保存的,可能是GBK编码,读进来就乱码了。用VS Code或者任何正经编辑器打开,另存为UTF-8格式就行。
坑2:让LLM按模板生成结构化数据的时候,输出格式经常不稳定。有时候多一个字段,有时候少一个字段,偶尔还会自己加一个"总结"板块。这个问题一两句话说不清楚,后面第2篇会单独展开说怎么用Pydantic做格式校验,确保LLM输出的JSON严格符合模板定义的结构。这里先记住一个原则:不要相信LLM的输出格式,一定要加校验。
官方文档:[python-docx](https://python-docx.readthedocs.io/)
Step 7:接入Mem0记忆层(Day 9-10)
这一步是让Agent从"工具"变成"助手"的关键。
Mem0能记住用户的偏好。比如"重点关注PD-L1和ADC方向的竞品动态"、"核心竞品公司是阿斯利康、罗氏、默沙东"。Agent下次检索的时候就会自动过滤无关文献,优先推送用户关心的内容。
# 部署Mem0 git clone https://github.com/mem0ai/mem0.git cd mem0 pip install -e .这里有一个非常重要的配置:Mem0默认会调用OpenAI的API做记忆提取。如果不改配置,你的数据就直接走外网了,药企数据出境这个事就不用我多说了吧。必须把config指向本地部署的模型:
# config.yaml llm: provider: openai config: model: "qwen3.5-35b-a3b" base_url: "http://内网IP:8000/v1" # ↑ 必须指向本地vLLM,绝对不能走OpenAI embedder: provider: openai config: model: "bge-m3" base_url: "http://内网IP:8001/v1" # 记忆类型选择 # 默认用vector(向量检索),对于偏好类信息够用了 # 如果涉及复杂的事实关系(比如"阿斯利康的PD-L1管线跟默沙东有合作"这种), # 可以用graph模式,但是需要额外配置图数据库,初期不建议折腾 memory_type: vector # 记忆检索相关度阈值 # 默认0.5,对中文场景偏高,建议调到0.3-0.4 # 不然很多相关的记忆检索不出来 threshold: 0.35from mem0 import Memory m = Memory() # 记录用户偏好 m.add("重点关注PD-L1和ADC方向的竞品动态", user_id="researcher_1") m.add("核心竞品公司:阿斯利康、罗氏、默沙东", user_id="researcher_1") # 检索时自动结合记忆 results = m.search("最新临床数据", user_id="researcher_1")接上Mem0之后,Agent推送的文献相关度明显提升。之前推10篇大概6篇有用,现在8篇都有价值。
踩坑记录
坑1:Mem0默认调OpenAI API做记忆提取。这个坑再强调一次——Mem0的config里如果不显式指定 base_url 指向本地模型,它会默认调OpenAI的API。数据直接出境,合规风险巨大。部署完之后第一件事就是检查网络请求,确认没有任何外网调用。
坑2:记忆检索的相关度阈值。默认阈值是0.5,对中文场景偏高,很多相关的记忆检索不出来。建议调到0.3-0.4之间。我最后用的是0.35,既能召回相关记忆,又不会引入太多噪音。如果你的用户群体比较大、记忆条目很多,可能需要根据实际效果微调。
官方文档:[Mem0](https://docs.mem0.ai/)
Step 8:RustFS文档存储(Day 10)
最后把文档存储也搭好。RustFS是个开源的分布式对象存储,S3兼容接口,Docker一行就起来了。
# Docker部署RustFS # 注意:必须通过环境变量设置access key和secret key, # 不然默认匿名访问,生产环境绝对不能这样 docker run -d \ --name rustfs \ -p 9000:9000 \ -p 9001:9001 \ -e RUSTFS_ACCESS_KEY="your-access-key" \ -e RUSTFS_SECRET_KEY="your-secret-key" \ -v /data/rustfs:/data \ rustfs/rustfs:latest # 创建存储桶(需要配置好认证信息) aws s3 mb s3://reports \ --endpoint-url http://内网IP:9000 \ --region us-east-1 # 周报自动上传 aws s3 cp weekly_report.docx s3://reports/2026-W28/ \ --endpoint-url http://内网IP:9000生成完的周报自动传到RustFS里,按"年份-周数"分目录存放。谁、什么时候、生成了一份什么报告,全都有记录。GxP审计追踪?这不就来了嘛。
踩坑记录
坑1:boto3连接RustFS的签名问题。RustFS的S3兼容接口跟AWS S3有细微差异,用Python的boto3客户端连接的时候,需要显式设置 signature_version='s3v4',不然上传大文件(比如几十MB的报告)会报签名错误。代码里加一行配置就行:
```python
import boto3
from botocore.config import Config
s3_client = boto3.client(
's3',
endpoint_url='http://内网IP:9000',
awsaccesskey_id='your-access-key',
awssecretkey_id='your-secret-key',
config=Config(signature_version='s3v4')
)
```
坑2:RustFS本身不做跨节点备份。单节点部署的话,硬盘挂了数据就没了。建议配合rsync做定期备份到另一台机器,写个crontab每天凌晨跑一次。生产环境别偷懒,数据丢了可没人帮你。
官方文档:[RustFS](https://rustfs.com/docs/)
4. 运行结果
部署前后对比,用数据说话:
指标 | 部署前 | 部署后 | 变化 |
|---|---|---|---|
竞品周报生成时间 | 4小时 | 30-40分钟 | ↓83-85% |
竞品动态追踪延迟 | 平均2周 | 2-3天获取 | ↓75-80% |
每周文献整理耗时 | 20h | 6-8h | ↓60-70% |
文献检索相关度 | 人工筛选~75% | Agent检索85-88%* | ↑10-13% |
周报覆盖信息量 | 3-5个竞品 | 10+个竞品 | ↑200% |
文献检索相关度数据说明:85-88%这个数据是基于200篇文献的抽样测试,Agent检索结果经人工逐篇复核后统计的相关率。不是Agent自动判定的,是真有人一篇篇看过了。
最让我意外的变化是周报覆盖信息量。之前人工只能盯3-5个核心竞品,因为精力有限。现在Agent同时跑10+个竞品的动态,人只需要看Agent整理好的简报做判断。覆盖面翻了2倍,人反而更轻松了。
数据方法论说明:以上数据取4周实际运行的每周平均值。测试环境为2×A100 40G + 64GB内存,文献库规模800篇。不同硬件配置和文献规模下数据会有差异。比如文献库扩到2000篇以上,检索延迟和解析时间都会上升,GPU显存也可能成为瓶颈。
资源消耗方面:
GPU:Qwen3.5-35B-A3B推理需要1-2×A100 40G(MoE架构,显存占用约70GB)
存储:800篇文献解析后约15GB(含向量数据)
内存:Dify + Mem0 + BGE-Reranker合计约16GB
说实话这个成本在药企IT预算里不算高。一台带双卡的GPU服务器就能跑起来,比买一套商业情报数据库便宜多了。
5. 总结与下一步
踩坑总结
10天跑下来,总结几个经验:
文档解析是地基。MinerU比pdfplumber更适合学术论文,但是两者互补使用效果最好。解析质量直接决定后面所有环节的产出质量。垃圾进垃圾出,这话在Agent场景里尤其准。
复用已有基础设施。PGVector不需要额外部署新系统,药企已经有PG了就直接加扩展。别动不动就想"上一套新的向量数据库",运维成本和稳定性都得考虑。
记忆层是Agent从"工具"变成"助手"的关键。没有Mem0的时候,Agent就是个检索工具,每次都从头来。接上之后,Agent开始记住你关心什么、不关心什么,推送越来越精准。这个差别很大。
报告模板决定输出质量。prompt写得再好,不如模板定义得清晰。把"生成一份竞品周报"改成"按管线进展、临床数据、专利动态、合作交易四个板块,每条不超过2行",输出质量直接上了一个台阶。
多源检索一定要做去重和格式适配。PubMed和Semantic Scholar的重叠率比想象的高,不去重的话周报里同一条信息出现两次,很丢人。而且各家返回的数据格式不一样,必须先统一格式再做后续处理。
核心洞察
做了这个项目之后我最大的感受是:Agent的价值不是替你检索,是替你"做情报"。
检索只是第一步。结构化、判断、呈现、学习——这些串起来才是完整的工作流。很多药企也上了AI工具,但是效果不好,问题不在工具本身,在于只做了"点"没做"链"。弄个ChatGPT接口让大家问答,这不叫Agent,这叫换了个搜索引擎。
真正的Agent是一条链:它知道去哪找、找到了怎么解析、解析完怎么结构化、结构化完怎么呈现给对的人、呈现完还能记住下次该怎么做得更好。每一步都自动化,串起来才有价值。
还有一点想说的:不要追求一步到位。我见过太多项目死在"等我们把架构设计完美了再上线"这句话上。我的做法是先跑通最小可用版本——哪怕周报格式丑一点、覆盖范围小一点,先让业务方用起来。用起来之后反馈自然就来了,迭代方向也就清楚了。从Day 1到Day 10,这套系统经历了三版大改,每一版都是根据实际使用反馈调的。
另外一个心得:药企的人其实很愿意接受新工具,前提是这个工具真的能帮他们省时间。不要跟业务部门讲什么"AI赋能"、"数字化转型"这种空话。你就跟他说"这个周报以后半小时就能出,你以前花4个小时"——他立马就愿意试了。
系列预告
文献情报解决了,但是药企的痛点远不止这一个。100多页的临床方案改一个入组标准,样本量、统计方法、安全性章节要联动修改,手动检查交叉引用平均遗漏8-12处。
下一篇,我们用同一套架构,给Agent加上长文档理解和参数联动能力。
本系列基于真实行业痛点,技术方案基于开源工具和通用架构设计,可迁移至真实场景。文中涉及的工具选型仅代表个人实践偏好,不构成商业推荐。