Herramientas de Shell

El paquete beta agent-framework-tools Python proporciona herramientas de ejecución de shell y reconocimiento del entorno a través del agent_framework.tools espacio de nombres .

Herramienta Úselo cuando
LocalShellTool Los comandos son de confianza o se aprueban individualmente y deben ejecutarse en el entorno de host del proceso del agente.
DockerShellTool Los comandos de shell generados por el modelo necesitan aislamiento de contenedor OCI.
ShellEnvironmentProvider El modelo necesita la familia de shell activa, el sistema operativo, el directorio de trabajo y las versiones de la CLI instaladas.
ShellPolicy Desea un filtro previo de lista de permitidos o de lista de denegación antes de la aprobación o ejecución.

Warning

La ejecución del shell puede modificar archivos, iniciar procesos, acceder a credenciales y comunicarse con sistemas externos. Use el nivel de ejecución con privilegios mínimos que admite la tarea.

Instalar el paquete

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

Uso del shell local y el reconocimiento del entorno

LocalShellExecutor admite modos sin estado y persistentes. ShellEnvironmentProvider sondea el entorno activo y agrega instrucciones de shell autoritativas al contexto del agente.

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 también está disponible para el filtrado previo del comando. Actualmente no se publica un ejemplo ejecutable DockerShellExecutor dedicado.

Instalar el paquete

pip install agent-framework-tools --pre

El paquete se psutil instala para finalizar los árboles de proceso secundarios cuando se agota el tiempo de espera de una ejecución.

Utilice LocalShellTool

LocalShellTool ejecuta comandos directamente en el host. El valor predeterminado es un shell persistente, un tiempo de espera de 30 segundos, truncamiento de salida de 64 KiB, bloqueo de directorios de trabajo y aprobación para cada comando.

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

Use mode="stateless" cuando cada llamada se ejecute en un proceso nuevo. Use la variable de AGENT_FRAMEWORK_SHELL entorno o el shell argumento constructor para invalidar el shell resuelto.

Importante

LocalShellTool no es un espacio aislado. La aprobación es el límite de seguridad principal. La deshabilitación de la aprobación requiere acknowledge_unsafe=True.

Restricción de comandos con ShellPolicy

ShellPolicy aplica listas de permitidos y denegados de expresiones regulares antes de la ejecución. Las reglas de denegación tienen prioridad.

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

Una directiva de comandos es un filtro previo de facilidad de uso, no un límite de seguridad. La sintaxis de Shell, alias, variables, intérpretes y cargas codificadas puede omitir la coincidencia de patrones simple.

Agregue ShellEnvironmentProvider.

ShellEnvironmentProvider sondea la familia de shell, la versión, el sistema operativo, el directorio de trabajo y las versiones seleccionadas de la CLI y, a continuación, inserta esa información antes de que se ejecute el agente. La lista de sondeos predeterminada es git, node, pythony 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)

Utilice DockerShellTool

DockerShellTool requiere Docker o Podman en PATH. Los valores predeterminados deshabilitan las redes, se ejecutan como un usuario no raíz, usan un sistema de archivos raíz de solo lectura, quitan funcionalidades, limitan la memoria a 512 MiB y limitan el contenedor en 256 procesos.

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)

La imagen predeterminada es mcr.microsoft.com/azurelinux/base/core:3.0. Pase docker_binary="podman" para usar Podman. Actualmente no se publica un ejemplo ejecutable DockerShellTool dedicado.

Elección de un nivel de ejecución

Escenario Herramienta Límite de aislamiento
Comandos de desarrollo de confianza LocalShellTool Aprobación en el proceso de host
Comandos de shell que no son de confianza DockerShellTool Contenedor de OCI con marcas de aislamiento predeterminadas
Código generado que no es de confianza sin un shell Hyperlight CodeAct MicroVM de Hyperlight

Go proporciona la ejecución del shell local y el sondeo del entorno a través de tool/shelltool. Consulte Uso de la herramienta de shell local.

DockerShellTool Las instrucciones no están disponibles actualmente para Go.

Uso de herramientas de shell con el agente de Harness

Agentes sin formato y HarnessAgent usan la misma configuración del shell de dos partes: registre la función del ejecutor como una herramienta y agregue ShellEnvironmentProvider cuándo el modelo debe recibir shell, sistema operativo, directorio de trabajo y contexto de versión de la CLI. HarnessAgent no crea ni posee un ejecutor de shell:

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 el valor predeterminado es el nombre run_shell y requireApproval: true. LocalShellExecutor el valor predeterminado es el modo persistente, un límite de 64 KiB por flujo de salida y sin tiempo de espera; en el ejemplo se usa explícitamente el valor recomendado de 30 segundos LocalShellExecutor.DefaultTimeout. ShellEnvironmentProviderOptions el valor predeterminado es sondear git, dotnet, node, pythony docker, con un tiempo de espera de cinco segundos por sondeo.

Cree un ejecutor persistente por sesión de usuario y hágalo cuando finalice la sesión. No lo comparta entre usuarios ni conversaciones simultáneas porque se comparten el directorio de trabajo, el entorno, el historial del shell, los trabajos en segundo plano y la cola de comandos. ShellPolicy es solo un filtro previo; mantener habilitada la aprobación, usar credenciales con privilegios mínimos y preferir DockerShellExecutor cuando los comandos requieren un límite de aislamiento más seguro.

Las herramientas de Shell están disponibles en el paquete de Microsoft.Agents.AI.Tools.Shell versión preliminar. HarnessAgent está disponible en Microsoft.Agents.AI.Harness.

Para un agente sin formato, cree la función de shell con client.get_shell_tool(func=shell.as_function()) y agregue ShellEnvironmentProvider por separado. create_harness_agent realiza ambos pasos al pasar 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 es opt-in y debe exponer as_function(). La factoría agrega la herramienta de shell y ShellEnvironmentProvider solo cuando el cliente implementa SupportsShellTool; de lo contrario, registra una advertencia y omite ambos. shell_environment_provider_options es opcional y solo se usa con shell_executor.

LocalShellTool el valor predeterminado es el modo persistente, un tiempo de espera de 30 segundos, la salida combinada de 64 KiB, el delimitador de directorio de trabajo y approval_mode="always_require". Dado que la aprobación de herramientas de Harness está habilitada de forma predeterminada, pase a AgentSessionrun. El autor de la llamada posee el ciclo de vida del ejecutor; use async with o llame a close()y cree una herramienta persistente por sesión de usuario. No comparta el estado de shell mutable entre usuarios ni conversaciones simultáneas.

El shell de host no es un espacio aislado. Mantenga habilitada la aprobación, use credenciales con privilegios mínimos y use DockerShellTool para el aislamiento de contenedor. La deshabilitación de la aprobación requiere approval_mode="never_require" y acknowledge_unsafe=True; ShellPolicy solo no es un límite de seguridad.

create_harness_agent se libera en agent-framework-core. El paquete de versión agent-framework-tools preliminar proporciona la integración de Shell y emite una ExperimentalWarning excepción cuando está habilitada.

Go Harness empaquetado no está disponible actualmente. Cree la herramienta de shell local y el proveedor de entorno directamente en un agente de Go sin formato.