fix(runtime): ROUTER_BASE_URL и ключ читаются из .env (e-261001091912) + тест
systemd-юниты не экспортируют .env; на Hetzner-агентах base_url тихо уходил в дефолт 127.0.0.1:20128 (живой 9router только на эталонном хосте). _env_or_file(): env > .env > default. E2E на агенте 82: venv без openai (в cloud-init добавлен отдельно в coop).
This commit is contained in:
parent
053bab3e78
commit
4fe412eac3
2 changed files with 99 additions and 10 deletions
|
|
@ -11,20 +11,35 @@ from datetime import datetime, timezone
|
||||||
_HARNESS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
_HARNESS_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
|
|
||||||
|
def _env_or_file(name: str, default: str = "") -> str:
|
||||||
|
"""env → файл .env рядом с кодом → default.
|
||||||
|
|
||||||
|
systemd-юниты харнеса не экспортируют .env (только PYTHONUNBUFFERED,
|
||||||
|
t-261001091912-24602f): на агентских серверах .env читается построчно
|
||||||
|
отсюда. Env имеет приоритет (12-фактор), файл — оффлайн-фолбэк.
|
||||||
|
"""
|
||||||
|
val = os.environ.get(name, "")
|
||||||
|
if val:
|
||||||
|
return val
|
||||||
|
path = os.path.join(_HARNESS_DIR, ".env")
|
||||||
|
try:
|
||||||
|
with open(path) as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith(name + "="):
|
||||||
|
return line.split("=", 1)[1].strip()
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
def _router_key():
|
def _router_key():
|
||||||
env = os.environ.get("ROUTER_KEY", "")
|
return _env_or_file("ROUTER_KEY")
|
||||||
if not env:
|
|
||||||
path = os.path.join(_HARNESS_DIR, ".env")
|
|
||||||
if os.path.exists(path):
|
|
||||||
for line in open(path):
|
|
||||||
if line.startswith("ROUTER_KEY="):
|
|
||||||
env = line.split("=", 1)[1].strip()
|
|
||||||
break
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def _router_base_url():
|
def _router_base_url():
|
||||||
return os.environ.get("ROUTER_BASE_URL", "http://127.0.0.1:20128/v1")
|
# Фолбэк на .env обязателен для агентов вне эталонного хоста: дефолт
|
||||||
|
# 127.0.0.1:20128 живёт только там, где стоит 9router локально.
|
||||||
|
return _env_or_file("ROUTER_BASE_URL", "http://127.0.0.1:20128/v1")
|
||||||
|
|
||||||
|
|
||||||
def build_level_model(level_cfg):
|
def build_level_model(level_cfg):
|
||||||
|
|
|
||||||
74
tests/test_router_env_fallback.py
Normal file
74
tests/test_router_env_fallback.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""t-261001091912-24602f: ROUTER_BASE_URL/ключ берётся и из .env, не только из env.
|
||||||
|
|
||||||
|
Запуск: ./venv/bin/python tests/test_router_env_fallback.py
|
||||||
|
|
||||||
|
Баг в e2e: systemd-юниты харнеса не экспортируют .env, runtime.py читал
|
||||||
|
ROUTER_BASE_URL только из окружения → у свежеразвёрнутого агента
|
||||||
|
base_url тихо уходил в дефолт 127.0.0.1:20128 (на эталонном хосте так
|
||||||
|
жив 9router, на Hetzner-агенте — нет) и LLM-тики падали до первого
|
||||||
|
look в .env. Фикс: _env_or_file() — env имеет приоритет, .env рядом
|
||||||
|
с кодом — фолбэк (как раньше делал _router_key); EnvironmentFile в
|
||||||
|
юнитах cloud-init — страховка сверху.
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
sys.path.insert(0, "/root/simbiot-harness")
|
||||||
|
|
||||||
|
fails = []
|
||||||
|
|
||||||
|
|
||||||
|
def check(name, cond, extra=""):
|
||||||
|
print(("PASS" if cond else "FAIL"), name, ("| " + extra) if not cond else "")
|
||||||
|
if not cond:
|
||||||
|
fails.append(name)
|
||||||
|
|
||||||
|
|
||||||
|
from harness import runtime
|
||||||
|
|
||||||
|
# hermetic: окружение тест-процесса не должно подсказывать значения
|
||||||
|
for _v in ("ROUTER_BASE_URL", "ROUTER_KEY", "OPENAI_API_KEY", "OPENAI_BASE_URL"):
|
||||||
|
os.environ.pop(_v, None)
|
||||||
|
|
||||||
|
# временный "каталог харнеса" с фейковым .env — реальный не трогаем
|
||||||
|
tmpdir = tempfile.mkdtemp(prefix="simbiot-envtest-")
|
||||||
|
with open(os.path.join(tmpdir, ".env"), "w") as f:
|
||||||
|
f.write("ROUTER_BASE_URL=https://router.cil.by/v1\n")
|
||||||
|
f.write("ROUTER_KEY=sk-envtest-key\n")
|
||||||
|
f.write("# комментарий\n")
|
||||||
|
|
||||||
|
runtime._HARNESS_DIR = tmpdir
|
||||||
|
|
||||||
|
# 1. env отсутствует, .env есть → значение из .env (суть фикса)
|
||||||
|
check("1: base_url из .env quando env пуст",
|
||||||
|
runtime._router_base_url() == "https://router.cil.by/v1",
|
||||||
|
str(runtime._router_base_url()))
|
||||||
|
check("1: ключ из .env (прежнее поведение сохранено)",
|
||||||
|
runtime._router_key() == "sk-envtest-key")
|
||||||
|
|
||||||
|
# 2. env присутствует → env сильнее .env
|
||||||
|
os.environ["ROUTER_BASE_URL"] = "https://env.takes/priority/v1"
|
||||||
|
check("2: env приоритетнее .env",
|
||||||
|
runtime._router_base_url() == "https://env.takes/priority/v1")
|
||||||
|
os.environ.pop("ROUTER_BASE_URL")
|
||||||
|
|
||||||
|
# 3. нет ни env, ни .env → дефолт с локальным роутером
|
||||||
|
runtime._HARNESS_DIR = tempfile.mkdtemp(prefix="simbiot-envtest-empty-")
|
||||||
|
check("3: нет .env → дефолт 127.0.0.1:20128",
|
||||||
|
runtime._router_base_url() == "http://127.0.0.1:20128/v1",
|
||||||
|
str(runtime._router_base_url()))
|
||||||
|
|
||||||
|
# 4. setup_provider(): end-to-end маппинг .env → OPENAI_* env
|
||||||
|
runtime._HARNESS_DIR = tmpdir
|
||||||
|
runtime.setup_provider()
|
||||||
|
check("4: setup_provider кладёт OPENAI_BASE_URL из .env",
|
||||||
|
os.environ["OPENAI_BASE_URL"] == "https://router.cil.by/v1",
|
||||||
|
os.environ.get("OPENAI_BASE_URL", ""))
|
||||||
|
check("4: setup_provider кладёт OPENAI_API_KEY из .env",
|
||||||
|
os.environ.get("OPENAI_API_KEY") == "sk-envtest-key")
|
||||||
|
|
||||||
|
print()
|
||||||
|
print("FAILS:", len(fails), "| файл:", " | ".join(fails) if fails else "—")
|
||||||
|
sys.exit(1 if fails else 0)
|
||||||
Loading…
Reference in a new issue