LangChain的函数,工具和代理(四):使用 OpenAI 函数进行标记(Tagging) 提取(Extraction)

在上一篇博客LangChain中轻松实现OpenAI函数调用 中我们学习了如何使用Pydantic来生成openai的函数描述对象,并且通过在langchain中调用Pydantic生成的函数描述变量来轻松实现openai的函数调用功能,在此基础上今天我们再介绍两个非常实用的功能:标记(Tagging)和提取(Extraction)。

一、标记(Tagging)

所谓“标记(Tagging)”是指有时候我们希望llm能够对用户提交的文本信息做出某些方面的评估,比如情感评估(positive, negative, neutral),语言评估(chinese,english,japanese等),并给出一个结构化的输出结果(如json格式)。

接下来在“抠腚”😀之前,先让我们做一些初始化的工作,如设置opai的api_key,这里我们需要说明一下,在我们项目的文件夹里会存放一个 .env的配置文件,我们将api_key放置在该文件中,我们在程序中会使用dotenv包来读取api_key,这样可以避免将api_key直接暴露在程序中:

#pip install -U python-dotenvimport os
import openaifrom dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']

要实现“Tagging”功能,我们需要定义一个Pydantic类,然后让langchain将其转换成openai的函数描述变量,如何还不熟悉Pydantic的同学可以看一下我先前写的LangChain中轻松实现OpenAI函数调用这篇博客。

from typing import List,Optional
from pydantic import BaseModel, Field
from langchain.utils.openai_functions import convert_pydantic_to_openai_function
from langchain.prompts import ChatPromptTemplate
from langchain.chat_models import ChatOpenAI#定义pydantic类用以生成openai的函数描述变量
class Tagging(BaseModel):"""Tag the piece of text with particular info."""sentiment: str = Field(description="sentiment of text, should be `pos`, `neg`, or `neutral`")language: str = Field(description="language of text (should be ISO 639-1 code)")

这里我们定义了一个“Tagging”它继承自pydantic的BaseModel类,因此Tagging类也具备了严格的数据类型校验功能,Tagging类包含了2给成员变量:sentiment和language,其中sentiment用来判断用户信息的情感包括pos(正面),neg(负面),neutral(中立),language用来判断用户使用的是哪国的语言,并且要符合ISO 639-1 编码规范。

接下来我们要将Tagging类转换成一个openai能识别的函数描述对象:

tagging_functions = [convert_pydantic_to_openai_function(Tagging)]
tagging_functions

 有了函数描述变量,接下来我们需要使用熟悉的langchian的LCEL语法来创建一个chain,不过在这之前我们需要创建prompt, model,然后绑定函数描述变量最后创建chain并调用chain:

#根据模板创建prompt
prompt = ChatPromptTemplate.from_messages([("system", "Think carefully, and then tag the text as instructed"),("user", "{input}")
])#创建llm
model = ChatOpenAI(temperature=0)
#绑定函数描述变量,指定函数名(意味着强制调用)
model_with_functions = model.bind(functions=tagging_functions,function_call={"name": "Tagging"}
)
#创建chain
tagging_chain = prompt | model_with_functions
#调用chain
tagging_chain.invoke({"input": "I love shanghai"})

 这里我们看到对于用户信息:“I love shanghai”,llm返回的结果中sentiment为pos, language为en, 下面我们用中文信息测试一下:

tagging_chain.invoke({"input": "这家饭店的菜真难吃"})

同样这里我们也看到llm给出了正确的判断。不过这里llm给出的AIMessage格式的结果,要从中提取出我们需要的内容看上去有点麻烦,不过我们可以利用langchain的LCEL语法,在创建chain的时候附加一个json的输出解析器就可以解决这个问题:

from langchain.output_parsers.openai_functions import JsonOutputFunctionsParsertagging_chain = prompt | model_with_functions | JsonOutputFunctionsParser()
tagging_chain.invoke({"input": "这家饭店的菜真难吃"})

 

二、提取(Extraction)

 “提取(Extraction)”与“标记(Tagging)”有点类似,只不过提取不是对用户信息的评估,而是从中抽取出指定的信息。

要实现“Extraction”功能,我们任然需要定义一个Pydantic类,然后让langchain将其转换成openai的函数描述变量:

class Person(BaseModel):"""Information about a person."""name: str = Field(description="person's name")age: Optional[int] = Field(description="person's age")class Information(BaseModel):"""Information to extract."""people: List[Person] = Field(description="List of info about people")

这里我们定义了Person和Information两个类,其中person类包含了2个成员,name和age,其中age是可选的(Optional)即age不是必须的。Information类包含了一个people成员,它一个person的集合(List)。后面我们要利用这个Information类来提取用户信息中的个人信息:姓名,年龄。

下面我们要将Information类转换成一个openai能识别的函数描述对象:

convert_pydantic_to_openai_function(Information)

 接下来我们来创建一个函数描述对象,并将其绑定在llm上,然后调用llm时输入文本:“小明今年15岁,他的妈妈是张丽丽”,我们看看llm会返回什么样的结果:

#创建函数描述变量
extraction_functions = [convert_pydantic_to_openai_function(Information)]#绑定函数描述变量
extraction_model = model.bind(functions=extraction_functions, function_call={"name": "Information"})#llm调用
extraction_model.invoke("小明今年15岁,他的妈妈是张丽丽")

这里从llm的返回信息中我们看到llm提取了 小明和他的年龄,以及 张丽丽和她的年龄,这里有个问题是用户的信息是:“小明今年15岁,他的妈妈是张丽丽”,此信息中并没有包含张丽丽的年龄,但是llm返回的信息中张丽丽的年龄是0岁,这明显是有问题的,如果信息中没有包含年龄,那就不应该提取,因为在定义Person类时age是可选的,那我们如何来解决这个问题呢?

要解决这个问题,我们需要创建一个prompt和chain,并在prompt中提醒llm不要提取不存在的信息:

prompt = ChatPromptTemplate.from_messages([("system", "Extract the relevant information, if not explicitly provided do not guess. Extract partial info"),("human", "{input}")
])#创建函数描述变量
extraction_functions = [convert_pydantic_to_openai_function(Information)]#绑定函数描述变量
extraction_model = model.bind(functions=extraction_functions, function_call={"name": "Information"})
#创建chain
extraction_chain = prompt | extraction_model
#调用chain
extraction_chain.invoke({"input": "小明今年15岁,他的妈妈是张丽丽"})

这里我们看到当我们在prompt中提醒llm:  "if not explicitly provided do not guess. Extract partial info",意思是在没有明确提供的信息的情况下,不要猜测,抽取部分信息即可,这样做有效的避免了llm产生“幻觉”而给出错误的答案。下面我们再创建一个json的键值解析器,这样可以更方便的从llm的返回信息中过滤出我们需要的内容:

from langchain.output_parsers.openai_functions import JsonKeyOutputFunctionsParser#创建chain
extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="people")#调用chain
extraction_chain.invoke({"input": "小明今年15岁,他的妈妈是张丽丽"})

 这里需要说明的是先前我们在做Tagging的时候使用了一个JsonOutputFunctionsParser输出解析器,该解析器能从llm返回的AIMessage中提取其中的arguments的内容,而这里我们使用的是JsonKeyOutputFunctionsParser输出解析器,它可以从arguments中根据key来提取内容,这里我们设置了key为“people”即提取arguments中key为“people”的内容。

三、真实场景的应用

在某些真实的应用场景中我们可能需要对某些长文本如论文,博客,新闻等内容进行总结,并提取其中的一些关键词,现在我们就可以用过langchain的Tagging和Extraction功能来实现这样的功能需求,下面我们要对凤凰网上的一篇科技文章:对话阿里云CTO周靖人:开源是唯一出路,通义千问和ChatGPT互有胜负_凤凰网 来实现打标签(tagging)的功能,我们要让llm对这篇文章内容进行总结,并识别文章用的语言,以及从文章中提取关键词,不过首先我们需要创建一个网页加载器以便从网页上拉取文章内容:

from langchain.document_loaders import WebBaseLoader#创建loader,获取网页数据
loader = WebBaseLoader("https://tech.ifeng.com/c/8VEctgVlwbk")
documents = loader.load()#查看网页内容
doc = documents[0]
page_content = doc.page_content[:3000]
print(page_content)

 下面我们定义一个Pydantic类“Overview”,它包含了三个成员:summary,language,keywords,其中summary表示对文章内容的总结,language表示文章所使用的语言,keyword表示文章中的关键词:

class Overview(BaseModel):"""Overview of a section of text."""summary: str = Field(description="Provide a concise summary of the content.")language: str = Field(description="Provide the language that the content is written in.")keywords: str = Field(description="Provide keywords related to the content.")

接下来我们需要创建函数描述变量,并将其和llm绑定在一起,然后再使用langchain的LCEL语法将prompt,llm,输出解析器组合在一起生成一个chain, 最后我们再调用这个chain:

#创建openai函数描述变量
overview_tagging_function = [convert_pydantic_to_openai_function(Overview)
]
#创建llm
tagging_model = model.bind(functions=overview_tagging_function,function_call={"name":"Overview"}
)
#创建prompt
prompt = ChatPromptTemplate.from_messages([("system", "Extract the relevant information, if not explicitly provided do not guess. Extract partial info"),("human", "{input}")
])
#创建chain
tagging_chain = prompt | tagging_model | JsonOutputFunctionsParser()
#调用chain
tagging_chain.invoke({"input": page_content})

 从上面的结果中我们看到llm轻松的完成了我们给它布置的任务,完美的对文章内容进行的总结,并且还给出了language和keywords。

接下来我们来实现提取(Extraction)功能, 我们要提取文章中的标题和作者,不过首先我们需要创建两个Pydantic类News,Info,这两个类用来创建函数描述变量:

class News(BaseModel):"""Information about news mentioned."""title: strauthor: Optional[str]class Info(BaseModel):"""Information to extract"""news: List[News]

接下来我们重复之前创建tagging_chain 的步骤来创建一个extraction_chain:

#创建函数描述变量
news_extraction_function = [convert_pydantic_to_openai_function(Info)
]#创建llm
model = ChatOpenAI(temperature=0)
#绑定函数描述变量
extraction_model = model.bind(functions=news_extraction_function, function_call={"name":"Info"}
)
#创建chain
extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="news")
#调用chain
extraction_chain.invoke({"input": page_content})

 

这里我们使用了JsonKeyOutputFunctionsParser解析器,因此我们可以从llm的返回消息中根据key来提取内容。

接下来我们再深入介绍一个实用的例子,我们需要从一篇论文:LLM Powered Autonomous Agents | Lil'Log ,如下图所示:

我们要从这篇论文中提取title,和author,这里要说明的是之前我们提取的是当前文章的title,和author, 而这里我们要提取不是这篇论文的title和author,而是要提取这篇论文中提及的其他论文的title和author。下面我们首先加载这篇在线论文:

#加载论文
loader = WebBaseLoader("https://lilianweng.github.io/posts/2023-06-23-agent/")
documents = loader.load()
doc = documents[0]
page_content = doc.page_content[:10000]
page_content 

 这里由于这篇论文内容太长,这里我们只截取了前10000个字符(page_content[:10000]),下面我们要创建两个Pydantic类:Paper和Info。然后我们再创建一个函数描述变量并将它与llm进行绑定:

#创建paper类
class Paper(BaseModel):"""Information about papers mentioned."""title: strauthor: Optional[str]#创建Info类
class Info(BaseModel):"""Information to extract"""papers: List[Paper]#创建函数描述变量        
paper_extraction_function = [convert_pydantic_to_openai_function(Info)
]
#将函数描述变量绑定llm
extraction_model = model.bind(functions=paper_extraction_function, function_call={"name":"Info"}
)

因为这回我们需要从该篇论文中提取所有的title和author,那么我们要将要求明确的告知LLM,所以我们需要创建prompt模板,并在这个模板中我们告知llm我们的要求是什么,然后从该模板来创建一个prompt:

template = """A article will be passed to you. Extract from it all papers that are mentioned by this article. Do not extract the name of the article itself. If no papers are mentioned that's fine - you don't need to extract any! Just return an empty list.Do not make up or guess ANY extra information. Only extract what exactly is in the text."""prompt = ChatPromptTemplate.from_messages([("system", template),("human", "{input}")
])

这里我们将prompt模板的内容翻译成中文,这样便于大家理解其意思:

最后我们使用langchain的LCEL语法将prompt,llm,输出解析器组合在一起生成一个chain, 最后我们再调用这个chain:


extraction_chain = prompt | extraction_model | JsonKeyOutputFunctionsParser(key_name="papers")extraction_chain.invoke({"input": page_content})

 这里我们发现LLM返回了部分我们所需要的title和author,只不过由于我只从这篇论文中截取了前10000个字符,导致LLM返回的结果并不完整,之所以只截取了论文前10000个字符是因为openai的模型对输入的上下文长度有一定的限制,如果超过限制将会导致异常,为了解决这个问题,我们可以考虑使用langchain的文本分割技术将长文本分割成多个文档块,然后逐一将文档块喂给LLM,这样就不会因为上下文长度超过限制而产生异常。下面我们就利用langchain的文档分割组件RecursiveCharacterTextSplitter来创建一个文档分割器,并在此基础上创建一RunnableLambda,它的作用是将分割器分割好的文档存储在一个list中以备后续是使用:

from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema.runnable import RunnableLambdatext_splitter = RecursiveCharacterTextSplitter(chunk_overlap=0)prep = RunnableLambda(lambda x: [{"input": doc} for doc in text_splitter.split_text(x)]
)response = prep.invoke(doc.page_content)
response

 这里我们看到论文被分割成了多个文档块,下面我们统计一下文档块的数量:

len(response)

 

这里我们看到该篇论文被分割成了14个文档块,接下来我们还需要定义一个矩阵的展开函数,该函数用于最终对输出结果的整理:

#定义矩阵展开函数
def flatten(matrix):flat_list = []for row in matrix:flat_list += rowreturn flat_list#测试展开函数
flatten([[1, 2], [3, 4]])

 这里的flatten函数的作用是将多行的矩阵展开为一个只有1行数据的矩阵,最后我们使用langchain的LCEL语法将prep,extraction_chain,flatten组合在一起生成一个新的chain, 最后我们再调用这个chain,需要注意的是这里在创建chain时使用的是前面的extraction_chain,而不是的先前例子中的model:

#创建chain
chain = prep | extraction_chain.map() | flatten#调用chain
chain.invoke(doc.page_content)

 这里我们看到llm返回了论文中所有的title,和author, 但是这里需要说明一下的是在创建chain时我们使用的“prep | extraction_chain.map() | flatten”,这里的prep和之前在创建chain时使用的prompt有所不同,之前的prompt只包含一个文档的信息,而这里的prep是一个list它包含了多个文档信息(14个文档块),而prep后面的 extraction_chain.map()作用是将prep中的每个文档单独映射到extraction_chain中,最后使用flatten将输出结果进行展开,如果不使用flatten那么在输出结果中会存在多个list,且每个list中都包含了对应的文档块中的所有title和author,这会让结果看 上去比较混乱。

四、总结 

今天我们学习了通过langchain和openai的函数调用来实现标记(Tagging)和提取(Extraction)功能,通过taggin我们可以让llm对用户信息进行评估,通过extration我们可以让llm从用户信息中提取有用的内容,最后我们介绍了两个真实的应用场景案例,我们介绍了如何使用langchain的长文本切割工具对长文本进行切割,从而解决了openai的llm对输入的上下文长度限制问题。希望今天的内容对大家有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/196625.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024年,Rust和Go学哪个更好?

Rust vs. Go,在2024年,应该选择哪一个?或者说应该选择哪种语言——GoLang还是Rust。这可能是许多程序员在选择语言时考虑的一个问题。选择理想的编程语言被视为在这个不断变化的环境中取得成功的重要抉择。 GoLang和Rust是当今使用的最年轻的…

【黑马甄选离线数仓day08_会员主题域开发】

1. 会员主题域需求说明 1.1 各类会员数量统计 说明:公司为了对不同会员进行不同的营销策略,对各类会员的数量都非常敏感,比如注册会员、消费会员、复购会员、活跃会员、沉睡会员。不仅需要看新增数量还要看累积数量。 指标:新增…

二十九、微服务案例完善(数据聚合、自动补全、数据同步)

目录 一、定义 二、分类 1、桶(Bucket)聚合: 2、度量(Metric)聚合: 3、管道聚合(Pipeline Aggregation): 4、注意: 参与聚合的字段类型必须是: 三、使用DSL实现聚合 聚合所必须的三要素: 聚合可配…

考研数学 每日一题

考研数学 每日一题

【Linux】初识云服务器 -- 使用 XShell 远程登录 Linux

Linux 是一款企业级后台操作系统,命令行方式交互,开源。 搭建属于自己的 Linux 服务器:我是直接选择购买的腾讯云轻量级服务器(CentOS 7.6),不贵又相对方便,可以直接上手使用,不需要…

Elasticsearch:为现代搜索工作流程和生成式人工智能应用程序铺平道路

作者:Matt Riley Elastic 的创新投资支持开放的生态系统和更简单的开发者体验。 在本博客中,我们希望分享 Elastic 为简化你构建 AI 应用程序的体验而进行的投资。 我们知道,开发人员必须在当今快速发展的人工智能环境中保持灵活性。 然而&a…

JavaEE之多线程编程(一):基础篇

文章目录 一、关于操作系统一、认识进程 process二、认识线程三、进程和线程的区别(重点!)四、Java的线程和操作系统线程的关系五、第一个多线程编程 一、关于操作系统 【操作系统】 驱动程序: 如:我们知道JDBC的驱动程…

Python 潮流周刊#29:Rust 会比 Python 慢?!

△请给“Python猫”加星标 ,以免错过文章推送 你好,我是猫哥。这里每周分享优质的 Python、AI 及通用技术内容,大部分为英文。本周刊开源,欢迎投稿[1]。另有电报频道[2]作为副刊,补充发布更加丰富的资讯。 &#x1f43…

【数据结构】手撕排序NO.1

🔥博客主页: 小羊失眠啦. 🎥系列专栏:《C语言》 《数据结构》 《Linux》《Cpolar》 ❤️感谢大家点赞👍收藏⭐评论✍️ 文章目录 一、排序的概念及其运用1.1 排序的概念1.2 常见的算法排序 二、 冒泡排序三、直接插入排…

【电源专题】什么是电源管理

电源管理为什么重要? 在电子系统和电路的设计中,负载往往需要恒定的电流电压,所以最先考虑的就是电源电路的设计。电源管理所考虑的问题是如何将电源有效分配给系统的不同组件,保障系统不同的负载正常运行。 如电源的输入是交流 (AC) 或直流 (DC)?输入电压是高于或低于输…

RedHat8 安装部署DzzOffice协同办公平台+onlyoffice(docker)以及问题解决(亲测可用,花费2天)

一、基础配置(MysqlDzzoffice) 1:安装软件需要的环境,我们用LAMP的环境。基本上CentOS8自带的软件版本都达到安装DzzOffice的要求。 2:关闭防火墙(不关的话需要开放80端口自行决定)。 systemctl disable --now fire…

HarmonyOS引入其他包,以引入请求axios为例

安装文件 安装文件位置: 总目录的oh-package.json5文件 dependencies:生产环境–上线运行时候必须需要的包 devDependencies:开发环境–开发适合为了方便提高效率的包。 包管理工具 OHPM CLI 作为鸿蒙生态三方库的包管理工具,支持OpenHar…

Facebook引流怎么做?写个脚本就好!

在当今的数字化时代,流量对于任何一个网站或应用程序来说都至关重要,Facebook,作为全球最大的社交网络平台,无疑是一个获取流量的绝佳场所,但是,如何有效地从Facebook引流呢?写个脚本就好了! 在本文中&am…

Python字符串模糊匹配工具:TheFuzz 库详解

更多资料获取 📚 个人网站:ipengtao.com 在处理文本数据时,常常需要进行模糊字符串匹配来找到相似的字符串。Python的 TheFuzz 库提供了强大的方法用于解决这类问题。本文将深入介绍 TheFuzz 库,探讨其基本概念、常用方法和示例代…

什么是Overlay网络?Overlay网络与Underlay网络有什么区别?

你们好,我的网工朋友。 在传统历史阶段,数据中心的网络是以三层架构(核心、汇聚、接入)为基本标准。 但是随着技术的发展,不同的厂家有不同的组建方式,比如说在核心层、汇聚层和接入层增加虚拟化技术。 …

Maven项目目录结构

项目结构 目录说明.ideaIDEA工具的配置文件.mvn用于运行Maven项目src源码文件夹target字节码文件夹.gitignore配置git忽略文件HELP.md自述文件mvnw运行Maven命令(Linux)mvnw.cmd运行Maven命令(Windows)pom.xml依赖管理文件 如图…

从零开始入门Zapier:与ChatGPT双剑合璧,手把手教程让你进入AI与自动化新纪元

coments 1. 1. 打开Zapier的官方界面 登录之后,会出现一个调查表,可以根据自己的情况进行选择。 第一次注册成功,会送你14天的免费体验

经验分享|MySQL分区实战(RANGE)

概述 分区概述 在 MySQL 中, InnoDB存储引擎长期以来一直支持表空间的概念。在 MySQL 8.0 中,同一个分区表的所有分区必须使用相同的存储引擎。但是,也可以为同一 MySQL 服务器甚至同一数据库中的不同分区表使用不同的存储引擎。 通俗地讲…

网络初识:局域网广域网网络通信基础

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、局域网LAN是什么?二、广域网是什么:三. IP地址四.端口号五.认识协议5.1五元组 总结 前言 一、局域网LAN是什么? 局域网…