-赋能智能体行动力(第5章)插件/函数注册)
一个智能体需要5个核心功能角色设定skill提示词工具与行动推理与规划知识与记忆评估与反馈。该章节学习的就是工具与行动。下面代码中sk版本是1.44.1一看看最基本的用法import os import json import asyncio from openai import OpenAI,AsyncOpenAI base_urlhttp://192.168.101.26:1234/v1 api_keysk-lm-am25JcFg:zSXiYznKSNHs9n5qhNGq service_id lm_studio_cpm model_id minicpm5-2b tools[ { type: function, function: { name: book_query, description: 提供书籍推荐查询., parameters: { type: object, properties: { topic: { type: string, description: 书籍的查询关键字列表., }, rating: { type: string, description: 书籍销售的评价., enum: [good, bad, terrible] }, }, required: [topic], }, }, }, { type: function, function: { name: phone_query, description: 提供手机推荐查询., parameters: { type: object, properties: { topic: { type: string, description: 手机的查询中文关键字列表., }, rating: { type: string, description: 手机销售的评价., enum: [good, bad, terrible] }, }, required: [topic, rating], }, }, } ] def ask_chatgpt(user_message): client OpenAI(base_urlbase_url,api_keyapi_key) response client.chat.completions.create( model model_id, messagesmessages, temperature0.3, toolstools ) response_model response.model_dump() print(json.dumps(response_model, indent4)) return response.choices[0].message.content messages [ { role: system, content: 你是一个电子产品购物达人乐于帮推荐相关商品. }, { role: user, content: 推荐3款适合玩手游的手机网上评价比较好的 }, ] response ask_chatgpt(messages) print(response)代码里tools结构里定义了2个函数并绑定到了聊天客户端。我们提示词推荐3款适合玩手游的手机网上评价比较好的。它就能够正确识别适合的函数并提取了参数。这个例子没有返回聊天结果但从反馈信息中看出工具的使用的信息tool_calls: [ { id: B5IWiF0tLGD7hyeXdCrYgv662IEBYzgL, function: { arguments: {\topic\:\gaming phones\,\rating\:\good\}, name: phone_query }, type: function } ],这里需要人工去解析和执行获取结果并再次提交给模型才能获取到反馈。这么使用实在不便捷。因此就有了许多库来简化这块工作semantic_kernel是其一。二sk的用法在sksemantic_kernel中支持直接把提示词带参数作为函数把提示词配置保存到文件加载为插件把自定义的功能类作为插件在提示词里绑定自定义的插件作为函数等方式集成各类工具实现智能体的行动力。import os import json import asyncio from openai import OpenAI,AsyncOpenAI import semantic_kernel as sk from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings import semantic_kernel.connectors.ai.open_ai as sk_oai from semantic_kernel.prompt_template.input_variable import InputVariable from semantic_kernel.prompt_template import PromptTemplateConfig from semantic_kernel.functions import KernelArguments from semantic_kernel.functions.kernel_function_decorator import kernel_function def get_kernel() - sk.Kernel: kernel sk.Kernel() api_key sk-lm-am25JcFg:zSXiYznKSNHs9n5qhNGq client AsyncOpenAI( base_urlhttp://192.168.101.26:1234/v1, api_keyapi_key ) org_id kernel.add_service( OpenAIChatCompletion( service_id service_id, ai_model_id model_id, async_client client, ), ) print(Registered Services:, list(kernel.services.keys())) return kernel execution_settings sk_oai.OpenAIChatPromptExecutionSettings( service_idservice_id, ai_model_idmodel_id, temperature0.7, )以上是基础代码后面的代码都是在其基础之上的扩展。在实验运行时把这些代码结合成一个文件即可1直接把提示词带参数作为函数def build_prompt_template(): prompt system: 你是一个购物经验丰富且对电脑手机等电子产品知识全面的购物达人 只要向你提供物品的种类年代价格中位数等信息 你就能推荐相应最佳物品. user: 请帮推荐以下要求的最佳3个物品 {{$format}} 具有 {{$subject}} {{$price}}以及 {{$custom}} prompt_template_config PromptTemplateConfig( templateprompt, nametldr, template_formatsemantic-kernel, input_variables[ InputVariable( nameformat, json_schemastr, description这是物品的种类品牌信息例如电脑 ), InputVariable( namesuject, description这是期望被推荐的物品是那个年代左右的产品例如2025, json_schemastr, ), InputVariable( nameprice, description这是期望被推荐的物品的价格中位数例如5000, json_schemastr, ), InputVariable( namecustom, description其他用户提供的信息, json_schemastr, is_requiredFalse, ), ], execution_settingsexecution_settings, ) return prompt_template_config async def run_recommendation( subject2023, format平板电脑, price1500, custom可以是苹果小米 ): kernel get_kernel() prompt_template build_prompt_template() recommend_function kernel.add_function( # add_function 或 add_plugin prompt_template_configprompt_template, function_nameRecommend_goods, plugin_nameRecommendation, ) recommendation await kernel.invoke( recommend_function, KernelArguments(subjectsubject, formatformat, priceprice, customcustom), ) print(recommendation) asyncio.run(run_recommendation())这里有几个学习点1在提示词中的变量格式{{$format}}这是在template_formatsemantic-kernel格式里其他还有handlebarsjinja2等格式是不一样的需区分。2在调用函数时传参方式用KernelArguments打包3把提示词注册为函数方法kernel.add_function4在定义参数时需要指定json_schema指定int,str,bool,float,array,object其他情况都是object类型2把提示词配置保存到文件加载为插件在项目中sk推荐把插件统一在一个目录里例如plugins以便于管理。提示词文件要求目录结构为2层父目录为plugins例如goodsquery/recommendf/ 文件分2个config.jsonskprompt.txt这2个文件名必须这样另一类yaml文件config.json是插件的配置skprompt.txt是提示词。config.json { schema: 1, description: 基于用户最近购物清单推荐与相关但不同类的商品例如用户买了婴儿奶粉可以推荐纸尿裤。, execution_settings: { default: { max_tokens: 1000, temperature: 0.7, top_p: 0.0, presence_penalty: 0.0, frequency_penalty: 0.0 } }, input_variables: [ { name: input, description: 用户最近购物清单., json_schema: str, default: } ], default_backends: [] } skprompt.txt 你是一个在京东购物经验丰富且对各类日用品生活用品等知识全面的家庭购物达人 只要向你提供一些以往的所购物品信息:{{$input}}你就能推荐用户最可能需要商品.这里也定义了一个参数input。plugins_directory ./plugins history_list [ 燕京啤酒, 驱蚊水, 达能饼干, 苹果, 清风纸巾, 手撕牛肉, 剃须刀片, 娃哈话, ] async def run_skill(): kernel get_kernel() recommender kernel.add_plugin( parent_directoryplugins_directory, plugin_namegoodsquery, ) recommend recommender[recommendf] result await kernel.invoke( recommend, KernelArguments( settingsexecution_settings, input, .join(history_list) ), ) print(result)这里有几个学习点1把提示词注册为函数方法kernel.add_plugin这个函数参数有点复杂因此这里指定了参数名2config.json里schemadescription必须要有如果有参数也需要定义这里补充add_plugin的声明从声明中看出各参数都有默认值因此使用时指定参数名更清晰不容易出错def add_plugin( self, plugin: KernelPlugin | object | dict[str, Any] | None None, plugin_name: str | None None, parent_directory: str | None None, description: str | None None, class_init_arguments: dict[str, dict[str, Any]] | None None, encoding: str utf-8, ) - KernelPlugin3把自定义的功能类作为插件class WeatherPlugin: day weather plugin. kernel_function(descriptionGet real-time weather information.) def current(self, location:str) - str: Returns the current weather. return fThe weather in {location} is sunny. async def run_native_func(): kernel get_kernel() weather kernel.add_plugin(WeatherPlugin(), plugin_nameweather) weather_func weather[current] result await kernel.invoke( weather_func, KernelArguments( settingsexecution_settings, location北京 ), ) print(result) asyncio.run(run_native_func())这里自定义类只给了个demo下一文中将实现。这里有几个学习点1add_plugin的声明中首个参数plugin: KernelPlugin | object | dict[str, Any] | None None这里WeatherPlugin()就是一个object2函数参数定义时推荐指定类型这样sk在构建schema时能够正确推断出变量的类型。4在提示词里绑定自定义的插件作为函数class HisgoodsPlugin: buy goods plugin. kernel_function(description基于城市获取用户已购物信息.) def get_by_city(self,city:str) - str: 返回用户已购物信息. return city : , .join(history_list) sk_prompt 你是一个购物经验丰富且对各类日用品生活用品等知识全面的家庭购物达人 只要向你提供一些以往的所购物品信息不需要工具你就能推荐用户最可能需要商品. 已购物品: {{HisgoodsPlugin.get_by_city}} prompt_template_config PromptTemplateConfig( templatesk_prompt, nametldr, template_formatsemantic-kernel, execution_settingsexecution_settings, ) async def run_native_func_Prompt(): kernel get_kernel() kernel.add_plugin(HisgoodsPlugin(), plugin_nameHisgoodsPlugin) bugGoodsFunc kernel.add_function(prompt_template_configprompt_template_config, function_namegetgoods, plugin_nameBuygoodsPlugin,) result await kernel.invoke( bugGoodsFunc, KernelArguments( city北京), ) print(result)这里有几个学习点1sk_prompt中注意它嵌入的是自定义类不同于变量格式为{{HisgoodsPlugin.get_by_userid}} 插件名.函数名 不需要对参数说明2函数的参数在KernelArguments中绑定值。3函数定义的名称参数的名字都很重要最佳和函数功能参数的意义一致否则容易导致模型误解。模型在推理时会使用这些字面含义以及额外的说明来处理。至此上面的示例都是手工kernel.invoke来执行插件但在项目中是有sk向模型提交tools schema在模型反馈工具调用信息后自动化执行后面继续实验。其中关于TMDB的内容在下一文中用天气示例就不必麻烦去注册了。