@miller-tech/uap 1.86.0 → 1.88.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/dist/.tsbuildinfo +1 -1
- package/dist/cli/react.d.ts.map +1 -1
- package/dist/cli/react.js +10 -0
- package/dist/cli/react.js.map +1 -1
- package/dist/coordination/recipe-signal.d.ts +18 -0
- package/dist/coordination/recipe-signal.d.ts.map +1 -0
- package/dist/coordination/recipe-signal.js +85 -0
- package/dist/coordination/recipe-signal.js.map +1 -0
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/scripts/confidence_escalation.py +135 -4
- package/tools/agents/tests/test_confidence_escalation.py +81 -6
package/dist/cli/react.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAG/F;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,cAAc,GAAG,MAAM,CAO3E;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AASD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,mBAAwB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuCnF"}
|
package/dist/cli/react.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolve } from '../coordination/reactor.js';
|
|
2
|
+
import { maybeWriteRecipeSignal } from '../coordination/recipe-signal.js';
|
|
2
3
|
/**
|
|
3
4
|
* Pure core of `uap react`: parse a JSON ReactorContext payload, resolve the
|
|
4
5
|
* dynamic UAP capabilities for the event, and return the JSON ReactorResult.
|
|
@@ -59,5 +60,14 @@ export async function reactCommand(options = {}) {
|
|
|
59
60
|
if (options.maxInjectChars)
|
|
60
61
|
opts.maxInjectChars = Number(options.maxInjectChars);
|
|
61
62
|
process.stdout.write(runReact(payload, opts) + '\n');
|
|
63
|
+
// Cross-process: stamp the recipe signal for this prompt so the serving-layer
|
|
64
|
+
// proxy can route on the reactor's actual capability/complexity. Best-effort.
|
|
65
|
+
try {
|
|
66
|
+
const promptText = JSON.parse(payload).promptText ?? '';
|
|
67
|
+
maybeWriteRecipeSignal(promptText);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* fail open */
|
|
71
|
+
}
|
|
62
72
|
}
|
|
63
73
|
//# sourceMappingURL=react.js.map
|
package/dist/cli/react.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA4C,MAAM,4BAA4B,CAAC;
|
|
1
|
+
{"version":3,"file":"react.js","sourceRoot":"","sources":["../../src/cli/react.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CapabilityRouter } from './capability-router.js';
|
|
2
|
+
export interface RecipeSignal {
|
|
3
|
+
ts: number;
|
|
4
|
+
promptHash: string;
|
|
5
|
+
complexity: string;
|
|
6
|
+
capabilities: string[];
|
|
7
|
+
shape: string;
|
|
8
|
+
recipe: string;
|
|
9
|
+
confidence: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function normalizePrompt(text: string): string;
|
|
12
|
+
export declare function promptHash(text: string): string;
|
|
13
|
+
export declare function computeRecipeSignal(promptText: string, router?: CapabilityRouter): RecipeSignal;
|
|
14
|
+
export declare function signalDir(dir?: string): string;
|
|
15
|
+
export declare function writeRecipeSignal(sig: RecipeSignal, dir?: string): void;
|
|
16
|
+
/** Best-effort: compute + write the signal for a prompt. Never throws. */
|
|
17
|
+
export declare function maybeWriteRecipeSignal(promptText: string, dir?: string): void;
|
|
18
|
+
//# sourceMappingURL=recipe-signal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-signal.d.ts","sourceRoot":"","sources":["../../src/coordination/recipe-signal.ts"],"names":[],"mappings":"AAYA,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAQpF,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,MAAM,EAClB,MAAM,GAAE,gBAAwC,GAC/C,YAAY,CAgCd;AAED,wBAAgB,SAAS,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAUvE;AAED,0EAA0E;AAC1E,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAO7E"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recipe signal (cross-process): the reactor writes per-prompt routing signals
|
|
3
|
+
* that the serving-layer proxy consumes for recipe selection. Keyed by a hash
|
|
4
|
+
* of the normalized prompt (identical algorithm to the Python proxy), plus a
|
|
5
|
+
* rolling latest.json for the single-session common case. Fails open.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { measureQueryComplexity } from '../utils/query-complexity.js';
|
|
12
|
+
import { getCapabilityRouter } from './capability-router.js';
|
|
13
|
+
const CODE_CAPS = new Set([
|
|
14
|
+
'typescript', 'javascript', 'python', 'rust', 'go', 'cpp', 'java',
|
|
15
|
+
'frontend', 'backend', 'cli', 'api-design',
|
|
16
|
+
]);
|
|
17
|
+
const REASONING_CAPS = new Set(['architecture', 'product', 'test-strategy', 'compliance']);
|
|
18
|
+
export function normalizePrompt(text) {
|
|
19
|
+
return (text || '').trim().toLowerCase().replace(/\s+/g, ' ');
|
|
20
|
+
}
|
|
21
|
+
export function promptHash(text) {
|
|
22
|
+
return createHash('sha1').update(normalizePrompt(text)).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
export function computeRecipeSignal(promptText, router = getCapabilityRouter()) {
|
|
25
|
+
const complexity = measureQueryComplexity(promptText); // simple | moderate | complex
|
|
26
|
+
let capabilities = [];
|
|
27
|
+
let confidence = 0;
|
|
28
|
+
try {
|
|
29
|
+
const r = router.routeTask({
|
|
30
|
+
id: 'react-signal', title: promptText, type: 'task', status: 'open',
|
|
31
|
+
priority: 2, labels: [], createdAt: '1970-01-01T00:00:00.000Z',
|
|
32
|
+
updatedAt: '1970-01-01T00:00:00.000Z',
|
|
33
|
+
});
|
|
34
|
+
capabilities = r.matchedCapabilities ?? [];
|
|
35
|
+
confidence = r.confidence ?? 0;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
/* fall back to text-only shape */
|
|
39
|
+
}
|
|
40
|
+
const shape = capabilities.some((c) => REASONING_CAPS.has(c))
|
|
41
|
+
? 'reasoning'
|
|
42
|
+
: capabilities.some((c) => CODE_CAPS.has(c))
|
|
43
|
+
? 'code'
|
|
44
|
+
: /\b(prove|why|explain|derive|which of|true or false)\b/i.test(promptText)
|
|
45
|
+
? 'reasoning'
|
|
46
|
+
: 'general';
|
|
47
|
+
const recipe = complexity === 'complex' || shape === 'reasoning' ? 'fusion' : 'confidence';
|
|
48
|
+
return {
|
|
49
|
+
ts: Date.now() / 1000,
|
|
50
|
+
promptHash: promptHash(promptText),
|
|
51
|
+
complexity,
|
|
52
|
+
capabilities,
|
|
53
|
+
shape,
|
|
54
|
+
recipe,
|
|
55
|
+
confidence,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
export function signalDir(dir) {
|
|
59
|
+
return dir || process.env.UAP_RECIPE_SIGNAL_DIR || join(homedir(), '.cache', 'uap', 'recipe-signals');
|
|
60
|
+
}
|
|
61
|
+
export function writeRecipeSignal(sig, dir) {
|
|
62
|
+
try {
|
|
63
|
+
const d = signalDir(dir);
|
|
64
|
+
if (!existsSync(d))
|
|
65
|
+
mkdirSync(d, { recursive: true });
|
|
66
|
+
const data = JSON.stringify(sig);
|
|
67
|
+
writeFileSync(join(d, `${sig.promptHash}.json`), data);
|
|
68
|
+
writeFileSync(join(d, 'latest.json'), data);
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
/* fail open — the proxy falls back to its own signal extraction */
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/** Best-effort: compute + write the signal for a prompt. Never throws. */
|
|
75
|
+
export function maybeWriteRecipeSignal(promptText, dir) {
|
|
76
|
+
if (!promptText || !promptText.trim())
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
writeRecipeSignal(computeRecipeSignal(promptText), dir);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
/* fail open */
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=recipe-signal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recipe-signal.js","sourceRoot":"","sources":["../../src/coordination/recipe-signal.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -27,8 +27,11 @@ Env:
|
|
|
27
27
|
from __future__ import annotations
|
|
28
28
|
|
|
29
29
|
import asyncio
|
|
30
|
+
import hashlib
|
|
31
|
+
import json
|
|
30
32
|
import os
|
|
31
33
|
import re
|
|
34
|
+
import time
|
|
32
35
|
from dataclasses import dataclass
|
|
33
36
|
|
|
34
37
|
|
|
@@ -98,15 +101,143 @@ def extract_text(anthropic_resp: dict) -> str:
|
|
|
98
101
|
return "".join(parts)
|
|
99
102
|
|
|
100
103
|
|
|
101
|
-
# ---- #2 recipe selection
|
|
104
|
+
# ---- #2 recipe selection (reactor-aligned signals) ------------------------
|
|
105
|
+
# Faithful port of src/utils/query-complexity.ts (the SAME difficulty signal the
|
|
106
|
+
# reactor's capability-router / auto-optimizer use), plus a task-shape signal.
|
|
107
|
+
# The serving layer extracts its own signals (vLLM-SR philosophy) so recipe
|
|
108
|
+
# choice is task-shaped, not prompt-length guesswork. Optional override:
|
|
109
|
+
# the harness may stamp x-uap-task-signal style hints into the request later.
|
|
110
|
+
_TECH_PATTERNS = [
|
|
111
|
+
re.compile(r"debug|fix|error|exception|bug", re.I),
|
|
112
|
+
re.compile(r"implement|refactor|optimize|build", re.I),
|
|
113
|
+
re.compile(r"configure|setup|install|deploy", re.I),
|
|
114
|
+
re.compile(r"security|vulnerability|cve|auth", re.I),
|
|
115
|
+
re.compile(r"performance|memory|cpu|latency", re.I),
|
|
116
|
+
re.compile(r"database|query|migration|schema", re.I),
|
|
117
|
+
re.compile(r"test|coverage|mock|spec", re.I),
|
|
118
|
+
]
|
|
119
|
+
_FILE_RE = re.compile(r"[\w./\\-]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|json|yaml|sh|sql)", re.I)
|
|
120
|
+
_MULTISTEP = re.compile(r"and then|after that|followed by|step \d|first.*then|also|additionally", re.I)
|
|
121
|
+
_WHYHOW = re.compile(r"^(why|how|what caused|explain)", re.I)
|
|
122
|
+
_ACTIONS = re.compile(r"\b(fix|implement|configure|debug|create|update|delete|add|remove)\b", re.I)
|
|
123
|
+
_REASONING = re.compile(
|
|
124
|
+
r"\b(prove|derive|theorem|reason|analy[sz]e|compare|evaluate|calculate|which of|true or false)\b"
|
|
125
|
+
r"|\b[A-D]\)\s", re.I,
|
|
126
|
+
)
|
|
127
|
+
_CODE = re.compile(r"```|\b(function|class|api|endpoint|module|compile|script)\b", re.I)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def complexity_score(text: str) -> float:
|
|
131
|
+
"""Port of query-complexity.ts measureQueryComplexity."""
|
|
132
|
+
t = text or ""
|
|
133
|
+
score = 0.0
|
|
134
|
+
wc = len(t.split())
|
|
135
|
+
if wc > 30:
|
|
136
|
+
score += 1.5
|
|
137
|
+
elif wc > 12:
|
|
138
|
+
score += 0.75
|
|
139
|
+
elif wc > 6:
|
|
140
|
+
score += 0.25
|
|
141
|
+
for pat in _TECH_PATTERNS:
|
|
142
|
+
if pat.search(t):
|
|
143
|
+
score += 0.4
|
|
144
|
+
files = _FILE_RE.findall(t)
|
|
145
|
+
score += len(files) * 0.3
|
|
146
|
+
if _MULTISTEP.search(t):
|
|
147
|
+
score += 1.0
|
|
148
|
+
if _WHYHOW.search(t):
|
|
149
|
+
score += 0.5
|
|
150
|
+
actions = _ACTIONS.findall(t)
|
|
151
|
+
if len(actions) > 1:
|
|
152
|
+
score += len(actions) * 0.3
|
|
153
|
+
return score
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def query_complexity(text: str) -> str:
|
|
157
|
+
score = complexity_score(text)
|
|
158
|
+
if score >= 2:
|
|
159
|
+
return "complex"
|
|
160
|
+
if score >= 1:
|
|
161
|
+
return "moderate"
|
|
162
|
+
return "simple"
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def task_shape(text: str) -> str:
|
|
166
|
+
t = text or ""
|
|
167
|
+
if _REASONING.search(t):
|
|
168
|
+
return "reasoning"
|
|
169
|
+
if _CODE.search(t) or _FILE_RE.search(t) or any(p.search(t) for p in _TECH_PATTERNS[:3]):
|
|
170
|
+
return "code"
|
|
171
|
+
if t.strip().endswith("?") and len(t.split()) <= 14:
|
|
172
|
+
return "qa"
|
|
173
|
+
return "general"
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def task_signals(anthropic_body: dict) -> dict:
|
|
177
|
+
text = latest_user_text(anthropic_body)
|
|
178
|
+
return {"complexity": query_complexity(text), "shape": task_shape(text)}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# ---- cross-process: consume the harness reactor's actual routeResult ----
|
|
182
|
+
# The reactor (TS, in the harness) writes a per-prompt signal keyed by a hash of
|
|
183
|
+
# the normalized prompt, plus a rolling latest.json. The proxy reads it here so
|
|
184
|
+
# recipe selection uses the reactor's real capability/complexity routing rather
|
|
185
|
+
# than re-deriving it. Falls back to the proxy's own signal extraction on miss.
|
|
186
|
+
def _default_signal_dir() -> str:
|
|
187
|
+
return os.environ.get("UAP_RECIPE_SIGNAL_DIR") or os.path.join(
|
|
188
|
+
os.path.expanduser("~"), ".cache", "uap", "recipe-signals"
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def normalize_prompt(text: str) -> str:
|
|
193
|
+
return " ".join((text or "").strip().lower().split())
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def prompt_hash(text: str) -> str:
|
|
197
|
+
return hashlib.sha1(normalize_prompt(text).encode("utf-8")).hexdigest()
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def load_reactor_signal(prompt_text, signal_dir=None, ttl=180.0, now=None):
|
|
201
|
+
"""Return the reactor's signal dict for this prompt, or None. Exact
|
|
202
|
+
prompt-hash match first; then a recent latest.json fallback (single-session
|
|
203
|
+
common case). Fresh within ttl seconds. Never raises."""
|
|
204
|
+
d = signal_dir or _default_signal_dir()
|
|
205
|
+
now = time.time() if now is None else now
|
|
206
|
+
for path in (os.path.join(d, prompt_hash(prompt_text) + ".json"), os.path.join(d, "latest.json")):
|
|
207
|
+
try:
|
|
208
|
+
if os.path.exists(path):
|
|
209
|
+
with open(path) as f:
|
|
210
|
+
sig = json.load(f)
|
|
211
|
+
if now - float(sig.get("ts", 0)) <= ttl:
|
|
212
|
+
return sig
|
|
213
|
+
except Exception:
|
|
214
|
+
continue
|
|
215
|
+
return None
|
|
216
|
+
|
|
217
|
+
|
|
102
218
|
def select_recipe(anthropic_body: dict, settings: Settings, has_tools: bool) -> str:
|
|
103
219
|
if not settings.enabled or has_tools:
|
|
104
220
|
return "single"
|
|
105
221
|
if settings.recipe != "auto":
|
|
106
222
|
return settings.recipe
|
|
107
|
-
|
|
108
|
-
#
|
|
109
|
-
|
|
223
|
+
text = latest_user_text(anthropic_body)
|
|
224
|
+
# Prefer the harness reactor's ACTUAL routeResult signal when fresh; else the
|
|
225
|
+
# proxy's own signal extraction (faithful port of query-complexity).
|
|
226
|
+
rsig = load_reactor_signal(text)
|
|
227
|
+
if rsig is not None:
|
|
228
|
+
rec = rsig.get("recipe")
|
|
229
|
+
if rec in {"single", "confidence", "fusion"}:
|
|
230
|
+
if rec == "fusion" and not settings.backend_configured():
|
|
231
|
+
return "confidence"
|
|
232
|
+
return rec
|
|
233
|
+
complexity = rsig.get("complexity") or query_complexity(text)
|
|
234
|
+
shape = rsig.get("shape") or task_shape(text)
|
|
235
|
+
else:
|
|
236
|
+
complexity = query_complexity(text)
|
|
237
|
+
shape = task_shape(text)
|
|
238
|
+
if settings.backend_configured() and (complexity == "complex" or shape == "reasoning"):
|
|
239
|
+
return "fusion"
|
|
240
|
+
if settings.backend_configured() and len(text) >= settings.auto_fusion_chars:
|
|
110
241
|
return "fusion"
|
|
111
242
|
return "confidence"
|
|
112
243
|
|
|
@@ -9,6 +9,8 @@ mod_path = Path(__file__).resolve().parents[3] / "tools" / "agents" / "scripts"
|
|
|
9
9
|
spec = importlib.util.spec_from_file_location("confidence_escalation", mod_path)
|
|
10
10
|
ce = importlib.util.module_from_spec(spec)
|
|
11
11
|
sys.modules["confidence_escalation"] = ce
|
|
12
|
+
import tempfile, os as _os2
|
|
13
|
+
_os2.environ["UAP_RECIPE_SIGNAL_DIR"] = tempfile.mkdtemp(prefix="uap-sig-")
|
|
12
14
|
spec.loader.exec_module(ce)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -52,17 +54,45 @@ class HelpersTest(unittest.TestCase):
|
|
|
52
54
|
self.assertEqual([x["temperature"] for x in v], [0.4, 0.6])
|
|
53
55
|
|
|
54
56
|
|
|
55
|
-
class
|
|
57
|
+
class SignalsTest(unittest.TestCase): # reactor-aligned signals
|
|
58
|
+
def test_complexity_port(self):
|
|
59
|
+
# faithful to query-complexity.ts: "and then" multi-step + tech + file + 2 actions
|
|
60
|
+
self.assertEqual(ce.query_complexity("fix the bug in src/a.ts and then update the tests"), "complex")
|
|
61
|
+
self.assertEqual(ce.query_complexity("add a button"), "simple")
|
|
62
|
+
self.assertEqual(ce.query_complexity("implement a redis rate limiter"), "simple")
|
|
63
|
+
self.assertIn(ce.query_complexity("implement a redis rate limiter with tests and config"), ("moderate", "complex"))
|
|
64
|
+
|
|
65
|
+
def test_task_shape(self):
|
|
66
|
+
self.assertEqual(ce.task_shape("prove that x is even"), "reasoning")
|
|
67
|
+
self.assertEqual(ce.task_shape("Which is right? A) x B) y"), "reasoning")
|
|
68
|
+
self.assertEqual(ce.task_shape("refactor the function in app.ts"), "code")
|
|
69
|
+
self.assertEqual(ce.task_shape("what is the capital of france?"), "qa")
|
|
70
|
+
self.assertEqual(ce.task_shape("tell me a story"), "general")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class SelectorTest(unittest.TestCase): # #2 task-shaped
|
|
56
74
|
def test_disabled_or_tools_single(self):
|
|
57
75
|
self.assertEqual(ce.select_recipe(body(), S(enabled=False), False), "single")
|
|
58
76
|
self.assertEqual(ce.select_recipe(body(), S(), True), "single")
|
|
59
77
|
|
|
60
|
-
def
|
|
61
|
-
self.assertEqual(ce.select_recipe(body(), S(recipe="fusion"), False), "fusion")
|
|
78
|
+
def test_explicit_recipe_overrides_signals(self):
|
|
79
|
+
self.assertEqual(ce.select_recipe(body("add a button"), S(recipe="fusion"), False), "fusion")
|
|
80
|
+
self.assertEqual(ce.select_recipe(body("prove x is even"), S(recipe="confidence"), False), "confidence")
|
|
81
|
+
|
|
82
|
+
def test_complex_task_routes_to_fusion(self):
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
ce.select_recipe(body("fix the bug in src/a.ts and then update the tests"), S(recipe="auto"), False),
|
|
85
|
+
"fusion",
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def test_reasoning_task_routes_to_fusion(self):
|
|
89
|
+
self.assertEqual(ce.select_recipe(body("prove that the sum of two evens is even"), S(recipe="auto"), False), "fusion")
|
|
62
90
|
|
|
63
|
-
def
|
|
64
|
-
self.assertEqual(ce.select_recipe(body("
|
|
65
|
-
|
|
91
|
+
def test_simple_task_routes_to_confidence(self):
|
|
92
|
+
self.assertEqual(ce.select_recipe(body("add a button"), S(recipe="auto"), False), "confidence")
|
|
93
|
+
|
|
94
|
+
def test_no_backend_never_fusion(self):
|
|
95
|
+
self.assertEqual(ce.select_recipe(body("fix the bug in a.ts and then update tests"), S(recipe="auto", endpoint=""), False), "confidence")
|
|
66
96
|
|
|
67
97
|
|
|
68
98
|
class ApplyConfidenceTest(unittest.TestCase): # #1 / #5
|
|
@@ -105,5 +135,50 @@ class ApplyFusionTest(unittest.TestCase): # #3
|
|
|
105
135
|
self.assertEqual(ce.extract_text(out), "c0")
|
|
106
136
|
|
|
107
137
|
|
|
138
|
+
|
|
139
|
+
class CrossProcessSignalTest(unittest.TestCase): # reactor -> proxy
|
|
140
|
+
def setUp(self):
|
|
141
|
+
import tempfile
|
|
142
|
+
self.d = tempfile.mkdtemp(prefix="uap-sig-test-")
|
|
143
|
+
|
|
144
|
+
def _write(self, name, sig):
|
|
145
|
+
import json, os
|
|
146
|
+
with open(os.path.join(self.d, name), "w") as f:
|
|
147
|
+
json.dump(sig, f)
|
|
148
|
+
|
|
149
|
+
def test_prompt_hash_match_consumed(self):
|
|
150
|
+
text = "build the thing"
|
|
151
|
+
h = ce.prompt_hash(text)
|
|
152
|
+
self._write(h + ".json", {"ts": __import__("time").time(), "recipe": "fusion"})
|
|
153
|
+
sig = ce.load_reactor_signal(text, signal_dir=self.d)
|
|
154
|
+
self.assertEqual(sig["recipe"], "fusion")
|
|
155
|
+
|
|
156
|
+
def test_stale_signal_ignored(self):
|
|
157
|
+
text = "old prompt"
|
|
158
|
+
self._write(ce.prompt_hash(text) + ".json", {"ts": 1, "recipe": "fusion"})
|
|
159
|
+
self.assertIsNone(ce.load_reactor_signal(text, signal_dir=self.d, ttl=180.0))
|
|
160
|
+
|
|
161
|
+
def test_latest_fallback(self):
|
|
162
|
+
self._write("latest.json", {"ts": __import__("time").time(), "recipe": "fusion"})
|
|
163
|
+
sig = ce.load_reactor_signal("anything not hashed", signal_dir=self.d)
|
|
164
|
+
self.assertEqual(sig["recipe"], "fusion")
|
|
165
|
+
|
|
166
|
+
def test_select_recipe_uses_reactor_recommendation(self):
|
|
167
|
+
text = "add a button" # self-extract would say confidence (simple)
|
|
168
|
+
self._write(ce.prompt_hash(text) + ".json",
|
|
169
|
+
{"ts": __import__("time").time(), "recipe": "fusion"})
|
|
170
|
+
import os
|
|
171
|
+
prev = os.environ.get("UAP_RECIPE_SIGNAL_DIR")
|
|
172
|
+
os.environ["UAP_RECIPE_SIGNAL_DIR"] = self.d
|
|
173
|
+
try:
|
|
174
|
+
r = ce.select_recipe(body(text), S(recipe="auto"), False)
|
|
175
|
+
finally:
|
|
176
|
+
if prev is None:
|
|
177
|
+
os.environ.pop("UAP_RECIPE_SIGNAL_DIR", None)
|
|
178
|
+
else:
|
|
179
|
+
os.environ["UAP_RECIPE_SIGNAL_DIR"] = prev
|
|
180
|
+
self.assertEqual(r, "fusion") # reactor signal overrides the simple self-classification
|
|
181
|
+
|
|
182
|
+
|
|
108
183
|
if __name__ == "__main__":
|
|
109
184
|
unittest.main()
|