mirror of
https://github.com/usestrix/strix.git
synced 2026-10-05 02:41:38 +00:00
fix(deps): ship google-auth with every install so Vertex AI works in release binaries
This commit is contained in:
parent
881bc09a06
commit
86edf1c5ff
12 changed files with 68 additions and 183 deletions
5
.github/workflows/build-release.yml
vendored
5
.github/workflows/build-release.yml
vendored
|
|
@ -72,7 +72,10 @@ jobs:
|
|||
TUI_NAME="strix-tui"
|
||||
dist/strix --version
|
||||
fi
|
||||
uv run pyi-archive_viewer -l "$PYI_BINARY" | grep -E "strix[/\\]+bin[/\\]+$TUI_NAME" >/dev/null
|
||||
uv run pyi-archive_viewer -l -r "$PYI_BINARY" > archive.txt
|
||||
grep -E "strix[/\\]+bin[/\\]+$TUI_NAME" archive.txt >/dev/null
|
||||
grep -E "'google\.auth\.transport\.requests'" archive.txt >/dev/null
|
||||
grep -E "'boto3\.session'" archive.txt >/dev/null
|
||||
|
||||
if [[ "${{ matrix.target }}" == "linux-arm64" ]]; then
|
||||
file dist/strix
|
||||
|
|
|
|||
10
.github/workflows/ci-package.yml
vendored
10
.github/workflows/ci-package.yml
vendored
|
|
@ -36,6 +36,8 @@ jobs:
|
|||
uv pip install --python /tmp/strix-smoke/bin/python dist/*.whl
|
||||
/tmp/strix-smoke/bin/strix --version
|
||||
/tmp/strix-smoke/bin/strix --help >/dev/null
|
||||
- name: Vertex AI and Bedrock dependencies install with the wheel
|
||||
run: /tmp/strix-smoke/bin/python -c "import google.auth.transport.requests, boto3"
|
||||
|
||||
binary:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -51,5 +53,9 @@ jobs:
|
|||
- run: make tui-build
|
||||
- run: uv run --frozen pyinstaller strix.spec --noconfirm
|
||||
- run: dist/strix --version
|
||||
- name: Binary bundles the TUI sidecar
|
||||
run: uv run --frozen pyi-archive_viewer -l dist/strix | grep -E "strix/bin/strix-tui" >/dev/null
|
||||
- name: Binary bundles the TUI sidecar and the provider dependencies
|
||||
run: |
|
||||
uv run --frozen pyi-archive_viewer -l -r dist/strix > archive.txt
|
||||
grep -E "strix/bin/strix-tui" archive.txt >/dev/null
|
||||
grep -E "'google\.auth\.transport\.requests'" archive.txt >/dev/null
|
||||
grep -E "'boto3\.session'" archive.txt >/dev/null
|
||||
|
|
|
|||
|
|
@ -3,14 +3,6 @@ title: "AWS Bedrock"
|
|||
description: "Configure Strix with models via AWS Bedrock"
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Bedrock requires the AWS SDK dependency. Install Strix with the bedrock extra:
|
||||
|
||||
```bash
|
||||
pipx install "strix-agent[bedrock]"
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -3,14 +3,6 @@ title: "Google Vertex AI"
|
|||
description: "Configure Strix with Gemini models via Google Cloud"
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
Vertex AI requires the Google Cloud dependency. Install Strix with the vertex extra:
|
||||
|
||||
```bash
|
||||
pipx install "strix-agent[vertex]"
|
||||
```
|
||||
|
||||
## Setup
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ dependencies = [
|
|||
"openai-agents[litellm]>=0.19.0,<0.20",
|
||||
"openai>=2.45.0,<3",
|
||||
"litellm>=1.101.0",
|
||||
"google-auth>=2.0.0",
|
||||
"pydantic>=2.11.3",
|
||||
"pydantic-settings>=2.13.0",
|
||||
"rich",
|
||||
|
|
@ -52,10 +53,6 @@ dependencies = [
|
|||
"pyyaml>=6.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
vertex = ["google-auth>=2.0.0"]
|
||||
bedrock = ["boto3>=1.28.0"]
|
||||
|
||||
[project.scripts]
|
||||
strix = "strix.interface.main:main"
|
||||
|
||||
|
|
|
|||
|
|
@ -180,6 +180,8 @@ hiddenimports = [
|
|||
]
|
||||
|
||||
hiddenimports += collect_submodules('litellm')
|
||||
hiddenimports += collect_submodules('google.auth')
|
||||
hiddenimports += collect_submodules('google.oauth2')
|
||||
hiddenimports += collect_submodules('rich')
|
||||
hiddenimports += collect_submodules('pydantic')
|
||||
hiddenimports += collect_submodules('pygments')
|
||||
|
|
@ -211,8 +213,6 @@ excludes = [
|
|||
'google.cloud',
|
||||
'google.cloud.aiplatform',
|
||||
'google.api_core',
|
||||
'google.auth',
|
||||
'google.oauth2',
|
||||
'google.protobuf',
|
||||
'grpc',
|
||||
'grpcio',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ Strix Agent Interface
|
|||
import argparse
|
||||
import asyncio
|
||||
import contextlib
|
||||
import logging
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
|
@ -53,21 +54,6 @@ from strix.telemetry import posthog, report_error, scarf, set_scan_phase
|
|||
from strix.telemetry.logging import setup_console_logging
|
||||
|
||||
|
||||
BEDROCK_MODEL_PREFIX = "bedrock/"
|
||||
BEDROCK_MISSING_MODULE_ERROR = "No module named 'boto3'"
|
||||
BEDROCK_EXTRA_HINT = (
|
||||
'Bedrock support is optional. Install it with: pipx install "strix-agent[bedrock]"'
|
||||
)
|
||||
VERTEX_MODEL_MARKER = "vertex"
|
||||
VERTEX_MISSING_MODULE_ERROR = "No module named 'google"
|
||||
VERTEX_EXTRA_HINT = (
|
||||
'Vertex AI support is optional. Install it with: pipx install "strix-agent[vertex]"'
|
||||
)
|
||||
|
||||
|
||||
import logging # noqa: E402
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_ROOT_SUBCOMMAND_HELP = """
|
||||
|
|
@ -96,29 +82,6 @@ def _exception_messages(exc: BaseException) -> tuple[str, ...]:
|
|||
return tuple(messages)
|
||||
|
||||
|
||||
def _provider_import_hint(exc: BaseException, model: str) -> str | None:
|
||||
"""Return an install hint when *exc* is a missing provider dependency.
|
||||
|
||||
Bedrock and Vertex AI ship as optional extras: Bedrock needs ``boto3`` and
|
||||
Vertex AI needs ``google-auth``. When either is absent, litellm may raise an
|
||||
``ImportError``/``ModuleNotFoundError`` directly or wrap it in a connection
|
||||
error. Map the missing module back to the matching extra so the user knows
|
||||
what to install. Returns ``None`` for any unrelated error.
|
||||
"""
|
||||
model_name = model.lower()
|
||||
messages = _exception_messages(exc)
|
||||
if any(
|
||||
BEDROCK_MISSING_MODULE_ERROR in message for message in messages
|
||||
) and model_name.startswith(BEDROCK_MODEL_PREFIX):
|
||||
return BEDROCK_EXTRA_HINT
|
||||
if (
|
||||
any(VERTEX_MISSING_MODULE_ERROR in message for message in messages)
|
||||
and VERTEX_MODEL_MARKER in model_name
|
||||
):
|
||||
return VERTEX_EXTRA_HINT
|
||||
return None
|
||||
|
||||
|
||||
def _subscription_error_hint(exc: BaseException) -> str | None:
|
||||
"""Return an actionable hint for a known ChatGPT-subscription error, or None."""
|
||||
if not codex.subscription_model(load_settings().llm.model):
|
||||
|
|
@ -364,7 +327,7 @@ def _print_error_panel(title: str, message: str) -> None:
|
|||
console.print()
|
||||
|
||||
|
||||
def _print_model_connection_error(exc: BaseException, model_name: str) -> None:
|
||||
def _print_model_connection_error(exc: BaseException) -> None:
|
||||
console = Console()
|
||||
error_text = Text()
|
||||
sub_hint = _subscription_error_hint(exc)
|
||||
|
|
@ -380,9 +343,6 @@ def _print_model_connection_error(exc: BaseException, model_name: str) -> None:
|
|||
error_text.append("\n\n", style="white")
|
||||
error_text.append("Could not establish connection to the language model.\n", style="white")
|
||||
error_text.append("Please check your configuration and try again.\n", style="white")
|
||||
hint = _provider_import_hint(exc, model_name)
|
||||
if hint is not None:
|
||||
error_text.append(f"\n{hint}\n", style="bold yellow")
|
||||
error_text.append(f"\nError: {exc}", style="dim white")
|
||||
|
||||
panel = Panel(
|
||||
|
|
@ -435,7 +395,7 @@ def _bootstrap_scan(args: argparse.Namespace) -> None:
|
|||
asyncio.run(warm_up_llm())
|
||||
except ModelConnectionError as exc:
|
||||
report_error("model_connection_failed", exc)
|
||||
_print_model_connection_error(exc, exc.model_name)
|
||||
_print_model_connection_error(exc)
|
||||
sys.exit(1)
|
||||
persist_current()
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ def test_direct_launch_with_a_bad_key_prints_the_panel_and_exits_before_the_tui(
|
|||
monkeypatch.setattr(
|
||||
cli_main,
|
||||
"_print_model_connection_error",
|
||||
lambda exc, model: calls.append(f"panel:{model}:{exc}"),
|
||||
lambda exc: calls.append(f"panel:{exc}"),
|
||||
)
|
||||
monkeypatch.setattr(cli_main, "persist_current", lambda: calls.append("persist"))
|
||||
monkeypatch.setattr(cli_main, "prepare_run", lambda _args: calls.append("prepare"))
|
||||
|
|
@ -89,7 +89,7 @@ def test_direct_launch_with_a_bad_key_prints_the_panel_and_exits_before_the_tui(
|
|||
cli_main._bootstrap_scan(argparse.Namespace(non_interactive=False, needs_setup=False))
|
||||
|
||||
assert exit_info.value.code == 1
|
||||
assert calls == ["panel:openai/gpt-4o:Error code: 401"]
|
||||
assert calls == ["panel:Error code: 401"]
|
||||
|
||||
|
||||
def test_tui_startup_failure_marks_the_prepared_run_failed(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
"""Tests for the optional-dependency extras declared in pyproject.toml."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PYPROJECT = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
||||
|
||||
|
||||
def _optional_dependencies() -> dict[str, list[str]]:
|
||||
data = tomllib.loads(PYPROJECT.read_text(encoding="utf-8"))
|
||||
extras: dict[str, list[str]] = data["project"]["optional-dependencies"]
|
||||
return extras
|
||||
|
||||
|
||||
def test_vertex_extra_pins_google_auth() -> None:
|
||||
extras = _optional_dependencies()
|
||||
assert "vertex" in extras
|
||||
assert any(req.startswith("google-auth") for req in extras["vertex"])
|
||||
|
||||
|
||||
def test_bedrock_extra_pins_boto3() -> None:
|
||||
extras = _optional_dependencies()
|
||||
assert "bedrock" in extras
|
||||
assert any(req.startswith("boto3") for req in extras["bedrock"])
|
||||
45
tests/test_provider_dependencies.py
Normal file
45
tests/test_provider_dependencies.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Vertex AI and Bedrock support ship with every install, not as extras."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _pyproject() -> dict[str, object]:
|
||||
return tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def test_google_auth_is_a_regular_dependency() -> None:
|
||||
project = _pyproject()["project"]
|
||||
assert isinstance(project, dict)
|
||||
assert any(req.startswith("google-auth") for req in project["dependencies"])
|
||||
assert "optional-dependencies" not in project
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"module",
|
||||
[
|
||||
"google.auth",
|
||||
"google.auth.transport.requests",
|
||||
"google.oauth2.service_account",
|
||||
"boto3",
|
||||
"litellm.llms.vertex_ai.vertex_llm_base",
|
||||
"litellm.llms.bedrock.base_aws_llm",
|
||||
],
|
||||
)
|
||||
def test_provider_modules_import(module: str) -> None:
|
||||
importlib.import_module(module)
|
||||
|
||||
|
||||
def test_pyinstaller_spec_bundles_google_auth() -> None:
|
||||
spec = (ROOT / "strix.spec").read_text(encoding="utf-8")
|
||||
assert "collect_submodules('google.auth')" in spec
|
||||
assert "'google.auth'," not in spec
|
||||
assert "'google.oauth2'," not in spec
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
"""Tests for the provider import-error hint helper in interface/main.py."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from strix.interface.main import _provider_import_hint
|
||||
|
||||
|
||||
VERTEX_MODEL = "vertex_ai/gemini-3-pro-preview"
|
||||
BEDROCK_MODEL = "bedrock/anthropic.claude-4-5-sonnet"
|
||||
VERTEX_EXTRA_NAME = "vertex"
|
||||
BEDROCK_EXTRA_NAME = "bedrock"
|
||||
INSTALL_EXTRA_COMMAND_FRAGMENT = 'pipx install "strix-agent['
|
||||
WRAPPED_VERTEX_GOOGLE_ERROR = "litellm.APIConnectionError: No module named 'google'"
|
||||
WRAPPED_BEDROCK_BOTO3_ERROR = "litellm.APIConnectionError: No module named 'boto3'"
|
||||
|
||||
|
||||
def test_bedrock_boto3_hint() -> None:
|
||||
exc = ModuleNotFoundError("No module named 'boto3'")
|
||||
hint = _provider_import_hint(exc, BEDROCK_MODEL)
|
||||
assert hint is not None
|
||||
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
|
||||
assert BEDROCK_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_vertex_google_hint() -> None:
|
||||
exc = ImportError("No module named 'google'")
|
||||
hint = _provider_import_hint(exc, VERTEX_MODEL)
|
||||
assert hint is not None
|
||||
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
|
||||
assert VERTEX_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_vertex_google_hint_for_litellm_wrapped_connection_error() -> None:
|
||||
exc = ConnectionError(WRAPPED_VERTEX_GOOGLE_ERROR)
|
||||
hint = _provider_import_hint(exc, VERTEX_MODEL)
|
||||
assert hint is not None
|
||||
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
|
||||
assert VERTEX_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_bedrock_boto3_hint_for_litellm_wrapped_connection_error() -> None:
|
||||
exc = ConnectionError(WRAPPED_BEDROCK_BOTO3_ERROR)
|
||||
hint = _provider_import_hint(exc, BEDROCK_MODEL)
|
||||
assert hint is not None
|
||||
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
|
||||
assert BEDROCK_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_vertex_google_submodule_hint() -> None:
|
||||
exc = ModuleNotFoundError("No module named 'google.auth'")
|
||||
hint = _provider_import_hint(exc, VERTEX_MODEL)
|
||||
assert hint is not None
|
||||
assert INSTALL_EXTRA_COMMAND_FRAGMENT in hint
|
||||
assert VERTEX_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_vertex_google_hint_for_deeply_chained_error() -> None:
|
||||
root = ModuleNotFoundError("No module named 'google.auth'")
|
||||
middle = RuntimeError("provider init failed")
|
||||
middle.__cause__ = root
|
||||
exc = ConnectionError("litellm.APIConnectionError: request failed")
|
||||
exc.__cause__ = middle
|
||||
hint = _provider_import_hint(exc, VERTEX_MODEL)
|
||||
assert hint is not None
|
||||
assert VERTEX_EXTRA_NAME in hint
|
||||
|
||||
|
||||
def test_non_import_error_returns_none() -> None:
|
||||
assert _provider_import_hint(ConnectionError("boom"), "bedrock/whatever") is None
|
||||
|
||||
|
||||
def test_unrelated_provider_returns_none() -> None:
|
||||
exc = ImportError("No module named 'something'")
|
||||
assert _provider_import_hint(exc, "openai/gpt-4") is None
|
||||
15
uv.lock
generated
15
uv.lock
generated
|
|
@ -1,5 +1,5 @@
|
|||
version = 1
|
||||
revision = 3
|
||||
revision = 5
|
||||
requires-python = ">=3.12"
|
||||
resolution-markers = [
|
||||
"python_full_version >= '3.15'",
|
||||
|
|
@ -2393,6 +2393,7 @@ dependencies = [
|
|||
{ name = "cryptography" },
|
||||
{ name = "cvss" },
|
||||
{ name = "docker" },
|
||||
{ name = "google-auth" },
|
||||
{ name = "litellm" },
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "openai" },
|
||||
|
|
@ -2406,14 +2407,6 @@ dependencies = [
|
|||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.optional-dependencies]
|
||||
bedrock = [
|
||||
{ name = "boto3" },
|
||||
]
|
||||
vertex = [
|
||||
{ name = "google-auth" },
|
||||
]
|
||||
|
||||
[package.dev-dependencies]
|
||||
dev = [
|
||||
{ name = "bandit" },
|
||||
|
|
@ -2429,12 +2422,11 @@ dev = [
|
|||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "boto3", marker = "extra == 'bedrock'", specifier = ">=1.28.0" },
|
||||
{ name = "caido-sdk-client", specifier = ">=0.2.0" },
|
||||
{ name = "cryptography", specifier = ">=48.0.1,<49" },
|
||||
{ name = "cvss", specifier = ">=3.2" },
|
||||
{ name = "docker", specifier = ">=7.1.0" },
|
||||
{ name = "google-auth", marker = "extra == 'vertex'", specifier = ">=2.0.0" },
|
||||
{ name = "google-auth", specifier = ">=2.0.0" },
|
||||
{ name = "litellm", specifier = ">=1.101.0" },
|
||||
{ name = "markdown-it-py", specifier = ">=3.0.0" },
|
||||
{ name = "openai", specifier = ">=2.45.0,<3" },
|
||||
|
|
@ -2447,7 +2439,6 @@ requires-dist = [
|
|||
{ name = "requests", specifier = ">=2.32.0" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
provides-extras = ["vertex", "bedrock"]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
dev = [
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue