fix(compression): preserve clarify responses
This commit is contained in:
parent
98e96e1a60
commit
d6511aecb6
|
|
@ -1314,6 +1314,41 @@ def _summarize_tool_result_unguarded(tool_name: str, tool_args: str, tool_conten
|
|||
return "[todo] updated task list"
|
||||
|
||||
if tool_name == "clarify":
|
||||
response_prefix = "[clarify] user responded: "
|
||||
max_summary_chars = 200
|
||||
truncation_marker = "...[truncated]"
|
||||
|
||||
# Idempotence: a later pressure-pruning pass may see the summary
|
||||
# produced by an earlier pass. Preserve it instead of trying to parse
|
||||
# the summary text as the original JSON result.
|
||||
if content.startswith(response_prefix):
|
||||
if len(content) <= max_summary_chars:
|
||||
return content
|
||||
return (
|
||||
content[: max_summary_chars - len(truncation_marker)].rstrip()
|
||||
+ truncation_marker
|
||||
)
|
||||
|
||||
try:
|
||||
result = json.loads(content)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
result = {}
|
||||
response = result.get("user_response") if isinstance(result, dict) else None
|
||||
resolved = (
|
||||
isinstance(response, str) and bool(response)
|
||||
) or (
|
||||
isinstance(response, list)
|
||||
and bool(response)
|
||||
and all(isinstance(item, str) and item for item in response)
|
||||
)
|
||||
if resolved:
|
||||
summary = response_prefix + json.dumps(response, ensure_ascii=False)
|
||||
if len(summary) > max_summary_chars:
|
||||
summary = (
|
||||
summary[: max_summary_chars - len(truncation_marker)].rstrip()
|
||||
+ truncation_marker
|
||||
)
|
||||
return summary
|
||||
return "[clarify] asked user a question"
|
||||
|
||||
if tool_name == "text_to_speech":
|
||||
|
|
|
|||
|
|
@ -61,6 +61,57 @@ class TestSummarizeToolResultWebExtract:
|
|||
assert summary == "[web_extract] https://example.com/h (500 chars)"
|
||||
|
||||
|
||||
class TestSummarizeToolResultClarify:
|
||||
def test_preserves_resolved_user_response_without_metadata(self):
|
||||
content = json.dumps({
|
||||
"question": "When should I deploy?",
|
||||
"choices_offered": ["Friday", "Monday"],
|
||||
"user_response": "Friday",
|
||||
})
|
||||
|
||||
summary = _summarize_tool_result("clarify", "{}", content)
|
||||
|
||||
assert summary == '[clarify] user responded: "Friday"'
|
||||
|
||||
def test_preserves_multi_select_user_response(self):
|
||||
content = json.dumps({
|
||||
"question": "Which checks should I run?",
|
||||
"choices_offered": ["lint", "tests", "types"],
|
||||
"user_response": ["lint", "tests"],
|
||||
})
|
||||
|
||||
summary = _summarize_tool_result("clarify", "{}", content)
|
||||
|
||||
assert summary == '[clarify] user responded: ["lint", "tests"]'
|
||||
|
||||
def test_long_response_is_bounded_and_survives_repeated_pruning(self):
|
||||
content = json.dumps({
|
||||
"question": "Describe the deployment constraints",
|
||||
"choices_offered": None,
|
||||
"user_response": "A" * 1_000,
|
||||
})
|
||||
|
||||
first_summary = _summarize_tool_result("clarify", "{}", content)
|
||||
second_summary = _summarize_tool_result("clarify", "{}", first_summary)
|
||||
|
||||
assert len(first_summary) == 200
|
||||
assert first_summary.startswith('[clarify] user responded: "AAA')
|
||||
assert first_summary.endswith("...[truncated]")
|
||||
assert second_summary == first_summary
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"content",
|
||||
[
|
||||
json.dumps({"error": "Failed to get user input: internal details"}),
|
||||
json.dumps({"question": "Q?", "user_response": ""}),
|
||||
json.dumps({"question": "Q?", "user_response": {"internal": "value"}}),
|
||||
"not json",
|
||||
],
|
||||
)
|
||||
def test_does_not_expose_unresolved_or_internal_content(self, content):
|
||||
summary = _summarize_tool_result("clarify", "{}", content)
|
||||
|
||||
assert summary == "[clarify] asked user a question"
|
||||
|
||||
|
||||
class TestShouldCompress:
|
||||
|
|
|
|||
Loading…
Reference in New Issue