Skip to main content

Core agent framework with MCP toolkit support

Project description

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

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

cyclops_ai-0.3.0.tar.gz (314.3 kB view details)

Uploaded Source

Built Distribution

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

cyclops_ai-0.3.0-py3-none-any.whl (26.4 kB view details)

Uploaded Python 3

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

Hashes for cyclops_ai-0.3.0.tar.gz
Algorithm Hash digest
SHA256 785452378248f9a7d905524e8be5d728cabb05a72a8c25c4b9180eb3b8be8fc7
MD5 128cc7a0b3d03213f10f35429b6418cb
BLAKE2b-256 e08a9c7a6b91d5c57d417ea04ec5525906742314c3e04acfc35ec4b8227f86e3

See more details on using hashes here.

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

Hashes for cyclops_ai-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7011b143a3661ca1bad83a72daa09f74918092abaa09f92a310cd3f37f72678c
MD5 22b6b81ce58d7b25cfeb93cd2517fb48
BLAKE2b-256 f565624bc62e2b784a6dc92b03b45fe748ba506b18a77ae09d687abebda1408a

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