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) # <-- 在这里,我们才将模型生成的回复返回给用户
|