掘金 后端 ( ) • 2024-04-26 13:16

theme: qklhk-chocolate

前言

回顾上篇文章写到如何实现最简单的OpenAI对话请求示例:

使用 Python接入 OpenAI API,实现简单的对话生成,介绍其中相应参数含义

from openai import OpenAI
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())
client = OpenAI()

response = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "Who won the world series in 2020?"},
    {"role": "assistant", "content": "The Los Angeles Dodgers won the World Series in 2020."},
    {"role": "user", "content": "Where was it played?"}
  ]
)
print(response)  # 打印的是一个对象
print(response.choices[0].message.content)  # 打印的是一个具体的回复的内容

最后返回了一个对象,其实在model="gpt-3.5-turbo",增加一行response_format={ "type": "json_object" }, 将返回值设置为json格式就会输出如下内容

{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "The World Series in 2020 was played at Globe Life Field in Arlington, Texas."
      }
    }
  ],
  "created": 1714051759,
  "id": "chatcmpl-6p9XYPYSTTRi0xEviKjjilqrWU2Ve",
  "model": "gpt-3.5-turbo-0125",
  "object": "chat.completion",
  "usage": {
    "prompt_tokens": 18,
    "completion_tokens": 54,
    "total_tokens": 72
  }
}

这样返回的内容格式可以更容易给开发者处理。

对话角色

在上面的例子中messages有几个角色分别为:system,user,assistant,对应的解释为:

  • system:用于设置 AI 的行为、背景等,比如设定其为人工智能专家等。

  • assistant:通常是模型的回复,可用于提供上下文。

  • user:模型的使用者,也即聊天内容的发起者,同时可用于提供上下文。

为了让API记住对话的上下文,就要每次在对话的时候都要 携带历史对话的assistant,user的记录,给个示例就明白:

from openai import OpenAI
from dotenv import load_dotenv, find_dotenv

_ = load_dotenv(find_dotenv())
client = OpenAI()

response = client.chat.completions.create(
  model="gpt-3.5-turbo",
  messages=[
    {"role": "system", "content": "你是一个ChatGPT助手,你帮助用户回答问题"},
    {"role": "user", "content": "说10遍王子请睡觉"},
    {"role": "assistant", "content": "王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。王子请睡觉。"},
    {"role": "user", "content": "你上面回答了几次王子请睡觉"}
  ]
)
print(response.choices[0].message.content)  # 我回答了10次“王子请睡觉”。

这就记住了上次的对话,当然携带的内容过多占用的token就会越多,目前ChatGPT3.5最大上下文应该是16K,16k对应16000个token,测试了一下复制了很多assistant和user的对话历史在代码中,token在超过16k的时候报错情况如下:

openai.BadRequestError: Error code: 400 - {'error': {'message': "This model's maximum context length is 16385 tokens. However, your messages resulted in 17184 tokens. Please reduce the length of the messages.", 'type': 'invalid_request_error', 'param': 'messages', 'code': 'context_length_exceeded'}}

所以在开发多轮对话API的时候要考虑token的占用预估的问题。OpenAI的文档有对token计算的说明,我们以后再研究。

函数调用

函数调用(Function Call)是 OpenAI API 的一项强大功能,它允许开发者向模型提供自定义函数,从而扩展模型的功能并使其能够处理更复杂的任务。借助函数调用,开发者可以:

  • 引入外部数据: 模型可以访问并处理来自外部 API 或数据库的数据,例如天气预报、股票行情等。

  • 实现复杂逻辑: 模型可以执行更复杂的逻辑操作,例如计算、排序、过滤等。

  • 增强交互性: 模型可以与用户进行更具交互性的对话,例如根据用户输入提供个性化建议或完成特定任务。

今天就引入外部数据做一个BTC价格查询的函数调用,详细注释在代码里面:

from openai import OpenAI
from dotenv import load_dotenv, find_dotenv
import requests,json


_ = load_dotenv(find_dotenv())
client = OpenAI()

def get_coin_prince(coin):
    # Execute query here
    url = 'https://data-api.binance.vision/api/v3/ticker/24hr?symbol='+coin.upper()+"USDT"  # binance直接请求获得结果数据
    response = requests.get(url)
    response = response.json()
    try:
      return response['lastPrice']+'USDT'
    except:
      return "The coin is not exist"

# 函数描述
tools = [
    {
        "type": "function",
        "function":{
          "name": "get_coin_prince",
          "description": "Get a coin Price from Binance, The price unit is in US dollars",
          "parameters": {
              "type": "object",
              "properties": {
                  "coin": {
                      "coin": "string",
                      "description": "Abbreviations for Virtual Currency",
                  }
              },
              "required": ["coin"],
          },
        }
    }
]

messages=[
         {"role": "user", "content": "BTC的价格是多少?"},
  ],
messages = []
messages.append({"role": "system", "content": "你是一个虚拟币查询机器人"})
messages.append({"role": "user", "content": "btc的价格是多少?"})
response = client.chat.completions.create(
  model="gpt-3.5-turbo-0613",
  messages=messages,
  tools=tools,
  tool_choice="auto", 
)

print(response)  # 打印的是一个对象

response_message = response.choices[0].message  # 消息对象

tool_calls = response_message.tool_calls  # 识别是否触发函数调用
if tool_calls: # 判断是否为函数调用
    available_functions = {
            "get_coin_prince": get_coin_prince,
        }  # 本例中只有一个函数,但可以有多个
    messages.append(response_message)
    for tool_call in tool_calls: # 遍历函数调用
      function_name = tool_call.function.name
      function_to_call = available_functions[function_name] # 找到本地目标函数
      function_args = json.loads(tool_call.function.arguments) # 获取函数参数的具体值
      function_response = function_to_call(
          coin=function_args.get("coin"),
      ) # 真实调用本地函数
      messages.append(
          {
              "tool_call_id": tool_call.id,
              "role": "tool",
              "name": function_name,
              "content": function_response, #本地调用函数内容返回值放进对话 ,接下来给ChatGPT组装内容
          }
      )  # 新一轮的对话的函数调用细节(函数ID,规则,名称,参数)

# 开始第二次对话
second_response = client.chat.completions.create(
    model="gpt-3.5-turbo-0125",
    messages=messages,
)  # 从模型中获取一个新的响应
print(messages) # 调试组装过的对话内容
print(second_response)
res =  second_response.choices[0].message.content

print(res)  # BTC的价格为64477.51 USDT。

从原理上面来梳理,就是声明函数,给对话增加额外参数提示可以自动调用函数,识别到有函数调用,就本地调用函数再组装进行第二次对话获得最终结果,第一次接触还是比较难以理解的,顺利弄完了很有成就感,注意以上代码需要有相对应的网络环境。

最后

今天算是真的搞懂了大模型的本地函数调用,找了好多资料都没有我这样的研究过程示例,学到了很开心,今天就介绍到这里,ChatGPT的API基础的都介绍完毕了,后面可能会来解密一下OpenAI API中的详细参数和Token计算,敬请期待~

如果觉得内容不错,欢迎点个赞,你的支持是我更新的动力。