@miller-tech/uap 1.20.8 → 1.20.10
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
|
@@ -51,7 +51,7 @@ Configuration (Environment Variables)
|
|
|
51
51
|
|
|
52
52
|
PROXY_CONTEXT_PRUNE_THRESHOLD Fraction of context window at which
|
|
53
53
|
conversation pruning activates (0.0-1.0)
|
|
54
|
-
Default: 0.
|
|
54
|
+
Default: 0.85
|
|
55
55
|
|
|
56
56
|
Usage
|
|
57
57
|
-----
|
|
@@ -113,10 +113,10 @@ PROXY_UPSTREAM_RETRY_DELAY_SECS = float(os.environ.get("PROXY_UPSTREAM_RETRY_DEL
|
|
|
113
113
|
PROXY_MAX_CONNECTIONS = int(os.environ.get("PROXY_MAX_CONNECTIONS", "20"))
|
|
114
114
|
PROXY_CONTEXT_WINDOW = int(os.environ.get("PROXY_CONTEXT_WINDOW", "0"))
|
|
115
115
|
PROXY_CONTEXT_PRUNE_THRESHOLD = float(
|
|
116
|
-
os.environ.get("PROXY_CONTEXT_PRUNE_THRESHOLD", "0.
|
|
116
|
+
os.environ.get("PROXY_CONTEXT_PRUNE_THRESHOLD", "0.85")
|
|
117
117
|
)
|
|
118
118
|
PROXY_CONTEXT_PRUNE_TARGET_FRACTION = float(
|
|
119
|
-
os.environ.get("PROXY_CONTEXT_PRUNE_TARGET_FRACTION", "0.
|
|
119
|
+
os.environ.get("PROXY_CONTEXT_PRUNE_TARGET_FRACTION", "0.50")
|
|
120
120
|
)
|
|
121
121
|
PROXY_LOOP_BREAKER = os.environ.get("PROXY_LOOP_BREAKER", "on").lower() not in {
|
|
122
122
|
"0",
|
|
@@ -277,6 +277,9 @@ PROXY_FORCED_TOOL_DAMPENER_AUTO_TURNS = int(
|
|
|
277
277
|
PROXY_FORCED_TOOL_DAMPENER_REJECTIONS = int(
|
|
278
278
|
os.environ.get("PROXY_FORCED_TOOL_DAMPENER_REJECTIONS", "2")
|
|
279
279
|
)
|
|
280
|
+
PROXY_TOOL_STARVATION_THRESHOLD = int(
|
|
281
|
+
os.environ.get("PROXY_TOOL_STARVATION_THRESHOLD", "5")
|
|
282
|
+
)
|
|
280
283
|
PROXY_SESSION_CONTAMINATION_BREAKER = os.environ.get(
|
|
281
284
|
"PROXY_SESSION_CONTAMINATION_BREAKER", "on"
|
|
282
285
|
).lower() not in {
|
|
@@ -609,6 +612,7 @@ class SessionMonitor:
|
|
|
609
612
|
loop_warnings_emitted: int = 0 # How many loop warnings sent to the model
|
|
610
613
|
no_progress_streak: int = 0 # Forced tool turns without new tool_result
|
|
611
614
|
unexpected_end_turn_count: int = 0 # end_turn without tool_use in active loop
|
|
615
|
+
tool_starvation_streak: int = 0 # Consecutive forced turns with no tool_calls produced
|
|
612
616
|
malformed_tool_streak: int = 0 # consecutive malformed pseudo tool payloads
|
|
613
617
|
invalid_tool_call_streak: int = 0 # consecutive invalid tool arg payloads
|
|
614
618
|
required_tool_miss_streak: int = 0 # required tool turns with no tool call
|
|
@@ -2279,6 +2283,29 @@ def build_openai_request(
|
|
|
2279
2283
|
last_user_has_tool_result,
|
|
2280
2284
|
)
|
|
2281
2285
|
|
|
2286
|
+
# TOOL STARVATION BREAKER: if model repeatedly fails to produce tool
|
|
2287
|
+
# calls despite required, strip tools to let it generate text and break
|
|
2288
|
+
# the forcing loop.
|
|
2289
|
+
if (
|
|
2290
|
+
monitor.consecutive_forced_count >= PROXY_TOOL_STARVATION_THRESHOLD
|
|
2291
|
+
and _last_assistant_was_text_only(anthropic_body)
|
|
2292
|
+
):
|
|
2293
|
+
openai_body.pop("tool_choice", None)
|
|
2294
|
+
openai_body.pop("tools", None)
|
|
2295
|
+
monitor.tool_starvation_streak += 1
|
|
2296
|
+
monitor.consecutive_forced_count = 0
|
|
2297
|
+
monitor.no_progress_streak = 0
|
|
2298
|
+
monitor.reset_tool_turn_state(reason="tool_starvation_breaker")
|
|
2299
|
+
logger.warning(
|
|
2300
|
+
"TOOL STARVATION BREAKER: stripped tools after %d forced turns with no tool output (starvation_streak=%d)",
|
|
2301
|
+
PROXY_TOOL_STARVATION_THRESHOLD,
|
|
2302
|
+
monitor.tool_starvation_streak,
|
|
2303
|
+
)
|
|
2304
|
+
# Skip all further tool_choice logic — no tools this turn
|
|
2305
|
+
if PROXY_DISABLE_THINKING_ON_TOOL_TURNS:
|
|
2306
|
+
openai_body["enable_thinking"] = False
|
|
2307
|
+
return openai_body
|
|
2308
|
+
|
|
2282
2309
|
# Check if forced-tool dampener or loop breaker should override tool_choice
|
|
2283
2310
|
if monitor.consume_forced_auto_turn():
|
|
2284
2311
|
openai_body["tool_choice"] = "auto"
|
|
@@ -2358,6 +2385,7 @@ def build_openai_request(
|
|
|
2358
2385
|
if not has_tool_results:
|
|
2359
2386
|
monitor.reset_tool_turn_state(reason="no_tool_results")
|
|
2360
2387
|
|
|
2388
|
+
|
|
2361
2389
|
if PROXY_DISABLE_THINKING_ON_TOOL_TURNS:
|
|
2362
2390
|
openai_body["enable_thinking"] = False
|
|
2363
2391
|
logger.info(
|
|
@@ -2788,6 +2816,15 @@ _SYSTEM_PROMPT_LEAK_MARKERS = (
|
|
|
2788
2816
|
"never emit literal tag artifacts",
|
|
2789
2817
|
"use tools for concrete work",
|
|
2790
2818
|
"stopping at analysis",
|
|
2819
|
+
# Client system prompt phrases that also leak into tool args
|
|
2820
|
+
"only produce a final text response without tool calls",
|
|
2821
|
+
"the entire task is fully complete",
|
|
2822
|
+
"always use tools to read, edit, write",
|
|
2823
|
+
"after reading files and identifying an issue",
|
|
2824
|
+
"do not output raw protocol tags",
|
|
2825
|
+
"valid tool call with strict json",
|
|
2826
|
+
"return exactly one valid tool call",
|
|
2827
|
+
"invalid tool call format",
|
|
2791
2828
|
)
|
|
2792
2829
|
|
|
2793
2830
|
|
|
@@ -3135,6 +3135,37 @@ class TestMinimalSupplementForQwen(unittest.TestCase):
|
|
|
3135
3135
|
self.assertIn("agentic-protocol", system_msg)
|
|
3136
3136
|
|
|
3137
3137
|
|
|
3138
|
+
class TestToolStarvationBreaker(unittest.TestCase):
|
|
3139
|
+
"""Tests for tool-call starvation breaker."""
|
|
3140
|
+
|
|
3141
|
+
def _make_body_with_tools(self):
|
|
3142
|
+
return {
|
|
3143
|
+
"model": "qwen3.5",
|
|
3144
|
+
"messages": [
|
|
3145
|
+
{"role": "user", "content": "hello"},
|
|
3146
|
+
{"role": "assistant", "content": "I will help you."},
|
|
3147
|
+
{"role": "user", "content": [{"type": "tool_result", "tool_use_id": "x", "content": "ok"}]},
|
|
3148
|
+
],
|
|
3149
|
+
"tools": [{"name": "Bash", "input_schema": {"type": "object", "properties": {"command": {"type": "string"}}}}],
|
|
3150
|
+
}
|
|
3151
|
+
|
|
3152
|
+
def test_starvation_breaker_strips_tools(self):
|
|
3153
|
+
monitor = proxy.SessionMonitor()
|
|
3154
|
+
monitor.consecutive_forced_count = proxy.PROXY_TOOL_STARVATION_THRESHOLD
|
|
3155
|
+
body = self._make_body_with_tools()
|
|
3156
|
+
result = proxy.build_openai_request(body, monitor)
|
|
3157
|
+
self.assertNotIn("tools", result)
|
|
3158
|
+
self.assertNotIn("tool_choice", result)
|
|
3159
|
+
self.assertEqual(monitor.tool_starvation_streak, 1)
|
|
3160
|
+
|
|
3161
|
+
def test_no_starvation_below_threshold(self):
|
|
3162
|
+
monitor = proxy.SessionMonitor()
|
|
3163
|
+
monitor.consecutive_forced_count = proxy.PROXY_TOOL_STARVATION_THRESHOLD - 1
|
|
3164
|
+
body = self._make_body_with_tools()
|
|
3165
|
+
result = proxy.build_openai_request(body, monitor)
|
|
3166
|
+
self.assertIn("tools", result)
|
|
3167
|
+
|
|
3168
|
+
|
|
3138
3169
|
if __name__ == "__main__":
|
|
3139
3170
|
unittest.main()
|
|
3140
3171
|
|