大语言模型插件功能在携程的Python实践( 三 )

定义插件对应的函数实现:
class Functions:@classmethoddef ping(cls, **kwargs):"""ping实现"""# 省略ping的代码实现pass@classmethoddef google(cls, **kwargs):"""google搜索实现"""# 查询关键字keyword = kwargs['keyword']# 搜索结果search_context = []# 使用google api搜索res = server['service'].cse().list(q=keyword, cx=server['cx'], ).execute()# 遍历搜索结果for row in res.get('items', []):# 提取每条搜索结果的简要信息search_context.Append(row['snippet'])# 汇总搜索结果和问题组成promptprompt = [{"role": "user", "content": f"请结合以下内容,回答问题:{keyword}n" + "n".join(search_context)}]# 调用大语言模型生成答案return reply_text(prompt)3.2 使用Function Calling实现插件功能
大体逻辑为:将插件信息和用户提问一起发送给大语言模型的API,得到与之匹配的插件,再调用插件对应的函数,得到结果返回给用户 。以下代码为简化的ChatGLM3示例代码:
import torchfrom transformers import AutoTokenizer, AutoModeldef main():"""使用插件时回复文字"""DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'tokenizer = AutoTokenizer.from_pretrained('/home/chatglm3-6b', tRust_remote_code=True)model = AutoModel.from_pretrained('/home/chatglm3-6b', trust_remote_code=True).to(DEVICE).eval()# 汇总所有的插件信息tools = [plugin['info'] for plugin in all_plugins.values()]# 将插件信息设置在对话历史中history = [{"role": "system", "content": "Answer the following questions as best as you can. You have access to the following tools:", "tools": tools}]# 调用function callingresponse, _ = model.chat(tokenizer, query, history=history)# 获取匹配的插件名称plugin_name = response.get("name", "")# 获取匹配的插件参数arguments = response.get("parameters", {})# 没有匹配到插件则退出if not plugin_name:return None# 获取插件完整信息plugin = all_plugins[plugin_name]# 使用反射机制获取插件对应的函数对象func = getattr(Functions, plugin_name)# 执行函数并返回结果res = func(**arguments)return res3.3 异步插件的实现
本项目Web后端使用的框架为flask,使用socketIO实现异步交互,需要安装对应的库:flask_socketio , 启动时 , 在flask的app上使用SocketIO包装一下即可,这样在同一个端口上同时开启了http服务和socketIO服务,下面只展示基本关键代码:
from flask import Flaskfrom flask_socketio import SocketIO# flask原始 appweb_app = Flask(__name__, static_folder=Config.STATIC_PATH)# socketIO包装appsocketio = SocketIO(web_app, cors_allowed_origins="*", logger=True)# 可监听连接和断开@socketio.on('connect')def handle_connect():print("connect")@socketio.on('disconnect')def handle_disconnect():print("disconnect")# 本地启动appif __name__ == '__main__':socketio.run(web_app, address, port, allow_unsafe_werkzeug=True)在socketIO中调用emit(event, *args, **kwargs)方法即可给指定目标(event,本项目对应为user)发送消息 。
我们通过function calling获取到对应插件时 , 如果是同步插件,则立即执行对应函数,如果是异步插件,应该异步开启执行对应函数,并立马结束当前会话,等异步函数执行完成后主动发送消息给前端用户,因此我们需要修改一下上面的插件代码:
def main(user, question):"""使用插件时回复文字"""...代码同上# 使用反射机制获取插件对应的函数对象func = getattr(Functions, plugin_name)# 判断插件是否同步if plugin['sync']:# 同步的插件,直接调用函数res = func(**arguments)else:# 异步的插件 , 这里使用线程池示例执行异步任务thread_pool = ThreadPool(3)# 定义回调函数, 接收到结果后推送给前端def callback(result):# 推送给前端socketio.emit(user, f"任务结果为: {result}")# 异步调用res = thread_pool.apply_async(func, kwds=arguments, callback=callback)return res四、 未来规划4.1 更多的插件
上述插件案例只是插件功能的冰山一角,通过该功能我们可以定义各种实用的插件,目前携程信息安全部的大语言模型智能聊天机器人只是支持一些基本的插件,也欢迎大家给我们提出宝贵的建议,集思广益 , 一起开发出更多实用好用的插件 。
4.2 每个用户的自定义插件
目前的插件功能可以支持我们这些项目的开发者实现自定义插件,这些插件也必须提前写入项目中 , 并不能支持终端用户直接自定义自己的插件 。后续我们会调研可行性方案,让终端的用户自己编写对应的插件代码,实现每个用户都能定义自己的插件 。


推荐阅读