Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 41 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,51 @@ ANTHROPIC_API_KEY=
# Google Gemini — set this if using Google as your primary provider.
GOOGLE_API_KEY=

# Azure OpenAI Service — your own deployment of GPT-* on Azure.
# Get keys from https://portal.azure.com (your AOAI resource → Keys & Endpoint).
AZURE_API_KEY=
AZURE_API_BASE= # e.g. https://my-resource.openai.azure.com
AZURE_API_VERSION= # e.g. 2024-08-01-preview

# Azure AI Foundry — catalog of Anthropic Claude (Opus, Sonnet), Llama, Mistral,
# DeepSeek, and other non-OpenAI models. Get keys at https://ai.azure.com.
# IMPORTANT: For Claude models, AZURE_AI_API_BASE must end with `/anthropic`
# (e.g. https://my-resource.services.ai.azure.com/anthropic).
# For other catalog models, use the bare resource URL.
AZURE_AI_API_KEY=
AZURE_AI_API_BASE= # e.g. https://my-resource.services.ai.azure.com[/anthropic]

# Ollama — local model server. No API key required; URL defaults to localhost.
# Install + run from https://ollama.com, then `ollama pull <model>`.
OLLAMA_API_BASE= # default: http://localhost:11434

# OpenAI-compatible — generic route for any vendor that exposes an
# OpenAI-compatible API (Ollama Cloud, Groq, Together AI, Mistral La Plateforme,
# OpenRouter, vLLM-based deployments, etc.). Uses dedicated env vars so it
# never collides with a real OPENAI_API_KEY.
# Examples:
# Groq: OPENAI_COMPAT_API_BASE=https://api.groq.com/openai/v1
# Together AI: OPENAI_COMPAT_API_BASE=https://api.together.xyz/v1
# Mistral: OPENAI_COMPAT_API_BASE=https://api.mistral.ai/v1
# OpenRouter: OPENAI_COMPAT_API_BASE=https://openrouter.ai/api/v1
# Ollama Cloud: see https://docs.ollama.com for the current endpoint
OPENAI_COMPAT_API_KEY=
OPENAI_COMPAT_API_BASE=


# ── Model selection ───────────────────────────

# Override the default model for all agents (set automatically by onboarding).
# OpenAI example: DEFAULT_MODEL=gpt-5.2
# Anthropic example: DEFAULT_MODEL=litellm/claude-sonnet-4-6
# Google example: DEFAULT_MODEL=litellm/gemini/gemini-3-flash
# Use the `/switch-provider` flow inside the TUI to change this at runtime.
# OpenAI example: DEFAULT_MODEL=gpt-5.2
# Anthropic example: DEFAULT_MODEL=litellm/claude-sonnet-4-6
# Google example: DEFAULT_MODEL=litellm/gemini/gemini-3-flash
# Azure OpenAI example: DEFAULT_MODEL=azure/my-gpt5-deployment
# Azure Foundry example: DEFAULT_MODEL=azure_ai/claude-opus-4-1
# DEFAULT_MODEL=azure_ai/Llama-3.3-70B-Instruct
# Ollama example: DEFAULT_MODEL=ollama_chat/llama3.1
# Ollama Cloud example: DEFAULT_MODEL=openai_compat/qwen3-coder:480b-cloud
# Groq example: DEFAULT_MODEL=openai_compat/llama-3.3-70b-versatile
DEFAULT_MODEL=


Expand Down
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,9 @@ cython_debug/

.agency_swarm/
third_party/
.claude/
.claude/
.omc/

# Smoke-test scaffolding (never commit)
.smoke_e2e.py
.venv-smoketest/
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,10 @@ The coding agent will read this file, understand the structure, and make the rig
- `instructions.md` is the agent's system prompt — edit it to change behavior
- Tools live in `tools/` and are auto-loaded by the agent definition
- `shared_tools/` contains Composio-powered integrations (Gmail, Slack, GitHub, etc.) available to all agents
- `orchestrator/tools/` holds tools that **only** the orchestrator gets — currently just `SwitchProvider`. Specialist agents must never import from this directory; the orchestrator's "router only" contract has one documented carve-out (provider switching) and that's it.
- Models are configured via `DEFAULT_MODEL` in `.env` — never hardcoded
- Provider routing: every supported provider is registered in `config.PROVIDER_REGISTRY` (a single source of truth). Adding a new provider means one entry there plus an optional UI entry in `onboard.PROVIDERS`.
- Runtime provider switch: orchestrator users say "switch to <slug> <model>"; the `SwitchProvider` tool writes `.env` and signals `run_utils.main()` to rebuild the agency on next TUI exit. The FastAPI server at `server.py` does **not** read this signal — switching there is a no-op.

Before proceeding with agent creation, please read the following instructions carefully:

Expand Down
26 changes: 18 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,22 +97,32 @@ They'll automatically customize all agents for your use case.

## ⚙️ API Keys & Setup

The setup wizard walks you through everything, but you'll need at least one of these:
The setup wizard walks you through everything, but you'll need at least one of these.

**Required (choose one):**
**Pick a primary provider (one required):**

- `OPENAI_API_KEY` - For GPT 5.5 and Sora video generation
- `ANTHROPIC_API_KEY` - For Claude models
- `OPENAI_API_KEY` — GPT 5.x and Sora video generation
- `ANTHROPIC_API_KEY` — Claude models
- `GOOGLE_API_KEY` — Gemini models (also drives image gen + Veo video)
- **Azure OpenAI Service** — `AZURE_API_KEY` + `AZURE_API_BASE` + `AZURE_API_VERSION` for your own GPT deployment
- **Azure AI Foundry** — `AZURE_AI_API_KEY` + `AZURE_AI_API_BASE` for the catalog (Claude on Azure, Llama, Mistral, DeepSeek, ...)
- **Ollama (local)** — no key required; defaults to `http://localhost:11434`
- **OpenAI-compatible** — `OPENAI_COMPAT_API_KEY` + `OPENAI_COMPAT_API_BASE` for Ollama Cloud, Groq, Together AI, Mistral La Plateforme, OpenRouter, vLLM

Switching providers mid-session: ask the orchestrator "switch to ollama llama3.1" (or any other slug + model) — it routes to the `SwitchProvider` tool, writes the new `DEFAULT_MODEL` to `.env`, and on next TUI exit OpenSwarm restarts with the new provider.

**Optional superpowers:**

- `COMPOSIO_API_KEY` - Unlock 10,000+ integrations (Gmail, Slack, GitHub, etc.)
- `GOOGLE_API_KEY` - Gemini image generation + Veo video
- `FAL_KEY` - Advanced video editing and effects
- `SEARCH_API_KEY` - Web search for research agent
- `COMPOSIO_API_KEY` — Unlock 10,000+ integrations (Gmail, Slack, GitHub, etc.)
- `FAL_KEY` — Advanced video editing and effects
- `SEARCH_API_KEY` — Web search for research agent

Tools gracefully degrade when keys are missing — you'll get clear instructions on what to add.

### Upgrading from an earlier version

If you already have a `.env` from before the multi-provider work, nothing breaks. Existing `DEFAULT_MODEL` values keep working: bare strings like `gpt-5.2` route to OpenAI directly, and `litellm/<model>` strings still route through LiteLLM. The wizard adds new variables for Azure, Ollama, and OpenAI-compatible setups; old keys stay in place. Re-run `python onboard.py` whenever you want to register a new provider.

---

## 🚀 Coming Soon
Expand Down
90 changes: 82 additions & 8 deletions config.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
"""Shared model configuration helpers — read by all agents at startup."""
"""Shared model configuration helpers — read by all agents at startup.

PROVIDER_REGISTRY is the single source of truth for provider routing. Every
new provider added to OpenSwarm should be registered here; the onboarding
wizard and the SwitchProvider tool both derive their behavior from this
table.
"""
import os

# Slug -> routing spec. Adding a new provider means adding one entry here
# and (optionally) a UI entry in onboard.PROVIDERS.
# prefix: DEFAULT_MODEL prefix that identifies this provider
# required_env: env vars that must be set before a model call works
PROVIDER_REGISTRY: dict[str, dict] = {
"openai": {"prefix": "", "required_env": ["OPENAI_API_KEY"]},
# Anthropic models on LiteLLM are always named claude-*; using a more
# specific prefix here means a stray litellm/cohere/... model won't be
# misclassified as anthropic.
"anthropic": {"prefix": "litellm/claude", "required_env": ["ANTHROPIC_API_KEY"]},
"google": {"prefix": "litellm/gemini/", "required_env": ["GOOGLE_API_KEY"]},
"azure": {"prefix": "azure/", "required_env": ["AZURE_API_KEY", "AZURE_API_BASE", "AZURE_API_VERSION"]},
"azure_ai": {"prefix": "azure_ai/", "required_env": ["AZURE_AI_API_KEY", "AZURE_AI_API_BASE"]},
"ollama": {"prefix": "ollama_chat/", "required_env": []},
# Only the base URL is strictly required — keyless endpoints (local vLLM,
# some OpenRouter / Mistral setups) are valid; LiteLLM passes None safely.
"openai_compat": {"prefix": "openai_compat/", "required_env": ["OPENAI_COMPAT_API_BASE"]},
}


def get_default_model(fallback: str = "gpt-5.2"):
"""Return the configured default model for standard agents."""
Expand All @@ -9,26 +34,75 @@ def get_default_model(fallback: str = "gpt-5.2"):


def is_openai_provider() -> bool:
"""Return True when the configured provider is OpenAI (not LiteLLM).
"""True when DEFAULT_MODEL routes to OpenAI's hosted API directly.

OpenAI model IDs never contain a slash (e.g. 'gpt-5.2', 'o3').
Any 'provider/model' string (e.g. 'anthropic/claude-sonnet-4-6',
'litellm/gemini/gemini-3-flash') is treated as a LiteLLM-routed model.
OpenAI model IDs never contain a slash (e.g. 'gpt-5.2', 'o3'). Any
'provider/model' string is treated as a LiteLLM-routed model.
"""
return "/" not in os.getenv("DEFAULT_MODEL", "")


def get_active_provider() -> str:
"""Slug derived from DEFAULT_MODEL by prefix table lookup.

Returns one of the slugs in PROVIDER_REGISTRY. Bare 'litellm/<model>'
strings that don't match anthropic (litellm/) or google (litellm/gemini/)
return 'unknown' so callers can distinguish 'I know this provider' from
'I don't recognize this'.
"""
model = os.getenv("DEFAULT_MODEL", "")
if "/" not in model:
return "openai"
# Longest prefix wins so 'azure_ai/' matches before 'azure/' would.
for slug, spec in sorted(
PROVIDER_REGISTRY.items(), key=lambda kv: -len(kv[1]["prefix"])
):
prefix = spec["prefix"]
if prefix and model.startswith(prefix):
return slug
return "unknown"


def _resolve(model: str):
"""Route 'provider/model' strings through LitellmModel.

Handles both explicit 'litellm/<model>' and bare 'provider/model' forms.
OpenAI model IDs contain no slash, so they pass through unchanged.
Bare strings (no slash) pass through for OpenAI's hosted API. Strings
with a slash are wrapped in LitellmModel. The 'openai_compat/<model>'
sentinel unwraps to LiteLLM's openai/<model> route with dedicated
OPENAI_COMPAT_* credentials, so the user's real OPENAI_API_KEY is
never overwritten.

Raises RuntimeError if 'openai_compat/' is configured without
OPENAI_COMPAT_API_BASE — better to fail loudly at startup than give
a cryptic LiteLLM error on first call.
"""
if "/" not in model:
return model

if model.startswith("openai_compat/"):
real_model = "openai/" + model[len("openai_compat/"):]
api_key = os.getenv("OPENAI_COMPAT_API_KEY")
api_base = os.getenv("OPENAI_COMPAT_API_BASE")
if not api_base:
raise RuntimeError(
"DEFAULT_MODEL uses openai_compat/ but OPENAI_COMPAT_API_BASE "
"is not set. Run `python onboard.py` to configure it."
)
try:
from agency_swarm import LitellmModel # noqa: PLC0415
except ImportError:
return real_model
return LitellmModel(model=real_model, api_key=api_key, base_url=api_base)

bare = model[len("litellm/"):] if model.startswith("litellm/") else model
try:
from agency_swarm import LitellmModel # noqa: PLC0415
return LitellmModel(model=bare)
except ImportError:
return model

# Thread Ollama's base URL explicitly. LiteLLM also reads OLLAMA_API_BASE
# from env, but passing it via base_url is unambiguous and consistent
# with the openai_compat branch.
if bare.startswith(("ollama/", "ollama_chat/")):
return LitellmModel(model=bare, base_url=os.getenv("OLLAMA_API_BASE"))
return LitellmModel(model=bare)
Loading