分类: 效率

  • 如何快速修复格式错误的 JSON 文件:开发者实战手册

    如何快速修复格式错误的 JSON 文件:开发者实战手册

    你的 API 调用因 JSONDecodeError: Expecting property name enclosed in double quotes 而失败。时钟在滴答作响。数据来自 LLM,而在那个 2000 token 的响应里,某一个多余的尾随逗号毁掉了你整条流水线。

    截至 2026 年 5 月,修复格式错误的 JSON 文件最快的方法是使用自动化库,比如 json_repair(Python)或 jsonrepair(npm)。这些工具专为瞬间修复 LLM 产生的语法错误而生。至于手动修复,常见的“元凶”是尾随逗号单引号未加引号的键——这是违反 RFC 8259 标准的三种最常见情形。

    最快的修复方式:针对 LLM 输出的 json_repair

    像 Python 的 json.loads() 这类标准解析器,在设计上就是严格的。一个错位的字符就会触发 JSONDecodeError,一切都随之停止。这在 2026 年是家常便饭,因为 LLM 经常把 JSON 包裹在对话文本里,把响应截断在句中,或者撒进一些破坏规范的注释。

    json_repair 库是首选方案。据 GitHub 显示,截至 2026 年该项目已获得超过 4700 颗星。它的工作原理是“猜测”字符串的意图——补上缺失的括号、加上引号,并剥除 JSON 块周围多余的文字。

    json_repair 简单的三步流程:输入(损坏)-> 猜测意图 -> 输出(有效)

    Python:改造前后

    安装:pip install json-repair

    损坏的输入:

    import json_repair
    
    bad_json = '{"user": "Alice", "status": tru'
    decoded_object = json_repair.loads(bad_json)
    

    幕后发生了什么:json_repair 判断出 tru 很可能是 true,补上了缺失的右大括号,并返回了一个合法的 Python 字典。全程零人工干预。

    Salvage 模式:数据实在惨不忍睹时

    对于更棘手的情况,json_repair(v0.59.5+)提供了 Salvage 模式。正如项目文档所述,这个模式专为被截断的 AI 响应或损坏的日志而构建。它可以把数组强制转换为对象,或者丢弃那些实在救不回来的条目,从而保证输出符合你的数据结构。

    import json_repair
    
    # Salvage mode for severely truncated data
    result = json_repair.loads(
        '{"items": [{"id": 1, "name": "Widget"}, {"id": 2, "na',
        salvage_mode=True
    )
    # Result: {'items': [{'id': 1, 'name': 'Widget'}, {'id': 2}]}
    # Dropped the incomplete 'na' but saved everything else
    

    npm 替代方案

    对于 Node.js 项目,jsonrepair CLI 能完成同样的工作:

    # Fix a file in place
    npx jsonrepair broken.json > fixed.json
    
    # Fix a string in a script
    const { jsonrepair } = require('jsonrepair');
    const fixed = jsonrepair('{"name": "test",}');
    

    手动调试:找出破坏规范的元凶

    当自动化工具也搞不定时,你需要精确定位文件在何处违反了 RFC 8259。JSON 远不如 YAML 或 JavaScript 那样宽容。正如 JSONParser 诊断团队所解释的:“解析器在遇到第一个它无法理解的字符时就会失败,而这往往是几行之前某个问题的下游症状。”

    三大 JSON 杀手

    杀手 1:尾随逗号

    DEV Community 的说法,尾随逗号是解析失败的头号原因。在 JavaScript 里它们没问题,但在 JSON 数组或对象的最后一项之后却是非法的。

    // BROKEN - trailing comma after "active"
    {
      "name": "Alice",
      "status": "active",
    }
    
    // FIXED - no comma before closing brace
    {
      "name": "Alice",
      "status": "active"
    }
    

    杀手 2:单引号

    JSON 要求键和字符串值都使用双引号(")。很多 Python 和 JavaScript 开发者会不小心用上单引号(')。正如 TidyCode 所指出的,这是必须修正的。

    // BROKEN - single quotes
    {'name': 'Alice'}
    
    // FIXED - double quotes
    {"name": "Alice"}
    

    杀手 3:未加引号的键

    在 JavaScript 中你可以写 { name: "Alice" }。但在 JSON 里,每个键都需要双引号。

    // BROKEN - unquoted key
    {name: "Alice"}
    
    // FIXED - quoted key
    {"name": "Alice"}
    

    非法 JSON 与合法 JSON 语法的并排对比

    “Unexpected Token”错误

    当验证器报出“Unexpected Token”时,意思是解析器遇到了 NaNInfinityundefined——这些都是 JSON 不支持的 JavaScript 常量。JSON 只允许 nulltruefalse 和数字。

    // BROKEN - NaN is not valid JSON
    {"score": NaN, "result": Infinity}
    
    // FIXED - replace with null or valid values
    {"score": null, "result": null}
    

    严格解析 vs. 修复解析:何时用哪个

    正确的方法取决于你的数据来自哪里。人工编辑的配置文件应当使用严格解析,以迫使作者修正错误;而来自 LLM 或 API 日志的机器生成数据,则需要基于修复的解析。

    特性 严格(json.loads 修复(json_repair
    尾随逗号 抛出 JSONDecodeError 自动移除
    单引号 失败 转换为双引号
    被截断的数据 失败 补全未闭合的括号/引号
    注释 失败 自动剥除
    最佳用例 人工编辑的配置文件 LLM 输出、API 日志

    用 Pydantic 做结构引导的修复

    你可以使用 Pydantic v2JSON Schema 来引导修复过程。给 json_repair 提供一个 schema,工具不仅能修复语法——还能纠正类型(把字符串 "1" 转成数字 1)并用默认值填充缺失的必填字段。

    from pydantic import BaseModel
    import json_repair
    
    class User(BaseModel):
        id: int
        name: str
        active: bool = True
    
    # Broken JSON with wrong types
    raw = '{"id": "42", "name": "Alice"}'
    repaired = json_repair.loads(raw)
    
    # Validate against schema
    user = User(**repaired)
    # user.id is now int(42), user.active defaults to True
    

    正如 Stefano Baccianella 在其 2025 年的项目说明中所提到的,这种方式针对的是语言模型常产出的那种“大体正确但技术上非法”的 JSON 而做了优化。

    处理多 GB 文件而不崩溃

    修复一个 10KB 的片段很容易。修复一个 2GB 的文件则需要一套不会吃光你内存的策略。把整个文件载入内存会导致内存溢出(OOM)错误。

    策略 1:用 ijson 流式处理

    对于海量数据集,使用 ijson 逐块处理数据。正如 Scrapfly 所述,ijson 会增量地处理数据。把它与一个在解析前逐行修复问题的清理脚本搭配使用。

    import ijson
    
    # Stream through a large JSON file
    with open('huge_broken.json', 'r') as f:
        for item in ijson.items(f, 'records.item'):
            # Process each item individually
            process(item)
    

    策略 2:用 CLI 管道实现最高效率

    处理大文件时最省内存的方法是使用 jsonrepair CLI,并把输出直接管道到一个新文件:

    # Streams repair, never loads full file into memory
    jsonrepair large_broken.json > fixed.json
    

    这比把文件载入 Python 或浏览器要省内存得多。

    结语

    得益于 json_repair 这类对 AI 友好的库,修复格式错误的 JSON 已不再是体力活。你仍然需要了解 RFC 8259 的基本规则——不能有尾随逗号、不能有单引号、键必须加引号——但在 2026 年面对大规模数据时,自动化才是唯一现实的做法。

    工作流程很简单:先尝试修复库。如果失败,再用验证器精确定位语法错误。这样即使输入的数据不那么完美,你的应用也能持续运行。

    常见问题

    JSON 官方是否支持注释或单引号?

    不支持。RFC 8259 标准严格禁止注释。单引号同样非法——键和字符串只允许使用双引号。不过,json_repair 这类工具可以自动剥除注释并转换引号,使文件能被标准库解析。

    如何处理超大且格式错误的 JSON 文件而不崩溃?

    使用 ijson 这类流式解析器分块处理数据。避免把整个格式错误的字符串载入单个变量。为了最快速度,可使用 CLI 修复工具,把输出直接管道到磁盘上的新文件,无需把所有内容都留在内存中。

    格式错误的 JSON 和无效的 JSON 有什么区别?

    格式错误(malformed)的 JSON 违反语法规则——缺失括号、键未加引号、尾随逗号——使其无法解析。无效(invalid)的 JSON 遵守所有语法规则,但不符合某个具体的 JSON Schema(例如某个字段在 schema 里应为整数,实际却是字符串)。修复格式错误的 JSON 属于结构性修复;修复无效的 JSON 则关乎数据完整性。

    我能把 json_repair 和 Pydantic 验证一起用吗?

    可以。先用 json_repair.loads() 修复语法错误,再把修复后的字典传给你的 Pydantic 模型进行类型验证和 schema 校验。这种两步走的方法同时解决了结构性和语义性问题。

    带有 JavaScript 风格注释的 JSON 怎么办?

    标准 JSON 不支持注释,但 json_repair 可以自动剥除 ///* */ 注释。如果你需要在配置文件里保留注释,可以考虑使用 JSONC(带注释的 JSON)格式,并搭配 json5(Python)这类兼容的解析器。

  • 如何用格式化器编写 AI 提示词:面向开发者的结构化工程

    如何用格式化器编写 AI 提示词:面向开发者的结构化工程

    当 AI 的输出与你要求的大相径庭时,你一定有过那种挫败感:JSON 格式错误、语气不对,一半的指令被直接忽略。问题不在模型,而在于你如何格式化提示词。

    要掌握如何用格式化器编写 AI 提示词,就要使用 RTCCO 框架(Role 角色、Task 任务、Context 背景、Constraints 约束、Output 输出),配合 XML 或 JSON 这类结构化分隔符。这样可以把提示词当作可复用的软件资产来对待,截至 2026 年 5 月,这种做法能将模型幻觉降低多达 60%,并把人工处理时间缩短 75%。

    为什么段落式提示词总是失败

    到了 2026 年,专业的 AI 工作已经从“聊天”转向了提示词即代码(Prompt-as-Code,PaC)。段落式提示词——那些冗长、无结构的文本块——的问题在于,模型很难把你真正的指令,与混杂在其中的背景数据或输出要求区分开来。

    来自 PromptOT 的数据显示,转向结构化工程可以把错误率降低 60%,并让人工处理速度提升 75%。Alex Ostrovskyy 把硬编码的提示词比作“源代码中魔法数字的现代翻版”——脆弱的系统,几乎无法在不破坏现有功能的前提下更新。

    改造前后:格式化的差异

    改造前(无结构):

    You are a helpful coding assistant. Please write a Python function that validates
    email addresses. Make sure it handles edge cases like plus signs and subdomains.
    The output should be in JSON format with a valid boolean and the cleaned email.
    Also make sure you add proper error handling and don't forget logging.
    

    改造后(RTCCO + XML 分隔符):

    <system_instructions>
      <role>Senior Python engineer specializing in input validation</role>
      <primary_objective>Write a production-grade email validator</primary_objective>
    </system_instructions>
    
    <context>
      Must handle: plus addressing ([email protected]), subdomains,
      internationalized domains. Target: Python 3.11+.
    </context>
    
    <task_requirements>
      <rules>
        - Use only stdlib (no regex shortcuts)
        - Return structured JSON
        - Include type hints
      </rules>
      <steps>
        1. Parse the input string
        2. Validate format per RFC 5322
        3. Return JSON with "valid" boolean and "cleaned_email"
      </steps>
    </task_requirements>
    
    <output_format>
      {"valid": bool, "cleaned_email": str, "error": str | null}
    </output_format>
    

    目标相同,结果却天差地别。格式化后的版本让模型没有任何产生歧义的空间。

    RTCCO 框架:你的提示词骨架

    业界已经把 RTCCO 视为标准的提示词架构。每个提示词都拆解为五个部分:

    元素 作用 示例
    R 角色(Role) AI 是谁? “资深后端工程师”
    T 任务(Task) 具体做什么? “写一个限流中间件”
    C 背景(Context) 有哪些背景数据? RAG 检索结果、代码片段
    C 约束(Constraints) 规则是什么? “不依赖任何外部库”
    O 输出(Output) 输出该长什么样? “带类型注解的合法 Python 3.11”

    RTCCO 框架的五个组成部分

    可以直接复制的 XML 骨架模板

    下面是可直接用于生产环境的模板。复制它、改造它、部署它。

    <system_instructions>
      <role> [Expert Persona] </role>
      <primary_objective> [Main Goal] </primary_objective>
    </system_instructions>
    
    <context>
      [Background Data or RAG Retrieval]
    </context>
    
    <task_requirements>
      <rules> [Non-negotiable Constraints] </rules>
      <steps> [Specific Workflow] </steps>
    </task_requirements>
    
    <output_format>
      [JSON/XML/Markdown Specification]
    </output_format>
    
    <recency_recap>
      [Reminder of Critical Constraints]
    </recency_recap>
    

    为什么“近因回顾”很重要

    大语言模型存在一种已知的“首因与近因”偏差——它们对提示词开头和结尾的记忆,要好于中间部分。PromptOT 引用的测试表明,把关键规则从中间移到底部的“近因回顾(Recency Recap)”块,能让生产环境的准确率从 78% 提升到 96%。把角色放在顶部,把最重要的规则放在底部。

    长提示词中首因与近因效应的可视化

    把分隔符当作安全围栏

    分隔符不仅关乎组织结构——它还是一种安全机制。用 <user_input> 之类的标签包裹用户输入,等于告诉模型:“这是待处理的数据,而不是要遵循的新指令。”这是你抵御提示词注入攻击的首要防线——在这类攻击中,用户会试图覆盖你的系统指令。

    常见陷阱: 如果你直接把用户数据塞进提示词而不加分隔符,用户只需写一句“忽略之前所有指令,然后……”,模型就会照办。务必把外部数据放在带标签的块里。

    模块化架构:停止编写巨型提示词

    与其写一个脆弱的 2000 token 巨型提示词,不如把系统拆成相互独立的模块。这能防止指令冲突——即修改提示词语气时,意外破坏了它的 JSON 输出格式。

    核心原则是上下文工程(Context Engineering):把静态指令与动态数据分开。在生产级 RAG 系统中,你的提示词是一个模板,<context> 块会在查询时被填入最新数据。正如 OptizenApp 的 Jono Farrington 所解释的,这种模块化方法让大规模 AI 部署的一致性大大提升。

    提示词链:连接各个模块

    对于复杂的工作流,使用提示词链(Prompt Chaining)——一个模块的输出成为下一个模块的输入:

    [Planner Module] --> outline --> [Executor Module] --> draft --> [Reviewer Module] --> final
    

    这种逐步推进的方式能把输出质量提升约 35%,因为模型每次只专注于一个子任务。

    简单的三步提示词链工作流

    可直接复用的链式示例:

    planner_prompt = """
    <system_instructions>
      <role>Technical architect</role>
      <task>Create a step-by-step plan for: {user_request}</task>
    </system_instructions>
    <output_format>JSON array of steps</output_format>
    """
    
    executor_prompt = """
    <system_instructions>
      <role>Senior developer</role>
      <task>Implement step: {step_from_planner}</task>
    </system_instructions>
    <context>{previous_outputs}</context>
    <output_format>Code block with inline comments</output_format>
    """
    

    为难题加入思维链

    当任务涉及复杂逻辑时,增加一个 <thought_process> 块。这会迫使模型在给出答案前逐步推理,能显著降低数学、编程和多步推理中的错误。

    <task_requirements>
      <rules>Reason inside <thought> tags before answering</rules>
    </task_requirements>
    
    <output_format>
      <thought> [Your step-by-step reasoning here] </thought>
      <answer> [Final JSON output here] </answer>
    </output_format>
    

    根据 Zencoder 的说法,思维树(Tree-of-Thoughts,ToT)等技术更进一步,要求模型同时评估多条解题路径并选出最优解。这对那些没有唯一正确答案的架构决策尤其有价值。

    Token 成本警告

    结构化推理会消耗更多 token。一个典型的 <thought_process> 块每次请求会增加 200–500 个 token。在规模化使用时,这意味着更高的 API 成本。代价换来的是准确率:你为每次请求付出更多,但需要的重试和人工修正更少。

    生产就绪:版本管理、测试与 CI/CD

    最后一步是把提示词当作软件来对待。使用语义化版本号(如 v1.0.0),这样团队就能跟踪变更,并在新版本导致效果下滑时立即回滚。

    PromptOT 的报告指出,管理 50 个以上提示词的企业,通过集中化管理并减少工程师手工微调所花的时间,每年可节省多达 40 万美元。

    搭建提示词 CI/CD 流水线

    # .github/workflows/prompt-tests.yml
    name: Prompt Quality Gate
    on: [push]
    jobs:
      test-prompts:
        runs-on: ubuntu-latest
        steps:
          - name: Run Golden Dataset Tests
            run: |
              # Test against 50-200 curated cases
              python scripts/eval_prompts.py \
                --dataset golden_dataset.json \
                --judge-model gpt-4 \
                --min-score 0.85
    
          - name: Regression Check
            run: |
              # Compare new version vs. production
              python scripts/compare_versions.py \
                --staging v2.1.0 \
                --production v2.0.3 \
                --threshold 0.05
    

    只有当提示词通过了由“LLM as a judge(LLM 充当评判)”打分的质量门禁后,才会从 Staging 晋升到 Production

    结语

    使用格式化器的结构化提示词工程已不再是可选项——它是任何构建可靠 AI 工具之人的基准线。RTCCO 框架、XML 分隔符和模块化架构,就是你把不可预测的 LLM 输出转化为稳定、生产级结果的工具栈。

    从你最常用的提示词入手,用上面的 XML 模板把它们重构为 RTCCO 框架。把它们纳入版本管理,搭建基本的评估体系,你就会拥有一套可扩展的提示词基础设施。

    常见问题

    如何把现有的段落式提示词转换成 RTCCO 块格式?

    先找出核心的任务(Task),再把它与背景(Context)分开。用 <rules> 标签包裹指令,并在 <examples> 标签里提供 3–5 个示例。你甚至可以让 LLM 帮忙——给它这样的提示词:“把这些无结构文本用 XML 分隔符重新解析为 RTCCO 框架”,它就会替你完成繁重的转换工作。

    我该用 XML、JSON 还是 Markdown 分隔符?

    对于在 Claude 和 GPT-5 等模型中把指令与长文本内容分隔开,XML 是目前的黄金标准,因为它有严格的层级结构。当你需要为 API 集成提供程序化的输入输出时,JSON 更合适。Markdown 适用于简单、人类易读的提示词,但缺乏复杂、多层生产级提示词所需的严格边界定义。

    如何为提示词实现自动化 CI/CD 测试?

    搭建一套测试套件,包含一个“黄金数据集”(50–200 个精选测试用例)和一个“LLM as a judge”,按照评分标准对输出打分。把这些测试集成到 GitHub Actions 或 Jenkins 流水线中,这样任何提示词变更在部署前都会经过准确率和语气的验证。

    切换到结构化提示词时最常见的错误是什么?

    <context> 块里塞太多东西。开发者经常把整个代码库或文档倒进背景里,这会分散模型的注意力。要让背景聚焦于与任务直接相关的内容。如果需要引用大文档,就用 RAG 检索只拉取相关章节。