af33f6c338
Let AzureOpenAIBackend drive any OpenAI-compatible chat-completions server (DeepSeek, self-hosted vLLM/Ollama, ...) alongside native Azure deployments. - __init__ resolves the endpoint as: explicit arg > AZURE_OPENAI_ENDPOINT env > the built-in _AZURE_ENDPOINTS table (previously a non-Azure endpoint could not be supplied at all). - _get_client() builds a plain openai.OpenAI(base_url=...) client when AZURE_OPENAI_AUTH_MODE=openai_compatible, matching the auth mode already supported by the sibling skillopt/model/azure_openai.py. This avoids the AzureOpenAI SDK rewriting request URLs with Azure-only ?api-version= query params and deployment path segments, which non-Azure servers reject with 404. - _call() sends max_tokens + extra_body thinking flag for deepseek* models and records the last exception in self.last_call_error so a failed night is diagnosable instead of collapsing to a silent empty->0 score. Adds docs/sleep/openai-compatible-endpoints.md and sanitized example runner/watchdog scripts documenting an Antigravity + DeepSeek integration. The default managed-identity Azure path is unchanged.
108 lines
5.5 KiB
Markdown
108 lines
5.5 KiB
Markdown
# OpenAI-compatible endpoints for SkillOpt-Sleep (DeepSeek, local vLLM, …)
|
|
|
|
This document describes a small 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
|
|
|
|
Three focused changes in `AzureOpenAIBackend` (`skillopt_sleep/backend.py`),
|
|
all backward-compatible — the default managed-identity path is unchanged:
|
|
|
|
1. **Endpoint resolution honors `AZURE_OPENAI_ENDPOINT`.**
|
|
`__init__` now resolves the endpoint as `explicit arg` → `AZURE_OPENAI_ENDPOINT`
|
|
env → the built-in `_AZURE_ENDPOINTS` table. Previously a non-Azure endpoint
|
|
could not be supplied at all, so calls always went to a hardcoded
|
|
`*.openai.azure.com` host.
|
|
|
|
2. **`openai_compatible` auth mode.** When
|
|
`AZURE_OPENAI_AUTH_MODE=openai_compatible` (also accepts `compat`/`openai`),
|
|
`_get_client()` builds a plain `openai.OpenAI(base_url=…)` client instead of
|
|
an `AzureOpenAI` client. This mirrors the auth mode already supported by the
|
|
sibling `skillopt/model/azure_openai.py` module, so the two are consistent.
|
|
|
|
3. **DeepSeek request shape + error surfacing.** `_call()` sends `max_tokens`
|
|
plus `extra_body={"thinking": {"type": "enabled"}}` for `deepseek*`
|
|
deployments (the DeepSeek reasoning models expect this), and records the last
|
|
exception in `self.last_call_error` so a failed night is diagnosable instead
|
|
of collapsing to a silent empty response scored `0.0`.
|
|
|
|
### Why the previous behavior failed on non-Azure servers
|
|
|
|
The `AzureOpenAI` SDK client rewrites request URLs with Azure-only structure —
|
|
a `?api-version=…` query string and deployment path segments. A non-Azure
|
|
OpenAI-compatible server (DeepSeek, vLLM, …) does not recognize those routes and
|
|
responds `404 Resource not found`. SkillOpt-Sleep then receives an empty
|
|
response, the judge scores it `0.0`, and every night reports
|
|
`baseline 0.0 -> candidate 0.0` with no usable diagnostic. Selecting a plain
|
|
`OpenAI` client via `openai_compatible` mode avoids the Azure URL rewriting and
|
|
talks to the endpoint directly.
|
|
|
|
## How to use it
|
|
|
|
Set four environment variables and run the cycle with `--backend azure_openai`:
|
|
|
|
```bash
|
|
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
|
|
# optimizer/target dual-role overrides are also honored, e.g.
|
|
# OPTIMIZER_AZURE_OPENAI_AUTH_MODE / TARGET_AZURE_OPENAI_AUTH_MODE, etc.
|
|
|
|
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 and set a matching `--model`.
|
|
|
|
## End-to-end integration: Antigravity + DeepSeek
|
|
|
|
The [`examples/`](examples/) directory contains a sanitized reference of how this
|
|
was wired into the [Antigravity](https://antigravity.google/) agent IDE so the
|
|
sleep cycle runs unattended:
|
|
|
|
- **`examples/runner.py`** — a thin launcher that loads a provider key from an
|
|
`.env` file, exports the four variables above, and invokes `skillopt-sleep run`
|
|
with the DeepSeek backend. It also implements a `session-end` hook that appends
|
|
task-outcome metadata to a rollout-evidence log (wired to Antigravity's `Stop`
|
|
hook) 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). On Windows this is registered as a
|
|
Scheduled Task so it survives logout; on Linux/macOS a `systemd` timer 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_error` empty, client type `OpenAI`).
|
|
- 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.json` for that night reports
|
|
`"backend": "azure_openai"` with a non-empty token count and an empty
|
|
`call_error` — i.e. a genuine optimization night, versus the prior all-`0.0`
|
|
nights 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.
|
|
|
|
## 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](https://github.com/BerriAI/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.
|