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 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 claude-3-5-sonnet-20241022 as model name.
  • A CodeActAgent configured with both the model and executor. It orchestrates their interaction until a final response is ready.
freeact/examples/basics.py
import asyncio
import os

from dotenv import load_dotenv

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


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)!
            )
            async with Logger(file="logs/agent.log") as logger:  # (7)!
                model = Claude(model_name="claude-3-5-sonnet-20241022", logger=logger)
                agent = CodeActAgent(model=model, executor=executor)
                await stream_conversation(agent, skill_sources=skill_sources)  # (1)!


if __name__ == "__main__":
    load_dotenv()
    asyncio.run(main())
  1. freeact/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. A contextual Logger for recording messages and metadata.

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. freeact/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 pre-built ghcr.io/gradion-ai/ipybox:example Docker image for sandboxed code execution.

Running

The Python example above is part of the freeact package and can be run with:

python -m freeact.examples.basics

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

python -m freeact.cli \
  --model-name=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-2.0-flash-exp \
  --ipybox-tag=ghcr.io/gradion-ai/ipybox:example \
  --executor-key=example \
  --skill-modules=freeact_skills.search.google.stream.api

Example conversation

output

Produced images:

image_0