2026/10/13 11:55:39

论文AI助手实战:RAG结合代码执行实现科研数据分析

论文AI助手实战:RAG结合代码执行实现科研数据分析 论文阅读正在成为一项越来越“重”的体力活。过去我们读一篇论文需要自己翻图表、找数据、核对实验设置现在虽然有大模型可以做问答但多数助手只停留在“聊天”层面——你问它“这篇论文的核心方法是什么”它能答你再问“把表2里不同模型的F1分数做个柱状图并计算相对提升幅度”它就只能泛泛而谈给不出可复现的结果。真正要被解决的不是“读懂论文”的问题而是“把论文里的数据真正用起来”的问题。论文AI助手如果只带检索问答本质上还只是一个带引用的搜索引擎只有让它具备“跑分析”的能力才能把论文从静态PDF变成一个可交互的科研数据工作台。这篇文章会带你从零搭建这样一个助手先用向量库把论文内容变成可检索的知识再让大模型通过函数调用执行Python代码完成数据统计、可视化甚至简单复现分析。读完你会理解传统RAG问答的边界在哪里也会拿到一套可以直接扩展的代码骨架。整套方案不绑定特定科研领域任何包含实验数据表的论文都可以套用。1. 论文AI助手的真实需求问答只是第一层如果你平时只是泛读论文那普通的摘要生成工具就够了。但科研和技术调研的真实场景往往是这样的你想比较多篇论文的实验结果需要从数据表中提取性能指标你想快速验证一篇论文的结论比如“作者声称方法A比基线高3个点”需要自己算一遍你想把某篇论文的数据重画成符合自己论文风格的图需要访问底层数据你需要追踪某篇论文不同版本或补充材料中的数据变化。这些需求都指向同一个关键点AI不仅要“读过”论文还要“碰到”论文里的数据。传统的RAG方案把PDF切块、向量化、检索最后把文本块塞进Prompt让大模型生成回答。这种方法对“论文主要讲了什么”很有效但对“帮我跑一个分析”基本无能为力。原因很简单表格数据被PDF切块后语义往往已经丢失即使大模型能读出表格内容它也无法直接动手算。所以要做一个真正有用的论文AI助手需要在RAG之上增加一层“工具调用层”。大模型负责理解用户意图、引用论文上下文再通过代码执行工具完成实际的数据处理。用户看到的不再是一段文字而是一个可以验证、可以修改、可以重新运行的分析结果。2. 整体架构检索增强 代码执行这个论文AI助手的核心由三部分组成首先是把论文变成结构化知识。PDF里既有连续文本又有非结构化的表格和图片。我们需要用工具抽取文本和表格文本用于构建向量库表格单独保存为CSV供后续代码分析使用。然后是检索问答。用户的每个问题先转换成向量在论文文本索引中检索最相关的片段连同问题一起交给大模型生成回答。这个过程负责处理“论文在讲什么”“方法是什么”“结论是什么”这类事实性问题。最后是分析执行。当用户需要计算、统计、画图时大模型会根据可用的数据文件生成Python代码调用一个受控的代码执行函数。执行结果文本输出、图片路径、新增数据文件再反馈给大模型由它组织成最终回答。打个比方传统RAG是一个“读过书的图书管理员”能告诉你书里写了什么加入代码执行后这个管理员还变成了“实验室助理”能帮你把书里的数据拿去做实验、画图表并把实验报告交给你。在实际实现中我们不必把这三部分做成微服务。一个Python脚本就足以跑通最小验证。架构上只需要保证数据文件与代码执行环境是隔离的防止论文中的恶意指令污染宿主环境。3. 环境准备与前置条件实现这套方案需要准备以下基础环境Python 3.10 或更高版本一个可用的 OpenAI API Key或任何兼容OpenAI接口的大模型服务如果使用本地模型需要保证机器有足够内存并选择支持函数调用和工具调用的模型操作系统不限Windows、macOS、Linux 均可。需要安装的依赖库如下版本请以实际安装为准本篇文章不锁定具体版本pip install openai pandas matplotlib pdfplumber langchain langchain-openai langchain-community faiss-cpu python-dotenv说明一点langchain-openai用于向量嵌入接口faiss-cpu用于向量检索。如果你更习惯使用Chroma或Elasticsearch也可以替换这里我们选择FAISS是因为它轻量、适合本地快速验证。项目目录结构建议这样组织paper_ai/ ├── input/ │ └── paper.pdf ├── data_tables/ │ └── (代码自动生成 CSV) ├── paper_index/ │ └── (FAISS 向量索引) ├── outputs/ │ └── (图表和分析结果) ├── .env └── paper_ai.py.env文件存放API密钥OPENAI_API_KEYsk-xxxxxxxx4. 论文解析把PDF变成可计算的数据解析论文是容易被低估的一步。很多人以为PDF解析就是把文本抽出来真正做起来会发现两个难点文本提取顺序错乱、表格提取困难。对于文本部分pypdf或pdfplumber都可以。既然我们要提取表格直接使用pdfplumber更方便。下面的代码会遍历PDF每一页分别提取文本和表格表格数据保存为CSV文件文本拼接后返回。# 文件路径parse_paper.py import pdfplumber import pandas as pd import os def parse_paper(pdf_path: str, output_dir: str data_tables) - str: os.makedirs(output_dir, exist_okTrue) full_text [] table_count 0 with pdfplumber.open(pdf_path) as pdf: for page_index, page in enumerate(pdf.pages): # 提取文本 page_text page.extract_text() or full_text.append(f### Page {page_index 1}\n{page_text}) # 提取表格 for table in page.extract_tables(): table_count 1 # 第一行作为表头其余作为数据行 header [str(col) if col else col for col in table[0]] rows table[1:] df pd.DataFrame(rows, columnsheader) # 清理空值和类型 df df.apply(lambda col: pd.to_numeric(col, errorsignore)) out_path os.path.join(output_dir, ftable_{table_count}.csv) df.to_csv(out_path, indexFalse) full_text.append(f\n### Table {table_count} located at Page {page_index 1}\n) full_text_content \n.join(full_text) return full_text_content if __name__ __main__: text parse_paper(input/paper.pdf) with open(paper_text.txt, w, encodingutf-8) as f: f.write(text) print(解析完成表格和文本已保存)这段代码会把PDF中的文本按页保存同时把提取到的表格写入data_tables/table_*.csv。注意PDF表格的格式千差万别extract_tables可能返回不完整的数据后续需要人工抽查和清洗。如果某篇论文的表格特别复杂更稳妥的办法是直接用Excel手动补充一份CSV把论文中的原始数据录进去。AI助手不介意数据来自PDF还是手动录入关键是数据要准确。5. 构建向量索引与基础问答论文文本准备好后接下来的任务是把文本切块、向量化存入FAISS索引。切块大小一般选择500到1000字符重叠100到200字符。这样既能保留上下文又不会让检索粒度太粗。# 文件路径build_index.py from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS from langchain.text_splitter import RecursiveCharacterTextSplitter def build_index(text: str, index_path: str paper_index): splitter RecursiveCharacterTextSplitter( chunk_size800, chunk_overlap150, separators[\n\n, \n, 。, , , ] ) chunks splitter.split_text(text) embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore FAISS.from_texts(chunks, embeddings) vectorstore.save_local(index_path) return len(chunks) if __name__ __main__: text open(paper_text.txt, encodingutf-8).read() count build_index(text) print(f已索引 {count} 个文本块)基于这个索引就可以写一个最基础的问答函数了。先检索相关文本片段再把它们和问题一起传给大模型。# 文件路径rag_qa.py from openai import OpenAI from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import FAISS import os client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) embeddings OpenAIEmbeddings(modeltext-embedding-3-small) vectorstore FAISS.load_local(paper_index, embeddings) def ask_question(question: str) - str: docs vectorstore.similarity_search(question, k4) context \n\n.join([doc.page_content for doc in docs]) response client.chat.completions.create( modelgpt-4o-mini, messages[ {role: system, content: 你是论文阅读助手请基于提供的论文片段回答用户问题。如果片段不足以回答请明确说明。}, {role: user, content: f论文片段\n{context}\n\n用户问题{question}} ] ) return response.choices[0].message.content这个函数已经可以满足“论文在讲什么”“实验条件是什么”“结论是什么”这类问题。但距离“跑分析”还差关键一步。6. 给AI加上“跑分析”能力函数调用与代码执行基础RAG只能输出文本而我们的目标是在回答中输出真实的数据分析结果。实现方式是用大模型的函数调用能力让模型在需要时生成Python代码然后由我们的程序去执行。先定义一个可安全执行的代码运行函数。这里使用的是Python内置的exec但必须明确它的边界它只能在受信任的本地环境中使用不能用于处理不可信来源的论文。生产环境推荐用Docker容器或云沙箱隔离。# 文件路径executor.py import os import pandas as pd import matplotlib matplotlib.use(Agg) import matplotlib.pyplot as plt def execute_python_code(code: str, data_dir: str data_tables, output_dir: str outputs) - dict: 执行模型生成的Python代码并返回文本结果和生成的图片路径。 代码运行前会统一把数据目录和输出目录注入环境。 os.makedirs(output_dir, exist_okTrue) global_env { pd: pd, plt: plt, os: os, data_dir: data_dir, output_dir: output_dir, __name__: __main__, } captured_output [] def print_wrapper(*args, **kwargs): captured_output.append( .join(str(arg) for arg in args)) global_env[print] print_wrapper try: exec(code, global_env) # 找出output目录下新增的图片 images [f for f in os.listdir(output_dir) if f.endswith(.png)] return { status: success, stdout: \n.join(captured_output), images: [os.path.join(output_dir, img) for img in images], } except Exception as e: return { status: error, stdout: \n.join(captured_output), error: f{type(e).__name__}: {e}, }函数本身很简单关键在于让大模型知道可以调用它。下面定义工具Schema# 文件路径agent_tools.py tools [ { type: function, function: { name: execute_python_code, description: 在本地Python环境中运行代码可读取data_tables/下的CSV数据文件进行统计分析并生成图片到outputs/目录。, parameters: { type: object, properties: { code: { type: string, description: 要执行的Python代码。示例df pd.read_csv(data_tables/table_1.csv); print(df.columns) } }, required: [code] } } } ]接下来是Agent循环。流程是把可用数据表的信息注入System Prompt让模型知道有哪些数据文件用户提问后如果模型返回工具调用我们就执行代码并把结果回传给模型直到模型给出最终回答。# 文件路径run_agent.py from openai import OpenAI import os import glob import json from executor import execute_python_code client OpenAI(api_keyos.getenv(OPENAI_API_KEY)) def list_available_tables(): tables [] for path in glob.glob(data_tables/*.csv): df pd.read_csv(path) tables.append({ path: path, columns: list(df.columns), shape: df.shape, }) return tables def run_agent(question: str, max_steps: int 5) - str: tables_info list_available_tables() system_prompt ( 你是一篇论文的AI分析助手。你可以阅读论文内容也可以调用execute_python_code工具对论文中的实验数据执行分析。 数据表位于data_tables目录下。当前可用数据表\n json.dumps(tables_info, ensure_asciiFalse, indent2) \n当你需要统计数据、计算指标、画图时请使用execute_python_code工具。 ) messages [ {role: system, content: system_prompt}, {role: user, content: question}, ] for step in range(max_steps): response client.chat.completions.create( modelgpt-4o, messagesmessages, toolstools, tool_choiceauto, ) message response.choices[0].message if not message.tool_calls: return message.content messages.append({ role: assistant, content: message.content, tool_calls: message.tool_calls, }) for tool_call in message.tool_calls: args json.loads(tool_call.function.arguments) result execute_python_code(args[code]) messages.append({ role: tool, tool_call_id: tool_call.id, content: json.dumps(result, ensure_asciiFalse), }) return 达到最大步骤限制未获得最终答案。 if __name__ __main__: question 帮我读取data_tables/table_1.csv统计不同方法在测试集上的F1分数并绘制柱状图保存到outputs目录。 answer run_agent(question) print(answer)这个Agent流程看起来不复杂但已经具备“跑分析”的核心能力大模型会根据问题生成代码代码在受控环境中执行结果返回后再由大模型归纳总结。用户可以继续追问“那个图里方法A比方法B高多少”系统会继续检索论文上下文并调用工具。7. 运行与效果验证按顺序运行脚本即可体验完整流程。首先解析论文python parse_paper.py解析完成后检查data_tables/目录下是否生成了CSV文件同时打开paper_text.txt确认文本没有乱码。然后构建向量索引python build_index.py最后启动Agentpython run_agent.py正常情况下你会在终端看到类似这样的过程模型先请求调用execute_python_code参数里包含一行读取CSV和绘图的代码工具返回执行结果例如{status: success, stdout: ..., images: [outputs/f1_bar.png]}模型根据工具结果生成最终回答说明图表已保存并简单解读柱状图中的关键对比。判断成功的标准有三个问题能得到基于论文上下文的回答数据分析结果有实际输出文件回答中的数字能够和论文原始表格对应上。如果模型生成的结果和论文表格不一致多半是CSV解析出错或代码读取了错误的列需要检查data_tables目录下的数据是否准确。8. 常见问题与排查思路问题现象可能原因排查方式解决方案PDF解析后文本乱码PDF是扫描版没有文本层用OCR工具识别或从数据库获取原始论文文本接入OCR管线人工校对关键段落表格提取为空或内容错位pdfplumber对复杂合并单元格支持有限打开PDF对应页检查表格结构提取后人工修正CSV或手动录入数据向量索引回答偏差大文本切块过大或过小检索召回不准确打印检索到的chunk内容调整splitter参数增加召回数量或加入摘要索引OpenAI API报错API Key无效、网络不通、余额不足检查.env配置和请求返回的错误码更换接口地址或重新配置密钥工具执行报NameError代码中使用了未导入的库或变量查看execute_python_code返回的error信息在System Prompt中补充可用库列表或扩充global_env模型不调用工具直接给泛泛回答模型版本不支持函数调用或工具描述不清晰检查模型参数和tool schema使用支持函数调用的模型完善工具描述生成代码包含危险操作Prompt注入或恶意论文指令检查代码内容与执行环境隔离在容器中运行限制文件系统访问禁用网络9. 最佳实践与工程建议把这套原型放到真实生产环境之前有几个问题值得提前思考。第一是数据可信度。论文PDF解析出的表格往往有误差尤其是跨页表格、合并单元格、小数点被截断的情况。一定要在构建索引前建立数据审计流程至少核对每张表的行数和关键统计量。宁可让助手的数据来源是人工整理的CSV也不能用来源不明的脏数据。第二是代码执行安全。exec是一把双刃剑。如果论文文本中存在恶意指令例如要求代码读取本地文件或联网模型可能在生成代码时被误导。生产环境中应该把代码执行放在Docker容器、云函数或专门的沙箱服务中并限制网络、文件系统访问权限。即使是本机调试也要确保运行环境中没有敏感数据。第三是函数调用设计。工具的名字和描述一定要具体不要只写“执行代码”。要把可用的数据表路径、列名、输出目录规则都写进System Prompt否则模型会反复生成错误的文件读取路径。第四是评估与回归。论文AI助手和普通聊天机器人一样需要一套评估集。准备10到20个固定问题覆盖事实问答、数据提取、统计计算、可视化四类每次改动解析逻辑或者Prompt后都跑一遍防止“修好一个功能搞坏另一个分析”。第五是版本锁定。langchain、openai、pdfplumber这些库的接口更新很快项目上线时建议把依赖版本锁定在requirements.txt中否则半年后代码可能完全跑不起来。10. 总结与后续学习方向从“能答问题”到“能跑分析”这套论文AI助手的核心改动其实只有两步把论文中的表格独立成结构化数据再为模型增加一个代码执行工具。如果你已经跑通过上面的流程下一步可以尝试把向量库从FAISS换成Chroma或Elasticsearch支持更大规模的论文库加入多篇论文对比功能让助手可以横向比较不同论文的实验数据将代码执行环境切换到Docker增加安全边界用LangSmith或自建日志来评估提示词和工具调用效果把助手封装成FastAPI服务供Web前端或Jupyter Notebook调用。真正值得投入的是数据解析和数据清洗部分。大模型能力的下限被工具决定但工具效果的上限被数据质量决定。一篇论文的PDF解析得好不好直接决定了这个AI助手是能解决科研问题还是只会在Token里消耗你的耐心。建议收藏这篇文章自己找一篇有数据表的论文试一次。只有跑通从PDF到图表的完整链路你才会真正体会到“论文AI助手”和“论文聊天机器人”之间的差别。