Core agent framework with MCP toolkit support
Project description
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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cyclops_ai-0.3.0.tar.gz.
File metadata
- Download URL: cyclops_ai-0.3.0.tar.gz
- Upload date:
- Size: 314.3 kB
- Tags: Source
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
785452378248f9a7d905524e8be5d728cabb05a72a8c25c4b9180eb3b8be8fc7
|
|
| MD5 |
128cc7a0b3d03213f10f35429b6418cb
|
|
| BLAKE2b-256 |
e08a9c7a6b91d5c57d417ea04ec5525906742314c3e04acfc35ec4b8227f86e3
|
File details
Details for the file cyclops_ai-0.3.0-py3-none-any.whl.
File metadata
- Download URL: cyclops_ai-0.3.0-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using 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}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7011b143a3661ca1bad83a72daa09f74918092abaa09f92a310cd3f37f72678c
|
|
| MD5 |
22b6b81ce58d7b25cfeb93cd2517fb48
|
|
| BLAKE2b-256 |
f565624bc62e2b784a6dc92b03b45fe748ba506b18a77ae09d687abebda1408a
|