Skip to content
Open
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,15 @@ export PERPLEXITY_API_KEY="your-api-key" # for search capabilities
export STRIX_REASONING_EFFORT="high" # control thinking effort (default: high, quick scan: medium)
```

Provider-native keys are also picked up automatically. For example, to use
[Groq's](https://console.groq.com/) fast inference you can set `GROQ_API_KEY`
directly instead of `LLM_API_KEY`:

```bash
export STRIX_LLM="groq/llama-3.3-70b-versatile"
export GROQ_API_KEY="your-api-key"
```

> [!NOTE]
> Strix automatically saves your configuration to `~/.strix/cli-config.json`, so you don't have to re-enter it on every run.

Expand Down
4 changes: 4 additions & 0 deletions strix/config/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ def configure_sdk_model_defaults(settings: Settings) -> None:
llm = settings.llm
set_tracing_disabled(True)
_configure_litellm_compatibility()
if llm.groq_api_key:
# LiteLLM reads GROQ_API_KEY natively; export it (without touching the
# generic api_key) so persisted config reaches the provider too.
os.environ.setdefault("GROQ_API_KEY", llm.groq_api_key)
if llm.api_key:
set_default_openai_key(llm.api_key, use_for_tracing=False)
_configure_litellm_default("api_key", llm.api_key)
Expand Down
4 changes: 4 additions & 0 deletions strix/config/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class LlmSettings(BaseSettings):
default=None,
validation_alias=AliasChoices("LLM_API_KEY", "OPENAI_API_KEY"),
)
# Provider-native key kept separate from the generic ``api_key`` so it is only
# ever exported as ``GROQ_API_KEY`` and never mistaken for another provider's
# credential (e.g. mirrored into ``OPENAI_API_KEY``).
groq_api_key: str | None = Field(default=None, alias="GROQ_API_KEY")
api_base: str | None = Field(
default=None,
validation_alias=AliasChoices(
Expand Down
24 changes: 24 additions & 0 deletions tests/test_config_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"STRIX_LLM",
"LLM_API_KEY",
"OPENAI_API_KEY",
"GROQ_API_KEY",
"LLM_API_BASE",
"OPENAI_API_BASE",
"OPENAI_BASE_URL",
Expand Down Expand Up @@ -150,6 +151,29 @@ def test_apply_config_override_invalidates_cache(tmp_path: Path) -> None:
assert loader.load_settings().llm.model == "second-model"


# --------------------------------------------------------------------------- #
# LLM provider keys
# --------------------------------------------------------------------------- #


def test_groq_api_key_populates_dedicated_field(monkeypatch: pytest.MonkeyPatch) -> None:
# GROQ_API_KEY must land on its own field, never the generic api_key, so it
# can't be mirrored into another provider's credential.
monkeypatch.setenv("GROQ_API_KEY", "gsk-test")
llm = loader.load_settings().llm
assert llm.groq_api_key == "gsk-test"
assert llm.api_key is None


def test_groq_and_openai_keys_do_not_collide(monkeypatch: pytest.MonkeyPatch) -> None:
# Both set: each key stays on its own field instead of one overriding the other.
monkeypatch.setenv("OPENAI_API_KEY", "sk-openai")
monkeypatch.setenv("GROQ_API_KEY", "gsk-test")
llm = loader.load_settings().llm
assert llm.api_key == "sk-openai"
assert llm.groq_api_key == "gsk-test"


# --------------------------------------------------------------------------- #
# persist_current
# --------------------------------------------------------------------------- #
Expand Down