文章目录
- SK vs. LangChain
- 概念对照
- 功能对照
- 环境搭建
- Plugins
- Semantic Functions
- skprompt.txt
- config.json
- Semantic Kernel Tools
- Native Functions
- 内置 Plugins
- Memory
- Planner
- 后记
SK vs. LangChain
#%% md
概念对照
LangChain | Semantic Kernel |
---|---|
Model | Connector |
Tools | Connector |
Vectorstore | Connector |
Memory | Memory |
Prompt Templates | Plugins |
Chain | Pipeline / Chain |
Agent | Planner |
TextSplitter | text.* |
OutputParser | 无 |
功能对照
LangChain | Semantic Kernel | |
---|---|---|
大模型 | 60+ | 5+ |
向量数据库 | 40+ | 11 |
Agent | 10+ | 4 |
#%% md
环境搭建
#%% md
- 安装 Python 3.x:https://www.python.org/downloads/
- 安装 SK 包:
pip install semantic-kernel
- 在项目目录创建 .env 文件,添加以下内容:
# .env
OPENAI_API_KEY=""
OPENAI_API_BASE=""
AZURE_OPENAI_DEPLOYMENT_NAME=""
AZURE_OPENAI_ENDPOINT=""
AZURE_OPENAI_API_KEY=""
OpenAI 和 Azure,配置好一个就行。
#%% md
## Hello, World!#%% md
这是一个简单示例。第一段代码是初始化。后面所有代码都要在执行过这段代码后,才能执行。#%%
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
import os# 加载 .env 到环境变量from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())# 创建 semantic kernelkernel = sk.Kernel()# 配置 OpenAI 服务api_key = os.getenv('OPENAI_API_KEY')
endpoint = os.getenv('OPENAI_API_BASE')
model = OpenAIChatCompletion("gpt-3.5-turbo", api_key, endpoint=endpoint)# 把 LLM 服务加入 kernel# 可以加多个。第一个加入的会被默认使用,非默认的要被指定使用kernel.add_text_completion_service("my-gpt3", model)
#%% md
执行讲笑话的 prompt。#%%# 定义 semantic functiontell_joke_about = kernel.create_semantic_function("给我讲个关于{{$input}}的笑话吧")# 看结果print(tell_joke_about("Hello world"))
#%% md
- 启动操作系统:
kernel = sk.Kernel()
- 安装驱动程序:
kernel.add_xxx_service()
- 安装应用程序:
func = kernel.create_semantic_function()
- 运行应用程序:
func()
基于 SK 开发的主要工作是写「应用程序」,也就是 Plugins
#%% md
Plugins
#%% md
简单说,plugin 就是一组函数的集合。它可以包含两种函数:
- Semantic Functions - 语义函数,本质是 Prompt Engineering
- Native Functions - 原生函数,类似 OpenAI 的 Function Calling
值得一提的是,SK 的 plugin 会和 ChatGPT、Bing、Microsoft 365 通用。「很快」你用 SK 写的 plugin 就可以在这些平台上无缝使用了。这些平台上的 plugin 也可以通过 SK 被你调用。
#%% md
Semantic Functions
#%% md
Semantic Functions 是纯用数据(prompt + 描述)定义的,不需要编写任何代码。所以它与编程语言无关,可以被任何编程语言调用。
一个典型的 semantic function 包含两个文件:
- skprompt.txt: 存放 prompt,可以包含参数,还可以调用其它函数
- config.json: 存放描述,包括函数功能,参数的数据类型,以及调用大模型时的参数
举例:把 LangChain 「生成 Linux 命令」的例子用 SK 实现。
#%% md
skprompt.txt
#%% raw
将用户的指令转换成 Linux 命令
The output should be formatted as a JSON instance that conforms to the JSON schema below.
As an example, for the schema {“properties”: {“foo”: {“title”: “Foo”, “description”: “a list of strings”, “type”: “array”, “items”: {“type”: “string”}}}, “required”: [“foo”]}}
the object {“foo”: [“bar”, “baz”]} is a well-formatted instance of the schema. The object {“properties”: {“foo”: [“bar”, “baz”]}} is not well-formatted.
Here is the output schema:
{"properties": {"command": {"title": "Command", "description": "linux shell命令名", "type": "string"}, "arguments": {"title": "Arguments", "description": "命令的参数 (name:value)", "type": "object", "additionalProperties": {"type": "string"}}}, "required": ["command", "arguments"]}
{{$input}}
#%% md
config.json
#%%
{
“schema”: 1,
“type”: “completion”,
“description”: “将用户的指令转换成 Linux 命令”,
“completion”: {
“max_tokens”: 256,
“temperature”: 0,
“top_p”: 0,
“presence_penalty”: 0,
“frequency_penalty”: 0
},
“input”: {
“parameters”: [
{
“name”: “input”,
“description”: “用户的指令”,
“defaultValue”: “”
}
]
}
}
#%% md
上面两个文件都在 sk_samples/SamplePlugin/GenerateCommand 目录下。
#%% md
#### 调用 Semantic Functions#%%# 加载 semantic function。注意目录结构functions = kernel.import_semantic_skill_from_directory("./sk_samples/", "SamplePlugin")
cli = functions["GenerateCommand"]# 看结果print(cli("将系统日期设为2023-04-01"))
#%% md
官方提供了大量的 Semantic Functions 可以参考:https://github.com/microsoft/semantic-kernel/tree/main/samples/skills
#%% md
Semantic Kernel Tools
#%% md
这是个 VS Code 的插件,在 VS Code 里可以直接创建和调试 Semantic Function。
安装地址:https://marketplace.visualstudio.com/items?itemName=ms-semantic-kernel.semantic-kernel
#%% md
Native Functions
#%% md
用编程语言写的函数,如果用 SK 的 Native Function 方式定义,就能纳入到 SK 的编排体系,可以被 Planner、其它 plugin 调用。
下面,写一个过滤有害 Linux 命令的函数,和 GenerateCommand 组合使用。
这个函数名是 harmful_command
。如果输入的命令是有害的,就返回 true
,否则返回 false
。
它也要放到目录结构中,在 sk_samples/SamplePlugin/SamplePlugin.py 里加入。
#%%# 因为是代码,不是数据,所以必须 importfrom sk_samples.SamplePlugin.SamplePlugin import SamplePlugin# 加载 semantic functionfunctions = kernel.import_semantic_skill_from_directory("./sk_samples/", "SamplePlugin")
cli = functions["GenerateCommand"]# 加载 native functionfunctions = kernel.import_skill(SamplePlugin(), "SamplePlugin")
harmful_command = functions["harmful_command"]# 看结果command = cli("删除根目录下所有文件")
print(command) # 这个 command 其实是 SKContext 类型
print(harmful_command(context=command)) # 所以要传参给 context
#%% md### 用 SKContext 实现多参数 Functions#%% md
如果 Function 都只有一个参数,那么只要把参数定义为 `{{$input}}`,就可以按前面的例子来使用,比较直观。`{{$input}}`会默认被赋值。多参数时,就不能用默认机制了,需要定义 `SKContext` 类型的变量。#%%# 因为是代码,不是数据,所以必须 importfrom sk_samples.SamplePlugin.SamplePlugin import SamplePlugin# 加载 native functionfunctions = kernel.import_skill(SamplePlugin(), "SamplePlugin")
add = functions["add"]# 看结果context = kernel.create_new_context()
context["number1"] = 1024
context["number2"] = 65536
total = add(context=context)
print(total)
#%% md
内置 Plugins
#%% md
SK 内置了若干好用的 plugin,但因为历史原因,它们叫 skill……
加载方法:
from semantic_kernel.core_skills import SkillName
它们是:
ConversationSummarySkill
- 生成对话的摘要FileIOSkill
- 读写文件HttpSkill
- 发出 HTTP 请求,支持 GET、POST、PUT 和 DELETEMathSkill
- 加法和减法计算TextMemorySkill
- 保存文本到 memory 中,可以对其做向量检索TextSkill
- 把文本全部转为大写或小写,去掉头尾的空格(trim)TimeSkill
- 获取当前时间及用多种格式获取时间参数WaitSkill
- 等待指定的时间WebSearchEngineSkill
- 在互联网上搜索给定的文本
#%% md
Memory
#%% md
SK 的 memory 使用非常简单:
- 用
kernel.add_text_embedding_generation_service()
添加一个文本向量生成服务 - 用
kernel.register_memory_store()
注册一个 memory store,可以是内存、文件、向量数据库等 - 用
kernel.memory.save_information_async()
保存信息到 memory store - 用
kernel.memory.search_async()
搜索信息
使用 ChatALL 的 README.md 做数据,使用内存作为 memory store,我们演示下基于文档对话。
#%% md### 初始化 Embedding#%%
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding
import os# 加载 .env 到环境变量from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())# 创建 semantic kernelkernel = sk.Kernel()# 配置 OpenAI 服务api_key = os.getenv('OPENAI_API_KEY')
endpoint = os.getenv('OPENAI_API_BASE')
model = OpenAIChatCompletion("gpt-3.5-turbo", api_key, endpoint=endpoint)# 把 LLM 服务加入 kernel# 可以加多个。第一个加入的会被默认使用,非默认的要被指定使用kernel.add_text_completion_service("my-gpt3", model)# 添加 embedding 服务kernel.add_text_embedding_generation_service("ada", OpenAITextEmbedding("text-embedding-ada-002", api_key, endpoint=endpoint))
#%% md### 文本向量化#%%# 使用内存做 memory storekernel.register_memory_store(memory_store=sk.memory.VolatileMemoryStore())# 读取文件内容with open('../05-langchain/ChatALL.md', 'r') as f:# with open('sk_samples/SamplePlugin/SamplePlugin.py', 'r') as f:content = f.read()# 将文件内容分片,单片最大 100 token(注意:SK 的 text split 功能目前对中文支持不如对英文支持得好)lines = sk.text.split_markdown_lines(content, 100)# 将分片后的内容,存入内存for index, line in enumerate(lines):await kernel.memory.save_information_async("chatall", id=index, text=line)
#%% md### 向量搜索#%%
result = await kernel.memory.search_async("chatall", "ChatALL怎么下载?")
print(result[0].text)
#%% md## Pipeline / Chain#%% md
SK 更想用 pipeline 来描述 LangChain 中 chain 的概念,大概因为 pipeline 这个词更操作系统吧。但 chain 这个名词影响力太大,所以 SK 时不时也会用它。但是,SK 没有在代码里定义什么是 pipeline,它并不是一个类,或者函数什么的。它是贯彻整个 kernel 的一个概念。当一个 kernel 添加了 LLM、memory、functions,我们写下的 functions 之间的组合调用,就是个 pipeline 了。如果需要多条 pipeline,就定义多个 kernel。<div class="alert alert-block alert-info">
<b>思考:</b>LangChain 定义了好几种 Chain;SK 只是用 kernel 把抽象功能组合起来,pipeline 的过程完全交给开发者自己定义。你觉得哪种设计更好?
</div>#%% md
现在用 pipeline 思想把对话式搜索 ChatALL 的 README.md 功能做完整。要用到内置的 `TextMemorySkill`。#%%# 导入内置的 `TextMemorySkill`。主要使用它的 `recall()`kernel.import_skill(sk.core_skills.TextMemorySkill())# 直接在代码里创建 semantic function。里面调用了 `recall()`sk_prompt = """
基于下面的背景信息回答问题。如果背景信息为空,或者和问题不相关,请回答"我不知道"。[背景信息开始]
{{recall $input}}
[背景信息结束]问题:{{$input}}
回答:
"""
ask = kernel.create_semantic_function(sk_prompt)# 提问context = kernel.create_new_context()
context[sk.core_skills.TextMemorySkill.COLLECTION_PARAM] = "chatall"
context[sk.core_skills.TextMemorySkill.RELEVANCE_PARAM] = 0.8
context["input"] = "ChatALL 怎么下载?"
result = ask(context=context)
print(result)
#%% md
Planner
#%% md
SK 的 planner 概念上对标 LangChain 的 agent,但目前实现得还比较简单(C# 版丰富些)。SK Python 提供了三种 planner:1. `BasicPlanner` - 把任务拆解,自动调用各个函数,完成任务。它只是个用于基础验证的功能,最终会被 `SequentialPlanner` 替代。[Prompt 地址](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/basic_planner.py#L27-L123)
2. `SequentialPlanner` - 开发中,未发布。比 `BasicPlanner` 更高级,但目标一致。[Prompt 地址](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/sequential_planner/Skills/SequentialPlanning/skprompt.txt)、[官方例程](https://github.com/microsoft/semantic-kernel/blob/main/python/samples/kernel-syntax-examples/sequential_planner.py)
3. `ActionPlanner` - 已发布。只输出 action,不执行。[Prompt 地址](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/planning/action_planner/skprompt.txt)、[官方例程](https://github.com/microsoft/semantic-kernel/blob/main/python/samples/kernel-syntax-examples/action_planner.py)来,把查周杰伦的生日是星期几,用 SK 的 `BasicPlanner` 再做一遍(版本 > 0.3.7dev 才能工作)。#%%
from semantic_kernel.core_skills import WebSearchEngineSkill, TimeSkill, MathSkill
from semantic_kernel.connectors.search_engine import BingConnector
from semantic_kernel.planning import BasicPlanner
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding
import os# 加载 .env 到环境变量from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv())# 创建 semantic kernelkernel = sk.Kernel()# 配置 OpenAI 服务api_key = os.getenv('OPENAI_API_KEY')
endpoint = os.getenv('OPENAI_API_BASE')
model = OpenAIChatCompletion("gpt-4", api_key, endpoint=endpoint) # GPT-4 才能完成此任务。不信改成 gpt-3.5-turbo 试试# 把 LLM 服务加入 kernel# 可以加多个。第一个加入的会被默认使用,非默认的要被指定使用kernel.add_text_completion_service("my-gpt4", model)# 导入搜索 pluginconnector = BingConnector(api_key=os.getenv("BING_API_KEY"))
kernel.import_skill(WebSearchEngineSkill(connector), "WebSearch")# 导入其它 plugin。所有被导入的 plugin,都是可以被 planner 调用的kernel.import_skill(MathSkill(), "math")
kernel.import_skill(TimeSkill(), "time")# 创建 plannerplanner = BasicPlanner()# 开始ask = "周杰伦的生日是星期几?"
plan = await planner.create_plan_async(ask, kernel)
print(plan.generated_plan)
result = await planner.execute_plan_async(plan, kernel)
print(result)
后记
📢博客主页:https://manor.blog.csdn.net
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 Maynor 原创,首发于 CSDN博客🙉
📢不能老盯着手机屏幕,要不时地抬起头,看看老板的位置⭐
📢专栏持续更新,欢迎订阅:https://blog.csdn.net/xianyu120/category_12471942.html