galileo-core 3.82.0__py3-none-any.whl → 3.83.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
galileo_core/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "3.82.0"
1
+ __version__ = "3.83.0"
@@ -6,11 +6,24 @@ from galileo_core.helpers.logger import logger
6
6
 
7
7
 
8
8
  class BaseGalileoException(Exception, ABC):
9
- """Base exception for all exceptions in galileo."""
9
+ """Base exception for all exceptions in galileo.
10
+
11
+ Attributes:
12
+ message: Human-readable error message
13
+ error_code: Optional EMS catalog lookup key for standardized error handling
14
+ LOG_LEVEL: Logging level for this exception type
15
+ """
10
16
 
11
17
  LOG_LEVEL: int = WARNING
12
18
 
13
- def __init__(self, message: str, logging_extra: Optional[Dict[str, Any]] = None) -> None:
19
+ def __init__(
20
+ self,
21
+ message: str,
22
+ logging_extra: Optional[Dict[str, Any]] = None,
23
+ *,
24
+ error_code: Optional[int] = None,
25
+ ) -> None:
14
26
  logger.log(self.LOG_LEVEL, message, extra=logging_extra)
15
27
  self.message: str = message
28
+ self.error_code: Optional[int] = error_code
16
29
  super().__init__(self.message)
@@ -34,6 +34,9 @@ class BaseSpan(BaseStep):
34
34
  class StepWithChildSpans(BaseSpan):
35
35
  spans: List["Span"] = Field(default_factory=list, description="Child spans.")
36
36
  _last_child_created_at: Optional[datetime] = PrivateAttr(default=None)
37
+ # Runtime back-pointer for cursor navigation in TracesLogger (not serialized).
38
+ # This enables O(1) navigation up the trace hierarchy without maintaining a stack.
39
+ _parent: Optional["StepWithChildSpans"] = PrivateAttr(default=None)
37
40
 
38
41
  def add_child_spans(self, spans: Sequence["Span"]) -> None:
39
42
  self.spans.extend(spans)
@@ -1,9 +1,10 @@
1
1
  from collections import deque
2
+ from contextvars import ContextVar
2
3
  from datetime import datetime
3
4
  from json import dumps
4
- from typing import Deque, Dict, List, Optional
5
+ from typing import Any, Deque, Dict, List, Mapping, Optional
5
6
 
6
- from pydantic import BaseModel, Field
7
+ from pydantic import BaseModel, ConfigDict, Field, PrivateAttr
7
8
  from pydantic.types import UUID4
8
9
 
9
10
  from galileo_core.schemas.logging.agent import AgentType
@@ -27,11 +28,127 @@ from galileo_core.utils.json import PydanticJsonEncoder
27
28
 
28
29
 
29
30
  class TracesLogger(BaseModel):
31
+ """Logger for managing traces and spans with async-safe parent tracking.
32
+
33
+ Each TracesLogger instance maintains its own isolated ContextVar for tracking
34
+ the current parent in the trace hierarchy. This allows:
35
+ - Multiple TracesLogger instances to operate independently
36
+ - Async-safe parent tracking within each instance (via ContextVar semantics)
37
+ """
38
+
39
+ model_config = ConfigDict(arbitrary_types_allowed=True)
30
40
  traces: List[Trace] = Field(default_factory=list, description="List of traces.")
31
- _parent_stack: Deque[StepWithChildSpans] = deque()
41
+
42
+ # Per-instance ContextVar for async-safe parent tracking.
43
+ # Each instance has its own ContextVar, providing isolation between logger instances.
44
+ _parent_context_var: ContextVar[Optional[StepWithChildSpans]] = PrivateAttr()
45
+
46
+ def _create_context_var(self) -> None:
47
+ """Create a new ContextVar for this instance.
48
+
49
+ Each instance must have its own ContextVar to maintain isolation.
50
+ """
51
+ self._parent_context_var = ContextVar(f"_current_parent_{id(self)}", default=None)
52
+
53
+ def model_post_init(self, __context: Any) -> None:
54
+ """Initialize the per-instance ContextVar after model construction."""
55
+ self._create_context_var()
56
+
57
+ def model_copy(self, *, update: Optional[Mapping[str, Any]] = None, deep: bool = False) -> "TracesLogger":
58
+ """Override model_copy to create a fresh ContextVar on the copy.
59
+
60
+ Pydantic's model_copy bypasses model_post_init and would share the
61
+ same ContextVar reference, so we must create a new one for isolation.
62
+ """
63
+ copied = super().model_copy(update=update, deep=deep)
64
+ copied._create_context_var()
65
+ return copied
66
+
67
+ def __copy__(self) -> "TracesLogger":
68
+ """Create fresh ContextVar when using copy.copy()."""
69
+ copied = super().__copy__()
70
+ copied._create_context_var()
71
+ return copied
72
+
73
+ def __deepcopy__(self, memo: Optional[Dict[int, Any]] = None) -> "TracesLogger":
74
+ """Create fresh ContextVar when using copy.deepcopy().
75
+
76
+ ContextVar objects cannot be pickled, so we must handle deepcopy
77
+ by creating a new instance with deepcopied data, then creating
78
+ a fresh ContextVar.
79
+ """
80
+ import copy
81
+
82
+ if memo is None:
83
+ memo = {}
84
+
85
+ # Deepcopy the traces (the main data)
86
+ copied_traces = copy.deepcopy(self.traces, memo)
87
+
88
+ # Create new instance with copied data
89
+ copied = TracesLogger(traces=copied_traces)
90
+ # model_post_init already creates the ContextVar for new instances
91
+
92
+ # Register in memo to handle circular references
93
+ memo[id(self)] = copied
94
+
95
+ return copied
96
+
97
+ def _set_current_parent(self, parent: Optional[StepWithChildSpans]) -> None:
98
+ """Set the current parent for the current async context."""
99
+ self._parent_context_var.set(parent)
32
100
 
33
101
  def current_parent(self) -> Optional[StepWithChildSpans]:
34
- return self._parent_stack[-1] if self._parent_stack else None
102
+ """Get the current parent span/trace for the current async context."""
103
+ return self._parent_context_var.get()
104
+
105
+ def reset_parent_tracking(self) -> None:
106
+ """Reset the current parent tracking for this logger instance.
107
+
108
+ Call this when reinitializing the logger or switching contexts.
109
+ """
110
+ self._parent_context_var.set(None)
111
+
112
+ @property
113
+ def _parent_stack(self) -> Deque[StepWithChildSpans]:
114
+ """Reconstruct the parent stack by walking up the _parent chain.
115
+
116
+ This property provides backward compatibility for code that accesses
117
+ _parent_stack directly. The returned deque represents the path from
118
+ root to current position.
119
+
120
+ Note: This is a read-only view. Mutations to this deque will not
121
+ affect the actual parent tracking. Use the span methods instead.
122
+ """
123
+ path: List[StepWithChildSpans] = []
124
+ current = self.current_parent()
125
+ while current is not None:
126
+ path.append(current)
127
+ current = current._parent
128
+ # Reverse to get root-to-current order (like a stack bottom-to-top)
129
+ return deque(reversed(path))
130
+
131
+ @_parent_stack.setter
132
+ def _parent_stack(self, value: Deque[StepWithChildSpans]) -> None:
133
+ """Set the current parent from a deque (for backward compatibility).
134
+
135
+ If deque is empty, clears the current parent.
136
+ Otherwise, sets current parent to the last item and rebuilds _parent chain.
137
+ """
138
+ if not value:
139
+ self._set_current_parent(None)
140
+ return
141
+
142
+ # Build the _parent chain from the deque
143
+ items = list(value)
144
+ for i, item in enumerate(items):
145
+ if i == 0:
146
+ item._parent = None
147
+ else:
148
+ item._parent = items[i - 1]
149
+
150
+ # Set current parent to the top of the stack (last item)
151
+ self._set_current_parent(items[-1])
35
152
 
36
153
  def add_child_span_to_parent(self, span: Span) -> None:
37
154
  current_parent = self.current_parent()
@@ -107,8 +224,9 @@ class TracesLogger(BaseModel):
107
224
  external_id=external_id,
108
225
  id=id,
109
226
  )
227
+ trace._parent = None # Trace is root
110
228
  self.traces.append(trace)
111
- self._parent_stack.append(trace)
229
+ self._set_current_parent(trace)
112
230
  return trace
113
231
 
114
232
  def add_single_llm_span_trace(
@@ -219,7 +337,7 @@ class TracesLogger(BaseModel):
219
337
  )
220
338
  self.traces.append(trace)
221
339
  # Single span traces are automatically concluded so we reset the current parent.
222
- self._parent_stack = deque()
340
+ self._set_current_parent(None)
223
341
  return trace
224
342
 
225
343
  def add_llm_span(
@@ -443,6 +561,7 @@ class TracesLogger(BaseModel):
443
561
  -------
444
562
  WorkflowSpan: The created span.
445
563
  """
564
+ parent = self.current_parent()
446
565
  span = WorkflowSpan(
447
566
  input=input,
448
567
  redacted_input=redacted_input,
@@ -456,8 +575,9 @@ class TracesLogger(BaseModel):
456
575
  id=id,
457
576
  step_number=step_number,
458
577
  )
578
+ span._parent = parent # Set back-pointer before adding to parent
459
579
  self.add_child_span_to_parent(span)
460
- self._parent_stack.append(span)
580
+ self._set_current_parent(span)
461
581
  return span
462
582
 
463
583
  def add_agent_span(
@@ -492,6 +612,7 @@ class TracesLogger(BaseModel):
492
612
  -------
493
613
  AgentSpan: The created span.
494
614
  """
615
+ parent = self.current_parent()
495
616
  span = AgentSpan(
496
617
  input=input,
497
618
  redacted_input=redacted_input,
@@ -506,8 +627,9 @@ class TracesLogger(BaseModel):
506
627
  id=id,
507
628
  step_number=step_number,
508
629
  )
630
+ span._parent = parent # Set back-pointer before adding to parent
509
631
  self.add_child_span_to_parent(span)
510
- self._parent_stack.append(span)
632
+ self._set_current_parent(span)
511
633
  return span
512
634
 
513
635
  def conclude(
@@ -542,7 +664,10 @@ class TracesLogger(BaseModel):
542
664
  if duration_ns is not None:
543
665
  current_parent.metrics.duration_ns = duration_ns
544
666
 
545
- finished_step = self._parent_stack.pop()
546
- if self.current_parent() is None and not isinstance(finished_step, Trace):
667
+ # Navigate up to parent via _parent pointer
668
+ parent = current_parent._parent
669
+ self._set_current_parent(parent)
670
+
671
+ if parent is None and not isinstance(current_parent, Trace):
547
672
  raise ValueError("Finished step is not a trace, but has no parent. Not added to the list of traces.")
548
673
  return self.current_parent()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: galileo-core
3
- Version: 3.82.0
3
+ Version: 3.83.0
4
4
  Summary: Shared schemas and configuration for Galileo's Python packages.
5
5
  License: Apache-2.0
6
6
  Keywords: llm,quality,language_models,galileo
@@ -1,4 +1,4 @@
1
- galileo_core/__init__.py,sha256=Za0uTMfFaftHcrL2K7d8-r1a_Sek_wUhBGK_J52fbow,23
1
+ galileo_core/__init__.py,sha256=FJQtOiI1SJSt2a463wEccA_T8wmbbGEon12b3WLW6k0,23
2
2
  galileo_core/constants/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
3
  galileo_core/constants/config.py,sha256=MYvixj3X3CoiC38QVMdidApURKFOAaPaYoHhFESOJlU,329
4
4
  galileo_core/constants/dataset_format.py,sha256=H6ZigDd2QMpRcRhLC38e0hRoMh0kKbvdhbmS5yK1gZU,116
@@ -9,7 +9,7 @@ galileo_core/constants/request_method.py,sha256=szwggZ9BPQwY7qVFdOJRREfFfp-wT_LO
9
9
  galileo_core/constants/routes.py,sha256=q0EJ6oidfkUAlRAF7UUXw2_fYO5L5ejGzqbN7Ut0JOI,1577
10
10
  galileo_core/constants/scorers.py,sha256=5Mojtg2arv03o7fxuUI64x9L36OGButJvwWVeM8LQrE,1229
11
11
  galileo_core/exceptions/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- galileo_core/exceptions/base.py,sha256=EHgqf3DAocpWyGZa4HwQYUDMYZQH0MCzDEQBoAtLS2g,502
12
+ galileo_core/exceptions/base.py,sha256=9eZQ0ak_fcvt8ywJWJg9wmkgKVCxcZ2U1BDvclNQGFw,847
13
13
  galileo_core/exceptions/execution.py,sha256=oR7h-aw1qgBfxKRiB6re2IrTavkrygC8ybE-AWiRTKM,2754
14
14
  galileo_core/exceptions/http.py,sha256=lW3oIcxb3U5l9ZfsiPZmKHxQiqG-ASK3CJtD4WK8n5w,343
15
15
  galileo_core/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -73,7 +73,7 @@ galileo_core/schemas/logging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NM
73
73
  galileo_core/schemas/logging/agent.py,sha256=zexqP7o_wjicj4H1ZhrF9wkaYBxcj-skLv_LXj8H7gU,252
74
74
  galileo_core/schemas/logging/llm.py,sha256=BN2oya2sqAWdOxthN2bCWCfDXEsjKBTCXjBYWh4Q758,7521
75
75
  galileo_core/schemas/logging/session.py,sha256=R7J5B33ffdQrNpzj7HEBLjQ3rKqNRuHOYttO_-PK378,751
76
- galileo_core/schemas/logging/span.py,sha256=k4UdVNV4fbUaCQ4ZsKir125jbAnN3ZD1vDNFA7fVB1I,12480
76
+ galileo_core/schemas/logging/span.py,sha256=smaGm3kvghRi3seaFdMKcgdvRF0ZLerptBIZAh-I6i4,12722
77
77
  galileo_core/schemas/logging/step.py,sha256=rRwRSeFbUFbnWg6mOfclo3s6VTtPayLE2-7qBMwqKFQ,8702
78
78
  galileo_core/schemas/logging/trace.py,sha256=irLlf6XyPL0k326A3u_dfN7sb6TzEdfR8veCXWB3c3o,925
79
79
  galileo_core/schemas/logging/tree.py,sha256=eC9LR10zARWYl7DFJzGvQrTTaEnaB02Jyxy0NyctZ9c,3188
@@ -109,7 +109,7 @@ galileo_core/schemas/shared/scorers/scorer_configuration.py,sha256=hSJ-rObA_Wqff
109
109
  galileo_core/schemas/shared/scorers/scorer_name.py,sha256=9MbDQzObVcmJl53AmmD-xrBM7Qxmm2YhNBr4RNhn2YE,3586
110
110
  galileo_core/schemas/shared/scorers/scorer_type.py,sha256=04ZPqHkvRR6b9ARitQJhGfn-9nKP4fNH_n1noeEt08c,1401
111
111
  galileo_core/schemas/shared/scorers/scorers.py,sha256=3OA8-QYkCslo7IOHrHQ5kVKRfBbaFvt5gG7WVTzi9pc,4962
112
- galileo_core/schemas/shared/traces_logger.py,sha256=HXydSyfHmizxcqYFzwAKYdamH1lfgRrxXThh1yirjYU,23498
112
+ galileo_core/schemas/shared/traces_logger.py,sha256=Xulis-c_pYXDm1ejyRxTWkMivFNtl6yYnuVL0xsd26c,28490
113
113
  galileo_core/schemas/shared/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
114
114
  galileo_core/schemas/shared/workflows/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
115
115
  galileo_core/schemas/shared/workflows/node_type.py,sha256=CKiYkOanhE9vtseAikj0UmWCiOnEjSLLiIISScl6xE8,241
@@ -122,8 +122,8 @@ galileo_core/utils/dataset.py,sha256=wjm3NdNRuxj5mK7GqQUdA-Map_EB0SpFfM1QKapd6-A
122
122
  galileo_core/utils/json.py,sha256=ug5P-iozgmqXRhLZS3Dm_JPViTICboKukg1mqqBq2i4,327
123
123
  galileo_core/utils/name.py,sha256=kaDeZJ1K3-SSazYrUeSbUsQrnZlnOBYU42t-QvX-coE,204
124
124
  galileo_core/utils/scorer_validation.py,sha256=bjNmDZhNsmmb8qHBfndXVAL60W5nCvoDdKrAoAegxJg,11259
125
- galileo_core-3.82.0.dist-info/LICENSE,sha256=4D_6tarPnIYkAqF-0LzrodE9wFtSulO3V4RZf__MriE,10946
126
- galileo_core-3.82.0.dist-info/METADATA,sha256=wBo3uAicViV37ZZy7RtvYX4rpgie7DQJVlevlvsKICY,2731
127
- galileo_core-3.82.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
128
- galileo_core-3.82.0.dist-info/entry_points.txt,sha256=epOvdlN6_7ua8stNHURhJhXDIIrheccwYv0Z-QFmHJU,61
129
- galileo_core-3.82.0.dist-info/RECORD,,
125
+ galileo_core-3.83.0.dist-info/LICENSE,sha256=4D_6tarPnIYkAqF-0LzrodE9wFtSulO3V4RZf__MriE,10946
126
+ galileo_core-3.83.0.dist-info/METADATA,sha256=6mkFU2IxzmZsCxyLavQbEqoaFHshCZq-uurvR8o29EQ,2731
127
+ galileo_core-3.83.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
128
+ galileo_core-3.83.0.dist-info/entry_points.txt,sha256=epOvdlN6_7ua8stNHURhJhXDIIrheccwYv0Z-QFmHJU,61
129
+ galileo_core-3.83.0.dist-info/RECORD,,