Shelltools

Das Beta-Python-Paket agent-framework-tools bietet Shellausführungs- und Umgebungsbewusstseinstools über den agent_framework.tools Namespace.

Werkzeug Verwenden Sie ihn, wenn
LocalShellTool Befehle sind vertrauenswürdig oder einzeln genehmigt und sollten in der Hostumgebung des Agentprozesses ausgeführt werden.
DockerShellTool Modellgenerierte Shellbefehle benötigen OCI-Containerisolation.
ShellEnvironmentProvider Das Modell benötigt die aktive Shellfamilie, das Betriebssystem, das Arbeitsverzeichnis und die installierten CLI-Versionen.
ShellPolicy Sie möchten vor genehmigungs- oder ausführungsfreien Listen eine Zulassungsliste oder deny-list-Vorabfilter verwenden.

Warning

Die Shellausführung kann Dateien ändern, Prozesse starten, Anmeldeinformationen zugreifen und mit externen Systemen kommunizieren. Verwenden Sie die Ausführungsebene mit den geringsten Rechten, die die Aufgabe unterstützt.

Installiere das Paket

dotnet add package Microsoft.Agents.AI.Tools.Shell --prerelease

Verwenden der lokalen Shell und des Umgebungsbewusstseins

LocalShellExecutor unterstützt zustandslose und persistente Modi. ShellEnvironmentProvider untersucht die aktive Umgebung und fügt dem Agentkontext autoritative Shell-Anleitungen hinzu.

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Tools.Shell;
using Microsoft.Extensions.AI;

var endpoint = Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("FOUNDRY_MODEL") ?? "gpt-5.4-mini";

// WARNING: DefaultAzureCredential is convenient for development but requires careful consideration in production.
// In production, consider using a specific credential (e.g., ManagedIdentityCredential) to avoid
// latency issues, unintended credential probing, and potential security risks from fallback mechanisms.
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential());

const string Instructions = """
    You are an agent with a single tool: run_shell. Use it to satisfy the
    user's request. Do not describe what you would do — actually run the
    commands. Reply with the final answer derived from real output.
    """;

// --------------------------------------------------------------------
// 1. Stateless mode — each call gets a fresh shell.
// --------------------------------------------------------------------
Console.WriteLine("### Stateless mode\n");
await using (var statelessShell = new LocalShellExecutor(new() { Mode = ShellMode.Stateless, AcknowledgeUnsafe = true }))
{
    var envProvider = new ShellEnvironmentProvider(statelessShell);
    var statelessAgent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions
    {
        ChatOptions = new()
        {
            ModelId = deploymentName,
            Instructions = Instructions,
            Tools = [statelessShell.AsAIFunction(requireApproval: false)],
        },
        AIContextProviders = [envProvider],
    });
// --------------------------------------------------------------------
// 2. Persistent mode — one shell, reused across calls. State carries.
// --------------------------------------------------------------------
Console.WriteLine("\n### Persistent mode\n");
await using (var persistentShell = new LocalShellExecutor(new() { Mode = ShellMode.Persistent, AcknowledgeUnsafe = true }))
{
    var envProvider = new ShellEnvironmentProvider(persistentShell);
    var persistentAgent = aiProjectClient.AsAIAgent(new ChatClientAgentOptions
    {
        ChatOptions = new()
        {
            ModelId = deploymentName,
            Instructions = Instructions,
            Tools = [persistentShell.AsAIFunction(requireApproval: false)],
        },
        AIContextProviders = [envProvider],
    });

    var persistentSession = await persistentAgent.CreateSessionAsync();

    // State carries across calls in persistent mode: cd into temp, then
    // verify the next call sees the new CWD.
    Console.WriteLine(await persistentAgent.RunAsync("Change directory into the system temp folder, then print the current working directory.", persistentSession));
    Console.WriteLine();
    Console.WriteLine(await persistentAgent.RunAsync("In a NEW shell call, print the current working directory again. Tell me whether it still matches the temp folder.", persistentSession));
    Console.WriteLine();

    // Same idea with an exported variable: set in one call, read in the next.
    Console.WriteLine(await persistentAgent.RunAsync("Set the environment variable DEMO_TOKEN to the value 'hello-world'.", persistentSession));
    Console.WriteLine();
    Console.WriteLine(await persistentAgent.RunAsync("Print the current value of DEMO_TOKEN. Tell me exactly what value the shell reports.", persistentSession));
    Console.WriteLine();

    PrintSnapshot(envProvider.CurrentSnapshot!);
}

ShellPolicy ist auch für die Vorabfilterung von Befehlen verfügbar. Ein dediziertes runnable-Beispiel DockerShellExecutor wird derzeit nicht veröffentlicht.

Installiere das Paket

pip install agent-framework-tools --pre

Das Paket wird installiert psutil , um untergeordnete Prozessstrukturen zu beenden, wenn eine Ausführung zu einem Ausfall führt.

LocalShellTool verwenden

LocalShellTool führt Befehle direkt auf dem Host aus. Standardmäßig wird eine persistente Shell, ein 30-Sekunden-Timeout, 64-KiB-Ausgabekürzung, Arbeitsverzeichniseinschränkung und Genehmigung für jeden Befehl verwendet.

import asyncio
from typing import Any

from agent_framework import Agent, Message
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import LocalShellTool
from dotenv import load_dotenv

# Load environment variables from .env file
load_dotenv()
async def main() -> None:
    print("=== OpenAI Agent with LocalShellTool Example ===")
    print("NOTE: Commands will execute on your local machine.\n")

    client = OpenAIChatClient(model="gpt-5.4-nano")

    async with LocalShellTool() as shell:
        agent = Agent(
            client=client,
            instructions="You are a helpful assistant that can run shell commands to help the user.",
            tools=[client.get_shell_tool(func=shell.as_function())],
        )

        query = "Use the shell tool to execute `python --version` and show only the command output."
        print(f"User: {query}")
        result = await run_with_approvals(query, agent)
        if isinstance(result, str):
            print(f"Agent: {result}\n")
            return
        if result.text:
            print(f"Agent: {result.text}\n")
        else:
            printed = False
            for message in result.messages:
                for content in message.contents:
                    if content.type == "function_result" and content.result:
                        print(f"Agent (tool output): {content.result}\n")
                        printed = True
            if not printed:
                print("Agent: (no text output returned)\n")


async def run_with_approvals(query: str, agent: Agent) -> Any:
    """Run the agent and handle shell approvals outside tool execution."""
    current_input: str | list[Any] = query

    while True:
        result = await agent.run(current_input)
        if not result.user_input_requests:
            return result

        next_input: list[Any] = [query]
        rejected = False
        for user_input_needed in result.user_input_requests:
            if user_input_needed.function_call is None:
                continue
            print(
                f"\nShell request: {user_input_needed.function_call.name}"
                f"\nArguments: {user_input_needed.function_call.arguments}"
            )
            user_approval = await asyncio.to_thread(input, "\nApprove shell command? (y/n): ")
            approved = user_approval.strip().lower() == "y"
            next_input.append(Message("assistant", [user_input_needed]))
            next_input.append(Message("user", [user_input_needed.to_function_approval_response(approved)]))
            if not approved:
                rejected = True
                break
        if rejected:
            print("\nShell command rejected. Stopping without additional approval prompts.")
            return "Shell command execution was rejected by user."
        current_input = next_input


if __name__ == "__main__":
    asyncio.run(main())

Wird verwendet mode="stateless" , wenn jeder Anruf in einem neuen Prozess ausgeführt werden soll. Verwenden Sie die Umgebungsvariable AGENT_FRAMEWORK_SHELL oder das shell Konstruktorargument, um die aufgelöste Shell außer Kraft zu setzen.

Important

LocalShellTool ist kein Sandkasten. Die Genehmigung ist die primäre Sicherheitsgrenze. Das Deaktivieren der Genehmigung erfordert acknowledge_unsafe=True.

Einschränken von Befehlen mit ShellPolicy

ShellPolicy wendet Listen mit regulären Ausdrücken vor der Ausführung zuzulassen und zu verweigern. Ablehnungsregeln haben Vorrang.

import asyncio

from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import LocalShellTool, ShellPolicy
from dotenv import load_dotenv
load_dotenv()
async def main() -> None:
    client = OpenAIChatClient(model="gpt-5.4-nano")

    shell = LocalShellTool(
        mode="stateless",
        approval_mode="never_require",
        acknowledge_unsafe=True,
        policy=ShellPolicy(
            allowlist=[
                r"^ls(\s|$)",
                r"^pwd$",
                r"^cat\s[^|;&]+$",
                r"^git\s+(status|log|diff)(\s|$)",
                r"^python\s+--version$",
            ],
        ),
        timeout=10,
    )

    agent = Agent(
        client=client,
        instructions=(
            "You can run a narrow set of read-only shell commands (ls, pwd, cat, "
            "git status/log/diff, python --version). Anything else will be rejected."
        ),
        tools=[client.get_shell_tool(func=shell.as_function())],
    )

    query = "Summarise the current directory and print the Python version."
    print(f"User: {query}")
    result = await agent.run(query)
    print(f"Agent: {result.text}")

Warning

Eine Befehlsrichtlinie ist eine Benutzerfreundlichkeit vor filter, keine Sicherheitsgrenze. Shellsyntax, Aliase, Variablen, Dolmetscher und codierte Nutzlasten können den einfachen Musterabgleich umgehen.

Fügen Sie ShellEnvironmentProvider hinzu.

ShellEnvironmentProvider prüft die Shellfamilie, Version, Betriebssystem, Arbeitsverzeichnis und ausgewählte CLI-Versionen und fügt diese Informationen ein, bevor der Agent ausgeführt wird. Die Standardsondenliste ist git, node, , pythonund docker.

import asyncio

from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
from agent_framework.tools import (
    LocalShellTool,
    ShellEnvironmentProvider,
    ShellEnvironmentProviderOptions,
)
from dotenv import load_dotenv
load_dotenv()
def _print_snapshot(label: str, provider: ShellEnvironmentProvider) -> None:
    snapshot = provider.current_snapshot
    if snapshot is None:
        print(f"[{label}] no snapshot captured")
        return
    print(f"\n[{label}] snapshot:")
    print(f"  family            = {snapshot.family.value}")
    print(f"  os                = {snapshot.os_description}")
    print(f"  shell_version     = {snapshot.shell_version}")
    print(f"  working_directory = {snapshot.working_directory}")
    for tool, version in snapshot.tool_versions.items():
        print(f"  {tool:<17} = {version}")


async def _ask(agent: Agent, query: str) -> None:
    print(f"\nUser: {query}")
    result = await agent.run(query)
    if result.text:
        print(f"Agent: {result.text}")


async def main() -> None:
    client = OpenAIChatClient(model="gpt-5.4-nano")
    options = ShellEnvironmentProviderOptions(
        probe_tools=("git", "python", "uv", "node"),
    )

    print("=== stateless mode ===")
    async with LocalShellTool(
        mode="stateless",
        approval_mode="never_require",
        acknowledge_unsafe=True,
    ) as shell:
        provider = ShellEnvironmentProvider(shell, options)
        agent = Agent(
            client=client,
            instructions="Use the shell tool to answer the user's question.",
            tools=[client.get_shell_tool(func=shell.as_function())],
            context_providers=[provider],
        )
        await _ask(agent, "Show me the current working directory.")
        await _ask(agent, "Now `cd ..` then show the working directory again.")
        await _ask(agent, "Show the working directory once more — did `cd` persist?")
        _print_snapshot("stateless", provider)

    print("\n=== persistent mode ===")
    async with LocalShellTool(
        mode="persistent",
        confine_workdir=False,
        approval_mode="never_require",
        acknowledge_unsafe=True,
    ) as shell:
        provider = ShellEnvironmentProvider(shell, options)
        agent = Agent(
            client=client,
            instructions="Use the shell tool to answer the user's question.",
            tools=[client.get_shell_tool(func=shell.as_function())],
            context_providers=[provider],
        )
        await _ask(agent, "Show me the current working directory.")
        await _ask(agent, "Now `cd ..` then show the working directory again.")
        await _ask(agent, "Show the working directory once more — did `cd` persist?")
        _print_snapshot("persistent", provider)

DockerShellTool verwenden

DockerShellTool erfordert Docker oder Podman auf PATH. Standardmäßig werden Netzwerke deaktiviert, als Nicht-Stammbenutzer ausgeführt, ein schreibgeschütztes Stammdateisystem, Drop-Funktionen, Speicher auf 512 MiB beschränkt und der Container auf 256 Prozesse begrenzt.

from agent_framework.tools import DockerShellTool

async with DockerShellTool(
    image="mcr.microsoft.com/azurelinux/base/core:3.0",
    approval_mode="never_require",
) as shell:
    result = await shell.run("uname -a && id")
    print(result.stdout)

Das Standardbild ist mcr.microsoft.com/azurelinux/base/core:3.0. Pass docker_binary="podman" to use Podman. Ein dediziertes runnable-Beispiel DockerShellTool wird derzeit nicht veröffentlicht.

Auswählen einer Ausführungsstufe

Szenario Werkzeug Isolationsgrenze
Vertrauenswürdige Entwicklungsbefehle LocalShellTool Genehmigung im Hostprozess
Nicht vertrauenswürdige Shellbefehle DockerShellTool OCI-Container mit Standardisolationskennzeichnungen
Nicht vertrauenswürdiger generierter Code ohne Shell Hyperlight CodeAct Hyperlight microVM

Go bietet lokale Shellausführung und Umgebungsdurchstechung tool/shelltool. Weitere Informationen finden Sie unter Verwenden des lokalen Shelltools.

DockerShellTool Anleitungen sind derzeit nicht für Go verfügbar.

Verwenden von Shelltools mit Harness Agent

Einfache Agents und HarnessAgent verwenden dieselbe zweiteilige Shelleinrichtung: Registrieren Sie die Funktion des Executors als Tool, und fügen Sie hinzu ShellEnvironmentProvider , wann das Modell Shell, Betriebssystem, Arbeitsverzeichnis und CLI-Versionskontext empfangen soll. HarnessAgent erstellt oder besitzt keinen Shell-Executor:

using System.IO;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Tools.Shell;
using Microsoft.Extensions.AI;

await using var shell = new LocalShellExecutor(new LocalShellExecutorOptions
{
    WorkingDirectory = Directory.GetCurrentDirectory(),
    Timeout = LocalShellExecutor.DefaultTimeout,
});

AIAgent agent = chatClient.AsHarnessAgent(new HarnessAgentOptions
{
    AIContextProviders = [new ShellEnvironmentProvider(shell)],
    ChatOptions = new ChatOptions
    {
        Tools = [shell.AsAIFunction(requireApproval: true)],
    },
});

AsAIFunction standardmäßig auf den Namen run_shell und requireApproval: true. LocalShellExecutor Standardmäßig wird der persistente Modus, eine 64-KiB-Kapitälchen pro Ausgabedatenstrom und kein Timeout verwendet. Im Beispiel wird explizit die empfohlene 30-Sekunden-Funktion LocalShellExecutor.DefaultTimeoutverwendet. ShellEnvironmentProviderOptionsStandardmäßig wird pro Probe ein Fünf-Sekunden-Timeout pro Probe durchgeführtgitdockerdotnetnodepython.

Erstellen Sie einen dauerhaften Executor pro Benutzersitzung, und löschen Sie ihn, wenn die Sitzung endet. Teilen Sie sie nicht für Benutzer oder gleichzeitige Unterhaltungen, da Arbeitsverzeichnis, Umgebung, Shellverlauf, Hintergrundaufträge und die Befehlswarteschlange freigegeben werden. ShellPolicy ist nur ein Vorabfilter; Lassen Sie die Genehmigung aktiviert, verwenden Sie die Anmeldeinformationen mit den geringsten Rechten, und bevorzugen DockerShellExecutor Sie, wenn Befehle eine stärkere Isolationsgrenze erfordern.

Shelltools sind im Vorabversionspaket Microsoft.Agents.AI.Tools.Shell verfügbar. HarnessAgent ist verfügbar von Microsoft.Agents.AI.Harness.

Erstellen Sie für einen einfachen Agent die Shellfunktion, client.get_shell_tool(func=shell.as_function()) und fügen Sie sie separat hinzu ShellEnvironmentProvider . create_harness_agent führt beide Schritte aus, wenn Sie folgendes übergeben shell_executor:

from agent_framework import create_harness_agent
from agent_framework.tools import LocalShellTool, ShellEnvironmentProviderOptions

async with LocalShellTool() as shell:
    agent = create_harness_agent(
        client=client,
        shell_executor=shell,
        shell_environment_provider_options=ShellEnvironmentProviderOptions(
            probe_tools=("git", "python"),
        ),
    )

    session = agent.create_session()
    response = await agent.run("Inspect the current repository.", session=session)

shell_executor ist opt-in und muss verfügbar gemacht werden as_function(). Die Factory fügt das Shelltool hinzu und ShellEnvironmentProvider nur, wenn der Client implementiert SupportsShellToolwird. Andernfalls protokolliert er eine Warnung und überspringt beide. shell_environment_provider_options ist optional und wird nur für shell_executor.

LocalShellTool Standardmäßig wird der persistente Modus, ein 30-Sekunden-Timeout, eine kombinierte 64-KiB-Ausgabe, eine Neuankerung des Arbeitsverzeichnisses und approval_mode="always_require". Da die Genehmigung des Tools "Harness" standardmäßig aktiviert ist, übergeben Sie eine AgentSession an run. Der Aufrufer besitzt den Ausführungslebenszyklus; verwenden async with oder aufrufen close()und ein dauerhaftes Tool pro Benutzersitzung erstellen. Geben Sie keinen änderbaren Shellstatus für Benutzer oder gleichzeitige Unterhaltungen frei.

Die Hostshell ist kein Sandkasten. Lassen Sie die Genehmigung aktiviert, verwenden Sie die Anmeldeinformationen mit den geringsten Rechten und verwenden Sie DockerShellTool sie für die Containerisolation. Das Deaktivieren der Genehmigung erfordert approval_mode="never_require" und acknowledge_unsafe=True; ShellPolicy allein ist keine Sicherheitsgrenze.

create_harness_agent wird in agent-framework-core. Die Shell-Integration wird durch das Vorabversionspaket agent-framework-tools bereitgestellt und gibt einen ExperimentalWarning zeitpunkt der Aktivierung aus.

Ein verpacktes Go Harness ist derzeit nicht verfügbar. Verfassen Sie das lokale Shelltool und den Umgebungsanbieter direkt auf einem einfachen Go-Agent.