Skip to content

Commit ca3d693

Browse files
Copilothusseinmozannarekzhu
authored
Make DockerCommandLineCodeExecutor the default for MagenticOne team (microsoft#6684)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: husseinmozannar <25182234+husseinmozannar@users.noreply.github.com> Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com> Co-authored-by: ekzhu <320302+ekzhu@users.noreply.github.com>
1 parent 7865151 commit ca3d693

3 files changed

Lines changed: 200 additions & 9 deletions

File tree

python/packages/autogen-ext/src/autogen_ext/teams/magentic_one.py

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,61 @@
1414
from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
1515
from autogen_ext.models.openai._openai_client import BaseOpenAIChatCompletionClient
1616

17+
# Docker imports for default code executor
18+
try:
19+
import docker
20+
from docker.errors import DockerException
21+
22+
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor
23+
24+
_docker_available = True
25+
except ImportError:
26+
docker = None # type: ignore
27+
DockerException = Exception # type: ignore
28+
DockerCommandLineCodeExecutor = None # type: ignore
29+
_docker_available = False
30+
1731
SyncInputFunc = Callable[[str], str]
1832
AsyncInputFunc = Callable[[str, Optional[CancellationToken]], Awaitable[str]]
1933
InputFuncType = Union[SyncInputFunc, AsyncInputFunc]
2034

2135

36+
def _is_docker_available() -> bool:
37+
"""Check if Docker is available and running."""
38+
if not _docker_available:
39+
return False
40+
41+
try:
42+
if docker is not None:
43+
client = docker.from_env()
44+
client.ping() # type: ignore
45+
return True
46+
except DockerException:
47+
return False
48+
49+
return False
50+
51+
52+
def _create_default_code_executor() -> CodeExecutor:
53+
"""Create the default code executor, preferring Docker if available."""
54+
if _is_docker_available() and DockerCommandLineCodeExecutor is not None:
55+
try:
56+
return DockerCommandLineCodeExecutor()
57+
except Exception:
58+
# Fallback to local if Docker fails to initialize
59+
pass
60+
61+
# Issue warning and use local executor if Docker is not available
62+
warnings.warn(
63+
"Docker is not available or not running. Using LocalCommandLineCodeExecutor instead of the recommended DockerCommandLineCodeExecutor. "
64+
"For security, it is recommended to install Docker and ensure it's running before using MagenticOne. "
65+
"To install Docker, visit: https://docs.docker.com/get-docker/",
66+
UserWarning,
67+
stacklevel=3,
68+
)
69+
return LocalCommandLineCodeExecutor()
70+
71+
2272
class MagenticOne(MagenticOneGroupChat):
2373
"""
2474
MagenticOne is a specialized group chat class that integrates various agents
@@ -77,7 +127,7 @@ class MagenticOne(MagenticOneGroupChat):
77127
78128
async def example_usage():
79129
client = OpenAIChatCompletionClient(model="gpt-4o")
80-
m1 = MagenticOne(client=client)
130+
m1 = MagenticOne(client=client) # Uses DockerCommandLineCodeExecutor by default
81131
task = "Write a Python script to fetch data from an API."
82132
result = await Console(m1.run_stream(task=task))
83133
print(result)
@@ -89,20 +139,22 @@ async def example_usage():
89139
90140
.. code-block:: python
91141
92-
# Enable human-in-the-loop mode
142+
# Enable human-in-the-loop mode with explicit Docker executor
93143
import asyncio
94144
from autogen_ext.models.openai import OpenAIChatCompletionClient
95145
from autogen_ext.teams.magentic_one import MagenticOne
146+
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor
96147
from autogen_agentchat.ui import Console
97148
98149
99150
async def example_usage_hil():
100151
client = OpenAIChatCompletionClient(model="gpt-4o")
101-
# to enable human-in-the-loop mode, set hil_mode=True
102-
m1 = MagenticOne(client=client, hil_mode=True)
103-
task = "Write a Python script to fetch data from an API."
104-
result = await Console(m1.run_stream(task=task))
105-
print(result)
152+
# Explicitly specify Docker code executor for better security
153+
async with DockerCommandLineCodeExecutor() as code_executor:
154+
m1 = MagenticOne(client=client, hil_mode=True, code_executor=code_executor)
155+
task = "Write a Python script to fetch data from an API."
156+
result = await Console(m1.run_stream(task=task))
157+
print(result)
106158
107159
108160
if __name__ == "__main__":
@@ -134,11 +186,11 @@ def __init__(
134186

135187
if code_executor is None:
136188
warnings.warn(
137-
"Instantiating MagenticOne without a code_executor is deprecated. Provide a code_executor to clear this warning (e.g., code_executor=LocalCommandLineCodeExecutor() ).",
189+
"Instantiating MagenticOne without a code_executor is deprecated. Provide a code_executor to clear this warning (e.g., code_executor=DockerCommandLineCodeExecutor() ).",
138190
DeprecationWarning,
139191
stacklevel=2,
140192
)
141-
code_executor = LocalCommandLineCodeExecutor()
193+
code_executor = _create_default_code_executor()
142194

143195
fs = FileSurfer("FileSurfer", model_client=client)
144196
ws = MultimodalWebSurfer("WebSurfer", model_client=client)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Init file for teams tests."""
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
"""Tests for MagenticOne team."""
2+
3+
import os
4+
import warnings
5+
from unittest.mock import Mock, patch
6+
7+
import pytest
8+
from autogen_agentchat.agents import CodeExecutorAgent
9+
from autogen_core.models import ChatCompletionClient
10+
from autogen_ext.code_executors.local import LocalCommandLineCodeExecutor
11+
from autogen_ext.teams.magentic_one import MagenticOne
12+
13+
14+
def docker_tests_enabled() -> bool:
15+
"""Check if Docker tests should be enabled."""
16+
if os.environ.get("SKIP_DOCKER", "unset").lower() == "true":
17+
return False
18+
19+
try:
20+
import docker
21+
from docker.errors import DockerException
22+
except ImportError:
23+
return False
24+
25+
try:
26+
client = docker.from_env()
27+
client.ping() # type: ignore
28+
return True
29+
except DockerException:
30+
return False
31+
32+
33+
def _is_docker_available() -> bool:
34+
"""Local implementation of Docker availability check."""
35+
return docker_tests_enabled()
36+
37+
38+
@pytest.fixture
39+
def mock_chat_client() -> Mock:
40+
"""Create a mock chat completion client."""
41+
mock_client = Mock(spec=ChatCompletionClient)
42+
mock_client.model_info = {"function_calling": True, "json_output": True, "vision": True}
43+
return mock_client
44+
45+
46+
@pytest.mark.skipif(not docker_tests_enabled(), reason="Docker is not available")
47+
def test_magentic_one_uses_docker_by_default(mock_chat_client: Mock) -> None:
48+
"""Test that MagenticOne uses Docker code executor by default when Docker is available."""
49+
from autogen_ext.code_executors.docker import DockerCommandLineCodeExecutor
50+
51+
with warnings.catch_warnings():
52+
warnings.simplefilter("ignore", DeprecationWarning)
53+
54+
m1 = MagenticOne(client=mock_chat_client)
55+
56+
# Find the CodeExecutorAgent in the participants list
57+
code_executor_agent = None
58+
for agent in m1._participants: # type: ignore[reportPrivateUsage]
59+
if isinstance(agent, CodeExecutorAgent):
60+
code_executor_agent = agent
61+
break
62+
63+
assert code_executor_agent is not None, "CodeExecutorAgent not found"
64+
assert isinstance(
65+
code_executor_agent._code_executor, # type: ignore[reportPrivateUsage]
66+
DockerCommandLineCodeExecutor, # type: ignore[reportPrivateUsage]
67+
), f"Expected DockerCommandLineCodeExecutor, got {type(code_executor_agent._code_executor)}" # type: ignore[reportPrivateUsage]
68+
69+
70+
def test_docker_availability_check() -> None:
71+
"""Test the Docker availability check function."""
72+
# This test should pass regardless of Docker availability
73+
result = _is_docker_available()
74+
assert isinstance(result, bool)
75+
76+
77+
@patch("autogen_ext.teams.magentic_one._is_docker_available")
78+
def test_magentic_one_falls_back_to_local_when_docker_unavailable(
79+
mock_docker_check: Mock, mock_chat_client: Mock
80+
) -> None:
81+
"""Test that MagenticOne falls back to local executor when Docker is not available."""
82+
mock_docker_check.return_value = False
83+
84+
with warnings.catch_warnings(record=True) as w:
85+
warnings.simplefilter("always")
86+
87+
m1 = MagenticOne(client=mock_chat_client)
88+
89+
# Find the CodeExecutorAgent in the participants list
90+
code_executor_agent = None
91+
for agent in m1._participants: # type: ignore[reportPrivateUsage]
92+
if isinstance(agent, CodeExecutorAgent):
93+
code_executor_agent = agent
94+
break
95+
96+
assert code_executor_agent is not None, "CodeExecutorAgent not found"
97+
assert isinstance(
98+
code_executor_agent._code_executor, # type: ignore[reportPrivateUsage]
99+
LocalCommandLineCodeExecutor, # type: ignore[reportPrivateUsage]
100+
), f"Expected LocalCommandLineCodeExecutor, got {type(code_executor_agent._code_executor)}" # type: ignore[reportPrivateUsage]
101+
102+
# Check that appropriate warnings were issued
103+
warning_messages = [str(warning.message) for warning in w]
104+
docker_warning_found = any("Docker is not available" in msg for msg in warning_messages)
105+
deprecated_warning_found = any(
106+
"Instantiating MagenticOne without a code_executor is deprecated" in msg for msg in warning_messages
107+
)
108+
109+
assert docker_warning_found, f"Docker unavailable warning not found in: {warning_messages}"
110+
assert deprecated_warning_found, f"Deprecation warning not found in: {warning_messages}"
111+
112+
113+
def test_magentic_one_with_explicit_code_executor(mock_chat_client: Mock) -> None:
114+
"""Test that MagenticOne uses the provided code executor when explicitly given."""
115+
explicit_executor = LocalCommandLineCodeExecutor()
116+
117+
with warnings.catch_warnings(record=True) as w:
118+
warnings.simplefilter("always")
119+
120+
m1 = MagenticOne(client=mock_chat_client, code_executor=explicit_executor)
121+
122+
# Find the CodeExecutorAgent in the participants list
123+
code_executor_agent = None
124+
for agent in m1._participants: # type: ignore[reportPrivateUsage]
125+
if isinstance(agent, CodeExecutorAgent):
126+
code_executor_agent = agent
127+
break
128+
129+
assert code_executor_agent is not None, "CodeExecutorAgent not found"
130+
assert code_executor_agent._code_executor is explicit_executor, "Expected the explicitly provided code executor" # type: ignore[reportPrivateUsage]
131+
132+
# No deprecation warning should be issued when explicitly providing a code executor
133+
warning_messages = [str(warning.message) for warning in w]
134+
deprecated_warning_found = any(
135+
"Instantiating MagenticOne without a code_executor is deprecated" in msg for msg in warning_messages
136+
)
137+
138+
assert not deprecated_warning_found, f"Unexpected deprecation warning found: {warning_messages}"

0 commit comments

Comments
 (0)