前言
Function Call是大模型与外部工具(例如用户定义的函数或 API)的连接桥梁,是大语言模型具备的第三类能力(前两类是对话和文本生成)。使用此类能力,用户可以轻松构建满足特定用例和实际问题的应用程序。
Function Call本质上是提供了一种控制 LLM 输出的方法,它可以更可靠地生成结构化响应。
Function Call的使用场景
Function Call的使用场景有很多,以下是常见的3类:
- 创建一个可以调用外部 API 来回答问题的助手,例如定义像send_email(to: string, body: string)或这样的函数get_current_weather(location: string, unit: 'celsius' | 'fahrenheit')
- 将自然语言转换为 API 调用,例如,将“谁是我的顶级客户?”转换为get_customers(min_revenue: int, created_before: string, limit: int)并调用您的内部 API
- 从文本中提取结构化数据,例如定义一个名为的函数extract_data(name: string, birthday: string),或者sql_query(query: string)
误区
Function Call顾名思义,函数调用,一般会理解为让大模型调用函数。其实,LLM 本身不会调用这些函数,而是创建一个 JSON 对象,用户可以使用该对象在其代码中调用这些函数。
使用流程
使用Function Call的步骤主要有以下4步
1.用户组装json参数,其中包含2个信息,函数的信息、问题
2.大模型决定是否需要调用函数
3.如果需要执行函数,则从第一部的问题中提取出相应的函数名称和参数
4.用户执行函数
可以看到,执行函数的动作是在用户端,接下来我们通过代码示例进一步理解Function Call
使用星火大模型提供的示例代码
以下代码是星火大模型官方提供的代码,参见文档
import _thread as thread
import base64
import datetime
import hashlib
import hmac
import json
from urllib.parse import urlparse
import ssl
from datetime import datetime
from time import mktime
from urllib.parse import urlencode
from wsgiref.handlers import format_date_time
import websocket # 使用websocket_client
answer = ""
class Ws_Param(object):
# 初始化
def __init__(self, APPID, APIKey, APISecret, Spark_url):
self.APPID = APPID
self.APIKey = APIKey
self.APISecret = APISecret
self.host = urlparse(Spark_url).netloc
self.path = urlparse(Spark_url).path
self.Spark_url = Spark_url
# 生成url
def create_url(self):
# 生成RFC1123格式的时间戳
now = datetime.now()
date = format_date_time(mktime(now.timetuple()))
# 拼接字符串
signature_origin = "host: " + self.host + "\n"
signature_origin += "date: " + date + "\n"
signature_origin += "GET " + self.path + " HTTP/1.1"
# 进行hmac-sha256进行加密
signature_sha = hmac.new(self.APISecret.encode('utf-8'), signature_origin.encode('utf-8'),
digestmod=hashlib.sha256).digest()
signature_sha_base64 = base64.b64encode(signature_sha).decode(encoding='utf-8')
authorization_origin = f'api_key="{self.APIKey}", algorithm="hmac-sha256", headers="host date request-line", signature="{signature_sha_base64}"'
authorization = base64.b64encode(authorization_origin.encode('utf-8')).decode(encoding='utf-8')
# 将请求的鉴权参数组合为字典
v = {
"authorization": authorization,
"date": date,
"host": self.host
}
# 拼接鉴权参数,生成url
url = self.Spark_url + '?' + urlencode(v)
# 此处打印出建立连接时候的url,参考本demo的时候可取消上方打印的注释,比对相同参数时生成的url与自己代码生成的url是否一致
return url
# 收到websocket错误的处理
def on_error(ws, error):
print("### error:", error)
# 收到websocket关闭的处理
def on_close(ws, one, two):
print(" ")
# 收到websocket连接建立的处理
def on_open(ws):
thread.start_new_thread(run, (ws,))
def run(ws, *args):
data = json.dumps(gen_params(appid=ws.appid, domain=ws.domain, question=ws.question))
ws.send(data)
# 收到websocket消息的处理
def on_message(ws, message):
# print(message)
data = json.loads(message)
code = data['header']['code']
if code != 0:
print(f'请求错误: {code}, {data}')
ws.close()
else:
choices = data["payload"]["choices"]
status = choices["status"]
content = choices["text"][0]["content"]
print(content, end="")
global answer
answer += content
# print(1)
if status == 2:
ws.close()
def gen_params(appid, domain, question):
"""
通过appid和用户的提问来生成请参数
"""
data = {
"header": {
"app_id": appid,
"uid": "1234"
},
"parameter": {
"chat": {
"domain": domain,
"temperature": 0.5,
"max_tokens": 2048
}
},
"payload": {
"message": {
"text": [
{"role": "user", "content": question}
]
},
"functions": {
"text": [
{
"name": "get_weather",
"description": "天气插件可以提供天气相关信息。你可以提供指定的地点信息、指定的时间点或者时间段信息,来精准检索到天气信息。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点,比如北京。"
},
"date": {
"type": "string",
"description": "日期。"
}
},
"required": [
"location"
]
}
},
{
"name": "get_tax_rate",
"description": "税率查询可以查询某个地方的个人所得税率情况。你可以提供指定的地点信息、指定的时间点,精准检索到所得税率。",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "地点,比如北京。"
},
"date": {
"type": "string",
"description": "日期。"
}
},
"required": [
"location"
]
}
}
]
}
}
}
return data
def main(appid, api_key, api_secret, Spark_url, domain, question):
print("星火:")
wsParam = Ws_Param(appid, api_key, api_secret, Spark_url)
wsUrl = wsParam.create_url()
ws = websocket.WebSocketApp(wsUrl, on_message=on_message, on_error=on_error, on_close=on_close, on_open=on_open)
ws.appid = appid
ws.question = question
ws.domain = domain
ws.run_forever(sslopt={"cert_reqs": ssl.CERT_NONE})
通过代码,可以清晰的看到payload.functions.text部分是要注册的函数的信息,payload.message.text是提问的问题,这两部分信息要同时提交给大模型。
使用讯飞星火大模型接入库 (spark-ai-python)
用户也可以通过讯飞官方提供的SDK方便的调用星火大模型,具体可参考spark-ai-python
import json
import os
from sparkai.core.messages import ChatMessage
from sparkai.core.utils.function_calling import convert_to_openai_tool, convert_to_openai_function
from sparkai.llm.llm import ChatSparkLLM, ChunkPrintHandler
def multiply(a, b: int) -> int:
"""你是一个乘法计算器,可以帮我计算两个数的乘积,例如:计算1乘1等于几或计算1*1等于几
Args:
a: 输入a
b: 输入b
Return:
返回 a*b 结果
"""
print("hello success")
return a * b
def test_function_call(question:str):
from sparkai.core.callbacks import StdOutCallbackHandler
messages = [
{'role': 'user',
'content': "帮我算下 12乘以12"},
{'role': 'user',
'content': question}]
spark = ChatSparkLLM(
spark_api_url='ws://spark-api.xf-yun.com/v3.5/chat',
spark_app_id='app_id',
spark_api_key='api_key',
spark_api_secret='api_secret',
spark_llm_domain='generalv3.5',
streaming=False,
)
function_definition = [convert_to_openai_function(multiply)]
messages = [
ChatMessage(role="user",content=messages[0]['content']),
]
handler = ChunkPrintHandler()
a = spark.generate([messages], callbacks=[handler], function_definition=function_definition)
print(a)
func_call_content = a.generations[0][0].message.function_call
print(func_call_content)
# print(a.llm_output)
return func_call_content
SDK使用方式及其方便,推荐使用。不过,方便的同时也隐藏了一些细节,如果要对细节感兴趣,可以研究示例代码。