simbiot-harness/tests/test_tool_result_cap.py

93 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""Кэп результатов инструментов в истории тика (burn-аудит 14.10, рек.1).
Запуск: ./venv/bin/python tests/test_tool_result_cap.py
Hermetic: без LLM, на живом strands-агенте (только реестр хуков).
Диагноз из аудита (docs/aum/burn-audit-2026-10-14.md): 99.9% бюджета —
промпт-токены; каждый агентный шаг пересылает накопленную историю с
полными tool-result'ами (до 75k ток/шаг). Кэп срезает гигантские
результаты до попадания в историю; полный текст остаётся в трейсе
(events.db, ToolResult пишет runtime-хук).
"""
import os
import sys
import tempfile as _iso_tf
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
os.environ["SIMBIOT_DB"] = os.path.join(_iso_tf.mkdtemp(prefix="simbiot_test_"), "harness.db")
from strands import Agent
from strands.hooks import AfterToolCallEvent
from harness import _llm_worker
fails = []
def check(name, cond, extra=""):
print(("PASS" if cond else "FAIL"), name, ("| " + extra) if not cond else "")
if not cond:
fails.append(name)
agent = Agent(callback_handler=None)
agent = _llm_worker._wire_tool_result_cap(agent)
_probe = AfterToolCallEvent(agent=agent, selected_tool=None, tool_use={},
invocation_state={},
result={"content": [{"text": ""}], "status": "success", "toolUseId": "t0"})
_cbs = list(agent.hooks.get_callbacks_for(_probe))
check("хук зарегистрирован", len(_cbs) >= 1)
def fire(text):
ev = AfterToolCallEvent(agent=agent, selected_tool=None,
tool_use={"name": "shell", "input": {}},
invocation_state={},
result={"content": [{"text": text}], "status": "success", "toolUseId": "t"})
for cb in _cbs:
cb(ev)
return ev.result["content"][0]["text"]
# 1) гигантский результат срезается сразу, с маркером
t1 = fire("x" * 20000)
check("20k срезан до кэпа", len(t1) < _llm_worker._TOOL_RESULT_CAP_CHARS + 200, len(t1))
check("маркер среза в тексте", "срез" in t1)
# 2) маленький результат не трогается
check("маленький не тронут", fire("ok") == "ok")
# 3) мягкий порог: после ~60k накопленных результатов средний (6k) тоже режется
for _ in range(12):
fire("z" * 5000)
t3 = fire("y" * 6000)
check("soft-порог включил срез средних", len(t3) < 6000, len(t3))
# 4) не-текстовый контент (json/image) не валит хук
_ev = AfterToolCallEvent(agent=agent, selected_tool=None,
tool_use={"name": "portal_mcp", "input": {}},
invocation_state={},
result={"content": [{"json": {"a": 1}}, {"image": b"x"}],
"status": "success", "toolUseId": "t4"})
for cb in _cbs:
cb(_ev)
check("не-текстовый контент жив", _ev.result["content"][0] == {"json": {"a": 1}})
# 5) мусорный result (не dict) не валит хук
try:
from strands.hooks import AfterToolCallEvent as _ATE
ev5 = _ATE(agent=agent, selected_tool=None, tool_use={}, invocation_state={},
result=None)
for cb in _cbs:
cb(ev5)
check("result=None не валит хук", True)
except Exception as e:
check("result=None не валит хук", False, str(e)[:120])
print()
if fails:
print("FAIL:", fails)
sys.exit(1)
print("все проверки пройдены")