Skip to content

Basic usage

A freeact agent system consists of:

  • A code execution Docker container, managed by the CodeExecutionContainer context manager. This tutorial uses the prebuilt ghcr.io/gradion-ai/ipybox:example image.
  • A code executor, managed by the CodeExecutor context manager. It manages an IPython kernel's lifecycle within the container and handles code execution.
  • A code action model that generates code actions to be executed by the executor. Models must implement the interfaces defined in the freeact.model package. This tutorial uses Claude, configured with anthropic/claude-3-5-sonnet-20241022 as model name (1).

    1. Valid model names are those accepted by LiteLLM.
  • A CodeActAgent configured with both the model and executor. It orchestrates their interaction until a final response is ready.

examples/basics.py
import asyncio
import os

from dotenv import load_dotenv

from examples.utils import stream_conversation
from freeact import (
    Claude,
    CodeActAgent,
    CodeExecutionContainer,
    CodeExecutor,
)


async def main():
    api_keys = {
        "ANTHROPIC_API_KEY": os.environ["ANTHROPIC_API_KEY"],
        "GOOGLE_API_KEY": os.environ["GOOGLE_API_KEY"],
    }

    async with CodeExecutionContainer(
        tag="ghcr.io/gradion-ai/ipybox:example",  # (2)!
        env=api_keys,
        workspace_path="workspace",  # (3)!
    ) as container:
        async with CodeExecutor(
            key="example",  # (4)!
            port=container.port,  # (5)!
            workspace=container.workspace,
        ) as executor:
            skill_sources = await executor.get_module_sources(
                ["freeact_skills.search.google.stream.api"],  # (6)!
            )
            model = Claude(model_name="anthropic/claude-3-5-sonnet-20241022")  # (7)!
            agent = CodeActAgent(model=model, executor=executor)
            await stream_conversation(agent, skill_sources=skill_sources)  # (1)!


if __name__ == "__main__":
    load_dotenv()
    asyncio.run(main())
  1. examples/utils.py::stream_conversation
    async def stream_conversation(agent: CodeActAgent, **kwargs):
        while True:
            user_message = await ainput("User message: ('q' to quit) ")
    
            if user_message.lower() == "q":
                break
    
            agent_turn = agent.run(user_message, **kwargs)
            await stream_turn(agent_turn)
    
    
    async def stream_turn(agent_turn: CodeActAgentTurn):
        produced_images: Dict[Path, Image.Image] = {}
    
        async for activity in agent_turn.stream():
            match activity:
                case CodeActModelTurn() as turn:
                    print("Agent response:")
                    async for s in turn.stream():
                        print(s, end="", flush=True)
                    print()
    
                    response = await turn.response()
                    if response.code:
                        print("\n```python")
                        print(response.code)
                        print("```\n")
    
                case CodeExecution() as execution:
                    print("Execution result:")
                    async for s in execution.stream():
                        print(s, end="", flush=True)
                    result = await execution.result()
                    produced_images.update(result.images)
                    print()
    
        if produced_images:
            print("\n\nProduced images:")
        for path in produced_images.keys():
            print(str(path))
    
  2. Tag of the ipybox Docker image.

  3. Path to the workspace directory on the host machine. This directory enables sharing custom skills modules between the container and host machine (see Skill development tutorial).

  4. Key for this executor's private workspace directories:

    • workspace/skills/private/example: Private skills and working directory
    • workspace/images/example: Directory for storing produced images
  5. Container host port. Automatically allocated by CodeExecutionContainer but can be manually specified.

  6. Skill modules on the executor's Python path that can be resolved to their source code and metadata. This information is included in the code action model's context.

  7. Valid model names are those accepted by LiteLLM.

A CodeActAgent can engage in multi-turn conversations with a user. Each turn is initiated using the agent's run method. We use the stream_conversation (1) helper function to run the agent and stream the output from both the agent's model and code executor to stdout.

  1. examples/utils.py::stream_conversation
    async def stream_conversation(agent: CodeActAgent, **kwargs):
        while True:
            user_message = await ainput("User message: ('q' to quit) ")
    
            if user_message.lower() == "q":
                break
    
            agent_turn = agent.run(user_message, **kwargs)
            await stream_turn(agent_turn)
    
    
    async def stream_turn(agent_turn: CodeActAgentTurn):
        produced_images: Dict[Path, Image.Image] = {}
    
        async for activity in agent_turn.stream():
            match activity:
                case CodeActModelTurn() as turn:
                    print("Agent response:")
                    async for s in turn.stream():
                        print(s, end="", flush=True)
                    print()
    
                    response = await turn.response()
                    if response.code:
                        print("\n```python")
                        print(response.code)
                        print("```\n")
    
                case CodeExecution() as execution:
                    print("Execution result:")
                    async for s in execution.stream():
                        print(s, end="", flush=True)
                    result = await execution.result()
                    produced_images.update(result.images)
                    print()
    
        if produced_images:
            print("\n\nProduced images:")
        for path in produced_images.keys():
            print(str(path))
    

This tutorial uses the freeact_skills.search.google.stream.api skill module from the freeact-skills project to process queries that require internet searches. This module provides generative Google search capabilities powered by the Gemini 2 API.

The skill module's source code is obtained from the executor and passed to the model through the agent's run method. Other model implementations may require skill module sources to be passed through their constructor instead.

Setup

Install freeact with:

pip install freeact

The tutorials require an ANTHROPIC_API_KEY for the Claude API and a GOOGLE_API_KEY for the Gemini 2 API. You can get them from Anthropic Console and Google AI Studio. Add them to a .env file in the current working directory:

.env
ANTHROPIC_API_KEY=...
GOOGLE_API_KEY=...

The tutorials use the prebuilt ghcr.io/gradion-ai/ipybox:example Docker image for sandboxed code execution.

Running

Download the Python example

mkdir examples
curl -o examples/basics.py https://raw.githubusercontent.com/gradion-ai/freeact/refs/heads/main/examples/basics.py
curl -o examples/utils.py https://raw.githubusercontent.com/gradion-ai/freeact/refs/heads/main/examples/utils.py

and run it with:

python examples/basics.py

For formatted and colored console output, as shown in the example conversation, you can use the freeact CLI:

python -m freeact.cli \
  --model-name=anthropic/claude-3-5-sonnet-20241022 \
  --ipybox-tag=ghcr.io/gradion-ai/ipybox:example \
  --executor-key=example \
  --skill-modules=freeact_skills.search.google.stream.api

To use Gemini instead of Claude, run:

python -m freeact.cli \
  --model-name=gemini/gemini-2.0-flash \
  --ipybox-tag=ghcr.io/gradion-ai/ipybox:example \
  --executor-key=example \
  --skill-modules=freeact_skills.search.google.stream.api

See also Supported models for other CLI examples.

Example conversation

output

Produced images:

image_0