mirror of
https://github.com/langchain-ai/deepagents.git
synced 2026-07-22 09:45:24 -04:00
fix(code): infer Fireworks provider from qualified model IDs (#4594)
Deep Agents Code now preserves the Fireworks provider when `/model` receives a fully qualified Fireworks model or router ID, so the switch confirmation and status bar display the provider correctly. --- Switching models with a fully qualified Fireworks ID such as `/model accounts/fireworks/models/kimi-k2p7-code` succeeded because LangChain inferred the provider during model initialization. Deep Agents Code did not make the same inference beforehand, though, so it lost the provider name: the switch confirmation showed only the raw ID and the status bar omitted the `fireworks:` prefix. `detect_provider` now recognizes the broad `accounts/fireworks/` stem, matching LangChain's inference for both model and router IDs. The existing, narrower model and router prefixes remain responsible for shortening those IDs in the status bar and classifying reasoning-effort support. Made by [Open SWE](https://openswe.vercel.app/agents/1f74e649-60d9-d586-f671-41b471b5a5d8) --------- Co-authored-by: open-swe[bot] <open-swe@users.noreply.github.com>
This commit is contained in:
@@ -13,11 +13,14 @@ from typing import Final
|
||||
DEFAULT_AGENT_NAME: Final[str] = "agent"
|
||||
"""Default agent / assistant identifier when no `-a` flag is given."""
|
||||
|
||||
FIREWORKS_PROVIDER_ID_PREFIX: Final[str] = "accounts/fireworks/"
|
||||
"""Prefix used to infer Fireworks from fully-qualified IDs."""
|
||||
|
||||
FIREWORKS_MODEL_ID_PREFIXES: Final[tuple[str, ...]] = (
|
||||
"accounts/fireworks/models/",
|
||||
"accounts/fireworks/routers/",
|
||||
)
|
||||
"""Fully-qualified prefixes for Fireworks model and router IDs."""
|
||||
"""Model and router ID prefixes used for stripping and classification."""
|
||||
|
||||
SYSTEM_MESSAGE_PREFIX: Final[str] = "[SYSTEM]"
|
||||
"""Prefix for synthetic human messages (e.g. interrupt cancellation notices).
|
||||
|
||||
@@ -19,6 +19,7 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any, Protocol
|
||||
from urllib.parse import unquote, urlparse
|
||||
|
||||
from deepagents_code._constants import FIREWORKS_PROVIDER_ID_PREFIX
|
||||
from deepagents_code._env_vars import (
|
||||
DISABLED_PROJECT_MCP_SERVERS,
|
||||
ENABLED_PROJECT_MCP_SERVERS,
|
||||
@@ -3796,8 +3797,8 @@ def detect_provider(model_name: str) -> str | None:
|
||||
|
||||
Returns:
|
||||
Provider name (openai, anthropic, google_genai, google_vertexai,
|
||||
nvidia) or `None` if the provider cannot be determined from the
|
||||
name alone.
|
||||
nvidia, fireworks) or `None` if the provider cannot be determined
|
||||
from the name alone.
|
||||
"""
|
||||
model_lower = model_name.lower()
|
||||
|
||||
@@ -3819,6 +3820,15 @@ def detect_provider(model_name: str) -> str | None:
|
||||
if model_lower.startswith(("nemotron", "nvidia/")):
|
||||
return "nvidia"
|
||||
|
||||
# Fireworks uses fully-qualified IDs like `accounts/fireworks/models/<name>`.
|
||||
# `init_chat_model` can infer the provider from this prefix, but the inferred
|
||||
# name is not exposed on the returned model, so resolving it here keeps the
|
||||
# provider visible to every downstream consumer of `detect_provider` (e.g.
|
||||
# the `/model` confirmation, the status bar, and the early credential check)
|
||||
# instead of leaving the raw ID unprefixed.
|
||||
if model_lower.startswith(FIREWORKS_PROVIDER_ID_PREFIX):
|
||||
return "fireworks"
|
||||
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -70,8 +70,10 @@ class ModelLabel(Widget):
|
||||
name = self.model
|
||||
if not name or not self.provider:
|
||||
return name
|
||||
# Match on normalized text but slice the original to preserve its casing.
|
||||
name_lower = name.lower()
|
||||
for prefix in PROVIDER_PREFIX_STRIPS.get(self.provider, ()):
|
||||
if name.startswith(prefix):
|
||||
if name_lower.startswith(prefix):
|
||||
return name[len(prefix) :]
|
||||
return name
|
||||
|
||||
|
||||
@@ -4750,6 +4750,14 @@ class TestDetectProvider:
|
||||
("gemini-3.1-pro-preview", "google_genai"),
|
||||
("nemotron-3-nano-30b-a3b", "nvidia"),
|
||||
("nvidia/nemotron-3-nano-30b-a3b", "nvidia"),
|
||||
("accounts/fireworks/models/kimi-k2p7-code", "fireworks"),
|
||||
("accounts/fireworks/routers/kimi-k2p7-code", "fireworks"),
|
||||
("Accounts/Fireworks/Models/Kimi-K2P7-Code", "fireworks"),
|
||||
("accounts/openai/models/gpt-5.5", None),
|
||||
# A different account whose name merely starts with "fireworks"
|
||||
# must not resolve to Fireworks; the trailing slash in the prefix
|
||||
# is what anchors the match to the exact account namespace.
|
||||
("accounts/fireworks-enterprise/models/kimi-k2p7-code", None),
|
||||
("llama3", None),
|
||||
("mistral-large", None),
|
||||
("some-unknown-model", None),
|
||||
|
||||
@@ -1026,6 +1026,47 @@ class TestModelSwitchBareModelName:
|
||||
assert settings.model_provider == "openai"
|
||||
assert any("Switched to openai:gpt-5.5" in m for m in captured_messages)
|
||||
|
||||
async def test_fireworks_qualified_id_gets_provider_prefix(self) -> None:
|
||||
"""A Fireworks `accounts/...` ID resolves to a `fireworks:` prefix.
|
||||
|
||||
Without provider inference the raw ID would surface unprefixed in the
|
||||
confirmation message and the status bar (which reads
|
||||
`settings.model_provider`). `detect_provider` recognizes the
|
||||
fully-qualified Fireworks ID so both reflect the `fireworks` provider.
|
||||
"""
|
||||
app = DeepAgentsApp()
|
||||
app._mount_message = AsyncMock() # ty: ignore
|
||||
app._agent = _make_remote_agent()
|
||||
|
||||
settings.model_name = "claude-sonnet-4-5"
|
||||
settings.model_provider = "anthropic"
|
||||
|
||||
captured_messages: list[str] = []
|
||||
original_init = AppMessage.__init__
|
||||
|
||||
def capture_init(self: AppMessage, message: str, **kwargs: Any) -> None:
|
||||
captured_messages.append(message)
|
||||
original_init(self, message, **kwargs)
|
||||
|
||||
model_id = "accounts/fireworks/models/kimi-k2p7-code"
|
||||
with (
|
||||
patch(
|
||||
"deepagents_code.model_config.get_provider_auth_status",
|
||||
return_value=_CONFIGURED_AUTH_STATUS,
|
||||
),
|
||||
patch(
|
||||
"deepagents_code.model_config.save_recent_model", return_value=True
|
||||
) as mock_save,
|
||||
patch.object(AppMessage, "__init__", capture_init),
|
||||
):
|
||||
await app._switch_model(model_id)
|
||||
|
||||
mock_save.assert_called_once_with(f"fireworks:{model_id}")
|
||||
assert app._model_override == f"fireworks:{model_id}"
|
||||
assert settings.model_name == model_id
|
||||
assert settings.model_provider == "fireworks"
|
||||
assert any(f"Switched to fireworks:{model_id}" in m for m in captured_messages)
|
||||
|
||||
async def test_bare_model_name_missing_credentials(self) -> None:
|
||||
"""Bare model name shows credential error when provider creds are missing."""
|
||||
app = DeepAgentsApp()
|
||||
|
||||
@@ -553,6 +553,24 @@ class TestModelLabelPrefixStripping:
|
||||
assert "fireworks:glm-5p1-fast" in rendered
|
||||
assert "accounts/fireworks/routers/" not in rendered
|
||||
|
||||
async def test_fireworks_prefix_stripped_case_insensitively(self) -> None:
|
||||
"""A mixed-case fireworks ID is stripped, preserving the tail's casing.
|
||||
|
||||
`detect_provider` resolves mixed-case `accounts/fireworks/...` IDs to
|
||||
the `fireworks` provider, so the display layer strips the prefix
|
||||
case-insensitively too. The remaining model name keeps its original
|
||||
casing rather than being lowercased.
|
||||
"""
|
||||
async with StatusBarApp().run_test() as pilot:
|
||||
label = pilot.app.query_one("#model-display", ModelLabel)
|
||||
label.provider = "fireworks"
|
||||
label.model = "Accounts/Fireworks/Models/Kimi-K2P6"
|
||||
await pilot.pause()
|
||||
assert label._clean_model() == "Kimi-K2P6"
|
||||
rendered = str(label.render())
|
||||
assert "fireworks:Kimi-K2P6" in rendered
|
||||
assert "Accounts/Fireworks/Models/" not in rendered
|
||||
|
||||
async def test_get_content_width_uses_stripped_name(self) -> None:
|
||||
"""`get_content_width` sizes to the stripped name, not the raw model."""
|
||||
async with StatusBarApp().run_test() as pilot:
|
||||
|
||||
Reference in New Issue
Block a user