1313SEMANTIC answer, not just ``/health`` — a role that is healthy but wrong FAILS
1414its probe. Throughput lives in ``lobes benchmark``; RUNTIME-only metrics
1515(latency/throughput/RTF, never correctness) live in ``lobes measure``.
16+
17+ Exit code: plain ``lobes assess`` / ``--preserve-thinking`` always exit
18+ ``EXIT_SUCCESS`` — they only emit a report. ``--probes`` is scriptable: it
19+ exits ``EXIT_SUCCESS`` when every probed role passes and ``EXIT_ENV_ERROR``
20+ when any role fails (mirrors ``lobes tunnel``'s status-driven exit code) —
21+ the ``--json``/text payload is unchanged either way, only the exit code
22+ differentiates pass from fail.
1623"""
1724
1825from __future__ import annotations
2128
2229from lobes import assess as _assess
2330from lobes .cli import _runtime_ops
31+ from lobes .cli ._errors import EXIT_ENV_ERROR , EXIT_SUCCESS
2432from lobes .cli ._output import emit_result
2533from lobes .roles import role_registry_from_env
2634from lobes .runtime import _compose , _env
2735
2836
37+ def _cmd_assess_probes (args : argparse .Namespace , url : str , json_mode : bool ) -> int :
38+ """``--probes``: per-role CORRECTNESS probes (issue #81, t7).
39+
40+ Each of cortex/embedder/reranker is resolved to its own endpoint via the
41+ shared role registry (same builder `lobes capabilities`/`lobes measure`
42+ use) rather than the single gateway `url`. Exits EXIT_SUCCESS when every
43+ probed role passes, EXIT_ENV_ERROR when any fails — the payload shape is
44+ identical either way.
45+ """
46+ env = _runtime_ops .deployment_env_soft (args )
47+ registry = role_registry_from_env (env , gateway_url = url )
48+ roles = (args .role ,) if getattr (args , "role" , None ) else _assess .PROBE_ROLES
49+ timeout = float (getattr (args , "timeout" , None ) or _assess .DEFAULT_PROBE_TIMEOUT )
50+ endpoints = {
51+ role : (info .endpoint , info .model ) if info .loaded and info .endpoint else None
52+ for role , info in registry .items ()
53+ }
54+ results = _assess .run_role_probes (endpoints , roles = roles , timeout = timeout )
55+ passed = all (r ["ok" ] for r in results .values ())
56+ if json_mode :
57+ emit_result ({"passed" : passed , "probes" : results }, json_mode = True )
58+ else :
59+ emit_result (_assess .render_role_probes (results ), json_mode = False )
60+ return EXIT_SUCCESS if passed else EXIT_ENV_ERROR
61+
62+
63+ def _cmd_assess_preserve_thinking (url : str , model : str | None , json_mode : bool ) -> int :
64+ """``--preserve-thinking``: the two-turn token-delta diagnostic (issue #93).
65+
66+ Reports both prompt-token counts and the delta; no host facts / correctness
67+ probes are needed.
68+ """
69+ pt = _assess .run_preserve_thinking_probe (url , model )
70+ emit_result (pt if json_mode else _assess .render_preserve_thinking (pt ), json_mode = json_mode )
71+ return EXIT_SUCCESS
72+
73+
74+ def _cmd_assess_correctness (
75+ args : argparse .Namespace , url : str , model : str | None , json_mode : bool
76+ ) -> int :
77+ """Default path: the two fixed correctness probes plus host-side facts."""
78+ result = _assess .run_correctness (url , model , check_tools = bool (getattr (args , "tools" , False )))
79+ host = {"image" : _compose .container_image (), "gpu_memory" : _compose .gpu_engine_mem ()}
80+ if json_mode :
81+ emit_result ({** result , "host" : host }, json_mode = True )
82+ else :
83+ header = (
84+ "### Host-side\n "
85+ f"- Image: `{ host ['image' ]} ` · GPU memory (EngineCore): { host ['gpu_memory' ]} \n "
86+ )
87+ emit_result (header + "\n " + _assess .render_correctness (result ), json_mode = False )
88+ return EXIT_SUCCESS
89+
90+
2991def cmd_assess (args : argparse .Namespace ) -> int :
3092 json_mode = bool (getattr (args , "json" , False ))
3193 port , deploy_dir = _runtime_ops .resolve_port_soft (args )
@@ -35,48 +97,11 @@ def cmd_assess(args: argparse.Namespace) -> int:
3597
3698 url = f"http://localhost:{ port } "
3799
38- # --probes is a standalone read-only diagnostic (issue #81, t7): per-role
39- # CORRECTNESS probes for cortex/embedder/reranker, each resolved to its own
40- # endpoint via the shared role registry (same builder `lobes capabilities`/
41- # `lobes measure` use) rather than the single `url` above. No host facts /
42- # single-model correctness probes are needed.
43100 if bool (getattr (args , "probes" , False )):
44- env = _runtime_ops .deployment_env_soft (args )
45- registry = role_registry_from_env (env , gateway_url = url )
46- roles = (args .role ,) if getattr (args , "role" , None ) else _assess .PROBE_ROLES
47- timeout = float (getattr (args , "timeout" , None ) or _assess .DEFAULT_PROBE_TIMEOUT )
48- endpoints = {
49- role : (info .endpoint , info .model ) if info .loaded and info .endpoint else None
50- for role , info in registry .items ()
51- }
52- results = _assess .run_role_probes (endpoints , roles = roles , timeout = timeout )
53- passed = all (r ["ok" ] for r in results .values ())
54- if json_mode :
55- emit_result ({"passed" : passed , "probes" : results }, json_mode = True )
56- else :
57- emit_result (_assess .render_role_probes (results ), json_mode = False )
58- return 0
59-
60- # --preserve-thinking is a standalone read-only diagnostic (issue #93): it
61- # runs the two-turn token-delta probe and reports both prompt-token counts +
62- # the delta. No host facts / correctness probes are needed.
101+ return _cmd_assess_probes (args , url , json_mode )
63102 if bool (getattr (args , "preserve_thinking" , False )):
64- pt = _assess .run_preserve_thinking_probe (url , model )
65- emit_result (pt if json_mode else _assess .render_preserve_thinking (pt ), json_mode = json_mode )
66- else :
67- result = _assess .run_correctness (
68- url , model , check_tools = bool (getattr (args , "tools" , False ))
69- )
70- host = {"image" : _compose .container_image (), "gpu_memory" : _compose .gpu_engine_mem ()}
71- if json_mode :
72- emit_result ({** result , "host" : host }, json_mode = True )
73- else :
74- header = (
75- "### Host-side\n "
76- f"- Image: `{ host ['image' ]} ` · GPU memory (EngineCore): { host ['gpu_memory' ]} \n "
77- )
78- emit_result (header + "\n " + _assess .render_correctness (result ), json_mode = False )
79- return 0
103+ return _cmd_assess_preserve_thinking (url , model , json_mode )
104+ return _cmd_assess_correctness (args , url , model , json_mode )
80105
81106
82107def register (sub : argparse ._SubParsersAction ) -> None :
0 commit comments