由Midjourney创建的图像,如一个悬浮在数据库里的数字文件。
最近我遇到了一个非常有趣的挑战,涉及AI对大量文档的数字化处理,并让用户能够提出复杂的、数据相关的问题,例如。
- 数据检索类型的问题: 这类问题涉及从数据库中获取特定的数据点或数据集,例如“电子产品有多少个?”或“2021年第四季度的总销售额是多少呢?”
- 聚合查询: 这类问题需要对数据进行总结,比如计算平均值、求和、计数等,例如“所有展示的产品的平均价格是多少?”或“每个地区的客户总数分别是多少?”
- 数据关系探索: 这类问题探究不同数据实体之间的关系,例如“哪些客户购买了超过三种不同类型的产品?”或“列出上个月没有发货任何产品的所有供应商。”
- 条件查询: 这类问题涉及条件或过滤器,例如“列出2022年单笔金额超过500美元的所有交易”或“列出所有缺货的产品。”
这些不是你通常能通过RAG解决的典型问题。相反地,我们将利用LangChain的SQL Agent工具从人类的文本中生成复杂的数据库查询。
这些文档应该包含各种规格要求,同时也要有更自然流畅的语言描述。
我们将按以下步骤操作,以便最终能够对大量文件提出复杂的问题。
- 读取所有 PDF 文件。
- 用 GPT 分析文档内容,解析成 JSON 对象。
- 将这些对象写入 SQLite,并分到多个表里。
- 用 LangChain SQL Agents 自动生成 SQL 语句提问。
免责声明: 本文涉及人工智能和数据操作的概念。为了获得最充分的理解,你应该具备Python编程、GPT模型、嵌入、向量搜索和SQL数据库等相关知识的基础。
向大家介绍:Osram 产品资料表你可以看看Osram的产品资料表。他们的网站上有许多这样的文档,你可以在这里找到很多。
OSRAM专业照明比如,“XBO 1000 W/HS OF”的产品数据表链接为:“XBO 1000 W/HS OF”。
这里有各种各样的文本,比如产品名称、描述、应用领域和好处,应有尽有。
此外,还有各种各样的规范:
别忘了在多行表格中列出的规格:
总而言之,我们已经准备好了一个坚实的基础,来迎接我们的由AI驱动的任务!
使用Python、LangChain和GPT分析文档我现在通常会做的就是启动Azure AI文档智能服务的实例,用少量PDF来训练模型,以检测内容为目的,让模型能够检测PDF内容。
不过这次我选择了另一条路。
我们将用Python和LangChain的来读取和分析PDF文件。我用的是Python 3.11。
首先,我们需要搭建环境,安装并引入所需的库:
在命令行中运行以下命令来安装所需的库:
%pip install pypdf
%pip install langchain
%pip install langchain_openai
%pip install sqlite3
这些命令将分别安装 pypdf
、langchain
、langchain_openai
和 sqlite3
库,帮助你完成相关任务。
从 pypdf 导入 PdfReader
从 langchain_core.messages 导入 HumanMessage, SystemMessage
从 langchain_openai 导入 ChatOpenAI
现在,让我们开始解析PDF文件。我们的目标是提取有价值的内容,忽略空行、页眉和页脚等不太重要的信息,使用_visitortext(文本访问者):
document_content = None # 初始化document_content为None
def visitor_body(text, cm, tm, fontDict, fontSize): # 定义visitor_body函数来处理文本和位置信息
y = tm[5] # 获取y坐标
if text and 35 < y < 770: # 如果text不为空且y坐标在35到770之间
page_contents.append(text) # 将文本添加到page_contents列表中
with open(f'./documents/ZMP_55852_XBO_1000_W_HS_OFR.pdf', 'rb') as file: # 打开PDF文件
pdf_reader = PdfReader(file) # 初始化PdfReader对象
page_contents = [] # 创建一个空列表来存储每页的内容
for page in pdf_reader.pages: # 遍历PDF中的每一页
page.extract_text(visitor_text=visitor_body) # 将每页的内容提取出来
document_content = "\n".join(page_contents) # 将所有页面的内容连接成一个字符串
print(document_content) # 打印文档内容
我们来看看解析出来的文档吧:
产品系列优势
_
短弧高亮度,提供更明亮的屏幕照明
_
整灯寿命期间色温恒定为6,000 K
_
易于维护
_
电弧稳定性高
_
热重启功能使屏幕瞬间点亮
_
宽调光范围
产品系列特性
_
色温:约6,000 K(日光)
_
功率:450…10,000 W
_
色温显色指数优良:Ra > 90
产品数据表
XBO 1000 W/HS OFR
适用于电影放映的XBO短弧灯,功率范围:450…10,000 W
[...]
包装单位
(件/个)
尺寸(长 x 宽 x 高)
体积(立方分米)
毛重(克)
4008321082114
XBO 1000 W/HS OFR
运输纸箱
1
410 mm x 184 mm x
180 mm
13.58 dm³
819.00 g
[...]
在审查解析内容之后,可以看出,内容缺乏一定的结构——表格之间缺乏联系,相关实体分散在各处。
这里我们请GPT来帮忙建立秩序:
- 我们将告诉GPT将解析后的数据格式化成结构化的JSON对象。
- 通过提供一个解析数据的示例,并加上以
<<<
为前缀的战略提示,我们可以引导GPT理解和构建文档结构。 - 使用OpenAI的聊天API,我们将让GPT从一组新的解析产品数据生成一个JSON对象。
让我们构建一个有洞察力的系统消息来启动这个过程。我们首先给GPT一个清晰的指令,接着提供解析的数据作为上下文,并穿插有针对性的提示来优化输出。
请大家仔细观察我们如何结合各种线索来形成所需的精准JSON输出:
你分析产品描述,将其导出为JSON格式。我会提供一个产品数据表,并描述各个JSON对象和属性,用`<<<`标记。然后你根据另一个产品数据表创建一个JSON对象。
>>> 示例产品:
产品优势 <<< benefits (string[])
_
短弧高亮度,屏幕照明更亮 <<< benefits.[*]
_
整个灯泡寿命内色温恒定为6,000 K <<< benefits.[*]
_
广泛的调光范围 <<< benefits.[*]
产品特性 <<< product_family (object)
_
色温:约6,000 K(日光) <<< product_family.temperature = 6000
_
功率范围:450…10,000 W <<< product_family.watts_min = 450, product_family.watts_max = 10000
_
高显色指数:Ra >90
产品数据表
XBO 1000 W/HS OFR <<< name
适用于电影放映的XBO | 氙气短弧灯450…10,000 W <<< description
[..]
技术数据
电气特性 <<< technical_data (object)
额定电流
50 A <<< technical_data.nominal_current = 50.00
电流控制范围
30…55 A <<< technical_data.control_range = 30, technical_data.control_range = 55
额定功率
1000.00 W <<< technical_data.nominal_wattage = 1000.00
额定电压
19.0伏特 <<< technical_data.nominal_voltage = 19.0
尺寸及重量 <<< dimensions (object)
[..]
安全使用说明
识别候选名单物质 <<< environmental_information.safe_use (开始字符串)
足够保证产品的安全使用。 <<< environmental_information.safe_use (结束字符串)
SCIP数据库声明编号
22b5c075-11fc-41b0-ad60-dec034d8f30c <<< environmental_information.scip_declaration_number (单个字符串!)
国家特定信息
[..]
包装尺寸
1
410 mm x 184 mm x <<< packaging_unity.length = 410, packaging_unit.width = 184
180 mm <<< packaging_unit.height = 180
[..]
"""
我的建议是一堆不同的方法:
- < << benefits (字符串[]) — 这里开始是一个字符串列表。
- *< << benefits.[]** — 这是字符串列表中的一行。
- < << product_family (对象) — 这里开始定义一个对象。
- < << product_family.temperature = 6000 — 这行是对象的一个整数属性,表示温度为6000。
- < << product_family.watts_min = 450, product_family.watts_max = 1000 — 这行定义了两个整数属性,分别是最小功率450瓦和最大功率1000瓦。
- 以此类推。
在这里你可以尽情发挥创意,尝试任何你觉得合适的事情。还有其他一些例子,如:
- 翻译成另一种语言。
- 提供摘要或完整的句子(如项目符号点所示)。
- 拆分数据,比如名字和姓氏。
- 等等。
好了,轮到GPT上场了,看看它能不能把我们乱七八糟的PDF文本转换成整齐的JSON对象。
我用GPT-3.5-Turbo 0125而不是更强的GPT-4,因为Azure的Document Intelligence太贵了,我得找一个既省钱又能完成任务的替代方案。
撰写本文之际,GPT-3.5-Turbo的价格已经相当诱人了。
- 每1K输入的令牌费用为0.0005美元
- 每1K输出的令牌费用为0.0015美元
更重要的是,GPT-3.5-Turbo 版本 0125 在回复 JSON 等格式时准确率更高,这对于我们的情况来说非常完美!更棒的是它的上下文窗口达到了 16,385 个令牌数。
我们已经准备好了系统消息内容,并将它与文档内容信息配对作为输入。
chat = ChatOpenAI(model_name='gpt-3.5-turbo-0125', temperature=0)
# 将文档内容转换为JSON格式
def convert_to_json(document_content):
messages = [
SystemMessage(
content=system_message
),
HumanMessage(
content=document_content
)
]
answer = chat.invoke(messages)
return answer.content
json = convert_to_json(document_content)
print(json)
好的,来看看我们得到的JSON输出吧,快速检查一下。
{
"name": "XBO 1000 W/HS OFR",
"description": "XBO 用于电影放映 | 氙气短弧灯 450…10,000 W",
"applications": [
"经典35毫米胶片放映",
"数字电影和视频投影",
"建筑和效果照明(‘光束效果’)",
"模拟自然阳光"
],
"technical_data": {
"nominal_current": 50.00,
"control_range_min": 30,
"control_range_max": 55,
"nominal_wattage": 1000.00,
"nominal_voltage": 19.0
},
"dimensions": {
"diameter": 40.0,
"length": 235.0,
"length_base": 205.00,
"light_center_length": 95.0,
"electrode_gap": 3.6,
"weight": 255.00
},
"operating_conditions": {
"max_temp": 230,
"lifespan": 2000,
"service_lifetime": 3000
},
"additional_data": {
"base_anode": "SFa27-11",
"base_cathode": "SFcX27-8",
"product_remark": "OFR = 无臭氧版本 / H = 适合水平点燃位置 / S = 短型"
},
"capabilities": {
"cooling": "强制风冷",
"burning_position": "S20/P20"
},
"environmental_information": {
"declaration_date": "10-03-2023",
"primary_product_number": "4008321082114 | 4050300933566",
"candidate_list_substance": "铅",
"cas_number": "7439-92-1",
"safe_use": "候选清单物质的标识已足够确保产品的安全使用。",
"scip_declaration_number": "SCIP 声明编号"
},
"logistical_data": {
"product_code": "4008321082114",
"product_name": "XBO 1000 W/HS OFR",
"packaging_unit": {
"product_code": "4008321082114",
"product_name": "XBO 1000 W/HS OFR",
"length": 410,
"width": 184,
"height": 180,
"volume": 13.58,
"weight": 819.00
}
}
}
不得不说,这真的很不错,它找到的对象和属性都非常准确。
然而,有一个明显的遗漏:GPT却忽略了几个关键要素,例如_benefits_和_productfamily\,这两个要素却完全不见踪影。
那么,我们的方案是什么?是转向GPT-4版本,它提供了增强的能力,但成本更高且无疑更慢,还是调整策略以结合函数调用来优化资源配置同时保持效率?
当然,我们选第二个!
通过提供 JSON 格式来优化提示内容函数调用是我使用GPT时最喜欢的功能之一。它不仅让你能够指定GPT可以执行的函数,还能指定你需要的JSON格式。
看看下面这个例子:
"function": {
"name": "get_current_weather",
"description": "获取某个位置的当前天气",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "城市及州,例如:San Francisco, CA",
},
"unit": {"type": "string", "enum": ["℃", "℉"]},
},
"required": ["location"],
},
},
最新推出的模型 gpt-3.5-turbo 和 gpt-4-turbo-preview ,被训练得在适当时候启动函数调用,并能更准确地输出符合指定签名(function signature)的JSON。
为了利用这一点,我们将提示进行优化,使其包含我们期望得到的JSON格式。
你需要分析产品描述,并将其转换成JSON格式。我会用 <<< 和 >>> 来描述各个JSON对象和属性。我会提供一份产品数据表给你,然后你根据另一份产品数据表创建一个JSON对象。
>>> 示例产品如下:
产品系列优势 <<< 优势(字符串数组)
[...]
-----
请按照以下模式提供你的JSON内容:
{
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"applications": {
"type": "array",
"items": {
"type": "string"
}
},
"benefits": {
"type": "array",
"items": {
"type": "string"
}
},
"product_family": {
"type": "object",
"properties": {
"temperature": {
"type": "number"
},
"watts_min": {
"type": "number"
},
"watts_max": {
"type": "number"
}
}
},
"technical_data": {
"type": "object",
"properties": {
"nominal_current": {
"type": "number"
},
"control_range_min": {
"type": "number"
},
"control_range_max": {
"type": "number"
},
"nominal_wattage": {
"type": "number"
},
"nominal_voltage": {
"type": "number"
}
}
},
"dimensions": {
"type": "object",
"properties": {
"diameter": {
"type": "number"
},
"length": {
"type": "number"
},
"length_base": {
"type": "number"
},
"light_center_length": {
"type": "number"
},
"electrode_gap": {
"type": "number"
},
"weight": {
"type": "number"
}
}
},
"operating_conditions": {
"type": "object",
"properties": {
"max_temp": {
"type": "string"
},
"lifespan": {
"type": "number"
},
"service_lifetime": {
"type": "number"
}
}
},
"logistical_data": {
"type": "object",
"properties": {
"product_code": {
"type": "string"
},
"product_name": {
"type": "string"
},
"packaging_unit": {
"type": "object",
"properties": {
"product_code": {
"type": "string"
},
"product_name": {
"type": "string"
},
"length": {
"type": "number"
},
"width": {
"type": "number"
},
"height": {
"type": "number"
},
"volume": {
"type": "number"
},
"weight": {
"type": "number"
}
}
}
}
}
}
}
调整了我们的方法之后,我们来看看新的输出。
{
"name": "XBO 1000 W/HS OFR",
"description": "XBO 适用于电影放映 | Xenon 短弧灯 450…10,000 瓦",
"applications": [
"经典35毫米电影胶片放映",
"数字电影和视频放映",
"建筑和效果照明('光笔')",
"模拟自然阳光"
],
"benefits": [
"短弧光源,亮度极高,屏幕更加明亮",
"在整个灯泡寿命期间,色温保持恒定(6000 K)",
"易于维护",
"高电弧稳定性",
"热重启功能实现瞬间屏幕点亮",
"广泛的调光范围"
],
"product_family": {
"temperature": 6000,
"watts_min": 450,
"watts_max": 10000
},
"technical_data": {
"nominal_current": 50,
"control_range_min": 30,
"control_range_max": 55,
"nominal_wattage": 1000.00,
"nominal_voltage": 19.0
},
"dimensions": {
"diameter": 40.0,
"length": 235.0,
"length_base": 205.00,
"light_center_length": 95.0,
"electrode_gap": 3.6,
"weight": 255.00
},
"operating_conditions": {
"max_temp": "230 °C",
"lifespan": 2000,
"service_lifetime": 3000
},
"logistical_data": {
"product_code": "4008321082114",
"product_name": "XBO 1000 W/HS OFR",
"packaging_unit": {
"product_code": "4008321082114",
"product_name": "XBO 1000 W/HS OFR",
"length": 410,
"width": 184,
"height": 180,
"volume": 13.58,
"weight": 819.00
}
}
}
那不是美得很嘛!
输出详细地列出了名称和描述,将好处和用途分类成字符串清单,并确定了最小值和最大值的范围——这完全符合我们对JSON模式的期待!
从成本上来说,我们得到了一个很好的交易:大约3000个输入的token和500个输出的token加起来大约是0.22美分。这个价格意味着我们只需花一美分就能分析几乎五个完整的PDF文档——真是划算极了,特别是考虑到这些模型随着时间推移不断降价(和效率提升)这一点。
我没有计算在开发过程中解析了多少份文档,但大致是几十甚至上百份,而我的总费用不到1美元。
探索LangChain问答的潜力尽管本文的主要焦点更侧重于PDF分析和SQL代理,但如果你想先试一试,可以试试LangChain Q&A with RAG,因为它与我们的总体目标非常吻合。
我直接给出一些代码及其输出,不做过多解释,这样我们就可以尽快进入重点内容了,随后。
我们先收集一些更多的产品数据。从Osram下载一些额外的PDF文件并把它们存放在指定的_documents_文件夹中。
然后,我们需要加入一些额外的类:
导入 os
# 从以下模块导入所需功能
from langchain.chains.question_answering 导入 load_qa_chain
from langchain_openai 导入 OpenAIEmbeddings
from langchain.vectorstores 导入 FAISS
下一步是读取并处理转换所有下载的PDF文件,然后将它们的JSON输出结果聚合到一个数组中。
# 获取当前目录下所有的PDF文件
pdf_files = [f for f in os.listdir('./documents') if f.endswith('.pdf')]
# 创建一个空列表用于存储转换后的JSON文档
json_documents = []
# 遍历所有PDF文件
for pdf_file in pdf_files:
# 以二进制读取模式打开文件
with open(f'./documents/{pdf_file}', 'rb') as file:
pdf_reader = PdfReader(file)
# 存储每页内容的列表
page_contents = []
# 遍历PDF中的每一页
for page in pdf_reader.pages:
# 提取页面文本
page.extract_text(visitor_text=visitor_body)
# 将提取的文本转换为JSON格式
json = convert_to_json("\n".join(page_contents))
# 将转换后的JSON文档添加到列表中
json_documents.append(json)
现在,我们开始使用OpenAI的文本嵌入模型(又称text-embedding-3-large),告别旧的_ada-002_版本。这个模型每1000个token只需$0.00002。所以无论导入哪个文档都无需担心费用。
此外,我们将使用FAISS Similarity Search库,使其与我们的文档内容和嵌入模型相适应,以便更好地将内容转换为向量。
FAISS 是 Meta 在 2017 年开发的(链接:https://engineering.fb.com/2017/03/29/data-infrastructure/faiss-a-library-for-efficient-similarity-search/),它是 Azure AI 搜索在 Azure 机器学习中的开源替代品,在比较嵌入向量方面,它的表现相当不错。真是个好选择!
embeddings = OpenAIEmbeddings(model="text-embedding-3-large") # 嵌入模型,用于生成文本嵌入向量
docsearch = FAISS.from_texts(文本, embeddings)
打好基础后,我们开始互动环节,就是提问了。
chain = load_qa_chain(chat, chain_type="stuff", verbose=True)
query = "这个XBO 1000 W/HS OFR能放进一个长350毫米、宽200毫米的盒子里吗?"
docs = docsearch.similarity_search(query)
chain.run(input_documents=docs, question=query)
看看这个基于最相关文档内容的回复:
‘实际上,XBO 1000 W/HS OFR 的尺寸是长 410 毫米,宽 184 毫米,比这个盒子的尺寸(长 350 毫米,宽 200 毫米)更大。’
嗯,差不多对。这是PDF文件。
它做得不错。现在让我们来看看更高级的应用。
SQLight 和 LangChain SQL 工具RAG 是一种建立的技术,让用户可以和他们自己的数据对话。它在需要非结构化内部信息的场景中特别强大。
虽然RAG在处理非结构化信息方面表现很棒,当我们需要更具体的数据时,应该怎样提问呢?比方说,给我所有功率至少4000瓦特的产品。再比如,我们怎样知道装所有产品所需的货箱尺寸呢?
说到以数据为中心的查询,SQL的精确度和结构就变得很重要。
我们可以在SQLite数据库中管理我们的数据为了管理我们的数据,需要将数据系统化地存储在一个数据库中。直观的步骤是将数据组织成SQLite中的表格,这样我们就能执行更复杂的查询以支持这些查询。
虽然这并不是强制性的,但我建议额外增加一个预备步骤:定义与我们JSON输出结构一致的类。
通过在数据插入数据库之前整合这些类,我们不仅简化了数据验证的过程,还确保数据符合预期格式。如果解析出来的 JSON 字典缺少未标记为可选的必要属性,则可能出问题了。
import json
# 导入json模块,从typing模块导入Any、List、Optional类型,从dataclasses模块导入dataclass和field
from typing import Any, List, Optional
from dataclasses import dataclass, field
@dataclass
class ProductFamily:
watts_min: int
watts_max: int
temperature: Optional[int] = field(default=0)
@staticmethod
def from_dict(obj: Any) -> 'ProductFamily':
_watts_min = int(obj.get("watts_min"))
_watts_max = int(obj.get("watts_max"))
_temperature = obj.get("temperature")
return ProductFamily(_watts_min, _watts_max, _temperature) # Any类型对象转换为ProductFamily
# 省略部分
@dataclass
class Product:
name: str
description: str
benefits: List[str]
product_family: ProductFamily
@staticmethod
def from_dict(obj: Any) -> 'Product':
_name = str(obj.get("name"))
_description = str(obj.get("description"))
_benefits = obj.get("benefits")
_product_family = ProductFamily.from_dict(obj.get("product_family")) # 基于产品家族对象创建ProductFamily实例
return Product(_name, _description, _benefits, _product_family)
建立这些类之后,我们再次查看我们的PDF文档。这次我们把它们转成JSON格式,并创建一个个_Product_对象。此外,我会把处理完的文档都挪到已处理的文件夹里。
import traceback
pdf_files = [f for f in os.listdir('./documents') if f.endswith('.pdf')]
products = []
for pdf_file in pdf_files:
json_content = None
try:
with open(f'./documents/{pdf_file}', 'rb') as file:
pdf_reader = PdfReader(file)
page_contents = []
for page in pdf_reader.pages:
page.extract_text(visitor_text=visitor_body)
文档内容 = "\n".join(page_contents)
json_content = convert_to_json(文档内容)
json_data = json.loads(json_content)
产品 = Product.from_dict(json_data)
products.append(产品)
except Exception as e:
print("{filename} 出现问题: {e}".format(filename=pdf_file, e=e))
print(traceback.format_exc())
print(json_content)
else:
os.rename(f'./documents/{pdf_file}', f'./processed/{pdf_file}')
太好了,我们现在准备好了强大的产品实例列表,准备插入到数据库表中。
对于我们这个演示来说,一个 SQLite 数据库就足够了,我们将用它来创建三个表来存储我们数据集中的信息:
- 产品信息 — 产品的基本规格(名称、描述信息、长度)。
- 特定产品的应用列表 — 特定产品的应用列表。
- 特定产品的优势列表 — 特定产品的益处列表。
我们将按照以下步骤来操作。
- 初始化数据库并建立表。
- 为每个表创建包含相关产品信息的记录。
- 执行数据插入操作。
让我们开始创建数据库和表吧。
import sqlite3
if not os.path.exists('./db'):
os.makedirs('./db')
db_file = './db/products.db'
db_connection = sqlite3.connect(db_file)
db_cursor = db_connection.cursor()
db_cursor.execute('''CREATE TABLE IF NOT EXISTS Product
(name TEXT PRIMARY KEY,
description TEXT,
temperature INTEGER,
watts_min INTEGER,
watts_max INTEGER,
dimension_diameter REAL,
dimension_length REAL,
dimension_weight REAL,
packaging_length INTEGER,
packaging_width INTEGER,
packaging_height INTEGER,
packaging_weight REAL) WITHOUT ROWID
''')
db_cursor.execute('''
CREATE TABLE IF NOT EXISTS ProductApplication (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product TEXT,
text TEXT NOT NULL,
FOREIGN KEY (product) REFERENCES Product(name)
)
''')
db_cursor.execute('''
CREATE TABLE IF NOT EXISTS ProductBenefit (
id INTEGER PRIMARY KEY AUTOINCREMENT,
product TEXT,
text TEXT NOT NULL,
FOREIGN KEY (product) REFERENCES Product(name)
)
''')
db_connection.commit()
接下来创建数据对:
products_sql_tuples = [
(
p.name,
p.description,
p.product_family.温度,
p.product_family.最小功率,
p.product_family.最大功率,
p.dimensions.直径,
p.dimensions.长度,
p.dimensions.重量,
p.logistical_data.packaging_unit.长度,
p.logistical_data.packaging_unit.宽度,
p.logistical_data.packaging_unit.高度,
p.logistical_data.packaging_unit.重量,
) for p in products
]
applications_sql_tuples = []
for product in products:
applications_sql_tuples.extend([
(product.name, application) for application in product.applications
])
benefits_sql_tuples = []
for product in products:
benefits_sql_tuples.extend([
(product.name, benefit) for benefit in product.benefits
])
最后输入数据:
db_cursor.executemany('''
REPLACE INTO Product (名称(名称), 描述(描述), 温度, 最小功率, 最大功率, 产品直径, 产品长度, 产品重量, 产品包装长度, 产品包装宽度, 产品包装高度, 产品包装重量)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', products_sql_tuple_list)
db_cursor.executemany('''
REPLACE INTO ProductApplication (产品, 文本)
VALUES (?, ?)
''', applications_sql_tuple_list)
db_cursor.executemany('''
REPLACE INTO ProductBenefit (产品, 文本)
VALUES (?, ?)
''', benefits_sql_tuple_list)
db_connection.commit()
别忘了关闭光标和连接。
db_cursor.close() # 关闭数据库游标
db_connection.close() # 关闭数据库连接
我们看看数据库里的表。一些字段是NULL,只是因为在产品数据表中没有这些信息。所以没问题。
但是,所有关键的信息都成功解析并转换了,所以我对这个结果真的很满意!
使用LangChain SQL代理: 查询相关信息我们现在来到了一个实至名归的部分:向这个LangChain SQL Agent提问问题。
LangChain 的 SQL Agent 提供了一种与 SQL 数据库进行动态交互的方式。它擅长解析表结构,并根据用户提示生成 SQL 查询,这真是令人印象深刻。
使用SQL代理的主要优点有:
- 它可以基于数据库的结构和内容(比如描述特定的表)来回答问题。
- 它可以通过运行生成的查询、捕获错误并重新生成正确的查询来从错误中恢复。
- 它可以根据需要多次查询数据库来回答用户的问题。
- 它会通过仅从相关表格中获取结构信息来节省令牌。
正如你对LangChain所期望的那样,它的代码非常简洁明了,即使在后台执行复杂的操作也是如此。
为了使用SQL Agent进行查询,这次我想利用GPT-4来帮忙。与处理大量文档所需的复杂操作相比,数据量相对较小,可以轻松处理。这就是为什么在这里我可以稍微多用一点资源。
从 langchain_community.utilities 导入 SQLDatabase as SQLDatabase
从 langchain.chains 导入 create_sql_query_chain as create_sql_query_chain
从 langchain_openai 导入 ChatOpenAI as ChatOpenAI
from langchain_community.agent_toolkits import create_sql_agent # 从langchain_community导入create_sql_agent函数
db = SQLDatabase.from_uri("sqlite:///db/products.db") # 初始化一个SQL数据库链接
# 创建一个ChatOpenAI对象,设置模型为"gpt-4-0125-preview",温度设置为0,这里的ChatOpenAI是用于对话的AI模型
llm = ChatOpenAI(model="gpt-4-0125-preview", temperature=0)
# 初始化一个SQL执行器,使用create_sql_agent函数,设置agent类型为"openai-tools",verbose设置为True,以提供详细日志信息
agent_executor = create_sql_agent(llm, db=db, agent_type="openai-tools", verbose=True)
一切都准备好了,是时候问一些问题了。我们先从一个简单的问题开始,比如我们现在有多少种产品。
prompt = "你有多少个产品?"
result = agent_executor.invoke({"input": prompt}) # 技术函数名,无需翻译
它给出了一个完全正确的回答,包括这条SQL语句,
调用
sql_db_query
,参数为SELECT COUNT(*) AS NumberOfProducts FROM Product
,返回值为 [(20,)]数据库中有20个产品
虽然看起来很简单,但SQL Agent不仅能生成SQL查询,还能识别已存在的表,这真的非常酷。
现在,我们来增加点难度。我想找到一个适合所有产品的包装尺寸。
prompt = "我需要找到一个适合所有产品的包装尺寸。这样的包装尺寸会是多少?"
result = agent_executor.invoke({"input": prompt})
结果正好:
为了适应所有产品的包装需求,包装尺寸需要如下规格:
长度:605毫米 / 宽度:420毫米 / 高度:900毫米
重约7177克
这个尺寸适合数据库中所有产品的包装需求。
这是SQL Agent设计的查询语句,用来获取所需信息:
使用 sql_db_query
如下:SELECT MAX(packaging_length) AS max_length, MAX(packaging_width) AS max_width, MAX(packaging_height) AS max_height, MAX(packaging_weight) AS max_weight FROM Product
.
好的,我们要更多内容!让我们要求温度最高的可能的产品信息及其应用情况——顺便说一下,这些信息存储在另一个表中。
prompt = "提供最高温度的产品及其应用。"
result = agent_executor.invoke({"input": "提供最高温度的产品及其应用。"})
而且答案正确啊。
SharXS 1500W Brilliant 是一款先进的金属卤素灯,双端结构,旨在提供卓越的照明性能。
其最大温度可达6200°C,非常适合需要高强度和可靠照明的场合。
这款灯特别适合音乐会和特效照明,其中鲜艳且动态的照明至关重要。
绝对,“SharXS 1500W 亮”肯定是这个温度最高的产品:
而且,SQL Agent 更是能够找到 ProductApplication 表中的引用信息。
结论部分像往常一样,经过调整并利用过去几年在大型语言模型方面的经验,我对这些经过调整后的技术之间的无缝配合印象深刻。
此外,GPT版本0125也运行得很好,能提供JSON格式的数据也毫无问题。很明显,OpenAI致力于提高功能调用的能力,并确保其符合创建计算机可读格式的标准场景。
我会把SQL Agent交给一个企业级生产数据库吗?只有在它确实是为了这种特定情况设计的情况下,并且仅限内部员工使用!允许一个语言模型自主执行查询在我看来这仍然显得过于冒险。
不过,对于公开场合,我愿意探索更安全的替代方案,例如发送GraphQL请求。
嗯,也许下次吧……
我希望这次的经历对你来说同样引人入胜,就像它对我一样启发人心。
玩得开心,下次再聊。