mirror of
https://github.com/microsoft/autogen.git
synced 2025-12-06 03:47:55 +00:00
Merge branch 'main' into copilot/fix-6542
This commit is contained in:
commit
b3e4b44a70
@ -34,7 +34,6 @@ html[data-theme="dark"] {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Adding header icon hover and focus effects */
|
/* Adding header icon hover and focus effects */
|
||||||
.bd-header a:hover,
|
|
||||||
.bd-header a:focus-visible {
|
.bd-header a:focus-visible {
|
||||||
color: var(--pst-color-secondary) !important;
|
color: var(--pst-color-secondary) !important;
|
||||||
text-decoration: underline !important;
|
text-decoration: underline !important;
|
||||||
|
|||||||
@ -31,6 +31,7 @@ class ModelFamily:
|
|||||||
GEMINI_1_5_PRO = "gemini-1.5-pro"
|
GEMINI_1_5_PRO = "gemini-1.5-pro"
|
||||||
GEMINI_2_0_FLASH = "gemini-2.0-flash"
|
GEMINI_2_0_FLASH = "gemini-2.0-flash"
|
||||||
GEMINI_2_5_PRO = "gemini-2.5-pro"
|
GEMINI_2_5_PRO = "gemini-2.5-pro"
|
||||||
|
GEMINI_2_5_FLASH = "gemini-2.5-flash"
|
||||||
CLAUDE_3_HAIKU = "claude-3-haiku"
|
CLAUDE_3_HAIKU = "claude-3-haiku"
|
||||||
CLAUDE_3_SONNET = "claude-3-sonnet"
|
CLAUDE_3_SONNET = "claude-3-sonnet"
|
||||||
CLAUDE_3_OPUS = "claude-3-opus"
|
CLAUDE_3_OPUS = "claude-3-opus"
|
||||||
@ -64,6 +65,7 @@ class ModelFamily:
|
|||||||
"gemini-1.5-pro",
|
"gemini-1.5-pro",
|
||||||
"gemini-2.0-flash",
|
"gemini-2.0-flash",
|
||||||
"gemini-2.5-pro",
|
"gemini-2.5-pro",
|
||||||
|
"gemini-2.5-flash"
|
||||||
# anthropic_models
|
# anthropic_models
|
||||||
"claude-3-haiku",
|
"claude-3-haiku",
|
||||||
"claude-3-sonnet",
|
"claude-3-sonnet",
|
||||||
@ -107,6 +109,7 @@ class ModelFamily:
|
|||||||
ModelFamily.GEMINI_1_5_PRO,
|
ModelFamily.GEMINI_1_5_PRO,
|
||||||
ModelFamily.GEMINI_2_0_FLASH,
|
ModelFamily.GEMINI_2_0_FLASH,
|
||||||
ModelFamily.GEMINI_2_5_PRO,
|
ModelFamily.GEMINI_2_5_PRO,
|
||||||
|
ModelFamily.GEMINI_2_5_FLASH,
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@ -236,7 +236,10 @@ class DockerJupyterCodeExecutor(CodeExecutor, Component[DockerJupyterCodeExecuto
|
|||||||
else:
|
else:
|
||||||
outputs.append(json.dumps(data.data))
|
outputs.append(json.dumps(data.data))
|
||||||
else:
|
else:
|
||||||
return DockerJupyterCodeResult(exit_code=1, output=f"ERROR: {result.output}", output_files=output_files)
|
existing_output = "\n".join([str(output) for output in outputs])
|
||||||
|
return DockerJupyterCodeResult(
|
||||||
|
exit_code=1, output=existing_output + "\nERROR: " + result.output, output_files=output_files
|
||||||
|
)
|
||||||
return DockerJupyterCodeResult(
|
return DockerJupyterCodeResult(
|
||||||
exit_code=0, output="\n".join([str(output) for output in outputs]), output_files=output_files
|
exit_code=0, output="\n".join([str(output) for output in outputs]), output_files=output_files
|
||||||
)
|
)
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class LocalCommandLineCodeExecutorConfig(BaseModel):
|
|||||||
timeout: int = 60
|
timeout: int = 60
|
||||||
work_dir: Optional[str] = None
|
work_dir: Optional[str] = None
|
||||||
functions_module: str = "functions"
|
functions_module: str = "functions"
|
||||||
|
cleanup_temp_files: bool = True
|
||||||
|
|
||||||
|
|
||||||
class LocalCommandLineCodeExecutor(CodeExecutor, Component[LocalCommandLineCodeExecutorConfig]):
|
class LocalCommandLineCodeExecutor(CodeExecutor, Component[LocalCommandLineCodeExecutorConfig]):
|
||||||
@ -78,6 +79,7 @@ class LocalCommandLineCodeExecutor(CodeExecutor, Component[LocalCommandLineCodeE
|
|||||||
a default working directory will be used. The default working directory is a temporary directory.
|
a default working directory will be used. The default working directory is a temporary directory.
|
||||||
functions (List[Union[FunctionWithRequirements[Any, A], Callable[..., Any]]]): A list of functions that are available to the code executor. Default is an empty list.
|
functions (List[Union[FunctionWithRequirements[Any, A], Callable[..., Any]]]): A list of functions that are available to the code executor. Default is an empty list.
|
||||||
functions_module (str, optional): The name of the module that will be created to store the functions. Defaults to "functions".
|
functions_module (str, optional): The name of the module that will be created to store the functions. Defaults to "functions".
|
||||||
|
cleanup_temp_files (bool, optional): Whether to automatically clean up temporary files after execution. Defaults to True.
|
||||||
virtual_env_context (Optional[SimpleNamespace], optional): The virtual environment context. Defaults to None.
|
virtual_env_context (Optional[SimpleNamespace], optional): The virtual environment context. Defaults to None.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@ -154,10 +156,12 @@ $functions"""
|
|||||||
]
|
]
|
||||||
] = [],
|
] = [],
|
||||||
functions_module: str = "functions",
|
functions_module: str = "functions",
|
||||||
|
cleanup_temp_files: bool = True,
|
||||||
virtual_env_context: Optional[SimpleNamespace] = None,
|
virtual_env_context: Optional[SimpleNamespace] = None,
|
||||||
):
|
):
|
||||||
if timeout < 1:
|
if timeout < 1:
|
||||||
raise ValueError("Timeout must be greater than or equal to 1.")
|
raise ValueError("Timeout must be greater than or equal to 1.")
|
||||||
|
self._timeout = timeout
|
||||||
|
|
||||||
self._work_dir: Optional[Path] = None
|
self._work_dir: Optional[Path] = None
|
||||||
if work_dir is not None:
|
if work_dir is not None:
|
||||||
@ -174,13 +178,6 @@ $functions"""
|
|||||||
self._work_dir = work_dir
|
self._work_dir = work_dir
|
||||||
self._work_dir.mkdir(exist_ok=True)
|
self._work_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
if not functions_module.isidentifier():
|
|
||||||
raise ValueError("Module name must be a valid Python identifier")
|
|
||||||
|
|
||||||
self._functions_module = functions_module
|
|
||||||
|
|
||||||
self._timeout = timeout
|
|
||||||
|
|
||||||
self._functions = functions
|
self._functions = functions
|
||||||
# Setup could take some time so we intentionally wait for the first code block to do it.
|
# Setup could take some time so we intentionally wait for the first code block to do it.
|
||||||
if len(functions) > 0:
|
if len(functions) > 0:
|
||||||
@ -188,6 +185,11 @@ $functions"""
|
|||||||
else:
|
else:
|
||||||
self._setup_functions_complete = True
|
self._setup_functions_complete = True
|
||||||
|
|
||||||
|
if not functions_module.isidentifier():
|
||||||
|
raise ValueError("Module name must be a valid Python identifier")
|
||||||
|
self._functions_module = functions_module
|
||||||
|
|
||||||
|
self._cleanup_temp_files = cleanup_temp_files
|
||||||
self._virtual_env_context: Optional[SimpleNamespace] = virtual_env_context
|
self._virtual_env_context: Optional[SimpleNamespace] = virtual_env_context
|
||||||
|
|
||||||
self._temp_dir: Optional[tempfile.TemporaryDirectory[str]] = None
|
self._temp_dir: Optional[tempfile.TemporaryDirectory[str]] = None
|
||||||
@ -228,15 +230,6 @@ $functions"""
|
|||||||
functions="\n\n".join([to_stub(func) for func in self._functions]),
|
functions="\n\n".join([to_stub(func) for func in self._functions]),
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def functions_module(self) -> str:
|
|
||||||
"""(Experimental) The module name for the functions."""
|
|
||||||
return self._functions_module
|
|
||||||
|
|
||||||
@property
|
|
||||||
def functions(self) -> List[str]:
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timeout(self) -> int:
|
def timeout(self) -> int:
|
||||||
"""(Experimental) The timeout for code execution."""
|
"""(Experimental) The timeout for code execution."""
|
||||||
@ -254,6 +247,20 @@ $functions"""
|
|||||||
self._started = True
|
self._started = True
|
||||||
return Path(self._temp_dir.name)
|
return Path(self._temp_dir.name)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def functions(self) -> List[str]:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@property
|
||||||
|
def functions_module(self) -> str:
|
||||||
|
"""(Experimental) The module name for the functions."""
|
||||||
|
return self._functions_module
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cleanup_temp_files(self) -> bool:
|
||||||
|
"""(Experimental) Whether to automatically clean up temporary files after execution."""
|
||||||
|
return self._cleanup_temp_files
|
||||||
|
|
||||||
async def _setup_functions(self, cancellation_token: CancellationToken) -> None:
|
async def _setup_functions(self, cancellation_token: CancellationToken) -> None:
|
||||||
func_file_content = build_python_functions_file(self._functions)
|
func_file_content = build_python_functions_file(self._functions)
|
||||||
func_file = self.work_dir / f"{self._functions_module}.py"
|
func_file = self.work_dir / f"{self._functions_module}.py"
|
||||||
@ -446,7 +453,16 @@ $functions"""
|
|||||||
break
|
break
|
||||||
|
|
||||||
code_file = str(file_names[0]) if file_names else None
|
code_file = str(file_names[0]) if file_names else None
|
||||||
return CommandLineCodeResult(exit_code=exitcode, output=logs_all, code_file=code_file)
|
code_result = CommandLineCodeResult(exit_code=exitcode, output=logs_all, code_file=code_file)
|
||||||
|
|
||||||
|
if self._cleanup_temp_files:
|
||||||
|
for file in file_names:
|
||||||
|
try:
|
||||||
|
file.unlink(missing_ok=True)
|
||||||
|
except OSError as error:
|
||||||
|
logging.error(f"Failed to delete temporary file {file}: {error}")
|
||||||
|
|
||||||
|
return code_result
|
||||||
|
|
||||||
async def restart(self) -> None:
|
async def restart(self) -> None:
|
||||||
"""(Experimental) Restart the code executor."""
|
"""(Experimental) Restart the code executor."""
|
||||||
@ -488,6 +504,7 @@ $functions"""
|
|||||||
timeout=self._timeout,
|
timeout=self._timeout,
|
||||||
work_dir=str(self.work_dir),
|
work_dir=str(self.work_dir),
|
||||||
functions_module=self._functions_module,
|
functions_module=self._functions_module,
|
||||||
|
cleanup_temp_files=self._cleanup_temp_files,
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -496,4 +513,5 @@ $functions"""
|
|||||||
timeout=config.timeout,
|
timeout=config.timeout,
|
||||||
work_dir=Path(config.work_dir) if config.work_dir is not None else None,
|
work_dir=Path(config.work_dir) if config.work_dir is not None else None,
|
||||||
functions_module=config.functions_module,
|
functions_module=config.functions_module,
|
||||||
|
cleanup_temp_files=config.cleanup_temp_files,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -308,6 +308,14 @@ _MODEL_INFO: Dict[str, ModelInfo] = {
|
|||||||
"structured_output": True,
|
"structured_output": True,
|
||||||
"multiple_system_messages": False,
|
"multiple_system_messages": False,
|
||||||
},
|
},
|
||||||
|
"gemini-2.5-flash-preview-05-20": {
|
||||||
|
"vision": True,
|
||||||
|
"function_calling": True,
|
||||||
|
"json_output": True,
|
||||||
|
"family": ModelFamily.GEMINI_2_5_FLASH,
|
||||||
|
"structured_output": True,
|
||||||
|
"multiple_system_messages": False,
|
||||||
|
},
|
||||||
"claude-3-haiku-20240307": {
|
"claude-3-haiku-20240307": {
|
||||||
"vision": True,
|
"vision": True,
|
||||||
"function_calling": True,
|
"function_calling": True,
|
||||||
@ -422,6 +430,7 @@ _MODEL_TOKEN_LIMITS: Dict[str, int] = {
|
|||||||
"gemini-2.0-flash": 1048576,
|
"gemini-2.0-flash": 1048576,
|
||||||
"gemini-2.0-flash-lite-preview-02-05": 1048576,
|
"gemini-2.0-flash-lite-preview-02-05": 1048576,
|
||||||
"gemini-2.5-pro-preview-03-25": 2097152,
|
"gemini-2.5-pro-preview-03-25": 2097152,
|
||||||
|
"gemini-2.5-flash-preview-05-20": 1048576,
|
||||||
"claude-3-haiku-20240307": 50000,
|
"claude-3-haiku-20240307": 50000,
|
||||||
"claude-3-sonnet-20240229": 40000,
|
"claude-3-sonnet-20240229": 40000,
|
||||||
"claude-3-opus-20240229": 20000,
|
"claude-3-opus-20240229": 20000,
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import types
|
|||||||
import venv
|
import venv
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import AsyncGenerator, TypeAlias
|
from typing import AsyncGenerator, TypeAlias
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
@ -109,7 +110,7 @@ async def executor_and_temp_dir(
|
|||||||
request: pytest.FixtureRequest,
|
request: pytest.FixtureRequest,
|
||||||
) -> AsyncGenerator[tuple[LocalCommandLineCodeExecutor, str], None]:
|
) -> AsyncGenerator[tuple[LocalCommandLineCodeExecutor, str], None]:
|
||||||
with tempfile.TemporaryDirectory() as temp_dir:
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir)
|
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, cleanup_temp_files=False)
|
||||||
await executor.start()
|
await executor.start()
|
||||||
yield executor, temp_dir
|
yield executor, temp_dir
|
||||||
|
|
||||||
@ -399,3 +400,48 @@ async def test_ps1_script(executor_and_temp_dir: ExecutorFixture) -> None:
|
|||||||
assert result.exit_code == 0
|
assert result.exit_code == 0
|
||||||
assert "hello from powershell!" in result.output
|
assert "hello from powershell!" in result.output
|
||||||
assert result.code_file is not None
|
assert result.code_file is not None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cleanup_temp_files_behavior() -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
# Test with cleanup_temp_files=True (default)
|
||||||
|
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, cleanup_temp_files=True)
|
||||||
|
await executor.start()
|
||||||
|
cancellation_token = CancellationToken()
|
||||||
|
code_blocks = [CodeBlock(code="print('cleanup test')", language="python")]
|
||||||
|
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "cleanup test" in result.output
|
||||||
|
# The code file should have been deleted
|
||||||
|
assert result.code_file is not None
|
||||||
|
assert not Path(result.code_file).exists()
|
||||||
|
|
||||||
|
# Test with cleanup_temp_files=False
|
||||||
|
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, cleanup_temp_files=False)
|
||||||
|
await executor.start()
|
||||||
|
cancellation_token = CancellationToken()
|
||||||
|
code_blocks = [CodeBlock(code="print('no cleanup')", language="python")]
|
||||||
|
result = await executor.execute_code_blocks(code_blocks, cancellation_token)
|
||||||
|
assert result.exit_code == 0
|
||||||
|
assert "no cleanup" in result.output
|
||||||
|
# The code file should still exist
|
||||||
|
assert result.code_file is not None
|
||||||
|
assert Path(result.code_file).exists()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_cleanup_temp_files_oserror(caplog: pytest.LogCaptureFixture) -> None:
|
||||||
|
with tempfile.TemporaryDirectory() as temp_dir:
|
||||||
|
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir, cleanup_temp_files=True)
|
||||||
|
await executor.start()
|
||||||
|
cancellation_token = CancellationToken()
|
||||||
|
code_blocks = [CodeBlock(code="print('cleanup test')", language="python")]
|
||||||
|
|
||||||
|
# Patch Path.unlink to raise OSError for this test
|
||||||
|
with patch("pathlib.Path.unlink", side_effect=OSError("Mocked OSError")):
|
||||||
|
with caplog.at_level("ERROR"):
|
||||||
|
await executor.execute_code_blocks(code_blocks, cancellation_token)
|
||||||
|
# The code file should have been attempted to be deleted and failed
|
||||||
|
assert any("Failed to delete temporary file" in record.message for record in caplog.records)
|
||||||
|
assert any("Mocked OSError" in record.message for record in caplog.records)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user