我们知道大模型,不管是国外的chatgpt,还是国内的kimi,都是基于大量的数据训练出来的。这些模型在很多方面都表现的很好,但是在一些特定的场景下,这些模型可能会出现一些问题。比如,这些模型不知道今天发生了什么?不会计算复杂的数字题?
  为了解决这些问题,大模型引入了tool_calls(kimi中的叫法,其它大模型略有不同)。这个工具的作用是,当大模型遇到一些特定的问题时,可以调用这个工具,来解决这些问题。比如,可以调用查询天气的工具,来查询今天的天气。可以调用计算器工具,来计算复杂的数字题。

tool_calls是什么?

  tool_calls是什么?这里引用kimi官方的回答:
  工具调用 tool_calls 给予了 Kimi 大模型执行具体动作的能力。 Kimi 大模型能进行对话聊天并回答用户提出的问题,这是“说”的能力, 而通过工具调用 tool_calls,Kimi 大模型也拥有了“做”的能力, 借助 tool_calls,Kimi 大模型能帮你搜索互联网内容、查询数据库,甚至操作智能家居。 我自己的理解是,我们可以自己定义一个特殊的功能赋予大模型,当大模型遇到这个特殊的功能时,可以调用这个功能来解决问题。 这样,大模型可以解决一些我们需要大模型解决的特定的问题,而不是只能解决它之前 训练的问题,这样大大提高了大模型的灵活性。

tool_calls和大模型结合的流程-以kimi为例(其它大模型为例)

  1. 按照kimi官方的文档,定义一个tool_calls的函数
  2. 在kimi中,指定可以使用的tool_calls的函数
  3. 向kimi提问时,当遇到特定的问题时,大模型判断可以调用tool_calls定义的函数,kimi会调用tool_calls的函数来解决问题
  4. kimi调用tool_calls的函数,拿到tool_calls的函数的返回值.
  5. kimi把tool_calls的函数的返回值,重新提交给大模型,大模型对返回值进行加工,重新加工,最终返回给用户。

注意:3、4、5是使用tool_calls和不使用的区别

通过 JSON Schema 来定义我们的工具

JSON Schema的定义:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
	{
		"type": "function",       # 约定的字段 type,目前支持 function 作为值.这是一个固定值
		"function": {             #  type  function 时,使用 function 字段定义具体的函数内容
			"name": "search",     # 函数的名称,请使用英文大小写字母、数据加上减号和下划线作为函数名称
			"description": """ 
                  函数的介绍	
			""",                  # 函数的介绍,在这里写上函数的具体作用以及使用场景,以便 Kimi 大模型能正确地选择使用哪些函数
			"parameters": {       # 使用 parameters 字段来定义函数接收的参数
				"type": "object", # 固定使用 type: object 来使 Kimi 大模型生成一个 JSON Object 参数
				"required": ["query"], # 使用 required 字段告诉 Kimi 大模型哪些参数是必填项
				"properties": {   # properties 中是具体的参数定义,你可以定义多个参数
					"query": {    # 在这里,key 是参数名称,value 是参数的具体定义
						"type": "string", # 使用 type 定义参数类型
						"description": """
							用户搜索的内容,请从用户的提问或聊天上下文中提取。
						"""       # 使用 description 描述参数以便 Kimi 大模型更好地生成参数
					}
				}
			}
		}
	}

tool_calls嵌入大模型,定义一个调用高德API查询天气的tool_calls

上代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
import os
from typing import *

import json

import httpx
from openai import OpenAI
KIMI_API_KEY = os.getenv("KIMI_API_KEY")
client = OpenAI(
    api_key=KIMI_API_KEY,  # 在这里将 MOONSHOT_API_KEY 替换为你从 Kimi 开放平台申请的 API Key
    base_url="https://api.moonshot.cn/v1",
)

tools = [
    {
        "type": "function",  # 约定的字段 type,目前支持 function 作为值
        "function": {  # 当 type 为 function 时,使用 function 字段定义具体的函数内容
            "name": "search",  # 函数的名称,请使用英文大小写字母、数据加上减号和下划线作为函数名称
            "description": """ 
				通过搜索引擎搜索互联网上的内容。
				当你的知识无法回答用户提出的问题,或用户请求你进行联网搜索时,调用此工具。请从与用户的对话中提取用户想要搜索的内容作为 query 参数的值。
				搜索结果包含网站的标题、网站的地址(URL)以及网站简介。
			""",  # 函数的介绍,在这里写上函数的具体作用以及使用场景,以便 Kimi 大模型能正确地选择使用哪些函数
            "parameters": {  # 使用 parameters 字段来定义函数接收的参数
                "type": "object",  # 固定使用 type: object 来使 Kimi 大模型生成一个 JSON Object 参数
                "required": ["query"],  # 使用 required 字段告诉 Kimi 大模型哪些参数是必填项
                "properties": {  # properties 中是具体的参数定义,你可以定义多个参数
                    "query": {  # 在这里,key 是参数名称,value 是参数的具体定义
                        "type": "string",  # 使用 type 定义参数类型
                        "description": """
							用户搜索的内容,请从用户的提问或聊天上下文中提取。
						"""  # 使用 description 描述参数以便 Kimi 大模型更好地生成参数
                    }
                }
            }
        }
    },
]


def search_impl(city: str) -> List[Dict[str, Any]]:
    """
    search_impl 高德的API接口,根据城市编号,也就是参数city,获取天气信息。
    """
    r = httpx.get("https://restapi.amap.com/v3/weather/weatherInfo?", params={"key":"b267c1a6381c2caa12e5694b69413a6c","extensions":"all","city": city})
    return r.json()


def search(arguments: Dict[str, Any]) -> Any:
    query = arguments["query"]
    result = search_impl(query)
    return {"result": result}

# 通过 tool_map 将每个工具名称及其对应的函数进行映射,以便在 Kimi 大模型返回 tool_calls 时能快速找到应该执行的函数
# 只有这里定义的函数才能被 Kimi 大模型作为tool_calls调用
tool_map = {
    "search": search,
}

#messages定义和常规的对话一样
messages = [
    {"role": "system",
     "content": "你是 Kimi,由 Moonshot AI 提供的人工智能助手,你更擅长中文和英文的对话。你会为用户提供安全,有帮助,准确的回答。同时,你会拒绝一切涉及恐怖主义,种族歧视,黄色暴力等问题的回答。Moonshot AI 为专有名词,不可翻译成其他语言。"},
    {"role": "user", "content": "请联网搜索[query=110000]"}  # 在提问中要求 Kimi 大模型联网搜索
]

finish_reason = None

# 我们的基本流程是,带着用户的问题和 tools 向 Kimi 大模型提问,如果 Kimi 大模型返回了 finish_reason: tool_calls,表示kimi大模型认为这里需要调用tool_calls,并返回调用的tool_calls,则我们执行对应的 tool_calls,
# 将执行结果以 role=tool 的 message 的形式重新提交给 Kimi 大模型,Kimi 大模型根据 tool_calls 结果进行下一步内容的生成。这时就是我们刚刚在[tool_calls和大模型结合的流程-以kimi为例(其它大模型为例)]第4点强调的。
#
#   1. 如果 Kimi 大模型认为当前的工具调用结果已经可以回答用户问题,则返回 finish_reason: stop,我们会跳出循环,打印出 message.content;
#   2. 如果 Kimi 大模型认为当前的工具调用结果无法回答用户问题,需要再次调用工具,我们会继续在循环中执行接下来的 tool_calls,直到 finish_reason 不再是 tool_calls;
#
# 在这个过程中,只有当 finish_reason 为 stop 时,我们才会将结果返回给用户。

while finish_reason is None or finish_reason == "tool_calls":  //这里是一个循环直到finish_reason不再是tool_calls,表示这个答复是大模型生成的不是tool_calls生成的这时把结果返回给用户
    completion = client.chat.completions.create(
        model="moonshot-v1-8k",
        messages=messages,
        temperature=0.3,
        tools=tools,  # <-- 我们通过 tools 参数,将定义好的 tools 提交给 Kimi 大模型
    )
    choice = completion.choices[0]
    finish_reason = choice.finish_reason
    if finish_reason == "tool_calls":                # <-- 判断当前返回内容是否包含 tool_calls。如果包含,我们需要把返回的结果重新提交给 tool_calls
        messages.append(choice.message)              # <-- 添加tool_calls的返回结果,以重新提交给大模型
        for tool_call in choice.message.tool_calls:  # <-- tool_calls 可能是多个,因此我们使用循环逐个执行
            tool_call_name = tool_call.function.name
            tool_call_arguments = json.loads(
                tool_call.function.arguments)         # <-- arguments 是序列化后的 JSON Object,我们需要使用 json.loads 反序列化一下
            tool_function = tool_map[tool_call_name]  # <-- 通过 tool_map 快速找到需要执行哪个函数
            tool_result = tool_function(tool_call_arguments)
                            
            messages.append({
                "role": "tool",                       # 使用函数执行结果构造一个 role=tool 的 message,以此来向模型展示工具调用的结果;
                "tool_call_id": tool_call.id,         # 注意,我们需要在 message 中提供 tool_call_id 和 name 字段,以便 Kimi 大模型,能正确匹配到对应的 tool_call。
                "name": tool_call_name,
                "content": json.dumps(tool_result),  # <-- 我们约定使用字符串格式向 Kimi 大模型提交工具调用结果,因此在这里使用 json.dumps 将执行结果序列化成字符串
            })

print(choice.message.content)  # <-- 在这里,我们才将模型生成的回复返回给用户

执行结果

上边代码可以直接拿来执行,有2个参数需要替换,一个是KIMI_API_KEY,一个是高德的key。这个key可以在高德开放平台申请。

北京市的天气预报显示,2024年9月5日至8日的天气情况如下:

  • 9月5日(星期四):多云转晴,气温29℃/17℃,北风1-3级。
  • 9月6日(星期五):多云转小雨,气温27℃/19℃,南风1-3级。
  • 9月7日(星期六):小雨转阴,气温23℃/18℃,北风1-3级。
  • 9月8日(星期日):多云转多云,气温27℃/18℃,南风1-3级。

请注意,天气情况可能会有变化,建议出行前查看最新的天气预报。