专做外贸的网站,赤峰做网站哪家好,枣庄联通网站备案,网站项目ppt怎么做全文共1w余字#xff0c;预计阅读时间约25~40分钟 | 满满干货(附代码案例)#xff0c;建议收藏#xff01;
本文目标#xff1a;围绕Chat模型的Function calling功能进行更高层次的函数封装#xff0c;并实现一个能够调用外部函数的多轮对话任务 写在前面#xff1a;本文…全文共1w余字预计阅读时间约25~40分钟 | 满满干货(附代码案例)建议收藏
本文目标围绕Chat模型的Function calling功能进行更高层次的函数封装并实现一个能够调用外部函数的多轮对话任务 写在前面本文内容的复现过程如果有条件的建议使用gpt 4接口输出稳定gpt3.5不太稳定但运行几次也能得到标准结果 如果存在Rate limit 报错是OpenAI的速率限制可以绑定信用卡后解除以保证程序正常运行 代码下载地址
一、Function calling 流程优化思路
在大模型开发(十一)Chat Completions模型的Function calling功能详解中已经详细解释了Function calling的用法回顾一下其函数流程是这样的 对于上述这种原始的Function calling实现流程而言尽管过程清晰但要完整跑通一个流程所涉及到的代码环节较多在高频调用该功能的场景中这个复杂的代码流程会极大程度影响使用和开发效率。因此需要考虑如何对上述流程进行优化。
总的来说优化的方向有两个
优化functions参数的编写效率
比如之前写到的这个外部函数
def calculate_algorithm(data):该函数定义了一种特殊的数据集计算过程:param data: 必要参数表示带入计算的数据表用字符串进行表示:return函数计算后的结果返回结果为表示为JSON格式的Dataframe类型对象data io.StringIO(data)df_new pd.read_csv(data, sep\s, index_col0)res np.sum(df_new, axis1) - 1return json.dumps(res.to_string())available_functions {calculate_algorithm: calculate_algorithm,}calculate_function {name: calculate_algorithm,description: 用于执行计算算法的函数定义了一种特殊的数据集计算过程,parameters: {type: object,properties: {data: {type: string,description: 执行计算算法的数据集},},required: [data],},}functions [calculate_function]
functions在手动实现Function calling功能这个过程中将外部函数信息输入到functions参数中的过程非常复杂需要涉及到大量的JSON Schema编写过程并且如果函数库中包含大量函数逐个编写JSON Schema会非常复杂。
因此**找到一种方法大幅降低Chat模型读取外部函数的门槛是非常有必要的。**例如当编写完calculate_algorithm(计算函数)后模型就能让大语言模型自动识别这个函数并创建相应的functions参数而不用手动对其进行编写。
2、优化second response流程
在做交互需求的开发中其实并不关心向大模型提问时中间有几次调用模型的过程只希望在一次对话中快速完成需求即无论是否进行外部函数调用都希望能够在一次代码交互过程中完成对话任务。
因此完成对对话流程进行更高层次的封装是非常有必要的。也就是说如果对话过程需要调用Function calling能自动执行second response并最终在一次对话中返回最终结果。
接下来就针对这两方面依次做一下优化。
二、优化一自动编写函数
functions参数其实是非常一类高度结构化的参数而参数中的核心信息其实都来源于函数本身 因此functions参数的编写其实本质上就是一个翻译的过程将函数原始的说明信息翻译成functions参数要求的格式。
而对于大语言模型(LLMs)来说它对JSON Schema格式的是非常熟悉的
response openai.ChatCompletion.create(modelgpt-3.5-turbo-16k-0613,messages[{role: user, content: 请问什么是JSON Schema}]
)response.choices[0].message[content]看下它的输出 能够看出Chat模型是具备JSON Schema相关知识储备的。由此不难判断只要详细的编写每个函数的函数说明并且通过合理的提示让模型理解functions参数结构同时借助模型本身对JSON Schema的理解是能够让Chat模型自主读取并创建函数的functions参数的。
尝试实现一下
Step 1提取函数说明
首先通过inspect库中的getdoc方法来将函数说明提取为字符串代码如下
import inspectfunction_description inspect.getdoc(calculate_algorithm)
function_description看下提取结果 inspect库中包含了非常多用于辅助验证函数功能的函数能够非常高效的执行获取函数参数、函数源码、函数说明等功能。 Step 2: 测试是否能将函数参数格式转化成JSON Schema类型
response openai.ChatCompletion.create(modelgpt-3.5-turbo-16k-0613,messages[{role: system, content: 以下是计算函数的函数说明%s % function_description},{role: user, content: 请帮我编写一个JSON Schema对象用于说明计算函数的参数输入规范。输出结果要求是JSON Schema格式的JONS类型对象不需要任何前后修饰语句。其中description部分请用中文}]
)
response.choices[0].message[content]看下输出结果
Chat函数的输出结果是JSON格式对象通过json.loads方法将其转化为python对象代码如下
json.loads(response.choices[0].message[content])看下结果 对比手动编写的结果其实模型能够根据函数的参数说明正确识别计算函数的参数格式并输出对应的JSON Schema对象。 additionalProperties’关键词表示不存在另一种输入格式这两个关键词对所描述的对象结构类型并没有任何影响。 Step 3编写自动编写functions参数函数
尝试通过合理的提示让模型能够自动编写functions参数以下是Chat模型提示过程经测试gpt3.5使用Zero-shot输出极不稳定所以使用Few-shot提示
# 定义Few-shot提示
Q1_system_prompt 以下是某函数说明%s % function_description
Q1_user_prompt 根据这个函数的函数说明请帮我创建一个JSON格式的字典这个字典有如下5点要求\1.字典总共有三个键值对\2.第一个键值对的Key是字符串namevalue是该函数的名字%s也是字符串\3.第二个键值对的Key是字符串descriptionvalue是该函数的函数的功能说明也是字符串\4.第三个键值对的Key是字符串parametersvalue是一个JSON Schema对象用于说明该函数的参数输入规范。\5.输出结果必须是一个JSON格式的字典且不需要任何前后修饰语句 % calculate_algorithm.__name__A1_system_prompt 测试算法函数该函数定义了一种特殊的数据集计算过程\n:param data: 必要参数要求字符串类型表示带入计算的数据表\n:return测试函数计算后的结果返回结果为json字符串类型对象
A1_user_prompt {name: testg_algorithm, \description: 测试算法函数该函数定义了一种特殊的数据集计算过程, \parameters: {title: 测试算法函数参数, \type: object, \properties: {data: {description: 字符串类型的数据表, type: string}}, \required: [data]}}system_prompt 以下是某函数说明%s % function_description
user_prompt 根据这个函数的函数说明请帮我创建一个JSON格式的字典这个字典有如下5点要求\1.字典总共有三个键值对\2.第一个键值对的Key是字符串namevalue是该函数的名字%s也是字符串\3.第二个键值对的Key是字符串descriptionvalue是该函数的函数的功能说明也是字符串\4.第三个键值对的Key是字符串parametersvalue是一个JSON Schema对象用于说明该函数的参数输入规范。\5.输出结果必须是一个JSON格式的字典且不需要任何前后修饰语句 % calculate_algorithm.__name__然后带入模型:
response openai.ChatCompletion.create(modelgpt-3.5-turbo-16k-0613,messages[{role: system, content: system_prompt},{role: user, content: user_prompt}]
)
response.choices[0].message[content]来看下最终的结果 这个过程按照格式输出文本语义理解的内容对于大语言模型来说是非常简单的推理过程在JSON Schema对象编写时Few-shot效果会明显好于当前的Zero-shot过程。
Step 4高级函数封装
对上述流程进行更高层次的封装编写一个自动输出functions参数的函数代码如下
def auto_functions(functions_list):Chat模型的functions参数编写函数:param functions_list: 包含一个或者多个函数对象的列表:return满足Chat模型functions参数要求的functions对象def functions_generate(functions_list):# 创建空列表用于保存每个函数的描述字典functions []# 对每个外部函数进行循环for function in functions_list:# 读取函数对象的函数说明function_description inspect.getdoc(function)# 读取函数的函数名字符串function_name function.__name__Q1_system_prompt 以下是某函数说明%s % function_descriptionQ1_user_prompt 根据这个函数的函数说明请帮我创建一个JSON格式的字典这个字典有如下5点要求\1.字典总共有三个键值对\2.第一个键值对的Key是字符串namevalue是该函数的名字%s也是字符串\3.第二个键值对的Key是字符串descriptionvalue是该函数的函数的功能说明也是字符串\4.第三个键值对的Key是字符串parametersvalue是一个JSON Schema对象用于说明该函数的参数输入规范。\5.输出结果必须是一个JSON格式的字典且不需要任何前后修饰语句 % calculate_algorithm.__name__A1_system_prompt 测试算法函数该函数定义了一种特殊的数据集计算过程\n:param data: 必要参数要求字符串类型表示带入计算的数据表\n:return测试函数计算后的结果返回结果为json字符串类型对象A1_user_prompt {name: testg_algorithm, \description: 测试算法函数该函数定义了一种特殊的数据集计算过程, \parameters: {title: 测试算法函数参数, \type: object, \properties: {data: {description: 字符串类型的数据表, type: string}}, \required: [data]}}system_prompt 以下是某函数说明%s % function_descriptionuser_prompt 根据这个函数的函数说明请帮我创建一个JSON格式的字典这个字典有如下5点要求\1.字典总共有三个键值对\2.第一个键值对的Key是字符串namevalue是该函数的名字%s也是字符串\3.第二个键值对的Key是字符串descriptionvalue是该函数的函数的功能说明也是字符串\4.第三个键值对的Key是字符串parametersvalue是一个JSON Schema对象类型为object,用于说明该函数的参数输入规范;\5.输出结果必须是一个JSON格式的字典只输出这个字典即可前后不需要任何前后修饰或说明的语句 % function_nameresponse openai.ChatCompletion.create(modelgpt-3.5-turbo-16k-0613,messages[{role: system, content: Q: Q1_system_prompt Q1_user_prompt A: A1_system_prompt A1_user_prompt },{role: user, content: Q: system_prompt user_prompt}])functions.append(json.loads(response.choices[0].message[content]))return functionsmax_attempts 3attempts 0while attempts max_attempts:try:functions functions_generate(functions_list)break # 如果代码成功执行跳出循环except Exception as e:attempts 1 # 增加尝试次数print(发生错误, e)if attempts max_attempts:print(已达到最大尝试次数程序终止。)raise # 重新引发最后一个异常else:print(正在重新运行...)return functions上述代码把函数功能的主题封装在functions_generate这个内嵌函数中然后外层函数主要控制报错时的处理流程即如果函数执行时报错三次内报错都会反复调用执行functions_generate三次报错之后则会直接停止运行。这里之所以要设置多次报错仍然反复执行是因为哪怕user_prompt中明确指出“只输出这个字典即可前后不需要任何前后修饰或说明的语句”但模型仍然可能会输出前后说明文字此时是无法直接使用functions.append(json.loads(response.choices[0].message[‘content’]))来提取functions字典的但这只是小概率事件再次进行相同问题的提问输出的结果大概率不会再包含前后修饰语句functions_generate即可正常运行。
Step 5单个外部函数带入测试
先提取到JSON Schema格式的描述
functions_list [calculate_algorithm]functions auto_functions(functions_list)functions输出如下 接下来测试将其带入Chat模型验证模型是否能顺利执行Function calling功能代码如下
# 创建一个DataFrame
df pd.DataFrame({x1:[1, 2], x2:[3, 4]})# 函数输出的结构都必须是字符串类型才能够被大模型正常的识别
df_str df.to_string()df_strmessages[{role: system, content: 数据集data%s数据集以字符串形式呈现 % df_str},{role: user, content: 请在数据集data上执行计算算法}
]response openai.ChatCompletion.create(modelgpt-3.5-turbo-16k-0613,messagesmessages,functionsfunctions,function_callauto, )
response[choices][0][message]看下输出 模型返回结果中存在function_call则说明模型完成了外部模型的挑选顺利执行了Function calling功能也进而说明自动functions参数编写函数切实有效。
Step 6多个外部函数带入测试
接下来进一步测试当添加多个外部函数时auto_functions函数能否顺利的依次翻译这些函数说明并组成functions列表同时在多个函数情况下模型能否根据实际对话需求智能选择外部函数代码如下
def smallC_algorithm(data):smallC算法函数该函数定义了一种特殊的数据集计算过程:param data: 必要参数表示带入计算的数据表用字符串进行表示:returnsmallC函数计算后的结果返回结果为表示为JSON格式的Dataframe类型对象df_new pd.read_json(data)res np.sum(df_new, axis1) 1return res.to_json(orientrecords)functions_list [calculate_algorithm, smallC_algorithm]functions auto_functions(functions_list)
functions看下输出结果 在添加了smallC算法之后auto-functions函数能够顺利输出正确结果并且模型也能够根据不同的提示智能筛选外部函数。
三、优化二编写自动应答函数
针对第二个优化点即将Function calling执行时的多轮对话封装在一个函数中。这里涉及到外部函数库字典创建该字典要求一个键值对代表一个函数每个键值对的Key表示函数名字符串对应的Value表示对应的函数。
Step 1创建函数库字典
对于此前的functions_list里面包含的两个函数可以使用如下方式创建这个函数库字典
function_dict {func.__name__: func for func in functions_list}
function_dict看下输出 Step 2构造函数
def run_conversation(messages, functions_listNone, modelgpt-3.5-turbo-16k-0613):能够自动执行外部函数调用的Chat对话模型:param messages: 必要参数字典类型输入到Chat模型的messages参数对象:param functions_list: 可选参数默认为None可以设置为包含全部外部函数的列表对象:param model: Chat模型可选参数默认模型为gpt-3.5-turbo-16k-0613:returnChat模型输出结果# 如果没有外部函数库则执行普通的对话任务if functions_list None:response openai.ChatCompletion.create(modelmodel,messagesmessages,)response_message response[choices][0][message]final_response response_message[content]# 若存在外部函数库则需要灵活选取外部函数并进行回答else:# 创建functions对象functions auto_functions(functions_list)# 创建外部函数库字典available_functions {func.__name__: func for func in functions_list}# first responseresponse openai.ChatCompletion.create(modelmodel,messagesmessages,functionsfunctions,function_callauto)response_message response[choices][0][message]# 判断返回结果是否存在function_call即判断是否需要调用外部函数来回答问题if response_message.get(function_call):# 需要调用外部函数# 获取函数名function_name response_message[function_call][name]# 获取函数对象fuction_to_call available_functions[function_name]# 获取函数参数function_args json.loads(response_message[function_call][arguments])# 将函数参数输入到函数中获取函数计算结果function_response fuction_to_call(**function_args)# messages中拼接first response消息messages.append(response_message) # messages中拼接函数输出结果messages.append({role: function,name: function_name,content: function_response,}) # 第二次调用模型second_response openai.ChatCompletion.create(modelmodel,messagesmessages,) # 获取最终结果final_response second_response[choices][0][message][content]else:final_response response_message[content]return final_responseStep 3函数测试
messages [{role: system, content: 数据集data%s数据集以字符串形式呈现 % df_str},{role: user, content: 请帮我介绍下data数据集}]
run_conversation(messages messages, functions_list functions_list)模型能够非常顺利的调用外部函数并围绕当前问题进行准确回答。至此就完成了既定的Function calling功能执行过程的代码优化通过借助run_conversation函数只需设置messages和外部函数列表即可让模型在回答时有选择性的选择外部函数进行回答全程无需手动进行调整。
四、实现一个多轮对话函数
更进一步在run_conversation基础之上再封装一个可以执行多轮对话的函数代码如下
def chat_with_model(functions_listNone, prompt你好呀, modelgpt-4-0613, system_message[{role: system, content: 你是以为乐于助人的助手。}]):messages system_messagemessages.append({role: user, content: prompt})while True: answer run_conversation(messagesmessages, functions_listfunctions_list, modelmodel)print(f模型回答: {answer})# 询问用户是否还有其他问题user_input input(您还有其他问题吗(输入退出以结束对话): )if user_input 退出:break# 记录用户回答messages.append({role: user, content: user_input})可以通过传入functions_list测试是否可以调用外部函数。
五、总结
本文首先概述了Function calling流程的优化思路接着分别详细介绍了两种主要的优化方法自动编写函数和编写自动应答函数。这两种优化方法可以显著提高Function calling的效率和实用性。最后演示了如何实现一个多轮对话函数。
最后感谢您阅读这篇文章如果您觉得有所收获别忘了点赞、收藏并关注我这是我持续创作的动力。您有任何问题或建议都可以在评论区留言我会尽力回答并接受您的反馈。如果您希望了解某个特定主题也欢迎告诉我我会乐于创作与之相关的文章。谢谢您的支持期待与您共同成长