概述
在 LangChain 里,输出解析器扮演着极为关键的角色,它能把语言模型生成的原始文本输出转化为特定的数据结构或格式,让输出更便于处理、存储和进一步分析。
输出解析器本质上是一个工具,主要用于解决语言模型输出的原始文本缺乏结构化、难以直接使用的问题。它依据预设的规则和格式,对模型输出进行解析和转换,将自由形式的文本转化为程序易于处理的结构化数据,像字典、列表、自定义对象等。
主要作用
数据结构化:把模型输出的自然语言文本转换为结构化的数据,例如将一段描述用户信息的文本解析成包含姓名、年龄、职业等字段的字典,这样能更方便地进行后续的程序操作。
错误处理:对模型输出进行格式验证,若输出不符合预先定义的格式,输出解析器会抛出错误或进行相应的处理,有助于及时发现和解决问题。
标准化输出:保证输出具有统一的格式,使得不同运行实例或不同模型的输出能够进行有效的比较和分析。
常用解析器
1. PydanticOutputParser:用于解析为Pydantic模型,支持复杂结构和类型验证。
2. SimpleJsonOutputParser:解析简单的JSON输出为字典。
3. CommaSeparatedListOutputParser:解析逗号分隔的列表。
4. OutputFixingParser:用于自动修复格式错误的输出。
5. RetryOutputParser:在解析失败时尝试重新生成。
6. DatetimeOutputParser:解析日期时间字符串。
7. BooleanOutputParser:解析布尔值(如是/否,true/false)为Python布尔类型。
8. StructuredOutputParser:旧版的键值对结构化解析(现在推荐使用PydanticOutputParser)。
9. JsonOutputParser:解析JSON字符串(与SimpleJsonOutputParser类似,但可能更灵活)。
StrOutputParser
StrOutputParser
是 LangChain 中一个较为基础且简单的输出解析器,它的主要功能是原封不动地返回语言模型的输出,也就是将模型输出作为字符串进行处理,不会对输出内容进行额外的解析或转换操作。以下为你详细介绍:
功能与用途
在很多情况下,我们可能并不需要对语言模型的输出进行复杂的结构化处理,只是单纯想获取模型输出的原始文本内容,这时 StrOutputParser
就非常适用。比如,当模型输出的本身就是一段完整且无需进一步解析的文本,像一篇文章、一首诗歌、一段故事等,使用 StrOutputParser
就能直接得到这些文本信息。
示例:
from langchain_core.output_parsers import StrOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(
api_key="",
base_url="https://api.baichuan-ai.com/v1",
model="Baichuan3-Turbo"
)
prompt = PromptTemplate.from_template("你是一个ai助手,回答用户的提出的问题:{question}")
chain = prompt | llm | StrOutputParser()
print("使用StrOutputParser解析器:", chain.invoke({"question": "你是谁?"}))
chain2 = prompt | llm
print("未使用StrOutputParser解析器:", chain2.invoke({"question": "你是谁?"}))
使用StrOutputParser解析器: 我叫百川大模型,是由百川智能的工程师们创造的大语言模型,我可以和人类进行自然交流、解答问题、协助创作,帮助大众轻松、普惠的获得世界知识和专业服务。如果你有任何问题,可以随时向我提问。
未使用StrOutputParser解析器: content='我叫百川大模型,是由百川智能的工程师们创造的大语言模型,我可以和人类进行自然交流、解答问题、协助创作,帮助大众轻松、普惠的获得世界知识和专业服务。如果你有任何问题,可以随时向我提问。' additional_kwargs={'refusal': None} response_metadata={'token_usage': {'completion_tokens': 56, 'prompt_tokens': 75, 'total_tokens': 131, 'completion_tokens_details': None, 'prompt_tokens_details': None, 'search_count': 0}, 'model_name': 'Baichuan3-Turbo', 'system_fingerprint': None, 'id': 'chatcmpl-M9B8D0277GMeokv', 'service_tier': None, 'finish_reason': 'stop', 'logprobs': None} id='run--86b34110-b039-4b94-89a7-81a8de385fae-0' usage_metadata={'input_tokens': 75, 'output_tokens': 56, 'total_tokens': 131, 'input_token_details': {}, 'output_token_details': {}}
使用StrOutputParser
直接会帮我们将输出文本提取出来
SimpleJsonOutputParser
SimpleJsonOutputParser
是 LangChain 提供的一个轻量级的输出解析器,专门用于让大语言模型(LLM)以JSON 格式输出结构化数据,并将 LLM 的输出解析为 Python 的字典(dict
)类型。
它的主要特点是:
-
简单易用:不需要复杂的配置或额外的依赖(如 Pydantic 模型)。
-
专注于 JSON 格式:让 LLM 输出符合 JSON 格式的字符串,然后将其解析为 Python 字典。
-
轻量级:相比
JsonOutputParser
(支持 Pydantic 模型),它的功能更简单,但也更直接。
示例:
from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser, XMLOutputParser, \
SimpleJsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import Field, BaseModel
from model.deepseek import deepseek_llm
class Joke(BaseModel):
setup: str = Field(description="笑话中的铺垫问题,必须以?结尾")
punchline: str = Field(description="笑话中回答铺垫问题的部分,通常是一种抖包袱方式回答铺垫问题,例如谐音,会错意等")
parser = SimpleJsonOutputParser(pydantic_object=Joke)
prompt = PromptTemplate(
template="回答用户的查询.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
print("提示词模板:", prompt.partial_variables['format_instructions'])
chat_prompt = prompt | deepseek_llm
output = chat_prompt.invoke({"query": "请给我讲一个笑话"})
print("格式化后的结果:", parser.invoke(output))
提示词模板: 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:
```
{"properties": {"setup": {"description": "笑话中的铺垫问题,必须以?结尾", "title": "Setup", "type": "string"}, "punchline": {"description": "笑话中回答铺垫问题的部分,通常是一种抖包袱方式回答铺垫问题,例如谐音,会错意等", "title": "Punchline", "type": "string"}}, "required": ["setup", "punchline"]}
```
格式化后的结果: {'setup': '为什么程序员总是分不清万圣节和圣诞节?', 'punchline': '因为Oct 31等于Dec 25。'}
PydanticOutputParser
PydanticOutputParser
是 LangChain 中的一个输出解析器,它借助 Pydantic 库,能够把语言模型的输出解析为自定义的 Pydantic 模型实例,让输出数据更具结构化和类型安全。
from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser, XMLOutputParser, \
SimpleJsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import Field, BaseModel
from model.deepseek import deepseek_llm
class Joke(BaseModel):
setup: str = Field(description="笑话中的铺垫问题,必须以?结尾")
punchline: str = Field(description="笑话中回答铺垫问题的部分,通常是一种抖包袱方式回答铺垫问题,例如谐音,会错意等")
parser = PydanticOutputParser(pydantic_object=Joke)
prompt = PromptTemplate(
template="回答用户的查询.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
print("提示词模板:", prompt.partial_variables['format_instructions'])
prompt_and_model = prompt | deepseek_llm
output = prompt_and_model.invoke({"query": "请给我讲一个笑话"})
print("格式化后的结果:", parser.invoke(output))
from langchain_core.output_parsers import PydanticOutputParser, JsonOutputParser, XMLOutputParser, \
SimpleJsonOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import Field, BaseModel
from model.deepseek import deepseek_llm
class Joke(BaseModel):
setup: str = Field(description="笑话中的铺垫问题,必须以?结尾")
punchline: str = Field(description="笑话中回答铺垫问题的部分,通常是一种抖包袱方式回答铺垫问题,例如谐音,会错意等")
parser = PydanticOutputParser(pydantic_object=Joke)
prompt = PromptTemplate(
template="回答用户的查询.\n{format_instructions}\n{query}\n",
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
print("提示词模板:", prompt.partial_variables['format_instructions'])
prompt_and_model = prompt | deepseek_llm
output = prompt_and_model.invoke({"query": "请给我讲一个笑话"})
print("格式化后的结果:", parser.invoke(output))
其它的解析器使用基本差不多,就不一一举例说明了,下面介绍两个特殊的解析器
OutputFixingParser
OutputFixingParser 是 LangChain 提供的一个工具类,用于修复格式错误的输出并尝试再次解析为有效的结构化数据(如 Pydantic 模型)。
当 LLM 的输出不符合指定的格式要求时(如 JSON 格式错误、字段缺失或类型不匹配),OutputFixingParser 会:
1.捕获解析异常
2.将错误的输出和解析器的要求发送给 LLM
3.让 LLM 尝试生成一个符合格式的正确输出
4.再次尝试解析
from typing import List
from langchain.output_parsers import OutputFixingParser
from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import PydanticOutputParser
from pydantic import BaseModel, Field
from model.deepseek import deepseek_llm
class Actor(BaseModel):
name: str = Field(description="演员的名字")
film_names: List[str] = Field(description="演员参演的电影名称")
parser = PydanticOutputParser(pydantic_object=Actor)
#假设大模型输出了一个错误格式数据
misformatted = """{'name': 'Judi Dench', 'film_names': ['Forrest Gump']}"""
try:
parser.parse(misformatted)
except OutputParserException as e:
print("数据解析错误,让大模型修复")
new_parser = OutputFixingParser.from_llm(parser=parser, llm=deepseek_llm)
resp = new_parser.parse(misformatted)
print("大模型修复后的输出",resp)
RetryOutputParser
RetryOutputParser 是 LangChain 提供的一个输出解析器,用于在解析失败时自动重试生成符合指定格式的响应。它通常与 PydanticOutputParser 一起使用,以处理 LLM 输出不规范或结构错误的情况。
from langchain.output_parsers import RetryOutputParser
from langchain_core.exceptions import OutputParserException
from langchain_core.output_parsers import PydanticOutputParser
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
from model.deepseek import deepseek_llm
template = """
Based on the user question, provide an Action and Action Input for what
step should be taken.
{format_instructions}
Question: {query}
Response:
"""
class Action(BaseModel):
action: str = Field(description="action to take")
action_input: str = Field(description="input to the action")
parser = PydanticOutputParser(pydantic_object=Action)
prompt = PromptTemplate(
template=template,
input_variables=["query"],
partial_variables={"format_instructions": parser.get_format_instructions()}
)
prompt_value = prompt.format_prompt(query='北京今天天气如何?')
#假设大模型输出了一个错误格式
bad_response = '{"action":"search"}'
try:
parser.parse(bad_response)
except OutputParserException as e:
print("解析出错了,开始重试了")
retry_parser = RetryOutputParser.from_llm(parser=parser, llm=deepseek_llm)
with_prompt = retry_parser.parse_with_prompt(bad_response, prompt_value)
print("重试后的结果:",with_prompt)
其它常用的结构化输出方式
with_structured_output
with_structured_output
是 LangChain 提供的一个链式调用方法,它可以将一个普通的 LLM 或 Chain 转换为一个能够输出结构化数据的新 Chain。
它的核心作用是:
-
让 LLM 按照指定的结构(如 JSON Schema 或 Pydantic 模型)输出数据。
-
自动处理 LLM 输出的解析,将自由文本转换为 Python 中的结构化对象(如字典、Pydantic 模型实例等)。
通过 with_structured_output
,开发者可以轻松构建需要精确数据格式的 LLM 应用,而无需手动编写复杂的 Prompt 或解析逻辑。openAI和Claude都是支持with_structured_output,
很多国产大模型不支持with_structured_output
,使用前请充分测试,
import json
from typing import Optional
from langchain_core.prompts import PromptTemplate
from pydantic import BaseModel, Field
from langchainproject.langchain_demo.chatgpt import openapi_llm
#生成一个笑话的段子
class Joke(BaseModel):
"""
笑话的结构类数据模型POVO
"""
setup: str = Field(description="笑话的开头部分")
punchline: str = Field(description="笑话的笑点")
rating: Optional[int] = Field(description="笑话的评分范围1到10分", ge=1, le=10)
prompt_template = PromptTemplate.from_template("帮我生成一个关于{topic}的笑话.")
runnable = openapi_llm.with_structured_output(Joke)
chain = prompt_template | runnable
result = chain.invoke({"topic": "猫"})
print(result)
print(result.__dict__)
json_str = json.dumps(result.__dict__, ensure_ascii=False)
print(json_str)
使用工具调用格式化
bind_tools()
是 LangChain 提供的一个方法,用于将一组“工具”(Tools)绑定到一个 LLM 或 Chain 上。它的核心作用是:
-
扩展 LLM 的能力:让 LLM 在需要时可以“调用工具”来完成某些特定任务(如计算、查询、API 调用等)。
-
结构化工具调用:LangChain 会自动将工具的使用封装成一种标准化的格式,使得 LLM 可以通过自然语言描述来调用工具,而无需直接了解工具的具体实现细节。
from pydantic import BaseModel, Field
from langchainproject.langchain_demo.chatgpt import openapi_llm
class ResponseFormatter(BaseModel):
answer: str = Field(description="对用户问题的答案")
followup_question: str = Field(description="用户可能提出的后续问题")
runnable = openapi_llm.bind_tools([ResponseFormatter])
result = runnable.invoke("细胞的动力源是什么?")
print(result.tool_calls[-1]['args'])