Eklenti nedir?
Eklentiler Semantik Çekirdeğin önemli bir bileşenidir. Microsoft 365'te ChatGPT veya Copilot uzantılarından gelen eklentileri zaten kullandıysanız, bunları zaten biliyorsunuz demektir. Eklentilerle mevcut API'lerinizi yapay zeka tarafından kullanılabilecek bir koleksiyonda kapsülleyebilirsiniz. Bu, yapay zekanıza aksi takdirde yapamayacağı eylemleri gerçekleştirme olanağı sunmanızı sağlar.
Semantik Çekirdek arka planda, LLM'lere izin vermek,
Tüm yapay zeka SDK'larının eklentilere benzer bir kavramı yoktur (çoğu yalnızca işlevlere veya araçlara sahiptir). Ancak kurumsal senaryolarda eklentiler, kurumsal geliştiricilerin zaten hizmet ve API'leri nasıl geliştirdiğini yansıtan bir işlev kümesini kapsüllediğinden değerlidir. Eklentiler de bağımlılık ekleme ile düzgün bir şekilde yürütülmektedir. Eklentinin oluşturucusunun içinde, eklentinin çalışmasını gerçekleştirmek için gereken hizmetleri (örneğin, veritabanı bağlantıları, HTTP istemcileri vb.) ekleyebilirsiniz. Eklenti olmayan diğer SDK'larla bunu başarmak zordur.
Eklentinin anatomisi
Yüksek seviyede, bir eklenti, yapay zeka uygulamaları ve hizmetlerine sunulabilecek işlevlerinin bir grubudur. Eklentilerdeki işlevler daha sonra kullanıcı isteklerini gerçekleştirmek için bir yapay zeka uygulaması tarafından düzenlenebilir. Anlam Çekirdeği'nin içinde, işlev çağrısı ile bu işlevleri otomatik olarak çağırabilirsiniz.
Not
Diğer platformlarda işlevler genellikle "araçlar" veya "eylemler" olarak adlandırılır. Anlam Çekirdeği'nde genellikle kod tabanınızda yerel işlevler olarak tanımlandığından "işlevler" terimini kullanırız.
Ancak yalnızca işlevleri sağlamak bir eklenti yapmak için yeterli değildir. İşlev çağrısıyla otomatik düzenlemeyi desteklemek için eklentilerin nasıl davrandıklarını gösteren ayrıntıları da sağlaması gerekir. İşlevin girişinden, çıkışından ve yan etkilerinden gelen her şeyin yapay zekanın anlayabileceği şekilde açıklanması gerekir, aksi takdirde yapay zeka işlevi doğru şekilde çağırmaz.
Örneğin, sağdaki örnek WriterPlugin
eklentisi, her işlevin ne yaptığını açıklayan anlamsal açıklamalara sahip işlevlere sahiptir. LlM daha sonra bu açıklamaları kullanarak kullanıcının isteklerini yerine getirmek için çağrılabilecek en iyi işlevleri seçebilir.
Sağdaki resimde LLM, sağlanan anlamsal açıklamalar sayesinde kullanıcıların taleplerini karşılamak için muhtemelen ShortPoem
ve StoryGen
işlevlerini çağıracaktır.
Farklı eklenti türlerini içeri aktarma
Eklentileri Anlam Çekirdeği'ne aktarmanın iki birincil yolu vardır:
Aşağıda yerel eklentiyi içeri aktarma ve kullanma ile ilgili basit bir örnek sağlıyoruz. Bu farklı eklenti türlerini içeri aktarma hakkında daha fazla bilgi edinmek için aşağıdaki makalelere bakın:
- Yerel kodu içeri aktarma
- OpenAPI belirtimlerini içeri aktarma
Bahşiş
Başlarken yerel kod eklentileri kullanmanızı öneririz. Uygulamanız büyüdükçe ve platformlar arası ekiplerde çalışırken eklentileri farklı programlama dillerinde ve platformlarda paylaşmak için OpenAPI belirtimlerini kullanmayı düşünebilirsiniz.
Farklı eklenti işlevleri türleri
Bir eklentide genellikle iki farklı işlev türü bulunur: bunlar bilgi artırımlı üretim (RAG) için veri toplayan ve görevleri otomatikleştiren işlevlerdir. Her tür işlevsel olarak aynı olsa da, bunlar genellikle Anlam Çekirdeği kullanan uygulamalarda farklı şekilde kullanılır.
Örneğin, alma işlevleriyle performansı iyileştirmek için stratejiler kullanmak isteyebilirsiniz (örneğin, önbelleğe alma ve özetleme için daha uygun maliyetli ara modelleri kullanma). Görev otomasyonu işlevlerinde görevlerin doğru şekilde tamamlandığından emin olmak için döngüdeki insan onay işlemlerini uygulamak isteyebilirsiniz.
Farklı eklenti işlevleri türleri hakkında daha fazla bilgi edinmek için aşağıdaki makalelere bakın:
Eklentileri kullanmaya başlama
Anlam Çekirdeği içinde eklentileri kullanmak her zaman üç adımlı bir işlemdir:
- Eklentinizi tanımlama
- Çekirdeğinize eklentiyi ekleyin
- Ve ardından ya işlev çağırma içeren bir komut isteminde ya da eklentinin işlevlerini çalıştırın
Aşağıda Anlam Çekirdeği içinde eklentinin nasıl kullanılacağına yönelik üst düzey bir örnek sağlayacağız. Eklentileri oluşturma ve kullanma hakkında daha ayrıntılı bilgi için yukarıdaki bağlantılara bakın.
1) Eklentinizi tanımlayın
Eklenti oluşturmanın en kolay yolu bir sınıf tanımlamak ve KernelFunction
özniteliğiyle yöntemlerine açıklama eklemektir. Bu cümle, Semantic Kernel'a bunun bir yapay zeka tarafından çağrılabilen veya istemde başvurulabilen bir işlev olduğunu bilmesini sağlar.
Eklentileri OpenAPI belirtimindende içeri aktarabilirsiniz.
Aşağıda, ışıkların durumunu alabilen ve durumunu değiştirebilen bir eklenti oluşturacağız.
Tavsiye
Çoğu Büyük Dil Modeli işlev çağrısı için Python ile eğitildiğinden, C# veya Java SDK'sını kullanıyor olsanız bile, işlev adları ve özellik adları için snake_case kullanılması önerilir.
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);
}
}
İşlev, dönüş değeri ve parametreler için açıklamalar sağladığımıza dikkat edin. Bu, yapay zekanın işlevin ne yaptığını ve nasıl kullanılacağını anlaması açısından önemlidir.
Bahşiş
Yapay zeka onları çağırmada sorun yaşıyorsa işlevleriniz için ayrıntılı açıklamalar sağlamaktan çekinmeyin. Birkaç örnek, işlevin ne zaman kullanılacağına (ve kullanılmayacağına) ilişkin öneriler ve gerekli parametrelerin nereden alınacağı konusunda rehberlik yararlı olabilir.
2) Eklentiyi çekirdeğinize ekleyin
Eklentinizi tanımladıktan sonra eklentinin yeni bir örneğini oluşturup çekirdeğin eklenti koleksiyonuna ekleyerek eklentiyi çekirdeğinize ekleyebilirsiniz.
Bu örnek, AddFromType
yöntemiyle bir sınıfı eklenti olarak eklemenin en kolay yolunu gösterir. Eklenti eklemenin diğer yolları hakkında bilgi edinmek için yerel eklenti ekleme makalesine bakın.
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) Eklentinin işlevlerini çağırma
Son olarak, yapay zekanın işlev çağrısını kullanarak eklentinizin işlevlerini çağırmasını sağlayabilirsiniz. Aşağıda, yapay zekanın, ışığı açmak için change_state
işlevini çağırmadan önce Lights
eklentisinden get_lights
işlevini çağırmaya ikna edildiğini gösteren bir örnek verilmiştir.
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_choice_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));
Yukarıdaki kodla aşağıdakine benzer bir yanıt almanız gerekir:
Rol | İleti |
---|---|
🔵 kullanıcı | Lütfen lambayı açın |
🔴 Yardımcısı (işlev çağrısı) | Lights.get_lights() |
🟢 Aracı | [{ "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" }] |
🔴 Yardımcısı (işlev çağrısı) | Lights.change_state(1, { "açık": true }) |
🟢 Aracı | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴 Yardımcısı | Lamba artık açık |
Bahşiş
Bir eklenti işlevini doğrudan çağırabilirsiniz ancak bu önerilmez çünkü hangi işlevlerin çağrılacağı yapay zekanın karar vermesi gerekir. Hangi işlevlerin çağrıldığı üzerinde açık denetime ihtiyacınız varsa, eklentiler yerine kod tabanınızda standart yöntemleri kullanmayı göz önünde bulundurun.
Eklenti yazmak için genel öneriler
Her senaryonun benzersiz gereksinimleri olduğu, farklı eklenti tasarımlarından yararlandığı ve birden çok LLM'yi içerdiği göz önünde bulundurulduğunda, eklenti tasarımı için herkese uygun bir kılavuz sağlamak zordur. Bununla birlikte, eklentilerin yapay zeka dostu olduğundan ve LLM'ler tarafından kolayca ve verimli bir şekilde kullanılabilmesini sağlamaya yönelik bazı genel öneriler ve yönergeler aşağıdadır.
Yalnızca gerekli eklentileri içeri aktarma
Yalnızca belirli senaryonuz için gerekli işlevleri içeren eklentileri içeri aktarın. Bu yaklaşım yalnızca tüketilen giriş belirteçlerinin sayısını azaltmakla kalmaz, aynı zamanda senaryoda kullanılmayan işlevlere yönelik hatalı çağrıları da en aza indirir. Genel olarak, bu strateji işlev çağırma doğruluğunu geliştirmeli ve hatalı pozitiflerin sayısını azaltmalıdır.
Ayrıca OpenAI, tek bir API çağrısında en fazla 20 araç kullanmanızı önerir; ideal olarak, en fazla 10 araç. OpenAI tarafından belirtildiği gibi:
Eklentileri yapay zeka kullanımına uygun hale getirme
LLM'nin eklentileri anlama ve kullanma becerisini geliştirmek için şu yönergeleri izlemesi önerilir:
Açıklayıcı ve kısa işlev adları kullanın: İşlev adlarının, modelin her işlevi ne zaman seçeceklerini anlamasına yardımcı olmak için amaçlarını açıkça ifade ettiğinden emin olun. Bir işlev adı belirsizse, netlik için yeniden adlandırmayı göz önünde bulundurun. İşlev adlarını kısaltmak için kısaltmalar veya kısaltmalar kullanmaktan kaçının. Gerekli olduğunda ek bağlam ve yönergeler sağlamak için
DescriptionAttribute
kullanın, belirteç tüketimini en aza indirin.İşlev parametrelerini en aza indirin: İşlev parametrelerinin sayısını sınırlayın ve mümkün olduğunda ilkel türleri kullanın. Bu yaklaşım belirteç tüketimini azaltır ve işlev imzasını basitleştirerek LLM'nin işlev parametrelerini etkili bir şekilde eşleştirmesini kolaylaştırır.
Fonksiyon parametrelerini net bir şekilde adlandırın: Amacını netleştirmek için fonksiyon parametrelerine açıklayıcı adlar atayın. Bu, LLM'nin parametreler hakkında düşünmesine ve doğru değerler sağlamasına yardımcı olacağı için, parametre adlarını kısaltmak için kısaltmalar veya kısaltmalar kullanmaktan kaçının. İşlev adlarıyla olduğu gibi,
DescriptionAttribute
yalnızca belirteç tüketimini en aza indirmek için gerektiğinde kullanın.
İşlev sayısı ve sorumlulukları arasında doğru dengeyi bulma
Bir yandan, işlevleri tek bir sorumlulukla sahip olmak, işlevleri birden çok senaryoda basit ve yeniden kullanılabilir tutmaya olanak tanıyan iyi bir uygulamadır. Öte yandan, her işlev çağrısı ağ gidiş dönüş gecikmesi ve tüketilen giriş ve çıkış belirteçlerinin sayısı açısından ek yük oluşturur: giriş belirteçleri işlev tanımını ve çağırma sonucunu LLM'ye göndermek için kullanılırken çıkış belirteçleri modelden işlev çağrısı alınırken kullanılır.
Alternatif olarak, kullanılan belirteç sayısını azaltmak ve ağ ek yükünü azaltmak için birden çok sorumluluğu olan tek bir işlev uygulanabilir, ancak bu, diğer senaryolarda daha az yeniden kullanılabilirlik maliyetine neden olur.
Ancak, birçok sorumluluğu tek bir işlevde birleştirmek, işlev parametrelerinin sayısını ve karmaşıklığını ve dönüş türünü artırabilir. Bu karmaşıklık, modelin işlev parametreleriyle doğru eşleşme sorununa yol açarak yanlış parametre veya yanlış tür değerlerine neden olabilen durumlara yol açabilir. Bu nedenle, ağ ek yükünü azaltmak için işlev sayısı ile her işlevin sahip olduğu sorumluluk sayısı arasında doğru dengeyi sağlamak ve modelin işlev parametreleriyle doğru şekilde eşleştiğinden emin olmak önemlidir.
Anlam Çekirdeği işlevlerini dönüştürme
Semantik Çekirdek işlevleri için dönüşüm tekniklerini, Semantik Çekirdek İşlevlerini Dönüştürme blog gönderisinde açıklandığı gibi kullanın:
İşlev davranışını değiştirme: bir işlevin varsayılan davranışının istenen sonuçla hizalanmayabileceği ve özgün işlevin uygulamasını değiştirmenin mümkün olmadığı senaryolar vardır. Böyle durumlarda, özgününü sarmalayan ve davranışını buna göre değiştiren yeni bir işlev oluşturabilirsiniz.
Bağlam bilgisi sağlayın: İşlevleri, LLM'nin çıkaramayacağı veya çıkaramayacağı parametreler gerektirebilir. Örneğin, bir işlevin geçerli kullanıcı adına hareket etmesi gerekiyorsa veya kimlik doğrulama bilgileri gerektiriyorsa, bu bağlam genellikle konak uygulaması tarafından kullanılabilir ancak LLM için kullanılamaz. Bu gibi durumlarda, LLM tarafından sağlanan bağımsız değişkenlerle birlikte barındırma uygulamasından gerekli bağlam bilgilerini sağlarken orijinal işlevi çağırmak için işlevi dönüştürebilirsiniz.
Parametre listesini, türlerini ve adlarını değiştirme: Özgün işlevin LLM'nin anlamakta zorlanabileceği karmaşık bir imzası varsa, LLM'nin daha kolay anlayabileceği daha basit bir imzaya fonksiyonu dönüştürebilirsiniz. Bu, parametre adlarını, türlerini, parametre sayısını değiştirmeyi ve karmaşık parametreleri düzleştirmeyi veya diğer ayarlamaları kaldırmayı içerebilir.
Yerel durum kullanımı
Belgeler, makaleler veya hassas bilgiler içeren e-postalar gibi nispeten büyük veya gizli veri kümelerinde çalışan eklentiler tasarlarken, LLM'ye gönderilmesi gerekmeyen özgün verileri veya ara sonuçları depolamak için yerel durumu kullanmayı göz önünde bulundurun. Bu tür senaryoların işlevleri bir durum kimliği kabul edebilir ve döndürebilir ve gerçek verileri LLM'ye geçirmek yerine yerel olarak aramanıza ve verilere erişmenize olanak tanır; bunu yalnızca sonraki işlev çağrısı için bağımsız değişken olarak geri alabilirsiniz.
Verileri yerel olarak depolayarak, işlev çağrıları sırasında gereksiz belirteç tüketiminden kaçınarak bilgileri özel ve güvenli tutabilirsiniz. Bu yaklaşım yalnızca veri gizliliğini geliştirmekle kalmaz, aynı zamanda büyük veya hassas veri kümelerini işlemede genel verimliliği de artırır.