Co to jest wtyczka?
Wtyczki są kluczowym składnikiem jądra semantycznego. Jeśli używasz już wtyczek z rozszerzeń ChatGPT lub Copilot na platformie Microsoft 365, znasz je już. Dzięki wtyczkom można hermetyzować istniejące interfejsy API do kolekcji, która może być używana przez sztuczną inteligencję. Dzięki temu można nadać sztucznej inteligencji możliwość wykonywania akcji, których nie będzie można wykonać w inny sposób.
W tle semantyczne jądro korzysta z wywoływania funkcji, natywnej funkcji większości najnowszych maszyn LLM, aby umożliwić maszynom LLM planowanie i wywoływanie interfejsów API. W przypadku wywoływania funkcji maszyny LLM mogą żądać (tj. wywołania) określonej funkcji. Semantyczne jądro następnie marshaluje żądanie do odpowiedniej funkcji w bazie kodu i zwraca wyniki z powrotem do usługi LLM, aby usługa LLM mogła wygenerować ostateczną odpowiedź.
Nie wszystkie zestawy SDK sztucznej inteligencji mają analogiczną koncepcję wtyczek (większość ma tylko funkcje lub narzędzia). Jednak w scenariuszach dla przedsiębiorstw wtyczki są cenne, ponieważ hermetyzują zestaw funkcji, które odzwierciedlają sposób, w jaki deweloperzy przedsiębiorstwa już opracowują usługi i interfejsy API. Wtyczki również dobrze grać z iniekcji zależności. W konstruktorze wtyczki można wstrzyknąć usługi, które są niezbędne do wykonania pracy wtyczki (np. połączeń z bazą danych, klientów HTTP itp.). Jest to trudne do osiągnięcia w przypadku innych zestawów SDK, które nie mają wtyczek.
Anatomia wtyczki
Na wysokim poziomie wtyczka to grupa funkcji, które mogą być widoczne dla aplikacji i usług sztucznej inteligencji. Funkcje w ramach wtyczek mogą być następnie orkiestrowane przez aplikację sztucznej inteligencji w celu realizacji żądań użytkowników. W ramach jądra semantycznego można automatycznie wywoływać te funkcje za pomocą wywoływania funkcji.
Uwaga
Na innych platformach funkcje są często określane jako "narzędzia" lub "akcje". W semantycznym jądrze używamy terminu "functions", ponieważ są one zwykle definiowane jako funkcje natywne w bazie kodu.
Po prostu dostarczanie funkcji nie wystarczy, aby wtyczka. Aby zapewnić automatyczną aranżację za pomocą wywoływania funkcji, wtyczki muszą również podać szczegółowe informacje, które semantycznie opisują sposób ich działania. Wszystko, od danych wejściowych, wyjściowych i skutków ubocznych funkcji, należy opisać w sposób, w jaki sztuczna inteligencja może zrozumieć, w przeciwnym razie sztuczna inteligencja nie będzie poprawnie wywoływać funkcji.
Na przykład przykładowa WriterPlugin
wtyczka po prawej stronie zawiera funkcje z semantycznymi opisami, które opisują działanie poszczególnych funkcji. Usługa LLM może następnie użyć tych opisów, aby wybrać najlepsze funkcje do wywołania w celu spełnienia pytania użytkownika.
Na zdjęciu po prawej stronie llM prawdopodobnie wywoła funkcję ShortPoem
i StoryGen
, aby spełnić wymagania użytkowników, dzięki podanym semantycznym opisom.
Importowanie różnych typów wtyczek
Istnieją dwa podstawowe sposoby importowania wtyczek do jądra semantycznego: przy użyciu kodu natywnego lub specyfikacji interfejsu OpenAPI. Pierwsza umożliwia tworzenie wtyczek w istniejącej bazie kodu, które mogą korzystać z już posiadanych zależności i usług. Ten ostatni umożliwia importowanie wtyczek ze specyfikacji interfejsu OpenAPI, którą można udostępniać w różnych językach programowania i platformach.
Poniżej przedstawiono prosty przykład importowania i używania wtyczki natywnej. Aby dowiedzieć się więcej na temat importowania tych różnych typów wtyczek, zapoznaj się z następującymi artykułami:
Napiwek
Podczas rozpoczynania pracy zalecamy używanie wtyczek kodu natywnego. W miarę dojrzewania aplikacji i pracy w zespołach międzyplatformowych warto rozważyć użycie specyfikacji interfejsu OpenAPI do udostępniania wtyczek w różnych językach programowania i platformach.
Różne typy funkcji wtyczki
W ramach wtyczki zazwyczaj będziesz mieć dwa różne typy funkcji, które pobierają dane na potrzeby pobierania rozszerzonej generacji (RAG) i tych, które automatyzują zadania. Mimo że każdy typ jest funkcjonalnie taki sam, są one zwykle używane inaczej w aplikacjach korzystających z jądra semantycznego.
Na przykład w przypadku funkcji pobierania warto użyć strategii w celu zwiększenia wydajności (np. buforowania i używania tańszych modeli pośrednich na potrzeby podsumowania). Podczas gdy w przypadku funkcji automatyzacji zadań warto zaimplementować procesy zatwierdzania pętli człowieka w pętli, aby upewnić się, że zadania są wykonywane poprawnie.
Aby dowiedzieć się więcej o różnych typach funkcji wtyczek, zapoznaj się z następującymi artykułami:
Wprowadzenie do wtyczek
Używanie wtyczek w ramach jądra semantycznego jest zawsze procesem trzech kroków:
- Definiowanie wtyczki
- Dodawanie wtyczki do jądra
- Następnie wywołaj funkcje wtyczki w wierszu polecenia z wywołaniem funkcji
Poniżej przedstawimy ogólny przykład użycia wtyczki w ramach jądra semantycznego. Zapoznaj się z powyższymi linkami, aby uzyskać bardziej szczegółowe informacje na temat tworzenia wtyczek i korzystania z nich.
1) Definiowanie wtyczki
Najprostszym sposobem utworzenia wtyczki jest zdefiniowanie klasy i dodawanie adnotacji do jej metod za pomocą atrybutu KernelFunction
. W tym celu semantyczne jądro wiemy, że jest to funkcja, która może być wywoływana przez sztuczną inteligencję lub przywoływała w wierszu polecenia.
Możesz również zaimportować wtyczki ze specyfikacji interfejsu OpenAPI.
Poniżej utworzymy wtyczkę, która może pobrać stan świateł i zmienić jego stan.
Napiwek
Ponieważ większość llM została wytrenowana z językiem Python na potrzeby wywoływania funkcji, zaleca się używanie wielkości liter węży dla nazw funkcji i nazw właściwości, nawet jeśli używasz języka C# lub zestawu JAVA SDK.
using System.ComponentModel;
using Microsoft.SemanticKernel;
public class LightsPlugin
{
// Mock data for the lights
private readonly List<LightModel> lights = new()
{
new LightModel { Id = 1, Name = "Table Lamp", IsOn = false, Brightness = 100, Hex = "FF0000" },
new LightModel { Id = 2, Name = "Porch light", IsOn = false, Brightness = 50, Hex = "00FF00" },
new LightModel { Id = 3, Name = "Chandelier", IsOn = true, Brightness = 75, Hex = "0000FF" }
};
[KernelFunction("get_lights")]
[Description("Gets a list of lights and their current state")]
[return: Description("An array of lights")]
public async Task<List<LightModel>> GetLightsAsync()
{
return lights
}
[KernelFunction("get_state")]
[Description("Gets the state of a particular light")]
[return: Description("The state of the light")]
public async Task<LightModel?> GetStateAsync([Description("The ID of the light")] int id)
{
// Get the state of the light with the specified ID
return lights.FirstOrDefault(light => light.Id == id);
}
[KernelFunction("change_state")]
[Description("Changes the state of the light")]
[return: Description("The updated state of the light; will return null if the light does not exist")]
public async Task<LightModel?> ChangeStateAsync(int id, LightModel LightModel)
{
var light = lights.FirstOrDefault(light => light.Id == id);
if (light == null)
{
return null;
}
// Update the light with the new state
light.IsOn = LightModel.IsOn;
light.Brightness = LightModel.Brightness;
light.Hex = LightModel.Hex;
return light;
}
}
public class LightModel
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("name")]
public string Name { get; set; }
[JsonPropertyName("is_on")]
public bool? IsOn { get; set; }
[JsonPropertyName("brightness")]
public byte? Brightness { get; set; }
[JsonPropertyName("hex")]
public string? Hex { get; set; }
}
from typing import TypedDict, Annotated
class LightModel(TypedDict):
id: int
name: str
is_on: bool | None
brightness: int | None
hex: str | None
class LightsPlugin:
lights: list[LightModel] = [
{"id": 1, "name": "Table Lamp", "is_on": False, "brightness": 100, "hex": "FF0000"},
{"id": 2, "name": "Porch light", "is_on": False, "brightness": 50, "hex": "00FF00"},
{"id": 3, "name": "Chandelier", "is_on": True, "brightness": 75, "hex": "0000FF"},
]
@kernel_function
async def get_lights(self) -> Annotated[list[LightModel], "An array of lights"]:
"""Gets a list of lights and their current state."""
return self.lights
@kernel_function
async def get_state(
self,
id: Annotated[int, "The ID of the light"]
) -> Annotated[LightModel | None], "The state of the light"]:
"""Gets the state of a particular light."""
for light in self.lights:
if light["id"] == id:
return light
return None
@kernel_function
async def change_state(
self,
id: Annotated[int, "The ID of the light"],
new_state: LightModel
) -> Annotated[Optional[LightModel], "The updated state of the light; will return null if the light does not exist"]:
"""Changes the state of the light."""
for light in self.lights:
if light["id"] == id:
light["is_on"] = new_state.get("is_on", light["is_on"])
light["brightness"] = new_state.get("brightness", light["brightness"])
light["hex"] = new_state.get("hex", light["hex"])
return light
return None
public class LightsPlugin {
// Mock data for the lights
private final Map<Integer, LightModel> lights = new HashMap<>();
public LightsPlugin() {
lights.put(1, new LightModel(1, "Table Lamp", false));
lights.put(2, new LightModel(2, "Porch light", false));
lights.put(3, new LightModel(3, "Chandelier", true));
}
@DefineKernelFunction(name = "get_lights", description = "Gets a list of lights and their current state")
public List<LightModel> getLights() {
System.out.println("Getting lights");
return new ArrayList<>(lights.values());
}
@DefineKernelFunction(name = "change_state", description = "Changes the state of the light")
public LightModel changeState(
@KernelFunctionParameter(name = "id", description = "The ID of the light to change") int id,
@KernelFunctionParameter(name = "isOn", description = "The new state of the light") boolean isOn) {
System.out.println("Changing light " + id + " " + isOn);
if (!lights.containsKey(id)) {
throw new IllegalArgumentException("Light not found");
}
lights.get(id).setIsOn(isOn);
return lights.get(id);
}
}
Zwróć uwagę, że udostępniamy opisy funkcji, wartości zwracanej i parametrów. Jest to ważne dla sztucznej inteligencji, aby zrozumieć, co robi funkcja i jak jej używać.
Napiwek
Nie bój się podawać szczegółowych opisów funkcji, jeśli sztuczna inteligencja ma problemy z ich wywoływaniem. Przykłady kilku strzałów, zalecenia dotyczące tego, kiedy należy użyć (i nie używać) funkcji, i wskazówki dotyczące tego, gdzie uzyskać wymagane parametry, mogą być przydatne.
2) Dodawanie wtyczki do jądra
Po zdefiniowaniu wtyczki możesz dodać ją do jądra, tworząc nowe wystąpienie wtyczki i dodając ją do kolekcji wtyczek jądra.
W tym przykładzie pokazano najprostszy sposób dodawania klasy jako wtyczki do AddFromType
metody . Aby dowiedzieć się więcej o innych sposobach dodawania wtyczek, zapoznaj się z artykułem dodawanie wtyczek natywnych.
var builder = new KernelBuilder();
builder.Plugins.AddFromType<LightsPlugin>("Lights")
Kernel kernel = builder.Build();
kernel = Kernel()
kernel.add_plugin(
LightsPlugin(),
plugin_name="Lights",
)
// Import the LightsPlugin
KernelPlugin lightPlugin = KernelPluginFactory.createFromObject(new LightsPlugin(),
"LightsPlugin");
// Create a kernel with Azure OpenAI chat completion and plugin
Kernel kernel = Kernel.builder()
.withAIService(ChatCompletionService.class, chatCompletionService)
.withPlugin(lightPlugin)
.build();
3) Wywoływanie funkcji wtyczki
Na koniec możesz wywołać funkcje wtyczki za pomocą wywołania funkcji. Poniżej przedstawiono przykład, który pokazuje, jak połączyć sztuczną inteligencję w celu wywołania funkcji z Lights
wtyczki przed wywołaniem get_lights
change_state
funkcji w celu włączenia światła.
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
// Create a kernel with Azure OpenAI chat completion
var builder = Kernel.CreateBuilder().AddAzureOpenAIChatCompletion(modelId, endpoint, apiKey);
// Build the kernel
Kernel kernel = builder.Build();
var chatCompletionService = kernel.GetRequiredService<IChatCompletionService>();
// Add a plugin (the LightsPlugin class is defined below)
kernel.Plugins.AddFromType<LightsPlugin>("Lights");
// Enable planning
OpenAIPromptExecutionSettings openAIPromptExecutionSettings = new()
{
FunctionChoiceBehavior = FunctionChoiceBehavior.Auto()
};
// Create a history store the conversation
var history = new ChatHistory();
history.AddUserMessage("Please turn on the lamp");
// Get the response from the AI
var result = await chatCompletionService.GetChatMessageContentAsync(
history,
executionSettings: openAIPromptExecutionSettings,
kernel: kernel);
// Print the results
Console.WriteLine("Assistant > " + result);
// Add the message from the agent to the chat history
history.AddAssistantMessage(result);
import asyncio
from semantic_kernel import Kernel
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
async def main():
# Initialize the kernel
kernel = Kernel()
# Add Azure OpenAI chat completion
chat_completion = AzureChatCompletion(
deployment_name="your_models_deployment_name",
api_key="your_api_key",
base_url="your_base_url",
)
kernel.add_service(chat_completion)
# Add a plugin (the LightsPlugin class is defined below)
kernel.add_plugin(
LightsPlugin(),
plugin_name="Lights",
)
# Enable planning
execution_settings = AzureChatPromptExecutionSettings()
execution_settings.function_call_behavior = FunctionChoiceBehavior.Auto()
# Create a history of the conversation
history = ChatHistory()
history.add_message("Please turn on the lamp")
# Get the response from the AI
result = await chat_completion.get_chat_message_content(
chat_history=history,
settings=execution_settings,
kernel=kernel,
)
# Print the results
print("Assistant > " + str(result))
# Add the message from the agent to the chat history
history.add_message(result)
# Run the main function
if __name__ == "__main__":
asyncio.run(main())
// Enable planning
InvocationContext invocationContext = new InvocationContext.Builder()
.withReturnMode(InvocationReturnMode.LAST_MESSAGE_ONLY)
.withToolCallBehavior(ToolCallBehavior.allowAllKernelFunctions(true))
.build();
// Create a history to store the conversation
ChatHistory history = new ChatHistory();
history.addUserMessage("Turn on light 2");
List<ChatMessageContent<?>> results = chatCompletionService
.getChatMessageContentsAsync(history, kernel, invocationContext)
.block();
System.out.println("Assistant > " + results.get(0));
Po wykonaniu powyższego kodu powinna zostać wyświetlona odpowiedź podobna do następującej:
Rola | Komunikat |
---|---|
🔵Użytkownik | Włącz lampę |
🔴Asystent (wywołanie funkcji) | Lights.get_lights() |
🟢Tool (Narzędzie dostępu do centrum danych) | [{ "id": 1, "name": "Table Lamp", "isOn": false, "brightness": 100, "hex": "FF0000" }, { "id": 2, "name": "Porch light", "isOn": false, "brightness": 50, "hex": "00FF00" }, { "id": 3, "name": "Chandelier", "isOn": true, "brightness": 75, "hex": "0000FF" }] |
🔴Asystent (wywołanie funkcji) | Lights.change_state(1, { "isOn": true }) |
🟢Tool (Narzędzie dostępu do centrum danych) | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴Asystent | Lampa jest teraz włączona |
Napiwek
Chociaż można bezpośrednio wywołać funkcję wtyczki, nie jest to zalecane, ponieważ sztuczna inteligencja powinna być jedyną decyzją o tym, które funkcje mają być wywoływane. Jeśli potrzebujesz jawnej kontroli nad wywoływanymi funkcjami, rozważ użycie standardowych metod w bazie kodu zamiast wtyczek.