概述

OpenAI 函数智能体是一种结合了OpenAI函数调用能力的智能体类型,它允许语言模型(如GPT-3.5和GPT-4)以结构化的方式使用工具。与ReAct智能体通过自由文本格式来推理和使用工具不同,OpenAI函数智能体利用模型内置的函数调用能力,能够生成结构良好的函数调用参数,大大提高了工具使用的准确性和可靠性。

这种智能体特别适合需要结构化输出和工具调用的应用场景,例如API调用、数据查询和结构化任务执行。

工作原理

OpenAI函数智能体的工作原理如下:

  1. 工具声明:将工具定义为函数(包含名称、描述和参数模式)

  2. 模型调用:向OpenAI模型发送用户查询和可用工具信息

  3. 函数选择:模型决定是直接回答还是调用特定函数

  4. 参数生成:如果选择调用函数,模型生成符合函数参数模式的JSON参数

  5. 工具执行:执行被调用的函数并获取结果

  6. 结果处理:将函数执行结果返回给模型进行下一步决策或最终回答生成

OpenAI

优势

  • 结构化输出:确保参数格式正确,减少解析错误

  • 更高的准确性:模型被优化为正确调用函数,减少格式错误

  • 简化提示工程:无需在提示中详细说明工具使用格式

  • 高效工具选择:能够智能选择最适合当前任务的工具

  • 更强的类型安全:函数参数具有严格的类型检查

基本用法示例

以下是创建和使用基本OpenAI函数智能体的完整示例:

from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.tools import DuckDuckGoSearchRun, WikipediaQueryRun, tool
from langchain_community.utilities import WikipediaAPIWrapper
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

# 初始化支持函数调用的LLM
llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)

# 创建标准工具
search_tool = DuckDuckGoSearchRun(name="搜索")
wiki_tool = WikipediaQueryRun(name="维基百科", api_wrapper=WikipediaAPIWrapper())

# 创建自定义工具
@tool
def get_weather(location: str, unit: str = "celsius"):
    """获取指定位置的天气信息"""
    # 在实际应用中,这里应该调用真实的天气API
    return f"{location}天气晴朗,温度25{unit[0]}"

# 组合所有工具
tools = [search_tool, wiki_tool, get_weather]

# 创建提示模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个有帮助的AI助手。使用提供的工具来回答用户问题。"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# 创建OpenAI函数智能体
agent = create_openai_functions_agent(llm=llm, tools=tools, prompt=prompt)

# 创建智能体执行器
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# 执行智能体查询
result = agent_executor.invoke({"input": "北京今天的天气怎么样?之后帮我查一下长城的历史。"})

print(result["output"])

高级用法

自定义工具定义

from typing import List, Dict, Any
from langchain.tools import tool, StructuredTool
from langchain.pydantic_v1 import BaseModel, Field

# 使用装饰器定义简单工具
@tool
def search_database(query: str):
    """在内部数据库中搜索信息"""
    # 实际应用中连接到数据库
    return f"数据库搜索结果:关于'{query}'的信息..."

# 定义带有复杂结构化输入的工具
class BookingDetails(BaseModel):
    location: str = Field(..., description="预订地点,例如'北京'或'上海'")
    check_in_date: str = Field(..., description="入住日期,格式为YYYY-MM-DD")
    check_out_date: str = Field(..., description="离店日期,格式为YYYY-MM-DD")
    guests: int = Field(..., description="客人数量")
    room_type: str = Field(default="标准间", description="房间类型,例如'标准间'、'豪华套房'等")

def book_hotel(details: BookingDetails) -> str:
    """预订酒店房间"""
    # 实际应用中连接到酒店预订系统
    return f"已为您在{details.location}预订了{details.room_type},入住日期:{details.check_in_date},离店日期:{details.check_out_date},{details.guests}位客人。"

# 将函数转换为结构化工具
booking_tool = StructuredTool.from_function(
    func=book_hotel,
    name="预订酒店",
    description="预订酒店房间的工具",
    args_schema=BookingDetails,
    return_direct=False  # 设置为True会直接返回工具结果作为最终回答
)

# 添加到工具列表
advanced_tools = [search_tool, wiki_tool, get_weather, search_database, booking_tool]

# 创建高级OpenAI函数智能体
advanced_agent = create_openai_functions_agent(
    llm=ChatOpenAI(model="gpt-4", temperature=0),
    tools=advanced_tools,
    prompt=prompt
)

advanced_executor = AgentExecutor(agent=advanced_agent, tools=advanced_tools, verbose=True)

处理多轮对话

from langchain.memory import ConversationBufferMemory

# 创建带记忆的提示模板
memory_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个有帮助的AI助手。使用提供的工具来回答用户问题。"),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# 初始化对话记忆
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)

# 创建带记忆的智能体
conversational_agent = create_openai_functions_agent(
    llm=llm,
    tools=tools,
    prompt=memory_prompt
)

# 创建带记忆的执行器
conversational_executor = AgentExecutor(
    agent=conversational_agent,
    tools=tools,
    memory=memory,
    verbose=True
)

# 多轮对话示例
response1 = conversational_executor.invoke({"input": "上海今天的天气怎么样?"})
print(response1["output"])

response2 = conversational_executor.invoke({"input": "那北京呢?"})
print(response2["output"])

response3 = conversational_executor.invoke({"input": "帮我找一些关于长城的信息"})
print(response3["output"])

实际应用场景

1. 个人助理应用

from langchain.tools import tool

@tool
def check_calendar(date: str):
    """查看指定日期的日程安排"""
    # 实际应用中连接到日历API
    return f"{date}的日程:9:00 团队会议,12:00 午餐,14:00 客户拜访"

@tool
def add_event(date: str, time: str, description: str):
    """向日历添加新事件"""
    # 实际应用中连接到日历API
    return f"已添加:{date} {time} - {description}"

@tool
def send_email(to: str, subject: str, body: str):
    """发送电子邮件"""
    # 实际应用中连接到电子邮件API
    return f"已向{to}发送主题为'{subject}'的电子邮件"

# 创建个人助理工具集
assistant_tools = [check_calendar, add_event, send_email, get_weather, search_tool]

# 创建个人助理智能体
personal_assistant = create_openai_functions_agent(
    llm=llm,
    tools=assistant_tools,
    prompt=ChatPromptTemplate.from_messages([
        ("system", "你是一个高效的个人助理。使用提供的工具来帮助用户管理日程和处理任务。"),
        MessagesPlaceholder(variable_name="chat_history"),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ])
)

personal_assistant_executor = AgentExecutor(
    agent=personal_assistant,
    tools=assistant_tools,
    memory=ConversationBufferMemory(memory_key="chat_history", return_messages=True),
    verbose=True
)

2. 数据查询和分析系统

@tool
def query_database(sql: str):
    """使用SQL查询数据库"""
    # 实际应用中连接到数据库
    if "sales" in sql.lower():
        return "销售数据:[{月份: '一月', 销售额: 12000}, {月份: '二月', 销售额: 15000}, ...]"
    elif "customer" in sql.lower():
        return "客户数据:[{ID: 1, 名称: '张三', 等级: 'VIP'}, ...]"
    return "查询结果为空"

@tool
def analyze_data(data_type: str, analysis_type: str):
    """对指定类型的数据执行指定类型的分析"""
    # 实际应用中执行实际数据分析
    if data_type == "sales" and analysis_type == "trend":
        return "销售趋势分析:过去6个月销售额逐月上升,环比增长5%"
    elif data_type == "customer" and analysis_type == "segmentation":
        return "客户细分分析:30%为VIP客户,贡献了60%的收入"
    return f"{data_type}的{analysis_type}分析结果"

# 创建数据分析工具集
data_tools = [query_database, analyze_data]

# 创建数据分析智能体
data_analyst = create_openai_functions_agent(
    llm=llm,
    tools=data_tools,
    prompt=ChatPromptTemplate.from_messages([
        ("system", "你是一个数据分析专家。使用提供的工具来查询和分析数据,提供洞察。"),
        ("human", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ])
)

data_analyst_executor = AgentExecutor(
    agent=data_analyst,
    tools=data_tools,
    verbose=True
)

最佳实践

  1. 提供清晰的工具描述:确保每个工具的描述明确说明其功能和适用场景。

  2. 明确参数要求:为工具参数提供详细的类型提示和描述,帮助模型生成正确格式的参数。

  3. 使用结构化输入:对于复杂输入,使用BaseModel定义结构化的参数模式。

  4. 为重要参数提供示例:在参数描述中包含示例值。

    class SearchParams(BaseModel):
        query: str = Field(..., description="搜索查询,例如'人工智能最新进展'")
        max_results: int = Field(default=5, description="要返回的最大结果数,例如5或10")
    
  5. 考虑参数默认值:为非必需参数设置合理的默认值。

  6. 注意工具命名:使用明确且不重叠的工具名称,避免混淆。

  7. 利用return_direct参数:对于某些工具,可以设置return_direct=True让工具结果直接作为最终答案返回。

  8. 使用强大的模型:对于需要复杂推理的任务,优先使用GPT-4等更强大的模型。

与ReAct智能体的对比

特性

OpenAI函数智能体

ReAct智能体

参数格式化

内置支持,格式一致

依赖提示工程,可能不一致

工具选择

直接支持函数选择

需要通过推理选择工具

性能

通常更高效

可能需要更多步骤

透明度

思考过程较隐蔽

思考过程更明显

可用模型

仅支持特定OpenAI模型

适用于大多数LLM

灵活性

受限于函数调用格式

更加灵活

常见问题与解决方案

  1. 函数参数格式不正确

    • 解决方案:使用更详细的参数描述和类型提示

    • 使用BaseModel明确字段格式要求

  2. 工具选择不当

    • 解决方案:提供更清晰的工具描述

    • 在系统提示中添加工具选择指南

  3. 无法处理复杂任务

    • 解决方案:将复杂任务分解为多个简单工具

    • 使用更强大的模型如GPT-4

  4. 缺少对话上下文

    • 解决方案:使用适当的记忆组件

    • 确保提示模板中包含对话历史占位符

总结

OpenAI函数智能体代表了一种强大且高效的工具使用方法,特别适合需要结构化输入输出的应用场景。通过将工具定义为函数并利用OpenAI模型的函数调用能力,这种智能体可以更可靠地使用工具,减少格式错误,并简化开发者的提示工程工作。虽然它仅限于支持函数调用的特定模型,但在适用的场景中,它通常比传统的ReAct方法提供更高效和可靠的性能。

通过遵循本文档的最佳实践和应用场景建议,开发者可以充分利用OpenAI函数智能体的优势,创建强大的智能应用。