让 Agent 能看图、能画图、能处理 PDF/Excel
前面两篇解决了"工具怎么安全"和"上下文怎么不爆",这篇把 Agent 的感知能力从纯文本扩展到图片、文档、表格。核心思路不变:多模态模型负责"看懂",LangGraph 负责"串联",工具层负责"解析"。
一、架构总览
用户输入(文字 + 图片/文件)
↓
路由节点(判断模态)
↓
┌─────────────────────────────┐
│ 图片 → 视觉分析节点 │
│ PDF → 文档解析节点 │
│ Excel→ 数据分析节点 │
│ 画图 → 图像生成节点 │
└─────────────────────────────┘
↓
整合节点(汇总结果输出)
关键点:在线问答和离线入库要分开。OCR 一个 200 页的扫描件可能要几分钟,不能让用户等。
二、图片理解:让 Agent "看图说话"
用支持视觉的多模态模型(GPT-4o / Claude 3.5 / Qwen-VL),把图片转成 Base64 直接喂给模型。
import base64
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
llm_vision = ChatOpenAI(model="gpt-4o", temperature=0)
def encode_image(path: str) -> str:
with open(path, "rb") as f:
return base64.b64encode(f.read()).decode("utf-8")
def analyze_image(image_path: str, question: str) -> str:
"""让模型看图并回答问题"""
b64 = encode_image(image_path)
message = HumanMessage(content=[
{"type": "text", "text": question},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}
])
return llm_vision.invoke([message]).content
# 示例
result = analyze_image("factory_equipment.jpg", "这台设备有没有潜在故障?")
典型场景:设备巡检拍照分析、截图问题诊断、设计稿评审。
三、PDF 解析:最复杂的格式
PDF 分三类,处理策略完全不同:
| 类型 | 特征 | 处理方式 |
|---|---|---|
| 原生文本 PDF | 可直接复制文字 | PyPDF2 / pdfplumber 直接提取 |
| 扫描版 PDF | 本质是图片 | OCR(PaddleOCR / Tesseract) |
| 混合 PDF | 文字+图表混排 | 分层提取:文字走文本提取,图表走 OCR |
import pdfplumber
from PIL import Image
import pytesseract
def parse_pdf(pdf_path: str) -> str:
"""解析 PDF,自动判断是否需要 OCR"""
full_text = []
with pdfplumber.open(pdf_path) as pdf:
for i, page in enumerate(pdf.pages):
# 1. 尝试提取文本
text = page.extract_text() or ""
# 2. 如果文本太少,可能是扫描件,走 OCR
if len(text.strip()) < 50:
image = page.to_image(resolution=200)
ocr_text = pytesseract.image_to_string(
image.original, lang="chi_sim+eng"
)
text = ocr_text
# 3. 提取表格(转成 Markdown)
tables = page.extract_tables()
table_text = ""
for table in tables:
if table:
# 转 Markdown 表格
header = "| " + " | ".join(table[0]) + " |"
sep = "| " + " | ".join(["---"] * len(table[0])) + " |"
rows = "\n".join(
"| " + " | ".join(row) + " |" for row in table[1:]
)
table_text += f"\n{header}\n{sep}\n{rows}\n"
full_text.append(f"--- 第 {i+1} 页 ---\n{text}\n{table_text}")
return "\n".join(full_text)
避坑:
- 多栏排版的 PDF 提取后文字顺序可能错乱,需要按坐标排序。
- 表格提取是难点,
pdfplumber和camelot可以互补使用。 - 大文件先分页截断,不要一次性塞给模型。
四、Excel/CSV 分析:让 Agent 会"算账"
Excel 处理的关键是先转成结构化数据,再交给代码解释器或模型分析。
import pandas as pd
def analyze_excel(file_path: str, question: str) -> str:
"""分析 Excel/CSV 文件"""
# 1. 读取文件
if file_path.endswith(".csv"):
df = pd.read_csv(file_path)
else:
df = pd.read_excel(file_path)
# 2. 生成数据摘要(让模型了解数据结构)
summary = {
"shape": df.shape,
"columns": list(df.columns),
"dtypes": df.dtypes.astype(str).to_dict(),
"sample": df.head(3).to_dict(orient="records"),
"describe": df.describe().to_dict(),
}
# 3. 让模型基于摘要决定下一步操作
prompt = f"""你是数据分析师。基于以下数据摘要,回答用户问题。
如果需要进行计算或绘图,请输出 Python 代码。
数据摘要:{summary}
用户问题:{question}
回答或输出可执行的 Python 代码。"""
response = llm.invoke(prompt).content
# 如果返回代码,交给代码解释器执行(见系列二第1篇)
return response
进阶:配合系列一第1篇的代码解释器,Agent 可以自动画折线图、柱状图,甚至生成数据透视表。
五、图像生成:让 Agent 会"画图"
用 DALL·E 3 或 Stable Diffusion 作为工具,让 Agent 根据描述生成图片。
from openai import OpenAI
client = OpenAI()
def generate_image(prompt: str, size: str = "1024x1024") -> str:
"""生成图片并返回 URL"""
response = client.images.generate(
model="dall-e-3",
prompt=prompt,
size=size,
quality="standard",
n=1,
)
return response.data[0].url
# 作为 Agent 工具注册
image_gen_tool = {
"name": "generate_image",
"description": "根据文字描述生成图片",
"parameters": {
"type": "object",
"properties": {
"prompt": {"type": "string", "description": "图片描述"}
},
"required": ["prompt"]
}
}
典型场景:写诗配图、产品概念图、营销海报草图。
六、完整多模态工作流(LangGraph)
from typing import TypedDict, Optional
from langgraph.graph import StateGraph, END
class MultiModalState(TypedDict):
query: str
image_path: Optional[str]
file_path: Optional[str]
parsed_content: str
analysis_result: str
final_output: str
def router_node(state: MultiModalState) -> str:
"""根据输入类型路由到不同处理节点"""
if state.get("image_path"):
return "vision"
elif state.get("file_path"):
ext = state["file_path"].split(".")[-1].lower()
if ext in ("pdf",):
return "document"
elif ext in ("xlsx", "xls", "csv"):
return "spreadsheet"
return "text_only"
def vision_node(state: MultiModalState) -> MultiModalState:
result = analyze_image(state["image_path"], state["query"])
return {"analysis_result": result}
def document_node(state: MultiModalState) -> MultiModalState:
content = parse_pdf(state["file_path"])
# 截断后交给模型总结
prompt = f"请总结以下文档内容,回答用户问题:{state['query']}\n\n{content[:15000]}"
result = llm.invoke(prompt).content
return {"parsed_content": content[:5000], "analysis_result": result}
def spreadsheet_node(state: MultiModalState) -> MultiModalState:
result = analyze_excel(state["file_path"], state["query"])
return {"analysis_result": result}
def text_only_node(state: MultiModalState) -> MultiModalState:
result = llm.invoke(state["query"]).content
return {"analysis_result": result}
def integrate_node(state: MultiModalState) -> MultiModalState:
return {"final_output": state["analysis_result"]}
# 构建图
workflow = StateGraph(MultiModalState)
workflow.add_node("router", router_node)
workflow.add_node("vision", vision_node)
workflow.add_node("document", document_node)
workflow.add_node("spreadsheet", spreadsheet_node)
workflow.add_node("text_only", text_only_node)
workflow.add_node("integrate", integrate_node)
workflow.set_entry_point("router")
workflow.add_conditional_edges("router", router_node, {
"vision": "vision",
"document": "document",
"spreadsheet": "spreadsheet",
"text_only": "text_only",
})
for node in ["vision", "document", "spreadsheet", "text_only"]:
workflow.add_edge(node, "integrate")
workflow.add_edge("integrate", END)
app = workflow.compile()
# 使用示例
result = app.invoke({
"query": "这份财报的核心数据是什么?",
"file_path": "financial_report.pdf",
})
print(result["final_output"])
七、生产级注意事项
- 离线/在线分离:文档入库走异步图(OCR → 校验 → 分块 → 向量化),在线问答只消费已索引的结果。
- 成本优化:视觉模型 Token 贵,先做预处理(裁剪、压缩、OCR 转文本)再调用。
- 质量门控:OCR 结果加一道"人工审核"节点,低置信度的结果不直接入库。
- 文件安全:上传的文件要做类型校验、大小限制、病毒扫描,防止恶意文件。
- 2026 生态动态:LangGraph 在 2026 年已支持多模态 Agent 编排,Deep Agents 框架也加入了多模态能力,生产级项目可以直接基于这些能力搭建。