☰
ottomarkdown-agent 表格转 Markdown 全解析:从 test.md 看 xlsx 转换管线与文档缓存机制
2026/10/12 1:46:34 网站建设 项目流程
  • 示例工程

【免费下载链接】ottomator-agents

All the open source AI Agents hosted on the oTTomator Live Agent Studio platform!

项目地址:https://gitcode.com/GitHub_Trending/ot/ottomator-agents
点击查看免费下载

导读

本文以 ottomarkdown-agent 项目markdown_results/目录中的真实转换产物 test.md 为切入点,完整还原一次 Excel(xlsx)文件到 Markdown 表格的转换过程:包括 base64 上传、MarkItDown 解析、多工作表渲染、AI Agent 上下文注入与 Supabase 文档缓存。读完本文,你将掌握 file_agent.py 三个核心 API 的调用方式、转换输出的实际形态,以及如何在本地或 Docker 中部署并验证这套文件转 Markdown 管线。

一、test.md 是什么:一次真实的 xlsx → Markdown 转换输出

test.md 位于ottomarkdown-agent/markdown_results/目录,是验证流程中由test_files/下的test.xlsx转换得到的 Markdown 文件。它完整保留了 Excel 工作簿的两个工作表,并把每个工作表渲染为「H2 标题 + 管道符 Markdown 表格」的结构:

工作表 1:Sheet1

AlphaBetaGammaDelta
898210012
76893342
60841919
7691017
87898654
2348925
70846259
83374321
71158832
20622067
67181548
4251567
586ff4173b-42a5-4784-9b19-f49caff4d93d229
4993638
8228139
95551882
50469886
31464782
40651931
95652962
68573454
96666314
87939580

工作表 2:09060124-b5e7-4717-9d07-3c046eb

ColAColBColCColD
1234
5678
9101112
131415affc7dad-52dc-4b98-9b5d-51e65d8a8ad0

从这个输出可以读出三条关键信息:

  1. 多工作表被完整保留:每个工作表都以## 工作表名作为二级标题,表格内容逐一渲染,顺序与源文件一致;
  2. 单元格内容原样呈现:Sheet1中混入的 UUID 字符串(如6ff4173b-42a5-4784-9b19-f49caff4d93d)和第二个工作表中的 UUID 单元格都被原样写入表格,说明转换管线只做格式转换、不做内容改写;
  3. 输出路径可复现:同一份test.xlsx在本地转换路径与 API 转换路径下产出的内容一致——对比 local_convert_test_xlsx.md 与 api_openrouter_test_xlsx.md,两者与 test.md 逐行相同,验证了两种调用方式的输出一致性。

二、转换链路:从 base64 上传到 Markdown 表格

Live Agent Studio 的「文件上传」能力会把文件以 JSON 格式发送给 Agent,文件内容编码为 base64 字符串。在 file_agent.py 中,整条转换链路可以拆解为四个环节:

1. 接收与解码

请求体中的files数组(或单文件接口中的file对象)包含name、type、base64三个字段。核心函数process_files_to_string(file_agent.py#L193-L275)对每个文件执行:

decoded_content = base64.b64decode(file['base64'])

解码后写入临时文件f"/tmp/temp_file_{file['name']}",供 MarkItDown 读取,转换完成后立即os.remove清理。

2. 图片检测分流

解码出的字节流会先经过imghdr.what()检测(file_agent.py#L210-L214):

content_stream = io.BytesIO(decoded_content) image_type = imghdr.what(content_stream) is_image = image_type is not None
  • 非图片(含 xlsx、pdf、docx 等文档)→ 使用OPENROUTER_MODEL配置的文本模型;
  • 图片 → 使用OPENROUTER_VLM_MODEL配置的视觉语言模型(VLM)。

3. MarkItDown 转换

根据文件类型实例化对应的MarkItDown对象并调用convert:

temp_md = MarkItDown(llm_client=openai_client, llm_model=model) result = temp_md.convert(temp_file_path, use_llm=True) markdown_content = result.text_content

MarkItDown是转换的核心引擎,markitdown[all]~=0.1.0a1依赖声明在 requirements.txt 中。它内部由 OpenRouter(兼容 OpenAI SDK,base_url="https://openrouter.ai/api/v1",见 file_agent.py#L38-L45)驱动:文本模型负责解析表格、标题等文档结构,视觉模型负责把图片内容描述为文字。

4. 输出落盘

save_markdown_file(file_agent.py#L166-L191)负责把转换结果写入markdown_results/目录:

clean_filename = re.sub(r'[^\w\-_\.]', '_', filename) markdown_path = os.path.join('markdown_results', f"{base}.md")

文件名会被正则清洗(非法字符替换为_),这就是test.xlsx最终生成test.md的原因。转换后的 Markdown 也会作为「人类消息」通过store_message写入 Supabase 的messages表,从而把文件内容并入会话上下文,供后续多轮对话引用(file_agent.py#L376-L406)。

三、三个 API 端点:转换、智能处理与缓存加速

file_agent.py 通过 FastAPI 暴露三个端点,它们共享同一套转换内核,区别在于职责边界:

端点作用源码位置
POST /api/convert-to-markdown单文件纯转换,返回 Markdownfile_agent.py#L420-L514
POST /api/file-agent多文件 + query 的 AI Agent 处理,维护会话上下文file_agent.py#L356-L418
POST /api/file-agent-cached同 file-agent,但增加文档缓存file_agent.py#L516-L604

1. 单文件转换:/api/convert-to-markdown

请求体结构:

字段类型说明
file.namestring原始文件名(含扩展名)
file.typestring文件的 MIME 类型
file.base64string文件的 base64 编码内容

响应结构:

字段类型说明
successboolean转换是否成功
markdownstring转换后的 Markdown 内容
errorstring失败时的错误信息

支持的输入类型(来自 README.md):文档.pdf.docx.txt;表格.xlsx.csv;演示文稿.pptx;网页.html;图片.jpg.jpeg.png.gif.bmp.tiff。

2. Agent 智能处理:/api/file-agent

在纯转换之上叠加了「query 指令 + 会话历史」能力:

字段类型说明
querystring给 AI Agent 的处理指令
filesarray待处理文件列表
files[].namestring原始文件名
files[].typestringMIME 类型
files[].base64stringbase64 编码内容
session_idstring会话唯一标识,用于维持上下文
user_idstring用户标识
request_idstring请求唯一标识

处理流程(对应 file_agent.py#L356-L418):

  1. fetch_conversation_history按session_id倒序拉取最近 10 条消息并反转成时间正序;
  2. 把用户 query 连同文件元数据写入messages表;
  3. process_files_to_string(request.files, query=request.query)转换文件,并让 LLM 按 query 对转换结果做加工;
  4. 将 Agent 回复再次入库,形成可追溯的多轮对话。

3. 缓存加速:/api/file-agent-cached

该端点在处理前先计算文档哈希并查询缓存(详见下一节),命中则直接返回缓存的 Markdown,未命中才走完整转换并回写缓存。请求额外支持use_cache参数,置为false可强制绕过缓存。

通用约束

  • 认证:三个端点都依赖verify_token(file_agent.py#L88-L101),校验请求头Authorization: Bearer <token>与API_BEARER_TOKEN环境变量是否一致;
  • 限流约束:单文件最大 10MB、单请求最多 5 个文件、每分钟最多 60 次请求;
  • 错误响应:统一为{"success": false, "markdown": "", "error": "..."}结构;HTTP 状态码 200/400/401/500;
  • 常见报错:API_BEARER_TOKEN未设置返回 500;token 不匹配返回 401;未提供文件时/api/file-agent-cached直接返回"No files provided"。

实战 curl 示例

# 1. 单文件转 Markdown curl -X POST http://localhost:8001/api/convert-to-markdown \ -H "Authorization: Bearer your_token_here" \ -H "Content-Type: application/json" \ -d '{ "file": { "name": "document.xlsx", "type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "base64": "'$(base64 -i document.xlsx)'" } }' # 2. AI Agent 处理(携带会话信息) curl -X POST http://localhost:8001/api/file-agent \ -H "Authorization: Bearer your_token_here" \ -H "Content-Type: application/json" \ -d '{ "query": "请汇总这份表格中 Alpha 列的最大值", "files": [{ "name": "test.xlsx", "type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "base64": "'$(base64 -i test.xlsx)'" }], "session_id": "session_123", "user_id": "user_456", "request_id": "req_789" }'

四、为什么输出长这样:MarkItDown 对工作簿的解析行为

从 test.md 及同目录其他产物的形态可以推断 MarkItDown 的解析规则:

  • 工作表 → 二级标题:每个worksheet的名称(如Sheet1、09060124-b5e7-4717-9d07-3c046eb)被渲染为##标题,多个工作表按源顺序依次排列;
  • 单元格区域 → 管道表格:表头行转为首行| Alpha | Beta | Gamma | Delta |,随后是| --- |分隔行与数据行;
  • 内容保真:数值、UUID 字符串等单元格内容不做任何改写,直接以文本形式输出。

同目录的 local_convert_test_docx.md 展示了 docx 中的表格被解析为相同形态的 Markdown 表格(| 1 | 2 | 3 | ... |),而 local_convert_test_pptx.md 则把每页幻灯片渲染为<!-- Slide number: N -->注释 + 标题层级结构,说明 MarkItDown 对每种格式有对应的结构映射策略——这正是「表格文件统一进、结构化 Markdown 统一出」的关键。

五、文档缓存机制:让同一份文件只转换一次

/api/file-agent-cached的性能优化依赖一套「哈希 + Supabase 缓存」机制,核心逻辑在 file_agent.py#L277-L354:

  1. 计算文档哈希:get_document_hash将base64内容、文件名、文件类型拼接后做sha256,保证内容不同则哈希不同;
  2. 查询缓存:get_cached_markdown按doc_hash查询document_cache表,命中后还会顺手更新last_accessed时间戳,便于缓存淘汰管理;
  3. 回写缓存:未命中时走完整转换,随后store_document_markdown通过upsert把doc_hash、file_name、file_type、markdown_content写入表中。

对应的表结构由迁移文件 20250128_document_cache.sql 定义,关键设计包括:

create table if not exists document_cache ( id bigint generated by default as identity primary key, doc_hash text not null unique, file_name text not null, file_type text not null, markdown_content text not null, created_at timestamp with time zone not null, last_accessed timestamp with time zone not null ); create index if not exists idx_document_cache_hash on document_cache(doc_hash);

doc_hash带unique约束并建有索引,配合开箱即用的 RLS 策略(允许读取、插入、更新),同一文件的重复请求可以跳过昂贵的 LLM 转换步骤。注意:缓存键包含 base64 内容,因此任何单元格改动都会生成新哈希,不会命中旧缓存——这也是缓存正确性的保障。

会话侧的数据结构由 20250128_messages.sql 提供:messages表以session_id建索引,data为jsonb,可承载request_id与files元数据;同时store_message会在内容超过 100KB 时截断并追加...(truncated)标记(file_agent.py#L119-L145),避免超大文档拖垮消息存储。

六、图片走 VLM:同一条管线里的多模态分支

虽然 test.md 是纯表格输出,但同一条转换管线也处理图片,这在源码中表现为imghdr分流后的 VLM 分支(file_agent.py#L220-L229):

if is_image: vlm_model = os.getenv("OPENROUTER_VLM_MODEL") temp_md = MarkItDown(llm_client=openai_client, llm_model=vlm_model)

OPENROUTER_VLM_MODEL未配置时,图片转换会抛出ValueError;若该模型在 API Key 权限之外,接口返回 401 类错误并提示「API key does not have access to vision model」。真实输出样例见 local_convert_test_jpg.md:test.jpg(一张婚纱照)被 VLM 转换为# Description:下的整段自然语言描述,证明图片内容同样进入了 Markdown 语义空间,可被下游 LLM 理解与检索。

七、环境变量与本地部署

转换管线全部通过环境变量驱动,配置模板见 env.example:

变量说明示例默认值
OPENROUTER_API_KEYOpenRouter API Key,需以sk-or-v1-开头sk-or-...
OPENROUTER_MODEL文本处理默认模型mistralai/mistral-7b-instruct
OPENROUTER_VLM_MODEL视觉语言模型,图片处理必需meta-llama/llama-3.2-11b-vision-instruct:free
SUPABASE_URLSupabase 项目 URLhttps://<project-id>.supabase.co
SUPABASE_SERVICE_KEYSupabase service_role 密钥—
API_BEARER_TOKENAPI 访问令牌toto(示例值)

部署方式有两种:

本地开发(Python 3.11+):

python -m venv venv source venv/bin/activate pip install -r requirements.txt cp .env.example .env # 填入真实凭据 mkdir -p test_files markdown_results uvicorn file_agent:app --reload --port 8001

Docker(Dockerfile 基于python:3.11-slim,内置test_files与markdown_results目录,端口可通过PORT构建参数覆盖):

docker build -t file-agent . docker run -p 8001:8001 --env-file .env file-agent

服务启动后监听http://localhost:8001。CORS 中间件放开了所有来源(file_agent.py#L59-L65),便于 Live Agent Studio 前端直接调用。

八、测试与验证:产出目录的命名规律

仓库提供了两条验证路径:

  1. 自动化测试脚本run_tests.sh:依次启动服务、调用 validation_test.py 中的测试函数(test_openrouter_api、test_file_processing、test_convert_to_markdown、test_file_processing_with_llm、test_image_processing_with_llm、test_api_file_agent_cached),结束后清理进程;
  2. 端到端验证:validation_test.py遍历test_files/下全部文件,将转换结果按三种命名规范写入markdown_results/:
    • local_convert_<文件名>_<扩展名>.md:本地 MarkItDown 直转(不使用 LLM 或使用本地模型);
    • api_openrouter_<文件名>_<扩展名>.md:走 API + LLM 转换;
    • agent_openrouter_summary_<文件名>_<扩展名>.md:转换后再让 LLM 生成摘要(图片文件还会附带原图引用与# Original Content、# Summary双段结构,见 agent_openrouter_summary_test_jpg.md)。

test.md 正是这套验证流程中test.xlsx的落盘结果,它与local_convert_*、api_openrouter_*两份同源产物内容完全一致,可以作为「转换管线行为正确」的可复核证据链。若你希望复现,只需把任意 xlsx 文件放入test_files/,运行run_tests.sh或直接调用/api/convert-to-markdown,即可在markdown_results/下生成对应的 Markdown 表格文件。

小结

通过 test.md 这一个真实样本,可以完整还原 ottomarkdown-agent 的文档转 Markdown 管线:base64 上传 → imghdr 分流 → MarkItDown 解析 → Markdown 落盘 →(可选)Supabase 缓存与会话入库。表格文件被结构化为带标题的分节 Markdown 表格,内容零改写;图片走 VLM 分支转为自然语言描述;doc_hash缓存让同一文档的重复处理成本趋近于零。这套实现既是 Live Agent Studio 文件处理 Agent 的参考模板,也是一条可直接移植的「文件 → 结构化文本 → LLM 上下文」通用链路。

  • 示例工程

【免费下载链接】ottomator-agents

All the open source AI Agents hosted on the oTTomator Live Agent Studio platform!

项目地址:https://gitcode.com/GitHub_Trending/ot/ottomator-agents
点击查看免费下载

相关推荐

上一篇:LiveSplit终极教程:从新手到高手的完整速度跑计时指南
下一篇:SteVe OCPP服务器:从零搭建智能充电管理平台的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询