Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
O pacote Python beta agent-framework-tools fornece ferramentas de execução em shell e consciência ambiental através do agent_framework.tools namespace.
| Tool | Use-o quando |
|---|---|
LocalShellTool |
Os comandos são confiáveis ou aprovados individualmente e devem ser executados no ambiente anfitrião do processo agente. |
DockerShellTool |
Os comandos de shell gerados por modelos requerem isolamento em contentores OCI. |
ShellEnvironmentProvider |
O modelo necessita da família de shell ativa, do sistema operativo, do diretório de trabalho e das versões de CLI instaladas. |
ShellPolicy |
Pretende um pré-filtro de lista de permissões ou de lista de bloqueio antes da aprovação ou da execução. |
Warning
A execução shell pode modificar ficheiros, iniciar processos, aceder a credenciais e comunicar com sistemas externos. Use o nível de execução menos privilegiado que suporte a tarefa.
Instale o pacote
dotnet add package Microsoft.Agents.AI.Tools.Shell --prerelease
Use a concha local e a consciência ambiental
LocalShellExecutor suporta modos sem estado e persistentes.
ShellEnvironmentProvider sonda o ambiente ativo e adiciona orientação autoritativa do shell ao contexto do 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 também está disponível para pré-filtragem por comandos. De momento, não está publicado um exemplo executável DockerShellExecutor dedicado.
Instale o pacote
pip install agent-framework-tools --pre
O pacote instala psutil para terminar árvores de processos subordinados quando uma execução atinge o tempo limite.
Utilize LocalShellTool
LocalShellTool executa comandos diretamente no sistema anfitrião. A predefinição é um shell persistente, um tempo limite de 30 segundos, truncagem da saída em 64 KiB e reposicionamento do diretório de trabalho. As chamadas de agente através de as_function() requerem aprovação por defeito. As chamadas diretas para run() não pedem aprovação.
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())
Chamadas diretas para run() devolvem a ShellResult com campos stderr, exit_code, duration_ms, truncated, timed_out e stdout separados. Quando o shell é executado na OpenAI Responses ou no alojamento Foundry, o Agent Framework preserva a saída padrão, o erro padrão, os resultados de saída do processo e os resultados por tempo limite ao longo da continuação pelo fornecedor. Códigos de saída diferentes de zero continuam a ser falhas, a saída de erro padrão mantém-se separada e um timeout não aparece como uma saída bem-sucedida.
Os itens de transcrição shell alojados pelo fornecedor OpenAI permanecem informativos, mesmo quando um executor shell local está configurado. Apenas uma chamada explícita local_shell_call, bem formada, ou uma chamada de shell marcada com environment.type="local", entra na função local e no fluxo de aprovação. Configurar LocalShellTool sozinho não faz com que chamadas shell alojadas pelo fornecedor sejam executadas no host.
Use mode="stateless" quando cada chamada deve ser executada num processo novo. Use a AGENT_FRAMEWORK_SHELL variável de ambiente ou o shell argumento construtor para sobrescrever o shell resolvido.
Importante
LocalShellTool não é um ambiente de teste. A aprovação humana acrescenta uma etapa de revisão, mas não isola a shell. Desativar a aprovação para chamadas de agente requer acknowledge_unsafe=True.
Restringir comandos com ShellPolicy
ShellPolicy aplica listas de permissões e bloqueios baseadas em expressões regulares ao texto do comando antes de ser executado. As regras de negação têm prioridade. Não inspeciona o que o shell executa em última instância nem restringe o acesso a ficheiros.
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",
# Unsafe for production as shown: these filters do not replace human approval or isolation.
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=("Use only these shell commands: ls, pwd, cat, git status/log/diff, python --version."),
tools=[client.get_shell_tool(func=shell.as_function())],
)
Warning
A amostra incapacita a aprovação humana e só é adequada para um ambiente isolado e descartável, sem segredos ou dados valiosos. Substituições de comandos, como $(...) e backticks, podem transmitir padrões simples de lista de permissões. Padrões que apenas correspondem ao início de um comando também podem permitir operações extra. Use isolamento aplicado separadamente e permissões restritas para produção. A revisão humana pode acrescentar uma verificação, mas não isola a shell.
Prefira padrões de cadeias de caracteres. O Python compila strings com o regex motor e aplica um orçamento de um segundo a cada partida. Um timeout da lista de bloqueio nega o comando, e um timeout da lista de permissões não concede permissão. Um pré-compilado regex.Pattern usa o mesmo limite. Uma biblioteca re.Pattern padrão pré-compilada preserva os seus flags, mas não pode ser interrompida, por isso evite expressões dispendiosas ou ambíguas nessa forma.
Adicionar ShellEnvironmentProvider
ShellEnvironmentProvider sonda a família de shell, versão, sistema operativo, diretório de trabalho e versões selecionadas da CLI, depois injeta essa informação antes da execução do agente. A lista padrão de sondas é git, node, python, e 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)
Utilize DockerShellTool
DockerShellTool requer Docker ou Podman em PATH. Os valores definidos desativam a rede, executam como utilizador não-root, usam um sistema de ficheiros raiz apenas de leitura, reduzem capacidades, limitam a memória a 512 MiB e limitam o contentor a 256 processos.
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)
A imagem padrão é mcr.microsoft.com/azurelinux/base/core:3.0. Passe docker_binary="podman" para usar o Podman. De momento, não está publicado um exemplo executável DockerShellTool dedicado.
extra_run_args Use apenas para opções Docker que não enfraqueçam o isolamento configurado ou os limites de recursos. A validação reconhece opções longas, opções curtas, valores associados e opções curtas agrupadas. Rejeita sobreposições como -u--user / ,--memory-m / , / --volume-v , --network, e .--pids-limit Utilize, em vez dessa, a opção DockerShellTool do construtor correspondente.
Escolha um nível de execução
| Scenario | Tool | Limite de isolamento |
|---|---|---|
| Comandos de desenvolvimento confiáveis | LocalShellTool |
Nenhum; a aprovação humana está ativada por defeito |
| Comandos de linha de comandos não fiáveis | DockerShellTool |
Container OCI com opções de isolamento predefinidas |
| Código gerado não confiável sem shell | Hyperlight CodeAct | MicroVM hiperleve |
O Go fornece execução local da shell e sondagem do ambiente através de tool/shelltool. Consulte Utilizar a ferramenta de shell local.
DockerShellTool As orientações não estão atualmente disponíveis para Go.
Utilize ferramentas de shell com o Harness Agent
Agentes simples e HarnessAgent usam a mesma configuração de shell em duas partes: registar a função do executor como uma ferramenta e adicionar ShellEnvironmentProvider quando o modelo deve receber shell, sistema operativo, diretório de trabalho e contexto da versão CLI.
HarnessAgent não cria nem possui um executor 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 tem por predefinição o nome run_shell e requireApproval: true.
LocalShellExecutor define por defeito o modo persistente, um limite de 64 KiB por fluxo de saída e sem limite de tempo; o exemplo utiliza explicitamente o LocalShellExecutor.DefaultTimeout recomendado de 30 segundos.
ShellEnvironmentProviderOptionspor defeito, sonda git, dotnet, nodepython, , e docker, com um tempo de espera de cinco segundos por sonda.
Crie um executor persistente por sessão de utilizador e elimine-o quando a sessão terminar. Não o partilhes entre utilizadores nem por conversas em simultâneo, porque o diretório de trabalho, o ambiente, o histórico da shell, os processos em segundo plano e a fila de comandos são partilhados.
ShellPolicy é apenas um pré-filtro; Mantenha a aprovação ativada, use credenciais com privilégios mínimos e prefira DockerShellExecutor quando os comandos exigem uma fronteira de isolamento mais forte.
As ferramentas shell estão disponíveis no pacote de pré-lançamento Microsoft.Agents.AI.Tools.Shell .
HarnessAgent está disponível em Microsoft.Agents.AI.Harness.
Para um agente simples, crie a função shell com client.get_shell_tool(func=shell.as_function()) e adicione ShellEnvironmentProvider separadamente.
create_harness_agent executa ambos os passos ao passar 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 é opcional e tem de expor as_function(). A fábrica adiciona a ferramenta shell e ShellEnvironmentProvider só quando o cliente implementa SupportsShellTool; caso contrário, regista um aviso e salta ambos.
shell_environment_provider_options é opcional e é usado apenas com shell_executor.
LocalShellTool tem, por predefinição, modo persistente, um tempo limite de 30 segundos, 64 KiB de saída combinada, reancoragem do diretório de trabalho e approval_mode="always_require". Como a aprovação da ferramenta harness está ativada por defeito, passe um AgentSession para run. O chamador é responsável pelo ciclo de vida do executor; use async with ou chame close() e crie uma ferramenta persistente para cada sessão de utilizador. Não partilhe estados mutáveis do shell entre utilizadores ou conversas simultâneas.
A concha do host não é um sandbox. Mantenha a aprovação ativada, use credenciais com privilégios mínimos e use DockerShellTool para isolamento de contentores. Desativar a aprovação requer approval_mode="never_require" e acknowledge_unsafe=True; ShellPolicy sozinho não é um limite de segurança.
create_harness_agent é lançado em agent-framework-core. A integração com a linha de comandos é fornecida pelo pacote de pré-lançamento agent-framework-tools e gera um ExperimentalWarning quando esta funcionalidade é ativada.
Uma versão empacotada do Go Harness não se encontra atualmente disponível. Componha a ferramenta de shell local e o fornecedor de ambiente diretamente num agente Go simples.