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.
101 lines
4.0 KiB
Python
101 lines
4.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Reference launcher for running SkillOpt-Sleep against an OpenAI-compatible
|
|
endpoint (DeepSeek shown here), plus an Antigravity `session-end` hook.
|
|
|
|
This is a *sanitized example*, not a supported entry point. Adapt the paths and
|
|
provider details to your environment. No API keys are hardcoded — the key is read
|
|
from an .env file or the process environment.
|
|
|
|
Usage:
|
|
python runner.py run # run a full sleep cycle against DeepSeek
|
|
python runner.py dry-run # harvest + replay, report only
|
|
python runner.py session-end # Antigravity Stop-hook: append rollout evidence
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import json
|
|
import subprocess
|
|
import datetime
|
|
from pathlib import Path
|
|
|
|
# --- Configure these for your environment -----------------------------------
|
|
# Path to a file containing your provider key as `sk-...` (kept out of source).
|
|
PROVIDER_ENV_FILE = Path(os.environ.get("SKILLOPT_PROVIDER_ENV_FILE", "provider.env"))
|
|
# Endpoint + model for the OpenAI-compatible provider.
|
|
PROVIDER_ENDPOINT = os.environ.get("SKILLOPT_PROVIDER_ENDPOINT", "https://api.deepseek.com")
|
|
PROVIDER_MODEL = os.environ.get("SKILLOPT_PROVIDER_MODEL", "deepseek-v4-pro")
|
|
# Project whose SKILL.md files the sleep cycle should evolve.
|
|
PROJECT_DIR = os.environ.get("SKILLOPT_PROJECT_DIR", os.getcwd())
|
|
# Where the session-end hook appends rollout evidence.
|
|
ROLLOUT_LOG = Path(os.environ.get("SKILLOPT_ROLLOUT_LOG", "brain/rollout-evidence.jsonl"))
|
|
# ----------------------------------------------------------------------------
|
|
|
|
|
|
def load_provider_key(env: dict) -> None:
|
|
"""Ensure DEEPSEEK_API_KEY is set, reading it from PROVIDER_ENV_FILE if needed."""
|
|
if env.get("DEEPSEEK_API_KEY"):
|
|
return
|
|
try:
|
|
text = PROVIDER_ENV_FILE.read_text(encoding="utf-8")
|
|
except OSError:
|
|
return
|
|
m = re.search(r"sk-[A-Za-z0-9]+", text)
|
|
if m:
|
|
env["DEEPSEEK_API_KEY"] = m.group(0)
|
|
|
|
|
|
def main() -> None:
|
|
if len(sys.argv) < 2:
|
|
print("Usage: runner.py [dry-run|run|status|adopt|session-end]")
|
|
sys.exit(1)
|
|
|
|
command = sys.argv[1]
|
|
|
|
# Antigravity Stop-hook: enrich future nights with task-outcome metadata.
|
|
if command == "session-end":
|
|
ROLLOUT_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
outcome = {
|
|
"timestamp": datetime.datetime.now().isoformat(),
|
|
"event": "SessionEnd",
|
|
"metadata": "Appended task outcome metadata",
|
|
}
|
|
with open(ROLLOUT_LOG, "a", encoding="utf-8") as f:
|
|
f.write(json.dumps(outcome) + "\n")
|
|
print("Rollout evidence metadata appended.")
|
|
return
|
|
|
|
env = os.environ.copy()
|
|
load_provider_key(env)
|
|
|
|
if env.get("DEEPSEEK_API_KEY"):
|
|
# OpenAI-compatible path — see docs/sleep/openai-compatible-endpoints.md
|
|
backend = "azure_openai"
|
|
env["PYTHONIOENCODING"] = "utf-8"
|
|
for prefix in ("", "OPTIMIZER_", "TARGET_"):
|
|
env[f"{prefix}AZURE_OPENAI_AUTH_MODE"] = "openai_compatible"
|
|
env[f"{prefix}AZURE_OPENAI_ENDPOINT"] = PROVIDER_ENDPOINT
|
|
env[f"{prefix}AZURE_OPENAI_API_KEY"] = env["DEEPSEEK_API_KEY"]
|
|
env["TARGET_DEPLOYMENT"] = PROVIDER_MODEL
|
|
env["OPTIMIZER_DEPLOYMENT"] = PROVIDER_MODEL
|
|
else:
|
|
# OPTIONAL, UNVERIFIED fallback: route the `claude` CLI backend through a
|
|
# local Anthropic-compatible proxy (e.g. LiteLLM) to reach Gemini. There
|
|
# is no native Gemini backend; this path was not validated. See the doc.
|
|
backend = "claude"
|
|
if "ANTHROPIC_API_KEY" not in env and "GEMINI_API_KEY" in env:
|
|
env["ANTHROPIC_API_KEY"] = env["GEMINI_API_KEY"]
|
|
env.setdefault("ANTHROPIC_BASE_URL", "http://127.0.0.1:4000")
|
|
|
|
args = ["skillopt-sleep", command]
|
|
if command in ("run", "dry-run"):
|
|
args = ["skillopt-sleep", command, "--backend", backend,
|
|
"--model", PROVIDER_MODEL, "--project", PROJECT_DIR]
|
|
|
|
print(f"Running: {' '.join(args)}")
|
|
subprocess.run(args, env=env, check=False)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|