优秀的编程知识分享平台

网站首页 > 技术文章 正文

OpenAI 函数调用功能的一些问题及其处理方法

nanyue 2024-11-22 18:34:36 技术文章 2 ℃

OpenAI 的 ChatGPT API 中的函数调用功能是一个强大的工具,使我们能够执行一些令人印象深刻的任务。如果您不熟悉 ChatGPT API 中的函数调用,您可能需要参考 我之前关于此主题的文章。在那篇文章中,我提供了许多函数调用的示例,以及关于使用类似 ChatGPT 的浏览和代码解释器插件构建基于 Flask 的聊天应用程序的综合教程。

本文是上一篇文章的后续文章,讨论使用 ChatGPT 函数调用 API 进行开发时可能出现的一些意外挑战。这篇文章所涵盖的内容摘要:

  • 让模型生成更准确的函数调用参数
  • 让模型生成函数调用链
  • 处理函数和参数名称的幻觉

让我们开始吧!

本文中显示的代码示例可在GitHub上获取(https://github.com/abhinav-upadhyay/chatgpt_function_call_gotchas/tree/main)。

函数调用快速概述

在讨论潜在问题之前,我们先简要看一下使用 ChatGPT API 的函数调用功能的示例。如果您熟悉函数调用的工作原理,请随意跳到下一节。

假设我们希望模型提供伦敦明天的天气,并且我们传递函数,该函数将位置名称作为参数。示例请求可能如下所示

messages = [{"role": "user",
        "content": "What's the weather like in Boston?"}]
    functions = [
        {
            "name": "get_current_weather",
            "description": "Get the current weather in a given location",
            "parameters": {
                "type": "object",
                "properties": {
                    "location": {
                        "type": "string",
                        "description": "The city and state",
                    }
                },
                "required": ["location"],
            },
        }
    ]

模型的响应可能包含如下函数调用:

{
	"index": 0,
	"message": {
		"role": "assistant",
		"content": None,
		"function_call": {
			"name": "get_current_weather",
			"arguments": "{\n  "location": "London"}"
		}
	},
	"finish_reason": "function_call"
}

为了响应来自模型的此函数调用请求,我们必须发回此函数调用的结果,该结果作为消息的一部分返回。角色应该是“function”,我们还需要提供函数的名称,如下所示:

def get_weather(location: str) -> Dict:
    return {"temperature": 30, "conditions": ["windy", "cloudy"]}

weather_info = get_weather(location)
messages.append(Message(role = "function",
                content = json.dumps(weather_info),
                name = "get_current_weather"))

# send the updated messages back to ChatGPT

现在我们已经看到了在聊天 API 中使用函数调用参数的示例,让我们讨论一下使用此功能时的一些潜在挑战。

将函数描述视为用户提示(User Prompts)

OpenAI 文档指出,传递给 API 的函数列表是提供给 ChatGPT 模型的上下文的一部分。这意味着这些函数的描述应该像提示一样编写,详细告知模型可以并且应该调用该函数的情况。让我们借助一个示例来理解这一点,在该示例中,我们为模型提供了 python 代码解释器函数。

from typing import List, Dict
import openai
import requests
from pprint import pprint

GPT_MODEL = "gpt-3.5-turbo-0613"
SYSTEM_PROMPT = """
    You are a helpful AI assistant. You answer the user's queries.
    When you are not sure of an answer, you take the help of
    functions provided to you.
    NEVER make up an answer if you don't know, just respond
    with "I don't know" when you don't know.
    Ask clarifying questions when you need more information
    """

functions = [
    {
        "name": "python",
        "description": "Executes python code and returns value printed on stdout",
        "parameters": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string",
                    "description": "Python code which needs to be executed"
                }
            }
        }
    }
]

def _chat_completion_request(messages) -> Dict:
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    body = {
        "model": GPT_MODEL,
        "messages": messages,
        "functions": functions,
        "temperature": 0.7
    }

    response = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers=headers,
        json=body,
    )
    return response.json()["choices"][0]["message"]

messages: List[Dict] = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": "what is today's date?"}
]
response = _chat_completion_request(messages)

if (response.get('function_call')):
    pprint(response.get('function_call'))
else:
    pprint(response)

我们硬编码了一条用户消息来询问今天的日期。该函数的描述非常简单:"Executes Python code and returns the value printed on stdout." 本质上,这意味着 ChatGPT 模型应该生成打印其值的 Python 代码,以便该函数返回代码的输出。让我们看看模型是否按照预期遵循说明:

{'arguments': '{\n'
              '"code": "import datetime\\n\\ndate = '
              'datetime.date.today()\\ndate"\n'
              '}',
 'name': 'python'}

正如您所看到的,即使它生成了对我们函数的函数调用,代码也不是预期的格式。它不会打印上日期变量的值,这违背了我们的预期。

解决方案

如前所述,我们需要将描述视为提供给模型的提示指令的一部分,这样它就不会因为做自己的事情而让我们感到惊讶。让我们看看如何修改 python 代码解释器函数的描述。

functions = [
    {
        "name": "python",
        "description": """
                    Read the value printed by the given python code.
                    The code SHOULD explicitly call print so that
                    this function returns the output.
                    
                    For example: "import math; print(math.pi)"
                    is correct

                    But "import math; math.pi" is incorrect because
                    it doesn't print the value            
            """,
        "parameters": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string",
                    "description": "Python code which needs to be executed"
                }
            }
        }
    }
]

这是更详细的描述。让我们运行该程序,看看 ChatGPT 现在是否执行正确的操作。

{'arguments': '{\n  "code": "import datetime; print(datetime.date.today())"\n}',
 'name': 'python'}

这看起来好多了,该值正在按照我们的要求打印。

在函数调用响应中发送类似提示的指令

使用函数调用功能的一般主题是每个元数据元素都应被视为指导提示。如果不这样做,我们就会面临模型错误解释事物的风险。

例如,假设我们有两个函数:一个执行 Web 搜索并返回匹配 URL 的列表,而另一个则抓取给定 URL 处的网页内容。当我们向模型询问有关莱昂纳多·迪卡普里奥现任女友的信息时,我们希望它执行网络搜索,然后调用抓取功能从结果 URL 中提取信息。但是,如果没有额外的提示,模型可能不会按预期进行。让我们在代码中看看:

from typing import List, Dict
import openai
import requests
from pprint import pprint
import json

GPT_MODEL = "gpt-3.5-turbo-0613"
SYSTEM_PROMPT = """
    You are a helpful AI assistant. You answer the user's queries.
    When you are not sure of an answer, you take the help of
    functions provided to you.
    NEVER make up an answer if you don't know, just respond
    with "I don't know" when you don't know.
    Ask clarifying questions when you need more information
    """

functions = [
    {
        "name": "web_search",
        "description": "Does a web search and return list of URLs of top 10 pages",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "user query"
                }
            }
        }
    },
    {
        "name": "web_scraper",
        "description": "Scrapes content at the given URL",
        "parameters": {
            "type": "object",
            "properties": {
                "url": {
                    "type": "string",
                    "description": "URL of the web page to be scraped"
                }
            }
        }
    },
]


def _chat_completion_request(messages) -> Dict:
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    body = {
        "model": GPT_MODEL,
        "messages": messages,
        "functions": functions,
        "temperature": 0.7
    }

    response = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers=headers,
        json=body,
    )
    return response.json()["choices"][0]["message"]


messages: List[Dict] = [
    {"role": "system",
     "content": SYSTEM_PROMPT
    },
    {"role": "user",
     "content": "who is Leonardo Dicaprio's current girlfriend?"
    }
]

response = _chat_completion_request(messages)
if response.get('function_call'):
    func_call = response.get('function_call')
    func_name = func_call.get('name')
    if func_name != 'web_search':
        raise Exception(f'Unsupported function name {func_name}')

    print('Executing web search')
    websearch_response =  {'urls': ['https://hollywood.com/leonardo']}
    messages.append({'role': 'function',
                     'content': json.dumps(websearch_response),
                     'name': 'websearch'})
    next_response = _chat_completion_request(messages)
    pprint(next_response)
else:
    pprint(response)

大部分样板与上一节的示例相同。我用粗体突出显示了更改的部分。以下是对这些变化的解释:

  • 我们修改了功能。我们现在将两个函数传递给 ChatGPT。一个负责执行网络搜索,返回匹配 URL 的列表,另一个负责抓取给定 URL 的网页内容。
  • 我们还更新了用户消息。在此示例中,我们向模型询问有关莱昂纳多·迪卡普里奥现任女友的信息。由于我们正在寻找当前信息,因此我们希望模型能够执行网络搜索。
  • 收到 ChatGPT 的响应后,我们检查它是否触发了函数调用。如果是,我们会生成 URL 形式的假设结果。我们将此添加为另一条消息,角色设置为“function”,并再次调用 ChatGPT。我们期望 ChatGPT 在收到这些 URL 后,将进行另一个函数调用以从这些 URL 中抓取内容。这是获取实际文本并使模型能够找出问题答案所必需的。

让我们运行代码并观察模型是否生成对 scraper 函数的调用。

{'content': "I'm sorry, but I couldn't find any information about Leonardo DiCaprio's current girlfriend.",
 'role': 'assistant'}

嗯,事情并没有像我们预期的那样进行。看起来我们需要提示 ChatGPT 调用 scraper 函数。

解决方案

同样,修复相对简单。我们必须在函数调用响应中添加更多指令,以便 ChatGPT 知道它需要调用另一个函数。我们看一下代码:

messages: List[Dict] = [
    {"role": "system",
     "content": SYSTEM_PROMPT
    },
    {"role": "user",
     "content": "who is Leonardo Dicaprio's current girlfriend?"
    }
]

response = _chat_completion_request(messages)
if response.get('function_call'):
    func_call = response.get('function_call')
    func_name = func_call.get('name')
    if func_name != 'web_search':
        raise Exception(f'Unsupported function name {func_name}')

    websearch_response =  {'urls':
                           ['https://hollywood.com/leonardo'],
                          'message': 'Scrape these URLs to get the text'
                           }
    messages.append({'role': 'function',
                     'content': json.dumps(websearch_response),
                     'name': 'websearch'})
    next_response = _chat_completion_request(messages)
    pprint(next_response)
else:
    pprint(response)

突出显示的部分显示了更改。我们只是在网络搜索功能的结果中添加了一条消息,表示需要抓取 URL。现在运行代码将显示模型现在生成对 scraper 函数的第二个函数调用。

{'content': None,
 'function_call': {
    'arguments': '{\n"url": "https://hollywood.com/leonardo"\n}',
                   'name': 'web_scraper'},
    'role': 'assistant'}

处理函数和参数名称的幻觉

使用 GPT-3.5 版本的聊天模型时,您可能会遇到的一个问题是在生成函数调用时产生函数名称(有时甚至是参数名称)的幻觉。当提供更大的函数列表时,这个问题可能会更加明显。我们将再次使用第一部分中的 Python 代码解释器函数示例。这是完整的代码:

functions = [
        {
        "name": "web_search",
        "description": "Does a web search and return list of URLs of top 10 pages",
        "parameters": {
            "type": "object",
            "properties": {
                "query": {
                    "type": "string",
                    "description": "user query"
                }
            }
        }
    },
    {
        "name": "web_scraper",
        "description": "Scrapes content at the given URL",
        "parameters": {
            "type": "object",
            "properties": {
                "url": {
                    "type": "string",
                    "description": "URL of the web page to be scraped"
                }
            }
        }
    },
    {
        "name": "python_interpreter",
        "description": "Executes python code and returns value printed on stdout",
        "parameters": {
            "type": "object",
            "properties": {
                "code": {
                    "type": "string",
                    "description": "Python code which needs to be executed"
                }
            }
        }
    }
]

def _chat_completion_request(messages) -> Dict:
    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + openai.api_key,
    }
    body = {
        "model": GPT_MODEL,
        "messages": messages,
        "functions": functions,
        "temperature": 0.7
    }

    response = requests.post(
        "https://api.openai.com/v1/chat/completions",
        headers=headers,
        json=body,
    )
    return response.json()["choices"][0]["message"]

messages: List[Dict] = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": "what is today's date?"}
]
response = _chat_completion_request(messages)

if (response.get('function_call')):
    pprint(response.get('function_call'))
else:
    pprint(response)

我已将函数名称从“python”更改为 “python_interpreter”,并在列表中添加了更多函数。当您传递更大的函数列表时,模型似乎更有可能在函数名称中出错。

python运行此代码时,您可能会看到 ChatGPT 有时会生成名为“python”而不是调用python_interpreter。我要尝试一下。

{'arguments': 'import datetime\n\ntoday = datetime.date.today()\ntoday',
 'name': 'python'}

可能需要多次尝试才能实现这一点。然而,在我的其他项目之一中,这种情况几乎总是发生,你的运气可能会有所不同。

您还应该注意到该模型也弄乱了JSON。arguments它直接将 python 代码作为“参数”的值,而不是将其作为参数名称及其值的键值对

"arguments": {"code": "<python code>"}

解决方案

我对这个问题的修复是引导 ChatGPT 使用正确的函数名称重试。有两种可能的方式来提供此指导:

  • 一种选择是向 ChatGPT 发送另一条带有“函数”角色的消息,以及一条要求其使用有效函数名称之一重试的消息。
  • 第二个选项是向 ChatGPT 发送用户消息,我们明确要求它使用有效函数名称之一重试。

我发现虽然第一种技术有效,但它并不可靠,并且模型可能会持续生成不正确的函数名称。然而,第二个选项工作更可靠。这是因为它类似于直接来自用户的指令,并且与函数调用结果中的指令相比,ChatGPT 似乎更能遵循用户指令

messages: List[Dict] = [
    {"role": "system", "content": SYSTEM_PROMPT},
    {"role": "user", "content": "what is today's date"}
]
response = _chat_completion_request(messages)

if (response.get('function_call')):
    func_name = response.get('function_call').get('name')
    if func_name != 'python_interpreter':
        print(f'Invalid function name {func_name}')
        messages.append({'role': 'user',
                         'content': """
                         Retry with one of the available functions:
                           [websearch, web_scraper, python_interpreter]
                         """
                         })
        next_response = _chat_completion_request(messages)
        pprint(next_response)
    else:
        pprint(response.get('function_call'))
else:
    pprint(response)

这是我们改变的:

  • 我们添加了对模型调用的函数名称的检查
  • 如果模型调用我们无法识别的函数,我们会再次调用 ChatGPT,在其中发送一条用户消息,要求其使用有效函数名称之一重试。

瞧,它有效了!

Invalid function name python
{'content': None,
 'function_call': {'arguments': 'import datetime\n'
                                '\n'
                                "# Get today's date\n"
                                'today = datetime.date.today()\n'
                                '\n'
                                'today',
                   'name': 'python_interpreter'},
 'role': 'assistant'}

警惕ChatGPT的训练数据截止

这是一个小问题。由于 ChatGPT 的训练数据截止日期是 2021 年,因此当函数需要当前参数值时,除非明确处理,否则它可能会生成不正确的值或信息。例如,如果您要求它查询过去 3 天的分析数据(该函数需要start_date参数 and end_date),它可能会生成 2021 年或 2022 年的日期。在这种情况下,建议提供一个处理日期的函数或包含提示中的日期。同样,可能还有其他场景,ChatGPT 需要在网络上查询最新信息,并且在没有网络搜索功能的情况下,可能会生成不准确的结果。

结束语

这些是我在使用插件支持的ChatGPT 应用程序遇到的一些问题。可能还有更多我还没有遇到过的极端情况。如果您知道任何问题,请在评论中分享,并说明您的解决方法。

值得注意的是,大多数这些问题都是特定 GPT-3.5而不是GPT-4。然而,由于其具有成本效益的定价,许多用户将继续使用,因此了解如何解决这些问题将很有价值。

原文:https://codeconfessions.substack.com/p/navigating-sharp-edges-in-openais?utm_source=profile&utm_medium=reader2

最近发表
标签列表