Simulate-Function-Call

去年本来已经试过function-calling,但是今年Manus火的时候测试了一下开源的OpenManus和OWL,然后发现一些大模型挂载不上去的原因是不支持function-calling,但是function-calling又不是什么高科技,支持一下有那么难么,那么多大模型都不支持?于是起了歪心思:用prompt让大模型假装支持function-calling行不行?

实测下来可以是可以的,只不过不支持function-calling的大模型有一些会在传递tools给它的时候直接返回400错误拒绝:

1
openai.BadRequestError: Error code: 400 - {'error': {'code': 'InvalidParameter', 'message': 'The parameter `` specified in the request are not valid: the requested model does not support function calling, please switch to a different model or contact with administrator if you believe this model should support function calling. Request id: 021741797140450d83cd2cf188667eeb29a001f49ec0ab19e1bf4', 'param': '', 'type': 'BadRequest'}}

另一些则直接忽略tools信息。但是无论如何都不会响应出来tool_calls类型的回复。因此无论如何在prompt之外还是需要一点点特殊处理:

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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
from openai import OpenAI
import json
from itertools import permutations, product
import operator

# 加载API密钥
with open('../keys.json', 'r') as f:
provider = json.load(f)["lkeap"]

# 定义计算24点的函数
def calc_24(nums):
# 定义运算符映射
op_map = {
'+': operator.add,
'-': operator.sub,
'*': operator.mul,
'/': operator.truediv
}

# 递归函数,用于计算表达式的值
def evaluate_expression(expr):
try:
return eval(expr)
except ZeroDivisionError:
return None

# 穷举所有可能的表达式
for nums_perm in permutations(nums):
for ops_perm in product(list(op_map.keys()), repeat=3):
# 构造所有可能的表达式
expressions = [
f"({nums_perm[0]} {ops_perm[0]} {nums_perm[1]}) {ops_perm[1]} ({nums_perm[2]} {ops_perm[2]} {nums_perm[3]})",
f"({nums_perm[0]} {ops_perm[0]} ({nums_perm[1]} {ops_perm[1]} {nums_perm[2]})) {ops_perm[2]} {nums_perm[3]}",
f"{nums_perm[0]} {ops_perm[0]} (({nums_perm[1]} {ops_perm[1]} {nums_perm[2]}) {ops_perm[2]} {nums_perm[3]})",
f"(({nums_perm[0]} {ops_perm[0]} {nums_perm[1]}) {ops_perm[1]} {nums_perm[2]}) {ops_perm[2]} {nums_perm[3]}",
f"{nums_perm[0]} {ops_perm[0]} ({nums_perm[1]} {ops_perm[1]} ({nums_perm[2]} {ops_perm[2]} {nums_perm[3]}))",
]

# 尝试计算每个表达式
for expr in expressions:
result = evaluate_expression(expr)
if result is not None and abs(result - 24) < 1e-6:
return True, expr.replace('/', '÷')

return False, ""

# 定义检查是否能计算出24点的函数
def can_make_24(cards):
cards = [float(card) for card in cards]
can, steps = calc_24(cards)
if can:
return f"Yes, the calculation steps are: {steps}"
else:
return "No"

# 定义工具列表
tools = [
{
'type': 'function',
'function': {
'name': 'can_make_24',
'description': 'Determine if four numbers can be used to calculate 24',
'parameters': {
'type': 'object',
'properties': {
'cards': {
'type': 'array',
'description': 'Four numbers',
'items': {
'type': 'integer',
},
},
},
'required': ['cards'],
},
}
}
]

def function_call_example(prompt):
messages = [{'role': 'user', 'content': prompt}]
client = OpenAI(api_key=provider["key"], base_url=provider["base"])
try:
response = client.chat.completions.create(
model=provider["models"][0],
messages=messages,
temperature=0.01,
top_p=0.95,
stream=False,
tool_choice = "auto",
tools=tools)
tool_calls= response.choices[0].message.tool_calls
if tool_calls is None:
#print("response:", response)
print("当前模型不支持函数调用,尝试用模拟函数调用方式")
return function_call_simulate(prompt)
except Exception as e:
print("Error message:", e.message)
print("发生了异常,有一些模型会用这种方式来拒绝函数调用,尝试用模拟函数调用方式")
return function_call_simulate(prompt)

func1_name = response.choices[0].message.tool_calls[0].function.name
func1_args = response.choices[0].message.tool_calls[0].function.arguments
func1_args = json.loads(func1_args) # 将字符串转换为字典
func1_out = eval(f'{func1_name}(**func1_args)') # 调用函数
print("函数调用结果:",func1_out)

# 将函数调用的结果添加到消息中
messages.append({
'role': 'assistant',
'content':func1_out
})
messages.append({
'role': 'user',
'content':'翻译assistant返回的结果,如果题目是有解的详细解释计算过程,浮点数如果有可能尽量显示为整数'
})

# 再次调用模型以完成对话
response = client.chat.completions.create(
model=provider["models"][0],
messages=messages,
temperature=0.01,
top_p=0.95,
stream=False)
return response.choices[0].message.content


def function_call_simulate(p):
prompt = '''请按步骤处理问题:
1,识别用户意图对应的函数,函数名和参数列表在assistant中定义。
2,提取参数,如果有必要可以对提取的参数进行优化以确保格式和类型正确
3,按以下JSON格式输出,不要附加任何与JSON内容无关的额外的标记、说明或者装饰:
[{"id":"call_{random-id}", "function":{"arguments":"{parameters}", "name":"{functionName}"}, "type":"function", "index":{index}}]
4 要处理的问题是:'''
messages = [{'role': 'user', 'content': prompt+p},
{'role': 'assistant', 'content':json.dumps(tools)}]
client = OpenAI(api_key=provider["key"], base_url=provider["base"])
response = client.chat.completions.create(
model=provider["models"][0],
messages=messages,
temperature=0.01,
top_p=0.95,
stream=False)
# 解析函数调用
clean_string = response.choices[0].message.content.strip("'''").strip('"""').strip()
start_index = clean_string.find("[")
end_index = clean_string.rfind("]")
if start_index != -1 and end_index != -1:
json_content = clean_string[start_index:end_index + 1] # 包含最后一个 ]
# 解析 JSON 内容
try:
tool_calls = json.loads(json_content)
func1_name = tool_calls[0]['function']['name']
func1_args = tool_calls[0]['function']['arguments']
func1_args = json.loads(func1_args) # 将字符串转换为字典
func1_out = eval(f'{func1_name}(**func1_args)') # 调用函数
print("函数调用结果:",func1_out)

# 将函数调用的结果添加到消息中
messages.append({
'role': 'assistant',
'content':func1_out
})
messages.append({
'role': 'user',
'content':'翻译assistant返回的结果,如果题目是有解的详细解释计算过程,浮点数如果有可能尽量显示为整数'
})

# 再次调用模型以完成对话
response = client.chat.completions.create(
model=provider["models"][0],
messages=messages,
temperature=0.01,
top_p=0.95,
stream=False)
return response.choices[0].message.content


except json.JSONDecodeError as e:
print("内容不符合json格式", e)
else:
print("未找到有效的 JSON 内容")




# 测试函数调用
prompt = "用中文回答:对于扑克牌 3, 3, 8, 8 是否能计算出24点?"
print(function_call_example(prompt))

function_call_simulate函数用prompt实现了function-calling过程的模拟,随后的逻辑处理了模拟的返回结果,然后去执行了真的function-calling。