Build LLM agents in Python. Any model. Any tool. No magic.
Quick Start | Examples | Docs | Why Cyclops
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.run → llm.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)
| File | Size | Uploaded | |
|---|---|---|---|
| cyclops_ai-0.3.0.tar.gz | 314.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| 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}
|