Addresses maintainer review on the OpenAI-compatible endpoints PR: 1. CLI: accept --backend azure_openai in skillopt_sleep/__main__.py (the documented command was rejected by the argparse choices). 2. Example runner: exit with the child's return code so watchdog/supervisors see a failed sleep run as a failure. 3. Error state: clear last_call_error when a retry recovers; set an explicit "empty response on all N attempts" diagnostic when every attempt returns empty text. 4. Security guard: the managed-identity path now refuses to send an Azure AD bearer token to any endpoint outside *.openai.azure.com / *.cognitiveservices.azure.com — a custom endpoint requires explicit AZURE_OPENAI_AUTH_MODE=openai_compatible + API key. 5. Provider-neutral requests: compat mode sends only the standard contract (max_tokens, default 8192 via SKILLOPT_SLEEP_COMPAT_MAX_TOKENS); provider-specific body fields are opt-in via SKILLOPT_SLEEP_CHAT_EXTRA_BODY (JSON) — the deepseek model-name inference is removed. 6. Docs: removed the unimplemented OPTIMIZER_*/TARGET_* env-var claim; added a configuration reference matching the implementation exactly. 7. Tests: tests/test_azure_openai_compat.py — 17 deterministic no-network unittest cases covering CLI acceptance, compat-vs-Azure client selection, endpoint resolution, the credential guard, request kwargs (opt-in extra body / token cap), retry-success error clearing, empty-response diagnostics, and runner exit-code propagation. Re-verified live against DeepSeek (deepseek-v4-pro, openai_compatible mode) after the rework: client type OpenAI, completion returned, no error state.
7.0 KiB
OpenAI-compatible endpoints for SkillOpt-Sleep (DeepSeek, local vLLM, …)
This document describes an enhancement to the azure_openai backend in
skillopt_sleep/backend.py that lets SkillOpt-Sleep drive any
OpenAI-compatible chat-completions endpoint — for example DeepSeek's hosted
API or a self-hosted vLLM/Ollama server — in addition to native Azure OpenAI
deployments. It also documents a concrete end-to-end integration: running the
nightly sleep cycle inside the Antigravity IDE against DeepSeek.
What changed
All changes are backward-compatible — the default managed-identity Azure path is unchanged:
-
CLI acceptance.
skillopt-sleep run --backend azure_openaiis now an accepted choice inskillopt_sleep/__main__.py(it was previously rejected by argparse even thoughget_backend()understood the name). -
Endpoint resolution honors
AZURE_OPENAI_ENDPOINT.AzureOpenAIBackend.__init__resolves the endpoint asexplicit arg→AZURE_OPENAI_ENDPOINTenv → the built-in_AZURE_ENDPOINTStable. Previously a non-Azure endpoint could not be supplied at all. -
openai_compatibleauth mode. WhenAZURE_OPENAI_AUTH_MODE=openai_compatible(also acceptscompat/openai),_get_client()builds a plainopenai.OpenAI(base_url=…)client withAZURE_OPENAI_API_KEYinstead of anAzureOpenAIclient. This mirrors the auth mode already supported by the siblingskillopt/model/azure_openai.pymodule. (TheAzureOpenAIclient rewrites request URLs with Azure-only?api-version=…query params and deployment path segments, which non-Azure servers reject with404 Resource not found— the sleep cycle then scores every rollout0.0with no diagnostic.) -
Managed-identity credential guard. The managed-identity path attaches an Azure AD bearer token to every request. If a custom endpoint outside
*.openai.azure.com/*.cognitiveservices.azure.comis configured without explicit compat auth, the backend now raises a clearValueErrorinstead of sending Azure credentials to an arbitrary host. -
Provider-neutral request shape. In compat mode the backend sends only the standard OpenAI-compatible contract (
model,messages,max_tokens). Provider-specific request fields are opt-in via environment variables (below) — nothing is inferred from model-name substrings. -
Reliable error state.
_call()records the last exception inself.last_call_error(surfaced indiagnostics.json), clears it when a retry recovers, and sets an explicit"empty response on all N attempts"diagnostic when every attempt returns empty text.
Configuration reference
SkillOpt-Sleep's azure_openai backend reads these environment variables
(unprefixed only — the OPTIMIZER_*/TARGET_* dual-role variables belong to
the separate skillopt.model.azure_openai module and are not used by the
sleep cycle):
| Variable | Meaning |
|---|---|
AZURE_OPENAI_AUTH_MODE |
openai_compatible (or compat/openai) selects the plain OpenAI client. Unset/other = Azure managed identity (default). |
AZURE_OPENAI_ENDPOINT |
Base URL of the server, e.g. https://api.deepseek.com. |
AZURE_OPENAI_API_KEY |
API key sent by the compat client. |
SKILLOPT_SLEEP_COMPAT_MAX_TOKENS |
Optional int (default 8192): max_tokens sent in compat mode. |
SKILLOPT_SLEEP_CHAT_EXTRA_BODY |
Optional JSON object passed as extra_body for provider-specific fields. |
How to use it
export AZURE_OPENAI_AUTH_MODE=openai_compatible
export AZURE_OPENAI_ENDPOINT=https://api.deepseek.com # no /v1, no trailing path
export AZURE_OPENAI_API_KEY=sk-... # your provider key
# DeepSeek reasoning models: enable the thinking channel (opt-in, not inferred)
export SKILLOPT_SLEEP_CHAT_EXTRA_BODY='{"thinking": {"type": "enabled"}}'
export SKILLOPT_SLEEP_COMPAT_MAX_TOKENS=8192
skillopt-sleep run \
--backend azure_openai \
--model deepseek-v4-pro \
--project /path/to/your/project
The same pattern works for any OpenAI-compatible server — point
AZURE_OPENAI_ENDPOINT at it, set a matching --model, and omit
SKILLOPT_SLEEP_CHAT_EXTRA_BODY unless your provider needs extra request
fields.
End-to-end integration: Antigravity + DeepSeek
The examples/ directory contains a sanitized reference of how this
was wired into the Antigravity agent IDE so the
sleep cycle runs unattended:
examples/runner.py— a thin launcher that loads a provider key from an.envfile, exports the variables above, invokesskillopt-sleep runwith the DeepSeek backend, and exits with the child's return code so supervisors see failures as failures. It also implements asession-endhook that appends task-outcome metadata to a rollout-evidence log (wired to Antigravity'sStophook) so future nights have richer sessions to mine.examples/watchdog.py— a minimal supervisor loop that invokes the runner on a fixed interval (e.g. every 4 hours) and logs non-zero exits as failures. On Windows this is registered as a Scheduled Task so it survives logout; on Linux/macOS asystemdtimer or cron entry serves the same role.
Verified result
On a Windows 11 host, driving the cycle against deepseek-v4-pro in
openai_compatible mode:
- A direct backend smoke test returns a live completion (no
404,last_call_errorempty, client typeOpenAI). - A full nightly cycle mined tasks from real IDE sessions and the held-out
validation gate moved from
0.250 → 1.000, accepting a DeepSeek-authored skill edit (accept_new_best).diagnostics.jsonfor that night reports"backend": "azure_openai"with a non-empty token count and an emptycall_error— i.e. a genuine optimization night, versus the prior all-0.0nights that the endpoint bug produced. - A subsequent unattended night triggered by the watchdog completed the full
chain (watchdog → runner →
skillopt-sleep→ DeepSeek) and the gate correctly rejected a non-improving proposal (0.3 → 0.3), confirming the validation gate behaves normally on the new backend.
Deterministic no-network coverage for the new behavior lives in
tests/test_azure_openai_compat.py (CLI acceptance, client selection,
endpoint/auth guard, request kwargs, retry error-state, empty-response
diagnostics, and runner exit-code propagation).
A note on Gemini (optional, unverified fallback)
examples/runner.py also contains a fallback branch that, when only a Gemini key
is present, routes the claude CLI backend through a local
Anthropic-compatible proxy (e.g. LiteLLM on
http://127.0.0.1:4000) by setting ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY.
There is no native Gemini backend in SkillOpt, and this proxy path was not
independently validated in this work — it is included only as a configuration
example. The verified, supported path in this document is DeepSeek via
openai_compatible mode. Treat the Gemini branch as illustrative, not tested.