- harness/es.py: append-only event store + проекции (образец — кооп-портал) - harness/levels.py: уровни 1с/1м/1ч/1д, контекст ∝ масштабу - harness/scheduler.py: уровневые потоки, идемпотентные тики, absorbed-стабы - harness/isolated_runner.py + _llm_worker.py: LLM-тик в сабпроцессе (глобальный asyncio-рантайм Strands не переживает 2+ create_harness) - cli.py: init/task/tasks/status/events + requeue (написан самим минутным агентом) - 9router: отдельный ключ sk-simbiot, модель auto (glm-5.3 первым)
201 lines
6.8 KiB
Python
201 lines
6.8 KiB
Python
"""Telemetry helpers for bidirectional streaming agent.
|
|
|
|
Wraps the shared Tracer primitives with bidi-specific span creation and attribute names.
|
|
Uses Tracer._start_span/_end_span directly rather than adding bidi-specific public methods
|
|
to the shared Tracer class, keeping experimental bidi telemetry self-contained.
|
|
"""
|
|
|
|
from opentelemetry.trace import Span
|
|
|
|
from ...telemetry.tracer import Tracer, serialize
|
|
from ...types.traces import AttributeValue
|
|
|
|
|
|
def start_session_span(
|
|
tracer: Tracer,
|
|
agent_name: str,
|
|
model_id: str | None = None,
|
|
tools: list[str] | None = None,
|
|
system_prompt: str | None = None,
|
|
) -> Span:
|
|
"""Start a span for the bidi session lifecycle.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
agent_name: Name of the bidi agent.
|
|
model_id: Model identifier.
|
|
tools: List of tool names available to the agent.
|
|
system_prompt: System prompt configured on the agent.
|
|
|
|
Returns:
|
|
The session span.
|
|
"""
|
|
attributes: dict[str, AttributeValue] = tracer._get_common_attributes(operation_name="bidi_session")
|
|
attributes["gen_ai.agent.name"] = agent_name
|
|
|
|
if model_id:
|
|
attributes["gen_ai.request.model"] = model_id
|
|
|
|
if tools:
|
|
attributes["gen_ai.agent.tools"] = serialize(tools)
|
|
|
|
if system_prompt:
|
|
attributes["gen_ai.system_instructions"] = tracer._redact("gen_ai.system_instructions", system_prompt)
|
|
|
|
return tracer._start_span(f"bidi_session {agent_name}", attributes=attributes)
|
|
|
|
|
|
def end_session_span(
|
|
tracer: Tracer,
|
|
span: Span,
|
|
input_tokens: int = 0,
|
|
output_tokens: int = 0,
|
|
total_tokens: int = 0,
|
|
cache_read_input_tokens: int = 0,
|
|
error: Exception | None = None,
|
|
) -> None:
|
|
"""End the bidi session span.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
span: The session span to end.
|
|
input_tokens: Accumulated input tokens for the session.
|
|
output_tokens: Accumulated output tokens for the session.
|
|
total_tokens: Accumulated total tokens for the session.
|
|
cache_read_input_tokens: Accumulated tokens read from cache for the session.
|
|
error: Exception if the session ended with an error.
|
|
"""
|
|
token_attributes = {
|
|
"gen_ai.usage.input_tokens": input_tokens,
|
|
"gen_ai.usage.output_tokens": output_tokens,
|
|
"gen_ai.usage.total_tokens": total_tokens,
|
|
"gen_ai.usage.cache_read.input_tokens": cache_read_input_tokens,
|
|
}
|
|
if not tracer.use_latest_genai_conventions:
|
|
# Deprecated pre-semconv name, dual-emitted unless opted into the latest conventions
|
|
token_attributes["gen_ai.usage.cache_read_input_tokens"] = cache_read_input_tokens
|
|
attributes: dict[str, AttributeValue] = {name: value for name, value in token_attributes.items() if value > 0}
|
|
|
|
tracer._end_span(span, attributes=attributes, error=error)
|
|
|
|
|
|
def start_response_span(tracer: Tracer, response_id: str, parent_span: Span | None = None) -> Span:
|
|
"""Start a span for a model response.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
response_id: Unique identifier for the response.
|
|
parent_span: Parent session span.
|
|
|
|
Returns:
|
|
The response span.
|
|
"""
|
|
attributes: dict[str, AttributeValue] = tracer._get_common_attributes(operation_name="bidi_response")
|
|
attributes["gen_ai.response.id"] = response_id
|
|
|
|
# Static span name (response_id is recorded as an attribute) to avoid high-cardinality names.
|
|
return tracer._start_span("bidi_response", parent_span, attributes=attributes)
|
|
|
|
|
|
def end_response_span(
|
|
tracer: Tracer,
|
|
span: Span,
|
|
time_to_first_audio_ms: int | None = None,
|
|
error: Exception | None = None,
|
|
) -> None:
|
|
"""End a model response span.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
span: The response span to end.
|
|
time_to_first_audio_ms: Milliseconds from response start to the first audio chunk, if any
|
|
audio was emitted for this response.
|
|
error: Exception if the response ended with an error.
|
|
"""
|
|
attributes: dict[str, AttributeValue] = {}
|
|
|
|
if time_to_first_audio_ms is not None:
|
|
attributes["gen_ai.server.time_to_first_audio"] = time_to_first_audio_ms
|
|
|
|
tracer._end_span(span, attributes=attributes, error=error)
|
|
|
|
|
|
def start_restart_span(
|
|
tracer: Tracer,
|
|
parent_span: Span | None = None,
|
|
reason: str = "timeout",
|
|
error_message: str | None = None,
|
|
) -> Span:
|
|
"""Start a span for a connection restart.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
parent_span: Parent session span.
|
|
reason: What triggered the restart ("timeout" reactively, "scheduled" proactively).
|
|
error_message: The timeout error message that triggered a reactive restart, if any.
|
|
|
|
Returns:
|
|
The restart span.
|
|
"""
|
|
attributes: dict[str, AttributeValue] = tracer._get_common_attributes(operation_name="bidi_connection_restart")
|
|
attributes["gen_ai.bidi.restart_reason"] = reason
|
|
|
|
# The message of the timeout that triggered a reactive restart. Namespaced under
|
|
# gen_ai.bidi.* (not gen_ai.error.*) so it does not read as a failure of this span,
|
|
# which may end successfully; a failed restart is recorded via end_restart_span.
|
|
if error_message:
|
|
attributes["gen_ai.bidi.restart_error_message"] = error_message
|
|
|
|
return tracer._start_span("bidi_connection_restart", parent_span, attributes=attributes)
|
|
|
|
|
|
def end_restart_span(tracer: Tracer, span: Span, error: Exception | None = None) -> None:
|
|
"""End a connection restart span.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
span: The restart span to end.
|
|
error: Exception if the restart failed.
|
|
"""
|
|
tracer._end_span(span, error=error)
|
|
|
|
|
|
def start_connection_span(tracer: Tracer, parent_span: Span | None = None, model_id: str | None = None) -> Span:
|
|
"""Start a span for the model connection establishment.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
parent_span: Parent session span.
|
|
model_id: Model identifier being connected to.
|
|
|
|
Returns:
|
|
The connection span.
|
|
"""
|
|
attributes: dict[str, AttributeValue] = tracer._get_common_attributes(operation_name="bidi_connect")
|
|
|
|
if model_id:
|
|
attributes["gen_ai.request.model"] = model_id
|
|
|
|
return tracer._start_span("bidi_connect", parent_span, attributes=attributes)
|
|
|
|
|
|
def end_connection_span(tracer: Tracer, span: Span, error: Exception | None = None) -> None:
|
|
"""End the model connection span.
|
|
|
|
Args:
|
|
tracer: Tracer instance.
|
|
span: The connection span to end.
|
|
error: Exception if the connection failed.
|
|
"""
|
|
tracer._end_span(span, error=error)
|
|
|
|
|
|
def add_barge_in_event(span: Span, reason: str) -> None:
|
|
"""Record a barge-in as a span event on the session span.
|
|
|
|
Args:
|
|
span: The session span to add the event to.
|
|
reason: Reason for the barge-in.
|
|
"""
|
|
if span and span.is_recording():
|
|
span.add_event("bidi_barge_in", attributes={"barge_in.reason": reason})
|