系列文章目录
GLM-4 (1) - 推理+概览
GLM-4 (2) - RoPE
GLM-4 (3) - GLMBlock
GLM-4 (4) - SelfAttention
GLM-4 (5) - API & Function Calling
前言
我们之前解析了GLM-4
模型相关的部分,这有助于我们对理解和使用开源大模型。然后,有一些场景对于大模型的性能(比如某个任务的推理准确率)有较高的要求,10B
以下参数的模型很可能无法胜任。那么就有两条路可以选择:1)收集与任务相关的数据,微调该模型;2)直接使用商业化的API
服务。由于第一条路收集数据工作量较大,所以调用API
是个不错的选择,也是本篇要讲述的内容。
大模型并不是万能的,推理的时候也会出错,比如近期的热搜就是大模型比较9.11
和9.8
大小的时候变成弱智。但是,大模型有Function Calling
的加持,调用外部函数,就能有效缓解这些问题。
本文将展示glm-4
、gpt-4o
以及deepseek coder v2
大模型的API
的调用,以及Function Calling
的集成。参考链接如下:
zhipuai:https://open.bigmodel.cn/dev/howuse/functioncall
openai:https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?tabs=python
deepseek:https://platform.deepseek.com/usage
微调大模型function calling:https://blog.csdn.net/weixin_40959890/article/details/140137952
一、获取API KEY
想要使用大模型API
服务,需要去各厂商官网上获取API KEY
。这比较容易,不再赘述。值得一提的是,智谱
和deepseek
只要注册就会有免费的资源包。对于gpt-4o
,我使用的是Azure
提供的服务。使用的API KEY
和模型如下:
ZHIPU_API_KEY = "xxxxx.xxxxx"
DEEPSEEK_API_KEY = "sk-xxxxx"
# Azure的服务
OPENAI_API_KEY = "xxxxx"
OPENAI_ENDPOINT = "https://xxxxx"
# 系列模型,在前面的效果好一些
ZHIPU_MODELS = ["glm-4-0520", "glm-4"]
DEEPSEEK_MODELS = ["deepseek-coder", "deepseek-chat"]
OPENAI_MODELS = ["gpt-4o"]
二、API调用 & Function Calling使用
1.引入库
我在类LLMRequestWithFunctionCalling
中集成了上述几种大模型的API
调用,且包含了Function Calling
(这里deepseek
不支持)。API
调用可以是SDK调用,也可以是HTTP
调用。这边在使用智谱
和deepseek
模型时,选择了SDK调用,对于Azure gpt-4o
则采用了HTTP
调用。
# function_calling.py
class LLMRequestWithFunctionCalling:
"""
在使用llm的时候,使用function calling以满足你的需求
有两种方式实现function calling:
1) function call: https://techcommunity.microsoft.com/t5/ai-azure-ai-services-blog/function-calling-is-now-available-in-azure-openai-service/ba-p/3879241
2)tool call: https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?tabs=python
我们这里使用tool call
"""
def __init__(self, system_prompt: str, prompt: str, tools: List[Dict] = None, model: str = "glm-4"):
# 系统提示
self.system_prompt = system_prompt
# 用户提示
self.prompt = prompt
# tools
self.tools = tools
# # 函数functions
# self.functions = [tool.get("function") for tool in tools if tool.get("type") == "function"]
# 使用的模型
self.model = model
# 消息
self.messages: List[Dict] = []
# 客户端
self.client: Union[OpenAI, ZhipuAI, None] = None
# http请求 -> 这是Azure的gpt-4o的请求方式
self.http_args: Union[Dict[str, Dict], None] = None
# 函数名称
self.function_names: List = []
# 初始化
self._init()
def _init(self):
"""
初始化
"""
# client & http_args
assert self.model in ZHIPU_MODELS + DEEPSEEK_MODELS + OPENAI_MODELS
if self.model in ZHIPU_MODELS + DEEPSEEK_MODELS:
if self.model in ZHIPU_MODELS:
# 使用智谱api
self.client = ZhipuAI(api_key=ZHIPU_API_KEY) # 填写您自己的APIKey
else:
# 使用deepseek api
self.client = OpenAI(api_key=DEEPSEEK_API_KEY, base_url="https://api.deepseek.com")
else:
# 使用Azure的gpt-4o api
self.http_args = {
"headers": {
"Content-Type": "application/json",
"api-key": OPENAI_API_KEY,
},
"generate_args": {
"temperature": 0.7,
"top_p": 0.95,
"max_tokens": 800
} # 这边设置了一些生成参数,当然上面client也是可以设置的
}
# messages
# 这是必备消息
self.messages.append({"role": "system", "content": self.system_prompt})
self.messages.append({"role": "user", "content": self.prompt})
# function name抽取一下
for tool in self.tools:
self.function_names.append(tool["function"]["name"])
def _llm_request(self,
messages: List[Dict],
tools: Optional[List[Dict]] = None) -> Union[ChatCompletion, Completion, Dict, None]:
"""
使用llm api请求
"""
if self.client is not None:
try:
response = self.client.chat.completions.create(
model=self.model, # 模型名称
messages=messages,
stream=False,
tools=tools,
# tool_choice="auto"
)
except Exception as e:
print("response error")
response = None
else:
# Azure提供的api服务
print()
headers = self.http_args["headers"]
payload = {
"messages": messages,
}
if tools is not None:
# function call
# functions = [tool.get("function") for tool in tools if tool.get("type") == "function"]
# payload.update({
# "function_call": "auto",
# "functions": functions
# })
# tool call
payload.update({
"tools": tools,
"tool_choice": "auto"
})
payload.update(**self.http_args["generate_args"])
# Send request
try:
response = requests.post(OPENAI_ENDPOINT, headers=headers, json=payload)
response.raise_for_status() # Will raise an HTTPError if the HTTP request returned an unsuccessful status code
except requests.RequestException as e:
# raise SystemExit(f"Failed to make the request. Error: {e}")
print(f"Failed to make the request. Error: {e}")
response = None
if response is not None:
# 转成dict, eg:{'choices': [{'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}, 'finish_reason': 'stop', 'index': 0, 'logprobs': None, 'message': {'content': '中国最发达的城市之一是上海。上海是中国的经济、金融、贸易和航运中心。它拥有世界上最繁忙的港口之一,同时也是许多跨国公司在中国的总部所在地。除了上海,北京、深圳和广州也被认为是中国最发达的城市之一。北京是中国的政治和文化中心,深圳是科技和创新的前沿城市,而广州则是重要的贸易和制造业中心。这些城市在经济、教育、医疗、基础设施等多个方面都非常发达。', 'role': 'assistant'}}], 'created': 1721784656, 'id': 'chatcmpl-9oL8Cm6q3LNTJcpP4EePv1MK2tEzj', 'model': 'gpt-4o-2024-05-13', 'object': 'chat.completion', 'prompt_filter_results': [{'prompt_index': 0, 'content_filter_results': {'hate': {'filtered': False, 'severity': 'safe'}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': {'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}], 'system_fingerprint': 'fp_abc28019ad', 'usage': {'completion_tokens': 116, 'prompt_tokens': 31, 'total_tokens': 147}}
response = response.json()
return response
def execute(self, use_functions: bool) -> Union[ChatCompletion, Completion, Dict, None]:
"""
执行请求,
当use_functions=True时才会进行函数调用
"""
if not use_functions or not self.tools:
return self._llm_request(self.messages)
if self.model in ZHIPU_MODELS:
return self._execute_zhipuai()
elif self.model in OPENAI_MODELS:
return self._execute_openai()
else:
raise NotImplementedError(f"{self.model} 在此处暂时不支持function calling!")
def _parse_function_call_zhipuai(self, response_to_tools):
"""
解析智谱的function_call
"""
if not response_to_tools.choices[0].message.tool_calls:
return
tool_call = response_to_tools.choices[0].message.tool_calls[0]
args = tool_call.function.arguments
function_result = {}
name = tool_call.function.name
if name in self.function_names:
function_result = eval(name)(**json.loads(args))
# 添加消息
self.messages.append({
"role": "tool",
"content": f"{json.dumps(function_result)}",
"tool_call_id": tool_call.id
})
def _parse_function_call_openai(self, response_to_tools):
"""
解析openai function_call (http请求)
"""
if not response_to_tools:
# todo: 观察字段
return
function_obj = response_to_tools["choices"][0]["message"]["tool_calls"][0]
tool_call_id = function_obj["id"]
function = function_obj["function"]
name = function["name"]
args = function["arguments"]
function_result = {}
if name in self.function_names:
function_result = eval(name)(**json.loads(args))
# 添加消息
self.messages.append({
"role": "tool",
"content": f"{json.dumps(function_result)}",
"tool_call_id": tool_call_id
})
def _execute_zhipuai(self) -> Union[Completion, Dict, None]:
"""
glm系列实现function calling
"""
# 调用tools并响应
response_to_tools = self._llm_request(self.messages, tools=self.tools)
if response_to_tools is None:
raise Exception("智谱函数调用失败")
# {'content': '', 'role': 'assistant', 'tool_calls': [{'id': 'call_202407301506203c337e808ac84619', 'function': {'arguments': '{"date":"2022-01-23","departure":"北京","destination":"广州"}', 'name': 'get_flight_number'}, 'type': 'function', 'index': 0}]}
self.messages.append(response_to_tools.choices[0].message.model_dump())
self._parse_function_call_zhipuai(response_to_tools)
# 以上添加了assistant和tool的响应,接下来根据所有message请求
response = self._llm_request(self.messages, tools=self.tools)
if response is not None:
self.messages.append(response.choices[0].message.model_dump())
return response
def _execute_openai(self) -> Union[ChatCompletion, Dict, None]:
"""
gpt系列(http请求)实现function calling
"""
response_to_tools = self._llm_request(self.messages, tools=self.tools)
if response_to_tools is None:
raise Exception("openai函数调用失败")
self.messages.append(response_to_tools["choices"][0]["message"])
self._parse_function_call_openai(response_to_tools)
response = self._llm_request(self.messages, tools=self.tools)
if response is not None:
self.messages.append(response["choices"][0]["message"])
return response
三、示例
假设这边有两个函数get_flight_number
和get_ticket_price
,我们构建了函数如下方的tools
所示,该结构化数据主要包含函数的名称name
,函数的描述description
,函数入参parameters
。
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"]
},
}
},
]
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"}
我们来让大模型借助Function Calling
来查一下航班:
system_prompt = "不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息"
prompt = "帮我查询1月23日,北京到广州的航班"
llmr = LLMRequestWithFunctionCalling(system_prompt, prompt, tools, "gpt-4o") # or glm-4
res = llmr.execute(use_functions=True)
print(res)
我测试了glm-4
以及gpt-4o
这两个模型,得到llmr.messages
结果如下:
# ------------------- glm-4 ---------------------
[{'role': 'system', 'content': '不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息'},
{'role': 'user', 'content': '帮我查询1月23日,北京到广州的航班'},
{'content': '', 'role': 'assistant', 'tool_calls': [
{'id': 'call_202407301506203c337e808ac84619',
'function': {'arguments': '{"date":"2022-01-23","departure":"北京","destination":"广州"}',
'name': 'get_flight_number'},
'type': 'function',
'index': 0}]},
{'role': 'tool', 'content': '{"flight_number": "8321"}', 'tool_call_id': 'call_202407301506203c337e808ac84619'},
{'content': '根据您的查询,经过API调用,我找到了1月23日从北京到广州的航班号,它是8321。', 'role': 'assistant', 'tool_calls': None}]
# ------------------- gpt-4o ---------------------
[{'role': 'system', 'content': '不要假设或猜测传入函数的参数值。如果用户的描述不明确,请要求用户提供必要信息'},
{'role': 'user', 'content': '帮我查询1月23日,北京到广州的航班'},
{'content': '请稍等,我将为您查询1月23日从北京到广州的航班信息。', 'role': 'assistant',
'tool_calls': [{'function': {
'arguments': '{"departure":"北京","destination":"广州","date":"2024-01-23"}',
'name': 'get_flight_number'},
'id': 'call_znSa2Quwbwze9xtjRebUgSsI',
'type': 'function'}]},
{'role': 'tool', 'content': '{"flight_number": "8321"}', 'tool_call_id': 'call_znSa2Quwbwze9xtjRebUgSsI'},
{'content': '查询到1月23日从北京到广州的航班号是8321。您是否需要查询该航班的票价信息?', 'role': 'assistant'}]
成功!
四、其他
Function Calling
功能现在都是使用tools
和tool_choice
参数来实现的,实际上也可以使用functions
和function_call
实现,比如Azure的example和OpenAI的example(这种方式可能比较老旧);- 上面有两个函数,我们在进行
Function Calling
的时候,tool_choice
参数要么是使用默认值,要么就是"auto"
,这意味着让模型自主选择其中的一个函数;如果将其设置为{"name": "your_function_name"}
时,可以强制API
返回特定函数的调用;根据智谱文档所说,目前它只支持默认的"auto"
; - 该博客中提到了
NexusRavenV2-13B
模型感觉也不错。
总结
本文介绍了大模型API
调用,以及Function Calling
的使用,适用于推理准确性要求较高的场景。