Skip to main content
Cyclops

Build LLM agents in Python. Any model. Any tool. No magic.


Quick Start | Examples | Docs | Why Cyclops


PyPI Python 3.10+ License: MIT Tests Stars


Cyclops is a thin wrapper around LiteLLM that gives you agents, tool calling, streaming, structured output, memory, and MCP in one clean API. It works with every model LiteLLM supports. It doesn't hide the underlying calls from you.

Install

pip install cyclops-ai
uv add cyclops-ai

Quick Start

from cyclops import Agent, AgentConfig

agent = Agent(AgentConfig(model="gpt-4o-mini"))
print(agent.run("What is the capital of Japan?"))

Add tools:

from cyclops import Agent, AgentConfig
from cyclops.toolkit import tool

@tool
def get_price(ticker: str) -> str:
    """Get stock price for a ticker symbol"""
    return f"{ticker}: $142.50"

agent = Agent(AgentConfig(model="gpt-4o-mini"), tools=[get_price])
print(agent.run("What is Apple's stock price?"))
# The agent calls get_price("AAPL") automatically

What's included

Tool loop Calls tools in a loop until the model stops asking for them, not just one round
Streaming agent.stream() and agent.astream() for token-by-token output
Structured output agent.run(..., response_model=MyModel) returns a Pydantic instance
Cost tracking agent.run_with_response() returns tokens used and estimated cost
Memory InMemoryStorage and FileStorage for persistent key-value context
MCP Connect to any MCP server as a tool source, or expose your tools as an MCP server
Plugins Install cyclops-toolkit-* packages and tools are discovered automatically
Any LLM OpenAI, Anthropic, Groq, Gemini, Ollama, Together AI, Bedrock, and 100+ more via LiteLLM
Fallback routing Pass a LiteLLM Router for automatic failover and load balancing
Naive mode Prompt-based tool calling for models without native function calling support

Streaming

agent = Agent(AgentConfig(model="gpt-4o-mini"))

for chunk in agent.stream("Explain the water cycle"):
    print(chunk, end="", flush=True)

Async:

async for chunk in agent.astream("Write a haiku about Python"):
    print(chunk, end="", flush=True)

Structured Output

from pydantic import BaseModel
from cyclops import Agent, AgentConfig

class Summary(BaseModel):
    title: str
    key_points: list[str]
    sentiment: str

agent = Agent(AgentConfig(model="gpt-4o-mini"))
result = agent.run("Summarize the French Revolution", response_model=Summary)

print(result.title)       # "The French Revolution"
print(result.key_points)  # ["Storming of the Bastille", ...]

Cost and Token Tracking

response = agent.run_with_response("Write a product description for noise-cancelling headphones")

print(f"Model:      {response.model}")
print(f"Tokens:     {response.tokens_used}")
print(f"Cost:       ${response.cost:.6f}")
print(f"Answer:     {response.content}")

Multi-Turn Conversations

History is kept automatically. Call reset() to start fresh.

agent = Agent(AgentConfig(model="gpt-4o-mini"))

agent.run("My name is Alice and I work in Tokyo")
print(agent.run("Where do I work?"))  # "You work in Tokyo"

agent.reset()
print(agent.run("What's my name?"))   # no memory of Alice

Memory

import asyncio
from cyclops import Agent, AgentConfig, FileStorage

memory = FileStorage("./memory.json")  # persists across restarts

async def main():
    await memory.store("user_name", "Alice")
    await memory.store("language", "Python")

    name = await memory.retrieve("user_name")
    print(f"Hello, {name}")

asyncio.run(main())

MCP

Connect to any MCP server:

from cyclops.mcp import MCPClient

client = MCPClient()
await client.connect_stdio(["npx", "-y", "@modelcontextprotocol/server-filesystem", "."])

tools = await client.list_tools()
result = await client.call_tool("read_file", {"path": "README.md"})
await client.disconnect()

Expose your tools as an MCP server:

from cyclops.mcp import MCPServer
from cyclops.toolkit import tool

@tool
def lookup(id: str) -> str:
    """Look up a record by ID"""
    return f"Record {id}: active"

server = MCPServer("my-server")
server.add_tool(lookup)
await server.run_stdio()

Observability

Wire TelemetryHooks to get OpenTelemetry spans for every LLM call and tool execution. Works with any OTLP backend — Jaeger, Honeycomb, Grafana Tempo, Datadog, or the console.

from cyclops import Agent, AgentConfig, TelemetryHooks

agent = Agent(AgentConfig(model="groq/llama-3.1-8b-instant", hooks=TelemetryHooks.console()))
agent.run("What files are in the current directory?")

Send to Jaeger / Tempo / Datadog instead:

# uv add opentelemetry-exporter-otlp-proto-grpc
agent = Agent(AgentConfig(model="groq/llama-3.1-8b-instant", hooks=TelemetryHooks.otlp("http://localhost:4317")))
agent.run("What files are in the current directory?")

Span hierarchy per run: agent.runllm.completion (with token counts + latency) and tool.<name> children. See Observability guide for OTLP/Jaeger setup and full attribute reference.

Providers

Switch models by changing one string. Set the matching API key as an environment variable.

# OpenAI
Agent(AgentConfig(model="gpt-4o-mini"))                        # OPENAI_API_KEY

# Anthropic
Agent(AgentConfig(model="claude-haiku-4-5-20251001"))          # ANTHROPIC_API_KEY

# Groq (fast, free tier)
Agent(AgentConfig(model="groq/llama-3.1-8b-instant"))          # GROQ_API_KEY

# Ollama (local, no API key)
Agent(AgentConfig(model="ollama/qwen3:4b"))

# Google
Agent(AgentConfig(model="gemini/gemini-1.5-flash"))            # GEMINI_API_KEY

Fallback Routing

from litellm import Router
from cyclops import Agent, AgentConfig

router = Router(
    model_list=[
        {"model_name": "primary", "litellm_params": {"model": "gpt-4o-mini"}},
        {"model_name": "primary", "litellm_params": {"model": "groq/llama-3.1-8b-instant"}},
    ],
    fallbacks=[{"primary": ["groq/llama-3.1-8b-instant"]}],
    num_retries=2,
)

agent = Agent(AgentConfig(model="primary", router=router))

Why Cyclops?

Most agent frameworks add heavy abstractions, require specific clouds, or make it hard to see what's actually being sent to the LLM. Cyclops doesn't. The Agent class is ~400 lines. _history is a plain list of dicts in LiteLLM format. Every call goes straight to LiteLLM.

Cyclops LangChain smolagents
Lines to first agent 3 15+ 8
Any LiteLLM model yes partial yes
MCP native yes plugin only no
Streaming yes yes no
Structured output yes yes no
Cost tracking yes partial no
Abstraction thin thick medium

Examples

See the examples/ directory:

File What it shows
basic_agent.py Hello world, multi-turn
agent_with_tools.py Tool calling
streaming_example.py stream() and astream()
structured_output.py response_model with Pydantic
cost_tracking.py Tokens and cost
multi_turn_agent.py Conversation history
tool_loop_demo.py Multi-step tool chains
memory_persistence.py FileStorage
async_agent.py arun() and concurrent calls
different_llms.py Groq, Ollama, OpenAI, Together AI
router_fallback.py Automatic failover
mcp_server.py Expose tools via MCP
plugin_system.py Auto-discover toolkit packages
otel_example.py OpenTelemetry tracing

Development

git clone https://github.com/gopaljigaur/cyclops
cd cyclops
uv sync
uv run pre-commit install
uv run pytest

Contributing

Bug reports, feature requests, and pull requests are welcome. Open an issue first for large changes.

License

MIT

Release files for cyclops-ai 0.3.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cyclops-ai 0.3.0
File Size Uploaded
cyclops_ai-0.3.0.tar.gz 314.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cyclops-ai 0.3.0
File Interpreter ABI Platform
cyclops_ai-0.3.0-py3-none-any.whl Python 3 none any Details

Total release size: 340.7 kB

Release files / cyclops_ai-0.3.0.tar.gz

Download URL cyclops_ai-0.3.0.tar.gz
Size 314.3 kB
Tags Source
SHA-256 checksum
How to use checksums
785452378248f9a7d905524e8be5d728cabb05a72a8c25c4b9180eb3b8be8fc7
BLAKE2b-256 checksum
How to use checksums
e08a9c7a6b91d5c57d417ea04ec5525906742314c3e04acfc35ec4b8227f86e3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / cyclops_ai-0.3.0-py3-none-any.whl

Download URL cyclops_ai-0.3.0-py3-none-any.whl
Size 26.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7011b143a3661ca1bad83a72daa09f74918092abaa09f92a310cd3f37f72678c
BLAKE2b-256 checksum
How to use checksums
f565624bc62e2b784a6dc92b03b45fe748ba506b18a77ae09d687abebda1408a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release history Release notifications | RSS feed

This release

0.3.0 This release

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page