Skip to content
Closed
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
1 change: 0 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ SWARMS_VERBOSE_GLOBAL="False"
SWARMS_API_KEY=""
# Telemetry is ON by default. Set to false/0/no/off to opt out.
SWARMS_TELEMETRY_ON="true"
# Library for model calls: "routehub" (installed by swarms[fast]) or "litellm". Empty uses RouteHub when installed.
SWARMS_LLM_BACKEND=""
# Write log files to WORKSPACE_DIR/logs. False by default (console only).
SWARMS_LOG_FILES="False"
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ Set your LLM API key as an environment variable before running:
export OPENAI_API_KEY="sk-..." # OpenAI / GPT models
export ANTHROPIC_API_KEY="sk-ant-..." # Claude models
export GROQ_API_KEY="..." # Groq
# Any provider supported by LiteLLM works
# Any provider supported by RouteHub works
```

All imports come from the top-level `swarms` package:
Expand Down Expand Up @@ -103,7 +103,7 @@ print(result)
| `agent_name` | str | `"swarm-worker-01"` | Unique name — used for memory file paths |
| `agent_description` | str | generic | Shown to orchestrators for routing |
| `system_prompt` | str | built-in | The agent's persona / instructions |
| `model_name` | str | `"gpt-5.4"` | Any LiteLLM model string |
| `model_name` | str | `"gpt-5.4"` | Any RouteHub model string |
| `max_loops` | int \| `"auto"` | `1` | Loops before returning; `"auto"` = autonomous until done |
| `tools` | list[Callable] | `None` | Python functions the agent can call |
| `streaming_on` | bool | `False` | Stream tokens to stdout |
Expand Down Expand Up @@ -140,7 +140,7 @@ result = agent.run("Research the top 5 vector databases and compare them.")

### Model names

Use any LiteLLM-compatible string:
Use any RouteHub-compatible string:

```python
# OpenAI
Expand Down
4 changes: 2 additions & 2 deletions SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ Swarms is a multi-agent orchestration framework. Everything is built from one pr
pip install -U swarms
```

Set the key for whichever provider you use — any [LiteLLM](https://docs.litellm.ai/docs/providers) model string works:
Set the key for whichever provider you use — any [RouteHub](https://github.com/The-Swarm-Corporation/RouteHub) model string works:

```bash
export OPENAI_API_KEY="sk-..."
Expand Down Expand Up @@ -56,7 +56,7 @@ result = agent.run("Summarize the state of the semiconductor market.")
| `agent_name` | `str` | `"swarm-worker-01"` | Unique identity; keys memory + routing |
| `agent_description` | `str` | generic | How orchestrators decide to route to it |
| `system_prompt` | `str` | built-in | Persona and instructions |
| `model_name` | `str` | `"gpt-5.4"` | Any LiteLLM model string |
| `model_name` | `str` | `"gpt-5.4"` | Any RouteHub model string |
| `max_loops` | `int \| "auto"` | `1` | Iterations, or autonomous mode |
| `tools` | `list[Callable]` | `None` | Python functions the agent may call |
| `temperature` | `float` | `0.5` | Sampling temperature |
Expand Down
7 changes: 3 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,20 @@ python-dotenv = "*"
PyYAML = "*"
networkx = "*"
rich = "*"
litellm = "1.76.1"
tqdm = "*"
httpx = "*"
requests = "*"
mcp = "*"
schedule = "*"
opentelemetry-sdk = "*"
opentelemetry-exporter-otlp-proto-http = "*"
zstandard = "*"
routehub = { version = ">=0.2.0", extras = ["fast"], optional = true }
routehub = { version = ">=0.2.0", extras = ["fast"] }
rustworkx = { version = "*", optional = true }

[tool.poetry.extras]
# RouteHub replaces litellm for model calls when installed.
# rustworkx backs GraphWorkflow(backend="rustworkx") in place of networkx.
fast = ["routehub", "rustworkx"]
fast = ["rustworkx"]

[tool.poetry.scripts]
swarms = "swarms.cli.main:main"
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ loguru
pydantic==2.12.5
tenacity
rich
tqdm
psutil
python-dotenv
PyYAML
networkx
httpx
requests
litellm==1.76.1
routehub[fast]>=0.2.0
mcp>=1.28.1,<3.0.0
schedule
opentelemetry-sdk
Expand Down
2 changes: 1 addition & 1 deletion swarms/agents/context_compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def _summarize(self, agent: Any, history: str) -> str:
],
temperature=self.summarizer_temperature,
max_tokens=self.summarizer_max_tokens,
# RouteHub reads this per call; litellm otherwise inherits it from the last agent built.
# RouteHub reads this per call.
drop_params=True,
)
call_usage = usage_from_response(response)
Expand Down
2 changes: 1 addition & 1 deletion swarms/agents/tree_of_thoughts.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,7 +545,7 @@ def __init__(
name: Name used in logs, in the conversation, and as the
prefix of every internal agent's name.
description: Short description of the agent, for orchestrators.
model_name: Any LiteLLM model string. The model must support
model_name: Any RouteHub model string. The model must support
function calling.
system_prompt: System prompt for every model call. Replace it to
give the search a domain persona; the step, evaluation and
Expand Down
2 changes: 1 addition & 1 deletion swarms/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2091,7 +2091,7 @@ def handle_tips(args: argparse.Namespace) -> None:

def handle_models(args: argparse.Namespace) -> None:
"""
Discover and inspect LLM models available via LiteLLM.
Discover and inspect LLM models available via RouteHub.

Flags:
--search PATTERN Fuzzy-match against the model list.
Expand Down
4 changes: 2 additions & 2 deletions swarms/cli/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
- ``swarms models --info <name>`` show context window, capabilities, pricing

All metadata comes from the LLM backend's ``model_list`` and
``get_model_info``: litellm's bundled table, or OpenRouter's live list when
``get_model_info``: RouteHub's bundled table, or OpenRouter's live list when
RouteHub is installed. Either way it stays in sync as providers ship models.
"""

Expand All @@ -25,7 +25,7 @@


def _provider_of(name: str) -> str:
"""Extract a provider key from a LiteLLM model string."""
"""Extract a provider key from a RouteHub model string."""
if "/" in name:
return name.split("/", 1)[0]
# Heuristics for the common no-prefix providers
Expand Down
2 changes: 1 addition & 1 deletion swarms/cli/tips.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@
]

MODEL_FLAG_TIPS: List[str] = [
"Any LiteLLM-supported model works in [bold]--model-name[/bold] — OpenAI, Anthropic, Groq, Gemini, Mistral, Cohere…",
"Any RouteHub-supported model works in [bold]--model-name[/bold] — OpenAI, Anthropic, Groq, Gemini, Mistral, Cohere…",
"[bold]--model-name claude-opus-4-7[/bold] picks the latest frontier Anthropic model",
"[bold]--model-name groq/llama-3.3-70b-versatile[/bold] is the fastest open-weight option",
"[bold]--model-name gemini/gemini-2.5-pro[/bold] gives you million-token context natively",
Expand Down
2 changes: 1 addition & 1 deletion swarms/cli/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def check_dependencies() -> Tuple[bool, str, str]:
Returns:
Tuple containing (success, status_icon, message)
"""
required_deps = ["torch", "transformers", "litellm", "rich"]
required_deps = ["torch", "transformers", "routehub", "rich"]
missing_deps = []

for dep in required_deps:
Expand Down
4 changes: 2 additions & 2 deletions swarms/prompts/auto_agent_builder_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
- **Structured extraction, classification, reformatting, routine summarization** — a smaller, cheaper model: `gpt-5.4-mini`, `claude-haiku-4-5-20251001`.
- **High-throughput or latency-sensitive work** — a fast hosted model: `groq/llama-3.3-70b-versatile`.

Use valid LiteLLM model strings. Provider-prefixed names route correctly (`groq/`, `together_ai/`, `openrouter/`, `gemini/`); bare OpenAI names do not need a prefix.
Use valid RouteHub model strings. Provider-prefixed names route correctly (`groq/`, `together_ai/`, `openrouter/`, `gemini/`); bare OpenAI names do not need a prefix.

Deliberately mixing tiers is a sign of good design. A roster where every agent runs the most expensive model usually means the work was never analyzed. So does one where every agent runs the cheapest.

Expand Down Expand Up @@ -129,7 +129,7 @@
- The roster covers the task end to end with no gap.
- Every name is unique and role-descriptive.
- Every `system_prompt` is task-specific, several paragraphs, and would stand alone.
- Every `model_name` is a valid LiteLLM string, chosen for that agent's actual workload.
- Every `model_name` is a valid RouteHub string, chosen for that agent's actual workload.
- The roster is within the requested maximum.

Then call `build_agents` once with the full roster.
Expand Down
8 changes: 1 addition & 7 deletions swarms/utils/litellm_wrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,7 @@ def __init__(
"num_retries": retries,
"drop_params": drop_params,
}
if llm_backend.BACKEND == "litellm":
litellm = llm_backend.backend_module()
for name, value in backend_options.items():
setattr(litellm, name, value)
else:
self._backend_options = backend_options
self._backend_options = backend_options

# Add system prompt if present (Anthropic rejects empty system blocks)
if isinstance(self.system_prompt, str):
Expand Down Expand Up @@ -1213,7 +1208,6 @@ def check_if_model_supports_vision(self, img: str = None):
# RouteHub's catalog comes from the network, so a model it can't find may still take images.
if (
out is False
and llm_backend.BACKEND == "routehub"
and self.model_name not in llm_backend.model_list
):
logger.warning(
Expand Down
89 changes: 30 additions & 59 deletions swarms/utils/llm_backend.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,55 @@
"""The library swarms makes model calls through.

RouteHub is used when it is installed, which pip install "swarms[fast]"
does, and litellm otherwise. Set SWARMS_LLM_BACKEND to "litellm" or
"routehub" to choose one explicitly.

Neither library is imported until a name is first used, so importing swarms
RouteHub is the only model-call backend and a required dependency. The
library is not imported until a name is first used, so importing swarms
stays fast. completion, acompletion and embedding are defined here; every
other name, such as get_model_info or AuthenticationError, is read from the
chosen library on first access.
other name, such as get_model_info or AuthenticationError, is read from
RouteHub on first access.

SWARMS_LLM_BACKEND is no longer read. A leftover "litellm" value draws a
warning so old setups learn the switch is gone; every other value is
ignored.
"""

import importlib
import importlib.util
import os
from types import ModuleType
from typing import Any

BACKENDS = ("routehub", "litellm")
from loguru import logger

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No change for this one. loguru is a declared runtime dependency (pyproject.toml and requirements.txt), and 45 modules under swarms/ on master already import it the same way. Pyre's environment does not install the package's dependencies, so it cannot resolve the import anywhere; it reports it here only because this PR added the line (the same alert was answered on #2451).

The test job does install dependencies and imports this module: all 9 tests in tests/utils/test_llm_backend.py pass, and the job's 246 failures and 7 errors are the same set as master's latest run on 6967b7d, all from tests that need a live OPENAI_API_KEY.


# The libraries refresh these themselves, so they are read on every access.
# The library refreshes these itself, so they are read on every access.
_UNCACHED = frozenset({"model_list", "model_cost"})

_INSTALL_HINTS = {
"routehub": 'pip install "swarms[fast]"',
"litellm": "pip install litellm",
}


def select_backend() -> str:
"""Name the library to make model calls through.

Returns:
str: "routehub" or "litellm".

Raises:
ValueError: If SWARMS_LLM_BACKEND names another library.
ImportError: If SWARMS_LLM_BACKEND names a library that is not
installed.
"""
requested = os.getenv("SWARMS_LLM_BACKEND", "").strip().lower()
if not requested:
if importlib.util.find_spec("routehub") is not None:
return "routehub"
return "litellm"
if requested not in BACKENDS:
raise ValueError(
f"SWARMS_LLM_BACKEND must be one of {', '.join(BACKENDS)}, "
f"got {requested!r}."
)
if importlib.util.find_spec(requested) is None:
raise ImportError(
f"SWARMS_LLM_BACKEND is {requested!r}, but {requested} is not "
f"installed. Install it with: {_INSTALL_HINTS[requested]}"
)
return requested


BACKEND = select_backend()

_module = None


def backend_module() -> ModuleType:
"""Import the chosen library on first use.
"""Import RouteHub on first use.

Returns:
ModuleType: The routehub or litellm module.
ModuleType: The routehub module.
"""
global _module
if _module is None:
_module = importlib.import_module(BACKEND)
if (
os.getenv("SWARMS_LLM_BACKEND", "").strip().lower()
== "litellm"
):
logger.warning(
"SWARMS_LLM_BACKEND=litellm is no longer supported; "
"swarms makes model calls through RouteHub."
)
_module = importlib.import_module("routehub")
return _module


def completion(*args: Any, **kwargs: Any) -> Any:
"""Make a chat completion call.

Args:
*args (Any): Positional arguments for the library's completion.
**kwargs (Any): Keyword arguments for the library's completion.
*args (Any): Positional arguments for RouteHub's completion.
**kwargs (Any): Keyword arguments for RouteHub's completion.

Returns:
Any: The response, or a stream of chunks when stream is on.
Expand All @@ -90,8 +61,8 @@
"""Make an async chat completion call.

Args:
*args (Any): Positional arguments for the library's acompletion.
**kwargs (Any): Keyword arguments for the library's acompletion.
*args (Any): Positional arguments for RouteHub's acompletion.
**kwargs (Any): Keyword arguments for RouteHub's acompletion.

Returns:
Any: The response, or an async stream of chunks when stream is on.
Expand All @@ -103,8 +74,8 @@
"""Make an embedding call.

Args:
*args (Any): Positional arguments for the library's embedding.
**kwargs (Any): Keyword arguments for the library's embedding.
*args (Any): Positional arguments for RouteHub's embedding.
**kwargs (Any): Keyword arguments for RouteHub's embedding.

Returns:
Any: The embedding response.
Expand All @@ -113,16 +84,16 @@


def __getattr__(name: str) -> Any:
"""Read a name from the chosen library on first access.
"""Read a name from RouteHub on first access.

Args:
name (str): A name the library exports, such as get_model_info.
name (str): A name RouteHub exports, such as get_model_info.

Returns:
Any: The library's function, class or value.

Raises:
AttributeError: If the library has no such name.
AttributeError: If RouteHub has no such name.
"""
# Dunder lookups come from tooling such as import machinery, not callers.
if name.startswith("__"):
Expand Down
Loading
Loading