ベータ agent-framework-tools Python パッケージは、agent_framework.tools名前空間を介してシェル実行ツールと環境認識ツールを提供します。
| ツール | 次の場合に使用します。 |
|---|---|
LocalShellTool |
コマンドは信頼されているか、個別に承認されており、エージェント プロセスのホスト環境で実行する必要があります。 |
DockerShellTool |
モデルによって生成されるシェル コマンドには、OCI コンテナーの分離が必要です。 |
ShellEnvironmentProvider |
このモデルには、アクティブなシェル ファミリ、オペレーティング システム、作業ディレクトリ、およびインストールされている CLI バージョンが必要です。 |
ShellPolicy |
承認または実行の前に、許可リストまたは拒否リストの事前フィルター処理が必要です。 |
Warning
シェルの実行では、ファイルの変更、プロセスの起動、資格情報へのアクセス、外部システムとの通信を行うことができます。 タスクをサポートする最小特権の実行レベルを使用します。
パッケージをインストールする
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}")
Warning
コマンド ポリシーは、セキュリティ境界ではなく、使いやすさの事前フィルターです。 シェル構文、エイリアス、変数、インタープリター、エンコードされたペイロードは、単純なパターン マッチングをバイパスできます。
ShellEnvironmentProviderを追加する
ShellEnvironmentProvider は、シェル ファミリ、バージョン、オペレーティング システム、作業ディレクトリ、および選択した CLI バージョンをプローブし、エージェントの実行前にその情報を挿入します。 既定のプローブ リストは、 git、 node、 python、および 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 には、 PATHに Docker または Podman が必要です。 既定値は、ネットワークを無効にし、非ルート ユーザーとして実行し、読み取り専用ルート ファイル システムを使用し、機能を削除し、メモリを 512 MiB に制限し、コンテナーを 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。 Podman を使用するには、 docker_binary="podman" を渡します。 専用の実行可能な DockerShellTool サンプルは現在公開されていません。
実行レベルを選択する
| シナリオ | ツール | 分離の境界 |
|---|---|---|
| 信頼できる開発コマンド | LocalShellTool |
ホスト プロセスでの承認 |
| 信頼されていないシェル コマンド | DockerShellTool |
既定の分離フラグを持つ OCI コンテナー |
| シェルを使用せずに信頼されていない生成されたコード | Hyperlight CodeAct | Hyperlight microVM |
Go では、ローカル シェルの実行と、 tool/shelltoolを介した環境プローブが提供されます。
「ローカル シェル ツールを使用する」を参照してください。
DockerShellTool 現在、ガイダンスは Go では使用できません。
Harness Agent でシェル ツールを使用する
プレーン エージェントと HarnessAgent は、同じ 2 部構成のシェル セットアップを使用します。Executor の関数をツールとして登録し、モデルがシェル、オペレーティング システム、作業ディレクトリ、CLI バージョンのコンテキストを受け取る必要があるときに ShellEnvironmentProvider を追加します。
HarnessAgent では、シェル 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 は、既定で名前 run_shell と requireApproval: trueに設定されます。
LocalShellExecutor 既定では永続的モード、出力ストリームあたり 64 KiB の上限、タイムアウトなし。この例では、推奨される 30 秒の LocalShellExecutor.DefaultTimeoutを明示的に使用します。
ShellEnvironmentProviderOptions は、プローブごとに 5 秒のタイムアウトで、 git、 dotnet、 node、 python、および dockerのプローブに既定で設定されます。
ユーザー セッションごとに 1 つの永続的な Executor を作成し、セッションの終了時に破棄します。 作業ディレクトリ、環境、シェル履歴、バックグラウンド ジョブ、コマンド キューが共有されるため、ユーザー間または同時の会話間で共有しないでください。
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()を公開する必要があります。 ファクトリは、クライアントがSupportsShellToolを実装する場合にのみシェル ツールとShellEnvironmentProviderを追加します。それ以外の場合は警告をログに記録し、両方をスキップします。
shell_environment_provider_options は省略可能であり、 shell_executorでのみ使用されます。
LocalShellTool は、既定では永続的モード、30 秒のタイムアウト、64 KiB の組み合わせ出力、作業ディレクトリの再アンカー、および approval_mode="always_require"に設定されます。 Harness ツールの承認は既定で有効になっているため、 AgentSession を runに渡します。 呼び出し元は Executor ライフサイクルを所有します。 async with または呼び出し close()を使用し、ユーザー セッションごとに 1 つの永続的なツールを作成します。 変更可能なシェルの状態をユーザー間または同時会話で共有しないでください。
ホスト シェルはサンドボックスではありません。 承認を有効のままにし、最小限の特権の資格情報を使用し、コンテナーの分離に DockerShellTool を使用します。 承認を無効にする場合、 approval_mode="never_require" と acknowledge_unsafe=Trueが必要です。 ShellPolicy だけではセキュリティ境界ではありません。
create_harness_agent は agent-framework-coreでリリースされます。 シェル統合はプレリリース agent-framework-tools パッケージによって提供され、有効にすると ExperimentalWarning が生成されます。
パッケージ化された Go Harness は現在使用できません。 プレーンな Go エージェントでローカル シェル ツールと環境プロバイダーを直接作成します。