Implement docker based command line code executor (#1856)

* implement docker based command line code executor

* undo import

* test skips

* format

* fix type issue

* skip docker tests

* fix paths

* add docs

* Update __init__.py

* class name

* precommit

* undo twoagent change

* use relative to directly

* Update, fixes, etc.

* update doc

* Update docstring

---------

Co-authored-by: Eric Zhu <ekzhu@users.noreply.github.com>
This commit is contained in:
Jack Gerrits 2024-03-07 13:11:52 -05:00 committed by GitHub
parent 1f9284ce54
commit e9219fefc0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 701 additions and 32 deletions

View File

@ -1,6 +1,8 @@
from .base import CodeBlock, CodeExecutor, CodeExtractor, CodeResult
from .factory import CodeExecutorFactory
from .markdown_code_extractor import MarkdownCodeExtractor
from .local_commandline_code_executor import LocalCommandLineCodeExecutor, CommandLineCodeResult
from .docker_commandline_code_executor import DockerCommandLineCodeExecutor
__all__ = (
"CodeBlock",
@ -9,4 +11,7 @@ __all__ = (
"CodeExecutor",
"CodeExecutorFactory",
"MarkdownCodeExtractor",
"LocalCommandLineCodeExecutor",
"CommandLineCodeResult",
"DockerCommandLineCodeExecutor",
)

View File

@ -0,0 +1,231 @@
from __future__ import annotations
import atexit
from hashlib import md5
import logging
from pathlib import Path
from time import sleep
from types import TracebackType
import uuid
from typing import List, Optional, Type, Union
import docker
from docker.models.containers import Container
from docker.errors import ImageNotFound
from .local_commandline_code_executor import CommandLineCodeResult
from ..code_utils import TIMEOUT_MSG, _cmd
from .base import CodeBlock, CodeExecutor, CodeExtractor
from .markdown_code_extractor import MarkdownCodeExtractor
import sys
if sys.version_info >= (3, 11):
from typing import Self
else:
from typing_extensions import Self
def _wait_for_ready(container: Container, timeout: int = 60, stop_time: int = 0.1) -> None:
elapsed_time = 0
while container.status != "running" and elapsed_time < timeout:
sleep(stop_time)
elapsed_time += stop_time
container.reload()
continue
if container.status != "running":
raise ValueError("Container failed to start")
__all__ = ("DockerCommandLineCodeExecutor",)
class DockerCommandLineCodeExecutor(CodeExecutor):
def __init__(
self,
image: str = "python:3-slim",
container_name: Optional[str] = None,
timeout: int = 60,
work_dir: Union[Path, str] = Path("."),
auto_remove: bool = True,
stop_container: bool = True,
):
"""(Experimental) A code executor class that executes code through
a command line environment in a Docker container.
The executor first saves each code block in a file in the working
directory, and then executes the code file in the container.
The executor executes the code blocks in the order they are received.
Currently, the executor only supports Python and shell scripts.
For Python code, use the language "python" for the code block.
For shell scripts, use the language "bash", "shell", or "sh" for the code
block.
Args:
image (_type_, optional): Docker image to use for code execution.
Defaults to "python:3-slim".
container_name (Optional[str], optional): Name of the Docker container
which is created. If None, will autogenerate a name. Defaults to None.
timeout (int, optional): The timeout for code execution. Defaults to 60.
work_dir (Union[Path, str], optional): The working directory for the code
execution. Defaults to Path(".").
auto_remove (bool, optional): If true, will automatically remove the Docker
container when it is stopped. Defaults to True.
stop_container (bool, optional): If true, will automatically stop the
container when stop is called, when the context manager exits or when
the Python process exits with atext. Defaults to True.
Raises:
ValueError: On argument error, or if the container fails to start.
"""
if timeout < 1:
raise ValueError("Timeout must be greater than or equal to 1.")
if isinstance(work_dir, str):
work_dir = Path(work_dir)
if not work_dir.exists():
raise ValueError(f"Working directory {work_dir} does not exist.")
client = docker.from_env()
# Check if the image exists
try:
client.images.get(image)
except ImageNotFound:
logging.info(f"Pulling image {image}...")
# Let the docker exception escape if this fails.
client.images.pull(image)
if container_name is None:
container_name = f"autogen-code-exec-{uuid.uuid4()}"
# Start a container from the image, read to exec commands later
self._container = client.containers.create(
image,
name=container_name,
entrypoint="/bin/sh",
tty=True,
auto_remove=auto_remove,
volumes={str(work_dir.resolve()): {"bind": "/workspace", "mode": "rw"}},
working_dir="/workspace",
)
self._container.start()
_wait_for_ready(self._container)
def cleanup():
try:
container = client.containers.get(container_name)
container.stop()
except docker.errors.NotFound:
pass
atexit.unregister(cleanup)
if stop_container:
atexit.register(cleanup)
self._cleanup = cleanup
# Check if the container is running
if self._container.status != "running":
raise ValueError(f"Failed to start container from image {image}. Logs: {self._container.logs()}")
self._timeout = timeout
self._work_dir: Path = work_dir
@property
def timeout(self) -> int:
"""(Experimental) The timeout for code execution."""
return self._timeout
@property
def work_dir(self) -> Path:
"""(Experimental) The working directory for the code execution."""
return self._work_dir
@property
def code_extractor(self) -> CodeExtractor:
"""(Experimental) Export a code extractor that can be used by an agent."""
return MarkdownCodeExtractor()
def execute_code_blocks(self, code_blocks: List[CodeBlock]) -> CommandLineCodeResult:
"""(Experimental) Execute the code blocks and return the result.
Args:
code_blocks (List[CodeBlock]): The code blocks to execute.
Returns:
CommandlineCodeResult: The result of the code execution."""
if len(code_blocks) == 0:
raise ValueError("No code blocks to execute.")
outputs = []
files = []
last_exit_code = 0
for code_block in code_blocks:
lang = code_block.language
code = code_block.code
code_hash = md5(code.encode()).hexdigest()
# Check if there is a filename comment
# Get first line
first_line = code.split("\n")[0]
if first_line.startswith("# filename:"):
filename = first_line.split(":")[1].strip()
# Handle relative paths in the filename
path = Path(filename)
if not path.is_absolute():
path = Path("/workspace") / path
path = path.resolve()
try:
path.relative_to(Path("/workspace"))
except ValueError:
return CommandLineCodeResult(exit_code=1, output="Filename is not in the workspace")
else:
# create a file with a automatically generated name
filename = f"tmp_code_{code_hash}.{'py' if lang.startswith('python') else lang}"
code_path = self._work_dir / filename
with code_path.open("w", encoding="utf-8") as fout:
fout.write(code)
command = ["timeout", str(self._timeout), _cmd(lang), filename]
result = self._container.exec_run(command)
exit_code = result.exit_code
output = result.output.decode("utf-8")
if exit_code == 124:
output += "\n"
output += TIMEOUT_MSG
outputs.append(output)
files.append(code_path)
last_exit_code = exit_code
if exit_code != 0:
break
code_file = str(files[0]) if files else None
return CommandLineCodeResult(exit_code=last_exit_code, output="".join(outputs), code_file=code_file)
def restart(self) -> None:
"""(Experimental) Restart the code executor."""
self._container.restart()
if self._container.status != "running":
raise ValueError(f"Failed to restart container. Logs: {self._container.logs()}")
def stop(self) -> None:
"""(Experimental) Stop the code executor."""
self._cleanup()
def __enter__(self) -> Self:
return self
def __exit__(
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
self.stop()

View File

@ -2,16 +2,17 @@ from __future__ import annotations
from pathlib import Path
import sys
from time import sleep
from types import TracebackType
import uuid
from typing import Dict, Optional, Union
from typing import Dict, Optional, Type, Union
import docker
import secrets
import io
import atexit
import logging
from ..docker_commandline_code_executor import _wait_for_ready
if sys.version_info >= (3, 11):
from typing import Self
else:
@ -22,17 +23,6 @@ from .jupyter_client import JupyterClient
from .base import JupyterConnectable, JupyterConnectionInfo
def _wait_for_ready(container: docker.Container, timeout: int = 60, stop_time: int = 0.1) -> None:
elapsed_time = 0
while container.status != "running" and elapsed_time < timeout:
sleep(stop_time)
elapsed_time += stop_time
container.reload()
continue
if container.status != "running":
raise ValueError("Container failed to start")
class DockerJupyterServer(JupyterConnectable):
DEFAULT_DOCKERFILE = """FROM quay.io/jupyter/docker-stacks-foundation
@ -162,6 +152,6 @@ WORKDIR "${HOME}"
return self
def __exit__(
self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
self.stop()

View File

@ -2,7 +2,7 @@ from __future__ import annotations
from dataclasses import dataclass
from types import TracebackType
from typing import Any, Dict, List, Optional, cast
from typing import Any, Dict, List, Optional, Type, cast
import sys
if sys.version_info >= (3, 11):
@ -111,7 +111,7 @@ class JupyterKernelClient:
return self
def __exit__(
self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
self.stop()

View File

@ -5,7 +5,7 @@ from pathlib import Path
import re
from types import TracebackType
import uuid
from typing import Any, ClassVar, List, Optional, Union
from typing import Any, ClassVar, List, Optional, Type, Union
import sys
if sys.version_info >= (3, 11):
@ -201,6 +201,6 @@ the output will be a path to the image instead of the image itself.
return self
def __exit__(
self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
self.stop()

View File

@ -1,7 +1,7 @@
from __future__ import annotations
from types import TracebackType
from typing import Optional, Union, cast
from typing import Optional, Type, Union, cast
import subprocess
import signal
import sys
@ -157,6 +157,6 @@ class LocalJupyterServer(JupyterConnectable):
return self
def __exit__(
self, exc_type: Optional[type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
self, exc_type: Optional[Type[BaseException]], exc_val: Optional[BaseException], exc_tb: Optional[TracebackType]
) -> None:
self.stop()

View File

@ -1,16 +1,29 @@
from pathlib import Path
import sys
import tempfile
import pytest
from autogen.agentchat.conversable_agent import ConversableAgent
from autogen.code_utils import is_docker_running
from autogen.coding.base import CodeBlock, CodeExecutor
from autogen.coding.factory import CodeExecutorFactory
from autogen.coding.docker_commandline_code_executor import DockerCommandLineCodeExecutor
from autogen.coding.local_commandline_code_executor import LocalCommandLineCodeExecutor
from autogen.oai.openai_utils import config_list_from_json
from conftest import MOCK_OPEN_AI_API_KEY, skip_openai
from conftest import MOCK_OPEN_AI_API_KEY, skip_openai, skip_docker
if skip_docker or not is_docker_running():
classes_to_test = [LocalCommandLineCodeExecutor]
else:
classes_to_test = [LocalCommandLineCodeExecutor, DockerCommandLineCodeExecutor]
def test_create() -> None:
@pytest.mark.parametrize("cls", classes_to_test)
def test_is_code_executor(cls) -> None:
assert isinstance(cls, CodeExecutor)
def test_create_local() -> None:
config = {"executor": "commandline-local"}
executor = CodeExecutorFactory.create(config)
assert isinstance(executor, LocalCommandLineCodeExecutor)
@ -20,18 +33,30 @@ def test_create() -> None:
assert executor is config["executor"]
def test_local_commandline_executor_init() -> None:
executor = LocalCommandLineCodeExecutor(timeout=10, work_dir=".")
assert executor.timeout == 10 and executor.work_dir == "."
@pytest.mark.skipif(
skip_docker or not is_docker_running(),
reason="docker is not running or requested to skip docker tests",
)
def test_create_docker() -> None:
config = {"executor": DockerCommandLineCodeExecutor()}
executor = CodeExecutorFactory.create(config)
assert executor is config["executor"]
@pytest.mark.parametrize("cls", classes_to_test)
def test_commandline_executor_init(cls) -> None:
executor = cls(timeout=10, work_dir=".")
assert executor.timeout == 10 and str(executor.work_dir) == "."
# Try invalid working directory.
with pytest.raises(ValueError, match="Working directory .* does not exist."):
executor = LocalCommandLineCodeExecutor(timeout=111, work_dir="/invalid/directory")
executor = cls(timeout=111, work_dir="/invalid/directory")
def test_local_commandline_executor_execute_code() -> None:
@pytest.mark.parametrize("cls", classes_to_test)
def test_commandline_executor_execute_code(cls) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir)
executor = cls(work_dir=temp_dir)
_test_execute_code(executor=executor)
@ -79,9 +104,10 @@ def _test_execute_code(executor: CodeExecutor) -> None:
@pytest.mark.skipif(sys.platform in ["win32"], reason="do not run on windows")
def test_local_commandline_code_executor_timeout() -> None:
@pytest.mark.parametrize("cls", classes_to_test)
def test_commandline_code_executor_timeout(cls) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
executor = LocalCommandLineCodeExecutor(timeout=1, work_dir=temp_dir)
executor = cls(timeout=1, work_dir=temp_dir)
_test_timeout(executor)
@ -96,6 +122,20 @@ def test_local_commandline_code_executor_restart() -> None:
_test_restart(executor)
# This is kind of hard to test because each exec is a new env
@pytest.mark.skipif(
skip_docker or not is_docker_running(),
reason="docker is not running or requested to skip docker tests",
)
def test_docker_commandline_code_executor_restart() -> None:
with DockerCommandLineCodeExecutor() as executor:
result = executor.execute_code_blocks([CodeBlock(code="echo $HOME", language="sh")])
assert result.exit_code == 0
executor.restart()
result = executor.execute_code_blocks([CodeBlock(code="echo $HOME", language="sh")])
assert result.exit_code == 0
def _test_restart(executor: CodeExecutor) -> None:
# Check warning.
with pytest.warns(UserWarning, match=r".*No action is taken."):
@ -148,9 +188,10 @@ def _test_conversable_agent_capability(executor: CodeExecutor) -> None:
assert code_result.exit_code == 0 and "hello world" in code_result.output.lower().replace(",", "")
def test_local_commandline_executor_conversable_agent_code_execution() -> None:
@pytest.mark.parametrize("cls", classes_to_test)
def test_commandline_executor_conversable_agent_code_execution(cls) -> None:
with tempfile.TemporaryDirectory() as temp_dir:
executor = LocalCommandLineCodeExecutor(work_dir=temp_dir)
executor = cls(work_dir=temp_dir)
with pytest.MonkeyPatch.context() as mp:
mp.setenv("OPENAI_API_KEY", MOCK_OPEN_AI_API_KEY)
_test_conversable_agent_code_execution(executor)
@ -196,3 +237,38 @@ def test_dangerous_commands(lang, code, expected_message):
assert expected_message in str(
exc_info.value
), f"Expected message '{expected_message}' not found in '{str(exc_info.value)}'"
# This is kind of hard to test because each exec is a new env
@pytest.mark.skipif(
skip_docker or not is_docker_running(),
reason="docker is not running or requested to skip docker tests",
)
def test_docker_invalid_relative_path() -> None:
with DockerCommandLineCodeExecutor() as executor:
code = """# filename: /tmp/test.py
print("hello world")
"""
result = executor.execute_code_blocks([CodeBlock(code=code, language="python")])
assert result.exit_code == 1 and "Filename is not in the workspace" in result.output
@pytest.mark.skipif(
skip_docker or not is_docker_running(),
reason="docker is not running or requested to skip docker tests",
)
def test_docker_valid_relative_path() -> None:
with tempfile.TemporaryDirectory() as temp_dir:
temp_dir = Path(temp_dir)
with DockerCommandLineCodeExecutor(work_dir=temp_dir) as executor:
code = """# filename: test.py
print("hello world")
"""
result = executor.execute_code_blocks([CodeBlock(code=code, language="python")])
assert result.exit_code == 0
assert "hello world" in result.output
assert "test.py" in result.code_file
assert (temp_dir / "test.py") == Path(result.code_file)
assert (temp_dir / "test.py").exists()

View File

@ -46,6 +46,11 @@ except ImportError as e:
classes_to_test = []
@pytest.mark.parametrize("cls", classes_to_test)
def test_is_code_executor(cls) -> None:
assert isinstance(cls, CodeExecutor)
@pytest.mark.skipif(skip, reason=skip_reason)
def test_create_dict() -> None:
config: Dict[str, Union[str, CodeExecutor]] = {"executor": "ipython-embedded"}

View File

@ -0,0 +1,362 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Command Line Code Executor\n",
"\n",
"Command line code execution is the simplest form of code execution. Generally speaking, it will save each code block to a file and the execute that file. This means that each code block is executed in a new process. There are two forms of this executor:\n",
"\n",
"- Docker ([`DockerCommandLineCodeExecutor`](/docs/reference/coding/docker_commandline_code_executor#dockercommandlinecodeexecutor)) - this is where all commands are executed in a Docker container\n",
"- Local ([`LocalCommandLineCodeExecutor`](/docs/reference/coding/local_commandline_code_executor#localcommandlinecodeexecutor)) - this is where all commands are executed on the host machine\n",
"\n",
"This executor type is similar to the legacy code execution in AutoGen.\n",
"\n",
"## Docker\n",
"\n",
"The [`DockerCommandLineCodeExecutor`](/docs/reference/coding/docker_commandline_code_executor#dockercommandlinecodeexecutor) will create a Docker container and run all commands within that container. The default image that is used is `python:3-slim`, this can be customized by passing the `image` parameter to the constructor. If the image is not found locally then the class will try to pull it. Therefore, having built the image locally is enough. The only thing required for this image to be compatible with the executor is to have `sh` and `python` installed. Therefore, creating a custom image is a simple and effective way to ensure required system dedendencies are available.\n",
"\n",
"You can use the executor as a context manager to ensure the container is cleaned up after use. Otherwise, the `atexit` module will be used to stop the container when the program exits.\n",
"\n",
"### Inspecting the container\n",
"\n",
"If you wish to keep the container around after AutoGen is finished using it for whatever reason (e.g. to inspect the container), then you can set the `auto_remove` parameter to `False` when creating the executor. `stop_container` can also be set to `False` to prevent the container from being stopped at the end of the execution.\n",
"\n",
"### Example"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"exit_code=0 output='Hello, World!\\n' code_file='coding/tmp_code_07da107bb575cc4e02b0e1d6d99cc204.py'\n"
]
}
],
"source": [
"from pathlib import Path\n",
"from autogen.coding import CodeBlock\n",
"from autogen.coding import DockerCommandLineCodeExecutor\n",
"\n",
"work_dir = Path(\"coding\")\n",
"work_dir.mkdir(exist_ok=True)\n",
"\n",
"with DockerCommandLineCodeExecutor(work_dir=work_dir) as executor:\n",
" print(\n",
" executor.execute_code_blocks(\n",
" code_blocks=[\n",
" CodeBlock(language=\"python\", code=\"print('Hello, World!')\"),\n",
" ]\n",
" )\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Local\n",
"\n",
"````{=mdx}\n",
":::danger\n",
"The local version will run code on your local system. Use it with caution.\n",
":::\n",
"````\n",
"\n",
"To execute code on the host machine, as in the machine running AutoGen, the [`LocalCommandLineCodeExecutor`](/docs/reference/coding/local_commandline_code_executor#localcommandlinecodeexecutor) can be used.\n",
"\n",
"### Example"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"exit_code=0 output='\\nHello, World!\\n' code_file='coding/065b51a16ee54f3298847518f9e999d7.py'\n"
]
}
],
"source": [
"from pathlib import Path\n",
"from autogen.coding import CodeBlock\n",
"from autogen.coding import LocalCommandLineCodeExecutor\n",
"\n",
"work_dir = Path(\"coding\")\n",
"work_dir.mkdir(exist_ok=True)\n",
"\n",
"executor = LocalCommandLineCodeExecutor(work_dir=str(work_dir))\n",
"print(\n",
" executor.execute_code_blocks(\n",
" code_blocks=[\n",
" CodeBlock(language=\"python\", code=\"print('Hello, World!')\"),\n",
" ]\n",
" )\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Assigning to an agent\n",
"\n",
"These executors can be used to facilitate the execution of agent written code. "
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"from autogen import ConversableAgent\n",
"from autogen.coding import DockerCommandLineCodeExecutor\n",
"from pathlib import Path\n",
"\n",
"work_dir = Path(\"coding\")\n",
"work_dir.mkdir(exist_ok=True)\n",
"\n",
"executor = DockerCommandLineCodeExecutor(work_dir=work_dir)\n",
"\n",
"code_executor_agent = ConversableAgent(\n",
" name=\"code_executor_agent\",\n",
" llm_config=False,\n",
" code_execution_config={\n",
" \"executor\": executor,\n",
" },\n",
" human_input_mode=\"NEVER\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"When using code execution it is critical that you update the system prompt of agents you expect to write code to be able to make use of the executor. For example, for the [`DockerCommandLineCodeExecutor`](/docs/reference/coding/docker_commandline_code_executor#dockercommandlinecodeexecutor) you might setup a code writing agent like so:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"# The code writer agent's system message is to instruct the LLM on how to\n",
"# use the Jupyter code executor with IPython kernel.\n",
"code_writer_system_message = \"\"\"\n",
"You have been given coding capability to solve tasks using Python code.\n",
"In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.\n",
" 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.\n",
" 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.\n",
"Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.\n",
"When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.\n",
"If you want the user to save the code in a file before executing it, put # filename: <filename> inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.\n",
"\"\"\"\n",
"\n",
"import os\n",
"\n",
"code_writer_agent = ConversableAgent(\n",
" \"code_writer\",\n",
" system_message=code_writer_system_message,\n",
" llm_config={\"config_list\": [{\"model\": \"gpt-4\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}]},\n",
" code_execution_config=False, # Turn off code execution for this agent.\n",
" max_consecutive_auto_reply=2,\n",
" human_input_mode=\"NEVER\",\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then we can use these two agents to solve a problem:"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
"\n",
"Write Python code to calculate the 14th Fibonacci number.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n",
"\n",
"Sure, we can calculate the Fibonacci series using a few different methods such as recursion, iterative, by using Binet's formula, or by using matrix exponentiation.\n",
"\n",
"But, since we only need to calculate the 14th number, we will simply use the iterative method as it's the most efficient for this case.\n",
"\n",
"Here is the Python code that solves the task:\n",
"\n",
"```python\n",
"def fibonacci(n):\n",
" a, b = 0, 1\n",
" for _ in range(n):\n",
" a, b = b, a + b\n",
" return a\n",
"\n",
"print(fibonacci(14))\n",
"```\n",
"\n",
"In this script, `fibonacci(n)` is a function that calculates the nth Fibonacci number. Inside the function, two variables `a` and `b` are initialised to `0` and `1` which are the first two numbers in the Fibonacci series. Then, a loop runs `n` times (14 times in your case), and in each iteration `a` is replaced with `b` and `b` is replaced with `a + b`, which generates the next number in the series. \n",
"\n",
"The function returns `a`, which is the nth Fibonacci number. The result is then printed to the console.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[31m\n",
">>>>>>>> EXECUTING 1 CODE BLOCKS (inferred languages are [python])...\u001b[0m\n",
"\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
"\n",
"exitcode: 0 (execution succeeded)\n",
"Code output: 377\n",
"\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mcode_writer\u001b[0m (to code_executor_agent):\n",
"\n",
"Great! The script has successfully computed the 14th Fibonacci number as 377. If you need to compute other Fibonacci numbers, you can simply change the argument in the `fibonacci()` function call. Please let me know if you need help with anything else.\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mcode_executor_agent\u001b[0m (to code_writer):\n",
"\n",
"\n",
"\n",
"--------------------------------------------------------------------------------\n",
"ChatResult(chat_id=None,\n",
" chat_history=[{'content': 'Write Python code to calculate the 14th '\n",
" 'Fibonacci number.',\n",
" 'role': 'assistant'},\n",
" {'content': 'Sure, we can calculate the Fibonacci '\n",
" 'series using a few different methods '\n",
" 'such as recursion, iterative, by using '\n",
" \"Binet's formula, or by using matrix \"\n",
" 'exponentiation.\\n'\n",
" '\\n'\n",
" 'But, since we only need to calculate the '\n",
" '14th number, we will simply use the '\n",
" \"iterative method as it's the most \"\n",
" 'efficient for this case.\\n'\n",
" '\\n'\n",
" 'Here is the Python code that solves the '\n",
" 'task:\\n'\n",
" '\\n'\n",
" '```python\\n'\n",
" 'def fibonacci(n):\\n'\n",
" ' a, b = 0, 1\\n'\n",
" ' for _ in range(n):\\n'\n",
" ' a, b = b, a + b\\n'\n",
" ' return a\\n'\n",
" '\\n'\n",
" 'print(fibonacci(14))\\n'\n",
" '```\\n'\n",
" '\\n'\n",
" 'In this script, `fibonacci(n)` is a '\n",
" 'function that calculates the nth '\n",
" 'Fibonacci number. Inside the function, '\n",
" 'two variables `a` and `b` are '\n",
" 'initialised to `0` and `1` which are the '\n",
" 'first two numbers in the Fibonacci '\n",
" 'series. Then, a loop runs `n` times (14 '\n",
" 'times in your case), and in each '\n",
" 'iteration `a` is replaced with `b` and '\n",
" '`b` is replaced with `a + b`, which '\n",
" 'generates the next number in the '\n",
" 'series. \\n'\n",
" '\\n'\n",
" 'The function returns `a`, which is the '\n",
" 'nth Fibonacci number. The result is then '\n",
" 'printed to the console.',\n",
" 'role': 'user'},\n",
" {'content': 'exitcode: 0 (execution succeeded)\\n'\n",
" 'Code output: 377\\n',\n",
" 'role': 'assistant'},\n",
" {'content': 'Great! The script has successfully '\n",
" 'computed the 14th Fibonacci number as '\n",
" '377. If you need to compute other '\n",
" 'Fibonacci numbers, you can simply change '\n",
" 'the argument in the `fibonacci()` '\n",
" 'function call. Please let me know if you '\n",
" 'need help with anything else.',\n",
" 'role': 'user'},\n",
" {'content': '', 'role': 'assistant'}],\n",
" summary='',\n",
" cost=({'gpt-4-0613': {'completion_tokens': 302,\n",
" 'cost': 0.04842,\n",
" 'prompt_tokens': 1010,\n",
" 'total_tokens': 1312},\n",
" 'total_cost': 0.04842},\n",
" {'gpt-4-0613': {'completion_tokens': 302,\n",
" 'cost': 0.04842,\n",
" 'prompt_tokens': 1010,\n",
" 'total_tokens': 1312},\n",
" 'total_cost': 0.04842}),\n",
" human_input=[])\n"
]
}
],
"source": [
"import pprint\n",
"\n",
"chat_result = code_executor_agent.initiate_chat(\n",
" code_writer_agent, message=\"Write Python code to calculate the 14th Fibonacci number.\"\n",
")\n",
"\n",
"pprint.pprint(chat_result)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, stop the container. Or better yet use a context manager for it to be stopped automatically."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"executor.stop()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "autogen",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.5"
}
},
"nbformat": 4,
"nbformat_minor": 2
}