Skip to content
Merged
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
21 changes: 16 additions & 5 deletions packages/autobot/autobot/agent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
``/v1``) with the model the stack stamps for blue. Provider keys and model
routing live in the proxy — the user's Decepticon LLM settings are reused,
never configured twice.
2. **Standalone mode** — ``AUTOBOT_MODEL`` builds a ChatModel directly through
the LiteLLM SDK (``ChatLiteLLM``), provider keys in the standard env.
2. **Standalone mode** — ``AUTOBOT_MODEL`` builds a ChatModel via langchain's
``init_chat_model`` (provider packages as optional extras; the LiteLLM-SDK
construction was dropped because ``langchain-litellm`` transitively forces
``mcp>=2``, conflicting with the Decepticon MCP server's ``mcp<2`` pin).

Resolution order: shared gateway (if fully configured) → standalone
``AUTOBOT_MODEL`` → fail-closed with guidance covering both modes.
Expand All @@ -37,7 +39,7 @@
import re
from typing import Any

from langchain_litellm import ChatLiteLLM
from langchain.chat_models import init_chat_model

_MODEL_ENV = "AUTOBOT_MODEL"
_FALLBACK_ENV = "AUTOBOT_FALLBACK_MODELS"
Expand Down Expand Up @@ -73,10 +75,19 @@ def _temperature() -> float | None:


def _build_chat(model: str, temperature: float | None) -> Any:
kwargs: dict[str, Any] = {"model": model}
# langchain's universal constructor. The LiteLLM-style ``provider/model``
# prefix is split: the provider selects the integration (whose package
# must be installed — autobot[openai], autobot[anthropic], ...) and the
# rest is the provider-native model name. Custom gateways (an Elice-served
# model behind a LiteLLM proxy) go through the shared-gateway mode —
# provider-agnostic.
provider, _, name = model.partition("/")
kwargs: dict[str, Any] = (
{"model": name, "model_provider": provider} if name else {"model": model}
)
if temperature is not None:
kwargs["temperature"] = temperature
return ChatLiteLLM(**kwargs)
return init_chat_model(**kwargs)


_GATEWAY_PROXY_ENV = "DECEPTICON_LLM__PROXY_URL"
Expand Down
6 changes: 4 additions & 2 deletions packages/autobot/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
[project]
name = "autobot"
# PyPI distribution name — the bare ``autobot`` name is already taken on
# PyPI by an unrelated project, so the short namespace form is used. The
# import package remains ``autobot``.
name = "bs-autobot"
# Sentinel — replaced with the git tag at build time, in lockstep with the
# rest of the workspace. See containers/langgraph.Dockerfile and the
# release workflow.
Expand Down Expand Up @@ -43,7 +46,6 @@ dependencies = [
"langchain-core>=0.3.0",
"langchain>=0.3.0",
"langgraph>=1.0.0",
"langchain-litellm>=0.9.0",
"langchain-openai>=0.3.0",
"langchain-anthropic>=0.3.0",
"deepagents>=0.3.0",
Expand Down
5 changes: 2 additions & 3 deletions packages/autobot/tests/unit/agent/test_blue_cell_wiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest

from autobot.agent.blue_cell import (
build_model,
create_blue_cell_agent,
default_middleware,
default_tools,
Expand Down Expand Up @@ -59,11 +60,9 @@ def test_system_prompt_carries_the_identity_and_readonly_contract() -> None:


def test_default_middleware_order_matches_the_design(monkeypatch: pytest.MonkeyPatch) -> None:
from langchain_litellm import ChatLiteLLM

monkeypatch.setenv("AUTOBOT_MODEL", "anthropic/claude-sonnet-4")
monkeypatch.setenv("AUTOBOT_BUDGET_TOKENS", "0")
model = ChatLiteLLM(model="anthropic/claude-sonnet-4") # construction only, no calls
model = build_model() # ChatAnthropic — construction only, no calls
order = [type(m).__name__ for m in default_middleware(model)]
assert order == [
"UntrustedOutputMiddleware",
Expand Down
7 changes: 4 additions & 3 deletions packages/autobot/tests/unit/agent/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@ def test_missing_model_fails_closed_with_guidance() -> None:
build_model()


def test_primary_only_builds_chatlitellm(monkeypatch: pytest.MonkeyPatch) -> None:
def test_primary_builds_provider_chat_model(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("AUTOBOT_MODEL", "anthropic/claude-sonnet-4")
model = build_model()
assert type(model).__name__ == "ChatLiteLLM"
# init_chat_model dispatches by the provider prefix.
assert type(model).__name__ == "ChatAnthropic"


def test_fallback_models_parse_from_commas_and_spaces(monkeypatch: pytest.MonkeyPatch) -> None:
Expand All @@ -31,7 +32,7 @@ def test_fallback_models_parse_from_commas_and_spaces(monkeypatch: pytest.Monkey

def test_fallbacks_attach_natively(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setenv("AUTOBOT_MODEL", "anthropic/claude-sonnet-4")
monkeypatch.setenv("AUTOBOT_FALLBACK_MODELS", "openai/gpt-4o")
monkeypatch.setenv("AUTOBOT_FALLBACK_MODELS", "anthropic/claude-haiku-4-5")
model = build_model()
from langchain_core.runnables import RunnableWithFallbacks

Expand Down
Loading
Loading