拓冰建站拓冰建站
首页 / 资讯中心 / 正文

LLM结构化输出实战:Function Calling、模板正则与Pydantic方案解析

1. 项目概述当LLM的JSON输出“失控”时如果你最近在折腾大语言模型LLM的应用开发尤其是需要它返回结构化的数据比如一个用户信息对象、一个商品列表或者一个复杂的决策树那你大概率踩过这个坑你满怀期待地向模型发送了一个精心设计的提示词要求它返回一个完美的JSON结果收到的却是一堆夹杂着解释、换行、甚至语法错误的“混合体”。更让人头疼的是模型有时会“自作主张”地添加一些json代码块标记或者干脆返回一段自然语言描述告诉你“好的这是一个JSON”然后后面跟着的并不是有效的JSON。这种“返回的JSON又炸了”的情况几乎成了LLM应用开发中的日常。这背后的核心痛点在于LLM在本质上是一个文本生成模型它并不“理解”JSON语法它只是在模仿它见过的文本模式。当你要求它输出JSON时它是在基于训练数据中的模式进行概率生成。这就导致了输出的不稳定性和不可靠性。对于需要将LLM输出集成到下游自动化流程如数据库写入、API调用、业务逻辑判断的应用来说这种不稳定性是致命的。一个解析失败的JSON会导致整个流程中断。因此“Structured Output”结构化输出技术应运而生并迅速成为LLM工程化中的关键一环。它不再是简单的提示词工程而是一套旨在强制或引导模型输出严格符合预定格式如JSON、YAML、Pydantic模型的技术方案。本次实测我将聚焦三种主流的、具有代表性的Structured Output实现方案它们分别代表了不同的技术路径和权衡。我会通过实际的代码示例展示如何从“祈祷模型输出正确”过渡到“确保模型输出可用”并分享在集成过程中那些文档里不会写的“坑”和实战心得。2. 三种结构化输出方案核心思路与选型在深入代码之前我们必须理解这三种方案各自的设计哲学和适用场景。没有“银弹”选择哪种方案取决于你的技术栈、对延迟/成本的容忍度以及对输出确定性的要求等级。2.1 方案一基于Function Calling / Tool Calling的“迂回”策略这是最早普及的方案由OpenAI的Function Calling功能带火。其核心思路是我们不直接要求模型输出JSON而是定义一系列“工具”Tools或“函数”Functions每个工具都有自己的名称、描述和参数模式一个JSON Schema。我们让模型根据对话内容决定是否要调用某个工具以及调用时传入什么参数。然后由我们的程序来实际执行这个“调用”模型的输出就是这个结构化的调用请求。为什么选择它原生支持与高可靠性对于OpenAI、Anthropic等主流厂商的API这是它们官方推荐和支持的方式。模型在训练时就被专门优化过以理解和生成这种格式因此输出的格式合规性几乎是100%。意图识别与路由它不仅仅输出结构化数据还包含了“意图”调用哪个函数。这对于构建Agent或多步骤工作流非常有用模型可以自主决定下一步该做什么。广泛的框架集成LangChain、LlamaIndex等主流框架都对其有深度封装开箱即用。需要避免的问题它本质上是一种“间接”的结构化输出。你得到的是一个“调用指令”而不是最终的数据对象。如果你的需求仅仅是获取一个固定格式的数据那么定义“工具”可能会显得有些冗余和抽象。此外不同厂商的Tool Calling格式细节可能有微小差异。2.2 方案二基于输出模板Output Template与正则的后处理这是一种更直接、更“朴素”但也更灵活的方法。其核心思路是在提示词中通过非常详细和强约束的文本描述为模型划定一个“输出框架”。例如使用XML标签、Markdown代码块、或者特定的分隔符来明确指示JSON的起始和结束。然后在收到模型回复后使用正则表达式Regex或简单的字符串查找方法从这个回复文本中提取出目标JSON字符串。为什么选择它模型无关性这是最大的优势。它不依赖于任何特定的API或模型供应商。无论是GPT-4、Claude、本地部署的Llama还是一个开源模型只要它能理解你的文本指令理论上就可以使用这种方法。极致灵活性你可以定义任何你想要的输出格式不限于JSON也可以是CSV、自定义的文本段落等。提示词模板完全由你控制。成本与延迟通常只需要一次API调用没有额外的开销。需要避免的问题可靠性是最大挑战。模型可能会不遵守你设定的模板格式比如漏掉一个闭合标签或者在JSON中间插入一句评论。后处理的正则表达式必须写得非常健壮以应对各种边界情况。这增加了代码的复杂性和维护成本。提示词的设计也需要技巧过于复杂的模板可能会影响模型的内容生成质量。2.3 方案三基于Pydantic与专用客户端库的“声明式”方案这是目前我认为在易用性和可靠性上平衡得最好的新一代方案以PydanticAI、Instructor、Marvin等库为代表。其核心思路是利用Python的类型提示Type Hints和Pydantic数据验证库你只需要像定义普通的数据类Data Class一样用Python代码声明你期望的输出结构。然后专用的客户端库会“魔法般”地将这个Pydantic模型转换成针对特定模型优化过的提示词和解析逻辑并处理与模型的通信。为什么选择它开发者体验极佳代码就是文档。你通过Python类定义结构清晰、直观且享受IDE的自动补全和类型检查。这大大降低了认知负担。强类型与自动验证Pydantic会在解析后自动进行数据验证和类型转换。如果模型返回的age是字符串30Pydantic会帮你转换成整数30。如果字段缺失或类型不匹配会抛出清晰的验证错误而不是一个晦涩的JSON解析异常。抽象与封装它封装了提示词工程、格式解析、重试逻辑等脏活累活让你专注于定义“你想要什么”而不是“如何让模型给出你想要的”。逐渐成为社区标准这种方式越来越流行很多新的AI应用框架都将其作为首选的结构化输出方式。需要避免的问题它通常依赖于特定的客户端库将你和该库的生态绑定。虽然PydanticAI等库支持多个模型后端但如果你使用的模型不在其支持列表内就需要回退到其他方案。此外这种“魔法”有时会隐藏底层细节当出现问题时调试可能比直接写提示词和后处理更复杂一些。3. 核心细节解析与实操要点3.1 Function Calling 的细节与“暗坑”虽然Function Calling很强大但魔鬼藏在细节里。以下是一些关键点和容易出错的地方参数模式JSON Schema的定义这是核心。你必须为每个参数定义清晰的type,description。description至关重要它是模型理解这个参数含义的主要依据。一个模糊的描述会导致模型传入错误的值。# 一个不够好的例子 { name: get_weather, description: 获取天气, parameters: { type: object, properties: { location: {type: string} # 描述缺失 } } } # 一个更好的例子 { name: get_weather, description: 获取指定城市当前天气状况, parameters: { type: object, properties: { location: { type: string, description: 城市名称例如北京、上海、New York。必须是一个明确的行政区划名称。 }, unit: { type: string, enum: [celsius, fahrenheit], description: 温度单位默认为摄氏度celsius。, default: celsius } }, required: [location] } }注意enum和default字段非常有用。enum将模型的输出限制在有限选项内极大提高了确定性。default可以处理可选参数避免模型在不需要时强行生成一个值。处理“不调用工具”的情况模型可能认为用户的问题不需要调用任何工具此时它会返回一个普通的聊天回复。你的代码必须能处理这两种分支。response client.chat.completions.create( modelgpt-4, messagesmessages, toolstools_list, # 你定义的工具列表 tool_choiceauto, # 可以是“auto”、“none”或指定某个工具 ) message response.choices[0].message if message.tool_calls: # 处理工具调用 for tool_call in message.tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) # ... 执行对应函数 else: # 处理普通聊天回复 print(message.content)“幻觉”参数即使你定义了严格的Schema模型偶尔仍会生成一个不存在的参数名或者给一个enum类型参数传入不在列表中的值。因此在将参数传递给实际函数前必须进行验证和清洗。可以使用Pydantic来二次验证function_args。多工具调用与并行较新的模型如GPT-4 Turbo支持在一个回复中并行调用多个工具。你的代码需要能遍历message.tool_calls这个数组。同时考虑这些工具调用是否具有依赖关系是否需要串行执行。3.2 输出模板与正则提取的实战技巧这个方案的成功90%取决于提示词模板的设计10%取决于后处理的鲁棒性。模板设计模式XML标签模式user_profilenameJohn/nameage30/age/user_profile。标签的闭合性对模型来说相对容易学习。Markdown代码块模式json { ... } 。明确告诉模型输出一个JSON代码块。严格键值对模式在提示词中给出近乎代码的示例。请严格按照以下格式输出 name: [字符串] age: [整数] hobbies: [字符串数组] 输出开始实操心得在System Prompt中强化指令不要只在用户消息里写格式要求。在系统指令中明确模型“角色”和“输出纪律”效果更好。例如“你是一个严格的数据格式化助手必须只输出JSON不添加任何解释性文字。”提供少数示例Few-Shot在对话历史messages中提供1-2个严格按照格式输入输出的例子这是引导模型行为最有效的方式之一。使用“分隔符”和“开始/结束”标记用、---或明确的输出开始、输出结束来框定输出范围便于正则定位。后处理正则表达式 正则表达式要兼顾精确性和容错性。import re import json def extract_json_from_response(response_text: str) - dict: 尝试从模型回复中提取JSON。 处理多种情况纯JSON、代码块中的JSON、被文本包裹的JSON。 # 情况1尝试匹配 markdown json 代码块 pattern_code_block r(?:json)?\s*([\s\S]*?)\s* match re.search(pattern_code_block, response_text) if match: json_str match.group(1) else: # 情况2尝试匹配可能被包裹在文本中的JSON对象最外层是花括号 # 这个正则比较宽松用于捕获第一个出现的 {...} pattern_json_obj r\{[\s\S]*\} match re.search(pattern_json_obj, response_text) if match: json_str match.group(0) else: raise ValueError(无法从回复中提取出有效的JSON结构) try: return json.loads(json_str) except json.JSONDecodeError as e: # 如果解析失败可以尝试一些简单的修复比如去除首尾空白、平衡引号简单情况 # 或者记录日志并抛出异常 print(fJSON解析失败原始字符串{json_str[:200]}...) raise e重要提示正则表达式无法解决所有问题。对于极其复杂的错误如嵌套引号错误、数组缺失逗号可能需要更复杂的解析器或直接请求模型重试。一个常见的策略是如果后处理失败则将错误信息和原始回复重新发送给模型要求它纠正并重新输出。这构成了一个简单的自我修正循环。3.3 PydanticAI 方案的精妙之处以PydanticAI为例它代表了这类库的先进设计。你不仅仅是在定义输出而是在定义一个“智能体”Agent或“模型”的行为。核心概念Model 和 Result你创建一个继承自pydantic_ai.BaseModel的类这个类既定义了对话的“系统提示”system_prompt也通过Pydantic字段定义了输出的数据结构。运行后你得到的是一个Result对象它包含了已解析并验证过的数据.data以及原始的响应消息等信息。from pydantic_ai import Agent from pydantic import BaseModel, Field import openai # 1. 定义你的输出数据结构 class UserProfile(BaseModel): name: str Field(description用户的完整姓名) age: int Field(ge0, le120, description用户年龄) email: str Field(patternr^[a-zA-Z0-9._%-][a-zA-Z0-9.-]\.[a-zA-Z]{2,}$) interests: list[str] Field(default_factorylist, description用户的兴趣列表) # 2. 创建智能体绑定模型和数据结构 agent Agent( modelopenai:gpt-4-turbo, result_typeUserProfile, # 关键指定输出类型 system_prompt你是一个信息提取助手请从文本中提取结构化信息。, deps{openai_client: openai.Client()} # 传入依赖 ) # 3. 运行并直接获取结构化结果 async def main(): result await agent.run( 我叫张三今年28岁了邮箱是zhangsanexample.com喜欢编程和读书。 ) # result.data 已经是 UserProfile 实例 profile: UserProfile result.data print(profile.name) # 输出张三 print(profile.age) # 输出28 (整数) print(profile.interests) # 输出[编程, 读书] # 邮箱格式已被Pydantic的pattern验证过踩坑记录依赖注入DepsPydanticAI通过deps参数管理外部依赖如数据库连接、API客户端。你需要正确设置它特别是在异步环境中。字段验证的严格性Pydantic的验证非常严格。如果模型返回的age是二十八即使人类能理解验证也会失败。因此在Field的description中必须给出极其明确的格式要求例如“请提供整数年龄”。错误处理当解析失败时PydanticAI会抛出ValidationError。你需要捕获这个异常并决定是重试、使用默认值还是向上游报告错误。好的做法是配置自动重试Retry逻辑。提示词的融合system_prompt和你的result_type字段描述会被库合并成最终的提示词发送给模型。你需要确保两者在语义上是一致的不会相互冲突。4. 实操过程与核心环节实现下面我将用一个贯穿始终的实例——“从一段自由文本中提取会议预约信息”——来分别展示三种方案的完整实现代码和关键步骤。我们的目标结构是一个MeetingInfo的Pydantic模型。# 目标数据结构定义 (我们将用它来评估所有方案) from pydantic import BaseModel, Field from datetime import time from typing import Optional class MeetingInfo(BaseModel): title: str Field(description会议主题) participants: list[str] Field(description参会人姓名列表) scheduled_date: str Field(patternr^\d{4}-\d{2}-\d{2}$, description会议日期格式 YYYY-MM-DD) start_time: str Field(patternr^\d{2}:\d{2}$, description开始时间格式 HH:MM24小时制) duration_minutes: int Field(ge5, le480, description会议时长单位分钟) location: Optional[str] Field(defaultNone, description会议地点如无则填None)4.1 方案一实现OpenAI Function Callingimport openai from openai.types.chat import ChatCompletionToolParam import json client openai.Client(api_keyyour-api-key) # 1. 根据目标结构定义Tool meeting_tool ChatCompletionToolParam( typefunction, function{ name: extract_meeting_info, description: 从文本中提取会议预约信息, parameters: { type: object, properties: { title: {type: string, description: 会议主题}, participants: {type: array, items: {type: string}, description: 参会人姓名列表}, scheduled_date: {type: string, description: 会议日期格式 YYYY-MM-DD}, start_time: {type: string, description: 开始时间格式 HH:MM24小时制}, duration_minutes: {type: integer, description: 会议时长单位分钟}, location: {type: string, description: 会议地点如无则填null} }, required: [title, participants, scheduled_date, start_time, duration_minutes] } } ) def extract_with_function_calling(text: str) - MeetingInfo: 使用Function Calling提取信息 response client.chat.completions.create( modelgpt-4-turbo, messages[ {role: system, content: 你是一个信息提取助手。请从用户提供的文本中提取会议信息。如果信息不明确请进行合理推断。}, {role: user, content: text} ], tools[meeting_tool], tool_choice{type: function, function: {name: extract_meeting_info}}, # 强制调用 ) message response.choices[0].message if not message.tool_calls: raise RuntimeError(模型未调用工具) tool_call message.tool_calls[0] if tool_call.function.name ! extract_meeting_info: raise RuntimeError(f调用了错误的工具: {tool_call.function.name}) # 2. 解析参数并验证 raw_args json.loads(tool_call.function.arguments) # 注意Tool Calling返回的JSON可能包含null需要转换为Python的None # 同时使用Pydantic模型进行强验证和类型转换 try: meeting_info MeetingInfo(**raw_args) return meeting_info except Exception as e: print(f参数验证失败: {e}, 原始参数: {raw_args}) raise # 测试 text 明天下午三点2024-05-20我们和小王、小李开一个项目评审会大概需要45分钟。地点在301会议室。 info extract_with_function_calling(text) print(info.json(indent2))关键步骤解析定义Tool将MeetingInfo的字段映射为JSON Schema。注意description要清晰required字段要准确。强制调用通过tool_choice参数强制模型调用我们指定的工具避免它返回普通聊天内容。二次验证这是至关重要的一步。不要直接信任模型返回的JSON。使用我们预先定义好的PydanticMeetingInfo模型对其进行验证和实例化。这能捕获类型错误、格式错误如日期格式不对和约束违反如时长超过8小时。4.2 方案二实现输出模板 正则提取import re import json from typing import Dict, Any def create_template_prompt(text: str) - str: 构建包含严格输出模板的提示词 system_prompt 你是一个严格的数据格式化助手。你的任务是从用户的输入中提取会议信息并严格按照给定的JSON格式输出。 输出要求 1. 只输出一个JSON对象不要有任何额外的解释、前缀、后缀或Markdown代码块标记。 2. JSON必须包含且仅包含以下字段title, participants, scheduled_date, start_time, duration_minutes, location。 3. scheduled_date格式必须为YYYY-MM-DD。 4. start_time格式必须为HH:MM24小时制。 5. participants是一个字符串数组。 6. location如果原文未提及请设置为null。 user_prompt f输入文本{text}\n\n请提取会议信息并输出符合上述要求的JSON return [ {role: system, content: system_prompt}, {role: user, content: user_prompt} ] def robust_json_extract(response_text: str) - Dict[str, Any]: 健壮的JSON提取函数尝试多种匹配策略 # 策略1尝试匹配最外层的 {...} json_pattern r\{[^{}]*\{.*\}[^{}]*\}|\{[^{}]*\} # 尝试匹配简单对象或包含嵌套的对象 match re.search(json_pattern, response_text, re.DOTALL) if not match: # 策略2尝试匹配被反引号包裹的内容 backtick_pattern r(?:json)?\s*(\{.*\})\s* match re.search(backtick_pattern, response_text, re.DOTALL) if not match: # 策略3尝试找到第一个{和最后一个}进行最宽松的匹配风险较高 start response_text.find({) end response_text.rfind(}) if start ! -1 and end ! -1 and end start: candidate response_text[start:end1] # 快速检查候选字符串是否大致像JSON if candidate.count({) candidate.count(}): match type(obj, (object,), {group: lambda x0: candidate})() if not match: raise ValueError(f无法从响应中定位JSON。响应内容{response_text[:500]}) json_str match.group(0) if hasattr(match, group) else match # 尝试解析 try: return json.loads(json_str) except json.JSONDecodeError as e: # 简单修复处理常见的多余逗号、未转义引号等仅适用于简单错误 # 对于复杂错误最好记录日志并重试或报错 print(f初次解析失败尝试简单清理。错误{e}) # 移除可能存在的首尾空白和换行 json_str_clean json_str.strip() # 这里可以添加更多启发式清理规则... try: return json.loads(json_str_clean) except json.JSONDecodeError: raise ValueError(f无法修复和解析JSON字符串{json_str_clean[:200]}...) def extract_with_template(text: str, client) - MeetingInfo: 使用模板和正则提取信息 messages create_template_prompt(text) response client.chat.completions.create( modelgpt-4-turbo, # 也可用更便宜的模型如gpt-3.5-turbo messagesmessages, temperature0.1, # 降低随机性使输出更稳定 ) response_text response.choices[0].message.content # 提取并解析JSON raw_data robust_json_extract(response_text) # 处理可能的null字符串将其转换为Python None for key in raw_data: if raw_data[key] null or raw_data[key] is None: raw_data[key] None # 使用Pydantic模型验证和转换 try: return MeetingInfo(**raw_data) except Exception as e: print(f数据验证失败。原始数据{raw_data} 错误{e}) # 可选在此处实现重试逻辑将错误信息反馈给模型 raise # 测试 info2 extract_with_template(text, client) print(info2.json(indent2))关键步骤解析提示词工程system_prompt的指令必须极其强硬和具体。我们明确列出了“只输出JSON”、“必须包含哪些字段”、“格式必须如何”等要求。temperature设置为较低值如0.1以减少输出的随机性。多层正则策略robust_json_extract函数展示了从易到难的多层匹配策略。优先尝试精确匹配失败后再尝试更宽松的匹配。这比单一的正则表达式更健壮。后处理与验证即使成功提取出字符串也要进行数据清洗如处理null字符串和最终的Pydantic验证。这构成了最后一道防线。4.3 方案三实现PydanticAI “声明式”提取from pydantic_ai import Agent from pydantic_ai.models.openai import OpenAIModel import asyncio # 0. 已经定义了 MeetingInfo (Pydantic Model) # 1. 创建Agent直接绑定结果类型 model OpenAIModel(gpt-4-turbo, openai_clientclient) agent Agent( modelmodel, result_typeMeetingInfo, # 核心声明输出类型 system_prompt你是一个精准的信息提取助手。请从用户的文本中提取会议信息。如果某些信息如地点未明确提及请将其留空null。请确保日期和时间格式正确。, deps{}, # 本例不需要额外依赖 ) async def extract_with_pydanticai(text: str) - MeetingInfo: 使用PydanticAI提取信息 result await agent.run(text) # result.data 已经是 MeetingInfo 实例且通过了Pydantic验证 return result.data # 异步测试 async def main_test(): info3 await extract_with_pydanticai(text) print(info3.json(indent2)) # 运行 asyncio.run(main_test())关键步骤解析极简的API整个核心逻辑就两步定义模型MeetingInfo和创建Agent。Agent的result_type参数将一切绑定在一起。自动化的魔法PydanticAI在背后做了大量工作它将MeetingInfo的字段定义和描述自动转换成适合模型的提示词指令。它可能使用了类似Function Calling的底层机制取决于后端模型也可能使用了精调的提示词。它自动处理了模型的响应尝试解析成JSON并最终通过Pydantic进行验证和实例化。错误处理内置如果解析或验证失败agent.run()会抛出异常如ValidationError。你可以在外层用try...except包裹实现重试或降级策略。5. 方案对比与选型建议为了更直观地对比我将三种方案的核心特性、优缺点和适用场景总结如下表特性维度方案一Function Calling方案二输出模板正则方案三PydanticAI输出确定性极高模型原生支持格式错误率极低。中到低依赖模型遵循指令和正则的健壮性。高库进行了优化并结合了Pydantic验证。开发复杂度中需定义JSON Schema处理工具调用逻辑。高需精心设计提示词和编写健壮的后处理正则。低声明式API代码最简洁。模型兼容性低仅限支持Tool Calling的模型OpenAI, Claude, 部分开源模型。极高任何能理解文本指令的模型都适用。中依赖库对模型后端的支持但支持列表在扩大。额外功能支持意图识别天然适合多工具/Agent场景。纯文本操控灵活性最高可输出任何格式。强类型验证自动类型转换与Python生态无缝集成。性能开销一次API调用无额外开销。一次API调用无额外开销。一次API调用库本身开销可忽略。维护成本中Schema需随业务变化更新。高提示词和正则都需要持续维护和测试。低只需更新Pydantic模型定义提示词由库管理。适用场景1. 使用主流商用API。2. 需要构建多步骤Agent。3. 对输出格式要求极其严格。1. 使用小众或自研模型。2. 输出格式非标准或经常变化。3. 追求最大程度的控制和灵活性。1. 快速原型开发和生产部署。2. 团队已熟悉Python类型提示和Pydantic。3. 追求代码清晰度和开发效率。个人选型建议如果你刚开始一个新项目且主要使用OpenAI/Anthropic等API优先考虑方案三PydanticAI。它的开发体验和可靠性是最好的。如果库不支持你的模型再考虑方案一。如果你需要构建复杂的、带有决策能力的AI Agent方案一Function Calling是更自然的选择因为它将“输出什么”和“做什么动作”结合在了一起。如果你在调试、研究或者使用的模型非常特殊从方案二模板正则开始。它能让你最直接地看到和操控你与模型的“对话”有助于理解问题所在。可以将其作为保底方案或调试工具。生产环境的建议无论选择哪种方案最终都必须用Pydantic这样的强类型模型对原始输出进行验证。这是保证数据质量、防止下游系统崩溃的最后一道也是最重要的一道防线。你可以将方案一或二的输出传入同一个MeetingInfo模型进行验证实现逻辑的统一。6. 常见问题与排查技巧实录在实际集成中你会遇到各种各样奇怪的问题。下面是我踩过的一些“坑”及解决方法。6.1 模型“不听话”总是添加额外文本问题即使使用了严格的System Prompt模型仍然在JSON前后加上“好的这是提取的信息”或“json”等文字。排查与解决检查System Prompt的权威性确保System Prompt是对话中的第一条消息并且指令足够强硬。尝试使用“你必须”、“只允许”、“禁止”等绝对性词语。降低Temperature将temperature参数设为0或一个很小的值如0.1这能显著减少模型的“创造性”使其输出更可控。使用Few-Shot示例在消息历史中提供1-2个完美的输入输出示例。这是最有效的引导方式之一。后处理正则的容错性如方案二所示你的提取代码必须能处理这些“包裹”文本。设计正则时优先匹配{...}或json ... 而不是匹配整个回复。考虑使用Chat Completion的response_format参数部分较新的OpenAI模型API支持response_format{ type: json_object }参数。这能强制模型输出合法的JSON是解决此问题的终极武器。务必在System Prompt中同时说明你期望的JSON结构。6.2 日期、数字等格式不一致问题模型有时返回2024/05/20有时返回May 20, 2024导致后续解析失败。排查与解决在描述中明确格式在Field的description或Tool的parameter description中使用正则表达式或非常具体的例子来规定格式。例如格式必须为YYYY-MM-DD例如2024-05-20。使用Pydantic验证器在Pydantic模型中可以为字段定义自定义验证器validator将各种常见格式统一转换为标准格式。from pydantic import validator class MeetingInfo(BaseModel): scheduled_date: str validator(scheduled_date) def validate_date_format(cls, v): # 尝试解析多种日期格式最后统一为YYYY-MM-DD from datetime import datetime for fmt in (%Y-%m-%d, %Y/%m/%d, %B %d, %Y): try: dt datetime.strptime(v, fmt) return dt.strftime(%Y-%m-%d) except ValueError: continue raise ValueError(日期格式无法识别)后处理清洗如果格式相对固定可以在正则提取后用一个简单的函数进行清洗和转换。6.3 数组字段处理不当问题participants字段模型可能返回一个字符串小王、小李而不是数组[小王, 小李]。排查与解决明确指定类型在描述中强调“列表”、“数组”、“用逗号分隔”等关键词。例如参会人姓名列表应是一个数组如[张三, 李四]。提供示例在Few-Shot示例中明确展示数组的格式。智能后处理如果拿到的是字符串可以编写一个简单的解析函数根据“和”、“、”、逗号等分隔符进行拆分。def parse_participants(raw: Any) - list[str]: if isinstance(raw, list): return raw if isinstance(raw, str): # 尝试按常见分隔符拆分 import re # 匹配中文顿号、逗号、空格、“和”字等 parts re.split(r[、,\s]|和, raw) return [p.strip() for p in parts if p.strip()] return [] # 或抛出异常6.4 如何处理信息缺失或模糊问题文本中未提及location模型是应该输出null、空字符串还是进行推断排查与解决在指令中明确在System Prompt或字段描述中明确规定。例如“如果原文未明确提及会议地点请将该字段设置为null不要猜测或推断。”利用Pydantic的Optional和Default将字段定义为Optional[str]并设置合理的默认值如None。这样即使模型返回null或该字段缺失Pydantic也能正确处理。不要依赖模型的推断对于关键业务信息如果原文缺失最好让模型明确输出一个如NOT_MENTIONED的占位符或者在后续流程中触发人工确认而不是让模型自由发挥导致错误。6.5 性能与重试策略问题API调用可能失败或者模型偶尔返回无法解析的格式。排查与解决实现指数退避重试对于网络错误或速率限制429错误使用带有指数退避的重试机制。许多HTTP客户端库如httpx,tenacity支持此功能。对解析失败进行重试如果因为格式问题导致解析失败可以设计一个重试循环。将原始回复和错误信息一起作为新的用户消息要求模型纠正。注意设置最大重试次数如3次避免无限循环。max_retries 3 for attempt in range(max_retries): try: result await agent.run(user_input) break # 成功则跳出循环 except ValidationError as e: if attempt max_retries - 1: # 将错误信息反馈给模型要求重试 user_input f上次解析失败错误信息{str(e)}。请根据原始文本重新生成正确的输出。原始文本{original_text} else: raise # 重试次数用尽抛出异常设置超时和回退为API调用设置合理的超时时间。如果主要方案如GPT-4持续失败可以考虑回退到更稳定但能力稍弱的模型如GPT-3.5-Turbo或者降级到基于规则的简单提取。最终结构化输出的稳定性是一个系统工程它结合了提示词工程、后处理逻辑、验证规则和健壮的错误处理。没有一劳永逸的方案但通过理解上述三种核心策略及其优劣你可以为你的应用场景选择最合适的工具并构建起一道坚固的防线让LLM的输出从“可能有用”变得“可靠可用”。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门