@miller-tech/uap 1.85.0 → 1.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.85.0",
3
+ "version": "1.86.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8003,38 +8003,68 @@ def openai_to_anthropic_response(
8003
8003
  }
8004
8004
 
8005
8005
 
8006
- async def _maybe_confidence_escalate(anthropic_resp, anthropic_body, client):
8007
- """Confidence recipe: if the cheap primary answer scores below threshold,
8008
- escalate the same (non-tool) turn to the stronger backend and return its
8009
- answer. Default OFF; fails open (returns the original) on any problem."""
8006
+ async def _maybe_apply_recipe(anthropic_resp, anthropic_body, openai_body, client):
8007
+ """Serving-layer recipe runtime (confidence / fusion, signal-selected).
8008
+ Default OFF; fails open. call_primary re-runs the cheap primary (llama) for
8009
+ fusion breadth; call_judge talks to the stronger escalation backend."""
8010
8010
  if _ce is None or not isinstance(anthropic_resp, dict):
8011
8011
  return anthropic_resp
8012
8012
  try:
8013
8013
  settings = _ce.Settings.from_env()
8014
- text = _ce.extract_text(anthropic_resp)
8015
- if not _ce.should_escalate(text, settings, _has_tool_definitions(anthropic_body)):
8014
+ if not settings.enabled:
8016
8015
  return anthropic_resp
8017
- payload = _ce.build_escalation_payload(anthropic_body, settings)
8018
- url = settings.endpoint.rstrip("/") + "/v1/messages"
8019
- headers = {
8020
- "Content-Type": "application/json",
8021
- "x-api-key": settings.api_key,
8022
- "anthropic-version": "2023-06-01",
8023
- }
8024
- resp = await client.post(url, json=payload, headers=headers, timeout=120.0)
8025
- if resp.status_code == 200:
8016
+ model_name = (openai_body or {}).get("model", "default")
8017
+
8018
+ async def call_primary(openai_variant):
8019
+ try:
8020
+ r = await _post_with_generation_timeout(
8021
+ client,
8022
+ f"{LLAMA_CPP_BASE}/chat/completions",
8023
+ openai_variant,
8024
+ {"Content-Type": "application/json"},
8025
+ )
8026
+ if getattr(r, "status_code", 0) != 200:
8027
+ return None
8028
+ return openai_to_anthropic_response(r.json(), model_name)
8029
+ except Exception:
8030
+ return None
8031
+
8032
+ async def call_judge(anthropic_payload):
8033
+ try:
8034
+ url = settings.endpoint.rstrip("/") + "/v1/messages"
8035
+ r = await client.post(
8036
+ url,
8037
+ json=anthropic_payload,
8038
+ headers={
8039
+ "Content-Type": "application/json",
8040
+ "x-api-key": settings.api_key,
8041
+ "anthropic-version": "2023-06-01",
8042
+ },
8043
+ timeout=120.0,
8044
+ )
8045
+ return r.json() if getattr(r, "status_code", 0) == 200 else None
8046
+ except Exception:
8047
+ return None
8048
+
8049
+ result = await _ce.apply_recipe(
8050
+ anthropic_resp,
8051
+ anthropic_body,
8052
+ openai_body,
8053
+ settings,
8054
+ _has_tool_definitions(anthropic_body),
8055
+ call_primary,
8056
+ call_judge,
8057
+ )
8058
+ if isinstance(result, dict) and result is not anthropic_resp:
8026
8059
  logger.warning(
8027
- "CONFIDENCE ESCALATION: primary confidence below %.2f -> escalated to %s",
8028
- settings.threshold, settings.model,
8060
+ "RECIPE applied: recipe=%s signal=%s -> response changed",
8061
+ _ce.select_recipe(anthropic_body, settings, _has_tool_definitions(anthropic_body)),
8062
+ settings.signal,
8029
8063
  )
8030
- return resp.json()
8031
- logger.warning(
8032
- "CONFIDENCE ESCALATION: backend %s returned %d; keeping primary",
8033
- settings.model, resp.status_code,
8034
- )
8064
+ return result if isinstance(result, dict) else anthropic_resp
8035
8065
  except Exception as exc:
8036
- logger.warning("CONFIDENCE ESCALATION: failed (%s); keeping primary", exc)
8037
- return anthropic_resp
8066
+ logger.warning("RECIPE: failed (%s); keeping primary answer", exc)
8067
+ return anthropic_resp
8038
8068
 
8039
8069
 
8040
8070
  async def _heartbeat_then_buffered(produce_coro, model: str):
@@ -9002,7 +9032,7 @@ async def messages(request: Request):
9002
9032
  "REQUIRED TOOL STREAM GUARDRAIL: served stream response via guarded non-stream path"
9003
9033
  )
9004
9034
 
9005
- anthropic_resp = await _maybe_confidence_escalate(anthropic_resp, body, client)
9035
+ anthropic_resp = await _maybe_apply_recipe(anthropic_resp, body, openai_body, client)
9006
9036
  return anthropic_resp
9007
9037
 
9008
9038
  if PROXY_STREAM_HEARTBEAT_SECS > 0:
@@ -1,26 +1,32 @@
1
- """Confidence-escalation looper (vLLM Semantic Router "Confidence" recipe).
2
-
3
- Try the cheap primary model first; if a confidence signal on its answer is below
4
- threshold, escalate the SAME request to a stronger backend and return that
5
- answer instead. Bounded (one escalation), default OFF, fails open. Scoped to
6
- non-tool (single-answer) turns escalating a mid-loop tool turn would need the
7
- stronger model to share the whole tool/context state, which is out of scope for
8
- this primitive.
9
-
10
- The confidence signal is intentionally pluggable. This module ships a cheap
11
- text heuristic; a stronger signal (token logprob margin, or UAP's real
12
- execution/acceptance GATE-pass the differentiator vs logprob) can replace
13
- ``text_confidence`` without touching the proxy hook.
14
-
15
- All config is env-driven and OFF by default:
16
- PROXY_CONFIDENCE_ESCALATE=on enable the looper
17
- PROXY_CONFIDENCE_THRESHOLD=0.5 escalate when confidence < this
18
- PROXY_ESCALATE_MODEL=<id> model id sent to the escalation backend
19
- PROXY_ESCALATE_ENDPOINT=<url> Anthropic-compatible /v1/messages base
20
- PROXY_ESCALATE_API_KEY=<key> x-api-key for the escalation backend
1
+ """Serving-layer recipe runtime (vLLM Semantic Router micro-agent patterns).
2
+
3
+ One bounded micro-agent loop behind one model API. Recipes:
4
+ - single : no collaboration (default).
5
+ - confidence : try the cheap primary; if a confidence signal is below
6
+ threshold, escalate the same single-answer turn to a stronger
7
+ backend (#1). The signal is pluggable: "heuristic" (cheap text)
8
+ or "selfverify" (a judge model rates the answer 0-10 -- the
9
+ gate-as-confidence idea at the serving layer, #5).
10
+ - fusion : run N breadth candidates from the primary, then a judge picks
11
+ the best; falls back to the primary on judge failure (#3).
12
+ A signal-driven selector (#2) chooses the recipe per request.
13
+
14
+ All orchestration is via INJECTED async callables (call_primary, call_judge) so
15
+ it is testable without a live backend. The proxy supplies real ones. Everything
16
+ is OFF by default and fails open.
17
+
18
+ Env:
19
+ PROXY_CONFIDENCE_ESCALATE=on master switch
20
+ PROXY_RECIPE=auto|single|confidence|fusion (default auto)
21
+ PROXY_CONFIDENCE_SIGNAL=heuristic|selfverify (default heuristic)
22
+ PROXY_CONFIDENCE_THRESHOLD=0.5
23
+ PROXY_FUSION_N=3 candidates incl. the primary (2..6)
24
+ PROXY_AUTO_FUSION_CHARS=600 auto: prompts longer than this -> fusion
25
+ PROXY_ESCALATE_MODEL / _ENDPOINT / _API_KEY stronger backend (judge/escalate)
21
26
  """
22
27
  from __future__ import annotations
23
28
 
29
+ import asyncio
24
30
  import os
25
31
  import re
26
32
  from dataclasses import dataclass
@@ -29,23 +35,39 @@ from dataclasses import dataclass
29
35
  @dataclass
30
36
  class Settings:
31
37
  enabled: bool
38
+ recipe: str
39
+ signal: str
32
40
  threshold: float
41
+ fusion_n: int
42
+ auto_fusion_chars: int
33
43
  model: str
34
44
  endpoint: str
35
45
  api_key: str
36
46
 
37
47
  @classmethod
38
48
  def from_env(cls) -> "Settings":
39
- on = os.environ.get("PROXY_CONFIDENCE_ESCALATE", "off").lower() not in {
40
- "", "0", "off", "false", "no",
41
- }
42
- try:
43
- thr = float(os.environ.get("PROXY_CONFIDENCE_THRESHOLD", "0.5"))
44
- except ValueError:
45
- thr = 0.5
49
+ def flag(name, default="off"):
50
+ return os.environ.get(name, default).lower() not in {"", "0", "off", "false", "no"}
51
+
52
+ def num(name, default, cast=float):
53
+ try:
54
+ return cast(os.environ.get(name, str(default)))
55
+ except (ValueError, TypeError):
56
+ return default
57
+
58
+ recipe = os.environ.get("PROXY_RECIPE", "auto").lower()
59
+ if recipe not in {"auto", "single", "confidence", "fusion"}:
60
+ recipe = "auto"
61
+ signal = os.environ.get("PROXY_CONFIDENCE_SIGNAL", "heuristic").lower()
62
+ if signal not in {"heuristic", "selfverify"}:
63
+ signal = "heuristic"
46
64
  return cls(
47
- enabled=on,
48
- threshold=thr,
65
+ enabled=flag("PROXY_CONFIDENCE_ESCALATE"),
66
+ recipe=recipe,
67
+ signal=signal,
68
+ threshold=num("PROXY_CONFIDENCE_THRESHOLD", 0.5, float),
69
+ fusion_n=max(2, min(6, num("PROXY_FUSION_N", 3, int))),
70
+ auto_fusion_chars=num("PROXY_AUTO_FUSION_CHARS", 600, int),
49
71
  model=os.environ.get("PROXY_ESCALATE_MODEL", ""),
50
72
  endpoint=os.environ.get("PROXY_ESCALATE_ENDPOINT", ""),
51
73
  api_key=os.environ.get("PROXY_ESCALATE_API_KEY", ""),
@@ -55,6 +77,41 @@ class Settings:
55
77
  return bool(self.model and self.endpoint)
56
78
 
57
79
 
80
+ # ---- helpers --------------------------------------------------------------
81
+ def latest_user_text(anthropic_body: dict) -> str:
82
+ for msg in reversed((anthropic_body or {}).get("messages", []) or []):
83
+ if msg.get("role") != "user":
84
+ continue
85
+ c = msg.get("content")
86
+ if isinstance(c, str):
87
+ return c
88
+ if isinstance(c, list):
89
+ return "".join(b.get("text", "") for b in c if isinstance(b, dict) and b.get("type") == "text")
90
+ return ""
91
+
92
+
93
+ def extract_text(anthropic_resp: dict) -> str:
94
+ parts = []
95
+ for blk in (anthropic_resp or {}).get("content", []) or []:
96
+ if isinstance(blk, dict) and blk.get("type") == "text":
97
+ parts.append(blk.get("text", ""))
98
+ return "".join(parts)
99
+
100
+
101
+ # ---- #2 recipe selection --------------------------------------------------
102
+ def select_recipe(anthropic_body: dict, settings: Settings, has_tools: bool) -> str:
103
+ if not settings.enabled or has_tools:
104
+ return "single"
105
+ if settings.recipe != "auto":
106
+ return settings.recipe
107
+ # Auto: signal-driven. Longer/harder prompts have higher reasoning variance
108
+ # -> fusion (breadth+judge); shorter -> confidence (cheap, escalate if weak).
109
+ if len(latest_user_text(anthropic_body)) >= settings.auto_fusion_chars and settings.backend_configured():
110
+ return "fusion"
111
+ return "confidence"
112
+
113
+
114
+ # ---- #1/#5 confidence signal ----------------------------------------------
58
115
  _UNCERTAIN = re.compile(
59
116
  r"\b(i'?m not sure|i am not sure|i don'?t know|i cannot|i can'?t (?:help|do|determine)|"
60
117
  r"unable to|not certain|no idea|as an ai|i'?m sorry,? but)\b",
@@ -63,8 +120,6 @@ _UNCERTAIN = re.compile(
63
120
 
64
121
 
65
122
  def text_confidence(text: str) -> float:
66
- """Cheap heuristic confidence in [0,1]. Conservative: only clearly weak
67
- answers (empty, refusal/uncertainty, trivially short) score low."""
68
123
  t = (text or "").strip()
69
124
  if not t:
70
125
  return 0.0
@@ -75,30 +130,115 @@ def text_confidence(text: str) -> float:
75
130
  return 0.9
76
131
 
77
132
 
78
- def extract_text(anthropic_resp: dict) -> str:
79
- """Concatenate text blocks of an Anthropic message response."""
80
- parts = []
81
- for blk in (anthropic_resp or {}).get("content", []) or []:
82
- if isinstance(blk, dict) and blk.get("type") == "text":
83
- parts.append(blk.get("text", ""))
84
- return "".join(parts)
133
+ def build_verify_payload(anthropic_body: dict, answer_text: str, settings: Settings) -> dict:
134
+ q = latest_user_text(anthropic_body)
135
+ prompt = (
136
+ "Rate from 0 to 10 how well this ANSWER satisfies the REQUEST "
137
+ "(0 = wrong or incomplete, 10 = fully correct and complete). "
138
+ "Reply with ONLY the number.\n\nREQUEST:\n" + q + "\n\nANSWER:\n" + answer_text
139
+ )
140
+ return {"model": settings.model, "max_tokens": 16,
141
+ "messages": [{"role": "user", "content": prompt}]}
85
142
 
86
143
 
87
- def should_escalate(text: str, settings: Settings, has_tools: bool) -> bool:
88
- if not settings.enabled or not settings.backend_configured():
89
- return False
90
- if has_tools: # single-answer scope only
91
- return False
92
- return text_confidence(text) < settings.threshold
144
+ def parse_verify_score(text: str):
145
+ m = re.search(r"\d+(?:\.\d+)?", text or "")
146
+ if not m:
147
+ return None
148
+ return max(0.0, min(1.0, float(m.group(0)) / 10.0))
93
149
 
94
150
 
151
+ # ---- escalation / fusion payloads -----------------------------------------
95
152
  def build_escalation_payload(anthropic_body: dict, settings: Settings) -> dict:
96
- """Re-issue the same conversation to the stronger model (no tools)."""
97
- payload = {
98
- "model": settings.model,
99
- "max_tokens": anthropic_body.get("max_tokens", 4096),
100
- "messages": anthropic_body.get("messages", []),
101
- }
153
+ payload = {"model": settings.model,
154
+ "max_tokens": anthropic_body.get("max_tokens", 4096),
155
+ "messages": anthropic_body.get("messages", [])}
102
156
  if anthropic_body.get("system"):
103
157
  payload["system"] = anthropic_body["system"]
104
158
  return payload
159
+
160
+
161
+ def build_fusion_variants(openai_body: dict, n: int) -> list[dict]:
162
+ """N-1 extra primary variants (the live response is candidate 0). Vary
163
+ temperature for breadth; everything else identical."""
164
+ out = []
165
+ for i in range(max(0, n - 1)):
166
+ v = dict(openai_body)
167
+ v["stream"] = False
168
+ v["temperature"] = round(0.4 + 0.2 * i, 2)
169
+ out.append(v)
170
+ return out
171
+
172
+
173
+ def build_judge_payload(anthropic_body: dict, candidate_texts: list[str], settings: Settings) -> dict:
174
+ q = latest_user_text(anthropic_body)
175
+ listing = "\n\n".join(f"[{i}]\n{t}" for i, t in enumerate(candidate_texts))
176
+ prompt = (
177
+ "You are a strict judge. Choose the SINGLE best answer to the REQUEST. "
178
+ "Reply with ONLY the index number of the best answer.\n\nREQUEST:\n"
179
+ + q + "\n\nANSWERS:\n" + listing
180
+ )
181
+ return {"model": settings.model, "max_tokens": 8,
182
+ "messages": [{"role": "user", "content": prompt}]}
183
+
184
+
185
+ def parse_judge_index(text: str, n: int):
186
+ m = re.search(r"\d+", text or "")
187
+ if not m:
188
+ return None
189
+ i = int(m.group(0))
190
+ return i if 0 <= i < n else None
191
+
192
+
193
+ def should_escalate(text: str, settings: Settings, has_tools: bool) -> bool:
194
+ """Back-compat (heuristic confidence path)."""
195
+ if not settings.enabled or not settings.backend_configured() or has_tools:
196
+ return False
197
+ return text_confidence(text) < settings.threshold
198
+
199
+
200
+ # ---- orchestration (injected callables) -----------------------------------
201
+ async def _confidence_score(text, anthropic_body, settings, call_judge):
202
+ if settings.signal == "selfverify" and settings.backend_configured() and call_judge is not None:
203
+ jr = await call_judge(build_verify_payload(anthropic_body, text, settings))
204
+ score = parse_verify_score(extract_text(jr)) if isinstance(jr, dict) else None
205
+ if score is not None:
206
+ return score
207
+ return text_confidence(text)
208
+
209
+
210
+ async def apply_recipe(primary_resp, anthropic_body, openai_body, settings, has_tools,
211
+ call_primary, call_judge):
212
+ """Dispatch to the selected recipe. Returns an anthropic response dict.
213
+ call_primary(openai_variant)->anthropic_resp|None ; call_judge(anthropic_payload)->anthropic_resp|None.
214
+ Fails open: returns primary_resp on any problem."""
215
+ try:
216
+ recipe = select_recipe(anthropic_body, settings, has_tools)
217
+ if recipe == "single" or not isinstance(primary_resp, dict):
218
+ return primary_resp
219
+ primary_text = extract_text(primary_resp)
220
+
221
+ if recipe == "confidence":
222
+ conf = await _confidence_score(primary_text, anthropic_body, settings, call_judge)
223
+ if conf < settings.threshold and settings.backend_configured() and call_judge is not None:
224
+ esc = await call_judge(build_escalation_payload(anthropic_body, settings))
225
+ if isinstance(esc, dict):
226
+ return esc
227
+ return primary_resp
228
+
229
+ if recipe == "fusion":
230
+ variants = build_fusion_variants(openai_body or {}, settings.fusion_n)
231
+ results = await asyncio.gather(*[call_primary(v) for v in variants],
232
+ return_exceptions=True)
233
+ candidates = [primary_resp] + [r for r in results if isinstance(r, dict)]
234
+ if len(candidates) <= 1 or not settings.backend_configured() or call_judge is None:
235
+ return primary_resp
236
+ texts = [extract_text(c) for c in candidates]
237
+ jr = await call_judge(build_judge_payload(anthropic_body, texts, settings))
238
+ idx = parse_judge_index(extract_text(jr), len(candidates)) if isinstance(jr, dict) else None
239
+ if idx is not None:
240
+ return candidates[idx]
241
+ return primary_resp # fallback to best valid evidence
242
+ except Exception:
243
+ return primary_resp
244
+ return primary_resp
@@ -1,55 +1,108 @@
1
- """Tests for the confidence-escalation looper (vLLM Confidence recipe)."""
1
+ """Tests for the serving-layer recipe runtime (Confidence #1/#5, Fusion #3, selector #2)."""
2
+ import asyncio
2
3
  import importlib.util
4
+ import sys
3
5
  import unittest
4
6
  from pathlib import Path
5
7
 
6
8
  mod_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts" / "confidence_escalation.py"
7
9
  spec = importlib.util.spec_from_file_location("confidence_escalation", mod_path)
8
10
  ce = importlib.util.module_from_spec(spec)
9
- import sys as _sys; _sys.modules["confidence_escalation"]=ce; spec.loader.exec_module(ce)
11
+ sys.modules["confidence_escalation"] = ce
12
+ spec.loader.exec_module(ce)
10
13
 
11
14
 
12
- def S(enabled=True, threshold=0.5, model="opus", endpoint="http://x/", api_key="k"):
13
- return ce.Settings(enabled=enabled, threshold=threshold, model=model, endpoint=endpoint, api_key=api_key)
15
+ def S(enabled=True, recipe="auto", signal="heuristic", threshold=0.5, fusion_n=3,
16
+ auto_chars=600, model="opus", endpoint="http://x/", key="k"):
17
+ return ce.Settings(enabled=enabled, recipe=recipe, signal=signal, threshold=threshold,
18
+ fusion_n=fusion_n, auto_fusion_chars=auto_chars, model=model,
19
+ endpoint=endpoint, api_key=key)
14
20
 
15
21
 
16
- class ConfidenceTest(unittest.TestCase):
17
- def test_text_confidence_levels(self):
22
+ def resp(text):
23
+ return {"content": [{"type": "text", "text": text}]}
24
+
25
+
26
+ def body(user="hi"):
27
+ return {"model": "qwen", "max_tokens": 100, "messages": [{"role": "user", "content": user}]}
28
+
29
+
30
+ def run(coro):
31
+ return asyncio.new_event_loop().run_until_complete(coro)
32
+
33
+
34
+ class HelpersTest(unittest.TestCase):
35
+ def test_text_confidence(self):
18
36
  self.assertEqual(ce.text_confidence(""), 0.0)
19
37
  self.assertEqual(ce.text_confidence("I don't know"), 0.2)
20
- self.assertEqual(ce.text_confidence("ok"), 0.3)
21
- self.assertGreater(ce.text_confidence("Here is a complete, detailed answer to your question."), 0.5)
38
+ self.assertGreater(ce.text_confidence("A thorough complete answer to the question."), 0.5)
22
39
 
23
- def test_extract_text(self):
24
- resp = {"content": [{"type": "text", "text": "a"}, {"type": "tool_use"}, {"type": "text", "text": "b"}]}
25
- self.assertEqual(ce.extract_text(resp), "ab")
40
+ def test_parse_verify_score(self):
41
+ self.assertAlmostEqual(ce.parse_verify_score("8"), 0.8)
42
+ self.assertEqual(ce.parse_verify_score("score: 3 of 10"), 0.3)
43
+ self.assertIsNone(ce.parse_verify_score("n/a"))
26
44
 
45
+ def test_parse_judge_index(self):
46
+ self.assertEqual(ce.parse_judge_index("The best is [2]", 3), 2)
47
+ self.assertIsNone(ce.parse_judge_index("9", 3))
27
48
 
28
- class ShouldEscalateTest(unittest.TestCase):
29
- def test_low_confidence_escalates(self):
30
- self.assertTrue(ce.should_escalate("I cannot help", S(), has_tools=False))
49
+ def test_fusion_variants(self):
50
+ v = ce.build_fusion_variants({"model": "qwen", "messages": []}, 3)
51
+ self.assertEqual(len(v), 2) # n-1 (primary is candidate 0)
52
+ self.assertEqual([x["temperature"] for x in v], [0.4, 0.6])
31
53
 
32
- def test_high_confidence_does_not(self):
33
- self.assertFalse(ce.should_escalate("A thorough and confident full answer here.", S(), has_tools=False))
34
54
 
35
- def test_disabled_never(self):
36
- self.assertFalse(ce.should_escalate("", S(enabled=False), has_tools=False))
55
+ class SelectorTest(unittest.TestCase): # #2
56
+ def test_disabled_or_tools_single(self):
57
+ self.assertEqual(ce.select_recipe(body(), S(enabled=False), False), "single")
58
+ self.assertEqual(ce.select_recipe(body(), S(), True), "single")
37
59
 
38
- def test_no_backend_never(self):
39
- self.assertFalse(ce.should_escalate("", S(endpoint=""), has_tools=False))
60
+ def test_explicit_recipe(self):
61
+ self.assertEqual(ce.select_recipe(body(), S(recipe="fusion"), False), "fusion")
40
62
 
41
- def test_tool_turn_never(self):
42
- self.assertFalse(ce.should_escalate("", S(), has_tools=True))
63
+ def test_auto_short_confidence_long_fusion(self):
64
+ self.assertEqual(ce.select_recipe(body("hi"), S(recipe="auto"), False), "confidence")
65
+ self.assertEqual(ce.select_recipe(body("x" * 700), S(recipe="auto"), False), "fusion")
43
66
 
44
67
 
45
- class PayloadTest(unittest.TestCase):
46
- def test_payload_uses_escalation_model_and_messages(self):
47
- body = {"model": "qwen", "max_tokens": 100, "system": "sys", "messages": [{"role": "user", "content": "hi"}]}
48
- p = ce.build_escalation_payload(body, S(model="opus"))
49
- self.assertEqual(p["model"], "opus")
50
- self.assertEqual(p["messages"], body["messages"])
51
- self.assertEqual(p["system"], "sys")
52
- self.assertNotIn("tools", p)
68
+ class ApplyConfidenceTest(unittest.TestCase): # #1 / #5
69
+ def test_low_confidence_escalates(self):
70
+ async def judge(p): return resp("ESCALATED ANSWER")
71
+ out = run(ce.apply_recipe(resp("I cannot help"), body(), {}, S(recipe="confidence"),
72
+ False, None, judge))
73
+ self.assertEqual(ce.extract_text(out), "ESCALATED ANSWER")
74
+
75
+ def test_high_confidence_keeps_primary(self):
76
+ async def judge(p): raise AssertionError("should not be called")
77
+ out = run(ce.apply_recipe(resp("A complete confident answer here."), body(), {},
78
+ S(recipe="confidence"), False, None, judge))
79
+ self.assertEqual(ce.extract_text(out), "A complete confident answer here.")
80
+
81
+ def test_selfverify_uses_judge_score(self): # #5
82
+ calls = {"n": 0}
83
+ async def judge(p):
84
+ calls["n"] += 1
85
+ return resp("9") # high score -> no escalation
86
+ out = run(ce.apply_recipe(resp("short"), body(), {}, S(recipe="confidence", signal="selfverify"),
87
+ False, None, judge))
88
+ self.assertEqual(calls["n"], 1) # verify call happened
89
+ self.assertEqual(ce.extract_text(out), "short") # high score, kept primary
90
+
91
+
92
+ class ApplyFusionTest(unittest.TestCase): # #3
93
+ def test_fusion_fans_out_and_judge_picks(self):
94
+ async def primary(v): return resp("candidate-1")
95
+ async def judge(p): return resp("1") # pick index 1
96
+ out = run(ce.apply_recipe(resp("candidate-0"), body(), {"model": "qwen", "messages": []},
97
+ S(recipe="fusion", fusion_n=2), False, primary, judge))
98
+ self.assertEqual(ce.extract_text(out), "candidate-1")
99
+
100
+ def test_fusion_judge_failure_falls_back_to_primary(self):
101
+ async def primary(v): return resp("c1")
102
+ async def judge(p): return None # judge failed
103
+ out = run(ce.apply_recipe(resp("c0"), body(), {"model": "qwen", "messages": []},
104
+ S(recipe="fusion", fusion_n=2), False, primary, judge))
105
+ self.assertEqual(ce.extract_text(out), "c0")
53
106
 
54
107
 
55
108
  if __name__ == "__main__":