t-261015083454: мета-информация в событиях — trace_id+level+ts в каждом trace-событии (ToolCalled/ToolResult 68k были без связи с тиком), duration_ms/ok/exception в ToolResult, имя инструмента вместо '?', level+trace_id в TaskCompleted/TaskFailed; смоук + 5 тестов PASS

This commit is contained in:
Simbiot Harness 2026-10-15 09:07:32 +00:00
parent f667ab6d2c
commit f3ab66e19e
3 changed files with 36 additions and 6 deletions

View file

@ -447,7 +447,8 @@ def main():
memory=False, memory=False,
skills=False, skills=False,
) )
agent = _wire_trace_hooks(agent, es, trace_id, model=payload.get("model")) agent = _wire_trace_hooks(agent, es, trace_id, model=payload.get("model"),
level=level)
agent = _wire_budget_hooks(agent, level, trace_id, model=payload.get("model")) agent = _wire_budget_hooks(agent, level, trace_id, model=payload.get("model"))
# хард-кап шагов (e-260930031800): cancel_tool + end_turn. # хард-кап шагов (e-260930031800): cancel_tool + end_turn.
# ВАЖНО: НЕ используем limits={"turns": N} — нативный стоп SDK # ВАЖНО: НЕ используем limits={"turns": N} — нативный стоп SDK

View file

@ -77,12 +77,20 @@ def setup_provider():
os.environ["OPENAI_BASE_URL"] = _router_base_url() os.environ["OPENAI_BASE_URL"] = _router_base_url()
def _wire_trace_hooks(agent, es, trace_id: str, model: str = None): def _wire_trace_hooks(agent, es, trace_id: str, model: str = None, level: str = None):
"""Каждая стадия агентного цикла → событие агрегата trace. """Каждая стадия агентного цикла → событие агрегата trace.
model (e-260930062120): model_id уровня в InvocationStarted — model (e-260930062120): model_id уровня в InvocationStarted —
видно, КАКАЯ модель запускалась на тике (запрошенная; "auto" видно, КАКАЯ модель запускалась на тике (запрошенная; "auto"
резолвится роутером на его стороне). резолвится роутером на его стороне).
t-261015083454 (аудит мета-информации в событиях): В КАЖДОМ
trace-событии теперь trace_id + ts + level (ToolCalled/ToolResult
раньше несли только tool/input — 68k событий без связи с тиком),
ToolResult дополнительно duration + ok/exception (из хука strands),
имя инструмента в ToolResult — из tool_use, а не заглушка "?".
Уровень недоступен (legacy-вызов) — поле не пишем, aggregate_id
по-прежнему "<level>-<seq>:<n>".
""" """
from strands import Agent from strands import Agent
from strands.hooks import BeforeInvocationEvent, BeforeToolCallEvent, AfterToolCallEvent from strands.hooks import BeforeInvocationEvent, BeforeToolCallEvent, AfterToolCallEvent
@ -93,25 +101,42 @@ def _wire_trace_hooks(agent, es, trace_id: str, model: str = None):
seq["n"] += 1 seq["n"] += 1
es.append_event("trace", f"{trace_id}:{seq['n']}", kind, payload) es.append_event("trace", f"{trace_id}:{seq['n']}", kind, payload)
def _meta(extra: dict = None) -> dict:
base = {"trace_id": trace_id, "ts": _now()}
if level:
base["level"] = level
if extra:
base.update(extra)
return base
def on_before_invocation(event): def on_before_invocation(event):
data = {"trace_id": trace_id, "ts": _now()} data = _meta()
if model: if model:
data["model"] = model data["model"] = model
_trace("InvocationStarted", data) _trace("InvocationStarted", data)
def on_before_tool(event): def on_before_tool(event):
tool = getattr(event, "tool_use", None) or {} tool = getattr(event, "tool_use", None) or {}
_trace("ToolCalled", { _trace("ToolCalled", _meta({
"tool": tool.get("name", "?"), "tool": tool.get("name", "?"),
"input": json.dumps(tool.get("input", {}), ensure_ascii=False, default=str)[:4000], "input": json.dumps(tool.get("input", {}), ensure_ascii=False, default=str)[:4000],
}) }))
def on_after_tool(event): def on_after_tool(event):
tool = getattr(event, "tool_use", None) or {}
try: try:
res = str(getattr(event, "result", ""))[:4000] res = str(getattr(event, "result", ""))[:4000]
except Exception: except Exception:
res = "<unreadable>" res = "<unreadable>"
_trace("ToolResult", {"tool": "?", "result": res}) extra = {"tool": tool.get("name", "?"), "result": res}
duration = getattr(event, "duration", None)
if duration is not None:
extra["duration_ms"] = round(float(duration) * 1000)
exc = getattr(event, "exception", None)
extra["ok"] = exc is None
if exc is not None:
extra["exception"] = str(exc)[:500]
_trace("ToolResult", _meta(extra))
agent.add_hook(on_before_invocation, BeforeInvocationEvent) agent.add_hook(on_before_invocation, BeforeInvocationEvent)
agent.add_hook(on_before_tool, BeforeToolCallEvent) agent.add_hook(on_before_tool, BeforeToolCallEvent)

View file

@ -624,6 +624,8 @@ def run_level_tick(level_cfg, seq: int, model: dict):
if not _task_still_active(tid): if not _task_still_active(tid):
continue continue
es.append_event("task", tid, "TaskFailed", { es.append_event("task", tid, "TaskFailed", {
# t-261015083454: мета — уровень/тик-воркер и его трасса
"level": level_cfg.name, "trace_id": trace_id,
"error": str(usage.get("error", "llm worker failed"))[:500], "error": str(usage.get("error", "llm worker failed"))[:500],
}) })
raise RuntimeError(str(usage.get("error", "llm worker failed"))) raise RuntimeError(str(usage.get("error", "llm worker failed")))
@ -676,6 +678,8 @@ def run_level_tick(level_cfg, seq: int, model: dict):
_log("skip батч-догзакрытие задач, решённых воркером: " + ", ".join(_skipped)) _log("skip батч-догзакрытие задач, решённых воркером: " + ", ".join(_skipped))
for tid in _resolve: for tid in _resolve:
es.append_event("task", tid, "TaskCompleted", { es.append_event("task", tid, "TaskCompleted", {
# t-261015083454: мета-поля — какой тик/уровень закрыл задачу
"level": level_cfg.name, "trace_id": trace_id,
"result_ref": trace_id, "answer": (text or "")[:2000], "result_ref": trace_id, "answer": (text or "")[:2000],
}) })
try: try: