Skip to main content

Zipcoil simplifies OpenAI tool usage

Project description

Zipcoil

Zipcoil is a Python library that simplifies OpenAI tool usage, helping developers build simple AI agents with ease. It provides a clean, decorator-based approach to define tools and an Agent class that handles the OpenAI tool-calling loop automatically.

Why Zipcoil?

Building AI agents that can use tools typically involves:

  • Converting Python functions to OpenAI's JSON schema format 😕
  • Handling the complex tool-calling conversation flow 🙁
  • Managing multiple iterations of tool calls and responses ☹️
  • Dealing with error handling and edge cases 😣

Zipcoil eliminates this boilerplate by providing:

  • A simple @tool decorator to help convert Python functions into OpenAI tools
  • Automatic schema generation from type hints and docstrings
  • Built-in agent loop that handles tool calling iterations
  • Type safety with comprehensive type hints including Optional, Union, Enum, and more
  • Error handling for malformed tool calls and execution errors
  • Minimal dependencies, built on top of the official OpenAI library
  • Works with both OpenAI and AzureOpenAI clients, in both sync and async modes

Installation

Zipcoil requires Python 3.11 or higher.

pip install zipcoil

Quick Start

Here's a simple example of creating an AI agent with tools:

import os
from enum import Enum

from openai import AzureOpenAI, OpenAI

from zipcoil import Agent, tool

# Initialize OpenAI client
# client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# or the Azure OpenAI client
client = AzureOpenAI(
    api_key=os.getenv("AZURE_OPENAI_API_KEY"),
    azure_endpoint=os.getenv("AZURE_OPENAI_API_BASE"),
    api_version=os.getenv("AZURE_OPENAI_API_VERSION"),
)


# Define tools using the @tool decorator
@tool
def get_weather(city: str, unit: str = "celsius") -> str:
    """Get the current weather for a city.

    Args:
        city: The name of the city
        unit: Temperature unit (celsius or fahrenheit)
    """
    # Your weather API call here
    return f"The weather in {city} is 22°{unit[0].upper()}"


class MathOp(Enum):
    ADD = 1
    SUBTRACT = 2
    MULTIPLY = 3
    DIVIDE = 4


@tool
def calculate(x: float, y: float, operation: MathOp) -> float:
    """Perform a mathematical calculation.

    Args:
        x: First number
        y: Second number
        operation: Operation to perform (add, subtract, multiply, divide)
    """
    # normalise int -> MathOp
    if isinstance(operation, int):
        try:
            operation = MathOp(operation)
        except ValueError as exc:
            raise ValueError(f"Unsupported operation value: {operation}") from exc

    operations = {
        MathOp.ADD: x + y,
        MathOp.SUBTRACT: x - y,
        MathOp.MULTIPLY: x * y,
        MathOp.DIVIDE: x / y if y != 0 else float("inf"),
    }
    return operations.get(operation, 0)


# Create an agent with tools
agent = Agent(model="gpt-4o", client=client, tools=[get_weather, calculate])

# Run a conversation
messages = [{"role": "user", "content": "What's the weather in Paris? Also calculate 15 * 23."}]

result = agent.run(messages)
print(result.choices[0].message.content)

Advanced Usage

Complex Type Support

Zipcoil supports various Python types including enums, optionals, and unions:

from enum import Enum
from typing import Optional, List, Dict

class Priority(Enum):
    LOW = 1
    MEDIUM = 2
    HIGH = 3

@tool
def create_task(
    title: str,
    description: Optional[str],
    priority: Priority,
    tags: List[str],
    metadata: Dict[str, str]
) -> str:
    """Create a new task.

    Args:
        title: Task title
        description: Optional task description
        priority: Task priority level
        tags: List of tags for the task
        metadata: Additional metadata as key-value pairs
    """
    return f"Created task '{title}' with priority {priority.name}"

Error Handling

Zipcoil automatically handles tool execution errors:

@tool
def divide_numbers(a: float, b: float) -> float:
    """Divide two numbers.

    Args:
        a: Numerator
        b: Denominator
    """
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# The agent will catch the error and include it in the conversation

Custom Agent Configuration

You can pass additional parameters to the underlying OpenAI API:

result = agent.run(
    messages=messages,
    temperature=0.7,
    max_completion_tokens=1000,
    max_iterations=5  # Limit tool calling iterations
)

Streaming Output

Set stream=True to get a chunk stream compatible with chat.completions.create(stream=True). Zipcoil still handles tool calls between streamed model turns:

stream = agent.run(messages=messages, stream=True)
for chunk in stream:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

For AsyncAgent, await agent.run(..., stream=True) and then async for chunk in stream: ....

Type Support

Zipcoil automatically converts Python types to OpenAI's JSON schema:

Python Type JSON Schema Type Notes
str string
int integer
float number
bool boolean
list array
dict object
Optional[T] [T, "null"] Union with null
Union[T, U] Mixed type For Optional types
Enum enum Extracts enum values

API Reference

@tool Decorator

Converts a Python function into an OpenAI tool. The function should:

  • Have type hints for all parameters
  • Have a docstring with Google-style Args section

Example:

@tool
def get_weather(city: str, unit: str = "celsius") -> str:
    """Get the current weather for a city.

    Args:
        city: The name of the city
        unit: Temperature unit (celsius or fahrenheit)
    """
    return f"The weather in {city} is 22°{unit[0].upper()}"

Agent and AsyncAgent Classes

Agent(
    model: Uniont[str, ChatModel],
    client: OpenAI,
    tools: Iterable[ToolProtocol]
)

AsyncAgent(
    model: Union[str, ChatModel],
    client: AsyncOpenAI,
    tools: Iterable[Union[ToolProtocol, AsyncToolProtocol]]
)

The main abstraction of the agentic event loop. It will take in a model name (more on this below), an OpenAI or AzureOpenAI client, and a list of tools decorated with the @tool decorator.

While Agent will accept only sync tools and will reject async ones, AsyncAgent will accept both sync and async tools and will await async tools. Use @tool for both sync and async functions.

Note: As opposed to standard OpenAI usage, Zipcoil associates the model with an agent to avoid having to specify it every time you call run.

Agent.run()

Runs the agentic loop, calling all tools as needed and iterating until the underlying model doesn't need to call any tools anymore, until it's ready to return a ChatCompletion.

Parameters:

  • max_iterations: Maximum number of tool calling iterations (default: 10)
  • stream: When True, run() returns streamed ChatCompletionChunk objects (default: False)
  • All other parameters are passed through to OpenAI's chat completion API
  • Return type:
    • stream=False: OpenAI ChatCompletion
    • stream=True: chunk stream compatible with chat.completions.create(stream=True)

Error Handling

Zipcoil handles several types of errors gracefully:

  1. Tool execution errors: Caught and passed back to the model as error messages
  2. JSON parsing errors: Invalid tool arguments are reported to the model
  3. Missing tools: Requests for non-existent tools return error messages
  4. Iteration limits: Prevents infinite loops with configurable max iterations

Contributing

Development setup:

# Install development dependencies
uv sync --group dev

# Run tests
uv run pytest

# Format code
uv run ruff format src/ tests/

# Run checks
just check

Requirements

  • Python 3.11+
  • OpenAI Python library (≥1.0.0)
  • docstring-parser (≥0.16)

License

This project is open-source, licensed under the GNU Lesser General Public License v3.0 - see the LICENSE file for details.


Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zipcoil-0.3.0.tar.gz (22.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zipcoil-0.3.0-py3-none-any.whl (13.7 kB view details)

Uploaded Python 3

File details

Details for the file zipcoil-0.3.0.tar.gz.

File metadata

  • Download URL: zipcoil-0.3.0.tar.gz
  • Upload date:
  • Size: 22.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.15

File hashes

Hashes for zipcoil-0.3.0.tar.gz
Algorithm Hash digest
SHA256 893a1c81fff3b5f0246362604b0fd185180936bcacf5289a14e61430a85f300b
MD5 5efe5adfff19b783ac2bd3b09d1fd2c8
BLAKE2b-256 770f977fc7c004cb010106a7b8d194abb50d42df3d4d3fe39ee3ea6785d3353d

See more details on using hashes here.

File details

Details for the file zipcoil-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: zipcoil-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 13.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.11.15

File hashes

Hashes for zipcoil-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7407bf9c8647a079efea6077171dc723b7ffc3e6684412d2393dcaf2a9f8b182
MD5 00dd5f8869632fd1dc8e3700196c6625
BLAKE2b-256 4667108256025a53b42d64753ea807a8ef4f3c88ee3cdb2c0796caebb94f0da9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page