6Lane conformance runner — the evidence behind a ``verification`` promotion.
10``python -m providers.openai_compat.conformance <lane> --live`` exercises
11one streamed chat round and one single-tool ReAct round against a lane's
12real endpoint and prints a verdict. A lane's catalogue ``verification``
13field is promoted from ``"chat"`` to ``"tools"`` only by a HUMAN who ran
14this and recorded the date in that row's ``notes`` (plan 215 P4) — the
15runner never edits the catalogue.
17Live network access requires the explicit ``--live`` flag. Without it the
18runner refuses to touch the network and exits non-zero, which is what
19makes it safe to exercise from the test suite: the suite drives it with an
20injected mock client and never opens a socket.
24- :func:`run_conformance` — the two rounds against an injected client.
25- :func:`build_live_client` — construct a real client for a lane, or fail
26 with the env-var NAME the operator must set.
27- :func:`format_report` — render a :class:`ConformanceResult` as ASCII.
28- :func:`main` — argv parsing, the ``--live`` gate, and the exit code.
40- Without ``--live`` the runner NEVER constructs a network client and
41 exits 2. This is a refusal, not a silent skip.
42- ``--live`` with an unset key env var exits 2 naming the variable; it
43 never falls back to another lane's key.
44- Exit codes: 0 both rounds passed; 1 a round failed; 2 refused to run
45 (no ``--live``, unknown lane, or missing key).
46- The runner reports ONLY what it observed. A tools round that produced
47 no tool call is reported as a FAIL with the reason, never softened into
48 a pass, and the runner never writes the catalogue.
49- No key value is printed, logged, or included in any report line.
53- oc_diagnostics (structured debug logging via _dbg)
54- providers.openai_compat.catalogue (LANES, Lane and lane_by_name to find the lane under test)
55- providers.openai_compat.client (lazy; OpenAICompatClient built for a live run)
57from __future__
import annotations
63from dataclasses
import dataclass, field
64from typing
import Any, Sequence
66from oc_diagnostics
import _dbg
68from .catalogue
import LANES, Lane, lane_by_name
76 "name":
"get_current_temperature",
77 "description":
"Return the current temperature for a named city.",
81 "city": {
"type":
"string",
"description":
"City name, e.g. 'Rotterdam'"},
88_CHAT_PROMPT =
"Reply with exactly the word: pong"
89_TOOLS_PROMPT =
"What is the current temperature in Rotterdam? Use the tool."
94 """Outcome of one conformance round."""
99 chunks: list[str] = field(default_factory=list)
104 """Outcome of a full lane conformance run."""
107 rounds: list[RoundResult] = field(default_factory=list)
111 """True only when every round passed."""
112 return bool(self.
rounds)
and all(r.passed
for r
in self.
rounds)
116 """``"tools"`` when both rounds passed, else ``"chat"`` or ``"none"``."""
117 by_name = {r.name: r.passed
for r
in self.
rounds}
118 if by_name.get(
"chat")
and by_name.get(
"tools"):
120 if by_name.get(
"chat"):
126 """One streamed chat round: assert content arrived."""
128 messages = [{
"role":
"user",
"content": _CHAT_PROMPT}]
129 text_parts: list[str] = []
130 kinds: list[str] = []
132 for kind, data
in client.stream_chat(messages, model):
134 if kind ==
"content":
135 text_parts.append(str(data))
136 except Exception
as exc:
138 _dbg(
"OC_CHAT.CLIENT", func,
139 f
"EXTERNAL_ERROR: {type(exc).__name__}: {exc}", 2)
140 return RoundResult(
"chat",
False, f
"{type(exc).__name__}: {exc}", kinds)
142 text =
"".join(text_parts).strip()
144 return RoundResult(
"chat",
False,
"stream yielded no content chunk", kinds)
145 preview = text
if len(text) <= 60
else text[:57] +
"..."
146 return RoundResult(
"chat",
True, f
"content received: {preview!r}", kinds)
150 """One single-tool ReAct round: assert a well-formed tool call arrived."""
151 func =
"_tools_round"
152 messages = [{
"role":
"user",
"content": _TOOLS_PROMPT}]
153 calls: list[dict] = []
154 kinds: list[str] = []
156 for kind, data
in client.stream_chat(messages, model, tools=[PROBE_TOOL]):
158 if kind ==
"tool_call" and isinstance(data, dict):
160 except Exception
as exc:
162 _dbg(
"OC_CHAT.CLIENT", func,
163 f
"EXTERNAL_ERROR: {type(exc).__name__}: {exc}", 2)
164 return RoundResult(
"tools",
False, f
"{type(exc).__name__}: {exc}", kinds)
169 "no tool_call chunk (the lane answered in prose or ignored the tool)",
173 if call.get(
"name") != PROBE_TOOL[
"function"][
"name"]:
176 f
"tool call named {call.get('name')!r}, expected "
177 f
"{PROBE_TOOL['function']['name']!r}",
181 args = json.loads(call.get(
"arguments")
or "{}")
182 except ValueError
as exc:
184 _dbg(
"OC_CHAT.CLIENT", func,
185 f
"VALIDATION_ERROR: {type(exc).__name__}: {exc}", 3)
188 f
"tool call arguments are not valid JSON: {exc}",
191 if not isinstance(args, dict)
or "city" not in args:
194 f
"tool call arguments missing the required 'city' key: {args!r}",
197 return RoundResult(
"tools",
True, f
"tool call well-formed: {args!r}", kinds)
200def run_conformance(client: Any, lane: Lane, model: str |
None =
None) -> ConformanceResult:
201 """Run both rounds for *lane* against an already-built *client*.
203 The client is INJECTED so the test suite can drive this with a mock and
204 stay off the network entirely (plan 215 W5).
206 func =
"run_conformance"
207 target = model
or lane.default_model
208 _dbg(
"OC_CHAT.CLIENT", func, f
"lane={lane.name} model={target}", 2)
216 """Construct a real client for *lane*, or raise :exc:`SystemExit(2)`.
218 Reads the key from the env-var NAME the catalogue row advertises. The
219 key value is never logged, echoed, or stored anywhere in this module.
221 func =
"build_live_client"
222 from .client
import OpenAICompatClient
224 api_key = os.environ.get(lane.api_key_env_var,
"").strip()
227 f
"ERROR: {lane.api_key_env_var} is not set; cannot run lane "
228 f
"{lane.name!r} live.",
232 base_url = os.environ.get(lane.base_url_env_var,
"").strip()
or lane.default_base_url
233 _dbg(
"OC_CHAT.CLIENT", func, f
"lane={lane.name} base_url={base_url}", 2)
237 extra_headers=lane.extra_headers,
243 """Render *result* as an ASCII report block."""
246 f
" Lane conformance: {lane.name} ({lane.display_label})",
249 f
" base URL : {lane.default_base_url}",
250 f
" catalogue : free={str(lane.free).lower()} verification={lane.verification}",
253 for round_result
in result.rounds:
254 status =
"PASS" if round_result.passed
else "FAIL"
255 lines.append(f
" [{status}] {round_result.name:<6} {round_result.detail}")
257 lines.append(f
" observed verification tier: {result.verdict}")
258 if result.verdict ==
"tools" and lane.verification !=
"tools":
260 " NOTE: this run supports promoting the catalogue row to "
261 "verification=\"tools\"."
264 " A human makes that edit and dates it in the row's notes; "
265 "this runner never writes the catalogue."
267 lines.append(
"=" * 66)
268 return "\n".join(lines)
272 """Build the runner's argument parser."""
273 parser = argparse.ArgumentParser(
274 prog=
"python -m providers.openai_compat.conformance",
276 "Exercise one streamed chat round and one single-tool ReAct round "
277 "against a lane's live endpoint. Requires --live; without it the "
278 "runner refuses to touch the network."
281 parser.add_argument(
"lane", help=f
"lane name ({', '.join(l.name for l in LANES)})")
285 help=
"REQUIRED to make real API calls. Costs money on a paid lane.",
290 help=
"model id to probe (default: the lane's 'default' tier)",
295def main(argv: Sequence[str] |
None =
None) -> int:
296 """Entry point. Returns the process exit code (0 pass, 1 fail, 2 refused)."""
301 lane = lane_by_name(args.lane)
302 except KeyError
as exc:
304 _dbg(
"OC_CHAT.CLIENT", func, f
"VALIDATION_ERROR: {exc}", 3)
305 print(f
"ERROR: {exc}", file=sys.stderr)
311 "ERROR: refusing to run without --live.\n"
312 " This runner makes REAL API calls against the lane's endpoint and "
313 "costs money on a paid lane.\n"
314 f
" Re-run with: python -m providers.openai_compat.conformance "
315 f
"{lane.name} --live",
321 model = args.model
or lane.default_model
324 return 0
if result.passed
else 1
327if __name__ ==
"__main__":
328 raise SystemExit(
main())