Wat is een plug-in?
Invoegtoepassingen zijn een belangrijk onderdeel van Semantische kernel. Als u al invoegtoepassingen van ChatGPT- of Copilot-extensies in Microsoft 365 hebt gebruikt, bent u er al bekend mee. Met invoegtoepassingen kunt u uw bestaande API's inkapselen in een verzameling die kan worden gebruikt door een AI. Hierdoor kunt u uw AI de mogelijkheid geven om acties uit te voeren die anders niet kunnen worden uitgevoerd.
Achter de schermen maakt Semantische kernel gebruik van functie dieaanroept, een systeemeigen functie van de meeste van de meest recente LLM's om LLM's toe te staan, planning uit te voeren en uw API's aan te roepen. Met functieaanroepen kunnen LLM's een bepaalde functie aanroepen. Semantische kernel geeft vervolgens de aanvraag door aan de juiste functie in uw codebasis en retourneert de resultaten terug naar de LLM, zodat de LLM een definitief antwoord kan genereren.
Niet alle AI SDK's hebben een analoog concept voor plug-ins (de meeste hebben alleen functies of hulpprogramma's). In bedrijfsscenario's zijn invoegtoepassingen echter waardevol omdat ze een set functionaliteit inkapselen die weerspiegelt hoe zakelijke ontwikkelaars al services en API's ontwikkelen. Plug-ins spelen ook mooi met afhankelijkheidsinjectie. Binnen de constructor van een invoegtoepassing kunt u services injecteren die nodig zijn om het werk van de invoegtoepassing uit te voeren (bijvoorbeeld databaseverbindingen, HTTP-clients, enzovoort). Dit is moeilijk te bereiken met andere SDK's die geen invoegtoepassingen hebben.
Anatomie van een invoegtoepassing
Op hoog niveau is een invoegtoepassing een groep functies die kunnen worden blootgesteld aan AI-apps en -services. De functies binnen invoegtoepassingen kunnen vervolgens worden ingedeeld door een AI-toepassing om gebruikersaanvragen uit te voeren. Binnen Semantic Kernel kunt u deze functies automatisch aanroepen met functieaanroep.
Notitie
In andere platforms worden functies vaak aangeduid als 'hulpprogramma's' of 'acties'. In Semantische kernel gebruiken we de term 'functies' omdat ze doorgaans worden gedefinieerd als systeemeigen functies in uw codebasis.
Het bieden van functies is echter niet voldoende om een invoegtoepassing te maken. Om automatische indeling met functie-aanroepen mogelijk te maken, moeten plug-ins ook details opgeven die semantisch beschrijven hoe ze zich gedragen. Alles van de invoer, uitvoer en bijwerkingen van de functie moet worden beschreven op een manier die de AI kan begrijpen, anders roept de AI de functie niet correct aan.
De voorbeeld-WriterPlugin
-invoegtoepassing aan de rechterkant bevat bijvoorbeeld functies met semantische beschrijvingen die beschrijven wat elke functie doet. Een LLM kan deze beschrijvingen vervolgens gebruiken om de beste functies te kiezen die moeten worden aangeroepen om te voldoen aan het verzoek van een gebruiker.
In de afbeelding aan de rechterkant zou een LLM waarschijnlijk de ShortPoem
- en StoryGen
-functies aanroepen om te voldoen aan de vragen van de gebruikers aan de hand van de opgegeven semantische beschrijvingen.
Importeren van verschillende typen invoegtoepassingen
Er zijn twee primaire manieren om invoegtoepassingen te importeren in Semantische kernel: het gebruik van systeemeigen code of het gebruik van een OpenAPI-specificatie. Met het vorige programma kunt u invoegtoepassingen maken in uw bestaande codebasis die gebruikmaken van afhankelijkheden en services die u al hebt. Met deze laatste kunt u invoegtoepassingen importeren uit een OpenAPI-specificatie, die kan worden gedeeld in verschillende programmeertalen en platforms.
Hieronder geven we een eenvoudig voorbeeld van het importeren en gebruiken van een systeemeigen invoegtoepassing. Raadpleeg de volgende artikelen voor meer informatie over het importeren van deze verschillende typen invoegtoepassingen:
Tip
Wanneer u aan de slag gaat, raden we u aan systeemeigen code-invoegtoepassingen te gebruiken. Naarmate uw toepassing verder wordt ontwikkeld en als u in meerdere platformoverschrijdende teams werkt, kunt u overwegen om OpenAPI-specificaties te gebruiken om invoegtoepassingen te delen in verschillende programmeertalen en platforms.
De verschillende typen invoegtoepassingsfuncties
Binnen een invoegtoepassing heb je doorgaans twee verschillende typen functies: functies die gegevens ophalen voor retrieval augmented generation (RAG) en functies die taken automatiseren. Hoewel elk type functioneel hetzelfde is, worden ze meestal anders gebruikt binnen toepassingen die gebruikmaken van Semantische kernel.
Met ophaalfuncties kunt u bijvoorbeeld strategieën gebruiken om de prestaties te verbeteren (bijvoorbeeld caching en het gebruik van goedkopere tussenliggende modellen voor samenvatting). Terwijl u met taakautomatiseringsfuncties waarschijnlijk human-in-the-loop goedkeuringsprocessen wilt implementeren om ervoor te zorgen dat taken correct worden uitgevoerd.
Raadpleeg de volgende artikelen voor meer informatie over de verschillende typen invoegtoepassingsfuncties:
Aan de slag met invoegtoepassingen
Het gebruik van invoegtoepassingen in Semantic Kernel is altijd een proces van drie stappen:
- Definitie van uw invoegtoepassing
- Voeg de plugin toe aan uw kernel
- En roep vervolgens de functies van de invoegtoepassing aan in een prompt met functie die
Hieronder vindt u een voorbeeld op hoog niveau van het gebruik van een invoegtoepassing in Semantic Kernel. Raadpleeg de bovenstaande koppelingen voor meer gedetailleerde informatie over het maken en gebruiken van invoegtoepassingen.
1) Definieer uw invoegtoepassing
De eenvoudigste manier om een invoegtoepassing te maken is door een klasse te definiëren en aantekeningen te maken met het kenmerk KernelFunction
. Dit laat semantische kernel weten dat dit een functie is die kan worden aangeroepen door een AI of waarnaar wordt verwezen in een prompt.
U kunt invoegtoepassingen ook importeren uit een OpenAPI-specificatie.
Hieronder maken we een plugin die de status van lichten kan ophalen en de status ervan kan wijzigen.
Tip
Omdat de meeste LLM zijn getraind met Python voor functie-aanroepen, is het raadzaam om snake case te gebruiken voor functienamen en eigenschapsnamen, zelfs als u de C# of Java SDK gebruikt.
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")]
public async Task<List<LightModel>> GetLightsAsync()
{
return lights
}
[KernelFunction("get_state")]
[Description("Gets the state of a particular 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")]
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) -> List[LightModel]:
"""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"]
) -> Optional[LightModel]:
"""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
) -> Optional[LightModel]:
"""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);
}
}
U ziet dat we beschrijvingen bieden voor de functie en parameters. Dit is belangrijk voor de AI om te begrijpen wat de functie doet en hoe u deze kunt gebruiken.
Tip
Wees niet bang om gedetailleerde beschrijvingen voor uw functies te bieden als een AI problemen ondervindt bij het aanroepen ervan. Enkele voorbeelden, aanbevelingen voor het gebruik van de functie (en niet) en richtlijnen voor het ophalen van vereiste parameters kunnen allemaal nuttig zijn.
2) Voeg de invoegtoepassing toe aan uw kernel
Zodra u uw invoegtoepassing hebt gedefinieerd, kunt u deze toevoegen aan uw kernel door een nieuw exemplaar van de invoegtoepassing te maken en deze toe te voegen aan de verzameling van de invoegtoepassing van de kernel.
In dit voorbeeld ziet u de eenvoudigste manier om een klasse als invoegtoepassing toe te voegen met de methode AddFromType
. Raadpleeg het systeemeigen plug-ins toevoegen artikel voor meer informatie over andere manieren om invoegtoepassingen toe te voegen.
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) Roep de functies van de invoegtoepassing aan
Ten slotte kunt u de AI de functies van uw invoegtoepassing laten aanroepen met behulp van functie-aanroepen. Hieronder ziet u een voorbeeld dat laat zien hoe u de AI kunt coaxeren om de get_lights
functie aan te roepen vanuit de Lights
-invoegtoepassing voordat u de change_state
-functie aanroept om een licht in te schakelen.
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));
Met de bovenstaande code krijgt u een antwoord dat er als volgt uitziet:
Rol | Bericht |
---|---|
🔵 gebruiker | Schakel de lamp in |
🔴 Assistent (functieoproep) | Lights.get_lights() |
🟢 Hulpprogramma | [{ "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" }] |
🔴 Assistent (functieoproep) | Lights.change_state(1, { "isOn": true }) |
🟢 Hulpprogramma | { "id": 1, "name": "Table Lamp", "isOn": true, "brightness": 100, "hex": "FF0000" } |
🔴 Assistent- | De lamp is nu ingeschakeld |
Tip
Hoewel u een invoegtoepassingsfunctie rechtstreeks kunt aanroepen, wordt dit niet aanbevolen omdat de AI degene moet zijn die bepaalt welke functies moeten worden aangeroepen. Als u expliciete controle nodig hebt over welke functies worden aangeroepen, kunt u overwegen om standaardmethoden in uw codebase te gebruiken in plaats van invoegtoepassingen.
Algemene aanbevelingen voor het ontwerpen van invoegtoepassingen
Aangezien elk scenario unieke vereisten heeft, verschillende invoegtoepassingsontwerpen gebruikt en meerdere LLM's kan bevatten, is het lastig om een allesomvattende gids voor het ontwerpen van invoegtoepassingen te bieden. Hieronder vindt u echter enkele algemene aanbevelingen en richtlijnen om ervoor te zorgen dat invoegtoepassingen AI-vriendelijk zijn en eenvoudig en efficiënt kunnen worden gebruikt door LLM's.
Alleen de benodigde invoegtoepassingen importeren
Importeer alleen de invoegtoepassingen die functies bevatten die nodig zijn voor uw specifieke scenario. Deze aanpak vermindert niet alleen het aantal verbruikte invoertokens, maar vermindert ook het optreden van misroepen van functies die niet in het scenario worden gebruikt. Over het algemeen moet deze strategie de nauwkeurigheid van het aanroepen van functies verbeteren en het aantal false positives verlagen.
Daarnaast raadt OpenAI aan dat u niet meer dan 20 hulpprogramma's in één API-aanroep gebruikt; idealiter niet meer dan 10 gereedschap. Zoals aangegeven door OpenAI: "We raden u aan niet meer dan 20 hulpprogramma's te gebruiken in één API-aanroep. Ontwikkelaars zien doorgaans een vermindering van de mogelijkheid van het model om het juiste hulpprogramma te selecteren zodra ze tussen de 10 en 20 hulpprogramma's zijn gedefinieerd.* Voor meer informatie kunt u hun documentatie raadplegen op OpenAI Function Calling Guide.
Invoegtoepassingen AI-vriendelijk maken
Als u de mogelijkheid van LLM wilt verbeteren om invoegtoepassingen te begrijpen en te gebruiken, wordt u aangeraden deze richtlijnen te volgen:
Beschrijvende en beknopte functienamen gebruiken: Zorg ervoor dat functienamen duidelijk hun doel overbrengen om het model te helpen begrijpen wanneer elke functie moet worden geselecteerd. Als een functienaam dubbelzinnig is, kunt u de naam ervan voor duidelijkheid wijzigen. Vermijd het gebruik van afkortingen of acroniemen om functienamen te verkorten. Gebruik de
DescriptionAttribute
om alleen indien nodig aanvullende context en instructies te bieden, waardoor het tokenverbruik wordt geminimaliseerd.Functieparameters minimaliseren: Het aantal functieparameters beperken en waar mogelijk primitieve typen gebruiken. Deze benadering vermindert het tokenverbruik en vereenvoudigt de functiehandtekening, waardoor de LLM gemakkelijker kan overeenkomen met functieparameters.
Naam de functieparameters duidelijk: Geef beschrijvende namen aan de functieparameters om hun doel te verduidelijken. Vermijd het gebruik van afkortingen of acroniemen om parameternamen te verkorten, omdat dit de LLM helpt bij het redeneren van de parameters en het leveren van nauwkeurige waarden. Net als bij functienamen gebruikt u de
DescriptionAttribute
alleen wanneer dat nodig is om het tokenverbruik te minimaliseren.
Een juiste balans vinden tussen het aantal functies en hun verantwoordelijkheden
Aan de ene kant is het hebben van functies met één verantwoordelijkheid een goede gewoonte om functies eenvoudig en herbruikbaar te houden in meerdere scenario's. Aan de andere kant leidt elke functie-aanroep tot overhead in termen van latentie van netwerkrondes en het aantal verbruikte invoer- en uitvoertokens: invoertokens worden gebruikt om de functiedefinitie en het aanroepresultaat naar de LLM te verzenden, terwijl uitvoertokens worden gebruikt bij het ontvangen van de functie-aanroep van het model.
Een enkele functie met meerdere verantwoordelijkheden kan ook worden geïmplementeerd om het aantal verbruikte tokens te verminderen en de netwerkoverhead te verlagen, hoewel dit de kosten voor verminderde hergebruik in andere scenario's met zich mee brengt.
Het consolideren van veel verantwoordelijkheden in één functie kan echter het aantal en de complexiteit van functieparameters en het retourtype ervan verhogen. Deze complexiteit kan leiden tot situaties waarin het model moeite kan hebben om correct overeen te komen met de functieparameters, wat resulteert in gemiste parameters of waarden van onjuist type. Daarom is het essentieel om de juiste balans te vinden tussen het aantal functies om de netwerkoverhead en het aantal verantwoordelijkheden te verminderen dat elke functie heeft, zodat het model nauwkeurig kan overeenkomen met functieparameters.
Semantische kernelfuncties transformeren
Gebruik de transformatietechnieken voor semantische kernelfuncties zoals beschreven in de Semantische kernelfuncties transformeren blogpost om:
Gedrag van functie wijzigen: Er zijn scenario's waarbij het standaardgedrag van een functie mogelijk niet overeenkomt met het gewenste resultaat en het niet haalbaar is om de implementatie van de oorspronkelijke functie te wijzigen. In dergelijke gevallen kunt u een nieuwe functie maken waarmee de oorspronkelijke functie wordt verpakt en het gedrag dienovereenkomstig wordt gewijzigd.
Contextinformatie opgeven: Functions vereist mogelijk parameters die de LLM niet of niet mag afleiden. Als een functie bijvoorbeeld namens de huidige gebruiker moet handelen of verificatiegegevens vereist, is deze context doorgaans beschikbaar voor de hosttoepassing, maar niet voor de LLM. In dergelijke gevallen kunt u de functie transformeren om de oorspronkelijke aan te roepen terwijl u de benodigde contextinformatie van de hostingtoepassing opgeeft, samen met argumenten van de LLM.
Parameterslijst, -typen en -namen wijzigen: Als de oorspronkelijke functie een complexe handtekening heeft die de LLM moeilijk kan interpreteren, kunt u de functie transformeren in een met een eenvoudigere handtekening die de LLM gemakkelijker kan begrijpen. Dit kan betrekking hebben op het wijzigen van parameternamen, typen, het aantal parameters en het afvlakken of het opheffen van complexe parameters, onder andere aanpassingen.
Lokaal toestandgebruik
Bij het ontwerpen van invoegtoepassingen die werken op relatief grote of vertrouwelijke gegevenssets, zoals documenten, artikelen of e-mailberichten met gevoelige informatie, kunt u overwegen de lokale status te gebruiken om oorspronkelijke gegevens of tussenliggende resultaten op te slaan die niet naar de LLM hoeven te worden verzonden. Functies voor dergelijke scenario's kunnen een status-id accepteren en retourneren, zodat u de gegevens lokaal kunt opzoeken en openen in plaats van de werkelijke gegevens door te geven aan de LLM, alleen om deze weer te ontvangen als argument voor de volgende functieaanroep.
Door gegevens lokaal op te slaan, kunt u de gegevens privé en veilig houden terwijl u onnodig tokenverbruik vermijdt tijdens functieoproepen. Deze aanpak verbetert niet alleen de privacy van gegevens, maar verbetert ook de algehele efficiëntie bij het verwerken van grote of gevoelige gegevenssets.
Schema voor retourtypen van functies opgeven voor een AI-model
Gebruik een van de technieken die worden beschreven in de Het retourneren van functies retourtypeschema aan LLM sectie om het retourtypeschema van de functie aan het AI-model te leveren.
Door gebruik te maken van een goed gedefinieerd retourtypeschema, kan het AI-model de beoogde eigenschappen nauwkeurig identificeren, waardoor mogelijke onnauwkeurigheden worden geëlimineerd die kunnen optreden wanneer het model aannamen maakt op basis van onvolledige of dubbelzinnige informatie, in de afwezigheid van het schema. Hierdoor wordt de nauwkeurigheid van functieaanroepen verbeterd, wat leidt tot betrouwbaardere en preciezere resultaten.