Home Assistant Unofficial Reference 2024.12.1
trace.py
Go to the documentation of this file.
1 """Debug traces for conversation."""
2 
3 from collections.abc import Generator
4 from contextlib import contextmanager
5 from contextvars import ContextVar
6 from dataclasses import asdict, dataclass, field
7 import enum
8 from typing import Any
9 
10 from homeassistant.util import dt as dt_util, ulid as ulid_util
11 from homeassistant.util.limited_size_dict import LimitedSizeDict
12 
13 STORED_TRACES = 3
14 
15 
16 class ConversationTraceEventType(enum.StrEnum):
17  """Type of an event emitted during a conversation."""
18 
19  ASYNC_PROCESS = "async_process"
20  """The conversation is started from user input."""
21 
22  AGENT_DETAIL = "agent_detail"
23  """Event detail added by a conversation agent."""
24 
25  TOOL_CALL = "tool_call"
26  """A conversation agent Tool call or default agent intent call."""
27 
28 
29 @dataclass(frozen=True)
31  """Event emitted during a conversation."""
32 
33  event_type: ConversationTraceEventType
34  data: dict[str, Any] | None = None
35  timestamp: str = field(default_factory=lambda: dt_util.utcnow().isoformat())
36 
37 
39  """Stores debug data related to a conversation."""
40 
41  def __init__(self) -> None:
42  """Initialize ConversationTrace."""
43  self._trace_id_trace_id = ulid_util.ulid_now()
44  self._events: list[ConversationTraceEvent] = []
45  self._error_error: Exception | None = None
46  self._result_result: dict[str, Any] = {}
47 
48  @property
49  def trace_id(self) -> str:
50  """Identifier for this trace."""
51  return self._trace_id_trace_id
52 
53  def add_event(self, event: ConversationTraceEvent) -> None:
54  """Add an event to the trace."""
55  self._events.append(event)
56 
57  def set_error(self, ex: Exception) -> None:
58  """Set error."""
59  self._error_error = ex
60 
61  def set_result(self, **kwargs: Any) -> None:
62  """Set result."""
63  self._result_result = {**kwargs}
64 
65  def as_dict(self) -> dict[str, Any]:
66  """Return dictionary version of this ConversationTrace."""
67  result: dict[str, Any] = {
68  "id": self._trace_id_trace_id,
69  "events": [asdict(event) for event in self._events],
70  }
71  if self._error_error is not None:
72  result["error"] = str(self._error_error) or self._error_error.__class__.__name__
73  if self._result_result is not None:
74  result["result"] = self._result_result
75  return result
76 
77 
78 _current_trace: ContextVar[ConversationTrace | None] = ContextVar(
79  "current_trace", default=None
80 )
81 _recent_traces: LimitedSizeDict[str, ConversationTrace] = LimitedSizeDict(
82  size_limit=STORED_TRACES
83 )
84 
85 
87  event_type: ConversationTraceEventType, event_data: dict[str, Any]
88 ) -> None:
89  """Append a ConversationTraceEvent to the current active trace."""
90  trace = _current_trace.get()
91  if not trace:
92  return
93  trace.add_event(ConversationTraceEvent(event_type, event_data))
94 
95 
96 @contextmanager
97 def async_conversation_trace() -> Generator[ConversationTrace]:
98  """Create a new active ConversationTrace."""
99  trace = ConversationTrace()
100  token = _current_trace.set(trace)
101  _recent_traces[trace.trace_id] = trace
102  try:
103  yield trace
104  except Exception as ex:
105  trace.set_error(ex)
106  raise
107  finally:
108  _current_trace.reset(token)
109 
110 
111 def async_get_traces() -> list[ConversationTrace]:
112  """Get the most recent traces."""
113  return list(_recent_traces.values())
114 
115 
116 def async_clear_traces() -> None:
117  """Clear all traces."""
118  _recent_traces.clear()
None add_event(self, ConversationTraceEvent event)
Definition: trace.py:53
list[ConversationTrace] async_get_traces()
Definition: trace.py:111
None async_conversation_trace_append(ConversationTraceEventType event_type, dict[str, Any] event_data)
Definition: trace.py:88
Generator[ConversationTrace] async_conversation_trace()
Definition: trace.py:97