避免网站 404,羊坊店网站建设,国内做服装的网站有哪些方面,中国建设人才服务信息网证书查询在 LangChain解锁LLM大语言模型的结构化输出能力#xff1a;调用 with_structured_output() 方法 这篇博客中#xff0c;我们了解了格式化LLM输出内容的必要性以及如何通过调用langchain框架中提供的 with_structured_output() 方法对LLM输出进行格式化#xff08;三种可选方…在 LangChain解锁LLM大语言模型的结构化输出能力调用 with_structured_output() 方法 这篇博客中我们了解了格式化LLM输出内容的必要性以及如何通过调用langchain框架中提供的 with_structured_output() 方法对LLM输出进行格式化三种可选方式基于 TypedDict 类类型化字典、JSON SchemaJSON 模式和 Pydantic 类。在本篇博客中我们将进一步学习了解对模型输出进行结构化控制的其他方案分别是 少样本示例引导Few-shot prompting、结构化方法指定Specifying the method for structuring outputs和 直接解析模型输出Direct prompting and parsing。
少样本示例引导Few-shot prompting
当我们希望规范LLM输出格式比较复杂的时候模型的格式化输出可能不会如预期稳定这个时候我们可以采用一个非常简单高效的方法对模型输出质量的稳定性进行控制那就是 few-shot prompting少样本提示是一种在自然语言处理中通过借助少量示例来引导语言模型生成预期输出的技术。
代码实现如下
我们首先还是基于 Pydantic 的方式去定义LLM输出的格式然后调用 .with_structured_output() 方法创建 structured_llm 变量
from langchain_ollama import ChatOllama
from typing import Optional
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate# Pydantic
class Joke(BaseModel):Joke to tell user.setup: str Field(descriptionThe setup of the joke)punchline: str Field(descriptionThe punchline to the joke)rating: Optional[int] Field(defaultNone, descriptionHow funny the joke is, from 1 to 10)llm ChatOllama(model llama3.1:latest, temperature 0.8)
structured_llm llm.with_structured_output(Joke)接着我们使用 ChatPromptTemplate 从消息列表中创建一个提示词模板并且将几个符合输出格式的实例放入到 system prompt 中实现 few-shot prompt然后再通过 | 管道操作将 prompt 传递给结构化的语言模型进行处理以下代码中文注释由 DeepSeek-R1 生成
# 定义系统提示信息
# 告知语言模型它的角色是一个滑稽的喜剧演员专长是敲敲门笑话
# 并说明了笑话的结构即需要包含设置对 Whos there? 的回应和最终笑点对 设置 who? 的回应
# 还给出了几个关于不同主题的笑话示例让语言模型了解输出的格式
system You are a hilarious comedian. Your specialty is knock - knock jokes. \
Return a joke which has the setup (the response to Whos there?) and the final punchline (the response to setup who?).Here are some examples of jokes:example_user: Tell me a joke about planes
example_assistant: {{setup: Why dont planes ever get tired?, punchline: Because they have rest wings!, rating: 2}}example_user: Tell me another joke about planes
example_assistant: {{setup: Cargo, punchline: Cargo vroom vroom, but planes go zoom zoom!, rating: 10}}example_user: Now about caterpillars
example_assistant: {{setup: Caterpillar, punchline: Caterpillar really slow, but watch me turn into a butterfly and steal the show!, rating: 5}}# 使用 ChatPromptTemplate 从消息列表中创建一个提示模板
# 消息列表包含一个系统消息和一个用户消息
# 系统消息是上面定义的系统提示信息
# 用户消息使用占位符 {input}表示后续可以传入具体的用户输入例如用户想要的笑话主题
prompt ChatPromptTemplate.from_messages([(system, system), (human, {input})])# 将提示模板与结构化的语言模型进行组合
# 这里将 prompt 和 structured_llm 进行管道操作|
# 意味着先根据提示模板处理输入然后将处理后的结果传递给结构化的语言模型进行处理
few_shot_structured_llm prompt | structured_llm接着我们调用模型让LLM基于用户提问进行符合预期的结构化输出。
# 调用组合后的模型传入用户输入 whats something funny about woodpeckers
# 表示用户想要一个关于啄木鸟的有趣笑话
# 模型会根据系统提示中的要求和示例生成一个符合格式的敲敲门笑话
response few_shot_structured_llm.invoke(whats something funny about woodpeckers)
print(response)
setupWoodpecker punchlineTheyre always drumming up some laughter! rating8结构化方法指定Specifying the method for structuring outputs
其实还有一种调用 with_structured_output() 方法但只给 method 的参数传递变量的方式可以对LLM的输出进行结构化我们先来看一下代码实现代码中文注释由 Doubao-1.5-pro-32k 生成
# 从 langchain_ollama 库中导入 ChatOllama 类
from langchain_ollama import ChatOllama# 创建一个 ChatOllama 实例
# model 参数指定要使用的模型这里使用的是 llama3.1:latest 模型
# temperature 参数控制生成文本的随机性值越大越随机这里设置为 0.8
llm ChatOllama(modelllama3.1:latest, temperature0.8)# 为 llm 实例添加结构化输出功能
# 第一个参数传入 None表示不使用自定义的schema
# method 参数指定使用 json_mode 方法即输出为 JSON 格式
structured_llm llm.with_structured_output(None, methodjson_mode)# 调用结构化的语言模型
# 传入的提示信息是 Tell me a joke about cats, respond in JSON with setup and punchline keys
# 意思是让模型讲一个关于猫的笑话并以包含 setup笑话的铺垫和 punchline笑话的笑点键的 JSON 格式响应
structured_llm.invoke(Tell me a joke about cats, respond in JSON with setup and punchline keys
)从以上代码可以注意到虽然我们调用的依旧是 with_structured_output() 方法但第一个参数我们传递的是 None也就是我们并没有传入基于 TypedDict 类类型化字典、JSON SchemaJSON 模式和 Pydantic 类 这三种方式声明的schema而是通过制定 methodjson_mode加上在用户提问中特别说明的 “respond in JSON with setup and punchline keys”的方式实现对LLM输出内容的结构化。当然对于输出schema比较复杂的情况这种方式的处理效果有待考量和验证。
直接解析模型输出Direct prompting and parsing
需要特别注意的是并非所有模型都支持 .with_structured_output() 方法因为并非所有模型都支持工具调用或 JSON 模式。那么对于这类模型我们可以直接提示模型使用特定的格式然后使用输出解析器从模型的原始输出中提取结构化的响应内容。以下结合代码介绍两种实现方法分别是使用langchain 框架内置的PydanticOutputParser 和自定义解析器。
使用 PydanticOutputParser
以下示例使用langchain内置的 PydanticOutputParser 来解析LLM的输出即通过提示词工程直接将定义模型输出的Pydantic 模式添加到system prompt中进行实现本质上就是提示词工程然后再对LLM的输出内容进行后置解析的思路进行实现。
实现代码如下中文注释由 DeepSeek-R1 生成
# 导入 List 类型提示用于类型注解表明变量将是一个列表
from typing import List# 从 langchain_core.output_parsers 模块导入 PydanticOutputParser 类
# 该类用于将文本输出解析为 Pydantic 模型实例
from langchain_core.output_parsers import PydanticOutputParser
# 从 langchain_core.prompts 模块导入 ChatPromptTemplate 类
# 该类用于创建聊天提示模板
from langchain_core.prompts import ChatPromptTemplate
# 从 pydantic 模块导入 BaseModel 和 Field 类
# BaseModel 是 Pydantic 模型的基类Field 用于定义模型字段的元数据
from pydantic import BaseModel, Field# 定义一个名为 Person 的 Pydantic 模型类
# 该类用于表示一个人的信息
class Person(BaseModel):Information about a person.# 定义 name 字段为字符串类型# ... 表示该字段是必需的# description 为该字段提供描述信息name: str Field(..., descriptionThe name of the person)# 定义 height_in_meters 字段为浮点数类型# 表示人的身高单位为米# ... 表示该字段是必需的# description 为该字段提供描述信息height_in_meters: float Field(..., descriptionThe height of the person expressed in meters.)# 定义一个名为 People 的 Pydantic 模型类
# 该类用于表示文本中所有人物的识别信息
class People(BaseModel):Identifying information about all people in a text.# 定义 people 字段为 Person 类型的列表people: List[Person]# 创建一个 PydanticOutputParser 实例
# 将其 pydantic_object 属性设置为 People 类
# 用于将文本输出解析为 People 模型实例
parser PydanticOutputParser(pydantic_objectPeople)# 创建一个聊天提示模板实例
# 使用 from_messages 方法从消息列表中创建模板
prompt ChatPromptTemplate.from_messages([(# 系统消息提示回答用户查询# 并将输出用 json 标签包裹# {format_instructions} 是一个占位符将在后续填充解析指令system,Answer the user query. Wrap the output in json tags\n{format_instructions}),(# 用户消息{query} 是一个占位符将在后续填充用户的查询内容human,{query})]
).partial(format_instructionsparser.get_format_instructions())我们来看一下将 user query传入之后构成的完整prompt内容是什么样的。
query Anna is 23 years old and she is 6 feet tall
print(prompt.invoke({query: query}).to_string())prompt
System: Answer the user query. Wrap the output in json tags
The output should be formatted as a JSON instance that conforms to the JSON schema below.As an example, for the schema {properties: {foo: {title: Foo, description: a list of strings, type: array, items: {type: string}}}, required: [foo]}
the object {foo: [bar, baz]} is a well-formatted instance of the schema. The object {properties: {foo: [bar, baz]}} is not well-formatted.Here is the output schema:{$defs: {Person: {description: Information about a person., properties: {name: {description: The name of the person, title: Name, type: string}, height_in_meters: {description: The height of the person expressed in meters., title: Height In Meters, type: number}}, required: [name, height_in_meters], title: Person, type: object}}, description: Identifying information about all people in a text., properties: {people: {items: {$ref: #/$defs/Person}, title: People, type: array}}, required: [people]}Human: Anna is 23 years old and she is 6 feet tall最后我们基于 prompt, llm 和 parser 通过管道创建一个链式调用
# 以下代码注释由 Doubao-1.5-pro-32k 生成# 使用 LangChain 提供的管道操作符 | 来创建一个链式调用。
# 这个链式调用的作用是将多个组件按顺序连接起来形成一个处理流程。
# 具体来说这个链式调用包含三个组件prompt、llm 和 parser。
# 1. prompt这是之前创建的聊天提示模板实例。它的作用是根据用户输入的查询内容和解析指令生成合适的提示信息。
# 2. llm这是一个语言模型实例例如 OpenAI 的 GPT 系列模型等。它接收来自 prompt 生成的提示信息然后根据这个提示信息生成相应的文本输出。
# 3. parser这是之前创建的 PydanticOutputParser 实例。它的作用是将 llm 生成的文本输出解析为 People 模型实例方便后续对输出数据进行结构化处理。
# 最终chain 就是一个包含了提示生成、语言模型调用和输出解析这三个步骤的处理链。
chain prompt | llm | parser# 调用 chain 的 invoke 方法来执行这个处理链。
# invoke 方法接收一个字典作为参数字典中的键 query 对应的值就是用户输入的查询内容。
# 处理链会按照之前定义的顺序依次执行各个组件
# 首先prompt 会根据传入的查询内容和解析指令生成提示信息。
# 然后这个提示信息会被传递给 llmllm 基于提示信息生成文本输出。
# 最后parser 会将 llm 生成的文本输出解析为 People 模型实例。
# 整个处理过程完成后会返回经过解析后的结构化数据方便后续的业务逻辑使用。
chain.invoke({query: query})打印查看格式化后的输出完全符合 People 类中定义的schema。
People(people[Person(nameAnna, height_in_meters1.8288)])自定义解析器
为了增加结构化LLM输出的灵活性我们可以使用LangChain表达语言 LangChain Expression Language (LCEL)来自定义prompt和解析器通过创建一个函数的方式。
import json
import re
from typing import List
from langchain_ollama import ChatOllama
from langchain_core.messages import AIMessage
from langchain_core.prompts import ChatPromptTemplate
from pydantic import BaseModel, Fieldllm ChatOllama(model llama3.1:latest, temperature 0.8)class Person(BaseModel):Information about a person.name: str Field(..., descriptionThe name of the person)height_in_meters: float Field(..., descriptionThe height of the person expressed in meters.)class People(BaseModel):Identifying information about all people in a text.people: List[Person]# 自定义prompt我们通过system prompt告知LLM要将输出内容wrap在json/json标签中
prompt ChatPromptTemplate.from_messages([(system,Answer the user query. Output your answer as JSON that matches the given schema: json\n{schema}\n/json. Make sure to wrap the answer in json and /json tags,),(human, {query}),]
).partial(schemaPeople.model_json_schema())我们看一下传入 user query后完整的prompt内容
query Anna is 23 years old and she is 6 feet tall
print(prompt.format_prompt(queryquery).to_string())prompt
System: Answer the user query. Output your answer as JSON that matches the given schema: json
{$defs: {Person: {description: Information about a person., properties: {name: {description: The name of the person, title: Name, type: string}, height_in_meters: {description: The height of the person expressed in meters., title: Height In Meters, type: number}}, required: [name, height_in_meters], title: Person, type: object}}, description: Identifying information about all people in a text., properties: {people: {items: {$ref: #/$defs/Person}, title: People, type: array}}, required: [people], title: People, type: object}
/json. Make sure to wrap the answer in json and /json tags
Human: Anna is 23 years old and she is 6 feet tall通过创建名为 extract_json 的函数自定义 parser
# Custom parser
def extract_json(message: AIMessage) - List[dict]:Extracts JSON content from a string where JSON is embedded between json and /json tags.Parameters:text (str): The text containing the JSON content.Returns:list: A list of extracted JSON strings.text message.content# Define the regular expression pattern to match JSON blockspattern rjson(.*?)/json# Find all non-overlapping matches of the pattern in the stringmatches re.findall(pattern, text, re.DOTALL)# Return the list of matched JSON strings, stripping any leading or trailing whitespacetry:return [json.loads(match.strip()) for match in matches]except Exception:raise ValueError(fFailed to parse: {message})最后我们基于 prompt, llm 和 自定义用来解析大模型输出内容的parser通过管道创建一个链式调用
chain prompt | llm | extract_json
chain.invoke({query: query})调用 chain 后得到的结构化输出如下
[{$defs: {Person: {description: Information about a person.,properties: {name: {description: The name of the person,title: Name,type: string},height_in_meters: {description: The height of the person expressed in meters.,title: Height In Meters,type: number}},required: [name, height_in_meters],title: Person,type: object}},description: Identifying information about all people in a text.,properties: {people: {items: {$ref: #/$defs/Person},title: People,type: array}},required: [people],title: People,type: object,people: [{name: Anna, height_in_meters: 1.8288}]}]但以上并非预期的输出预期输出应该如下
[{people: [{name: Anna, height_in_meters: 1.8288}]}]至于原因目前还不清楚初步猜测是因为选用的 llama3.1:latest 模型因为量化程度较高导致对带输出格式说明的system prompt的理解力不够当然也有可能是官方文档里提供的示例代码有些问题。我在本地第一次运行的时候是报错的对代码稍微调整了一下才fix了报错的问题