Hacker News 上有人问了一个很实际的问题工具给 LLM 报告数据用什么格式最好这个提问看起来简单但凡是写过 Agent、Function Calling、RAG 管道或者后端服务的人基本都踩过类似的坑——工具明明把数据查出来了大模型却要么没读明白要么回答到一半 token 用尽要么根本解析不了你精心构造的嵌套结构。这篇文章就把这个问题摊开讲清楚对比 JSON、Markdown、YAML、JSON Lines、纯文本日志和自然语言摘要几种常见做法的优缺点并给出一套可以落地验证的测试思路。先说结论不存在一种“通用最优格式”。同一个工具给人和给 LLM 提供的数据视图大概率需要分开设计。机器对机器对接默认优先考虑 JSON需要人和模型同时阅读、或者要展示查询结果时Markdown 是更自然的选择当原始数据规模很大或者你希望尽量省 token可以让工具先生成一层摘要再把摘要和少量关键字段喂给模型而不是把整份原始日志倒进上下文。这篇文章会从格式选型开始接着讲格式设计的基本原则然后展开一个监控告警工具的实战示例再给出测试环境、接口调用、批量验证方法以及 token 开销观察和常见问题排查。适合正在做 LLM 应用开发、Agent 工具链、RAG 或日志诊断服务的开发者阅读。如果你正在纠结“工具输出的结果字段到底该怎么组织”这篇建议收藏备用。1. 数据报告格式方案速览格式机器可解析人类可读Token 开销容错性典型场景JSON高中中高中Function Calling、接口返回、结构化输出Markdown中高中中查询结果展示、人机共读上下文YAML高中高低低配置说明、Schema 描述JSON Lines高低中高日志流、批量任务、ETL纯文本 / 日志低中低高日志分析、RAG 文本块自然语言摘要低高低高Agent 决策、预消化内容表格里的“高/中/低”是经验判断最终要以实际使用的 tokenizer 统计为准。选格式时重点看三件事这个数据最终是否还要被代码解析是只给大模型看还是人也要看以及上下文窗口是否紧张。2. 适用场景与设计原则2.1 这个问题的适用人群这个问题的典型场景有几类。第一类是你正在开发 Agent工具调用之后需要把执行结果返回给 LLM让它决定下一步行动第二类是你在做日志或监控分析想用大模型快速定位故障但日志原文太长不能全部塞进提示词第三类是你在做 RAG需要把数据库查询、API 返回或文档片段组织成模型容易消费的文本块第四类是你在设计批量任务比如每天把一批业务报表喂给 LLM 生成摘要。这些场景表面上是“格式选型”本质上是“如何用最低的 token 成本把信息无损地送进模型上下文”。2.2 不适合直接上 LLM 的场景也要说清边界。如果你的场景是低延迟、高并发的纯规则判断比如根据告警阈值直接发工单那完全不需要大模型介入。如果数据是几百 MB 的原始日志、视频字幕或数据库全量导出也不应该直接灌进上下文需要先做采样、聚合、索引或摘要。如果业务要求每次输出都必须严格符合某个数据库事务约束LLM 的输出天然带随机性必须配合校验和重试机制甚至改用规则引擎兜底。2.3 隐私、安全与合规边界工具向 LLM 报告数据本质上是在跨系统传递信息。日志里经常混着用户名、手机号、IP、Token、密钥等敏感信息直接喂给外部大模型接口等于变相外发数据。所以格式设计的第一条原则不是“选什么格式”而是“数据能不能给、要给多少”。建议在工具出口统一做脱敏手机号、邮箱、身份证号先打码密码和令牌直接丢弃内部 IP 和主机名按需替换成脱敏别名。不要为了图省事在 prompt 里带上明文的数据库连接串或云厂商 Secret。3. 主流格式横向对比3.1 JSON机器对接的默认选项JSON 是 LLM 应用里最常见的工具输出格式原因很直接几乎所有的 Function Calling 协议、Agent 框架和工具调用 SDK 都用 JSON 传递参数大模型的训练语料里也包含大量 JSON模型对字段名、嵌套结构的理解通常比较稳定。一个标准工具返回可以长这样{ tool: db_query, status: success, elapsed_ms: 120, result: { row_count: 32, columns: [user_id, amount], sample: [ {user_id: 1001, amount: 89.5}, {user_id: 1002, amount: 120.0} ] } }这段 JSON 的好处是能被代码直接解析也可以通过 JSON Schema 做严格校验。缺点是字段名、花括号和引号会消耗不少 token如果嵌套层次太深或者字段名取得又长又重复模型反而容易“迷失”。设计时建议把最重要的字段放到顶层或靠近开头不要让模型翻到底部才能找到结论。如果不需要给用户看原始结果可以去掉冗余字段只保留关键的 row_count、sample 和 status。3.2 Markdown人机共读的最优解当输出结果需要同时面向人和模型时Markdown 的性价比很高。它比纯文本多了结构又比 JSON 更接近自然语言。典型例子是数据库查询结果直接渲染成 Markdown 表格| 服务 | 状态 | 最近错误 | | --- | --- | --- | | api-gateway | ok | - | | billing | degraded | timeout: 3 times | | auth | ok | - |大模型对这种表格的理解通常不错因为表格的行列语义清楚列名就是上下文。人看起来也直观。缺点是模型在复述 Markdown 时容易产生多余的表头或分隔线如果表格列数太多、单元格太长token 消耗会明显上升。对于宽表建议先做列裁剪只保留模型决策需要的几列。3.3 YAML省 token 但缩进敏感YAML 在 token 效率上比 JSON 更有优势因为省掉了花括号和大量引号。比如上面这个 JSON 例子用 YAML 写出来更短tool: db_query status: success elapsed_ms: 120 result: row_count: 32 sample: - user_id: 1001 amount: 89.5YAML 的缺点是缩进和换行容易出错模型在生成 YAML 时偶尔会破坏缩进代码解析时比 JSON 更容易踩坑。所以在“工具输出给 LLM”这个方向上YAML 更适合作为配置说明或 Schema 注释而不是默认的工具返回格式。3.4 JSON Lines批量与流式场景JSON Lines每行一个 JSON 对象在日志分析、批量任务和 ETL 场景非常合适。它天然支持流式追加每一行都可以独立解析即使其中一行损坏也不影响其他行。示例{ts: 2025-05-10T10:00:01Z, level: INFO, message: request started} {ts: 2025-05-10T10:00:02Z, level: ERROR, message: upstream timeout} {ts: 2025-05-10T10:00:03Z, level: INFO, message: retry}想用 LLM 分析日志时可以按时间窗口把 JSON Lines 切成若干段每段内部按时间正序排列这样模型的因果推理会更容易。缺点是直接读起来不够友好需要配合摘要或查询再喂给模型。3.5 纯文本与自然语言摘要当数据量很大时与其把全部结构化字段都塞进上下文不如让工具先生成一段摘要。比如一个脚本检查了 500 条错误日志它不需要把 500 条全部发给 LLM而是输出最近 10 分钟 billing 服务出现 3 次 upstream timeout 分布在 10:00:02 / 10:00:11 / 10:00:30均来自同一上游地址 其余服务正常。CPU 峰值 72%内存占比 61%。这种“预消化文本”的 token 效率最高模型阅读负担最低。适合做 Agent 决策、故障摘要和日报生成。缺点是信息已经被工具压缩过如果摘要本身有偏差LLM 无法看到原始细节所以在摘要后面带上原始日志的引用路径或 ID 很关键。4. 与 Function Calling、MCP、RAG 的衔接4.1 Function Calling 的回传结构在 Function Calling 流程里工具返回的内容通常是一个字符串框架把它作为 function result 回传给模型。这里最容易犯的错误是把返回文本写成一段无结构的话比如“查询成功了查到 32 行数据”。更好的做法是让工具返回一个紧凑的 JSON 字符串再由调用方决定是否原样透传import json def query_database(sql: str) - str: # 实际执行 SQL这里只做演示 result { tool: db_query, status: success, sql: sql, row_count: 32, sample: [ {user_id: 1001, amount: 89.5}, ] } return json.dumps(result, ensure_asciiFalse)这里的关键是返回给 LLM 的字符串应该保持结构化方便后续链路继续解析同时要控制长度如果样本很多sample 里只放前几行再补一个“total_count”或“has_more”字段。4.2 双视图设计成熟的工具输出应该做双视图一个视图给机器和程序消费用完整 JSON另一个视图给 LLM 和人类阅读用 Markdown 或摘要。举个例子一个告警查询工具可以同时返回{ summary: billing 服务 degraded最近 10 分钟出现 3 次 upstream timeout, raw: { service: billing, error_count: 3, time_window: 10m } }LLM 优先读 summary需要细节时再看 raw。这种设计既控制了 token又保留了信息回溯能力。在实现时工具函数可以返回一个 Python dataclass再统一序列化成字符串避免每次手写拼接。4.3 MCP 与 RAG 的格式思路MCPModel Context Protocol的出现把工具、资源和提示词做了标准化但工具输出给模型的格式问题依然存在。MCP 工具返回的内容最终还是要变成文本或结构化内容回到模型上下文设计原则与上面一致尽量结构化、控制长度、确保关键信息在前部。RAG 场景也一样文本块不能是孤立的正文需要带上 source、heading、score 等元数据让模型知道这段内容的来源和可信度。例如{ source: docs/deploy.md, heading: Rollback, score: 0.87, content: 如果发布后出现异常先回滚到上一个稳定版本... }带上元数据的文本块能让 Agent 在引用依据时更准确也方便做来源追溯。5. 实战示例监控告警工具的报告格式设计下面模拟一个真实场景一个采集脚本每 5 分钟收集服务器的 CPU、内存、磁盘和最近错误日志然后调用 LLM 生成一段故障分析和处理建议。这个场景足够典型能看出格式设计如何影响模型效果。第一版直接全部原始字段发给 LLMJSON 结构可能是这样{ host: prod-billing-01, cpu_percent: 72, memory_percent: 61, disk_percent: 45, errors: [ {time: 10:00:02, message: upstream timeout, service: billing}, {time: 10:00:11, message: upstream timeout, service: billing}, {time: 10:00:30, message: upstream timeout, service: billing} ], window_minutes: 10 }这个 JSON 信息量没问题但如果采集周期很长、错误日志很多errors 数组会迅速膨胀。比如一个小时内产生 2000 条错误日志这条消息可能超过 2 万 token既浪费成本也会撑爆上下文。第二版可以改成“摘要 关键样本”模式工具先做聚合统计错误类型和出现次数只保留少量代表性错误样例{ host: prod-billing-01, metrics: { cpu_percent: 72, memory_percent: 61, disk_percent: 45 }, alert_summary: billing 服务 degraded最近 10 分钟出现 3 次 upstream timeout连续重试失败, top_errors: [ {type: upstream_timeout, count: 3, last_time: 10:00:30} ] }如果希望模型直接给出可读性更好的结论可以不把完整 JSON 放进系统提示而是拼接成一段紧凑的 Markdown 再喂给模型Host: prod-billing-01 指标: CPU 72%, 内存 61%, 磁盘 45% 告警摘要: billing 服务 degraded最近 10 分钟出现 3 次 upstream timeout 代表错误: upstream_timeout x3, 最近一次 10:00:30两种方案都能用区别在于后续是否还需要程序解析输出。如果后面有工单系统要解析保留 JSON 更合适如果只是给模型生成人类可读的分析报告Markdown 或纯文本摘要更省 token。实际项目里可以把两者结合接口层返回 JSON喂给模型前再用模板转成 Markdown。6. 测试环境、接口调用与批量验证6.1 最小测试环境要验证格式选型不需要搭复杂系统。建议准备一个本地测试环境Python 3.10 以上安装 requests 或你使用的 LLM SDK准备一个 OpenAI 兼容接口或本地模型服务比如本地部署的 Ollama、vLLM 等。还要准备一份样本数据最好来自真实业务比如 50 条日志、100 行查询结果或一批监控告警。再准备几套不同格式的同一份数据用于 A/B 对比。6.2 发送不同格式数据的脚本下面是一个通用示例用来比较 JSON 和 Markdown 两种格式的效果。注意 endpoint、模型名和密钥需要按照你的实际服务替换import requests import json url http://your-llm-server/v1/chat/completions headers {Authorization: Bearer your_token} json_payload { model: your_model_name, messages: [ {role: system, content: 你是运维分析助手从工具输出中提取故障服务。}, {role: user, content: json.dumps({ service: billing, error_count: 3, errors: [upstream timeout] * 3 }, ensure_asciiFalse)} ], temperature: 0.2 } response requests.post(url, jsonjson_payload, timeout60) print(response.json())这里不要一次性把两种格式都塞进同一个请求应该控制变量同一份数据、同一个 prompt 模板只改数据格式分别调用多次记录成功率、token 消耗和输出质量。6.3 校验 LLM 输出为了减少“模型输出不合法”的问题建议用 JSON Schema 或 Pydantic 做输出校验。一个简单的做法是强制模型返回 JSON并用 JSON Schema 校验{ type: object, properties: { failed_services: {type: array, items: {type: string}} }, required: [failed_services] }如果校验失败可以让模型重试一次或者走降级逻辑比如提取所有行首的服务名而不是直接报错。6.4 批量验证指标批量验证时建议记录以下指标任务成功率、JSON 解析率、平均 token 消耗、平均响应时间、输出是否包含预期字段。把结果整理成表格比如格式调用次数成功率平均 token平均响应时间JSON2090%12804.2sMarkdown2085%9403.6s有了这个表你就能根据成本和效果做决策而不是凭感觉选格式。7. Token 开销与上下文占用观察7.1 Token 怎么统计不同模型有不同 tokenizer不能只看字符数。可以用模型对应的 tokenizer 库来统计。如果你用的是 OpenAI 兼容模型可以用 tiktoken如果用开源模型可以用 transformers 的 AutoTokenizer。示例from transformers import AutoTokenizer tokenizer AutoTokenizer.from_pretrained(your_model_name) text json.dumps(your_payload, ensure_asciiFalse) tokens tokenizer.encode(text) print(len(tokens))这个统计可以在离线阶段完成。把同一份数据用 JSON、Markdown、纯文本三种格式分别编码观察 token 数量差距。字段名、引号、缩进、重复结构都会影响 token实际差别往往比想象的大。7.2 控制上下文的几个手段一是数据裁剪只保留模型决策需要的字段。二是聚合压缩把相同的错误类型合并计数而不是逐条罗列。三是分块按时间窗口或业务维度切分后分批喂给模型。四是滑动窗口只保留最近的 N 条记录更早的内容用摘要替换。五是摘要反馈先跑一次轻量摘要把摘要作为后续上下文的一部分。7.3 降级方案即使做了压缩也可能会出现超长输入。稳妥的做法是设置硬性上限比如超过 8000 字符就强制截断并在 prompt 里明确告诉模型“数据已截断仅基于提供内容分析”。同时工具侧应保留完整数据的存储路径或查询 ID模型需要细节时可以再发起一次工具调用获取。8. 常见问题与排查方法问题现象可能原因排查方式解决方式模型回答时忽略了部分字段字段分布太靠后或被嵌套太深观察完整 prompt 内容把关键字段前置减少嵌套层次模型输出无法解析未指定 JSON 输出或温度过高检查返回内容和报错信息开启 JSON 模式降低 temperature增加 Schema 校验与重试请求提示上下文超长原始数据过多或字段冗余统计 token 数量做摘要、裁剪、分块只保留关键样本Markdown 表格数据错乱列数过多、单元格过长查看模型是否复述表头错误裁剪列把长文本移出表格YAML 缩进被破坏模型生成时缩进不稳定解析 YAML 报错换回 JSON或让工具生成 YAML私密数据外泄风险日志未脱敏直接上报检查日志内容和接口日志在工具出口做脱敏禁止上传密钥和明文手机号批量任务中途卡住某条数据格式异常或超时加日志记录每条任务状态设置超时、失败重试和任务级错误隔离9. 最佳实践与合规建议先定义工具的返回 Schema再写工具逻辑。Schema 能同时约束程序解析和模型理解。数据最小化。只传模型做决策必需的字段不传无关信息。关键信息前置。结论、告警级别、受影响服务放在最前面。摘要与原始引用并存。摘要降低 token 消耗引用保证可回溯。双视图输出。机器消费完整 JSON模型消费精简摘要人看 Markdown 报告。统一做敏感信息脱敏。手机号、邮箱、IP、密钥在工具出口处理绝不上传明文 Token。对模型输出做校验和重试。避免一次解析失败就中断整个流程。批量任务记录日志。每个任务的成功、失败、token、耗时都要可回放。外部接口调用注意访问控制。本地服务接口不要无鉴权暴露到公网。涉及用户数据、商业日志时确权后再进入 LLM 链路并且优先使用本地部署或企业版接口。10. 总结与下一步这个问题的答案可以压缩成一句话机器对接用 JSON人机共读用 Markdown数据量大就先让工具做摘要再决定是给结构化字段还是给自然语言文本。没有放之四海而皆准的格式只有面向场景的组合方案。最应该先做的验证是拿一份真实业务数据用同一套系统提示词分别跑 JSON、Markdown 和摘要文本三种格式记录成功率、token 消耗和模型输出质量。最容易踩的坑是把所有原始日志、全部字段不加选择地塞进上下文导致成本暴涨和关键信息被淹没。接下来可以继续往两个方向深化一是结合 Function Calling 或 MCP 协议把工具输出设计成标准的结构化上下文二是建立一个小型评测集把格式变更纳入回归测试防止后续改动影响 Agent 的决策质量。