@@ -40,7 +40,19 @@ async def on_span_event(self, span, event): ...
4040import inspect
4141import logging
4242import time
43- from typing import TYPE_CHECKING , Any , ClassVar , Literal , Protocol
43+ from typing import (
44+ TYPE_CHECKING ,
45+ Any ,
46+ ClassVar ,
47+ Generic ,
48+ Literal ,
49+ Protocol ,
50+ overload ,
51+ )
52+
53+ # ``typing.TypeVar`` lacks the ``default=`` kwarg on Python <3.13.
54+ # Use the typing_extensions backport so this works on 3.12 too.
55+ from typing_extensions import TypeVar
4456
4557from ..types import messages as messages_
4658
@@ -81,6 +93,15 @@ class RetrievalSpanData:
8193 span_name : ClassVar [str ]
8294
8395
96+ # Covariant: adapters take ``Span`` (= ``Span[SpanData]``) and must
97+ # accept any concretely-typed span. They read ``data``, never replace
98+ # it, so the widened reference is safe in practice.
99+ DataT_co = TypeVar ("DataT_co" , bound = SpanData , default = SpanData , covariant = True )
100+ # Function-scoped variant for ``span()``: a covariant variable can't
101+ # appear as a parameter.
102+ DataT = TypeVar ("DataT" , bound = SpanData )
103+
104+
84105@dataclasses .dataclass
85106class RunSpanData :
86107 """One ``Agent.run``: the whole loop."""
@@ -181,9 +202,13 @@ class SpanEvent:
181202
182203
183204@dataclasses .dataclass
184- class Span :
205+ class Span ( Generic [ DataT_co ]) :
185206 """A record of a unit of work.
186207
208+ Generic in its data type: ``span(RetrievalSpanData(...))`` gives a
209+ ``Span[RetrievalSpanData]``, so late assignments to ``sp.data``
210+ fields are type checked. A bare ``Span`` is ``Span[SpanData]``.
211+
187212 ``replay=True`` marks work that is being replayed (resume,
188213 serverless re-entry) rather than performed live.
189214
@@ -197,7 +222,7 @@ class Span:
197222 """
198223
199224 name : str
200- data : SpanData
225+ data : DataT_co
201226 id : str
202227 trace_id : str
203228 parent_id : str | None
@@ -285,25 +310,65 @@ async def _dispatch(method: str, span_: Span, *args: Any) -> None:
285310 )
286311
287312
288- @contextlib .asynccontextmanager
289- async def span (
313+ @overload
314+ def span (
315+ name_or_data : str ,
316+ / ,
317+ * ,
318+ replay : bool = False ,
319+ set_as_current : bool = True ,
320+ ** attributes : Any ,
321+ ) -> contextlib .AbstractAsyncContextManager [Span [CustomSpanData ]]: ...
322+
323+
324+ @overload
325+ def span (
326+ name_or_data : DataT ,
327+ / ,
328+ * ,
329+ replay : bool = False ,
330+ set_as_current : bool = True ,
331+ ) -> contextlib .AbstractAsyncContextManager [Span [DataT ]]: ...
332+
333+
334+ def span (
290335 name_or_data : str | SpanData ,
291336 / ,
292337 * ,
293338 replay : bool = False ,
294339 set_as_current : bool = True ,
295340 ** attributes : Any ,
296- ) -> AsyncIterator [Span ]:
341+ ) -> contextlib . AbstractAsyncContextManager [Span [ Any ] ]:
297342 """Open a span; it is "current" (parents new spans) inside the block.
298343
299344 Pass a name plus attributes for a user span, or a :class:`SpanData`
300- instance for a typed one. Exceptions are recorded on the span and
301- re-raised.
345+ instance for a typed one — the span is generic in it, so late
346+ assignments to ``sp.data`` fields are type checked. Exceptions are
347+ recorded on the span and re-raised.
302348
303349 ``set_as_current=False`` keeps the span from becoming current:
304350 work done while it is open parents to *its* parent instead. Used by
305351 ai.stream because of the context manager api.
306352 """
353+ # The indirection exists because type checkers can't apply
354+ # ``asynccontextmanager`` to an overloaded function directly.
355+ return _span_impl (
356+ name_or_data ,
357+ replay = replay ,
358+ set_as_current = set_as_current ,
359+ ** attributes ,
360+ )
361+
362+
363+ @contextlib .asynccontextmanager
364+ async def _span_impl (
365+ name_or_data : str | SpanData ,
366+ / ,
367+ * ,
368+ replay : bool ,
369+ set_as_current : bool ,
370+ ** attributes : Any ,
371+ ) -> AsyncIterator [Span [Any ]]:
307372 if isinstance (name_or_data , str ):
308373 name = name_or_data
309374 data : SpanData = CustomSpanData (attributes = dict (attributes ))
0 commit comments