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