你好,我是 shengjk1,多年大廠經(jīng)驗,努力構(gòu)建 通俗易懂的、好玩的編程語言教程。 歡迎關(guān)注!你會有如下收益:
- 了解大廠經(jīng)驗
- 擁有和大廠相匹配的技術(shù)等 希望看什么,評論或者私信告訴我!
一、 前言
GPT-4 出來后的一大特色就是 Function call,一直想去嘗試一下。后來智普出來了 GLM-4 模型也支持了 Function call,所以就來試一下。
在正式的了解這一塊之前呢,我一直以為 Function call 就是大模型可以執(zhí)行函數(shù)呢,從有了這個概念之后到目前為止一直沒有想明白,大模型是如何執(zhí)行函數(shù),腦海里的想法是:大模型調(diào)用 python 解釋器執(zhí)行。
但研讀了 GLM-4 和 GPT-4 的 Function call 之后,發(fā)現(xiàn)不是這樣的
二、函數(shù)調(diào)用
2.1 Function call 功能
函數(shù)調(diào)用功能可以增強模型推理效果或進行其他外部操作,包括信息檢索、數(shù)據(jù)庫操作、知識圖譜搜索與推理、操作系統(tǒng)、觸發(fā)外部操作等工具調(diào)用場景。
需要注意的是,大模型的 Function call 不會執(zhí)行任何函數(shù)調(diào)用,僅返回調(diào)用函數(shù)所需要的參數(shù)。開發(fā)者可以利用模型輸出的參數(shù)在應(yīng)用中執(zhí)行函數(shù)調(diào)用。
2.2 GLM-4 是如何進行函數(shù)調(diào)用的
假設(shè)我們要創(chuàng)建一個具備查詢航班功能的聊天機器人
2.2.1 定義外部函數(shù)
我們定義如下兩個外部函數(shù)供模型選擇調(diào)用:
- 查詢兩地之間某日航班號函數(shù):get_flight_number(departure: str, destination: str, date: str)
- 查詢某航班某日票價函數(shù):get_ticket_price(flight_number: str, date: str)
def get_flight_number(date:str , departure:str , destination:str):
flight_number = {
"北京":{
"上海" : "1234",
"廣州" : "8321",
},
"上海":{
"北京" : "1233",
"廣州" : "8123",
}
}
return { "flight_number":flight_number[departure][destination] }
def get_ticket_price(date:str , flight_number:str):
return {"ticket_price": "1000"}
2.2.2 描述函數(shù)功能
為了向模型描述外部函數(shù)庫,需要向 tools 字段傳入可以調(diào)用的函數(shù)列表。參數(shù)如下表:
| 參數(shù)名稱 | 類型 | 是否必填 | 參數(shù)說明 |
|---|---|---|---|
| type | String | 是 | 設(shè)置為function |
| function | Object | 是 | |
| name | String | 是 | 函數(shù)名稱 |
| description | String | 是 | 用于描述函數(shù)功能。模型會根據(jù)這段描述決定函數(shù)調(diào)用方式。 |
| parameters | Object | 是 | parameters字段需要傳入一個 Json Schema 對象,以準確地定義函數(shù)所接受的參數(shù)。若調(diào)用函數(shù)時不需要傳入?yún)?shù),省略該參數(shù)即可。 |
| required | 否 | 指定哪些屬性在數(shù)據(jù)中必須被包含。 |
樣例:
tools = [
{
"type": "function",
"function": {
"name": "get_flight_number",
"description": "根據(jù)始發(fā)地、目的地和日期,查詢對應(yīng)日期的航班號",
"parameters": {
"type": "object",
"properties": {
"departure": {
"description": "出發(fā)地",
"type": "string"
},
"destination": {
"description": "目的地",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "departure", "destination", "date" ]
},
}
},
{
"type": "function",
"function": {
"name": "get_ticket_price",
"description": "查詢某航班在某日的票價",
"parameters": {
"type": "object",
"properties": {
"flight_number": {
"description": "航班號",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "flight_number", "date"]
},
}
},
]
2.3 Function call 代碼編寫
函數(shù)描述
tools = [
{
"type": "function",
"function": {
"name": "get_flight_number",
"description": "根據(jù)始發(fā)地、目的地和日期,查詢對應(yīng)日期的航班號",
"parameters": {
"type": "object",
"properties": {
"departure": {
"description": "出發(fā)地",
"type": "string"
},
"destination": {
"description": "目的地",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "departure", "destination", "date" ]
},
}
},
{
"type": "function",
"function": {
"name": "get_ticket_price",
"description": "查詢某航班在某日的票價",
"parameters": {
"type": "object",
"properties": {
"flight_number": {
"description": "航班號",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "flight_number", "date"]
},
}
},
]
創(chuàng)建 client
沒有 key 可以自己去 智普開發(fā)平臺注冊一下,目前注冊送 100萬 token
client = ZhipuAI(api_key='')
請求模型,這一塊僅僅是一個中間步驟
我們想查詢2024年1月20日從北京前往上海的航班。我們向模型提供這個信息:
messages = []
messages.append({"role": "user", "content": "幫我查詢從2024年1月20日,從北京出發(fā)前往上海的航班"})
response = client.chat.completions.create(
model="glm-4", # 填寫需要調(diào)用的模型名稱
messages=messages,
tools=tools,
tool_choice='auto'
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
關(guān)于 tool_choice 如果不寫,則默認情況下模型將決定何時適合使用其中一個函數(shù)。
如果要控制模型如何選擇函數(shù)調(diào)用,需要設(shè)置 tool_choice 參數(shù)。參數(shù)默認值為auto,此時模型根據(jù)上下文信息自行選擇是否返回函數(shù)調(diào)用。
若將其設(shè)置為 {"name": "your_function_name"} 時,可以強制 API 返回特定函數(shù)的調(diào)用。
還可以通過將 tool_choice 參數(shù)設(shè)置為 "none" 來強制 API 不返回任何函數(shù)的調(diào)用。
輸出
content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8495942909317716104', function=Function(arguments='{"date":"2024-01-20","departure":"北京","destination":"上海"}', name='get_flight_number'), type='function')]
可以看到此時模型成功觸發(fā)對 get_flight_number 函數(shù)的調(diào)用 參數(shù)為:date="2024-01-20",departure="北京",destination="上海"
定義處理 Function call 的函數(shù),這參數(shù)高潮,其實所謂的 Function call,就是通過大模型選擇函數(shù)以及獲取函數(shù)的參數(shù)
def parse_function_call(model_response,messages):
# 處理函數(shù)調(diào)用結(jié)果,根據(jù)模型返回參數(shù),調(diào)用對應(yīng)的函數(shù)。
# 調(diào)用函數(shù)返回結(jié)果后構(gòu)造tool message,再次調(diào)用模型,將函數(shù)結(jié)果輸入模型
# 模型會將函數(shù)調(diào)用結(jié)果以自然語言格式返回給用戶。
if model_response.choices[0].message.tool_calls:
tool_call = model_response.choices[0].message.tool_calls[0]
args = tool_call.function.arguments
function_result = {}
if tool_call.function.name == "get_flight_number":
function_result = get_flight_number(**json.loads(args))
if tool_call.function.name == "get_ticket_price":
function_result = get_ticket_price(**json.loads(args))
messages.append({
"role": "tool",
"content": f"{json.dumps(function_result)}",
"tool_call_id":tool_call.id
})
response = client.chat.completions.create(
model="glm-4", # 填寫需要調(diào)用的模型名稱
messages=messages,
tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
請求模型獲取最終結(jié)果
查詢北京到廣州的航班:
# 清空對話
messages = []
messages.append({"role": "system", "content": "不要假設(shè)或猜測傳入函數(shù)的參數(shù)值。如果用戶的描述不明確,請要求用戶提供必要信息"})
messages.append({"role": "user", "content": "幫我查詢1月23日,北京到廣州的航班"})
response = client.chat.completions.create(
model="glm-4", # 填寫需要調(diào)用的模型名稱
messages=messages,
tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
parse_function_call(response,messages)
返回
content=None role='assistant' tool_calls=[CompletionMessageToolCall(id='call_8282666790542042140', function=Function(arguments='{"date":"2023-01-23","departure":"北京","destination":"廣州"}', name='get_flight_number'), type='function')]
content='根據(jù)您的要求,我已經(jīng)查詢到了1月23日從北京到廣州的航班號,航班號為8321。' role='assistant' tool_calls=None
查詢票價也是同理
三、完整代碼
def get_flight_number(date:str , departure:str , destination:str):
flight_number = {
"北京":{
"上海" : "1234",
"廣州" : "8321",
},
"上海":{
"北京" : "1233",
"廣州" : "8123",
}
}
return { "flight_number":flight_number[departure][destination] }
def get_ticket_price(date:str , flight_number:str):
return {"ticket_price": "1000"}
tools = [
{
"type": "function",
"function": {
"name": "get_flight_number",
"description": "根據(jù)始發(fā)地、目的地和日期,查詢對應(yīng)日期的航班號",
"parameters": {
"type": "object",
"properties": {
"departure": {
"description": "出發(fā)地",
"type": "string"
},
"destination": {
"description": "目的地",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "departure", "destination", "date" ]
},
}
},
{
"type": "function",
"function": {
"name": "get_ticket_price",
"description": "查詢某航班在某日的票價",
"parameters": {
"type": "object",
"properties": {
"flight_number": {
"description": "航班號",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "flight_number", "date"]
},
}
},
]
client = ZhipuAI(api_key='')
def parse_function_call(model_response,messages):
# 處理函數(shù)調(diào)用結(jié)果,根據(jù)模型返回參數(shù),調(diào)用對應(yīng)的函數(shù)。
# 調(diào)用函數(shù)返回結(jié)果后構(gòu)造tool message,再次調(diào)用模型,將函數(shù)結(jié)果輸入模型
# 模型會將函數(shù)調(diào)用結(jié)果以自然語言格式返回給用戶。
if model_response.choices[0].message.tool_calls:
tool_call = model_response.choices[0].message.tool_calls[0]
args = tool_call.function.arguments
function_result = {}
if tool_call.function.name == "get_flight_number":
function_result = get_flight_number(**json.loads(args))
if tool_call.function.name == "get_ticket_price":
function_result = get_ticket_price(**json.loads(args))
messages.append({
"role": "tool",
"content": f"{json.dumps(function_result)}",
"tool_call_id":tool_call.id
})
response = client.chat.completions.create(
model="glm-4", # 填寫需要調(diào)用的模型名稱
messages=messages,
tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
# 清空對話
messages = []
messages.append({"role": "system", "content": "不要假設(shè)或猜測傳入函數(shù)的參數(shù)值。如果用戶的描述不明確,請要求用戶提供必要信息"})
messages.append({"role": "user", "content": "幫我查詢1月23日,北京到廣州的航班"})
response = client.chat.completions.create(
model="glm-4", # 填寫需要調(diào)用的模型名稱
messages=messages,
tools=tools,
)
print(response.choices[0].message)
messages.append(response.choices[0].message.model_dump())
parse_function_call(response,messages)
四、總結(jié)
本文介紹了大模型 Function call 功能的基本概念和使用方法,包括定義外部函數(shù)、描述函數(shù)功能、代碼編寫等。同時,文章還提到了如果沒有 Function call,類似的問題也可以通過其他方式解決。本文適合初學(xué)者了解大模型 Function call 功能。
整體的過程:大模型 Function call 其實就是通過大模型找到對應(yīng)的函數(shù),然后再把函數(shù)執(zhí)行后的結(jié)果,返回給大模型,最后大模型給出結(jié)果。
五、擴展
如果沒有 Fcuntion call,類似的問題能解決嗎?當(dāng)然可以。
這里呢,我們葫蘆AI終身免費使用GPT-4來看一下效果
我們輸入 promot
現(xiàn)在有兩個函數(shù)的描述:
{
"type": "function",
"function": {
"name": "get_flight_number",
"description": "根據(jù)始發(fā)地、目的地和日期,查詢對應(yīng)日期的航班號",
"parameters":
"type": "object",
"properties": {
"departure": {
"description": "出發(fā)地",
"type": "string"
},
"destination": {
"description": "目的地",
"type": "string"
},
"date": {
"description": "日期",
"type": "string",
}
},
"required": [ "departure", "destination", "date" ]
},
}
},
依據(jù)上述兩個函數(shù)的描述,我想查一下從北京到天津 20240322 的航班,請問該選擇哪個函數(shù),并以 json 的格式返回函數(shù)對應(yīng)的參數(shù)
返回結(jié)果

所以 Function call 的技術(shù)復(fù)雜度有多少,自然一目了然