掌握ChatGPT的函数调用API:聪明的方式和...不太聪明的方式(使用Python)

在ChatGPT中调用功能就像是对开发者的一份礼物 - 但这份礼物需要一些“组装”。它具有一些令人印象深刻的功能,但也有一些陷阱等待着捕捉不谨慎的人。

然而,一旦你掌握了它,感觉就不像编码,更像是指挥一个智能代理。事实上,你可以说它正在引领人工智能应用架构的新时代。

简单的方式

以下是要点:在使用函数调用API时,您要告诉ChatGPT哪些功能是可用的。它会回应您,告诉您它需要执行哪个功能,以及参数。然后您给出这些参数,这种愉快的来回持续进行,直到ChatGPT可以提供最终答案。

处理这个问题的简单方法?只需将您想要提供的功能打包,发出API调用,让像pygptcalls这样方便的库来处理来回的事情。毫无压力。

让我们看看它在操作中的表现。想象一下,你希望让ChatGPT具有与你的文件系统交互的能力。你需要提供列出、读取和写入文件的功能。简单,对吧?

这是一个简短的代码片段来实现这一点:


import os
from typing import List

def find_files_with_extension(root_dir: str, extension: str) -> List[str]:
"""
Recursively find files from a root directory with a specific extension.

Args:
root_dir (str): The root directory to search.
extension (str): The file extension to look for (e.g., '.txt').

Returns:
List[str]: A list of file paths matching the given extension.
"""
matched_files = []
for dirpath, _, filenames in os.walk(root_dir):
for filename in filenames:
if filename.endswith(extension):
matched_files.append(os.path.join(dirpath, filename))
return matched_files

def read_file_to_string(file_path: str) -> str:
"""
Read a file from a specified path and return its contents as a string.

Args:
file_path (str): The path to the file to read.

Returns:
str: The contents of the file as a string.
"""
with open(file_path, 'r') as file:
return file.read()

def write_string_to_file(file_path: str, content: str) -> None:
"""
Write a string to a file at a specified path.

Args:
file_path (str): The path to the file to write.
content (str): The string content to write into the file.

Returns:
None
"""
with open(file_path, 'w') as file:
file.write(content)

在这里添加基本文档是至关重要的。为什么?因为这些文档成为了prompt ChatGPT用来理解和执行您的函数的一部分。可以将其看作是为您的代码提供了一个小“抄袭笔记”。

现在,让我们来清理一些Python文件。使用pygptcalls,我们可以漂亮地执行这个操作:

from pygptcalls import gptcall
import file_ops

if __name__ == '__main__':
prompt = "update all python files in the directory sample_project. Add documentation and type annotations in every method."
gptcall(file_ops, prompt)

提示很简单:

更新目录sample_project中的所有python文件。在每个方法中添加文档和类型注释。

结果?魔法。您的Python代码将转变为更好的东西,每个函数都有文档和注释。例如:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin

def fetch_webpage(url):
try:
response = requests.get(url)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Error fetching the webpage: {e}")
return None

def parse_webpage(html_content):
soup = BeautifulSoup(html_content, 'html.parser')
return soup

def extract_links(soup, base_url):
links = []
for a_tag in soup.find_all('a', href=True):
href = a_tag.get('href')
full_url = urljoin(base_url, href)
links.append(full_url)
return links

def print_links(links):
for link in links:
print(link)

def main(url):
html_content = fetch_webpage(url)
if html_content:
soup = parse_webpage(html_content)
links = extract_links(soup, url)
print_links(links)

if __name__ == "__main__":
url = input("Enter the URL: ")
main(url)

被更新为:

import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin


def fetch_webpage(url: str) -> str:
"""
Fetches the content of the webpage at the given URL.

Args:
url (str): The URL to fetch the webpage from.

Returns:
str: The content of the webpage as a string, or None if an error occurred.
"""
try:
response = requests.get(url)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Error fetching the webpage: {e}")
return None


def parse_webpage(html_content: str) -> BeautifulSoup:
"""
Parses the HTML content and returns a BeautifulSoup object.

Args:
html_content (str): The HTML content to parse.

Returns:
BeautifulSoup: The parsed BeautifulSoup object.
"""
soup = BeautifulSoup(html_content, 'html.parser')
return soup


def extract_links(soup: BeautifulSoup, base_url: str) -> list:
"""
Extracts all the links from the given BeautifulSoup object.

Args:
soup (BeautifulSoup): The BeautifulSoup object containing the parsed HTML.
base_url (str): The base URL to resolve relative links.

Returns:
list: A list of extracted links (full URLs).
"""
links = []
for a_tag in soup.find_all('a', href=True):
href = a_tag.get('href')
full_url = urljoin(base_url, href)
links.append(full_url)
return links

def print_links(links: list) -> None:
"""
Prints the extracted links to the console.

Args:
links (list): The list of links to print.
"""
for link in links:
print(link)


def main(url: str) -> None:
"""
Main function to execute the link extraction process.

Args:
url (str): The URL to fetch and process.
"""
html_content = fetch_webpage(url)
if html_content:
soup = parse_webpage(html_content)
links = extract_links(soup, url)
print_links(links)


if __name__ == "__main__":
url = input("Enter the URL: ")
main(url)

当然,为单个文件做这个并不困难,但是想象一下为一个包含上百个文件的整个项目自动化这个过程!您甚至可以添加更高级的任务,如“查找安全问题”,ChatGPT 将忠实地执行它的函数调用魔力。又或者“查找错误并写出尖酸刻薄的评论。”

函数调用的顺序可能类似于这样:

find_files_with_extension('sample_project', '.py')
read_file_to_string('sample_project/extract_links.py')
write_string_to_file('sample_project/extract_links.py', ...)
....

用pygptcalls处理交互,你只需要轻松坐下来看节目。

坚硬的方式

现在,如果你想走DIY的路线——而不使用像pygptcalls这样的辅助库——准备好了。这并不是什么难题,但可能会让人感觉到困难。以下是逐步进行的方式。

  1. 创建一个描述所有可用功能及其参数的JSON模式。
  2. 调用ChatGPT API,提供这个架构。
  3. 当ChatGPT建议函数调用时,执行本地的函数,将结果附加到消息线程,并将其发送回API。这个过程可能重复多次。
  4. 一旦处理了所有函数调用,就返回最终结果。

例如,您的工具JSON可能如下所示:

[
{
"type": "function",
"function": {
"strict": true,
"name": "find_files_with_extension",
"description": "Recursively find files from a root directory with a specific extension.\n \n Args:\n root_dir (str): The root directory to search.\n extension (str): The file extension to look for (e.g., '.txt').\n \n Returns:\n List[str]: A list of file paths matching the given extension.",
"parameters": {
"type": "object",
"properties": {
"root_dir": {
"name": "root_dir",
"type": "string",
"description": "The root directory to search."
},
"extension": {
"name": "extension",
"type": "string",
"description": "The file extension to look for (e.g., '.txt')."
}
},
"required": [
"root_dir",
"extension"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"strict": true,
"name": "read_file_to_string",
"description": "Read a file from a specified path and return its contents as a string.\n \n Args:\n file_path (str): The path to the file to read.\n \n Returns:\n str: The contents of the file as a string.",
"parameters": {
"type": "object",
"properties": {
"file_path": {
"name": "file_path",
"type": "string",
"description": "The path to the file to read."
}
},
"required": [
"file_path"
],
"additionalProperties": false
}
}
},
{
"type": "function",
"function": {
"strict": true,
"name": "write_string_to_file",
"description": "Write a string to a file at a specified path.\n\n Args:\n file_path (str): The path to the file to write.\n content (str): The string content to write into the file.\n\n Returns:\n None",
"parameters": {
"type": "object",
"properties": {
"file_path": {
"name": "file_path",
"type": "string",
"description": "The path to the file to write."
},
"content": {
"name": "content",
"type": "string",
"description": "The string content to write into the file."
}
},
"required": [
"file_path",
"content"
],
"additionalProperties": false
}
}
}
]

现在,您可以使用这些工具进行API调用:

response =  client.beta.chat.completions.parse(
messages=[
{
"role": "system",
"content": "You are a helpful assistant."
},
{
"role": "user",
"content": prompt
}
] + messages,
model="gpt-4o-mini",#gpt-4o-mini
tools=tools_json
)

在上面的json文件中。

当ChatGPT决定调用一个函数时,您会在以下位置找到它:

response.choices[0].message.tool_calls

工具调用将具有调用函数和参数的功能。可能不止一个。

接下来是比较棘手的部分:为了将响应链接在一起,您需要将本地函数的结果附加到消息列表,并包含 tool_call ID。类似于:

 {
"role": "tool",
"content": json.dumps(function_result),
"tool_call_id": tool_call.id
}

这个过程可能会持续几次迭代,直到您到达最终响应。就像乒乓球一样,但涉及代码。

结论

结论

功能调用是一个改变游戏规则的功能。它允许您创建智能的、多功能的代理,具有优雅的架构和对执行的完全控制。您甚至可以添加安全限制,以确保这些代理的行为。有什么不喜欢的呢?

所以,无论你选择简单的方法还是困难的方法,一旦掌握了函数调用,你会想知道你以前是怎么生活的。

喜欢吗?给我留个评论。

2024-09-25 04:31:21 AI中文站翻译自原文