Utilizzo degli strumenti funzionali con un agente

Questo passaggio dell'esercitazione illustra come usare gli strumenti di funzione con un agente creato nel servizio Completamento chat di Azure OpenAI.

Importante

Non tutti i tipi di agente supportano gli strumenti delle funzioni. Alcuni potrebbero supportare solo strumenti predefiniti personalizzati, senza consentire al chiamante di fornire le proprie funzioni. Questo passaggio usa un ChatClientAgent, che supporta gli strumenti di funzione.

Prerequisites

Per i prerequisiti e l'installazione dei pacchetti NuGet, vedere il passaggio Creare ed eseguire un agente semplice in questa esercitazione.

Creare l'agente con gli strumenti per le funzioni

Gli strumenti delle funzioni sono solo codice personalizzato che si desidera che l'agente possa invocare quando necessario. È possibile trasformare qualsiasi metodo C# in uno strumento funzione usando il AIFunctionFactory.Create metodo per creare un'istanza AIFunction dal metodo .

Se è necessario fornire descrizioni aggiuntive sulla funzione o sui relativi parametri per l'agente, in modo che possa scegliere in modo più accurato tra funzioni diverse, è possibile usare l'attributo System.ComponentModel.DescriptionAttribute nel metodo e i relativi parametri.

Ecco un esempio di uno strumento di funzione semplice che fa finta di ottenere il meteo per una determinata posizione. Viene decorato con attributi di descrizione per fornire descrizioni aggiuntive su se stesso e il relativo parametro di posizione per l'agente.

using System.ComponentModel;

[Description("Get the weather for a given location.")]
static string GetWeather([Description("The location to get the weather for.")] string location)
    => $"The weather in {location} is cloudy with a high of 15°C.";

Quando crei l'agente, puoi ora fornire lo strumento di funzione passando un elenco di strumenti al metodo AsAIAgent.

using System;
using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

AIAgent agent = new AIProjectClient(
    new Uri("<your-foundry-project-endpoint>"),
    new DefaultAzureCredential())
     .AsAIAgent(
        model: "gpt-4o-mini",
        instructions: "You are a helpful assistant",
        tools: [AIFunctionFactory.Create(GetWeather)]);

Avvertimento

DefaultAzureCredential è utile per lo sviluppo, ma richiede un'attenta considerazione nell'ambiente di produzione. Nell'ambiente di produzione prendere in considerazione l'uso di credenziali specifiche ,ad esempio ManagedIdentityCredential, per evitare problemi di latenza, probe di credenziali indesiderate e potenziali rischi per la sicurezza dai meccanismi di fallback.

Ora è possibile eseguire l'agente come di consueto e l'agente sarà in grado di chiamare la funzione dello strumento GetWeather quando necessario.

Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?"));

Importante

Non tutti i tipi di agente supportano gli strumenti delle funzioni. Alcuni potrebbero supportare solo strumenti predefiniti personalizzati, senza consentire al chiamante di fornire le proprie funzioni. Questo passaggio usa gli agenti creati tramite client di chat, che supportano gli strumenti per le funzioni.

Prerequisites

Per i prerequisiti e l'installazione dei pacchetti Python, vedere il passaggio Creare ed eseguire un agente semplice in questa esercitazione.

Creare l'agente con gli strumenti per le funzioni

Gli strumenti delle funzioni sono solo codice personalizzato che si desidera che l'agente possa invocare quando necessario. È possibile trasformare qualsiasi funzione Python in uno strumento di funzione passandola al parametro dell'agente tools durante la creazione dell'agente.

Se è necessario fornire descrizioni aggiuntive sulla funzione o sui relativi parametri per l'agente, in modo che possa scegliere in modo più accurato tra funzioni diverse, è possibile usare le annotazioni dei tipi di Annotated Python con Field e Pydantic per fornire descrizioni.

Ecco un esempio di uno strumento di funzione semplice che fa finta di ottenere il meteo per una determinata posizione. Usa annotazioni di tipo per fornire descrizioni aggiuntive sulla funzione e il relativo parametro location all'agente.

from typing import Annotated
from pydantic import Field

def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
    """Get the weather for a given location."""
    return f"The weather in {location} is cloudy with a high of 15°C."

È anche possibile usare l'elemento @tool Decorator per specificare in modo esplicito il nome e la descrizione della funzione:

from typing import Annotated
from pydantic import Field
from agent_framework import tool

@tool(name="weather_tool", description="Retrieves weather information for any location")
def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
    return f"The weather in {location} is cloudy with a high of 15°C."

Se non si specificano i parametri name e description nel decoratore @tool, il framework userà automaticamente il nome della funzione e la sua docstring come fallback.

Usare schemi espliciti con @tool

Quando è necessario il controllo completo sullo schema esposto al modello, passare il schema parametro a @tool. È possibile fornire un modello Pydantic o un dizionario dello schema JSON non elaborato.

from pydantic import BaseModel, Field

# Load environment variables from .env file
load_dotenv()


# Approach 1: Pydantic model as explicit schema
class WeatherInput(BaseModel):
    """Input schema for the weather tool."""

    location: Annotated[str, Field(description="The city name to get weather for")]
    unit: Annotated[str, Field(description="Temperature unit: celsius or fahrenheit")] = "celsius"


@tool(
    name="get_weather",
    description="Get the current weather for a given location.",
)
def get_weather(location: str, unit: str = "celsius") -> str:
    """Get the current weather for a location."""
    return f"The weather in {location} is 22 degrees {unit}."


# Approach 2: JSON schema dictionary for a trusted, non-sensitive tool.
# This receives lightweight top-level checks only; use Pydantic for runtime enforcement.
get_current_time_schema = {
    "type": "object",
    "properties": {
        "timezone": {"type": "string", "description": "The timezone to get the current time for", "default": "UTC"},
    },
}


@tool(

Passare il contesto di runtime a uno strumento

Usare i parametri di funzione normali per i valori che il modello deve fornire. Usare FunctionInvocationContext per valori di sola esecuzione, function_invocation_kwargs ad esempio o la sessione corrente. Il parametro di contesto inserito è nascosto dallo schema esposto al modello.

import asyncio
from typing import Annotated

from agent_framework import Agent, FunctionInvocationContext, tool
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
from pydantic import Field
# Define the function tool with explicit invocation context.
# The context parameter can also be declared as an untyped ``ctx`` parameter.
@tool(approval_mode="never_require")
def get_weather(
    location: Annotated[str, Field(description="The location to get the weather for.")],
    ctx: FunctionInvocationContext,
) -> str:
    """Get the weather for a given location."""
    # Extract the injected argument from the explicit context
    user_id = ctx.kwargs.get("user_id", "unknown")

    # Simulate using the user_id for logging or personalization
    print(f"Getting weather for user: {user_id}")

    return f"The weather in {location} is cloudy with a high of 15°C."


async def main() -> None:
    agent = Agent(
        client=OpenAIChatClient(),
        name="WeatherAgent",
        instructions="You are a helpful weather assistant.",
        tools=[get_weather],
    )

    # Pass the runtime context explicitly when running the agent.
    response = await agent.run(
        "What is the weather like in Amsterdam?",
        function_invocation_kwargs={"user_id": "user_123"},
    )

    print(f"Agent: {response.text}")

Per ulteriori dettagli su ctx.kwargs, ctx.session e il middleware delle funzioni, vedere Runtime Context.

Creare strumenti solo dichiarativi

Se uno strumento viene implementato all'esterno del framework (ad esempio, lato client in un'interfaccia utente), è possibile dichiararlo senza un'implementazione usando FunctionTool(..., func=None). Il modello può comunque ragionare e chiamare lo strumento e l'applicazione può fornire il risultato in un secondo momento.

# A declaration-only tool: the schema is sent to the LLM, but the framework
# has no implementation to execute. The caller must supply the result.
get_user_location = FunctionTool(
    name="get_user_location",
    func=None,
    description="Get the user's current city. Only the client application can resolve this.",
    input_model={
        "type": "object",
        "properties": {
            "reason": {"type": "string", "description": "Why the location is needed"},
        },
        "required": ["reason"],
    },
)

Analizzare i risultati degli strumenti personalizzati

Impostare result_parser su FunctionTool o @tool quando è necessario convertire un valore restituito non elaborato in una stringa o in un elenco di Content elementi. Se un parser personalizzato solleva un'eccezione, l'invocazione diretta propaga l'eccezione, mentre l'invocazione automatica della funzione restituisce un normale risultato di errore dello strumento e rispetta include_detailed_errors. Il valore restituito grezzo non viene usato come valore di fallback, quindi gestite gli errori di conversione recuperabili all'interno del parser personalizzato.

Quando si crea l'agente, è ora possibile fornire lo strumento funzione all'agente passandolo al parametro tools.

import asyncio
import os
from agent_framework.openai import OpenAIChatCompletionClient
from azure.identity import AzureCliCredential

agent = OpenAIChatCompletionClient(
    model=os.environ["AZURE_OPENAI_CHAT_COMPLETION_MODEL"],
    azure_endpoint=os.environ["AZURE_OPENAI_ENDPOINT"],
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
    credential=AzureCliCredential(),
).as_agent(
    instructions="You are a helpful assistant",
    tools=get_weather
)

Ora è possibile eseguire l'agente come di consueto e l'agente sarà in grado di chiamare la funzione dello strumento get_weather quando necessario.

async def main():
    result = await agent.run("What is the weather like in Amsterdam?")
    print(result.text)

asyncio.run(main())

Limitare la chiamata automatica degli strumenti

Imposta limiti per il client chat per controllare l'invocazione automatica degli strumenti in base ai round trip del modello, al numero totale di chiamate di funzione e al tempo di clock effettivo trascorso:

max_function_calls e max_duration_seconds sono impostati per impostazione predefinita su None, il che significa illimitato. Impostarli su valori positivi. Quando il client raggiunge un limite, smette di richiamare gli strumenti e chiede al modello una risposta di testo finale.

Questi limiti sono ottimali e vengono controllati dopo ogni batch di chiamate di strumenti paralleli, in modo che un batch possa superare il numero di chiamate o il limite di durata. Il tempo trascorso in attesa dell'approvazione dello strumento viene conteggiato ai fini di max_duration_seconds.

from agent_framework.openai import OpenAIChatCompletionClient

client = OpenAIChatCompletionClient()
client.function_invocation_configuration.update(
    {
        "max_iterations": 5,
        "max_function_calls": 20,
        "max_duration_seconds": 30.0,
    }
)

Dettagli dell'errore dello strumento di controllo

Per impostazione predefinita, include_detailed_errors è False. Gli errori di esecuzione delle funzioni e di convalida degli argomenti restituiscono risultati generici al modello e ad altri canali serializzati. La diagnostica originale rimane disponibile al codice host attendibile in Content.exception. Content.to_dict() sostituisce quel campo con un marcatore di errore fisso e privo di informazioni sensibili, e le conversioni di protocollo non espongono le informazioni diagnostiche.

Impostare client.function_invocation_configuration["include_detailed_errors"] = True solo quando il risultato rimane su un canale attendibile. Il testo dell'eccezione può contenere dati sensibili e questa impostazione aggiunge il testo al risultato visibile al canale.

Creare una classe con più strumenti per le funzioni

Quando diversi strumenti condividono dipendenze o stato modificabile, incapsularli in una classe e passare i metodi legati all'agente. Usare gli attributi della classe per i valori che il modello non deve fornire, ad esempio client del servizio, flag di funzionalità o stato memorizzato nella cache.

import asyncio
from typing import Annotated

from agent_framework import Agent, tool
from agent_framework.openai import OpenAIChatClient
from dotenv import load_dotenv
class MyFunctionClass:
    def __init__(self, safe: bool = False) -> None:
        """Simple class with two tools: divide and add.

        The safe parameter controls whether divide raises on division by zero or returns `infinity` for divide by zero.
        """
        self.safe = safe

    def divide(
        self,
        a: Annotated[int, "Numerator"],
        b: Annotated[int, "Denominator"],
    ) -> str:
        """Divide two numbers, safe to use also with 0 as denominator."""
        result = "∞" if b == 0 and self.safe else a / b
        return f"{a} / {b} = {result}"

    def add(
        self,
        x: Annotated[int, "First number"],
        y: Annotated[int, "Second number"],
    ) -> str:
        return f"{x} + {y} = {x + y}"


async def main():
    # Creating my function class with safe division enabled
    tools = MyFunctionClass(safe=True)
    # Applying the tool decorator to one of the methods of the class
    add_function = tool(description="Add two numbers.")(tools.add)

    agent = Agent(
        client=OpenAIChatClient(),
        name="ToolAgent",
        instructions="Use the provided tools.",
    )
    print("=" * 60)
    print("Step 1: Call divide(10, 0) - tool returns infinity")
    query = "Divide 10 by 0"
    response = await agent.run(
        query,
        tools=[add_function, tools.divide],
    )
    print(f"Response: {response.text}")
    print("=" * 60)
    print("Step 2: Call set safe to False and call again")
    # Disabling safe mode to allow exceptions
    tools.safe = False

Questo modello è ideale per lo stato degli strumenti di lunga durata. Usare FunctionInvocationContext invece quando il valore cambia per ogni chiamata.

Strumenti per le funzioni

Gli strumenti di funzione consentono agli agenti di chiamare funzioni Go personalizzate. Il pacchetto functool offre un modo semplice per definire strumenti tipizzati in modo sicuro con generazione automatica dello schema.

Definire uno strumento per le funzioni

import (
    "context"

    "github.com/microsoft/agent-framework-go/tool"
    "github.com/microsoft/agent-framework-go/tool/functool"
)

var weatherTool = functool.MustNew(functool.Config{
    Name:        "weather",
    Description: "Get the current weather for a given location",
}, func(_ context.Context, location string) (string, error) {
    return fmt.Sprintf("The weather in %s is cloudy with a high of 15°C.", location), nil
})

La firma della funzione determina lo schema di input dello strumento. Il context.Context parametro viene inserito dal framework e non è esposto al modello.

Tipi di input strutturati

Per gli strumenti con più parametri, definire uno struct:

type WeatherInput struct {
    Location string `json:"location" jsonschema:"description=The city to check weather for"`
    Unit     string `json:"unit" jsonschema:"description=Temperature unit (celsius or fahrenheit),enum=celsius,enum=fahrenheit"`
}

var weatherTool = functool.MustNew(functool.Config{
    Name:        "weather",
    Description: "Get weather for a location",
}, func(_ context.Context, input WeatherInput) (string, error) {
    return fmt.Sprintf("Weather in %s: 15°%s", input.Location, input.Unit), nil
})

Creare un agente con strumenti

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You are a helpful assistant.",
    Config: agent.Config{
        Tools: []tool.Tool{weatherTool},
    },
})

resp, err := a.RunText(ctx, "What is the weather like in Amsterdam?").Collect()

Usare un agente come strumento per le funzioni

Qualsiasi agente può essere incapsulato come strumento funzione per essere usato da un altro agente:

import "github.com/microsoft/agent-framework-go/tool/agenttool"

weatherAgent := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You answer questions about the weather.",
    Config: agent.Config{
        Name:        "WeatherAgent",
        Description: "An agent that answers weather questions.",
        Tools:       []tool.Tool{weatherTool},
    },
})

mainAgent := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "You are a helpful assistant who responds in French.",
    Config: agent.Config{
        Tools: []tool.Tool{agenttool.New(weatherAgent, agenttool.Config{})},
    },
})

Usare lo strumento shell locale

L'SDK Go include tool/shelltool per l'esecuzione di comandi nella shell locale. Lo strumento richiede l'approvazione per impostazione predefinita e può essere associato a un provider di contesto di ambiente in modo che il modello conosca la famiglia di shell corrente, la directory di lavoro e le versioni comuni degli strumenti.

import "github.com/microsoft/agent-framework-go/tool/shelltool"

shell, err := shelltool.NewLocal(shelltool.LocalConfig{
    Mode: shelltool.ModeStateless,
})
if err != nil {
    return err
}
defer shell.Close()

envProvider := shelltool.NewEnvironmentProvider(shell, shelltool.EnvironmentProviderConfig{})

a := foundryprovider.NewAgent(endpoint, token, foundryprovider.ModelDeployment(model), foundryprovider.AgentConfig{
    Instructions: "Run shell commands only when needed and summarize the result.",
    Config: agent.Config{
        Tools:            []tool.Tool{shell},
        ContextProviders: []agent.ContextProvider{envProvider},
    },
})

Usare shelltool.ModeStateless quando ogni chiamata deve essere eseguita in una nuova shell. Usare shelltool.ModePersistent solo quando una singola sessione agente richiede lo stato della shell, ad esempio directory modificate o variabili di ambiente esportate, per rendere persistenti le chiamate. Impostare AcknowledgeUnsafe: true solo quando si fornisce un confine di isolamento indipendente e non è necessario il meccanismo di approvazione integrato.

Usare gli strumenti di funzione con l'agente di infrastruttura

Un agente semplice usa gli strumenti forniti in fase di creazione dell’agente, mentre eventuali provider o middleware aggiuntivi vengono composti da te. Un agente Harness usa gli stessi strumenti per le funzioni, ma preconfigura la pipeline di invocazione delle funzioni, la persistenza della cronologia per chiamata di servizio, il supporto per l'approvazione degli strumenti e altre funzionalità di Harness.

Passare gli strumenti di funzione tramite HarnessAgentOptions.ChatOptions.Tools quando si crea un HarnessAgent con AsHarnessAgent:

using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;

AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
    ChatOptions = new ChatOptions
    {
        Instructions = "You are a helpful assistant.",
        Tools = [AIFunctionFactory.Create(GetWeather)],
    },
});

AgentSession session = await agent.CreateSessionAsync();
AgentResponse response = await agent.RunAsync(
    "What is the weather like in Amsterdam?",
    session);

HarnessAgent configura automaticamente FunctionInvokingChatClient. Impostare HarnessAgentOptions.MaximumIterationsPerRequest per sovrascrivere il limite di invocazione della funzione; il valore predefinito null usa quello predefinito di FunctionInvokingChatClient. L'infrastruttura inoltre aggiunge HostedWebSearchTool per impostazione predefinita, quindi impostare DisableWebSearch = true se l'agente deve esporre solo gli strumenti in ChatOptions.Tools.

Passare uno strumento oppure una sequenza di strumenti al parametro tools di create_harness_agent:

from agent_framework import create_harness_agent

agent = create_harness_agent(
    client=client,
    agent_instructions="You are a helpful assistant.",
    tools=get_weather,
)

session = agent.create_session()
response = await agent.run(
    "What is the weather like in Amsterdam?",
    session=session,
)
print(response.text)

La factory configura l'invocazione automatica della funzione e la persistenza della cronologia per ogni chiamata del servizio. Le funzioni decorate con @tool usano approval_mode="never_require" per impostazione predefinita. disable_web_search=False aggiunge anche lo strumento di ricerca sul Web del client quando il client lo supporta; imposta disable_web_search=True per ometterlo.

L'infrastruttura installa ToolApprovalMiddleware per impostazione predefinita (disable_tool_auto_approval=False) e tale middleware richiede una AgentSession per ogni esecuzione. Passare session=agent.create_session() come illustrato oppure impostare esplicitamente disable_tool_auto_approval=True, se non è necessario il middleware di approvazione dell'infrastruttura.

Un'imbracatura Go in pacchetto non è attualmente disponibile. Aggiungi strumenti per funzioni a agent.Config.Tools e componi direttamente il middleware e i provider di contesto richiesti.

Passaggi successivi

Controllo della disponibilità degli strumenti in fase di esecuzione

È possibile aggiungere o rimuovere strumenti durante l'esecuzione di un agente usando FunctionInvocationContext.add_tools() / remove_tools(), controllare le chiamate tramite middleware di funzione oppure forzare una prima chiamata specifica con tool_choice. Vedere Controllo della disponibilità degli strumenti per i modelli completi.