如何使用具有函数调用的 GPT-4o Vision
GPT-4o(截至 2024 年 11 月,可用版本为 gpt-4o-2024-11-20)现在支持函数调用,并具备视觉能力、更强的推理能力,知识截止日期为 2023 年 10 月。将图像与函数调用结合使用将解锁多模态用例和推理能力,使您能够超越 OCR 和图像描述。
我们将通过两个示例来演示如何将函数调用与 GPT-4o Vision 结合使用:
- 模拟客户服务助手,提供送货异常支持
- 分析组织结构图以提取员工信息
安装和设置
!pip install pymupdf --quiet
!pip install openai --quiet
!pip install matplotlib --quiet
# Instructor 可以轻松处理函数调用
!pip install instructor --quiet
import base64
import os
from enum import Enum
from io import BytesIO
from typing import Iterable
from typing import List
from typing import Literal, Optional
import fitz
# Instructor 由 Pydantic 提供支持,而 Pydantic 由类型提示提供支持。模式验证、提示由类型注解控制
import instructor
import matplotlib.pyplot as plt
import pandas as pd
from IPython.display import display
from PIL import Image
from openai import OpenAI
from pydantic import BaseModel, Field
Matplotlib 正在构建字体缓存;这可能需要一些时间。
1. 模拟客户服务助手,提供送货异常支持
我们将模拟一个送货服务的客户服务助手,该助手能够分析包裹的图像。助手将根据图像分析执行以下操作:
- 如果包裹在图像中显示损坏,则根据政策自动处理退款。
- 如果包裹看起来湿了,则启动更换。
- 如果包裹看起来正常且未损坏,则将其升级给代理。
让我们看一下客户服务助手将要分析的包裹样本图像,以确定适当的操作。我们将把图像编码为 base64 字符串以供模型处理。
# 将图像编码为 base64 的函数
def encode_image(image_path: str):
# 检查图像是否存在
if not os.path.exists(image_path):
raise FileNotFoundError(f"Image file not found: {image_path}")
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# 用于测试的样本图像
image_dir = "images"
# 对目录中的所有图像进行编码
image_files = os.listdir(image_dir)
image_data = {}
for image_file in image_files:
image_path = os.path.join(image_dir, image_file)
# 使用图像文件名作为键对图像进行编码
image_data[image_file.split('.')[0]] = encode_image(image_path)
print(f"Encoded image: {image_file}")
Encoded image: wet_package.jpg
Encoded image: damaged_package.jpg
Encoded image: normal_package.jpg
def display_images(image_data: dict):
fig, axs = plt.subplots(1, 3, figsize=(18, 6))
for i, (key, value) in enumerate(image_data.items()):
img = Image.open(BytesIO(base64.b64decode(value)))
ax = axs[i]
ax.imshow(img)
ax.axis("off")
ax.set_title(key)
plt.tight_layout()
plt.show()
display_images(image_data)
我们已成功将样本图像编码为 base64 字符串并显示它们。客户服务助手将分析这些图像,以根据包裹状况确定适当的操作。
现在,让我们定义用于订单处理的函数/工具,例如将订单升级给代理、退款和更换订单。我们将创建占位符函数来模拟基于已识别工具的操作处理。我们将使用 Pydantic 模型来定义订单操作的数据结构。
MODEL = "gpt-4o-2024-11-20"
class Order(BaseModel):
"""代表一个订单,包含订单 ID、客户姓名、产品名称、价格、状态和送货日期等详细信息。"""
order_id: str = Field(..., description="订单的唯一标识符")
product_name: str = Field(..., description="产品的名称")
price: float = Field(..., description="产品的价格")
status: str = Field(..., description="订单的状态")
delivery_date: str = Field(..., description="订单的送货日期")
# 订单处理的占位符函数
def get_order_details(order_id):
# 根据订单 ID 检索订单详细信息的占位符函数
return Order(
order_id=order_id,
product_name="Product X",
price=100.0,
status="Delivered",
delivery_date="2024-04-10",
)
def escalate_to_agent(order: Order, message: str):
# 将订单升级给人工代理的占位符函数
return f"Order {order.order_id} has been escalated to an agent with message: `{message}`"
def refund_order(order: Order):
# 处理订单退款的占位符函数
return f"Order {order.order_id} has been refunded successfully."
def replace_order(order: Order):
# 更换订单的占位符函数
return f"Order {order.order_id} has been replaced with a new order."
class FunctionCallBase(BaseModel):
rationale: Optional[str] = Field(..., description="采取行动的原因。")
image_description: Optional[str] = Field(
..., description="包裹图像的详细描述。"
)
action: Literal["escalate_to_agent", "replace_order", "refund_order"]
message: Optional[str] = Field(
...,
description="如果操作是 escalate_to_agent,则要升级给代理的消息",
)
# 用于根据订单 ID 处理操作的占位符函数
def __call__(self, order_id):
order: Order = get_order_details(order_id=order_id)
if self.action == "escalate_to_agent":
return escalate_to_agent(order, self.message)
if self.action == "replace_order":
return replace_order(order)
if self.action == "refund_order":
return refund_order(order)
class EscalateToAgent(FunctionCallBase):
"""升级给代理以获得进一步帮助。"""
pass
class OrderActionBase(FunctionCallBase):
pass
class ReplaceOrder(OrderActionBase):
"""用于更换订单的工具调用。"""
pass
class RefundOrder(OrderActionBase):
"""用于退款订单的工具调用。"""
pass
模拟用户消息并处理包裹图像
我们将模拟包含包裹图像的用户消息,并使用 GPT-4o with Vision 模型处理这些图像。模型将根据图像分析和预定义的损坏、潮湿或正常包裹的操作来识别适当的工具调用。然后,我们将根据订单 ID 处理已识别的操作,并显示结果。
# 从响应中提取工具调用
ORDER_ID = "12345" # 用于测试的占位符订单 ID
INSTRUCTION_PROMPT = "您是一家送货服务的客户服务助手,能够分析包裹的图像。如果包裹在图像中显示损坏,则根据政策自动处理退款。如果包裹看起来湿了,则启动更换。如果包裹看起来正常且未损坏,则将其升级给代理。对于任何其他问题或不清楚的图像,请升级给代理。您必须始终使用工具!"
def delivery_exception_support_handler(test_image: str):
payload = {
"model": MODEL,
"response_model": Iterable[RefundOrder | ReplaceOrder | EscalateToAgent],
"tool_choice": "auto", # 根据上下文自动选择工具
"temperature": 0.0, # 减少响应的多样性
"seed": 123, # 设置种子以实现可复现性
}
payload["messages"] = [
{
"role": "user",
"content": INSTRUCTION_PROMPT,
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{image_data[test_image]}"
}
},
],
}
]
function_calls = instructor.from_openai(
OpenAI(), mode=instructor.Mode.PARALLEL_TOOLS
).chat.completions.create(**payload)
for tool in function_calls:
print(f"- Tool call: {tool.action} for provided img: {test_image}")
print(f"- Parameters: {tool}")
print(f">> Action result: {tool(ORDER_ID)}")
return tool
print("正在处理不同包裹图像的送货异常支持...")
print("\n===================== 模拟用户消息 1 =====================")
assert delivery_exception_support_handler("damaged_package").action == "refund_order"
print("\n===================== 模拟用户消息 2 =====================")
assert delivery_exception_support_handler("normal_package").action == "escalate_to_agent"
print("\n===================== 模拟用户消息 3 =====================")
assert delivery_exception_support_handler("wet_package").action == "replace_order"
正在处理不同包裹图像的送货异常支持...
===================== 模拟用户消息 1 =====================
- Tool call: refund_order for provided img: damaged_package
- Parameters: rationale='The package appears damaged as it is visibly crushed and deformed.' image_description='A package that is visibly crushed and deformed, with torn and wrinkled packaging material.' action='refund_order' message=None
>> Action result: Order 12345 has been refunded successfully.
===================== 模拟用户消息 2 =====================
- Tool call: escalate_to_agent for provided img: normal_package
- Parameters: rationale='The package appears normal and undamaged in the image.' image_description='A cardboard box placed on a wooden floor, showing no visible signs of damage or wetness.' action='escalate_to_agent' message='The package appears normal and undamaged. Please review further.'
>> Action result: Order 12345 has been escalated to an agent with message: `The package appears normal and undamaged. Please review further.`
===================== 模拟用户消息 3 =====================
- Tool call: replace_order for provided img: wet_package
- Parameters: rationale='The package appears wet, which may compromise its contents.' image_description="A cardboard box labeled 'Fragile' with visible wet spots on its surface." action='replace_order' message=None
>> Action result: Order 12345 has been replaced with a new order.
2. 分析组织结构图以提取员工信息
对于第二个示例,我们将分析组织结构图图像以提取员工信息,例如员工姓名、角色、经理和经理角色。我们将使用 GPT-4o with Vision 来处理组织结构图图像,并提取有关组织中员工的结构化数据。事实上,函数调用使我们能够超越 OCR,实际推断和翻译结构图中的层级关系。
我们将从一个我们想要分析的样本组织结构图开始,并将 PDF 的第一页转换为 JPEG 图像以供分析。
# 将单个 PDF 页面转换为 JPEG 图像的函数
def convert_pdf_page_to_jpg(pdf_path: str, output_path: str, page_number=0):
if not os.path.exists(pdf_path):
raise FileNotFoundError(f"PDF file not found: {pdf_path}")
doc = fitz.open(pdf_path)
page = doc.load_page(page_number) # 0 是第一页
pix = page.get_pixmap()
# 将 pixmap 保存为 JPEG
pix.save(output_path)
def display_img_local(image_path: str):
img = Image.open(image_path)
display(img)
pdf_path = 'data/org-chart-sample.pdf'
output_path = 'org-chart-sample.jpg'
convert_pdf_page_to_jpg(pdf_path, output_path)
display_img_local(output_path)
组织结构图图像已成功从 PDF 文件中提取并显示。现在,让我们定义一个函数来使用新的 GPT4o with Vision 分析组织结构图图像。该函数将提取有关员工、他们的角色以及他们经理的信息。我们将使用函数/工具调用来指定组织结构的输入参数,例如员工姓名、角色以及经理姓名和角色。我们将使用 Pydantic 模型来定义数据结构。
base64_img = encode_image(output_path)
class RoleEnum(str, Enum):
"""定义组织中可能存在的角色。"""
CEO = "CEO"
CTO = "CTO"
CFO = "CFO"
COO = "COO"
EMPLOYEE = "Employee"
MANAGER = "Manager"
INTERN = "Intern"
OTHER = "Other"
class Employee(BaseModel):
"""代表一名员工,包括他们的姓名、角色以及可选的经理信息。"""
employee_name: str = Field(..., description="员工的姓名")
role: RoleEnum = Field(..., description="员工的角色")
manager_name: Optional[str] = Field(None, description="经理的姓名(如果适用)")
manager_role: Optional[RoleEnum] = Field(None, description="经理的角色(如果适用)")
class EmployeeList(BaseModel):
"""组织结构中的员工列表。"""
employees: List[Employee] = Field(..., description="员工列表")
def parse_orgchart(base64_img: str) -> EmployeeList:
response = instructor.from_openai(OpenAI()).chat.completions.create(
model=MODEL,
response_model=EmployeeList,
messages=[
{
"role": "user",
"content": '分析给定的组织结构图并非常仔细地提取信息。',
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_img}"
}
},
],
}
],
)
return response
现在,我们将定义一个函数来解析 GPT-4o with vision 的响应并提取员工数据。我们将对提取的数据进行制表,以便于可视化。请注意,提取数据的准确性可能会因输入图像的复杂性和清晰度而异。
# 调用函数来分析组织结构图并解析响应
result = parse_orgchart(base64_img)
# 对提取的数据进行制表
df = pd.DataFrame([{
'employee_name': employee.employee_name,
'role': employee.role.value,
'manager_name': employee.manager_name,
'manager_role': employee.manager_role.value if employee.manager_role else None
} for employee in result.employees])
display(df)
employee_name | role | manager_name | manager_role | |
---|---|---|---|---|
0 | Juliana Silva | CEO | None | None |
1 | Kim Chun Hei | CFO | Juliana Silva | CEO |
2 | Cahaya Dewi | Manager | Kim Chun Hei | CFO |
3 | Drew Feig | Employee | Cahaya Dewi | Manager |
4 | Richard Sanchez | Employee | Cahaya Dewi | Manager |
5 | Sacha Dubois | Intern | Cahaya Dewi | Manager |
6 | Chad Gibbons | CTO | Juliana Silva | CEO |
7 | Shawn Garcia | Manager | Chad Gibbons | CTO |
8 | Olivia Wilson | Employee | Shawn Garcia | Manager |
9 | Matt Zhang | Intern | Shawn Garcia | Manager |
10 | Chiaki Sato | COO | Juliana Silva | CEO |
11 | Aaron Loeb | Manager | Chiaki Sato | COO |
12 | Avery Davis | Employee | Aaron Loeb | Manager |
13 | Harper Russo | Employee | Aaron Loeb | Manager |
14 | Taylor Alonso | Intern | Aaron Loeb | Manager |
从组织结构图中提取的数据已成功解析并显示在 DataFrame 中。这种方法使我们能够利用 GPT-4o with Vision 的功能从组织结构图和图表等图像中提取结构化信息,并处理数据以进行进一步分析。通过使用函数调用,我们可以将多模态模型的шы功能扩展到执行特定任务或调用外部函数。