- 示例工程
【免费下载链接】ottomator-agents
All the open source AI Agents hosted on the oTTomator Live Agent Studio platform!
导读
本文以 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
| Alpha | Beta | Gamma | Delta |
|---|---|---|---|
| 89 | 82 | 100 | 12 |
| 76 | 89 | 33 | 42 |
| 60 | 84 | 19 | 19 |
| 7 | 69 | 10 | 17 |
| 87 | 89 | 86 | 54 |
| 23 | 4 | 89 | 25 |
| 70 | 84 | 62 | 59 |
| 83 | 37 | 43 | 21 |
| 71 | 15 | 88 | 32 |
| 20 | 62 | 20 | 67 |
| 67 | 18 | 15 | 48 |
| 42 | 5 | 15 | 67 |
| 58 | 6ff4173b-42a5-4784-9b19-f49caff4d93d | 22 | 9 |
| 49 | 93 | 6 | 38 |
| 82 | 28 | 1 | 39 |
| 95 | 55 | 18 | 82 |
| 50 | 46 | 98 | 86 |
| 31 | 46 | 47 | 82 |
| 40 | 65 | 19 | 31 |
| 95 | 65 | 29 | 62 |
| 68 | 57 | 34 | 54 |
| 96 | 66 | 63 | 14 |
| 87 | 93 | 95 | 80 |
工作表 2:09060124-b5e7-4717-9d07-3c046eb
| ColA | ColB | ColC | ColD |
|---|---|---|---|
| 1 | 2 | 3 | 4 |
| 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | affc7dad-52dc-4b98-9b5d-51e65d8a8ad0 |
从这个输出可以读出三条关键信息:
- 多工作表被完整保留:每个工作表都以
## 工作表名作为二级标题,表格内容逐一渲染,顺序与源文件一致; - 单元格内容原样呈现:
Sheet1中混入的 UUID 字符串(如6ff4173b-42a5-4784-9b19-f49caff4d93d)和第二个工作表中的 UUID 单元格都被原样写入表格,说明转换管线只做格式转换、不做内容改写; - 输出路径可复现:同一份
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_contentMarkItDown是转换的核心引擎,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 | 单文件纯转换,返回 Markdown | file_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.name | string | 原始文件名(含扩展名) |
| file.type | string | 文件的 MIME 类型 |
| file.base64 | string | 文件的 base64 编码内容 |
响应结构:
| 字段 | 类型 | 说明 |
|---|---|---|
| success | boolean | 转换是否成功 |
| markdown | string | 转换后的 Markdown 内容 |
| error | string | 失败时的错误信息 |
支持的输入类型(来自 README.md):文档.pdf.docx.txt;表格.xlsx.csv;演示文稿.pptx;网页.html;图片.jpg.jpeg.png.gif.bmp.tiff。
2. Agent 智能处理:/api/file-agent
在纯转换之上叠加了「query 指令 + 会话历史」能力:
| 字段 | 类型 | 说明 |
|---|---|---|
| query | string | 给 AI Agent 的处理指令 |
| files | array | 待处理文件列表 |
| files[].name | string | 原始文件名 |
| files[].type | string | MIME 类型 |
| files[].base64 | string | base64 编码内容 |
| session_id | string | 会话唯一标识,用于维持上下文 |
| user_id | string | 用户标识 |
| request_id | string | 请求唯一标识 |
处理流程(对应 file_agent.py#L356-L418):
fetch_conversation_history按session_id倒序拉取最近 10 条消息并反转成时间正序;- 把用户 query 连同文件元数据写入
messages表; process_files_to_string(request.files, query=request.query)转换文件,并让 LLM 按 query 对转换结果做加工;- 将 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:
- 计算文档哈希:
get_document_hash将base64内容、文件名、文件类型拼接后做sha256,保证内容不同则哈希不同; - 查询缓存:
get_cached_markdown按doc_hash查询document_cache表,命中后还会顺手更新last_accessed时间戳,便于缓存淘汰管理; - 回写缓存:未命中时走完整转换,随后
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_KEY | OpenRouter 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_URL | Supabase 项目 URL | https://<project-id>.supabase.co |
| SUPABASE_SERVICE_KEY | Supabase service_role 密钥 | — |
| API_BEARER_TOKEN | API 访问令牌 | 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 8001Docker(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 前端直接调用。
八、测试与验证:产出目录的命名规律
仓库提供了两条验证路径:
- 自动化测试脚本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),结束后清理进程; - 端到端验证:
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!
相关推荐
BlockNote Markdown 导出机制解析:从复杂文档快照看 blocks 到 Markdown 的转换管线
BlockNote Markdown 导出机制解析:从复杂文档快照看 blocks 到 Markdown 的转换管线 本指南以 BlockNote 仓库中格式转
前端富文本UI组件AI 应用ottomarkdown-agent 实战解析:PPTX 转 Markdown 的转换原理、输出结构与测试复现
ottomarkdown agent 实战解析:PPTX 转 Markdown 的转换原理、输出结构与测试复现 导读 本文以 ottomarkdown agen
示例工程ConvertX文件缓存机制:加速重复格式转换
ConvertX文件缓存机制:加速重复格式转换 你是否遇到过这样的情况:重复上传相同文件转换为同一格式时,每次都要等待漫长的处理过程?ConvertX的文件缓存
后端前端音视频
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考