Narzędzia powłoki

Pakiet Python w wersji beta agent-framework-tools zapewnia narzędzia do wykonywania powłoki i rozpoznawania środowiska za pośrednictwem agent_framework.tools przestrzeni nazw.

Narzędzie Użyj go, gdy
LocalShellTool Polecenia są zaufane lub indywidualnie zatwierdzone i powinny być uruchamiane w środowisku hosta procesu agenta.
DockerShellTool Polecenia powłoki generowane przez model wymagają izolacji kontenera OCI.
ShellEnvironmentProvider Model wymaga aktywnej rodziny powłoki, systemu operacyjnego, katalogu roboczego i zainstalowanych wersji interfejsu wiersza polecenia.
ShellPolicy Chcesz, aby przed zatwierdzeniem lub wykonaniem filtru wstępnego listy dozwolonych lub listy odmowy.

Warning

Wykonywanie powłoki może modyfikować pliki, uruchamiać procesy, uzyskiwać dostęp do poświadczeń i komunikować się z systemami zewnętrznymi. Użyj warstwy wykonywania o najniższych uprawnieniach, która obsługuje zadanie.

Instalowanie pakietu

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

Używanie lokalnej powłoki i świadomości środowiska

LocalShellExecutor obsługuje tryby bezstanowe i trwałe. ShellEnvironmentProvider sonduje aktywne środowisko i dodaje do kontekstu agenta wskazówki dotyczące autorytatywnej powłoki.

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 Jest również dostępny do filtrowania wstępnego poleceń. Dedykowany przykład z możliwością DockerShellExecutor uruchamiania nie jest obecnie publikowany.

Instalowanie pakietu

pip install agent-framework-tools --pre

Pakiet jest instalowany psutil w celu zakończenia podrzędnych drzew procesów, gdy upłynął limit czasu wykonywania.

Użyj LocalShellTool

LocalShellTool uruchamia polecenia bezpośrednio na hoście. Domyślnie jest to trwałe powłoki, limit czasu 30 sekund, obcięcie danych wyjściowych 64-KiB, ograniczenie katalogu roboczego i zatwierdzenie dla każdego polecenia.

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())

Użyj mode="stateless" polecenia , gdy każde wywołanie powinno zostać uruchomione w nowym procesie. Użyj zmiennej środowiskowej AGENT_FRAMEWORK_SHELL lub argumentu konstruktora shell , aby zastąpić rozpoznaną powłokę.

Ważna

LocalShellTool nie jest piaskownicą. Zatwierdzenie jest podstawową granicą zabezpieczeń. Wyłączenie zatwierdzenia wymaga .acknowledge_unsafe=True

Ogranicz polecenia za pomocą polecenia ShellPolicy

ShellPolicy stosuje listy dozwolonych wyrażeń regularnych i odmowy przed wykonaniem. Reguły odmowy mają pierwszeństwo.

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

Zasady poleceń to wstępny filtr użyteczności, a nie granica zabezpieczeń. Składnia powłoki, aliasy, zmienne, interpretery i zakodowane ładunki mogą pomijać proste dopasowywanie wzorca.

Dodaj ShellEnvironmentProvider

ShellEnvironmentProvider sonduje rodzinę powłoki, wersję, system operacyjny, katalog roboczy i wybrane wersje interfejsu wiersza polecenia, a następnie wprowadza te informacje przed uruchomieniem agenta. Domyślna lista sond to git, , pythonnodei 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)

Użyj DockerShellTool

DockerShellTool program wymaga platformy Docker lub narzędzia Podman w systemie PATH. Wartości domyślne wyłączają sieć, działają jako użytkownik niebędący użytkownikiem głównym, używają głównego systemu plików tylko do odczytu, możliwości porzucania, ograniczania pamięci do 512 MiB i ograniczania kontenera w 256 procesach.

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)

Domyślnym obrazem jest mcr.microsoft.com/azurelinux/base/core:3.0. Przekaż docker_binary="podman" , aby użyć narzędzia Podman. Dedykowany przykład z możliwością DockerShellTool uruchamiania nie jest obecnie publikowany.

Wybieranie warstwy wykonywania

Scenario Narzędzie Granica izolacji
Zaufane polecenia programistyczne LocalShellTool Zatwierdzenie w procesie hosta
Niezaufane polecenia powłoki DockerShellTool Kontener OCI z domyślnymi flagami izolacji
Niezaufany kod wygenerowany bez powłoki Hyperlight CodeAct MikroVM funkcji Hyperlight

Środowisko Go zapewnia lokalne wykonywanie powłoki i sondowanie środowiska za pomocą polecenia tool/shelltool. Zobacz Korzystanie z lokalnego narzędzia powłoki.

DockerShellTool wskazówki nie są obecnie dostępne dla języka Go.

Używanie narzędzi powłoki z agentem uprzęży

Zwykły agenci i HarnessAgent używają tej samej konfiguracji powłoki dwuczęściowej: zarejestruj funkcję wykonawcy jako narzędzie i dodaj ShellEnvironmentProvider , kiedy model powinien odbierać powłokę, system operacyjny, katalog roboczy i kontekst wersji interfejsu wiersza polecenia. HarnessAgent nie tworzy ani nie jest właścicielem funkcji wykonawczej powłoki:

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 wartość domyślna to nazwa run_shell i requireApproval: true. LocalShellExecutor wartość domyślna dla trybu trwałego, limit 64 KiB na strumień wyjściowy i brak limitu czasu; w przykładzie jawnie użyto zalecanego 30-sekundowego LocalShellExecutor.DefaultTimeout. ShellEnvironmentProviderOptionsWartość domyślna to sondowanie git, , dotnetnode, pythoni docker, z pięciosekundowym limitem czasu na sondę.

Utwórz jedną trwałą funkcję wykonawcza dla sesji użytkownika i zlikwiduj ją po zakończeniu sesji. Nie udostępniaj jej między użytkownikami ani współbieżnych konwersacji, ponieważ katalog roboczy, środowisko, historia powłoki, zadania w tle i kolejka poleceń są współużytkowane. ShellPolicy jest tylko filtrem wstępnym; Zachowaj włączone zatwierdzenie, używaj poświadczeń o najniższych uprawnieniach i preferuj DockerShellExecutor , gdy polecenia wymagają silniejszej granicy izolacji.

Narzędzia powłoki są dostępne w pakiecie w wersji wstępnej Microsoft.Agents.AI.Tools.Shell . HarnessAgent jest dostępny w witrynie Microsoft.Agents.AI.Harness.

W przypadku zwykłego agenta utwórz funkcję powłoki za pomocą client.get_shell_tool(func=shell.as_function()) polecenia i dodaj ShellEnvironmentProvider oddzielnie. create_harness_agent wykonuje oba kroki po przekazaniu shell_executorpolecenia :

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 jest opt-in i musi uwidocznić as_function(). Fabryka dodaje narzędzie powłoki i ShellEnvironmentProvider tylko wtedy, gdy klient implementuje SupportsShellTool; w przeciwnym razie rejestruje ostrzeżenie i pomija oba te elementy. shell_environment_provider_options jest opcjonalny i jest używany tylko z shell_executor.

LocalShellTool wartość domyślna to tryb trwały, 30-sekundowy limit czasu, 64-KiB połączone dane wyjściowe, ponowne zakotwiczenie katalogu roboczego i approval_mode="always_require". Ze względu na to, że domyślnie włączono zatwierdzanie narzędzia uprzężenie, przekaż element AgentSession do runelementu . Obiekt wywołujący jest właścicielem cyklu życia funkcji wykonawczej; użyj metody async with lub wywołaj close()metodę , a następnie utwórz jedno trwałe narzędzie na sesję użytkownika. Nie udostępniaj modyfikowalnego stanu powłoki między użytkownikami ani współbieżnych konwersacji.

Powłoka hosta nie jest piaskownicą. Zachowaj włączone zatwierdzanie, używaj poświadczeń z najniższymi uprawnieniami i używaj DockerShellTool ich do izolacji kontenerów. Wyłączenie zatwierdzenia wymaga approval_mode="never_require" i acknowledge_unsafe=True; ShellPolicy sam nie jest granicą zabezpieczeń.

create_harness_agent jest zwalniany w systemie agent-framework-core. Integracja powłoki jest dostarczana przez pakiet wstępny agent-framework-tools i emituje element ExperimentalWarning po włączeniu.

Spakowane narzędzie Go Harness nie jest obecnie dostępne. Utwórz lokalne narzędzie powłoki i dostawcę środowiska bezpośrednio na zwykłym agencie języka Go.