AI 大模型应用最佳实践
如何提升 GPT 模型使用效率与质量
技巧与原则
-
角色设定:擅于使用 System 给GPT设定角色和任务,如“哲学大师” ;
-
指令注入:在 System 中注入常驻任务指令,如“主题创作” ;
-
问题拆解:将复杂问题拆解成的子问题,分步骤执行,如:Debug 和多任务;
-
分层设计:创作长篇内容,分层提问,先概览再章节,最后补充细节,如:小说生成;
-
编程思维:将prompt当做编程语言,主动设计变量、模板和正文,如:评估模型输出质量;
-
Few-Shot:基于样例的prompt设计,规范推理路径和输出样式,如:构造训练数据;
AI 大模型应用最佳实践 Official Playground
playground 链接
- https://platform.openai.com/playground/
Playground 赋予不同的角色
给 GPT 赋予哲学大师
import os import openai openai.api_key = os.getenv("OPENAI_API_KEY") response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ { "role": "system", "content": "对于我的所有问题,你都用大道至简的佛学或中国哲学来解答,\n不要喝嗦,要有大师范儿。" }, { "role": "user", "content": "为什么天天要上班,还是学不到东西" }, { "role": "assistant", "content": "善知识,人生在世,常常需要通过工作来维持生计。然而,工作的目的并不仅仅是为了学习知识,而是为了体验和修行。在工作过程中,我们有机会锻炼自己的专业技能、人际交往能力和解决问题的能力。有时候,我们可能感觉学不到东西是因为我们对自己的工作态度不正确,没有把工作看作是一种修行的机会。\n\n行者,我教你古人所云:“工欲善其事,必先利其器。”在工作中,我们要善用工具、技能和知识,不断提升自己的专业素养和学习能力。此外,我们还要虚心向他人学习,与同事们互动交流、分享经验和观点。倾听他人" } ], temperature=1, max_tokens=256, top_p=1, frequency_penalty=0, presence_penalty=0 )
内容创作与生成
import os import openai openai.api_key = os.getenv("OPENAI_API_KEY") response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ { "role": "system", "content": "对于陈列出的每一个[话题],创作100字的内容,体现话题的论\n点,语言风趣幽默。" }, { "role": "user", "content": "对于我的话题,你要从3个不同的角度进行分析和阐述,并最终形成一个完整的观点,\n世界上最好的编程语言是什么?" }, { "role": "assistant" }, { "role": "assistant", "content": "角度一:简洁易学\n世界上最好的编程语言当属Python。它简洁易学,就像大海中的一条鲤鱼,让初学者能快速入门。Python有着清晰的语法结构,优雅的表达方式,让人感觉轻松自在。使用Python编写代码,就像" } ], temperature=1, max_tokens=256, top_p=1, frequency_penalty=0, presence_penalty=0 )
文章摘要和总结
import os import openai openai.api_key = os.getenv("OPENAI_API_KEY") response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ { "role": "system", "content": "您将获得一对关于同一主题的文章(用一标签分隔)。\n首先总结每篇文章的论点。然后指出哪篇文章提出了更好的论\n点,并解释原因。" }, { "role": "user", "content": "1.[PHP是世界上最好的语言]\nPHP是世界上最好的情感派编程语言,无需逻辑和算法,只要情绪。它能被蛰伏在冰箱里的PHP大神轻易驾\n取,会话结束后的感叹号也能传达对代码的热情。写PHP就像是在做被萨,不需要想那么多,只需把配料全\n部扔进一个碗,然后放到服务器上,热乎乎出炉的网页就好了。\n2.[Python是世界上最好的语言]\nPython是世界上最好的拜金主义者语言。它坚信:美丽就是力量,简洁就是灵魂。Pytho就像是那个永远在\n你皱眉的那一刻扔给你言情小说的好友。只有Pythoi,你才能够在两行代码之间感受到飘逸的花香和清新的\n微风。记住,这世上只有一种语言可以使用空格来领导全世界的进步,那就是Pytho。\n3.[Java是世界上最好的语言]\nJava是世界上最好的德育课编程语言,它始终坚守了严谨、安全的编程信条。Java就像一个严格的老师,他\n不会对你怀柔,不会让你偷懒,也不会让你走捷径,但他教会你规范和自律。Jv就像是那个喝咖啡也算加\n班费的上司,拥有对邪恶的深度厌恶和对善良的深度拥护" }, { "role": "assistant", "content": "第一篇文章的论点是\"PHP是世界上最好的语言\",并强调PHP的简单性和情感派特点。\n第二篇文章的论点是\"Python是世界上最好的语言\",并强调Python的美学和简洁性。\n第三篇文章的论点是\"Java是世界上最好的语言\",并强调Java的严谨和安全性。\n\n我认为第三篇文章提出了更好的论点。原因是它强调了Java的严谨和安全的编程信条,与其他两篇文章相比更加专业和实际。尽管第一篇文章试图让读者相信PHP的简单性和情感特点,但它的观点相对较为主观和不具备广泛的适用性。第二篇文章强调Python的美学和简洁性,但这些特点并不一定" } ], temperature=1, max_tokens=256, top_p=1, frequency_penalty=0, presence_penalty=0 )
Moderation
程序分析
import os import openai openai.api_key = os.getenv("OPENAI_API_KEY") response = openai.ChatCompletion.create( model="gpt-3.5-turbo", messages=[ { "role": "system", "content": "您将收到DEBUG问题,将每个问题分类为报错日志分析、根因\n可能性推断、解决方案、参考资料,分点陈列输出,格式如下所\n示:\n报错日志分析:\n根因可能性推断:\n解决方案:\n参考资料:" }, { "role": "user", "content": "openai.error.APIConnectionError:Error communicating with OpenAI:\nSOCKSHTTPSConnectionPool(host='api.openai.com',port=443):Max retries exceeded with url:\n/v1/engines/text-embedding-ada-002/embeddings(Caused by SSLError(SSLEOFError(8,'EOF\noccurred in violation of protocol(ssl.c1007刀))" }, { "role": "assistant", "content": "报错日志分析:\nopenai.error.APIConnectionError:Error communicating with OpenAI: SOCKSHTTPSConnectionPool(host='api.openai.com',port=443):Max retries exceeded with url: /v1/engines/text-embedding-ada-002/embeddings(Caused by SSLError(SSLEOFError(8,'EOF occurred in violation of protocol(ssl.c1007\n\n根因可能性推断:\n根据错误日志可以看出,出现了与OpenAI的通信错误。其中涉及到了一个SSLError。根据错误堆栈信息可以推断可能是由于SSL协议的问题导致连接中断。\n\n解决方案:\n1. 检查网络连接:首先,确保您的网络连接正常并且没有任何代理、VPN等网络设置干扰。尝试重新连接到OpenAI的API,确保网络连接稳定。\n\n2. 更新SSL证书:如果问题仍然存在,尝试更新或重新安装SSL证书。可以通过下载和安装最新的SSL证书来解决此问题。\n\n3. 联系技术支持:如果以上解决方案仍然无效,请联系OpenAI的技术支持团队,向他们详细描述您遇到的问题并提交错误报告。他们可能会提供更具体的解决方案或帮助您诊断问题的根本原因。\n\n参考资料:\n1. OpenAI官方文档:https://openai.com/docs/\n2. SSL证书更新指南:https://www.ssl.com/how-to/update-or-renew-ssl-certificates/" } ], temperature=1, max_tokens=1814, top_p=1, frequency_penalty=0, presence_penalty=0 )
新特性:Function Calling 介绍与实战
基于 Chat Completions API 实现外部函数调用
2023年6月20日,OpenAI 官方在 Chat Completions API 原有的三种不同角色设定(System, Assistant, User)基础上,新增了 Function Calling 功能
functions
是 Chat Completion API 中的可选参数,用于提供函数定义。其目的是使 GPT 模型能够生成符合所提供定义的函数参数。请注意,API不会实际执行任何函数调用。开发人员需要使用GPT 模型输出来执行函数调用。
如果提供了functions
参数,默认情况下,GPT 模型将决定在何时适当地使用其中一个函数。
可以通过将function_call
参数设置为{"name": "<insert-function-name>"}
来强制 API 使用指定函数。
同时,也支持通过将function_call
参数设置为"none"
来强制API不使用任何函数。
如果使用了某个函数,则响应中的输出将包含"finish_reason": "function_call"
,以及一个具有该函数名称和生成的函数参数的function_call
对象。
本 Notebook 介绍了如何将 Chat Completions API 与外部函数结合使用,以扩展 GPT 模型的功能。包含以下2个部分:
- 如何使用
functions
参数 - 如何使用
function_call
参数 - 使用 GPT 模型生成函数和参数
- 实际执行 GPT 模型生成的函数(以 SQL 查询为例)
安装依赖包
pip install scipy tenacity tiktoken termcolor openai requests
导入依赖包
import json import openai import requests from tenacity import retry, wait_random_exponential, stop_after_attempt from termcolor import colored GPT_MODEL = "gpt-3.5-turbo"
定义工具函数
首先,让我们定义一些用于调用聊天完成 API 的实用工具,并维护和跟踪对话状态。
# 使用了retry库,指定在请求失败时的重试策略。 # 这里设定的是指数等待(wait_random_exponential),时间间隔的最大值为40秒,并且最多重试3次(stop_after_attempt(3))。 # 定义一个函数chat_completion_request,主要用于发送 聊天补全 请求到OpenAI服务器 @retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3)) def chat_completion_request(messages, functions=None, function_call=None, model=GPT_MODEL): # 设定请求的header信息,包括 API_KEY headers = { "Content-Type": "application/json", "Authorization": "Bearer " + openai.api_key, } # 设定请求的JSON数据,包括GPT 模型名和要进行补全的消息 json_data = {"model": model, "messages": messages} # 如果传入了functions,将其加入到json_data中 if functions is not None: json_data.update({"functions": functions}) # 如果传入了function_call,将其加入到json_data中 if function_call is not None: json_data.update({"function_call": function_call}) # 尝试发送POST请求到OpenAI服务器的chat/completions接口 try: response = requests.post( "https://api.openai.com/v1/chat/completions", headers=headers, json=json_data, ) # 返回服务器的响应 return response # 如果发送请求或处理响应时出现异常,打印异常信息并返回 except Exception as e: print("Unable to generate ChatCompletion response") print(f"Exception: {e}") return e
定义一个函数pretty_print_conversation,用于打印消息对话内容
def pretty_print_conversation(messages): # 为不同角色设置不同的颜色 role_to_color = { "system": "red", "user": "green", "assistant": "blue", "function": "magenta", } # 遍历消息列表 for message in messages: # 如果消息的角色是"system",则用红色打印“content” if message["role"] == "system": print(colored(f"system: {message['content']}\n", role_to_color[message["role"]])) # 如果消息的角色是"user",则用绿色打印“content” elif message["role"] == "user": print(colored(f"user: {message['content']}\n", role_to_color[message["role"]])) # 如果消息的角色是"assistant",并且消息中包含"function_call",则用蓝色打印"function_call" elif message["role"] == "assistant" and message.get("function_call"): print(colored(f"assistant[function_call]: {message['function_call']}\n", role_to_color[message["role"]])) # 如果消息的角色是"assistant",但是消息中不包含"function_call",则用蓝色打印“content” elif message["role"] == "assistant" and not message.get("function_call"): print(colored(f"assistant[content]: {message['content']}\n", role_to_color[message["role"]])) # 如果消息的角色是"function",则用品红色打印“function” elif message["role"] == "function": print(colored(f"function ({message['name']}): {message['content']}\n", role_to_color[message["role"]]))
如何使用 functions 参数
这段代码定义了两个可以在程序中调用的函数,分别是获取当前天气和获取未来N天的天气预报。
每个函数(function)都有其名称、描述和需要的参数(包括参数的类型、描述等信息)。
我们将把这些传递给 Chat Completions API,以生成符合规范的函数。
# 定义一个名为functions的列表,其中包含两个字典,这两个字典分别定义了两个功能的相关参数 # 第一个字典定义了一个名为"get_current_weather"的功能 functions = [ { "name": "get_current_weather", # 功能的名称 "description": "Get the current weather", # 功能的描述 "parameters": { # 定义该功能需要的参数 "type": "object", "properties": { # 参数的属性 "location": { # 地点参数 "type": "string", # 参数类型为字符串 "description": "The city and state, e.g. San Francisco, CA", # 参数的描述 }, "format": { # 温度单位参数 "type": "string", # 参数类型为字符串 "enum": ["celsius", "fahrenheit"], # 参数的取值范围 "description": "The temperature unit to use. Infer this from the users location.", # 参数的描述 }, }, "required": ["location", "format"], # 该功能需要的必要参数 }, }, # 第二个字典定义了一个名为"get_n_day_weather_forecast"的功能 { "name": "get_n_day_weather_forecast", # 功能的名称 "description": "Get an N-day weather forecast", # 功能的描述 "parameters": { # 定义该功能需要的参数 "type": "object", "properties": { # 参数的属性 "location": { # 地点参数 "type": "string", # 参数类型为字符串 "description": "The city and state, e.g. San Francisco, CA", # 参数的描述 }, "format": { # 温度单位参数 "type": "string", # 参数类型为字符串 "enum": ["celsius", "fahrenheit"], # 参数的取值范围 "description": "The temperature unit to use. Infer this from the users location.", # 参数的描述 }, "num_days": { # 预测天数参数 "type": "integer", # 参数类型为整数 "description": "The number of days to forecast", # 参数的描述 } }, "required": ["location", "format", "num_days"] # 该功能需要的必要参数 }, }, ]
这段代码首先定义了一个messages
列表用来存储聊天的消息,然后向列表中添加了系统和用户的消息。
然后,它使用了之前定义的chat_completion_request
函数发送一个请求,传入的参数包括消息列表和函数列表。
在接收到响应后,它从JSON响应中解析出助手的消息,并将其添加到消息列表中。
最后,它打印出 GPT 模型回复的消息。
(如果我们询问当前天气,GPT 模型会回复让你给出更准确的问题。)
# 定义一个空列表messages,用于存储聊天的内容 messages = [] # 使用append方法向messages列表添加一条系统角色的消息 messages.append({ "role": "system", # 消息的角色是"system" "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous." # 消息的内容 }) # 向messages列表添加一条用户角色的消息 messages.append({ "role": "user", # 消息的角色是"user" "content": "What's the weather like today" # 用户询问今天的天气情况 }) # 使用定义的chat_completion_request函数发起一个请求,传入messages和functions作为参数 chat_response = chat_completion_request( messages, functions=functions ) # 解析返回的JSON数据,获取助手的回复消息 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) pretty_print_conversation(messages)
使用 GPT 模型生成函数和对应参数
下面这段代码先向messages列表中添加了用户的位置信息。
然后再次使用了chat_completion_request函数发起请求,只是这次传入的消息列表已经包括了用户的新消息。
在获取到响应后,它同样从JSON响应中解析出助手的消息,并将其添加到消息列表中。
最后,打印出助手的新的回复消息。
# 向messages列表添加一条用户角色的消息,用户告知他们在苏格兰的格拉斯哥 messages.append({ "role": "user", # 消息的角色是"user" "content": "I'm in Shanghai, China." # 用户的消息内容 }) # 再次使用定义的chat_completion_request函数发起一个请求,传入更新后的messages和functions作为参数 chat_response = chat_completion_request( messages, functions=functions ) # 解析返回的JSON数据,获取助手的新的回复消息 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的新的回复消息添加到messages列表中 messages.append(assistant_message) pretty_print_conversation(messages)
这段代码的逻辑大体与上一段代码相同,区别在于这次用户的询问中涉及到未来若干天(x天)的天气预报。
在获取到回复后,它同样从JSON响应中解析出助手的消息,并将其添加到消息列表中。
然后打印出助手的回复消息
(通过不同的prompt方式,我们可以让它针对我们告诉它的其他功能。)
# 初始化一个空的messages列表 messages = [] # 向messages列表添加一条系统角色的消息,要求不做关于函数参数值的假设,如果用户的请求模糊,应该寻求澄清 messages.append({ "role": "system", # 消息的角色是"system" "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous." }) # 向messages列表添加一条用户角色的消息,用户询问在未来x天内苏格兰格拉斯哥的天气情况 messages.append({ "role": "user", # 消息的角色是"user" "content": "what is the weather going to be like in Shanghai, China over the next x days" }) # 使用定义的chat_completion_request函数发起一个请求,传入messages和functions作为参数 chat_response = chat_completion_request( messages, functions=functions ) # 解析返回的JSON数据,获取助手的回复消息 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) # 打印助手的回复消息 pretty_print_conversation(messages)
(GPT 模型再次要求我们澄清,因为它还没有足够的信息。在这种情况下,它已经知道预测的位置,但需要知道需要多少天的预测。)
这段代码的主要目标是将用户指定的天数(5天)添加到消息列表中,然后再次调用chat_completion_request函数发起一个请求。
返回的响应中包含了助手对用户的回复,即未来5天的天气预报。
这个预报是基于用户指定的地点(上海)和天数(5天)生成的。
在代码的最后,它解析出返回的JSON响应中的第一个选项,这就是助手的回复消息。
# 向messages列表添加一条用户角色的消息,用户指定接下来的天数为5天 messages.append({ "role": "user", # 消息的角色是"user" "content": "5 days" }) # 使用定义的chat_completion_request函数发起一个请求,传入messages和functions作为参数 chat_response = chat_completion_request( messages, functions=functions ) # 解析返回的JSON数据,获取第一个选项 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) # 打印助手的回复消息 pretty_print_conversation(messages)
强制使用指定函数
我们可以通过使用function_call
参数来强制GPT 模型使用指定函数,例如get_n_day_weather_forecast
。
通过这种方式,可以让 GPT 模型学习如何使用该函数。
# 在这个代码单元中,我们强制GPT 模型使用get_n_day_weather_forecast函数 messages = [] # 创建一个空的消息列表 # 添加系统角色的消息 messages.append({ "role": "system", # 角色为系统 "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous." }) # 添加用户角色的消息 messages.append({ "role": "user", # 角色为用户 "content": "Give me a weather report for San Diego, USA." }) # 使用定义的chat_completion_request函数发起一个请求,传入messages、functions以及特定的function_call作为参数 chat_response = chat_completion_request( messages, functions=functions, function_call={"name": "get_n_day_weather_forecast"} ) # 解析返回的JSON数据,获取第一个选项 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) # 打印助手的回复消息 pretty_print_conversation(messages)
下面这段代码演示了在不强制使用特定函数(get_n_day_weather_forecast
)的情况下,GPT 模型可能会选择不同的方式来回应用户的请求。对于给定的用户请求"Give me a weather report for San Diego, USA.",GPT 模型可能不会调用get_n_day_weather_forecast
函数。
# 如果我们不强制GPT 模型使用 get_n_day_weather_forecast,它可能不会使用 messages = [] # 创建一个空的消息列表 # 添加系统角色的消息 messages.append({ "role": "system", # 角色为系统 "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous." }) # 添加用户角色的消息 messages.append({ "role": "user", # 角色为用户 "content": "Give me a weather report for San Diego, USA." }) # 使用定义的chat_completion_request函数发起一个请求,传入messages和functions作为参数 chat_response = chat_completion_request( messages, functions=functions ) # 解析返回的JSON数据,获取第一个选项 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) # 打印助手的回复消息 pretty_print_conversation(messages)
强制不使用函数
然后,我们创建另一个消息列表,并添加系统和用户的消息。这次用户请求的是加拿大多伦多当前的天气(使用摄氏度)。
随后,代码再次调用chat_completion_request
函数,
但这次在function_call
参数中明确指定了"none",表示GPT 模型在处理此请求时不能调用任何函数。
最后,代码解析返回的JSON响应,获取第一个选项的消息,即 GPT 模型的回应。
# 创建另一个空的消息列表 messages = [] # 添加系统角色的消息 messages.append({ "role": "system", # 角色为系统 "content": "Don't make assumptions about what values to plug into functions. Ask for clarification if a user request is ambiguous." }) # 添加用户角色的消息 messages.append({ "role": "user", # 角色为用户 "content": "Give me the current weather (use Celcius) for Toronto, Canada." }) # 使用定义的chat_completion_request函数发起一个请求,传入messages、functions和function_call作为参数 chat_response = chat_completion_request( messages, functions=functions, function_call="none" ) # 解析返回的JSON数据,获取第一个选项 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的回复消息添加到messages列表中 messages.append(assistant_message) # 打印助手的回复消息 pretty_print_conversation(messages)
执行 GPT 模型生成的函数
接着,我们将演示如何执行输入为 GPT 模型生成的函数,并利用这一点来实现一个可以帮助我们回答关于数据库的问题的代理。
为了简单起见,我们将使用Chinook样本数据库。
注意: 在生产环境中,SQL生成可能存在较高风险,因为GPT 模型在生成正确的SQL方面并不完全可靠。
定义一个执行SQL查询的函数
首先,让我们定义一些有用的实用函数来从SQLite数据库中提取数据。
import sqlite3 conn = sqlite3.connect("data/chinook.db") print("Opened database successfully")
首先定义三个函数get_table_names
、get_column_names
和get_database_info
,用于从数据库连接对象中获取数据库的表名、表的列名以及整体数据库的信息
def get_table_names(conn): """返回一个包含所有表名的列表""" table_names = [] # 创建一个空的表名列表 # 执行SQL查询,获取数据库中所有表的名字 tables = conn.execute("SELECT name FROM sqlite_master WHERE type='table';") # 遍历查询结果,并将每个表名添加到列表中 for table in tables.fetchall(): table_names.append(table[0]) return table_names # 返回表名列表 def get_column_names(conn, table_name): """返回一个给定表的所有列名的列表""" column_names = [] # 创建一个空的列名列表 # 执行SQL查询,获取表的所有列的信息 columns = conn.execute(f"PRAGMA table_info('{table_name}');").fetchall() # 遍历查询结果,并将每个列名添加到列表中 for col in columns: column_names.append(col[1]) return column_names # 返回列名列表 def get_database_info(conn): """返回一个字典列表,每个字典包含一个表的名字和列信息""" table_dicts = [] # 创建一个空的字典列表 # 遍历数据库中的所有表 for table_name in get_table_names(conn): columns_names = get_column_names(conn, table_name) # 获取当前表的所有列名 # 将表名和列名信息作为一个字典添加到列表中 table_dicts.append({"table_name": table_name, "column_names": columns_names}) return table_dicts # 返回字典列表
将数据库信息转换为 Python 字典类型
database_schema_dict = get_database_info(conn) # 将数据库信息转换为字符串格式,方便后续使用 database_schema_string = "\n".join( [ f"Table: {table['table_name']}\nColumns: {', '.join(table['column_names'])}" for table in database_schema_dict ] ) database_schema_dict
[{'table_name': 'albums', 'column_names': ['AlbumId', 'Title', 'ArtistId']}, {'table_name': 'sqlite_sequence', 'column_names': ['name', 'seq']}, {'table_name': 'artists', 'column_names': ['ArtistId', 'Name']}, {'table_name': 'customers', 'column_names': ['CustomerId', 'FirstName', 'LastName', 'Company', 'Address', 'City', 'State', 'Country', 'PostalCode', 'Phone', 'Fax', 'Email', 'SupportRepId']}, {'table_name': 'employees', 'column_names': ['EmployeeId', 'LastName', 'FirstName', 'Title', 'ReportsTo', 'BirthDate', 'HireDate', 'Address', 'City', 'State', 'Country', 'PostalCode', 'Phone', 'Fax', 'Email']}, {'table_name': 'genres', 'column_names': ['GenreId', 'Name']}, {'table_name': 'invoices', 'column_names': ['InvoiceId', 'CustomerId', 'InvoiceDate', 'BillingAddress', 'BillingCity', 'BillingState', 'BillingCountry', 'BillingPostalCode', 'Total']}, {'table_name': 'invoice_items', 'column_names': ['InvoiceLineId', 'InvoiceId', 'TrackId', 'UnitPrice', 'Quantity']}, {'table_name': 'media_types', 'column_names': ['MediaTypeId', 'Name']}, {'table_name': 'playlists', 'column_names': ['PlaylistId', 'Name']}, {'table_name': 'playlist_track', 'column_names': ['PlaylistId', 'TrackId']}, {'table_name': 'tracks', 'column_names': ['TrackId', 'Name', 'AlbumId', 'MediaTypeId', 'GenreId', 'Composer', 'Milliseconds', 'Bytes', 'UnitPrice']}, {'table_name': 'sqlite_stat1', 'column_names': ['tbl', 'idx', 'stat']}]
然后,定义一个函数ask_database
。
目标是让 GPT 模型帮我们构造一个完整的 SQL 查询。
# 定义一个功能列表,其中包含一个功能字典,该字典定义了一个名为"ask_database"的功能,用于回答用户关于音乐的问题 functions = [ { "name": "ask_database", "description": "Use this function to answer user questions about music. Output should be a fully formed SQL query.", "parameters": { "type": "object", "properties": { "query": { "type": "string", "description": f""" SQL query extracting info to answer the user's question. SQL should be written using this database schema: {database_schema_string} The query should be returned in plain text, not in JSON. """, } }, "required": ["query"], }, } ]
执行 SQL 查询
首先,定义两个函数ask_database
和execute_function_call
- 前者用于实际执行 SQL 查询并返回结果
- 后者用于根据消息中的功能调用信息来执行相应的功能并获取结果
然后,创建一个消息列表,并向其中添加了一个系统消息和一个用户消息。系统消息的内容是指示对话的目标,用户消息的内容是用户的问题。
接着,使用chat_completion_request
函数发出聊天请求并获取响应,然后从响应中提取出助手的消息并添加到消息列表中。
如果助手的消息中包含功能调用,那么就使用execute_function_call
函数执行这个功能调用并获取结果,然后将结果作为一个功能消息添加到消息列表中。
最后,使用pretty_print_conversation
函数打印出整个对话。
def ask_database(conn, query): """使用 query 来查询 SQLite 数据库的函数。""" try: results = str(conn.execute(query).fetchall()) # 执行查询,并将结果转换为字符串 except Exception as e: # 如果查询失败,捕获异常并返回错误信息 results = f"query failed with error: {e}" return results # 返回查询结果 def execute_function_call(message): """执行函数调用""" # 判断功能调用的名称是否为 "ask_database" if message["function_call"]["name"] == "ask_database": # 如果是,则获取功能调用的参数,这里是 SQL 查询 query = json.loads(message["function_call"]["arguments"])["query"] # 使用 ask_database 函数执行查询,并获取结果 results = ask_database(conn, query) else: # 如果功能调用的名称不是 "ask_database",则返回错误信息 results = f"Error: function {message['function_call']['name']} does not exist" return results # 返回结果
创建一个空的消息列表
messages = [] # 向消息列表中添加一个系统角色的消息,内容是 "Answer user questions by generating SQL queries against the Chinook Music Database." messages.append({"role": "system", "content": "Answer user questions by generating SQL queries against the Chinook Music Database."}) # 向消息列表中添加一个用户角色的消息,内容是 "Hi, who are the top 5 artists by number of tracks?" messages.append({"role": "user", "content": "Hi, who are the top 5 artists by number of tracks?"}) # 使用 chat_completion_request 函数获取聊天响应 chat_response = chat_completion_request(messages, functions) # 从聊天响应中获取助手的消息 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的消息添加到消息列表中 messages.append(assistant_message) # 如果助手的消息中有功能调用 if assistant_message.get("function_call"): # 使用 execute_function_call 函数执行功能调用,并获取结果 results = execute_function_call(assistant_message) # 将功能的结果作为一个功能角色的消息添加到消息列表中 messages.append({"role": "function", "name": assistant_message["function_call"]["name"], "content": results}) # 使用 pretty_print_conversation 函数打印对话 pretty_print_conversation(messages)
database_schema_dict
[{'table_name': 'albums', 'column_names': ['AlbumId', 'Title', 'ArtistId']}, {'table_name': 'sqlite_sequence', 'column_names': ['name', 'seq']}, {'table_name': 'artists', 'column_names': ['ArtistId', 'Name']}, {'table_name': 'customers', 'column_names': ['CustomerId', 'FirstName', 'LastName', 'Company', 'Address', 'City', 'State', 'Country', 'PostalCode', 'Phone', 'Fax', 'Email', 'SupportRepId']}, {'table_name': 'employees', 'column_names': ['EmployeeId', 'LastName', 'FirstName', 'Title', 'ReportsTo', 'BirthDate', 'HireDate', 'Address', 'City', 'State', 'Country', 'PostalCode', 'Phone', 'Fax', 'Email']}, {'table_name': 'genres', 'column_names': ['GenreId', 'Name']}, {'table_name': 'invoices', 'column_names': ['InvoiceId', 'CustomerId', 'InvoiceDate', 'BillingAddress', 'BillingCity', 'BillingState', 'BillingCountry', 'BillingPostalCode', 'Total']}, {'table_name': 'invoice_items', 'column_names': ['InvoiceLineId', 'InvoiceId', 'TrackId', 'UnitPrice', 'Quantity']}, {'table_name': 'media_types', 'column_names': ['MediaTypeId', 'Name']}, {'table_name': 'playlists', 'column_names': ['PlaylistId', 'Name']}, {'table_name': 'playlist_track', 'column_names': ['PlaylistId', 'TrackId']}, {'table_name': 'tracks', 'column_names': ['TrackId', 'Name', 'AlbumId', 'MediaTypeId', 'GenreId', 'Composer', 'Milliseconds', 'Bytes', 'UnitPrice']}, {'table_name': 'sqlite_stat1', 'column_names': ['tbl', 'idx', 'stat']}]
向消息列表中添加一个用户的问题,内容是 "What is the name of the album with the most tracks?"
messages.append({"role": "user", "content": "What is the name of the album with the most tracks?"}) # 使用 chat_completion_request 函数获取聊天响应 chat_response = chat_completion_request(messages, functions) # 从聊天响应中获取助手的消息 assistant_message = chat_response.json()["choices"][0]["message"] # 将助手的消息添加到消息列表中 messages.append(assistant_message) # 如果助手的消息中有功能调用 if assistant_message.get("function_call"): # 使用 execute_function_call 函数执行功能调用,并获取结果 results = execute_function_call(assistant_message) # 将功能的结果作为一个功能角色的消息添加到消息列表中 messages.append({"role": "function", "content": results, "name": assistant_message["function_call"]["name"]}) # 使用 pretty_print_conversation 函数打印对话 pretty_print_conversation(messages)
Homework
# 使用了retry库,指定在请求失败时的重试策略。 # 这里设定的是指数等待(wait_random_exponential),时间间隔的最大值为40秒,并且最多重试3次(stop_after_attempt(3))。 # 定义一个函数chat_completion_api,通过 OpenAI Python 库调用 Chat Completions API @retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3)) def chat_completion_api(messages, functions=None, function_call=None, model=GPT_MODEL): try: # TODO pass # 如果发送请求或处理响应时出现异常,打印异常信息并返回 except Exception as e: print("Unable to generate ChatCompletion response") print(f"Exception: {e}") return e
使用类似搜索引擎API,实现完整可执行的天气查询应用