拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

读书笔记(ai agents in action)-赋能智能体行动力(第5章)插件/函数注册

读书笔记(ai agents in action)-赋能智能体行动力(第5章)插件/函数注册

一个智能体需要5个核心功能:角色设定(skill,提示词),工具与行动,推理与规划,知识与记忆,评估与反馈。该章节学习的就是:工具与行动。下面代码中sk版本是1.44.1

一,看看最基本的用法

import os import json import asyncio from openai import OpenAI,AsyncOpenAI base_url="http://192.168.101.26:1234/v1" api_key="sk-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_url=base_url,api_key=api_key) response = client.chat.completions.create( model= model_id, messages=messages, temperature=0.3, tools=tools ) response_model = response.model_dump() print(json.dumps(response_model, indent=4)) 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的用法

在sk(semantic_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_url="http://192.168.101.26:1234/v1", api_key=api_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_id=service_id, ai_model_id=model_id, temperature=0.7, )

以上是基础代码,后面的代码都是在其基础之上的扩展。(在实验运行时,把这些代码结合成一个文件即可)

1,直接把提示词带参数作为函数

def build_prompt_template(): prompt = """ system: 你是一个购物经验丰富,且对电脑,手机等电子产品知识全面的购物达人, 只要向你提供物品的种类,年代,价格中位数等信息, 你就能推荐相应最佳物品. user: 请帮推荐以下要求的最佳3个物品 {{$format}} 具有 {{$subject}} , {{$price}},以及 {{$custom}} """ prompt_template_config = PromptTemplateConfig( template=prompt, name="tldr", template_format="semantic-kernel", input_variables=[ InputVariable( name="format", json_schema="str", description="这是物品的种类,品牌信息,例如电脑" ), InputVariable( name="suject", description="这是期望被推荐的物品是那个年代左右的产品,例如2025", json_schema="str", ), InputVariable( name="price", description="这是期望被推荐的物品的价格中位数,例如5000", json_schema="str", ), InputVariable( name="custom", description="其他用户提供的信息", json_schema="str", is_required=False, ), ], execution_settings=execution_settings, ) return prompt_template_config async def run_recommendation( subject="2023", format="平板电脑", price="1500", custom="可以是苹果,小米" ): kernel = get_kernel() prompt_template = build_prompt_template() recommend_function = kernel.add_function( # add_function 或 add_plugin prompt_template_config=prompt_template, function_name="Recommend_goods", plugin_name="Recommendation", ) recommendation = await kernel.invoke( recommend_function, KernelArguments(subject=subject, format=format, price=price, custom=custom), ) print(recommendation) asyncio.run(run_recommendation())

这里有几个学习点:

1,在提示词中的变量格式:{{$format}},这是在template_format="semantic-kernel"格式里;其他还有:handlebars,jinja2等格式是不一样的,需区分。

2,在调用函数时传参方式:用KernelArguments打包;

3,把提示词注册为函数方法:kernel.add_function();

4,在定义参数时,需要指定json_schema指定(int,str,bool,float,array,object),其他情况都是object类型;

2,把提示词,配置保存到文件加载为插件

在项目中,sk推荐把插件统一在一个目录里(例如:plugins)以便于管理。提示词文件要求目录结构为2层(父目录为plugins),例如:goodsquery/recommendf/ ,文件分2个:config.json,skprompt.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_directory=plugins_directory, plugin_name="goodsquery", ) recommend = recommender["recommendf"] result = await kernel.invoke( recommend, KernelArguments( settings=execution_settings, input=", ".join(history_list) ), ) print(result)

这里有几个学习点:

1,把提示词注册为函数方法:kernel.add_plugin(),这个函数参数有点复杂,因此这里指定了参数名;

2,config.json里"schema","description"必须要有,如果有参数也需要定义

这里补充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", ) -> "KernelPlugin"

3,把自定义的功能类作为插件

class WeatherPlugin: """day weather plugin.""" @kernel_function(description="Get real-time weather information.") def current(self, location:str) -> str: """Returns the current weather.""" return f"The weather in {location} is sunny." async def run_native_func(): kernel = get_kernel() weather =kernel.add_plugin(WeatherPlugin(), plugin_name="weather") weather_func = weather["current"] result = await kernel.invoke( weather_func, KernelArguments( settings=execution_settings, location="北京" ), ) print(result) asyncio.run(run_native_func())

这里自定义类只给了个demo,下一文中将实现。

这里有几个学习点:

1,add_plugin()的声明中首个参数plugin: KernelPlugin | object | dict[str, Any] | None = None,这里WeatherPlugin()就是一个object;

2,函数参数定义时推荐指定类型,这样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( template=sk_prompt, name="tldr", template_format="semantic-kernel", execution_settings=execution_settings, ) async def run_native_func_Prompt(): kernel = get_kernel() kernel.add_plugin(HisgoodsPlugin(), plugin_name="HisgoodsPlugin") bugGoodsFunc =kernel.add_function(prompt_template_config=prompt_template_config, function_name="getgoods", plugin_name="BuygoodsPlugin",) result = await kernel.invoke( bugGoodsFunc, KernelArguments( city="北京"), ) print(result)

这里有几个学习点:

1,sk_prompt中注意,它嵌入的是自定义类(不同于变量),格式为:{{HisgoodsPlugin.get_by_userid}} 插件名.函数名, 不需要对参数说明;

2,函数的参数在KernelArguments中绑定值。

3,函数定义的名称,参数的名字都很重要,最佳和函数功能,参数的意义一致,否则容易导致模型误解。模型在推理时,会使用这些字面含义,以及额外的说明来处理。

至此,上面的示例都是手工kernel.invoke来执行插件,但在项目中是有sk向模型提交tools schema,在模型反馈工具调用信息后自动化执行(后面继续实验)。其中关于TMDB的内容,在下一文中用天气示例,就不必麻烦去注册了。

返回列表