Оболочные инструменты

Бета-Python agent-framework-tools пакет предоставляет средства выполнения оболочки и средства осведомленности о среде через agent_framework.tools пространство имен.

инструмент Используйте его, когда
LocalShellTool Команды являются доверенными или индивидуально утвержденными и должны выполняться в среде узла процесса агента.
DockerShellTool Для команд оболочки, созданных моделью, требуется изоляция OCI-container.
ShellEnvironmentProvider Модель нуждается в активном семействе оболочки, операционной системе, рабочем каталоге и установленных версиях ИНТЕРФЕЙСА командной строки.
ShellPolicy Перед утверждением или выполнением необходимо предварительно отфильтровать список разрешений или запретов.

Предупреждение

Выполнение оболочки может изменять файлы, запускать процессы, получать доступ к учетным данным и взаимодействовать с внешними системами. Используйте наименее привилегированный уровень выполнения, поддерживающий задачу.

Установите пакет

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

Использование локальной оболочки и осведомленности о среде

LocalShellExecutor поддерживает режимы без отслеживания состояния и постоянные. ShellEnvironmentProvider проверяет активную среду и добавляет в контекст агента авторитетные инструкции по оболочке.

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 также доступен для предварительной фильтрации команд. Выделенный пример запуска DockerShellExecutor в настоящее время не публикуется.

Установите пакет

pip install agent-framework-tools --pre

Пакет устанавливает psutil для завершения дочерних деревьев процессов при истечении времени ожидания выполнения.

Используйте LocalShellTool

LocalShellTool выполняет команды непосредственно на узле. По умолчанию используется постоянная оболочка, 30-секундный тайм-аут, усечение выходных данных 64-KiB, ограничение рабочего каталога и утверждение для каждой команды.

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

Используйте mode="stateless" , когда каждый вызов должен выполняться в новом процессе. AGENT_FRAMEWORK_SHELL Используйте переменную среды или shell аргумент конструктора, чтобы переопределить разрешенную оболочку.

Important

LocalShellTool не песочница. Утверждение является основной границей безопасности. Для отключения утверждения требуется acknowledge_unsafe=True.

Ограничение команд с помощью ShellPolicy

ShellPolicy Применяет регулярные выражения разрешенных и запрещенных списков перед выполнением. Запретить правила имеют приоритет.

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}")

Предупреждение

Политика команд — это предварительная фильтрация, а не граница безопасности. Синтаксис оболочки, псевдонимы, переменные, интерпретаторы и закодированные полезные данные могут обойти простое сопоставление шаблонов.

Добавить ShellEnvironmentProvider

ShellEnvironmentProvider проверяет семейство оболочки, версию, операционную систему, рабочий каталог и выбранные версии CLI, а затем внедряет эти сведения перед запуском агента. Список проб по умолчанию : git, pythonnodeи 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

DockerShellTool требуется Docker или Podman в PATH. Значения по умолчанию отключают сеть, запускаются от имени пользователя, не являющегося корневым пользователем, используют корневую файловую систему только для чтения, раскрывают возможности, ограничивают память до 512 МиБ и ограничивают контейнер в 256 процессах.

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)

По умолчанию используется mcr.microsoft.com/azurelinux/base/core:3.0изображение. Передайте docker_binary="podman" для использования Podman. Выделенный пример запуска DockerShellTool в настоящее время не публикуется.

Выбор уровня выполнения

Сценарий инструмент Граница изоляции
Команды доверенной разработки LocalShellTool Утверждение в процессе узла
Ненадежные команды оболочки DockerShellTool Контейнер OCI с флагами изоляции по умолчанию
Ненадежный созданный код без оболочки Hyperlight CodeAct Гиперлайт микроVM

Go обеспечивает выполнение локальной оболочки и проверку tool/shelltoolсреды. См . статью "Использование локального средства оболочки".

DockerShellTool В настоящее время руководство недоступно для Go.

Использование средств оболочки с агентом Use

Обычные агенты и HarnessAgent используйте ту же настройку двух частей оболочки: зарегистрируйте функцию исполнителя в качестве средства и добавьте ShellEnvironmentProvider , когда модель должна получать оболочку, операционную систему, рабочий каталог и контекст версии CLI. HarnessAgent не создает или не владеет исполнителем оболочки:

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 по умолчанию используется имя run_shell и requireApproval: true. LocalShellExecutor по умолчанию используется постоянный режим, ограничение 64-KiB для потока вывода и время ожидания; в примере явно используется рекомендуемый 30-секундный LocalShellExecutor.DefaultTimeout. ShellEnvironmentProviderOptionsпо умолчанию используется для проверки git, dotnet, pythonnodeи ( иdocker) с пятисекундным тайм-аутом для каждой пробы.

Создайте один постоянный исполнитель для каждого сеанса пользователя и удалите его при завершении сеанса. Не делитесь им между пользователями или параллельными беседами, так как общий доступ к рабочему каталогу, среде, журналу оболочки, фоновым заданиям и очереди команд. ShellPolicy — это только предварительная фильтрация; сохраняйте разрешение включено, используйте учетные данные с наименьшими привилегиями и предпочитайте DockerShellExecutor , если командам требуется более надежная граница изоляции.

Средства оболочки доступны из предварительного Microsoft.Agents.AI.Tools.Shell пакета. HarnessAgent доступен из Microsoft.Agents.AI.Harness.

Для обычного агента создайте функцию оболочки и client.get_shell_tool(func=shell.as_function()) добавьте ShellEnvironmentProvider ее отдельно. create_harness_agent выполняет оба шага при передаче 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 является согласием и должен предоставляться as_function(). Фабрика добавляет средство оболочки и ShellEnvironmentProvider только когда клиент реализует SupportsShellTool; в противном случае он регистрирует предупреждение и пропускает оба. shell_environment_provider_options является необязательным и используется только с shell_executor.

LocalShellTool по умолчанию используется постоянный режим, 30-секундный тайм-аут, 64-KiB объединенных выходных данных, перевязка рабочего каталога и approval_mode="always_require". Так как утверждение средства Использования включено по умолчанию, передайте в AgentSessionrun. Вызывающий объект владеет жизненным циклом исполнителя; используйте async with или вызовите close()и создайте одно постоянное средство для каждого сеанса пользователя. Не делитесь изменяемым состоянием оболочки между пользователями или одновременными беседами.

Оболочка узла не является песочницей. Сохраняйте разрешение включено, используйте учетные данные с минимальными привилегиями и используйте DockerShellTool для изоляции контейнеров. Отключение утверждения требуется approval_mode="never_require" и acknowledge_unsafe=True; ShellPolicy только не является границей безопасности.

create_harness_agent выпущен в agent-framework-core. Интеграция оболочки предоставляется пакетом предварительной версии agent-framework-tools и выдает ExperimentalWarning значение при включении.

В настоящее время упакованный Go Harness недоступен. Создайте локальное средство оболочки и поставщик среды непосредственно на простом агенте Go.