Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

167 lines
6.9 KiB
Python

"""Construct the concrete LLM client for a resolved route.
Given an :class:`~core.llm.types.LLMRoute`, build the client for the transport
(CLI-backed, LiteLLM, or native vendor SDK) and provider the route resolved to.
``build_agent_client`` builds the tool-calling client; ``build_reasoning_client``
builds the streaming reasoning client for a model tier. The routing decision itself
lives in :mod:`core.llm.factory`; these functions only construct.
Import discipline: construction imports (``sdk`` / ``litellm`` / ``config``) are done
lazily inside functions, matching the rest of ``core.llm``. Keeping them lazy avoids
pulling the full provider stack at module import time and holds the ``importlinter``
contract.
"""
from __future__ import annotations
import os
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from core.llm.types import AgentLLMClient, LLMRoute, ModelType
__all__ = ["build_agent_client", "build_reasoning_client"]
# ---------------------------------------------------------------------------
# Tool-calling (agent) clients
# ---------------------------------------------------------------------------
def build_agent_client(route: LLMRoute) -> AgentLLMClient:
"""Build the tool-calling client for the route: CLI or LiteLLM transport, else native SDK."""
if route.cli_provider_registration is not None:
return _cli_agent_client(route.cli_provider_registration)
if route.use_litellm:
from core.llm.transports.litellm.routing import build_litellm_agent_client
return build_litellm_agent_client(route.settings, route.provider)
return _native_sdk_agent_client(route)
def _cli_agent_client(registration: Any) -> AgentLLMClient:
"""Build the subprocess CLI-backed tool-calling client for a CLI provider registration."""
from core.llm.transports.sdk.agent_clients import CLIBackedAgentClient
model_name = os.getenv(registration.model_env_key, "").strip() or None
return CLIBackedAgentClient(registration.adapter_factory(), model=model_name)
def _native_sdk_agent_client(route: LLMRoute) -> AgentLLMClient:
"""Build the native vendor-SDK tool-calling client for the route's provider."""
from config.config import PROVIDER_ANTHROPIC, PROVIDER_BEDROCK, PROVIDER_OLLAMA, PROVIDER_OPENAI
from core.llm.providers.openai_compat_providers import (
is_openai_compat_provider,
resolve_openai_compat_provider,
)
from core.llm.providers.provider_registry import FIRST_PARTY_PROVIDERS
from core.llm.transports.sdk import agent_clients as sdk
settings, provider = route.settings, route.provider
if is_openai_compat_provider(provider):
resolved = resolve_openai_compat_provider(settings, provider, "reasoning")
max_tokens = 1024 if provider == PROVIDER_OLLAMA else resolved.config.max_tokens
return sdk.OpenAIAgentClient(
model=resolved.model,
max_tokens=max_tokens,
base_url=resolved.base_url,
api_key_env=resolved.api_key_env,
api_key_default=resolved.api_key_default,
)
spec = FIRST_PARTY_PROVIDERS.get(provider) or FIRST_PARTY_PROVIDERS[PROVIDER_ANTHROPIC]
model = getattr(settings, f"{spec.env_prefix}_reasoning_model")
if provider == PROVIDER_BEDROCK:
from core.llm.providers.bedrock_model_ids import is_anthropic_bedrock_model
if is_anthropic_bedrock_model(model):
return sdk.BedrockAgentClient(model=model, max_tokens=spec.max_tokens)
return sdk.BedrockConverseAgentClient(model=model, max_tokens=spec.max_tokens)
if provider == PROVIDER_OPENAI:
return sdk.OpenAIAgentClient(model=model, max_tokens=spec.max_tokens)
return sdk.AnthropicAgentClient(model=model, max_tokens=spec.max_tokens)
# ---------------------------------------------------------------------------
# Streaming reasoning clients
# ---------------------------------------------------------------------------
def build_reasoning_client(route: LLMRoute, model_type: ModelType) -> Any:
"""Build the reasoning client for the route and model tier: CLI or LiteLLM, else native SDK."""
if route.cli_provider_registration is not None:
return _cli_llm_client(route.cli_provider_registration, model_type)
if route.use_litellm:
from core.llm.shared.usage import emit_usage
from core.llm.transports.litellm.routing import build_litellm_llm_client
return build_litellm_llm_client(
route.settings,
route.provider,
model_type,
usage_callback=emit_usage,
)
return _native_sdk_llm_client(route, model_type)
def _cli_llm_client(registration: Any, model_type: ModelType) -> Any:
"""Build the subprocess CLI-backed reasoning client for a CLI provider registration."""
from config.config import DEFAULT_MAX_TOKENS
from platform.harness_ports import build_cli_client
model_name = os.getenv(registration.model_env_key, "").strip() or None
return build_cli_client(
registration.adapter_factory(),
model=model_name,
max_tokens=DEFAULT_MAX_TOKENS,
model_type=model_type,
)
def _native_sdk_llm_client(route: LLMRoute, model_type: ModelType) -> Any:
"""Build the native vendor-SDK reasoning client for the route's provider and tier."""
from config.config import PROVIDER_ANTHROPIC, PROVIDER_BEDROCK, PROVIDER_OPENAI
from core.llm.providers.openai_compat_providers import (
is_openai_compat_provider,
resolve_openai_compat_provider,
)
from core.llm.providers.provider_registry import FIRST_PARTY_PROVIDERS
from core.llm.transports.sdk import llm_clients as sdk
settings, provider = route.settings, route.provider
def _fallback_model(provider_prefix: str) -> str | None:
if model_type == "toolcall":
return None
return str(getattr(settings, f"{provider_prefix}_toolcall_model", None) or "") or None
if is_openai_compat_provider(provider):
compat = resolve_openai_compat_provider(settings, provider, model_type)
return sdk.OpenAILLMClient(
model=compat.model,
model_fallback=_fallback_model(provider),
max_tokens=compat.config.max_tokens,
base_url=compat.base_url,
api_key_env=compat.api_key_env,
api_key_default=compat.api_key_default,
temperature=compat.temperature,
)
spec = FIRST_PARTY_PROVIDERS.get(provider) or FIRST_PARTY_PROVIDERS[PROVIDER_ANTHROPIC]
model = str(getattr(settings, f"{spec.env_prefix}_{model_type}_model"))
if provider == PROVIDER_OPENAI:
return sdk.OpenAILLMClient(
model=model,
model_fallback=_fallback_model("openai"),
max_tokens=spec.max_tokens,
)
if provider == PROVIDER_BEDROCK:
return sdk.BedrockLLMClient(model=model, max_tokens=spec.max_tokens)
return sdk.LLMClient(model=model, max_tokens=spec.max_tokens)