Golang 中强制 LLM 返回 JSON 的无感解法 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
IndexOutOfBounds
V2EX    分享创造

Golang 中强制 LLM 返回 JSON 的无感解法

  •  
  • /a>
      IndexOutOfBounds 256 天前 2990 次点击
    这是一个创建于 256 天前的主题,其中的信息可能已经有所发展或是发生改变。

    https://github.com/glidea/llm-structed

    背景

    在 chat 场景中,通常模型不需要返回结构化的数据。但在 LLM 应用开发里,模型通常被视为提供某种原子能力的 API Service ,此时我们希望直接得到一个 JSON ,通常的解法有:

    1. 直接在 Prompt 里强调输出格式

    • 优:简单,对模型 API 没有任何额外要求
    • 缺:格式不稳定,特别是对于能力较差的模型

    2. 使用 response_format: { type: "json_object" } + Prompt 说明具体字段

    • 优:总是确保返回合法 JSON
    • 缺:字段不稳定,特别是对于能力较差的模型

    3. 使用 response_format: { type: "json_schema", json_schema: {"strict": true, "schema": ...} }

    • 优:确保返回合法 JSON ,且字段稳定
    • 缺:仅部分模型支持

    SDK

    • 在 OpenAI 提供的 SDK 中直接支持 Class 作为 Response Format ,但仅支持 Python
    • go-openai 中,使用方式过于通用繁琐
    • llm-structed 专门针对结构化场景优化,对方案 3 和方案 2 提供原生支持

    例子

    package main import ( "context" "fmt" "github.com/glidea/llm-structed" ) type Summary struct { Title string `json:"title" desc:"The title of the summary"` Content string `json:"content" desc:"A concise summary of the article content"` Keywords []string `json:"keywords" desc:"Key topics mentioned in the article"` Score int `json:"score" desc:"The quality score of the article (1-10)"` Category string `json:"category" desc:"The category of the article" enum:"Technology,Science,Business,Health,Education,Other"` } func main() { // New client (In minimal configuration, you only need to set the APIKey) cli, _ := llmstructed.New(llmstructed.Config{ BaseURL: "https://openrouter.ai/api/v1", APIKey: "sk-...", Model: "google/gemini-flash-1.5", Temperature: 0.3, StructuredOutputSupported: true, // 使用方案 3 Retry: 1, Debug: true, // See source code comments of llmstructed.Config for these config detail }) ctx := context.Background() // Structured Outputed var summary Summary _ = cli.Do(ctx, []string{`Please generate a summary of this article: Artificial Intelligence (AI) is transforming the way we live and work. It refers to computer systems that can perform tasks that normally require human intelligence. These tasks include visual perception, speech recognition, decision-making, and language translation. Machine learning, a subset of AI, enables systems to learn and improve from experience without being explicitly programmed. Deep learning, particularly, has revolutionized AI by using neural networks to process complex patterns in data.`, }, &summary) fmt.Printf("Go Struct: %v\n\n", summary) // Simple method for single value str, _ := cli.String(ctx, []string{"Hello, who are you?"}) fmt.Printf("String: %s\n\n", str) languages, _ := cli.StringSlice(ctx, []string{"List some popular programming languages."}) fmt.Printf("String Slice: %v\n\n", languages) count, _ := cli.Int(ctx, []string{`How many words are in this sentence: "Hello world, this is a test."`}) fmt.Printf("Integer: %d\n\n", count) yes, _ := cli.Bool(ctx, []string{"Are you happy?"}) fmt.Printf("Boolean: %v\n\n", yes) trues, _ := cli.BoolSlice(ctx, []string{"Are these statements true? [\"The sky is blue\", \"Fish can fly\", \"Water is wet\"]"}) fmt.Printf("Boolean Slice: %v\n\n", trues) pi, _ := cli.Float(ctx, []string{"What is the value of pi (to two decimal places)?"}) fmt.Printf("Float: %.2f\n\n", pi) } 
    第 1 条附言    255 天前
    收藏比 star 数多系列,有需要帮忙点点 star
    https://github.com/glidea/llm-structed

    下个饼是直接使用现有第三方 sdk 作为 tool ,尽量减少类似 langchain 等其它框架的额外适配
    14 条回复    2025-02-10 14:05:39 +08:00
    abc634
        1
    abc634  
       255 天前   1
    我的一个小经验:
    让 api 返回 markdown 或者 html 或者 xml ,都比 json 好。
    然后再解析 xml 就简单了。
    IndexOutOfBounds
        2
    IndexOutOfBounds  
    OP
       255 天前
    @abc634 我理解场景有些区别,markdown 是半结构化的,主要用于直接展示,比如直接让 AI 用 markdown 写篇文章,这是很好的选择

    json 是开发内部使用的,比如你需要提供接口给前端做二次展示

    另外通过 json schema 可以做到很强的约束,比如文中给文章分类的例子,通过 enum:"Technology,Science,Business,Health,Education,Other" 强限制分类范围
    otakustay
        4
    otakustay  
       255 天前   1
    用 function calling 呢?你需要的模型里有不支持 function 的吗
    还有个办法是 Assistant Prefill ,不过我估计也不稳
    otakustay
        5
    otakustay  
       255 天前
    @abc634 XML 层次深了也不行,我还特地为这个调过专门的方案
    reeco
        6
    reeco  
       255 天前
    还是 1 的方案吧,2 ,3 的方案你换个非 openai 的模型照样不能用
    ychost
        7
    ychost  
       255 天前
    如果输出的 JSON 没有嵌套,就没必要让它输出 JSON 了,直接输出 kv 对,然后代码解析这样容错性更好
    IndexOutOfBounds
        8
    IndexOutOfBounds  
    OP
       255 天前
    @otakustay 暂时不支持 function call ,不过确实有这个想法,自动注入结构体 Method
    IndexOutOfBounds
        9
    IndexOutOfBounds  
    OP
       255 天前
    @reeco 非 openai ,比如 gemini ,deepseek 也支持 json 输出的,算是一个通用的规范了

    https://openrouter.ai/models?fmt=cards&order=newest&supported_parameters=structured_outputs
    otakustay
        10
    otakustay  
       255 天前
    要不这样,用写代码的思路,把你要的 schema 变成 TS interface ,然后下面写个 function 定义接收这个类型的参数,再一段注释说明你的需求,最后是 functionName({起头,让模型给你补……
    74123gzy
        11
    74123gzy  
       255 天前
    @otakustay #10 那不还是 1. 直接在 Prompt 里强调输出格式
    lovestudykid
        12
    lovestudykid  
       255 天前
    还有个办法是给 json 开个头
    neptuno
        13
    neptuno  
       254 天前
    试了很多,function calling 是最优解
    abc634
        14
    abc634  
       251 天前
    @otakustay 谢谢,
    我之前还没有碰到 这种情况,我碰到的是上下文太长超出窗口,
    (多页内容)
    然后我就把前一页的 xml 内容和新的页面 丢给 gpt 让他自己补全,效果还可以。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4367 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 100ms UTC 01:03 PVG 09:03 LAX 18:03 JFK 21:03
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86