1. 项目概述当文档处理遇上智能体最近在开源社区里一个名为landing-ai/agentic-doc的项目引起了我的注意。这个名字本身就很有意思它把“智能体”Agentic和“文档”Doc这两个词结合在了一起。简单来说这个项目探索的是如何用智能体技术也就是我们常说的AI Agent来重新定义我们处理文档的方式。如果你每天还在和PDF、Word、Excel、PPT、图片里的文字信息搏斗手动复制粘贴、整理格式、提取关键信息那么这个项目所代表的方向很可能就是你未来工作流的答案。传统的文档处理无论是用Python的PyPDF2、python-docx还是用OCR技术识别图片文字本质上都是“静态”的。我们写一个脚本告诉程序第一步做什么、第二步做什么流程是固定的。但现实中的文档处理需求往往是动态和复杂的一份几十页的合同我需要快速找到所有涉及“违约责任”的条款并总结要点一份市场调研报告我需要提取出所有图表的数据趋势并生成一个执行摘要甚至我可能只是模糊地记得某份文档里提过一个概念想不起来具体位置需要AI帮我“回忆”并解释。agentic-doc瞄准的正是这些场景——它试图构建一个能理解任务意图、自主规划步骤、调用合适工具并最终交付结果的文档处理智能体。这个项目适合所有需要与大量非结构化文档打交道的开发者、数据分析师、产品经理乃至法务、市场人员。它不是一个开箱即用的最终产品而更像一个框架、一套最佳实践或一个强大的起点展示了如何将大语言模型LLM的推理能力与具体的文档处理工具链结合起来实现从“机械化执行”到“智能化理解与操作”的跃迁。接下来我将深入拆解这个项目的核心思路、技术实现并分享如何基于其思想构建你自己的文档处理智能体。2. 核心架构与设计哲学2.1 从“工具链”到“智能体”的范式转变要理解agentic-doc首先要跳出“脚本”思维。传统的自动化文档处理我们构建的是一条“工具链”Toolchain。比如一个典型的PDF信息提取流水线可能是PDF读取 - 文本解析 - 正则匹配/关键词搜索 - 结果输出。这条链上的每个环节都是确定的输入输出格式固定。如果需求变了比如从提取“日期”变成提取“金额”或者文档结构稍有不同整个脚本可能需要大改。agentic-doc引入的智能体范式核心是加入了“大脑”——一个大语言模型如GPT-4、Claude 3或开源的Llama 3、Qwen等。这个大脑负责三件事任务理解与规划将用户用自然语言描述的需求如“帮我总结这份财报第三季度的亮点和风险”分解成一系列可执行的子任务。工具调用与协调根据子任务动态选择并调用最合适的工具。工具可以是项目内置的如PDF解析器、OCR引擎、向量数据库查询也可以是外部API。结果综合与判断对各个工具返回的中间结果进行综合、分析、判断最终生成符合用户要求的答案或执行下一步操作。这种架构的优势在于灵活性和泛化能力。你不需要为每一种新的查询模式重写代码。智能体通过理解语义可以应对大量此前未明确编程过的需求。例如用户问“这份合同里哪几条对乙方最不利”智能体需要先理解“对乙方不利”这个抽象概念然后定位到责任条款、赔偿条款等再进行比较和判断这超出了传统规则引擎的能力范围。2.2 智能体系统的关键组件拆解一个完整的agentic-doc风格系统通常包含以下几个核心组件它们协同工作构成了智能体的“躯体”和“神经系统”Orchestrator编排器/大脑这是系统的核心通常由一个大语言模型驱动。它接收用户查询维护对话状态和任务上下文并决定下一步该做什么。它不直接处理文档而是发号施令。Toolkit工具集这是智能体的“双手”。一套精心设计的工具函数每个函数都有明确的功能描述。例如read_pdf(file_path: str) - str: 读取PDF文件并返回纯文本。extract_tables_from_pdf(file_path: str) - List[DataFrame]: 提取PDF中的所有表格。search_document_chunks(query: str, top_k: int) - List[str]: 在文档块向量数据库中做语义搜索。summarize_text(text: str, max_length: int) - str: 总结一段文本。answer_question_based_on_context(question: str, context: str) - str: 基于给定上下文回答问题。 这些工具的描述函数名、参数、功能说明会被转换成LLM能理解的格式如OpenAI的Function Calling格式供编排器调用。Knowledge Base知识库/记忆对于需要处理大量文档或进行复杂问答的场景仅仅靠LLM的上下文窗口是不够的。这里通常会引入向量数据库如Chroma、Pinecone、Weaviate。工作流程是先将所有文档进行切分chunking然后通过嵌入模型Embedding Model将每个文本块转换为向量存入向量数据库。当用户提问时先将问题转换为向量在数据库中检索出最相关的几个文本块将这些块作为“上下文”提供给LLM让LLM基于这些精确的上下文生成答案。这解决了LLM的“幻觉”问题和上下文长度限制。Agent Loop智能体循环这是执行流程。通常是一个while循环观察用户输入当前状态 - 思考LLM决定行动 - 行动调用工具 - 观察工具返回结果循环直到任务完成或达到步骤限制。2.3 设计中的权衡与选型考量在构建这样一个系统时会面临几个关键选择每个选择都影响着系统的能力、成本和复杂度LLM选型云端 vs. 本地云端API如OpenAI、Anthropic能力强大、省心但涉及数据隐私和持续成本。本地模型如通过Ollama部署的Llama 3、Qwen数据不出域长期成本低但对硬件有要求且在某些复杂推理任务上可能略逊一筹。agentic-doc类项目通常设计为与LLM提供商解耦让你可以灵活切换。工具粒度粗粒度 vs. 细粒度工具应该设计得多精细一个process_financial_report的工具很强大但不够灵活而拆分成read_pdf,find_section,extract_table,calculate_growth_rate等多个小工具则赋予LLM更大的组合灵活性但也对LLM的规划能力提出了更高要求。上下文管理如何有效利用TokenLLM的上下文窗口是宝贵资源。需要精心设计提示词Prompt过滤掉不相关的工具调用历史只保留对当前决策最关键的信息。有时还需要采用“反思”机制让LLM对之前的步骤进行总结用总结替代冗长的历史记录。实操心得在项目初期建议从云端LLM如GPT-4和粗粒度工具开始快速验证智能体范式的可行性。当核心流程跑通后再逐步细化工具、优化提示词并评估是否迁移到成本更低的本地模型。切忌一开始就追求大而全容易陷入细节泥潭。3. 核心工具链与文档处理技术详解智能体的大脑固然重要但它的“双手”——文档处理工具链的可靠性和能力边界直接决定了整个系统的实用性。agentic-doc项目的价值很大程度上体现在它对这些底层工具的集成和封装上。3.1 文档解析从格式到结构化数据不同类型的文档需要不同的解析策略目标都是将非结构化的文件内容转化为机器可读、可查询的结构化或半结构化数据。PDF解析这是难点最多的一类。简单的文本提取可以用PyPDF2或pdfplumber但它们对扫描版PDF或复杂排版无能为力。策略一OCR引擎。对于扫描件必须使用OCR。Tesseract是开源首选但需要搭配预处理如OpenCV进行去噪、二值化提升识别率。云端OCR服务如Azure Form Recognizer、Google Document AI准确率更高能直接输出带结构的JSON识别段落、表格、键值对但涉及数据出域和费用。策略二专用解析库。pdfplumber在提取文本和简单表格方面表现很好。camelot、tabula-py专门用于提取表格对于财务报表类文档至关重要。layoutparser等库可以分析PDF的版面布局区分标题、正文、图表区域。实操要点永远不要假设一个PDF解析器能解决所有问题。在实际项目中我通常会实现一个解析器路由先尝试用pdfplumber提取文本如果返回的文本非常少或杂乱则判断为扫描件自动切换到OCR流程。对于包含重要表格的文档会并行使用camelot进行表格提取。Office文档解析相对规范工具成熟。Word (.docx)使用python-docx库可以按段落、表格、图片遍历文档获取带样式的文本。关键是要利用好样式信息如“Heading 1”来推断文档结构。Excel (.xlsx)使用pandas的read_excel函数是最佳选择。需要注意处理多个工作表、合并单元格以及可能存在的公式。智能体可能需要判断该将哪个工作表或数据范围作为分析对象。PowerPoint (.pptx)使用python-pptx可以提取每页幻灯片的文本和形状。对于PPT文本通常分散在多个文本框里需要按视觉顺序进行拼接。纯文本与标记语言最简单但也需要注意编码问题。使用Python标准库即可对于HTML/XMLBeautifulSoup或lxml是解析利器。图片中的文字除了前述OCR对于手机拍摄的、有透视变形的文档图片可能需要先用OpenCV进行透视校正getPerspectiveTransform再进行OCR能极大提升识别准确率。3.2 文本处理与信息提取的中间层解析出原始文本只是第一步。原始文本通常杂乱包含无关信息页眉页脚、错误的换行缺乏结构。需要经过清洗和增强才能喂给LLM或存入向量库。文本清洗与标准化去除无关字符清理不可见字符、多余空格、乱码。修复错误的换行PDF解析中一个段落经常被错误地拆分成多行。简单的启发式规则是如果一行不以句号、问号等结束符结尾且下一行开头是小写字母则将它们合并。更复杂的方法可以用基于统计的语言模型来判断。编码统一确保所有文本为UTF-8编码。文档结构重建利用字体大小、加粗等信息从pdfplumber或python-docx获取来识别标题和章节。使用基于规则或机器学习的方法进行段落分割。nltk的punkt句子分割器可以作为基础。目标是生成一个带有层级结构如标题1、标题2、段落的文档对象这对于后续的精准检索至关重要。关键信息提取NER与关系抽取 对于合同、发票、简历等特定类型文档我们需要提取预定义的实体如人名、公司名、金额、日期及其关系。这里可以分两层基于LLM的零样本/少样本抽取直接给LLM一段文本和指令如“提取所有甲方和乙方的名称”效果惊人地好且无需训练数据。适合快速原型和泛化需求。基于预训练模型如spaCy、Stanford NER的抽取速度更快本地可运行但对领域外实体识别效果可能下降。可以将其作为一个工具集成到智能体中处理大批量、固定模式的抽取任务。3.3 向量化与语义检索引擎这是实现“智能”问答和关联检索的核心。其流程是切分 - 嵌入 - 存储 - 检索。文本切分Chunking如何把一篇长文档切成适合嵌入模型处理的片段简单按固定字符数如500字切分会割裂语义。递归切分优先按段落切如果段落太长再按句子切最后按固定长度切。保留一定的重叠如50字防止关键信息被切断。基于语义的切分使用句子嵌入模型计算句子间的相似度在语义变化大的地方进行切分。langchain的RecursiveCharacterTextSplitter是常用的工具。实操要点没有一种切分方法适合所有文档。对于技术手册按章节切分很好对于小说按段落或场景切分更合适。最好能根据文档类型提供不同的切分策略。嵌入模型Embedding Model负责将文本块转换为高维向量。向量的质量直接决定检索精度。开源模型sentence-transformers库提供了大量优秀模型如all-MiniLM-L6-v2平衡速度与质量、all-mpnet-base-v2质量更高。它们可以在本地CPU/GPU上运行。云端APIOpenAI的text-embedding-3-small/large性能顶尖使用简单。选型建议如果数据敏感且量不大首选开源模型。如果追求最高检索质量且可接受数据出域用OpenAI的嵌入API。注意嵌入模型的选择应与后续使用的LLM有一定兼容性虽然不是强制。向量数据库Vector Database存储和检索向量。轻量级/内置方案Chroma、FAISS。它们可以嵌入到应用中无需单独服务非常适合原型和中小规模数据。Chroma还自带简单的元数据过滤功能。生产级方案Pinecone、Weaviate、Qdrant。它们提供托管服务支持分布式、高可用、自动扩缩容以及更丰富的过滤和查询功能。核心操作add添加带元数据的向量、query根据查询向量返回最相似的K个结果。元数据如文档ID、章节标题、页码在结果后处理中非常有用。检索策略纯语义检索直接用问题向量去数据库里找最相似的文本块。这是基础。混合检索结合关键词检索如BM25和语义检索。例如先用关键词过滤出包含特定术语的候选集再用语义检索进行精排。这种方法能更好地处理包含专有名词、代号的问题。重排序Re-ranking语义检索返回的Top K个结果可能有一些相关性不高。可以使用一个更精细但更慢的重排序模型如BAAI/bge-reranker对K个结果重新打分排序提升最终喂给LLM的上下文质量。注意事项向量检索不是万能的。对于需要精确匹配的信息如产品型号、代码编号传统的数据库或关键词搜索可能更有效。智能体应能根据问题类型决定使用哪种检索方式或结合使用。4. 构建你自己的文档处理智能体从零到一理解了核心组件后我们来动手搭建一个简化但功能完整的智能体系统。我们将使用LangChain框架因为它提供了大量构建智能体所需的抽象和工具能让我们聚焦在逻辑而非底层通信协议上。4.1 环境准备与依赖安装首先创建一个新的Python环境并安装核心依赖。这里我们选择开源路线使用本地嵌入模型和LLM通过Ollama以及本地的向量数据库。# 创建并激活虚拟环境可选但推荐 python -m venv agentic_doc_env source agentic_doc_env/bin/activate # Linux/macOS # agentic_doc_env\Scripts\activate # Windows # 安装核心库 pip install langchain langchain-community langchain-chroma # LangChain核心及Chroma集成 pip install sentence-transformers # 用于本地嵌入模型 pip install pypdf2 pdfplumber python-docx pandas # 文档解析 pip install beautifulsoup4 # HTML解析 pip install ollama # 用于运行本地LLM需先安装Ollama本体此外你需要在本地安装并运行 Ollama 。Ollama使得在本地运行如llama3、qwen等大型模型变得非常简单。安装后在终端拉取一个模型ollama pull llama3:8b # 拉取Llama 3 8B模型对硬件要求相对友好4.2 定义智能体的工具集我们将创建一系列工具函数并用tool装饰器LangChain风格或StructuredTool来包装它们以便LLM识别和调用。from langchain.tools import tool from typing import List, Optional import os import pdfplumber from docx import Document import pandas as pd from langchain_community.document_loaders import PyPDFLoader, TextLoader, Docx2txtLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_chroma import Chroma # 工具1加载并解析文档 tool def load_and_parse_document(file_path: str) - str: 加载并解析一个文档文件支持PDF, TXT, DOCX返回其纯文本内容。 if not os.path.exists(file_path): return f错误文件路径 {file_path} 不存在。 ext os.path.splitext(file_path)[1].lower() text try: if ext .pdf: # 使用pdfplumber它能更好地保留文本顺序和简单表格 with pdfplumber.open(file_path) as pdf: pages_text [] for page in pdf.pages: page_text page.extract_text() if page_text: pages_text.append(page_text) text \n.join(pages_text) if not text.strip(): return 警告PDF文件可能为扫描件或图像未提取到文本请考虑使用OCR工具。 elif ext .docx: doc Document(file_path) text \n.join([para.text for para in doc.paragraphs]) elif ext .txt: with open(file_path, r, encodingutf-8) as f: text f.read() else: return f错误不支持的文件格式 {ext}。目前支持 .pdf, .docx, .txt。 except Exception as e: return f解析文档时出错{str(e)} return text if text else 文档内容为空。 # 工具2将文档内容存入向量数据库知识库 tool def index_document_to_vectorstore(file_path: str, persist_directory: str ./chroma_db) - str: 将指定文档的内容进行切分、向量化并存储到Chroma向量数据库中。 后续可以通过查询该数据库进行语义搜索。 # 1. 加载文档使用LangChain的Loader if file_path.endswith(.pdf): loader PyPDFLoader(file_path) elif file_path.endswith(.docx): loader Docx2txtLoader(file_path) elif file_path.endswith(.txt): loader TextLoader(file_path) else: return 不支持的文件格式用于向量化。 documents loader.load() # 2. 切分文本 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, length_functionlen, separators[\n\n, \n, 。, , , , , , ] ) splits text_splitter.split_documents(documents) # 3. 创建嵌入模型和向量库 embeddings HuggingFaceEmbeddings(model_nameall-MiniLM-L6-v2) vectorstore Chroma.from_documents( documentssplits, embeddingembeddings, persist_directorypersist_directory ) vectorstore.persist() return f文档已成功索引并保存到 {persist_directory}。共处理了 {len(splits)} 个文本块。 # 工具3从向量数据库进行语义搜索 tool def search_vectorstore(query: str, persist_directory: str ./chroma_db, k: int 4) - str: 在已建立的向量数据库中搜索与问题最相关的文档片段。 if not os.path.exists(persist_directory): return 错误向量数据库不存在请先使用 index_document_to_vectorstore 工具索引文档。 embeddings HuggingFaceEmbeddings(model_nameall-MiniLM-L6-v2) vectorstore Chroma(persist_directorypersist_directory, embedding_functionembeddings) # 执行相似性搜索 docs vectorstore.similarity_search(query, kk) # 格式化结果 results [] for i, doc in enumerate(docs): # 显示片段内容及其元数据如来源页码 source doc.metadata.get(source, 未知) page doc.metadata.get(page, 未知) content_preview doc.page_content[:200] ... if len(doc.page_content) 200 else doc.page_content results.append(f[结果 {i1}] 来源{source} (页码{page})\n片段{content_preview}\n) return 搜索完成。以下是最相关的片段\n \n---\n.join(results) if results else 未找到相关结果。 # 工具4基于上下文的摘要 tool def summarize_text(text: str, max_length: int 300) - str: 使用LLM对给定的文本进行总结提炼核心内容。 # 注意这个工具内部需要调用LLM。在实际的智能体循环中这个总结动作可能由主LLM完成。 # 这里我们将其设计为一个独立工具演示工具可以嵌套LLM调用。 # 为简化示例我们这里模拟一个调用。 # 真实实现中这里会调用你配置的LLM如通过Ollama。 from langchain_community.llms import Ollama llm Ollama(modelllama3:8b) prompt f请对以下文本进行摘要摘要长度不超过{max_length}字要求抓住核心事实和观点 文本 {text} 摘要 summary llm.invoke(prompt) return summary # 将工具放入列表供智能体使用 tools [load_and_parse_document, index_document_to_vectorstore, search_vectorstore, summarize_text]4.3 组装智能体并运行现在我们使用LangChain的create_react_agentReAct范式来创建一个能够使用上述工具的智能体。ReAct范式要求LLM以“思考 - 行动 - 观察”的循环来工作。from langchain import hub from langchain.agents import create_react_agent, AgentExecutor from langchain_community.llms import Ollama # 1. 初始化LLM使用本地Ollama服务 llm Ollama(modelllama3:8b, temperature0) # temperature0使输出更确定 # 2. 获取ReAct风格的提示词模板 prompt hub.pull(hwchase17/react) # 3. 创建智能体 agent create_react_agent(llm, tools, prompt) # 4. 创建代理执行器 agent_executor AgentExecutor(agentagent, toolstools, verboseTrue, handle_parsing_errorsTrue) # 5. 运行一个示例查询 if __name__ __main__: # 假设我们有一个名为“contract.pdf”的合同文件 file_path ./samples/contract.pdf # 用户查询 user_query 请先帮我分析一下contract.pdf这份合同然后告诉我里面关于‘保密协议’的主要条款有哪些 print(f用户提问{user_query}) print(*50) # 执行智能体 try: result agent_executor.invoke({input: user_query}) print(\n *50) print(智能体最终回答) print(result[output]) except Exception as e: print(f执行过程中出现错误{e})当你运行这段代码时verboseTrue会打印出智能体的思考过程类似于 进入新的AgentExecutor链... 思考用户想分析contract.pdf并找到保密协议条款。我需要先读取文档内容。 行动使用 load_and_parse_document 工具参数为 {file_path: ./samples/contract.pdf} 观察工具返回的合同全文文本 思考现在我有合同文本了。用户想找“保密协议”条款。我可以在文本中直接搜索但合同可能很长。更好的方法是先为文档建立向量索引然后进行语义搜索这样更准确。 行动使用 index_document_to_vectorstore 工具参数为 {file_path: ./samples/contract.pdf} 观察文档已成功索引... 思考现在我可以搜索“保密协议”相关条款了。 行动使用 search_vectorstore 工具参数为 {query: 保密协议 条款 义务 责任, k: 5} 观察搜索返回了5个相关片段其中片段1和3看起来直接相关... 思考我找到了相关片段。现在我需要把这些信息整合起来用清晰的语言回答用户。 行动我将直接基于观察到的信息进行回答。 最终回答根据合同内容关于“保密协议”的主要条款集中在第8条。主要内容包括1. 定义了“保密信息”的范围... 2. 规定了接收方的保密义务... 3. 保密期限为合同终止后三年... 4. 列出了除外情况... 链结束。这个简单的示例展示了智能体如何自主规划它没有直接去全文搜索而是判断先建立向量索引再进行语义搜索是更优解。这就是智能体与普通脚本的区别。5. 高级技巧与生产环境考量构建一个可演示的原型是一回事让智能体系统稳定、可靠、高效地运行在生产环境是另一回事。以下是几个关键的进阶议题。5.1 提示词工程与智能体引导智能体的表现极度依赖给它的提示词系统指令。一个好的系统提示词应该明确角色和能力“你是一个专业的文档分析助手擅长阅读和理解法律、技术和商业文档。”定义工具使用规范“你可以使用以下工具[工具列表]。请逐步思考每次只使用一个工具。在得到工具返回结果后再决定下一步。”设定输出格式和约束“你的最终答案应基于文档事实不要捏造信息。如果信息不足请明确指出。答案请使用中文并分点列出。”提供示例Few-shot在提示词中加入一两个“用户提问-智能体思考过程”的例子能显著提升智能体使用工具的准确性。对于复杂的多步骤任务单纯的ReAct可能不够。可以采用Plan-and-Execute模式先让一个“规划者”LLM可以是同一个模型制定一个详细的步骤计划然后由一个“执行者”LLM或同一个模型按计划执行逐步调用工具完成。这能处理更冗长、更复杂的任务。5.2 处理复杂、多模态与长文档多文档关联分析用户问题可能涉及多个文件如“对比A合同和B合同中的赔偿条款”。需要扩展工具让index_document_to_vectorstore能增量添加文档并在元数据中清晰标记文档来源。检索时可以同时从多个集合中查询或使用支持多租户的向量库。超长文档处理当单个文档超过LLM上下文窗口时不能一次性喂入。策略是先通过向量检索找到最相关的几个片段将这些片段连同问题一起送给LLM。如果答案仍不完整可以采用“迭代检索”或“Map-Reduce”方法将文档分成多个部分分别提问总结最后再综合所有总结得出最终答案。多模态文档文档中包含图表、流程图。解决方案是使用专用工具提取图片pdfplumber或python-docx可以获取图片位置。使用视觉描述模型如GPT-4V、开源的LLaVA为图片生成文字描述。将图片描述作为附加文本与周围的正文一起嵌入向量数据库。当用户问到“请解释图5的趋势”时智能体就能检索到对应的图片描述来回答。5.3 评估、监控与持续改进一个投入使用的智能体系统需要持续观察和优化。评估指标任务完成率智能体是否能正确理解并最终给出有效回答工具调用准确率它是否在正确的时机调用了正确的工具人工反馈收集用户对回答质量的评分如1-5星。成本与延迟平均处理一个查询消耗的Token数和时间。日志与监控详细记录每个会话的原始问题、智能体的思考链Chain of Thought、调用的工具及参数、工具返回结果、最终输出。这些日志是分析和调试的黄金数据。迭代优化根据监控数据你会发现智能体的常见失败模式可能是某个工具描述不清、可能是提示词有歧义、也可能是对某种类型的问题缺乏处理能力。针对性地调整提示词、增加新工具或提供更多示例可以逐步提升系统表现。5.4 安全与权限边界在企业环境中这是重中之重。工具权限隔离不是所有用户都能使用所有工具。例如“删除文档”工具只能给管理员。需要在智能体调用工具前增加一个权限校验层。输入输出净化对用户输入和工具返回的内容进行安全检查防止提示词注入攻击Prompt Injection导致智能体执行恶意指令。数据访问控制向量数据库和文档存储应有访问控制列表ACL。智能体在检索时只能检索当前用户有权限访问的文档块。这通常需要在文档切分和嵌入时就将用户/组权限作为元数据一并存储并在查询时作为过滤器。构建一个成熟可用的agentic-doc系统是一个持续迭代的过程。它不仅仅是一个技术项目更是一个对现有文档工作流的深刻重构。从简单的信息提取到复杂的分析、推理和报告生成智能体正在将我们从繁琐的文档处理劳动中解放出来让我们能更专注于需要人类判断和创造力的高价值任务。