GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing github.com — devstack/support/strixite-probe.py at dev · lpb-stack/devstack Expand file tree/ Copy pathstrixite-probe.pyMore file actions executable file ·212 lines (185 loc) · 9.84 KB / Copy pathstrixite-probe.pyFile metadata and controlsexecutable file ·212 lines (185 loc) · 9.84 KB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 #!/usr/bin/env python3 """Validate the strixite wire contract that pi's models.json entry assumes. pi talks to strixite natively (models.json, no plugin), so the provider's quirks live in MODEL_CAPABILITIES in scripts/localpibox/setup.py — pi's request shape is decided by pi, and only a live request proves those values are still right. Run this after a strixite upgrade or when the stack's defaults change; non-zero exit means the entry in the repo is now wrong. python3 support/strixite-probe.py --server http://127.0.0.1:8000 \ --model qwen3.8-flash-next --api-key strixite Checks (numbered in the output): 1 /v1/models reports the model and its context_length (pi's contextWindow) 2 reasoning_effort accepts minimal|low|medium|high|xhigh (none: check 4) 3 "max" is rejected — hence thinkingLevelMap {"max": "xhigh"} 4 reasoning_effort:"none" really stops thinking — hence {"off": "none"} 5 a level turns thinking on and returns reasoning_content 6 vision works — hence input: ["text","image"] 7 max_completion_tokens caps the response — pi's maxTokens is honoured 8 pi's extras (store, stream_options, include) are ignored, not 400s 9 /health answers with status+model (setup marks models loaded from it) 10 structured output refuses what pi must never send: response_format with tools, and tool_choice "required" (a harness that adds either breaks here) """ from __future__ import annotations import argparse import json import sys import urllib.error import urllib.request LEVELS_ON = ("minimal", "low", "medium", "high", "xhigh") LEVELS_REJECTED = ("max", "auto") # Red left half / blue right half, 2x2: a wrong answer can only mean no image. TEST_IMG_B64 = ( "iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAIAAAD91JpzAAAAEElEQVR42mP4zwAE" "/xkgFAAb8gP9PpddpAAAAABJRU5ErkJggg==") def post(url: str, payload: dict, api_key: str = "", timeout: float = 120.0): headers = {"Content-Type": "application/json"} if api_key: headers["Authorization"] = f"Bearer {api_key}" req = urllib.request.Request( url, data=json.dumps(payload).encode(), headers=headers, method="POST") try: with urllib.request.urlopen(req, timeout=timeout) as r: return r.status, json.loads(r.read().decode()) except urllib.error.HTTPError as e: body = e.read().decode() try: body = json.loads(body) except json.JSONDecodeError: pass return e.code, body def chat(base: str, model: str, key: str, extra: dict, prompt="Reply with OK.", max_tokens=64): # include is what surfaces the per-request diagnostics and the chain of # thought: "strix" adds the strix object (thinking / skipped / off + drop # reasons), "reasoning.content" the final reasoning_content on the message. return post(f"{base}/chat/completions", { "model": model, "messages": [{"role": "user", "content": prompt}], "max_completion_tokens": max_tokens, "include": ["strix", "reasoning.content"], **extra}, key) def content_parts(body): """(message, strix) of the first choice. strix is top-level on the response in the current build; older/other shapes put it on the message or choice.""" choice = ((body or {}).get("choices") or [{}])[0] msg = choice.get("message") or {} return msg, (msg.get("strix") or choice.get("strix") or (body or {}).get("strix") or {}) def main() -> int: ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--server", default="http://127.0.0.1:8000") ap.add_argument("--model", default="qwen3.8-flash-next") ap.add_argument("--api-key", default="strixite") args = ap.parse_args() base = args.server.rstrip("/") if not base.endswith("/v1"): base += "/v1" failures = [] def check(label: str, ok: bool, detail: str = ""): print(f" {'PASS' if ok else 'FAIL'} {label}" + (f" — {detail}" if detail else "")) if not ok: failures.append(label) # 1 — model list + context window try: with urllib.request.urlopen( urllib.request.Request(f"{base}/models", headers={ "Authorization": f"Bearer {args.api_key}"}), timeout=10) as r: listing = json.loads(r.read().decode()) except Exception as e: # noqa: BLE001 - the point is to report the failure print(f"FAIL server unreachable at {base}: {e}") return 1 entry = next((m for m in listing.get("data", []) if m.get("id") == args.model), None) check("1 /v1/models lists the model", entry is not None, f"ids={[m.get('id') for m in listing.get('data', [])]}") ctx = (entry or {}).get("context_length") check("1 context_length reported (pi contextWindow)", bool(ctx), f"={ctx}") # 2/3 — effort spellings for level in LEVELS_ON: code, body = chat(base, args.model, args.api_key, {"reasoning_effort": level}, "Reply with OK.") check(f"2 reasoning_effort '{level}' accepted", code == 200, "" if code == 200 else str(body)[:150]) for level in LEVELS_REJECTED: code, body = chat(base, args.model, args.api_key, {"reasoning_effort": level}) check(f"3 reasoning_effort '{level}' rejected (needs thinkingLevelMap)", code == 400, f"HTTP {code}") # 4 — the off spelling pi must send for thinking=off code, body = chat(base, args.model, args.api_key, {"reasoning_effort": "none"}, "What is 12*9? Answer with the number only.") msg, strix = content_parts(body if code == 200 else None) check("4 reasoning_effort 'none' -> thinking off", code == 200 and strix.get("thinking") == "off" and not (msg.get("reasoning_content") or ""), f"strix.thinking={strix.get('thinking')}") # 5 — thinking on, content delivered code, body = chat(base, args.model, args.api_key, {"reasoning_effort": "low"}, "What is 17*4? Answer with the number only.") msg, strix = content_parts(body if code == 200 else None) check("5 reasoning_effort 'low' -> thinking on + reasoning_content", code == 200 and strix.get("thinking") == "on" and bool(msg.get("reasoning_content")), f"strix.thinking={strix.get('thinking')} " f"reasoning_chars={len(msg.get('reasoning_content') or '')}") # 6 — vision code, body = post(f"{base}/chat/completions", { "model": args.model, "messages": [{"role": "user", "content": [ {"type": "text", "text": "What colour is the left half of this image? " "Answer with one word."}, {"type": "image_url", "image_url": {"url": f"data:image/png;base64,{TEST_IMG_B64}"}}]}], "max_completion_tokens": 32, "reasoning_effort": "none", "include": ["strix", "reasoning.content"]}, args.api_key) answer = "" if code == 200: answer = ((body.get("choices") or [{}])[0].get("message") or {}).get("content") or "" check("6 vision (image_url) works", code == 200 and "red" in answer.lower(), f"answer={answer.strip()[:40]!r}") # 7 — output cap honoured (pi's maxTokens) code, body = chat(base, args.model, args.api_key, {"reasoning_effort": "none"}, "Count from 1 to 500, one number per line.", max_tokens=16) choice = (body.get("choices") or [{}])[0] if code == 200 else {} fr = choice.get("finish_reason") check("7 max_completion_tokens caps output (length stop)", code == 200 and fr == "length", f"finish_reason={fr}") # 8 — pi sends these; strixite must ignore rather than reject code, body = chat(base, args.model, args.api_key, {"store": False, "include": ["reasoning.content"], "stream_options": {"include_usage": True}, "reasoning_effort": "none"}) check("8 pi extras (store/include/stream_options) ignored", code == 200, "" if code == 200 else str(body)[:150]) # 9 — health endpoint (setup's probe marks the served model loaded from it) try: with urllib.request.urlopen(f"{base[:-3]}/health", timeout=5) as r: health = json.loads(r.read().decode()) check("9 /health reports status + model", health.get("status") == "ok" and health.get("model") == args.model, f"status={health.get('status')} context_length={health.get('context_length')}") except Exception as e: # noqa: BLE001 check("9 /health reports status + model", False, str(e)[:120]) # 10 — structured-output refusals: pi's normal path (tools, no # response_format) is safe; these two combos 400 by design tools = [{"type": "function", "function": { "name": "noop", "description": "test tool", "parameters": {"type": "object", "properties": {}}}}] code, body = post(f"{base}/chat/completions", { "model": args.model, "messages": [{"role": "user", "content": "hi"}], "tools": tools, "max_completion_tokens": 16, "reasoning_effort": "none", "response_format": {"type": "json_object"}}, args.api_key) code2, body2 = post(f"{base}/chat/completions", { "model": args.model, "messages": [{"role": "user", "content": "hi"}], "tools": tools, "tool_choice": "required", "max_completion_tokens": 16, "reasoning_effort": "none"}, args.api_key) check("10 response_format+tools and tool_choice 'required' rejected (400)", code == 400 and code2 == 400, f"rf+tools HTTP {code}, tool_choice HTTP {code2}") print() if failures: print(f"FAIL — {len(failures)} check(s) drifted from MODEL_CAPABILITIES: " + ", ".join(failures)) return 1 print("PASS — strixite matches MODEL_CAPABILITIES (strixite entry in models.json)") return 0 if __name__ == "__main__": sys.exit(main()) |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |