feat(llm-provider): add BYO-CLI + Cloud-Paid synthesis paths#168
feat(llm-provider): add BYO-CLI + Cloud-Paid synthesis paths#168
Conversation
Extend the unified LLMProvider abstraction to support three BYO modes:
* BYO-Key — Anthropic / OpenAI / generic OpenAI-compat (existing)
* BYO-CLI — subprocess-shell to claude / codex / gemini, reusing
the user's Max-plan OAuth or ChatGPT Plus subscription;
no API key required
* Cloud-Paid — POST to a Gradata-hosted relay; server holds the LLM
key, billing flows through the Gradata subscription
Changes:
- llm_provider.py: new CLIProvider, GradataCloudProvider,
GoogleGemmaProvider; per-instance circuit breaker (3 fails / 5 min);
centralized SSRF guard + sanitize hook in base; 'auto' factory
resolution (cli → anthropic → openai → cloud → gemma → None).
- meta_rules._try_llm_principle: routes through unified provider when
GRADATA_LLM_PROVIDER ∈ {cli, cloud, auto} is explicitly set.
Legacy GRADATA_LLM_KEY/BASE + GRADATA_GEMMA_API_KEY paths preserved
so existing tests (which patch internal helpers) stay green.
- tests/test_llm_provider.py: 9 new tests covering CLI happy path,
binary-missing degradation, circuit breaker, codex argv shape,
cloud happy path, 402 quota-exhausted, no-key, factory auto
resolution, unknown provider error.
Council verdict: /tmp/council-meta-rules.out
Investigation: /tmp/sdk-meta-rules-investigation.md
Enables Claude Pro / ChatGPT Plus subscribers to run meta-rule
synthesis without spending API credits, and Gradata Cloud customers
to offload synthesis to a managed relay.
Test plan: 44 existing tests (test_meta_rules + test_agentic_synthesis
+ test_llm_synthesis) + 5 test_rule_synthesizer + 9 new
test_llm_provider all green (58 total).
Layering: all changes inside Layer 1 (enhancements/). No imports from
cloud/. No new required dependencies.
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughSummary
WalkthroughThis PR refactors the LLM provider abstraction from simple implementations to a unified base class with centralized request control, including per-instance circuit-breaker logic and optional pre-prompt sanitization. Four new provider modes are added (CLI, cloud relay, Gemma, and auto-selection), existing providers are adapted to the new pattern, and meta-rule synthesis integrates the unified provider factory. ChangesUnified Provider Abstraction & Auto-Resolution
Sequence DiagramsequenceDiagram
participant Caller
participant LLMProvider
participant CircuitBreaker
participant _complete_impl
participant Backend
Caller->>LLMProvider: complete(prompt, sanitize=False)
LLMProvider->>CircuitBreaker: Is circuit open?
alt Circuit open (cooldown active)
CircuitBreaker-->>LLMProvider: Yes, return None
LLMProvider-->>Caller: None
else Circuit closed
CircuitBreaker-->>LLMProvider: No, proceed
LLMProvider->>LLMProvider: Optionally sanitize prompt
LLMProvider->>_complete_impl: _complete_impl(prompt, ...)
_complete_impl->>Backend: Invoke provider backend (CLI/HTTP/SDK)
alt Backend success
Backend-->>_complete_impl: Response text
_complete_impl-->>LLMProvider: text (validated)
LLMProvider->>CircuitBreaker: Record success (reset counter)
LLMProvider-->>Caller: text
else Backend failure
Backend-->>_complete_impl: Error/timeout/None
_complete_impl-->>LLMProvider: None
LLMProvider->>CircuitBreaker: Record failure (increment counter)
alt Threshold reached (3 failures)
CircuitBreaker->>CircuitBreaker: Open circuit for cooldown (60s)
end
LLMProvider-->>Caller: None
end
end
sequenceDiagram
participant Caller
participant get_provider
participant _resolve_auto
participant Environment
participant PathCheck
Caller->>get_provider: get_provider('auto', **kwargs)
get_provider->>_resolve_auto: Resolve 'auto' mode
_resolve_auto->>Environment: Check ANTHROPIC_API_KEY
_resolve_auto->>Environment: Check OPENAI_API_KEY
_resolve_auto->>Environment: Check GRADATA_API_KEY
_resolve_auto->>Environment: Check GOOGLE_API_KEY
_resolve_auto->>PathCheck: which claude / codex / gemini?
alt CLI found in PATH
PathCheck-->>_resolve_auto: Yes
_resolve_auto-->>get_provider: CLIProvider(...)
else Anthropic env var set
Environment-->>_resolve_auto: ANTHROPIC_API_KEY
_resolve_auto-->>get_provider: AnthropicProvider(...)
else OpenAI env var set
Environment-->>_resolve_auto: OPENAI_API_KEY
_resolve_auto-->>get_provider: OpenAIProvider(...)
else Gradata cloud env var set
Environment-->>_resolve_auto: GRADATA_API_KEY
_resolve_auto-->>get_provider: GradataCloudProvider(...)
else Gemma env var set
Environment-->>_resolve_auto: GOOGLE_API_KEY
_resolve_auto-->>get_provider: GoogleGemmaProvider(...)
else No match
_resolve_auto-->>get_provider: None
end
get_provider-->>Caller: Provider or None
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 OpenGrep (1.20.0)OpenGrep fatal error (exit code 2): �[32m✔�[39m �[1mOpengrep OSS�[0m �[1m Loading rules from local config...�[0m Review rate limit: 4/5 reviews remaining, refill in 12 minutes. Comment |
Summary
Extend the unified
LLMProviderabstraction with three BYO modes for meta-rule synthesis:claude/codex/gemini, reusing the user's Max-plan OAuth or ChatGPT Plus subscription. No API key required.This lets Claude Pro / ChatGPT Plus subscribers run meta-rule synthesis without spending API credits, and Gradata Cloud customers offload synthesis to a managed relay.
Changes
enhancements/llm_provider.pyCLIProvider,GradataCloudProvider,GoogleGemmaProvider.require_https()guard +sanitize_lesson_contentpre-prompt hook in the base class.autofactory resolution: cli → anthropic → openai → cloud → gemma → None.None(instead of raising) whenautofinds nothing — caller falls back to deterministic synthesis.enhancements/meta_rules.py:_try_llm_principleroutes through the unified provider whenGRADATA_LLM_PROVIDER ∈ {cli, cloud, auto}is explicitly set. LegacyGRADATA_LLM_KEY+GRADATA_LLM_BASEandGRADATA_GEMMA_API_KEYpaths preserved so existing tests (which patch internal helpers) stay green.tests/test_llm_provider.py: 9 new tests.Test plan
test_dualwrite_atomicity.pyfailures observed are pre-existing onmain(kill-9 + concurrency tests, unrelated to this PR).Layering check
All changes live inside Layer 1 (
enhancements/). No upward imports introduced. No imports fromcloud/. No new required dependencies —anthropicandopenaiSDKs remain optional.Risk
provider="anthropic"kw onllm_synthesize_rulesretained for back-compat.GRADATA_LLM_KEY+GRADATA_LLM_BASE→ Gemma) still fires.GRADATA_LLM_PROVIDER=cli|cloud|auto.