前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
基础篇
进阶篇
高频篇
精选篇
手写篇
原理篇
面经篇
AI 面试
自检篇
每日一题
  • 综合
    • 综合题型
    • 其他问题
    • 设计模式
    • 思维导图
    • 学习路线
  • 前端基础
    • HTTP
    • 浏览器
    • 计算机基础
  • 进阶学习
    • NPM工作流
    • Docker
    • Canvas
    • Node学习指南
    • 前端综合文章
  • 其他
    • Handbook
    • 职场话题
    • CSS可视化
小程序题库
公众号动态
博客动态
AI 热点
开发者导航
返回 AI 情报前线
All News · 全部资讯3809
  • LLM读取非结构化运维笔记的工程实践
  • 基于规则的单区域共享出行Agent实现
  • Claude Code 8月14日起默认启用自动安全模式
  • Zawinski's Law of MultiAgents:多Agent系统的经验法则
  • 模型变更的金丝雀和蓝绿部署策略
  • MCP 协议详解:连接 LLM 与外部工具的开放标准
  • 模型级联:先用便宜模型,失败再升级贵的
  • 模型迁移的金丝雀发布:生产变更的完整策略
  • 校准:让 0.8 真正代表 80% 概率
  • 大模型「中间丢失」问题:上下文越长表现反而越差
  • LoRA 原理:简单思想如何改变大模型微调格局
  • 长上下文 RAG 的陷阱:检索越多效果越差
  • 本地推理何时比调用 API 更划算
  • LLM 数据分析失效的三个层次:代码、统计、解释
  • LLM 成本仪表板设计:五个图表回答三类人的实际问题
  • 分类任务用单 token 而非 JSON:延迟降低一个数量级
  • 模型校准:置信度与真实正确率的统计学对应关系
  • Alert 与 Cap 的本质区别:一个事后通知,一个事前拒绝
  • LLM 账单翻白夜的排查手册:六步查询定位八个根因
  • LLM-as-a-Judge 实战:用少量人工标注换取可信赖的自动化评估
  • LLM 返回列表不全的四种原因及诊断顺序
  • AI 应用 API 密钥安全:推理密钥是支付工具而非普通凭证
  • LLM 监控告警:避免告警疲劳的工程实践
  • 模型为何死不认错:从强化学习目标看幻觉根源
  • JSON Mode、Structured Outputs 与 Grammar Constraints 深度对比
  • 越狱攻击分类学:Jailbreak 与 Prompt Injection 的本质区别
  • LLM 线上排查:先查 prompt 再研究可解释性
  • LLM 计费内幕:账单上每行数字的含义
  • AI助手引用来源的技术原理
  • HNSW向量索引原理详解
  • 推理Token的隐藏成本:可见性与计费
  • 向量嵌入的隐性成本清单
  • Grok Imagine Image 2.0登陆Vercel AI Gateway,支持图文排版与编辑
  • OpenAI意外攻击Hugging Face事件完整时间线
  • LLM开销拆解公式:U×a×s×r×c
  • Transformer 每次推理的计算量:2N×T 法则
  • 一文搞懂 fp32/fp16/bf16/fp8/int4 浮点数格式
  • Flash Attention 为什么更快:IO 感知的本质
  • 微调开源模型的完整流水线与避坑指南
  • 微调任务失败了就看这六种验证错误
  • 微调 vs RAG:解决的是不同问题
  • BERT 编码器模型并未消亡:分类和抽取仍是主场
  • 用 MCP 协议把 Google Sheets 接入 n8n 工作流
  • 推荐系统双塔模型:千万级候选的毫秒级检索架构
  • 向量漂移的四种类型及检测方法
  • 你的 AI 提供商是否用你的数据训练?四个问题分清楚
  • 推理模型真的在推理吗?三个层面的诚实分析
  • 向量距离度量详解:欧氏、余弦、曼哈顿、汉明
  • 部署系统中差分隐私的实际泄露风险
  • 架构模式:随机核心外的确定性轨道
  • GitHub Copilot周更:支持断点续聊与上下文保留
  • 已加载 51 / 3809
9.0
重磅
AI SCORE
技术实践2026-08-08 08:44

LLM 返回列表不全的四种原因及诊断顺序

dev.to · AI#LLM#结构化输出#调试
Editor brief · 编辑速览

结构化输出中列表截断最常见,但原因分四种:max_tokens 截断、上下文边界漏页、minItems 约束被静默忽略、模型真正漏记。诊断顺序按成本从低到高排列。

文章思维导图
Knowledge map
拖拽缩放
Full translation

完整中文译文

你请求提取发票上的每一行项目。共有十行。你得到了七行,JSON 验证通过,没有任何地方报错。这是结构化输出中被报告最多的一类 bug,而且至少有一半的时候模型并不是根因。

四种原因,天差地别的修复方案

按这个顺序诊断,因为前三种排查成本低,第四种修复成本高。每次提取调用都记录 finish_reason、usage.completion_tokens 和你发送的文本长度,前两个问题自己就会浮出水面。

你以为设下的约束

minItems 和 maxItems 位于托管 strict 模式文档支持关键字集之外。在不同的技术栈下,发送它们要么得到一个指出该关键字的 400——很好,你立即就知道了;要么得到一个 200,但关键字被悄悄丢弃了。

第二种情况才是陷阱,因为你的 schema 现在变成了一条注释。你以为底数被强制执行了,API 返回了成功,数组却很短。没有哪个日志说这个约束从未被应用。先确认你的端点属于哪一种,再依赖它——无论哪种情况,都要在数组的 description 里写上"每一项都要,不要总结或跳过",因为这会传达给模型,无论该关键字是否存活。

为什么计数对 decoder 来说是难题

没有计数器。每个 token 从上下文生成,而上下文包含已经输出的项——所以"我是否得到了全部"不是一次查找,而是模型在每个数组元素处根据它能看到的内容重新做出的一次判断。两个结构性后果:

相似项互相竞争。六个只在数字上不同的近邻行是最难的情况,因为已输出的前缀看起来就像是剩余工作的延续。这就是为什么带有重复结构的表格数据是这类 bug 集中的地方。

停止也是一个普通的 token。关闭数组是一个与继续输出竞争的决定。任何让文档感觉已经完成的东西——摘要行、总计、分页符——都会提高输出闭括号的概率。

Schema 设计的推论:永远不要在数组之前请求总数。先输出的 total_items 字段会迫使模型做出一个它随后必须满足的承诺,而它会产生一个与数组不一致的数字。如果在数组之后输出,同样的字段没有任何成本,还给你一个免费的内部一致性检查——len(items) != total_items 是一个真实的信号,表明模型知道自己已经跟丢了。

最强修复把一个开放式召回问题转化为逐项决策。给输入中的候选行编号,并要求输出把编号带回来:

user message:
  Extract every line item. The document lines are numbered.
  Return one object per line that is a line item, carrying its line number.
  Do not skip lines. If a line is not a line item, omit it.

  [L001] Widget, large            2   12.50   25.00
  [L002] Bolt M6                 40    0.15    6.00
  [L003] --- subtotal ---                     31.00
  [L004] Delivery                 1    4.95    4.95

schema:  items[]: { source_line: string, description: string, qty: number,
                    unit_price: number, line_total: number }

三件事同时改变。模型现在要四次判断"L003 是行项目吗",这比一次判断"我找到全部了吗"要容易得多。输出可以在没有标准答案的情况下与输入对照检验,因为你知道哪些行号存在。而且排除变得可见了:被正确省略的小计行在外观上与漏掉的行完全相同,直到行号告诉你发生了什么。

对账函数

def reconcile(sent_lines: list[str], items: list[dict]) -> dict:
    """sent_lines: the [Lnnn] labels you put in the prompt, in order."""
    expected = set(sent_lines)
    got = [i["source_line"] for i in items]

    return {
        "missing":     [l for l in sent_lines if l not in set(got)],  # unexplained
        "hallucinated": [g for g in got if g not in expected],        # not sent
        "duplicated":  [g for g in set(got) if got.count(g) > 1],
        "coverage":    round(len(set(got) & expected) / max(len(expected), 1), 3),
    }

# Then, in the pipeline:
r = reconcile(sent, items)
if r["hallucinated"]:
    fail(record, "cited lines that were not in the input")   # never retry into this
if r["coverage"] < 0.80:
    retry_smaller_chunks(record)                             # a chunking problem
# "missing" is expected and informative: those are the lines the model
# judged not to be items. Sample ten of them by hand once a week.

注意 missing 不是什么。它不是错误列表——小计行应该 missing。它是一个审查队列,是你唯一能看到的模型排除决策的窗口,否则它们完全不可见。一个悄悄丢弃每一条折扣行的模型会出现在这里,不会出现在任何其他地方。

如果因为输入没有天然的行而无法枚举,备选方案是两次 pass:一次调用只列出标识符——输出短,截断风险低;然后每个标识符一次调用提取其字段。这需要更多调用,但对长文档来说显著更可靠,因为两个 pass 都不需要同时做召回和提取。

还有两个小效应来兜底。顺序不保证,你不应该依赖它:要求模型按文档顺序输出项通常会照做,但没有任何东西强制执行它,所以如果顺序重要,就提取排序键并在代码中排序。而重复与遗漏表现相反——uniqueItems 和 minItems 一样位于支持关键字集之外,所以重复项也无法被阻止。重叠的分块使重复成为预期结果而非异常,这就是为什么去重键属于你的合并步骤,而不是 schema。

最后,抵制通过调高 temperature 或在 prompt 里加"要彻底"来修复短数组的冲动。这两个都不针对四个原因中的任何一个;前者用虚构的项换遗漏的项,后者是那种在你测试的三份文档上看起来有效的改动。给四个原因上仪器,找到你遇到的是哪一个,然后修复那个。

Extracting Structured Data From Messy Documents

Optional Fields, Nulls and Unions: Where Schemas Break

Validation and Repair: What to Do With Malformed Output

Original source

本文由 AI 翻译整理自 dev.to · AI,原文版权归原作者所有。

阅读英文原文
上一篇
LLM-as-a-Judge 实战:用少量人工标注换取可信赖的自动化评估
下一篇
AI 应用 API 密钥安全:推理密钥是支付工具而非普通凭证