@miller-tech/uap 1.20.13 → 1.20.14

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.20.13",
3
+ "version": "1.20.14",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -219,7 +219,7 @@ PROXY_MALFORMED_TOOL_GUARDRAIL = os.environ.get(
219
219
  "no",
220
220
  }
221
221
  PROXY_MALFORMED_TOOL_RETRY_MAX = int(
222
- os.environ.get("PROXY_MALFORMED_TOOL_RETRY_MAX", "2")
222
+ os.environ.get("PROXY_MALFORMED_TOOL_RETRY_MAX", "3")
223
223
  )
224
224
  PROXY_MALFORMED_TOOL_RETRY_MAX_TOKENS = int(
225
225
  os.environ.get("PROXY_MALFORMED_TOOL_RETRY_MAX_TOKENS", "2048")
@@ -3890,6 +3890,40 @@ async def _apply_completion_contract_guardrail(
3890
3890
  return retried
3891
3891
 
3892
3892
 
3893
+ def _sanitize_assistant_messages_for_retry(messages: list[dict]) -> list[dict]:
3894
+ """Strip malformed tool-like text from assistant messages to prevent copy-contamination.
3895
+
3896
+ Only sanitizes the last 4 assistant messages to avoid excessive processing.
3897
+ """
3898
+ import re
3899
+
3900
+ # Patterns that indicate malformed tool call text in assistant content
3901
+ _TOOL_LIKE_PATTERNS = re.compile(
3902
+ r"<tool_call>.*?</tool_call>"
3903
+ r"|<function_call>.*?</function_call>"
3904
+ r'|\{"name"\s*:\s*"[^"]+"\s*,\s*"arguments"\s*:'
3905
+ r"|```json\s*\{[^}]*\"name\"\s*:",
3906
+ re.DOTALL,
3907
+ )
3908
+
3909
+ result = list(messages)
3910
+ sanitized_count = 0
3911
+ for i in range(len(result) - 1, -1, -1):
3912
+ if sanitized_count >= 4:
3913
+ break
3914
+ msg = result[i]
3915
+ if msg.get("role") != "assistant":
3916
+ continue
3917
+ content = msg.get("content", "")
3918
+ if isinstance(content, str) and _TOOL_LIKE_PATTERNS.search(content):
3919
+ cleaned = _TOOL_LIKE_PATTERNS.sub("", content).strip()
3920
+ if not cleaned:
3921
+ cleaned = "I will use the appropriate tool."
3922
+ result[i] = {**msg, "content": cleaned}
3923
+ sanitized_count += 1
3924
+ return result
3925
+
3926
+
3893
3927
  def _build_malformed_retry_body(
3894
3928
  openai_body: dict,
3895
3929
  anthropic_body: dict,
@@ -3901,7 +3935,11 @@ def _build_malformed_retry_body(
3901
3935
  retry_body = dict(openai_body)
3902
3936
  retry_body["stream"] = False
3903
3937
  retry_body["tool_choice"] = tool_choice
3904
- retry_body["temperature"] = PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
3938
+ # Escalate temperature down on successive retries for more deterministic output
3939
+ if total_attempts > 1 and attempt > 1:
3940
+ retry_body["temperature"] = 0.0
3941
+ else:
3942
+ retry_body["temperature"] = PROXY_MALFORMED_TOOL_RETRY_TEMPERATURE
3905
3943
 
3906
3944
  if tool_choice == "required":
3907
3945
  retry_instruction = (
@@ -3922,7 +3960,10 @@ def _build_malformed_retry_body(
3922
3960
  }
3923
3961
  existing_messages = retry_body.get("messages")
3924
3962
  if isinstance(existing_messages, list) and existing_messages:
3925
- retry_body["messages"] = [*existing_messages, malformed_retry_instruction]
3963
+ # Strip malformed tool-like text from assistant messages to prevent
3964
+ # the model from copying contaminated patterns on retry
3965
+ sanitized = _sanitize_assistant_messages_for_retry(existing_messages)
3966
+ retry_body["messages"] = [*sanitized, malformed_retry_instruction]
3926
3967
 
3927
3968
  if PROXY_MALFORMED_TOOL_RETRY_MAX_TOKENS > 0:
3928
3969
  current_max = int(
@@ -3377,6 +3377,66 @@ class TestCycleBreakOptions(unittest.TestCase):
3377
3377
  self.assertEqual(monitor.cycling_tool_names, [])
3378
3378
 
3379
3379
 
3380
+ class TestMalformedRetryHardening(unittest.TestCase):
3381
+ """Tests for malformed retry improvements: budget, temp escalation, message sanitization."""
3382
+
3383
+ def test_retry_max_default_is_3(self):
3384
+ """Option 1: default retry budget increased from 2 to 3."""
3385
+ self.assertEqual(proxy.PROXY_MALFORMED_TOOL_RETRY_MAX, 3)
3386
+
3387
+ def test_sanitize_assistant_messages_strips_tool_like_text(self):
3388
+ """Option 3: malformed tool-like text stripped from assistant messages on retry."""
3389
+ messages = [
3390
+ {"role": "system", "content": "You are helpful."},
3391
+ {"role": "user", "content": "Run a command"},
3392
+ {"role": "assistant", "content": 'Here is the result <tool_call>{"name": "Bash", "arguments": {"command": "ls"}}</tool_call>'},
3393
+ {"role": "user", "content": "ok"},
3394
+ ]
3395
+ sanitized = proxy._sanitize_assistant_messages_for_retry(messages)
3396
+ # System and user messages unchanged
3397
+ self.assertEqual(sanitized[0]["content"], "You are helpful.")
3398
+ self.assertEqual(sanitized[1]["content"], "Run a command")
3399
+ self.assertEqual(sanitized[3]["content"], "ok")
3400
+ # Assistant message should have tool_call stripped
3401
+ self.assertNotIn("<tool_call>", sanitized[2]["content"])
3402
+ self.assertNotIn("Bash", sanitized[2]["content"])
3403
+
3404
+ def test_sanitize_preserves_clean_assistant_messages(self):
3405
+ """Clean assistant messages are not modified by sanitization."""
3406
+ messages = [
3407
+ {"role": "assistant", "content": "I will read the file for you."},
3408
+ ]
3409
+ sanitized = proxy._sanitize_assistant_messages_for_retry(messages)
3410
+ self.assertEqual(sanitized[0]["content"], "I will read the file for you.")
3411
+
3412
+ def test_sanitize_replaces_empty_content_with_placeholder(self):
3413
+ """If stripping leaves empty content, a placeholder is used."""
3414
+ messages = [
3415
+ {"role": "assistant", "content": '<tool_call>{"name": "Bash", "arguments": {}}</tool_call>'},
3416
+ ]
3417
+ sanitized = proxy._sanitize_assistant_messages_for_retry(messages)
3418
+ self.assertEqual(sanitized[0]["content"], "I will use the appropriate tool.")
3419
+
3420
+ def test_retry_body_uses_sanitized_messages(self):
3421
+ """Retry body messages are sanitized before adding retry instruction."""
3422
+ openai_body = {
3423
+ "messages": [
3424
+ {"role": "system", "content": "sys"},
3425
+ {"role": "user", "content": "do it"},
3426
+ {"role": "assistant", "content": '<tool_call>{"name":"X","arguments":{}}</tool_call>'},
3427
+ ],
3428
+ "tools": [{"type": "function", "function": {"name": "X", "parameters": {}}}],
3429
+ }
3430
+ anthropic_body = {"tools": [{"name": "X", "input_schema": {"type": "object"}}]}
3431
+ retry = proxy._build_malformed_retry_body(
3432
+ openai_body, anthropic_body, attempt=1, total_attempts=3,
3433
+ )
3434
+ # The assistant message should be sanitized
3435
+ assistant_msgs = [m for m in retry["messages"] if m.get("role") == "assistant"]
3436
+ for m in assistant_msgs:
3437
+ self.assertNotIn("<tool_call>", m.get("content", ""))
3438
+
3439
+
3380
3440
  if __name__ == "__main__":
3381
3441
  unittest.main()
3382
3442