随着理解的加深,后续会回来优化。
1 输出封装
LLM模型的输出通常都是字符串形式,Langchain中的输出封装OutputParser
可以将其转化解析成结构化对象。简单举例如下:
from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import CommaSeparatedListOutputParser
prompt1='说出几种常见的水果'
prompt2="输出20以内的素数"
llm=ChatOpenAI()
comma_parser=CommaSeparatedListOutputParser()
response=llm.invoke(prompt1)
print("===模型原始输出===")
print(response.content)
print('===CommaSeparatedListOutputParser解析输出===')
print(comma_parser.parse(response.content))
response=llm.invoke(prompt2)
print("===模型原始输出===")
print(response.content)
print('===CommaSeparatedListOutputParser解析输出===')
print(comma_parser.parse(response.content))
其输出结果如下:
===模型原始输出===
苹果、香蕉、橙子、草莓、葡萄、梨、樱桃、桃子、西瓜、哈密瓜、柚子、柠檬、火龙果、李子、菠萝、榴莲等。
===CommaSeparatedListOutputParser解析输出===
['苹果、香蕉、橙子、草莓、葡萄、梨、樱桃、桃子、西瓜、哈密瓜、柚子、柠檬、火龙果、李子、菠萝、榴莲等。']
===模型原始输出===
2, 3, 5, 7, 11, 13, 17, 19
===CommaSeparatedListOutputParser解析输出===
['2', '3', '5', '7', '11', '13', '17', '19']
Tips:针对prompt1模型输出的结果中的没有,
所以CommaSeparatedListOutputParser解析器没有正确解析出来。
本篇主要介绍langchain
和langchain_core
中的解析器。
1.1 langchain_core中的解析器
目前langchain_core.output_parsers
中的OutputParser
类主要包括以下几种(虚拟类不列出):
json
类:JsonOutputParser
,将结果解析成json串。list
类:CommaSeparatedListOutputParser
、MarkdownListOutputParser
和NumberedListOutputParser
,将结果解析成类似List的对象。CommaSeparatedListOutputParser
其结果是对LLM模型返回的字符串使用(,
)进行分割得到的List。后面两种分别采用正则表达式从LLM模型返回的字符串中提取出符合规则的部分。string
类。StrOutputParser
,直接输出LLM模型返回的结果。xml
类。XMLOutputParser
,将LLM模型返回的结果解析成xml。pydantic
类。PydanticOutputParser
, 第2部分介绍。openai_function
和openai_tools
类:这两类可以帮助提取出LLM产生的函数调用的参数。openai_function
系列针对的是使用function_call
字段表示函数调用的参数的情况,后者针对的使用tool_calls
字段表示function call的参数的情况(GPT系列模型采用的是这种方式)。这里只介绍openai_tools
类。主要包括JsonOutputKeyToolsParser
、JsonOutputToolsParser
和PydanticToolsParser
。其用法举例如下:
from langchain_openai import ChatOpenAI
from langchain_core.outputs import ChatGeneration
#注意引用方法
from langchain_core.output_parsers.openai_tools import JsonOutputToolsParser
tools=[{
"type": "function",
"function": {
"name": "sum",
"description": "加法器,计算一组数的和",
"parameters": {
"type": "object",
"properties": {
"numbers": {"type": "array", "items": { "type": "number"}}
}
}
}
}
]
llm=ChatOpenAI(model_kwargs={"tools":tools})
parser=JsonOutputToolsParser()
response=llm.invoke("计算一组数据的和:13,49837,3489,23423")
print(response)
response=ChatGeneration(message=response)
print("---解析后结果---")
print(parser.parse_result([response]))
其输出结果为:
content=‘’ additional_kwargs={‘tool_calls’: [{‘id’: ‘call_ek805JD7PATXCZ6yjNErBgpL’, ‘function’: {‘arguments’: ‘{“numbers”:[13,49837,3489,23423]}’, ‘name’: ‘sum’}, ‘type’: ‘function’}]} response_metadata={‘token_usage’: {‘completion_tokens’: 22, ‘prompt_tokens’: 68, ‘total_tokens’: 90}, ‘model_name’: ‘gpt-3.5-turbo’, ‘system_fingerprint’: ‘fp_c2295e73ad’, ‘finish_reason’: ‘tool_calls’, ‘logprobs’: None} id=‘run-ba534c13-9a3d-4e77-a056-37465fd58549-0’ tool_calls=[{‘name’: ‘sum’, ‘args’: {‘numbers’: [13, 49837, 3489, 23423]}, ‘id’: ‘call_ek805JD7PATXCZ6yjNErBgpL’}]
—解析后结果—
[{‘args’: {‘numbers’: [13, 49837, 3489, 23423]}, ‘type’: ‘sum’}]
1.2 langchain中的输出封装
目前langchain
中的OutputParser
类主要包括以下几种:
输出类型 | 类名称 | 备注 |
---|---|---|
boolean | BooleanOutputParser | 返回True或False |
combining | CombiningOutputParser | 同时将多个输出解析成同一个格式 |
datetime | DatetimeOutputParser | 将日期转化成特定格式 |
enum | EnumOutputParser | 将LLM模型输出解析成enum |
fix | OutputFixingParser | 可以自动修复异常并重新解析 |
pandas_dataframe | PandasDataFrameOutputParser | 使用pandas.dataframe的format对LLM进行解析 |
regex | RegexParser | 使用正则表达式对LLM模型返回进行解析 |
regex_dict | RegexDictParser | 功能同上 |
retry | RetryOutputParser 、RetryWithErrorOutputParser | 可以自动修复异常并重新解析 |
structured | StructuredOutputParser | 将LLM模型输出进行结构化解析 |
yaml | YamlOutputParser | 提取LLM模型输出中的YAML部分 |
虽然OutputParser
可以对LLM的输出结果进行解析,但这种使用方式用户体验并不好,正如第一个案例中针对prompt1的输出结果,因为无法确定LLM模型输出的结果中是否使用,
,所以解析结果无法掌控。可以使用OutputParser
类的get_format_instructions()
方法来调整LLM模型输出。举例如下:
from langchain.output_parsers import DatetimeOutputParser
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
parser=DatetimeOutputParser()
format_instruction=parser.get_format_instructions()
llm=ChatOpenAI()
prompt=PromptTemplate.from_template(template="计算机时间的起始时间是什么时候?{format_instruction}")
prompt=prompt.format(format_instruction=format_instruction)
response=llm.invoke(prompt)
print(response.content)
其输出结果如下:
0001-01-01T00:00:00.000000Z
2 PydanticOutputParser
PydanticOutputParser
类可以利用Pydantic
库自定义解析模型。
2.1 Pydantic基本用法
Pydantic
是Python 中一个流行的数据验证和设置库,允许自定义数据模型,并在运行时验证输入数据是否符合模型规范,确保应用程序在处理数据时的健壮性和可靠性。其用法举例如下(目前Langchain中可能只支持pydantic的v1版本,为保持一致,这里也仅使用v1版本):
from pydantic import BaseModel,Field,validator
import re
class Func_Json(BaseModel):
name:str=Field(description="函数名称,只能由字母组成并且长度不超过10")
description:str=Field(description="函数描述")
@validator("name")
def valid_name(cls,field):
if re.match(r'^[a-zA-Z]+$',field) and len(field)<10:
return field
else:
raise ValueError("函数名称不符合要求")
print(Func_Json(name="sum",description="求一组数据的和"))
print(Func_Json(name='sum2',description="求一组数据的和"))
其代码运行结果如下:
2.2 PydanticOutputParser基本用法
from pydantic import BaseModel,Field,validator
from langchain_core.output_parsers import PydanticOutputParser
from langchain_openai import ChatOpenAI
from typing import List
from langchain_core.prompts import PromptTemplate
class New_List(BaseModel):
item:List[str]
llm=ChatOpenAI()
parser=PydanticOutputParser(pydantic_object=New_List)
prompt=PromptTemplate.from_template("请列出10种常见的水果。{format_instruction}")
prompt=prompt.format(format_instruction=parser.get_format_instructions())
response=llm.invoke(prompt)
print(response.content)
prompt="请列出10种常见的汽车品牌"
new_parser=OutputFixingParser.from_llm(parser=parser,llm=ChatOpenAI())
response=llm.invoke(prompt)
print(response.content)
print(new_parser.parse(response.content))
其结果如下:
{
“item”: [“apple”, “banana”, “orange”, “strawberry”, “grape”, “watermelon”, “kiwi”, “pineapple”, “mango”, “pear”]
}
- Toyota
- Ford
- Chevrolet
- Honda
- Volkswagen
- BMW
- Mercedes-Benz
- Audi
- Nissan
- Hyundai
item=[‘Toyota’, ‘Ford’, ‘Chevrolet’, ‘Honda’, ‘Volkswagen’, ‘BMW’, ‘Mercedes-Benz’, ‘Audi’, ‘Nissan’, ‘Hyundai’]
参考文献
- https://blog.csdn.net/codename_cys/article/details/107675748