Fork me on GitHub

1.引言

有人说,智慧是 知识 + 运用知识的能力。从这个角度来看,大语言模型(如ChatGPT)
在某种程度上可以被认为具有一定形式的智慧。然而现在针对大语言模型的使用有一个问题,
就是模型所具有的知识无法保持实时更新。比如某个API文档发生变化,大语言模型仍然只能基于历史知识进行回答。
有没有办法让大语言模型获取最新的知识,而不至于stale

显然如果它能够调用搜索引擎,获取最新知识,就可以解决这个问题。
事实上,现在kimi的联网搜索和ChatGPT的搜索网页都是为了解决这个问题。


下面让我们看看如何利用function calling来实现大语言模型从搜索引擎获取数据,继而回答用户的提问。

2. 大语言模型通过搜索引擎获取数据

首先,我们来看一个问题:“今天武汉和上海的气温,哪一个更高?”
对于人类而言,我们要解决这个问题,必须经过以下步骤

  • 1)获取武汉和上海的气温数据
  • 2)比较武汉和上海的最高气温,得出结论

下面萌叔提供一个完整的Python程序,看看如何使用function calling来回答这个提问

import json
from email.policy import strict

from openai import OpenAI
import requests
from openai._utils import maybe_transform
from openai.types.chat import ChatCompletion

base_url = "{此处请自行替换}"
api_key = "{此处请自行替换}"
google_search_key = "{此处请自行替换}"
google_search_engine = "{此处请自行替换}"

# 定义 google_search 函数(示例)
def google_search(query: str) -> str:
    url = "https://www.googleapis.com/customsearch/v1"
    query_params = {
        "key": google_search_key,
        "cx": google_search_engine,
        "q": query,
        "num": 5,
    }

    response = requests.get(url, params=query_params)
    print(response.url)
    return response.text

# 调用大模型
def send_messages(messages):
    # 函数信息,用于 Function Calling
    tools = [
        {
            "type": "function",
            "function": {
                "name": "google_search",
                "description": "通过 Google 搜索获取信息",
                "parameters": {
                    # 参数类型必须是object
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "需要搜索的关键词或短语",
                        }
                    },
                    "required": ["query"],
                    "additionalProperties": False,
                },
                "strict": True
            }
        },
    ]
    client = OpenAI(api_key=api_key, base_url=base_url)
    response = client.chat.completions.create(
        model="gpt-4o-mini", # 确保使用支持 Function Calling 的模型
        messages=messages,
        tools=tools,
        max_tokens=4096,
        parallel_tool_calls=True,
    )

    dd = maybe_transform(response, ChatCompletion)
    data = json.dumps(dd, ensure_ascii=False)
    print(data)
    return response.choices[0].message

if __name__ == "__main__":
    # 示例对话
    # input_text = input("请输入您的问题:")

    messages = [
        {"role": "system",
         "content": "你是一个智能搜索机器人。你根据用户提出的问题,利用google_search函数,获取额外信息,然后回答用户的提问"},
        {"role": "user", "content": "今天武汉和上海的气温,哪一个更高?"},
    ]
    # 1. **第1次调用AI接口**
    message = send_messages(messages)
    call_cout = len(message.tool_calls)
    print(f"将会发起{call_cout}次调用...")

    # 2. **在client端调用google_search()**
    messages.append(message)
    for tool in message.tool_calls:
        if tool.function.name == "google_search":
            parsed_arguments = json.loads(tool.function.arguments)
            query = parsed_arguments.get("query", None)  # 获取 query 值
            print("query:", query)
            content = google_search(query)
            messages.append({"role": "tool", "tool_call_id": tool.id, "content": content})

    # 3. **第2次调用AI接口**
    message = send_messages(messages)
    print(f"Model>\t {message.content}")

0) 函数说明

这里我们给出了函数google_search()说明, 并把它传递给大模型,
告诉它在处理问题的时候,可以考虑使用上我们提供的函数。

    tools = [
        {
            "type": "function",
            "function": {
                "name": "google_search",
                "description": "通过 Google 搜索获取信息",
                "parameters": {
                    # 参数类型必须是object
                    "type": "object",
                    "properties": {
                        "query": {
                            "type": "string",
                            "description": "需要搜索的关键词或短语",
                        }
                    },
                    "required": ["query"],
                    "additionalProperties": False,
                },
                "strict": True
            }
        },
    ]

提示: tools中可以包含多个函数,大模型会自主选择使用哪些函数

1) 第1次调用AI接口

{
    "id": "chatcmpl-ApD9VJN3P1lo65kq2ZX7XKQ7hDpwa",
    "choices": [{
        "finish_reason": "tool_calls",
        "index": 0,
        "logprobs": null,
        "message": {
            "content": null,
            "refusal": null,
            "role": "assistant",
            "tool_calls": [{
                "id": "call_jZ3hzsJVFaqMiz0UF2BxDdm8",
                "function": {
                    "arguments": "{\"query\": \"武汉今天气温\"}",
                    "name": "google_search"
                },
                "type": "function"
            }, {
                "id": "call_cKwbFiAxGdUuCf2dgsUpG3Qn",
                "function": {
                    "arguments": "{\"query\": \"上海今天气温\"}",
                    "name": "google_search"
                },
                "type": "function"
            }]
        }
    }],
    ...
}

如果模型确定应该调用某个函数,它将在响应中返回一个tool_calls字段,您可以使用该字段来确定模型是否生成了函数调用以及参数是什么。

2) 在client端调用google_search()

    for tool in message.tool_calls:
        if tool.function.name == "google_search":
            parsed_arguments = json.loads(tool.function.arguments)
            query = parsed_arguments.get("query", None)  # 获取 query 值
            content = google_search(query)
            messages.append({"role": "tool", "tool_call_id": tool.id, "content": content})

When you use the OpenAI API with function calling, the model never actually executes functions itself - instead,
it simply generates parameters that can be used to call your function.
You are then responsible for handling how the function is executed in your code.

注意: 当您使用 OpenAI API 进行函数调用时,模型实际上并不会自己执行函数——它只是生成可以用于调用您函数的参数。然后,您需要负责处理在代码中如何执行该函数。

3) 第2次调用AI接口

    message = send_messages(messages)
    print(f"Model>\t {message.content}")

下面是第2次调用AI接口,提交的messages信息,从搜索引擎获得的内容萌叔做了简化

[{
    "role": "system",
    "content": "你是一个智能搜索机器人。你根据用户提出的问题,利用google_search函数,获取额外信息,然后回答用户的提问"
}, {
    "role": "user",
    "content": "今天武汉和上海的气温,哪一个更高?"
}, {
    "content": "",
    "role": "assistant",
    "tool_calls": [{
        "id": "call_0_b1edafcc-f274-4a3f-b350-5c879c5a6095",
        "function": {
            "arguments": "{\"query\":\"武汉今天气温\"}",
            "name": "google_search"
        },
        "type": "function",
        "index": 0
    }, {
        "id": "call_1_e02f2dac-1c89-4363-894f-050831f89060",
        "function": {
            "arguments": "{\"query\":\"上海今天气温\"}",
            "name": "google_search"
        },
        "type": "function",
        "index": 1
    }]
}, {
    "role": "tool",
    "tool_call_id": "call_0_b1edafcc-f274-4a3f-b350-5c879c5a6095",
    "content": "武汉今天的最高气温是17°C"
}, {
    "role": "tool",
    "tool_call_id": "call_1_e02f2dac-1c89-4363-894f-050831f89060",
    "content": "上海今天的最高气温是12°C"
}]

可以看到从搜索引擎获得的数据也和指令、提问等数据一起作为上下文发给了大模型。

这里似乎是因为搜索引擎不能正确理解"今天"导致最终返回结果有错误。

3. 延伸

其实大语言模型本身就很擅长从非结构化数据中提取结构化数据,所以我们可以以类似下面的方式提问,也可以获得函数的使用建议。

提示: Function Calling的返回结果与大模型的智商有一定关系,openAI早期模型并不支持Function Calling

后记

2024年01月14日,懒得申请注册google搜索API的,也可以用mock函数替代

# 定义 google_search 函数(示例)
def google_search(query: str) -> str:
    if query.__contains__("武汉"):
        return "15摄氏度"
    elif query.__contains__("上海"):
        return "12摄氏度"

参考资料

1.Function calling
2.google custom search申请注册
3.Assistants 5:Function calling


作者: vearne
文章标题: 给大语言模型插上翅膀
发表时间: 2025年01月14日
文章链接: https://vearne.cc/archives/40245
版权说明: CC BY-NC-ND 4.0 DEED


微信公众号

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注