traceAI-transformers 0.1.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.
@@ -0,0 +1,79 @@
1
+ """Trace local Transformers pipelines, independently of remote Hub clients."""
2
+ from typing import Any, Collection
3
+
4
+ from fi_instrumentation import FITracer, TraceConfig
5
+ from opentelemetry import trace
6
+ from opentelemetry.instrumentation.instrumentor import BaseInstrumentor
7
+ from wrapt import FunctionWrapper
8
+
9
+ from ._wrappers import _PipelineWrapper
10
+ from .version import __version__
11
+
12
+ # Look up optional historical classes individually: Transformers 5 removed them.
13
+ _TARGETS = (
14
+ ("TextGenerationPipeline", "text-generation", "LLM"),
15
+ ("FeatureExtractionPipeline", "feature-extraction", "EMBEDDING"),
16
+ ("Text2TextGenerationPipeline", "text2text-generation", "LLM"),
17
+ ("SummarizationPipeline", "summarization", "LLM"),
18
+ ("TranslationPipeline", "translation", "LLM"),
19
+ ("QuestionAnsweringPipeline", "question-answering", "CHAIN"),
20
+ )
21
+
22
+
23
+ class TransformersInstrumentor(BaseInstrumentor):
24
+ """Instrument supported pipeline calls; bare ``model.generate`` is not patched.
25
+
26
+ ``capture_content=False`` (default) records metadata only. Opt in to bounded
27
+ input/output previews with ``capture_content=True``; ``config=TraceConfig``
28
+ still controls privacy. Embedding vectors and lazy result payloads are never
29
+ collected. Supply ``tracer_provider`` to avoid changing the global provider.
30
+ """
31
+
32
+ def instrumentation_dependencies(self) -> Collection[str]:
33
+ return ("transformers>=4.42,<6",)
34
+
35
+ def _instrument(self, **kwargs: Any) -> None:
36
+ import transformers.pipelines as pipelines
37
+
38
+ config = kwargs.get("config")
39
+ if config is None:
40
+ config = TraceConfig()
41
+ if not isinstance(config, TraceConfig):
42
+ raise TypeError("config must be a TraceConfig")
43
+ capture = kwargs.get("capture_content", False)
44
+ if not isinstance(capture, bool):
45
+ raise TypeError("capture_content must be a bool")
46
+ tracer = FITracer(
47
+ trace.get_tracer(__name__, __version__, kwargs.get("tracer_provider")),
48
+ config=config,
49
+ )
50
+ self._patches = []
51
+ try:
52
+ for name, task, kind in _TARGETS:
53
+ cls = getattr(pipelines, name, None)
54
+ if cls is None:
55
+ continue
56
+ own_method = "__call__" in vars(cls)
57
+ original = vars(cls)["__call__"] if own_method else cls.__call__
58
+ wrapper = _PipelineWrapper(tracer=tracer, config=config, capture=capture, task=task, kind=kind)
59
+ patched = FunctionWrapper(original, wrapper)
60
+ self._patches.append((cls, own_method, original, patched, wrapper))
61
+ cls.__call__ = patched
62
+ except BaseException:
63
+ self._uninstrument()
64
+ raise
65
+
66
+ def _uninstrument(self, **kwargs: Any) -> None:
67
+ for cls, own_method, original, patched, wrapper in reversed(self._patches):
68
+ # An outer, third-party patch may still hold ours: disable it without
69
+ # overwriting their patch. In-flight lazy iterators finish normally.
70
+ wrapper.enabled = False
71
+ if vars(cls).get("__call__") is patched:
72
+ if own_method:
73
+ cls.__call__ = original
74
+ else:
75
+ delattr(cls, "__call__")
76
+ self._patches = []
77
+
78
+
79
+ __all__ = ["TransformersInstrumentor", "__version__"]
@@ -0,0 +1,336 @@
1
+ """Bounded metadata and iteration-scoped tracing without touching model outputs."""
2
+ import json
3
+ import logging
4
+ from collections.abc import Iterator
5
+ from contextlib import contextmanager
6
+ from contextvars import ContextVar
7
+ from copy import copy
8
+ from itertools import islice
9
+ from time import time_ns
10
+ from types import GeneratorType
11
+ from typing import Any, Callable
12
+
13
+ from fi_instrumentation import FITracer, TraceConfig
14
+ from opentelemetry import context, trace
15
+ from opentelemetry.context import _SUPPRESS_INSTRUMENTATION_KEY
16
+ from opentelemetry.trace import Status, StatusCode
17
+ from wrapt import ObjectProxy
18
+
19
+ _ACTIVE: ContextVar[tuple[int, ...]] = ContextVar("traceai_transformers_active", default=())
20
+ _MAX_PREVIEW = 4096
21
+ logger = logging.getLogger(__name__)
22
+ logger.addHandler(logging.NullHandler())
23
+
24
+
25
+ def _log_failure(action: str, error: Exception) -> None:
26
+ # Telemetry is fail-open, but not silent. Do not log exception messages or
27
+ # traceback source lines: they can bypass TraceConfig's content privacy.
28
+ logger.warning("Transformers instrumentation failed to %s (%s)", action, type(error).__name__)
29
+
30
+
31
+ def _record_error(span, error: BaseException, config: TraceConfig) -> None:
32
+ if config.pii_redaction or config.hide_error_details:
33
+ # Supported FI core versions mask supplied attributes, but not the
34
+ # message/traceback generated by record_exception or status descriptions.
35
+ # Omit those surfaces entirely, including chained errors and notes;
36
+ # never stringify or mutate the exception being returned to the caller.
37
+ cls = type(error)
38
+ exception_type = cls.__qualname__
39
+ if cls.__module__ and cls.__module__ != "builtins":
40
+ exception_type = f"{cls.__module__}.{exception_type}"
41
+ span.add_event("exception", {
42
+ "exception.type": exception_type,
43
+ "exception.escaped": "False",
44
+ })
45
+ span.set_status(StatusCode.ERROR)
46
+ else:
47
+ span.record_exception(error)
48
+ span.set_status(Status(StatusCode.ERROR, str(error)))
49
+
50
+
51
+ def _preview(value: object) -> str:
52
+ # No repr/default=str, tensor conversion, custom iteration or full JSON
53
+ # serialization of unbounded collections. A global node budget also bounds
54
+ # nested batches, not just each individual list.
55
+ budget = [64]
56
+
57
+ def trim(item, depth=0):
58
+ budget[0] -= 1
59
+ if budget[0] < 0 or depth > 5:
60
+ return "[truncated]"
61
+ if type(item) is str:
62
+ return item[:_MAX_PREVIEW]
63
+ if item is None or type(item) in (bool, int, float):
64
+ return item
65
+ if type(item) in (list, tuple):
66
+ return [trim(v, depth + 1) for v in item[:16] if budget[0] > 0]
67
+ if type(item) is dict:
68
+ result = {}
69
+ for key, val in islice(item.items(), 16):
70
+ if budget[0] <= 0 or len(result) >= 16:
71
+ break
72
+ if type(key) is str:
73
+ result[key[:128]] = trim(val, depth + 1)
74
+ return result
75
+ return "[not captured]"
76
+
77
+ return json.dumps(trim(value), ensure_ascii=True)[:_MAX_PREVIEW]
78
+
79
+
80
+ class _Invocation:
81
+ def __init__(self, wrapper, instance, value, parent):
82
+ self.tracer = wrapper.tracer
83
+ self.config = wrapper.config
84
+ self.identity = id(instance)
85
+ self.parent = parent
86
+ self.name = "transformers." + wrapper.task
87
+ self.attributes = {
88
+ "gen_ai.span.kind": wrapper.kind,
89
+ "gen_ai.provider.name": "huggingface",
90
+ "gen_ai.operation.name": (
91
+ "embeddings" if wrapper.kind == "EMBEDDING" else
92
+ "text_completion" if wrapper.kind == "LLM" else "question_answering"
93
+ ),
94
+ "transformers.task": wrapper.task,
95
+ }
96
+ # Model architecture is safe metadata, not a local filesystem path or
97
+ # arbitrary user-supplied model identifier.
98
+ model_type = getattr(getattr(getattr(instance, "model", None), "config", None), "model_type", None)
99
+ if type(model_type) is str:
100
+ self.attributes["gen_ai.request.model"] = model_type[:256]
101
+ # Generation responses can contain the entire original prompt/chat.
102
+ # Never re-export hidden inputs through a full-text output preview.
103
+ self.capture_output = wrapper.capture and wrapper.kind != "EMBEDDING" and not (
104
+ wrapper.config.hide_outputs or wrapper.config.hide_output_text or
105
+ wrapper.config.hide_output_messages or wrapper.config.hide_inputs or
106
+ wrapper.config.hide_input_text or wrapper.config.hide_input_messages
107
+ )
108
+ if wrapper.capture and not (
109
+ wrapper.config.hide_inputs or wrapper.config.hide_input_text or
110
+ wrapper.config.hide_input_messages
111
+ ):
112
+ self.attributes["input.value"] = _preview(value)
113
+ self.span = None
114
+ self.started = False
115
+ self.finished = False
116
+
117
+ def start(self, start_time=None):
118
+ if not self.started:
119
+ self.started = True
120
+ try:
121
+ self.span = self.tracer.start_span(
122
+ self.name, context=self.parent, attributes=self.attributes, start_time=start_time,
123
+ )
124
+ except Exception as error:
125
+ # An exporter/provider failure must not prevent local inference.
126
+ _log_failure("start span", error)
127
+ self.attributes = None
128
+
129
+ @contextmanager
130
+ def activate(self):
131
+ token = context.attach(self.parent)
132
+ active = _ACTIVE.set((*_ACTIVE.get(), self.identity))
133
+ try:
134
+ self.start()
135
+ if self.span is None:
136
+ yield
137
+ else:
138
+ with trace.use_span(self.span, end_on_exit=False, record_exception=False,
139
+ set_status_on_exception=False):
140
+ yield
141
+ finally:
142
+ _ACTIVE.reset(active)
143
+ context.detach(token)
144
+
145
+ def finish(self, error=None, output=None):
146
+ if self.finished:
147
+ return
148
+ self.finished = True
149
+ if self.span is None:
150
+ return
151
+ try:
152
+ if error is not None:
153
+ _record_error(self.span, error, self.config)
154
+ else:
155
+ if self.capture_output and output is not None:
156
+ self.span.set_attribute("output.value", _preview(output))
157
+ self.span.set_status(StatusCode.OK)
158
+ except Exception as telemetry_error:
159
+ _log_failure("record span result", telemetry_error)
160
+ finally:
161
+ try:
162
+ self.span.end()
163
+ except Exception as telemetry_error:
164
+ _log_failure("end span", telemetry_error)
165
+
166
+
167
+ class _TracedIterator(Iterator):
168
+ """One span per lazy call, current only while the producer executes.
169
+
170
+ Payloads are never accumulated. Close an abandoned iterator explicitly;
171
+ finalization is only a best-effort fallback, as with Python generators.
172
+ """
173
+ def __init__(self, wrapped, invocation, *, iterator=None):
174
+ self._wrapped = wrapped
175
+ self._iterator = iterator
176
+ self._invocation = invocation
177
+ self._closed = False
178
+
179
+ def __next__(self):
180
+ if self._closed:
181
+ raise StopIteration
182
+ try:
183
+ with self._invocation.activate():
184
+ if self._iterator is None:
185
+ self._iterator = iter(self._wrapped)
186
+ return next(self._iterator)
187
+ except StopIteration:
188
+ self._closed = True
189
+ self._invocation.finish()
190
+ self._wrapped = self._iterator = None
191
+ raise
192
+ except BaseException as error:
193
+ self._closed = True
194
+ self._invocation.finish(error)
195
+ self._wrapped = self._iterator = None
196
+ raise
197
+
198
+ def close(self):
199
+ if self._closed:
200
+ return
201
+ self._closed = True
202
+ try:
203
+ closer = getattr(self._wrapped, "close", None)
204
+ if closer is not None:
205
+ # Do not start an inference span for an unconsumed iterator.
206
+ if self._invocation.started:
207
+ with self._invocation.activate():
208
+ closer()
209
+ else:
210
+ closer()
211
+ except BaseException as error:
212
+ self._invocation.finish(error)
213
+ raise
214
+ finally:
215
+ self._invocation.finish()
216
+ # Release DataLoader iterators/workers and their last batch promptly.
217
+ self._wrapped = self._iterator = None
218
+
219
+ def __getattr__(self, name):
220
+ return getattr(self._wrapped, name)
221
+
222
+ def __del__(self):
223
+ self._invocation.finish()
224
+
225
+
226
+ class _TracedDataset(ObjectProxy):
227
+ """Keep upstream dataset protocols, with a separate span per traversal.
228
+
229
+ PipelineIterator is an IterableDataset whose __iter__ resets its producer,
230
+ including during a partial traversal. A one-shot Iterator wrapper loses that
231
+ behavior. ObjectProxy preserves __len__ and isinstance(..., IterableDataset),
232
+ which DataLoader uses to choose iterable rather than indexed access.
233
+ """
234
+ def __init__(self, wrapped, invocation):
235
+ super().__init__(wrapped)
236
+ # This template stays unstarted and retains only metadata and context,
237
+ # not source previews, outputs, or a pipeline/model reference.
238
+ self._self_template = invocation
239
+ self._self_cursor = _TracedIterator(wrapped, copy(invocation))
240
+
241
+ def __iter__(self):
242
+ self._self_cursor.close()
243
+ invocation = copy(self._self_template)
244
+ try:
245
+ iterator = iter(self.__wrapped__)
246
+ except BaseException as error:
247
+ invocation.start()
248
+ invocation.finish(error)
249
+ raise
250
+ self._self_cursor = _TracedIterator(self.__wrapped__, invocation, iterator=iterator)
251
+ return self
252
+
253
+ def __next__(self):
254
+ return next(self._self_cursor)
255
+
256
+ def __reduce_ex__(self, protocol):
257
+ # Pickle the native dataset, not this proxy: its invocation retains a
258
+ # tracer provider and parent context which cannot safely cross a spawn
259
+ # boundary (and may contain unpicklable locks or private metadata).
260
+ # Inference in a deserialized DataLoader worker is deliberately untraced.
261
+ return self.__wrapped__.__reduce_ex__(protocol)
262
+
263
+ def close(self):
264
+ # Explicit close ends the current traversal; a later iter() retains
265
+ # the underlying dataset's own ability (or inability) to start again.
266
+ self._self_cursor.close()
267
+
268
+
269
+ def _input(args, kwargs):
270
+ if args:
271
+ return args[0]
272
+ for key in ("inputs", "text_inputs", "texts", "X"):
273
+ if key in kwargs:
274
+ return kwargs[key]
275
+ # Extractive QA uses named question/context rather than a single input.
276
+ return {key: kwargs[key] for key in ("question", "context") if key in kwargs}
277
+
278
+
279
+ def _lazy_input(value, task):
280
+ if task == "question-answering":
281
+ # Upstream's QA argument handler materializes its inputs eagerly.
282
+ return False
283
+ from torch.utils.data import Dataset
284
+ return isinstance(value, (GeneratorType, Dataset))
285
+
286
+
287
+ class _PipelineWrapper:
288
+ def __init__(self, *, tracer: FITracer, config: TraceConfig, capture: bool, task: str, kind: str) -> None:
289
+ self.tracer, self.config, self.capture = tracer, config, capture
290
+ self.task, self.kind = task, kind
291
+ self.enabled = True
292
+
293
+ def __call__(self, wrapped: Callable[..., Any], instance: Any,
294
+ args: tuple[Any, ...], kwargs: dict[str, Any]) -> Any:
295
+ if not self.enabled or id(instance) in _ACTIVE.get() or context.get_value(_SUPPRESS_INSTRUMENTATION_KEY):
296
+ return wrapped(*args, **kwargs)
297
+ try:
298
+ value = _input(args, kwargs)
299
+ invocation = _Invocation(self, instance, value, context.get_current())
300
+ lazy = _lazy_input(value, self.task)
301
+ except Exception as error:
302
+ _log_failure("prepare span metadata", error)
303
+ return wrapped(*args, **kwargs)
304
+ if lazy:
305
+ # The framework sets up the iterator here but does not run inference.
306
+ # Guard super().__call__ wrappers without creating a premature span.
307
+ active = _ACTIVE.set((*_ACTIVE.get(), id(instance)))
308
+ call_start = time_ns()
309
+ try:
310
+ result = wrapped(*args, **kwargs)
311
+ except BaseException as error:
312
+ invocation.start(start_time=call_start)
313
+ invocation.finish(error)
314
+ raise
315
+ finally:
316
+ _ACTIVE.reset(active)
317
+ if isinstance(result, Iterator):
318
+ # Do not keep even bounded source previews for lazy streams.
319
+ invocation.attributes.pop("input.value", None)
320
+ from torch.utils.data import IterableDataset
321
+ if isinstance(result, IterableDataset):
322
+ return _TracedDataset(result, invocation)
323
+ return _TracedIterator(result, invocation)
324
+ # Some framework/backend paths materialize the result; avoid
325
+ # consuming or transforming it to manufacture lazy behavior.
326
+ invocation.start(start_time=call_start)
327
+ invocation.finish(output=result)
328
+ return result
329
+ try:
330
+ with invocation.activate():
331
+ result = wrapped(*args, **kwargs)
332
+ except BaseException as error:
333
+ invocation.finish(error)
334
+ raise
335
+ invocation.finish(output=result)
336
+ return result
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0" # x-release-please-version
@@ -0,0 +1,103 @@
1
+ Metadata-Version: 2.5
2
+ Name: traceAI-transformers
3
+ Version: 0.1.0
4
+ Summary: OpenTelemetry instrumentation for local Hugging Face Transformers pipelines
5
+ Project-URL: Repository, https://github.com/future-agi/traceAI/tree/dev/python/frameworks/transformers
6
+ Project-URL: Documentation, https://docs.futureagi.com/docs/tracing
7
+ Author-email: Future AGI <no-reply@futureagi.com>
8
+ License-Expression: Apache-2.0
9
+ Requires-Python: <3.14,>=3.10
10
+ Requires-Dist: fi-instrumentation-otel<2,>=1.2.2
11
+ Requires-Dist: opentelemetry-instrumentation<1,>=0.50b0
12
+ Requires-Dist: transformers<6,>=4.42
13
+ Requires-Dist: wrapt<2,>=1.15
14
+ Provides-Extra: dev
15
+ Requires-Dist: mypy<2,>=1.15; extra == 'dev'
16
+ Requires-Dist: pytest<9,>=8; extra == 'dev'
17
+ Requires-Dist: torch<3,>=2.6; extra == 'dev'
18
+ Requires-Dist: traceai-huggingface>=0.1.0; extra == 'dev'
19
+ Description-Content-Type: text/markdown
20
+
21
+ # traceAI Transformers
22
+
23
+ Trace local Hugging Face Transformers pipeline inference in Future AGI. For remote Hugging Face InferenceClient calls, use `traceAI-huggingface` instead; the two instrumentors can run together.
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ pip install traceAI-transformers torch
29
+ ```
30
+
31
+ Python 3.10–3.13 and Transformers 4.42–5.x are supported. Transformers 5 requires Python 3.11 or later. Use PyTorch pipelines; select the appropriate PyTorch build for your CPU or GPU.
32
+
33
+ ## Trace inference
34
+
35
+ Set `FI_API_KEY` and `FI_SECRET_KEY` in your environment, then configure tracing before calling your pipeline:
36
+
37
+ ```python
38
+ import os
39
+ from fi_instrumentation import register
40
+ from fi_instrumentation.fi_types import ProjectType
41
+ from traceai_transformers import TransformersInstrumentor
42
+ from transformers import pipeline
43
+
44
+ provider = register(
45
+ project_name="local-transformers",
46
+ project_type=ProjectType.OBSERVE,
47
+ headers={
48
+ "FI_API_KEY": os.environ["FI_API_KEY"],
49
+ "FI_SECRET_KEY": os.environ["FI_SECRET_KEY"],
50
+ },
51
+ verbose=False,
52
+ )
53
+ TransformersInstrumentor().instrument(tracer_provider=provider)
54
+
55
+ generate = pipeline(
56
+ "text-generation",
57
+ model="HuggingFaceTB/SmolLM2-135M-Instruct",
58
+ device=-1,
59
+ )
60
+ result = generate(
61
+ [{"role": "user", "content": "Explain what a tokenizer does in one sentence."}],
62
+ max_new_tokens=32,
63
+ do_sample=False,
64
+ )
65
+ print(result[0]["generated_text"][-1]["content"])
66
+ provider.force_flush()
67
+ provider.shutdown()
68
+ ```
69
+
70
+ The first run downloads the model. `register` exports OTLP traces to `https://api.futureagi.com/tracer/v1/traces` with `FI_API_KEY` and `FI_SECRET_KEY` headers. This is a traces-only endpoint, not a destination for OTLP metrics or logs. Open the `local-transformers` project in Future AGI to inspect `transformers.text-generation` spans.
71
+
72
+ ## Content and privacy
73
+
74
+ By default, only task, model architecture, timing, status and error information are recorded. Model paths, generation parameters, embedding vectors and token usage estimates are not collected.
75
+
76
+ To include bounded input/output previews:
77
+
78
+ ```python
79
+ from fi_instrumentation import TraceConfig
80
+
81
+ TransformersInstrumentor().instrument(
82
+ tracer_provider=provider,
83
+ capture_content=True,
84
+ config=TraceConfig(hide_error_details=True),
85
+ )
86
+ ```
87
+
88
+ Choose this configuration on your first `instrument()` call, or call `uninstrument()` before changing it. Previews are limited to 4,096 characters and may be truncated. `TraceConfig` input/output privacy flags override content capture. Hiding inputs also suppresses output previews, because pipeline responses can contain the original prompt or conversation. Set `FI_HIDE_ERROR_DETAILS=true` to omit exception messages, stack traces and error descriptions. Error details can contain inputs even when content capture is off.
89
+
90
+ ## Supported calls
91
+
92
+ | Pipeline | Span kind | Versions |
93
+ | --- | --- | --- |
94
+ | Text generation, including chat, batches and streamers | LLM | 4.42–5.x |
95
+ | Feature extraction, including tensor results | EMBEDDING | 4.42–5.x |
96
+ | Text-to-text generation, summarization and translation | LLM | 4.x only |
97
+ | Extractive question answering | CHAIN | 4.x only |
98
+
99
+ Transformers 5 removed the historical text-to-text, summarization, translation and extractive question-answering pipelines. Use a text-generation chat model on 5.x. Direct `model.generate()` calls, other tasks, TensorFlow pipelines, and model loading are not instrumented.
100
+
101
+ Generator and dataset calls remain lazy. Each traversal gets a span that starts when the first result is requested and ends on exhaustion, error or explicit `iterator.close()`. Dataset results preserve their length and `DataLoader` compatibility. Starting another traversal preserves the dataset's reset behavior and ends any unfinished span from the previous traversal. The span is current only while producing the next result, not while your application handles it. Close partially consumed iterators to end their spans promptly. Lazy payloads are not collected. Multiprocessing `DataLoader` workers receive the native dataset through serialization, not the instrumentor's tracer provider or parent context; inference in those workers is not traced by this dataset wrapper. To trace inference inside a worker, initialize its own exporter there and add explicit spans around that worker's inference; the parent instrumentor does not trace a deserialized dataset. Streamer spans end when pipeline inference returns, independently of when queued tokens are consumed. When starting a worker thread, propagate OpenTelemetry context explicitly if it should inherit a parent span.
102
+
103
+ Instrumentation is idempotent and supports `TransformersInstrumentor().uninstrument()` and re-instrumentation. It uses your supplied tracer provider without replacing the global provider.
@@ -0,0 +1,7 @@
1
+ traceai_transformers/__init__.py,sha256=A1tsx1bgfDu6tp1p-AMyCeIA-QQx6UcdmdMGdcIAn28,3362
2
+ traceai_transformers/_wrappers.py,sha256=gbv0x_fiSmXWgDkEsEn8hoZpA-x--W5kQChHQX7Ow5E,13586
3
+ traceai_transformers/version.py,sha256=IOEzhCbX916JIkvZr03GFY4bl9YqSkfjdqLO4qJ2FOs,50
4
+ traceai_transformers-0.1.0.dist-info/METADATA,sha256=DDlSVwwhkX5S6tS_qQT3q2VSpVE45IAkFv21gW37bac,5658
5
+ traceai_transformers-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
6
+ traceai_transformers-0.1.0.dist-info/entry_points.txt,sha256=eMW-1VNv3gPxiIxD6HA4RuSx04B8D0_XUXrnhA6jVZQ,90
7
+ traceai_transformers-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [opentelemetry_instrumentor]
2
+ transformers = traceai_transformers:TransformersInstrumentor