mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-19 18:31:13 +00:00
detect_backend() picks a cloud provider purely from whichever API key is set in the environment, and community labeling additionally falls back to claude -p when the CLI is on PATH. Keys in a developer environment are often there for other tools, so a bare `graphify extract .` could send uncached documents to a provider the user did not intend. Add explicit control without breaking existing auto-detect flows: - GRAPHIFY_BACKEND=<name> selects a backend explicitly and wins over ambient keys; GRAPHIFY_BACKEND=none disables every LLM backend, including the label claude -p fallback. `graphify extract` then keeps the local AST pass and reports what it held back instead of failing or auto-selecting a provider. - Auto-detection now reports the exact environment variable that selected the backend, so sending a corpus to a cloud provider is never a surprise. - The claude -p labeling fallback is announced, and GRAPHIFY_BACKEND=none opts out of it (it is otherwise kept: silently downgrading good labels to "Community N" was the reason it exists). - extract_files_direct raises a clear error naming GRAPHIFY_BACKEND when the library caller would otherwise auto-detect a disabled backend. README data-residency note updated. detect_backend_with_source() now carries the detection logic and names its source; detect_backend() delegates to it. Note: this deliberately keeps auto-detection ON by default (the issue's alternative #2) rather than flipping cloud use to opt-in, which would break every existing key-less `graphify extract` flow. GRAPHIFY_BACKEND makes the opt-in switch a one-liner for a follow-up if maintainers prefer it.
79 lines
3.0 KiB
Python
79 lines
3.0 KiB
Python
"""Explicit backend selection + disable switch for ambient-key auto-detection (#4361).
|
|
|
|
`detect_backend()` picks a cloud provider purely from whichever API key is set
|
|
in the environment, and community labeling additionally falls back to `claude -p`
|
|
when the CLI is on PATH. Keys in a developer environment are often there for
|
|
other tools, so a bare `graphify extract .` could send uncached documents to a
|
|
provider the user did not intend.
|
|
|
|
These tests pin the additions that put the user back in control:
|
|
|
|
- `GRAPHIFY_BACKEND=<name>` selects a backend explicitly, overriding ambient keys;
|
|
- `GRAPHIFY_BACKEND=none` disables every LLM backend, including the `claude -p`
|
|
fallback, so the AST pass keeps working;
|
|
- auto-detection reports the exact environment variable that selected it.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from graphify import llm
|
|
|
|
DISABLE_SENTINELS = ["none", "off", "disable", "disabled"]
|
|
|
|
|
|
def test_detect_backend_with_source_names_the_ambient_variable(monkeypatch):
|
|
monkeypatch.setenv("GEMINI_API_KEY", "ambient")
|
|
backend, source = llm.detect_backend_with_source()
|
|
assert backend == "gemini"
|
|
assert source == "auto-detected from GEMINI_API_KEY"
|
|
assert llm.detect_backend() == "gemini"
|
|
|
|
|
|
def test_graphify_backend_selects_explicitly(monkeypatch):
|
|
monkeypatch.setenv("GEMINI_API_KEY", "ambient")
|
|
monkeypatch.setenv("GRAPHIFY_BACKEND", "openai")
|
|
backend, source = llm.detect_backend_with_source()
|
|
assert backend == "openai", "GRAPHIFY_BACKEND must win over an ambient key"
|
|
assert source == "GRAPHIFY_BACKEND=openai"
|
|
|
|
|
|
@pytest.mark.parametrize("sentinel", DISABLE_SENTINELS)
|
|
def test_graphify_backend_none_disables(monkeypatch, sentinel):
|
|
monkeypatch.setenv("GEMINI_API_KEY", "ambient")
|
|
monkeypatch.setenv("GRAPHIFY_BACKEND", sentinel)
|
|
assert llm.detect_backend() is None
|
|
assert llm.backend_disabled_by_env() is True
|
|
_, source = llm.detect_backend_with_source()
|
|
assert source == f"GRAPHIFY_BACKEND={sentinel}"
|
|
|
|
|
|
def test_backend_not_disabled_when_unset(monkeypatch):
|
|
assert llm.backend_disabled_by_env() is False
|
|
|
|
|
|
def test_extract_files_direct_refuses_cleanly_when_disabled(monkeypatch):
|
|
monkeypatch.setenv("GRAPHIFY_BACKEND", "none")
|
|
with pytest.raises(ValueError, match="GRAPHIFY_BACKEND=none"):
|
|
llm.extract_files_direct([], backend=None)
|
|
|
|
|
|
def test_generate_community_labels_skips_claude_cli_when_disabled(monkeypatch):
|
|
monkeypatch.setenv("GRAPHIFY_BACKEND", "none")
|
|
monkeypatch.setattr(llm, "_claude_cli_available", lambda: True)
|
|
|
|
calls = {"n": 0}
|
|
|
|
def _spy(*args, **kwargs): # pragma: no cover - must not run
|
|
calls["n"] += 1
|
|
return {}
|
|
|
|
monkeypatch.setattr(llm, "label_communities", _spy)
|
|
|
|
labels, source = llm.generate_community_labels(
|
|
None, {0: ["a"], 1: ["b"]}, quiet=True
|
|
)
|
|
assert source == "placeholder"
|
|
assert labels == {0: "Community 0", 1: "Community 1"}
|
|
assert calls["n"] == 0, "no backend may be contacted when GRAPHIFY_BACKEND=none"
|