BAML jsonish 柔性解析器:把 LLM 自由文本可靠地解析成结构化数据

发布时间:2026/9/25 3:00:50
BAML jsonish 柔性解析器:把 LLM 自由文本可靠地解析成结构化数据 编程语言AI Agent编译器CLI人工智能【免费下载链接】bamlThe programming language for agents项目地址https://gitcode.com/gh_mirrors/ba/baml点击查看免费下载本文聚焦 BAML 引擎中的jsonish库位于 engine/baml-lib/jsonish围绕其原始文档 README.md 展开它对外暴露的from_str接口保证只要输入里包含符合 schema 的内容就能被灵活地解析出来。读完本文你可以掌握 jsonish 的两段式解析管线JSONish 词法/结构解析 类型强制转换、解析候选与补全状态的数据模型、柔性决策如何被标记与打分以及 BAML 运行时fetch_as、prompt renderer是如何消费其结果的。一、核心接口与设计承诺jsonish 是 BAML 引擎里的一个 Rust cratecrate 名jsonish见 Cargo.toml它解决的核心问题是大模型或其他上游给出的原始字符串往往不是干净的 JSON但业务侧的 schema类型是明确的。原始文档 README.md 将其承诺概括为一个函数pub fn from_str( of: OutputFormatContent, target: FieldType, raw_string: str, allow_partials: bool, ) - ResultBamlValueWithFlags它提供的保证是schema 能从输入中被柔性地解析出来It provides a guarantee that the schema is able to be flexibly parsed out from the input典型场景包括在带前缀/后缀文本里找出目标对象如模型回复The answer is true目标类型是bool按字段名别名alias解析字段把值强制转换cast到正确的目标类型在必要时把单个值包装成数组遵守约束constraints。需要注意源码与文档的演进差异当前 src/lib.rs 中的实际签名为pub fn from_str( of: OutputFormatContent, target: TypeIR, raw_string: str, raw_string_is_done: bool, ) - ResultBamlValueWithFlags即FieldType已由更通用的TypeIR取代而allow_partials语义被反转为raw_string_is_done表示输入是否已完整收到。从调用点 prompt_renderer/mod.rs 可以印证这一对应关系jsonish::from_str(def, target, raw_string, !allow_partials)。第四个参数之所以存在是因为解析器必须支持流式场景——LLM 输出尚未结束时比如只收到[1, 2也要能返回一个部分完成的结构而不是直接报错。返回值BamlValueWithFlags返回类型BamlValueWithFlags定义在 deserializer/types.rs是一棵带解析账本的值树每个节点记录实际解析出的值String/Int/Float/Bool/List/Map/Enum/Class/Null/Media目标类型TypeIRDeserializerConditions即一组柔性转换时打上的Flag详见第四节。它提供了到运行时值体系的多种From转换转BamlValue、转BamlValueWithMeta...并实现score()方法对整棵树打分——分数越小说明解析过程越少动用技巧结果越可信。二、两段式管线JSONish 解析 类型强制转换from_str的实现只有两步见 src/lib.rs第一步jsonish::parse(raw_string, ParseOptions::default(), raw_string_is_done)——把原始字符串解析成一棵与具体 schema 无关的Value树第二步target.coerce(ctx, target, Some(value))——以目标类型TypeIR为驱动把Value树强制转换成BamlValueWithFlags。若目标类型本身就是纯字符串会直接短路返回原始字符串lib.rs因为schema 是 string 时就不该再解析。另外若第二步中出现了Flag::InferedObject(String(...))这类无法接受的标记会显式报错失败——灵活性有边界。2.1 解析入口的四级回退策略第一步的核心是 parser/entry.rs 中的parse_func它按顺序尝试多级策略前一级失败才进入下一级严格 JSON 解析先serde_json::from_str。成功即返回并依据值类型设定补全状态——比如裸数字1会被标记为Incomplete因为模型可能还会继续输出12而带引号的字符串、对象、数组一旦解析成功必然是完整的Markdown 解析allow_markdown_jsonmarkdown_parser.rs 提取 json 代码块若文本里出现多个 JSON 对象会构造一个候选集AnyOf候选包括每个对象单独所有对象作为列表按字符串解析等多种读法全文扫描 JSON 对象all_finding_all_json_objectsmulti_json_parser.rs 在前后缀任意文本中grep 出所有 JSON 片段结果打上Fixes::GreppedForJSON标记修复式解析allow_fixesfixing_parser.rs 处理残缺 JSON缺失括号、未闭合引号等对应Fixes枚举里的修复记录兜底为字符串allow_as_string以上全失败时把整段文本当作字符串候选返回交由第二步的强制转换层去做从文本里抠出 bool/数字之类的工作。每级之间的切换由 parser/mod.rs 的ParseOptions控制默认全开pub struct ParseOptions { all_finding_all_json_objects: bool, // 默认 true allow_markdown_json: bool, // 默认 true allow_fixes: bool, // 默认 true allow_as_string: bool, // 默认 true depth: usize, // 递归深度保护 }next_from_mode会根据当前所处解析模式逐级收窄选项避免重复尝试同一层策略同时parse_func对递归深度设了上限超过 100 层即报 Depth limit reached防止病态输入引发深递归。2.2 Value 树候选集与补全状态解析产物是 jsonish/value.rs 中的Value枚举。除了常规 JSON 值String/Number/Boolean/Null/Object/Array还有三个对柔性至关重要的变体Markdown(String, BoxValue, CompletionState)从 Markdown 代码块里挖出的 JSON附带原文标记FixedJson(BoxValue, VecFixes)经过修复/扫描才得到的 JSONFixes枚举记录了GreppedForJSON全文扫描所得、InferredArray推断出的数组包装等修复动作AnyOf(VecValue, String)多个解析候选。同一段文本可能有多种合理解读对象、列表、字符串jsonish 不急着裁决而是把候选集原样传给第二步让目标类型来选最优。每个带状态的值都携带CompletionStateComplete/Incomplete支撑流式解析completion_state()会自底向上聚合任一候选未完成则整体未完成complete_deeply()可在输出结束后把整棵树标记为完成。顶层裸数字会被刻意标为 Incomplete见 entry.rs因为数字可能被后续 token 延长。三、强制转换层五个柔性场景的落地第二步的实现在 deserializer/coercer/ 目录下按类型分文件组织coerce_alias.rs字段名别名匹配对应文档中 Parsing in field names with aliasescoerce_class.rs / coerce_enum.rs类与枚举的字段/取值匹配coerce_primitive.rs基础类型转换Casting to the right typecoerce_array.rs数组解析与单值包装为数组Wrapping around arrays when necessarycoerce_map.rs / coerce_union.rsmap 与联合类型含打分选择最优分支match_string.rs从自由文本中匹配目标取值。约束Obeying constraints则由 deserialize_flags.rs 中的Flag::ConstraintResults(Vec(String, JinjaExpression, bool))承载——每个约束的求值结果名称、Jinja 表达式、是否通过被记录在解析账本里最终通过constraint_results()汇总给运行时。测试用例直观展示了这些柔性行为摘自 tests/test_basics.rs// 千分位数字直接解析 test_deserializer!(test_number_2, EMPTY_FILE, 12,111, TypeIR::int(), 12111); // 大小写不敏感的布尔 test_deserializer!(test_bool_2, EMPTY_FILE, True, TypeIR::bool(), true); // 前缀文本里抠出 bool并自动包成列表 test_deserializer!( test_bool_wrapped, EMPTY_FILE, The answer is true, TypeIR::bool().as_list(), [true] ); // 带加粗 Markdown 的结论 test_deserializer!( test_bool_wrapped_mismatched_case_preceded_by_text, EMPTY_FILE, The tax return you provided has section for dependents.\n\nAnswer: **True**, TypeIR::bool(), true ); // 歧义输入则明确失败而不是猜 test_failing_deserializer!( test_ambiguous_bool, EMPTY_FILE, The answer is true or false, TypeIR::bool() );这个测试目录还覆盖 别名、类、约束、联合类型、部分值/流式 等主题Cargo.toml 中用 criterion 配置了 字面量、类、列表、联合类型、部分值 等基准说明该库在性能与行为边界上都有系统性验证。四、Flags 与 Score让每一次开恩都留痕柔性解析最危险的副作用是静默地歪曲数据。jsonish 的对策是决策留痕每动用一次技巧就向DeserializerConditions打一个Flag。Flag 枚举 相当详尽能回答这个值是怎么来的例如ObjectFromMarkdown/ObjectFromFixedJson(VecFixes)对象来自 Markdown 代码块或修复后的 JSONImpliedKey(String)/ExtraKey(String, Value)字段名被推断出来或出现了 schema 之外的多余键SubstringMatch/StrippedNonAlphaNumeric值是从文本子串匹配、或剥掉非字母数字字符后得到的SingleToArray单值被包装成了数组StringToBool/StringToFloat/FloatToInt等类型强转记录FirstMatch/UnionMatch联合类型在多个候选中选了第一个/某个分支并保留全部候选的成败结果Incomplete/Pending流式场景下的完成状态标记。基于这层账本库提供两类可观测能力打分score.rs 中的WithScore让每个Flag有代价BamlValueWithFlags::score()递归求和见 types.rs。打分机制使联合类型等场景能从多个候选分支中择优也让上层能判断这个结果干净还是勉强可解释错误explanation_json()types.rs、lib.rs把失败原因按root.field.parsed:0这样的作用域路径组织成 UI 可渲染的 JSONParsingErrorToUiJson供编辑器/Studio 等前端直接展示。五、运行时消费从解析结果到 BAML 值jsonish 的产出最终服务于 BAML 运行时。在 async_vm_runtime.rs 中表达式函数的fetch_as拿到 HTTP 响应体后直接调用jsonish::from_str(output_format, parse_as_type, body, true)把任意响应体按目标类型解析成BamlValueWithFlags再包成ResponseBamlValueprompt_renderer 同理用!allow_partials传入完成标志。ResponseBamlValuelib.rs是面向流式的最终封装元数据ResponseValueMeta携带(flags, checks, completion, TypeIR)。其Serialize实现区分SerializeMode::Final与Partial两种模式——流式输出未完成时序列化会附带state流状态字段且类字段按该字段是否已 required 完成单独决定用 Final 还是 Partial 序列化lib.rs从而让 SDK 用户能在 token 到达的瞬间看到部分但有效的结构化响应。六、工程要点小结两段式架构先得到 schema 无关的Value候选树再由目标类型coerce收敛是 jsonish 能同时服务 LLM 输出、HTTP 响应、prompt 渲染结果等多种来源的原因候选集而非裁决AnyOf把多种合理解读延迟到类型已知时才选择配合Fixes/Flag保证选择过程可追溯、可打分流式一等公民CompletionState与raw_string_is_done参数贯穿解析、转换、序列化三层残缺输入返回部分值而不是抛错跨平台Cargo.toml 为 wasm32 目标单独引入了uuid/getrandom的 js feature说明该库也能编译进 WebAssembly供浏览器端语言客户端使用。如需继续深入建议按 src/lib.rs → parser/entry.rs → coercer/ → tests/ 的顺序阅读源码测试目录基本就是每个柔性能力的活文档。赞分享编程语言AI Agent编译器CLI人工智能【免费下载链接】bamlThe programming language for agents项目地址https://gitcode.com/gh_mirrors/ba/baml点击查看免费下载相关推荐agno 文本抽取Text Extraction用 Pydantic Schema 把自由文本变成结构化数据agno 文本抽取Text Extraction用 Pydantic Schema 把自由文本变成结构化数据 导读 cookbook/data_label人工智能大模型AI AgentAgent 框架多智能体工具调用RAGAgent 工作流Agent 记忆Tokenization 与数据形状全解析train-llm-from-scratch 如何把文本变成可训练张量Tokenization 与数据形状全解析train llm from scratch 如何把文本变成可训练张量 本篇技术指南以仓库文档 docs/found人工智能大模型深度学习预训练微调强化学习rrweb 序列化机制解析如何把 DOM 转成可传输、可回放的数据结构rrweb 序列化机制解析如何把 DOM 转成可传输、可回放的数据结构 导读 本文聚焦 rrweb 序列化Serialization模块的设计与实现为什前端可观测性开发工具上一篇消息队列中的UUID终极指南在Kafka和RabbitMQ中实现全局唯一标识符下一篇OpenCode开源AI编程助手如何让你的开发效率提升3倍创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考