| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- """The model configuration bundle (Admin → Model page): the OpenAI
- endpoint every saddle assignment is sent to lives in the model_config
- row — environment variables no longer configure it (the harness's
- SADDLE_LLM_BASE_URL seeds the row once, on the app's first read; the
- page's save action owns it afterwards).
- """
- from harness import * # noqa: F401,F403 — the shared wire helpers
- def run(ctx):
- auth, fake, fake_id, fake_token = ctx.auth, ctx.fake, ctx.fake_id, ctx.fake_token
- print("== model configuration: the Admin → Model page ==")
- fake.ask_on_register = False
- fake.hold_state = True
- # The bootstrap seed: the app's first configuration read imported
- # the harness's SADDLE_LLM_BASE_URL environment into the row.
- model = slot_public(auth, "/models", "model")
- check(model.get("dispatch_ready") is True,
- "the page reports dispatch ready (the env bootstrap seeded the row)")
- check(model.get("base_url", "").startswith("http://"),
- "the seeded base URL is visible on the page")
- # The save action rewrites the row: the next assignment carries the
- # DATABASE's endpoint, not the environment's.
- task_id = create_queued(auth, {
- "project_id": "1", "title": "Model config round trip",
- "priority": "2"})
- set_model_config(base_url="http://127.0.0.1:9", model="db-model",
- api_key="db-key-12345678")
- ask_status, assignment = fake.ask_now()
- check(ask_status == 200 and int(assignment["task"]["id"]) == task_id,
- "the ask claims the task under the database configuration")
- llm = assignment.get("llm_config") or {}
- check(llm.get("base_url") == "http://127.0.0.1:9"
- and llm.get("model") == "db-model"
- and llm.get("api_key") == "db-key-12345678",
- "the assignment's llm_config carries the database endpoint")
- # close the round so the fake saddle is not left busy (a busy
- # saddle's ask 204s for its OWN open session, which would mask the
- # configuration gate the later checks isolate)
- fake.state = "done"
- fake.push_results("done", summary="# done",
- session_id=assignment.get("session_id"))
- wait_for(lambda: task_state(auth, task_id).get("status") == "done"
- or False, timeout=30, what="model config task done")
- # The page's own state reflects the row (the key is never echoed —
- # only that one is set plus a masked hint).
- model = slot_public(auth, "/models", "model")
- check(model.get("base_url") == "http://127.0.0.1:9"
- and model.get("model") == "db-model",
- "the page shows the saved endpoint fields")
- check(model.get("api_key_set") is True
- and "db-key-12345678" not in json.dumps(model),
- "the page never echoes the stored key (set flag + masked hint only)")
- check(model.get("api_key_hint", "").endswith("5678")
- and len(model.get("api_key_hint", "")) <= 5,
- "the masked hint carries only the key's tail")
- # A save with a BLANK api key keeps the stored one (the page's
- # write-only contract); a new base URL and model still land.
- model = slot_public(auth, "/models", "model")
- act(auth, model["save"], {
- "base_url": "http://127.0.0.1:10", "model": "db-model-2",
- "api_key": "", "advisor_base_url": "", "advisor_model": "",
- "advisor_api_key": ""})
- conn = db()
- row = conn.execute(
- "select base_url, model, api_key from model_config where id = 1").fetchone()
- conn.close()
- check(row is not None and row[0] == "http://127.0.0.1:10" and row[1] == "db-model-2",
- "the save action persists the new endpoint fields")
- check(row is not None and row[2] == "db-key-12345678",
- "a blank key field KEEPS the stored key")
- # The advisor override resolves independently: an advisor base URL
- # set through the page overrides, empty falls back to the main
- # fields (observable through the advisor-configured flags).
- set_model_config(advisor_base_url="http://127.0.0.1:11", advisor_model="adv")
- model = slot_public(auth, "/models", "model")
- check(model.get("advisor_base_url") == "http://127.0.0.1:11"
- and model.get("advisor_model") == "adv",
- "the advisor override shows on the page")
- set_model_config(advisor_base_url="")
- model = slot_public(auth, "/models", "model")
- check(model.get("advisor_base_url") == "",
- "clearing the advisor override falls back to the main endpoint")
- # Validation: a non-http base URL is refused and keeps the row.
- model = slot_public(auth, "/models", "model")
- directives = act(auth, model["save"], {
- "base_url": "ftp://nope", "model": "", "api_key": "",
- "advisor_base_url": "", "advisor_model": "", "advisor_api_key": ""})
- notified = json.dumps(directives)
- check("http://" in notified or "error" in notified,
- "a non-http base URL is rejected with a notice")
- conn = db()
- row = conn.execute("select base_url from model_config where id = 1").fetchone()
- conn.close()
- check(row is not None and row[0] == "http://127.0.0.1:10",
- "the rejected save left the stored configuration untouched")
- # Emptying the base URL pauses dispatch: a fresh queued task finds
- # no taker while no model endpoint is configured.
- set_model_config(base_url="")
- model = slot_public(auth, "/models", "model")
- check(model.get("dispatch_ready") is False,
- "the page reports dispatch paused with no base URL")
- paused_task = create_queued(auth, {
- "project_id": "1", "title": "Paused until an endpoint exists",
- "priority": "1"})
- ask_status, assignment = fake.ask_now()
- check(ask_status == 204,
- "the ask returns 204 while no model endpoint is configured")
- # ...and saving one through the PAGE lifts the pause.
- model = slot_public(auth, "/models", "model")
- act(auth, model["save"], {
- "base_url": "http://127.0.0.1:12", "model": "db-model-3",
- "api_key": "", "advisor_base_url": "", "advisor_model": "",
- "advisor_api_key": ""})
- ask_status, assignment = fake.ask_now()
- check(ask_status == 200 and int(assignment["task"]["id"]) == paused_task,
- "saving an endpoint through the page lifts the dispatch pause")
|