traceAI-livekit 0.1.3__tar.gz → 0.2.0__tar.gz

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,221 @@
1
+ Metadata-Version: 2.4
2
+ Name: traceAI-livekit
3
+ Version: 0.2.0
4
+ Summary: OpenTelemetry instrumentation for LiveKit
5
+ License: Apache-2.0
6
+ Keywords: traceai,futureagi,opentelemetry,tracing,observability,llm,livekit
7
+ Author: Future AGI
8
+ Author-email: no-reply@futureagi.com
9
+ Requires-Python: >=3.10,<3.14
10
+ Classifier: Framework :: OpenTelemetry
11
+ Classifier: Framework :: OpenTelemetry :: Instrumentations
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Topic :: System :: Monitoring
23
+ Requires-Dist: fi-instrumentation-otel (>=1.3.0)
24
+ Project-URL: Changelog, https://github.com/future-agi/traceAI/blob/dev/python/frameworks/livekit/CHANGELOG.md
25
+ Project-URL: Documentation, https://docs.futureagi.com/docs/integrations/traceai/livekit
26
+ Project-URL: Homepage, https://futureagi.com
27
+ Project-URL: Issues, https://github.com/future-agi/traceAI/issues
28
+ Project-URL: Repository, https://github.com/future-agi/traceAI/tree/dev/python/frameworks/livekit
29
+ Description-Content-Type: text/markdown
30
+
31
+ # traceAI-livekit
32
+
33
+ OpenTelemetry instrumentation for LiveKit agents integrated with Future AGI.
34
+
35
+ This package provides automatic attribute mapping from LiveKit's native OpenTelemetry instrumentation to Future AGI's semantic conventions, ensuring rich and structured traces in your dashboard.
36
+
37
+ ## Installation
38
+
39
+ ```bash
40
+ pip install traceai-livekit
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ To instrument your LiveKit agent, simply initialize `fi-instrumentation` and enable the attribute mapping **inside your agent's entrypoint**.
46
+
47
+ ```python
48
+ from livekit.agents import AgentServer, JobContext
49
+ from fi_instrumentation.otel import register, ProjectType
50
+ from traceai_livekit import enable_http_attribute_mapping
51
+
52
+ server = AgentServer()
53
+
54
+ @server.rtc_session()
55
+ async def entrypoint(ctx: JobContext):
56
+ # 1. Initialize TraceAI (inside entrypoint for multiprocessing safety)
57
+ register(
58
+ project_name="My LiveKit Agent",
59
+ project_type=ProjectType.OBSERVE,
60
+ set_global_tracer_provider=True,
61
+ )
62
+
63
+ # 2. Enable LiveKit attribute mapping
64
+ # This automatically hooks into LiveKit's telemetry system
65
+ enable_http_attribute_mapping()
66
+
67
+ # ... rest of your agent logic ...
68
+ await ctx.connect()
69
+
70
+ if __name__ == "__main__":
71
+ server.run()
72
+ ```
73
+
74
+ `enable_http_attribute_mapping()` (or `enable_grpc_attribute_mapping()`) hands the provider to LiveKit for you. If you call
75
+ LiveKit's `set_tracer_provider` yourself on the provider `register()` returned, call it after `enable_*`: before it, the call
76
+ drops the Future AGI exporter. LiveKit installs its privacy filter once per provider, so an `allow_pii` passed in a later call is
77
+ ignored. To keep conversation content out of your traces, set `LIVEKIT_TELEMETRY_ALLOW_PII=false` before calling `enable_*`
78
+ (livekit-agents 1.8 and later).
79
+
80
+ ### Flushing at the end of a job
81
+
82
+ On Linux a LiveKit job process ends without running `atexit`, and LiveKit flushes only its own processors, so the spans
83
+ still queued when a job ends, such as the root `agent_session` span, used to never reach Future AGI. `enable_*` now sends
84
+ them twice over:
85
+
86
+ - **When the job shuts down**, if `enable_*` runs inside a job (the usual place, the entrypoint): this sends the voice
87
+ trace, `agent_session` included, as soon as the call ends.
88
+ - **When the process exits**, just before it stops: this sends what LiveKit ends after the shutdown callbacks, its own
89
+ `job_shutdown` span, and the job's root `job_entrypoint` span when tracing is set up before the job starts. With
90
+ set-up inside the entrypoint, LiveKit starts `job_entrypoint` before tracing is on, so it is never recorded and the
91
+ job trace arrives rooted at `job_shutdown`.
92
+
93
+ Each waits at most 3 seconds for the collector, well inside the 10 seconds LiveKit gives a job process to exit, so its own
94
+ clean-up still runs when the collector is slow.
95
+
96
+ If you set tracing up outside a job (for example at import time, in the job process), only the process-exit flush runs.
97
+ To also send each call's trace as soon as the call ends, call `flush_on_job_shutdown()` in each job entrypoint:
98
+
99
+ ```python
100
+ from traceai_livekit import flush_on_job_shutdown
101
+
102
+ @server.rtc_session()
103
+ async def entrypoint(ctx: JobContext):
104
+ flush_on_job_shutdown()
105
+ ...
106
+ ```
107
+
108
+ Do not pass `provider.force_flush` to `ctx.add_shutdown_callback`: LiveKit calls it with the shutdown reason, which
109
+ `force_flush` takes as its timeout, so it fails and nothing is flushed.
110
+
111
+ Limits:
112
+ - A process LiveKit kills (for example when a worker drains and the job overruns its exit time) sends nothing more.
113
+ - Before livekit-agents 1.3, LiveKit closes the session at the same time as the job-shutdown flush, so that flush can miss
114
+ `agent_session`; the process-exit flush still sends it.
115
+ - A second `AgentSession` in one job that is still closing when the shutdown callbacks run is sent by the process-exit
116
+ flush, not the job-shutdown one.
117
+ - In LiveKit Cloud observability mode the session's parent span is recorded on LiveKit's own provider and never reaches
118
+ Future AGI, so the trace has no stored root there.
119
+
120
+ ## Recording metadata on a session trace
121
+
122
+ `record_egress` records metadata only: it never records, opens, downloads or uploads media. Each call creates and promptly ends one INTERNAL `livekit.recording.snapshot` span with FI kind `CHAIN`, parented to the supplied session span. Multiple recordings and start/stop/update responses remain separate snapshots instead of overwriting a root.
123
+
124
+ Use an explicitly owned session root/envelope before starting recording. An ended span or a `NonRecordingSpan` holding its saved context is valid for later callbacks. Without `session_span`, the helper uses the current valid OTel span; it does not discover the native LiveKit root or search ancestors. Without a valid parent it returns `False` and creates no orphan recording trace.
125
+
126
+ ```python
127
+ from fi_instrumentation import FITracer, TraceConfig
128
+ from opentelemetry import trace
129
+ from traceai_livekit import record_egress, wrap_egress_client
130
+
131
+ # provider is the provider from register(); enable_* has already configured mapping.
132
+ policy = TraceConfig()
133
+ tracer = provider.get_tracer("my-agent-recordings")
134
+ root = FITracer(tracer, config=policy).start_span(
135
+ "LiveKit Agent Session", fi_span_kind="agent"
136
+ )
137
+ saved_parent = trace.NonRecordingSpan(root.get_span_context())
138
+
139
+ # Bind the egress SERVICE, not the owning API context manager. Requests and
140
+ # response objects are forwarded unchanged; only successful responses are read.
141
+ egress = wrap_egress_client(
142
+ api_owner.egress, session_span=saved_parent, room_name=room_name,
143
+ conversation_id=conversation_id, # optional: may differ from room_name
144
+ tracer=tracer, trace_config=policy,
145
+ )
146
+ with trace.use_span(root, end_on_exit=False):
147
+ info = await egress.start_room_composite_egress(start_request)
148
+ # Start AgentSession under this context too, where the LiveKit mode supports it.
149
+
150
+ # Later, for a final EgressInfo-like response ALREADY received by your application:
151
+ recorded_locally = record_egress(
152
+ final_info, session_span=saved_parent, room_name=room_name,
153
+ conversation_id=conversation_id, tracer=tracer, trace_config=policy,
154
+ )
155
+ # End root at actual session shutdown; the application owns API stop/close.
156
+ # See examples/egress_agent.py for a bounded, idempotent shutdown callback.
157
+ ```
158
+
159
+ `True` means a snapshot was locally created and ended. It does **not** confirm collector delivery, recording completion, an available final file, or a playable URL. `False` also covers tracing suppression, unsampled children, invalid/missing ids, room mismatch and instrumentation failure. Delegate failures/cancellation are not swallowed or logged by the proxy. A failed or cancelled start with no returned id can leave server-side state unknown; the adapter does not retry or discover that state.
160
+
161
+ ### Metadata contract
162
+
163
+ These are package-local custom attributes, not standardized recording semantics or a dashboard-player API:
164
+
165
+ | FI export-copy attribute | Meaning |
166
+ | --- | --- |
167
+ | `livekit.recording.operation` | `snapshot` or a fixed captured method name |
168
+ | `livekit.recording.egress_id` | Bounded nonempty response id |
169
+ | `livekit.recording.status` | Recognized `EGRESS_*` label or decimal enum number; zero is retained, numbers are not interpreted |
170
+ | `livekit.recording.started_at_unix_nano`, `livekit.recording.ended_at_unix_nano` | Positive int64 Unix nanoseconds, only when supplied |
171
+ | `livekit.recording.room_name` | Explicit room, otherwise response room |
172
+ | `gen_ai.conversation.id` | Explicit conversation id, independently masked; otherwise the fully masked room passed through conversation masks. A withheld room supplies no implicit conversation; a redacted room supplies only its replacement |
173
+ | `livekit.recording.file_locations` | At most eight credential-free object locators or safe relative filenames |
174
+ | `livekit.recording.stream_locations` | At most eight stream origins; paths may be bearer keys and are omitted |
175
+
176
+ The helper's EgressInfo-like contract reads only `egress_id`, `room_name`, `status`, `started_at`, `ended_at`, `file_results` (`location`, or `filename` only when location is absent) and `stream_results` (`url`). Use a caller-normalized response if your SDK differs. Timestamps must already be Unix nanoseconds; there is no magnitude-based conversion. Missing/zero/invalid timestamps, unknown status text, unsupported legacy singular/segment/image results and absent locations are omitted. A successful start/stop may still be STARTING/ENDING with no final location; no duration or completion is inferred.
177
+
178
+ Ids/room/conversation are at most 256 characters; locations at most 2048. Oversize values are omitted, not truncated. Only the first eight elements of each results collection are examined. A nonempty explicit room that differs from the response room refuses capture. No request, upload configuration, auth header, protobuf serialization or media bytes are inspected.
179
+
180
+ ### Privacy and adapter limits
181
+
182
+ The recording helper controls only the metadata it writes. Attributes, events or links that your own samplers, span processors or code add are exported as written; the recording policy does not filter them, except the helper's reserved keys, which export always re-checks.
183
+
184
+ - Helper-authored values pass existing FI `TraceConfig.mask` gates for both the destination alias and source key **before** span creation. Locations also pass the `output.value` policy, so output hiding applies. Both the helper policy and any supplied FITracer policy apply. A withheld helper value is not placed into attributes, events, links, exception text, logs, baggage, context keys, resources or the helper's boolean result.
185
+ - FI export re-applies those policies by **key name**, not writer provenance, to the nine source/alias pairs in the metadata table (including `lk.pii.room_name` and `gen_ai.conversation.id`) and `session.id`. This also covers reserved attribute keys in application-created events and links; unrelated keys, event names, exception text and link targets are not filtered by the recording policy. Missing sources do not recreate aliases. A masking `Exception` replaces the affected value with `__REDACTED__` rather than exporting raw metadata or exception text. Export rechecking may transform an already-masked value again: custom masks should be idempotent.
186
+ - Capture uses a per-call copy of the ordinary OTel SDK tracer without changing the supplied tracer/provider, sampler, or application callbacks. A lifecycle adapter retains the started child for `finally` cleanup and carries the policy tuple on a local instrumentation-scope copy for delayed/batched FI export. Normal scope fields and attributes are unchanged; no response values, policy data or identity registry are serialized. Application samplers/processors are not sandboxed or restricted. Unknown/custom tracers, subclasses, nested tracer proxies or inaccessible FI configuration fail closed before starting a span. Minimum-FI/SDK and LiveKit-copy compatibility still require verification.
187
+ - Through creation **and ending**, the helper attaches a fresh context containing only the selected parent, including its trace state. It does not inherit ambient FI session/user/metadata attributes or baggage. Suppression is checked first; child termination and restoration of the caller context run in `finally`, including when `start_span` fails after SDK allocation. Initial extraction/masking failures still create no child.
188
+ - The helper writes sensitive live-span values only under `lk.pii.*`; its aliases are created on the FI export copy. LiveKit PII opt-out and Cloud redaction can therefore remove helper-authored ids, room, conversation and locations before aliasing. The exact `livekit.recording.snapshot` and supported `livekit.recording.<method>` names remain reserved across tracer scopes: loss of the operation attribute to sampling or SDK limits cannot enable generic room-to-conversation fallback. Ordinary non-recording spans keep their existing mapping.
189
+ - File locations allow `http`, `https`, `s3`, `gs` or safe relative names. Stream locations allow `http`, `https`, `rtmp`, `rtmps`, `rtsp`, `rtsps`, but only their origin survives. Malformed/unsupported locations, controls, backslashes and unsafe relative paths are omitted. All query parameters, fragments and userinfo are dropped. Credential-free locators are intentionally **not usable presigned playback links**. This stricter local projection supplements FI masks; it does not claim reuse of an independently verified shared URL/credential sanitizer.
190
+ - The opt-in proxy recognizes coroutine functions named `start_room_composite_egress`, `start_web_egress`, `start_participant_egress`, `start_track_composite_egress`, `start_track_egress`, `stop_egress`, `update_stream`, and `update_layout`. This is a structural service contract, not a verified SDK-version matrix. Non-coroutine/decorated methods and unrelated attributes pass through; non-info responses produce no snapshot. Use the manual helper when recognition is unsupported.
191
+ - The wrapper freezes its parent at creation; an unbound wrapper stays capture-disabled even if a different task later has a current span. Rewrapping with omitted/identical options is idempotent; conflicting supplied bindings raise a value-free `ValueError`. No global room registry or response history is kept.
192
+ - Calls on the original service, other processes and server-side completion events are not intercepted. There is no polling or webhook server. Your application supplies final updates and distributed trace-context propagation. The API owner remains responsible for shutdown; special-method/context-manager protocols are not proxied.
193
+ - The envelope is not a promise that FI receives LiveKit Cloud's native root. The earlier Cloud-root limitation still applies. Native `AgentSession` parent inheritance and real SDK compatibility must be validated for the deployed mode.
194
+
195
+ The pinned development lock contains LiveKit Agents 1.8.6, API 1.2.1, protocol 1.1.27, FI 1.3.0 and OpenTelemetry SDK 1.44.0. Those pins are **verification targets, not executed compatibility results for this feature**. Fake-service tests do not establish SDK schemas, timestamp semantics, native-session linkage or a playback UI.
196
+
197
+ ## Compatibility
198
+
199
+ * **livekit-agents**: 1.x. 1.8 is the tested version (it needs Python 3.10 or later). Since 1.7, LiveKit names its content
200
+ attributes `lk.pii.*` (for example `lk.pii.user_input`); both those names and the earlier ones are mapped to `input.value`
201
+ and `output.value`.
202
+ * **Python**: 3.10 to 3.13.
203
+ * **fi-instrumentation-otel**: 1.3.0 or later; the recording helpers' privacy tests fail with 1.1.0. 1.3.0 needs Python 3.10
204
+ or later and sets the default base64 image cap to 0 (see its changelog). 1.0 moved span attributes from the `llm.*` names to the OpenTelemetry GenAI `gen_ai.*` names, and
205
+ renamed the `SpanAttributes` constants with them. Code that only calls `register()` is unaffected; if your code sets or reads
206
+ those attributes directly, see the
207
+ [mapping from the old names](https://github.com/future-agi/traceAI/blob/main/docs/OTEL_GENAI_SEMANTIC_CONVENTIONS.md#migration-from-openinference-llm).
208
+
209
+ ## Features
210
+
211
+ * **Automatic Attribute Mapping**: Converts LiveKit attributes (like `lk.user_input`, `gen_ai.usage.input_tokens`) to Future AGI standard attributes (`input.value`, `llm.tokenCount.prompt`).
212
+ * **Raw Data Preservation**: Keeps original LiveKit attributes and adds `raw.input` / `raw.output` for full debugging context.
213
+ * **Multiprocessing Support**: Designed to work with LiveKit's worker process model.
214
+ * **Event Parsing**: Extracts LLM outputs from `gen_ai.choice` events when available.
215
+
216
+ ## Examples
217
+
218
+ Check the `examples/` directory for agent examples (validate recording SDK compatibility before deployment):
219
+ * `simple_agent.py`: A basic voice assistant agent.
220
+ * `egress_agent.py`: An agent with opt-in Egress metadata capture, an explicit session envelope and bounded stop/close cleanup. The modern `LiveKitAPI().egress` request shape and native-session linkage require the deployment's SDK/mode validation.
221
+
@@ -0,0 +1,190 @@
1
+ # traceAI-livekit
2
+
3
+ OpenTelemetry instrumentation for LiveKit agents integrated with Future AGI.
4
+
5
+ This package provides automatic attribute mapping from LiveKit's native OpenTelemetry instrumentation to Future AGI's semantic conventions, ensuring rich and structured traces in your dashboard.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install traceai-livekit
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ To instrument your LiveKit agent, simply initialize `fi-instrumentation` and enable the attribute mapping **inside your agent's entrypoint**.
16
+
17
+ ```python
18
+ from livekit.agents import AgentServer, JobContext
19
+ from fi_instrumentation.otel import register, ProjectType
20
+ from traceai_livekit import enable_http_attribute_mapping
21
+
22
+ server = AgentServer()
23
+
24
+ @server.rtc_session()
25
+ async def entrypoint(ctx: JobContext):
26
+ # 1. Initialize TraceAI (inside entrypoint for multiprocessing safety)
27
+ register(
28
+ project_name="My LiveKit Agent",
29
+ project_type=ProjectType.OBSERVE,
30
+ set_global_tracer_provider=True,
31
+ )
32
+
33
+ # 2. Enable LiveKit attribute mapping
34
+ # This automatically hooks into LiveKit's telemetry system
35
+ enable_http_attribute_mapping()
36
+
37
+ # ... rest of your agent logic ...
38
+ await ctx.connect()
39
+
40
+ if __name__ == "__main__":
41
+ server.run()
42
+ ```
43
+
44
+ `enable_http_attribute_mapping()` (or `enable_grpc_attribute_mapping()`) hands the provider to LiveKit for you. If you call
45
+ LiveKit's `set_tracer_provider` yourself on the provider `register()` returned, call it after `enable_*`: before it, the call
46
+ drops the Future AGI exporter. LiveKit installs its privacy filter once per provider, so an `allow_pii` passed in a later call is
47
+ ignored. To keep conversation content out of your traces, set `LIVEKIT_TELEMETRY_ALLOW_PII=false` before calling `enable_*`
48
+ (livekit-agents 1.8 and later).
49
+
50
+ ### Flushing at the end of a job
51
+
52
+ On Linux a LiveKit job process ends without running `atexit`, and LiveKit flushes only its own processors, so the spans
53
+ still queued when a job ends, such as the root `agent_session` span, used to never reach Future AGI. `enable_*` now sends
54
+ them twice over:
55
+
56
+ - **When the job shuts down**, if `enable_*` runs inside a job (the usual place, the entrypoint): this sends the voice
57
+ trace, `agent_session` included, as soon as the call ends.
58
+ - **When the process exits**, just before it stops: this sends what LiveKit ends after the shutdown callbacks, its own
59
+ `job_shutdown` span, and the job's root `job_entrypoint` span when tracing is set up before the job starts. With
60
+ set-up inside the entrypoint, LiveKit starts `job_entrypoint` before tracing is on, so it is never recorded and the
61
+ job trace arrives rooted at `job_shutdown`.
62
+
63
+ Each waits at most 3 seconds for the collector, well inside the 10 seconds LiveKit gives a job process to exit, so its own
64
+ clean-up still runs when the collector is slow.
65
+
66
+ If you set tracing up outside a job (for example at import time, in the job process), only the process-exit flush runs.
67
+ To also send each call's trace as soon as the call ends, call `flush_on_job_shutdown()` in each job entrypoint:
68
+
69
+ ```python
70
+ from traceai_livekit import flush_on_job_shutdown
71
+
72
+ @server.rtc_session()
73
+ async def entrypoint(ctx: JobContext):
74
+ flush_on_job_shutdown()
75
+ ...
76
+ ```
77
+
78
+ Do not pass `provider.force_flush` to `ctx.add_shutdown_callback`: LiveKit calls it with the shutdown reason, which
79
+ `force_flush` takes as its timeout, so it fails and nothing is flushed.
80
+
81
+ Limits:
82
+ - A process LiveKit kills (for example when a worker drains and the job overruns its exit time) sends nothing more.
83
+ - Before livekit-agents 1.3, LiveKit closes the session at the same time as the job-shutdown flush, so that flush can miss
84
+ `agent_session`; the process-exit flush still sends it.
85
+ - A second `AgentSession` in one job that is still closing when the shutdown callbacks run is sent by the process-exit
86
+ flush, not the job-shutdown one.
87
+ - In LiveKit Cloud observability mode the session's parent span is recorded on LiveKit's own provider and never reaches
88
+ Future AGI, so the trace has no stored root there.
89
+
90
+ ## Recording metadata on a session trace
91
+
92
+ `record_egress` records metadata only: it never records, opens, downloads or uploads media. Each call creates and promptly ends one INTERNAL `livekit.recording.snapshot` span with FI kind `CHAIN`, parented to the supplied session span. Multiple recordings and start/stop/update responses remain separate snapshots instead of overwriting a root.
93
+
94
+ Use an explicitly owned session root/envelope before starting recording. An ended span or a `NonRecordingSpan` holding its saved context is valid for later callbacks. Without `session_span`, the helper uses the current valid OTel span; it does not discover the native LiveKit root or search ancestors. Without a valid parent it returns `False` and creates no orphan recording trace.
95
+
96
+ ```python
97
+ from fi_instrumentation import FITracer, TraceConfig
98
+ from opentelemetry import trace
99
+ from traceai_livekit import record_egress, wrap_egress_client
100
+
101
+ # provider is the provider from register(); enable_* has already configured mapping.
102
+ policy = TraceConfig()
103
+ tracer = provider.get_tracer("my-agent-recordings")
104
+ root = FITracer(tracer, config=policy).start_span(
105
+ "LiveKit Agent Session", fi_span_kind="agent"
106
+ )
107
+ saved_parent = trace.NonRecordingSpan(root.get_span_context())
108
+
109
+ # Bind the egress SERVICE, not the owning API context manager. Requests and
110
+ # response objects are forwarded unchanged; only successful responses are read.
111
+ egress = wrap_egress_client(
112
+ api_owner.egress, session_span=saved_parent, room_name=room_name,
113
+ conversation_id=conversation_id, # optional: may differ from room_name
114
+ tracer=tracer, trace_config=policy,
115
+ )
116
+ with trace.use_span(root, end_on_exit=False):
117
+ info = await egress.start_room_composite_egress(start_request)
118
+ # Start AgentSession under this context too, where the LiveKit mode supports it.
119
+
120
+ # Later, for a final EgressInfo-like response ALREADY received by your application:
121
+ recorded_locally = record_egress(
122
+ final_info, session_span=saved_parent, room_name=room_name,
123
+ conversation_id=conversation_id, tracer=tracer, trace_config=policy,
124
+ )
125
+ # End root at actual session shutdown; the application owns API stop/close.
126
+ # See examples/egress_agent.py for a bounded, idempotent shutdown callback.
127
+ ```
128
+
129
+ `True` means a snapshot was locally created and ended. It does **not** confirm collector delivery, recording completion, an available final file, or a playable URL. `False` also covers tracing suppression, unsampled children, invalid/missing ids, room mismatch and instrumentation failure. Delegate failures/cancellation are not swallowed or logged by the proxy. A failed or cancelled start with no returned id can leave server-side state unknown; the adapter does not retry or discover that state.
130
+
131
+ ### Metadata contract
132
+
133
+ These are package-local custom attributes, not standardized recording semantics or a dashboard-player API:
134
+
135
+ | FI export-copy attribute | Meaning |
136
+ | --- | --- |
137
+ | `livekit.recording.operation` | `snapshot` or a fixed captured method name |
138
+ | `livekit.recording.egress_id` | Bounded nonempty response id |
139
+ | `livekit.recording.status` | Recognized `EGRESS_*` label or decimal enum number; zero is retained, numbers are not interpreted |
140
+ | `livekit.recording.started_at_unix_nano`, `livekit.recording.ended_at_unix_nano` | Positive int64 Unix nanoseconds, only when supplied |
141
+ | `livekit.recording.room_name` | Explicit room, otherwise response room |
142
+ | `gen_ai.conversation.id` | Explicit conversation id, independently masked; otherwise the fully masked room passed through conversation masks. A withheld room supplies no implicit conversation; a redacted room supplies only its replacement |
143
+ | `livekit.recording.file_locations` | At most eight credential-free object locators or safe relative filenames |
144
+ | `livekit.recording.stream_locations` | At most eight stream origins; paths may be bearer keys and are omitted |
145
+
146
+ The helper's EgressInfo-like contract reads only `egress_id`, `room_name`, `status`, `started_at`, `ended_at`, `file_results` (`location`, or `filename` only when location is absent) and `stream_results` (`url`). Use a caller-normalized response if your SDK differs. Timestamps must already be Unix nanoseconds; there is no magnitude-based conversion. Missing/zero/invalid timestamps, unknown status text, unsupported legacy singular/segment/image results and absent locations are omitted. A successful start/stop may still be STARTING/ENDING with no final location; no duration or completion is inferred.
147
+
148
+ Ids/room/conversation are at most 256 characters; locations at most 2048. Oversize values are omitted, not truncated. Only the first eight elements of each results collection are examined. A nonempty explicit room that differs from the response room refuses capture. No request, upload configuration, auth header, protobuf serialization or media bytes are inspected.
149
+
150
+ ### Privacy and adapter limits
151
+
152
+ The recording helper controls only the metadata it writes. Attributes, events or links that your own samplers, span processors or code add are exported as written; the recording policy does not filter them, except the helper's reserved keys, which export always re-checks.
153
+
154
+ - Helper-authored values pass existing FI `TraceConfig.mask` gates for both the destination alias and source key **before** span creation. Locations also pass the `output.value` policy, so output hiding applies. Both the helper policy and any supplied FITracer policy apply. A withheld helper value is not placed into attributes, events, links, exception text, logs, baggage, context keys, resources or the helper's boolean result.
155
+ - FI export re-applies those policies by **key name**, not writer provenance, to the nine source/alias pairs in the metadata table (including `lk.pii.room_name` and `gen_ai.conversation.id`) and `session.id`. This also covers reserved attribute keys in application-created events and links; unrelated keys, event names, exception text and link targets are not filtered by the recording policy. Missing sources do not recreate aliases. A masking `Exception` replaces the affected value with `__REDACTED__` rather than exporting raw metadata or exception text. Export rechecking may transform an already-masked value again: custom masks should be idempotent.
156
+ - Capture uses a per-call copy of the ordinary OTel SDK tracer without changing the supplied tracer/provider, sampler, or application callbacks. A lifecycle adapter retains the started child for `finally` cleanup and carries the policy tuple on a local instrumentation-scope copy for delayed/batched FI export. Normal scope fields and attributes are unchanged; no response values, policy data or identity registry are serialized. Application samplers/processors are not sandboxed or restricted. Unknown/custom tracers, subclasses, nested tracer proxies or inaccessible FI configuration fail closed before starting a span. Minimum-FI/SDK and LiveKit-copy compatibility still require verification.
157
+ - Through creation **and ending**, the helper attaches a fresh context containing only the selected parent, including its trace state. It does not inherit ambient FI session/user/metadata attributes or baggage. Suppression is checked first; child termination and restoration of the caller context run in `finally`, including when `start_span` fails after SDK allocation. Initial extraction/masking failures still create no child.
158
+ - The helper writes sensitive live-span values only under `lk.pii.*`; its aliases are created on the FI export copy. LiveKit PII opt-out and Cloud redaction can therefore remove helper-authored ids, room, conversation and locations before aliasing. The exact `livekit.recording.snapshot` and supported `livekit.recording.<method>` names remain reserved across tracer scopes: loss of the operation attribute to sampling or SDK limits cannot enable generic room-to-conversation fallback. Ordinary non-recording spans keep their existing mapping.
159
+ - File locations allow `http`, `https`, `s3`, `gs` or safe relative names. Stream locations allow `http`, `https`, `rtmp`, `rtmps`, `rtsp`, `rtsps`, but only their origin survives. Malformed/unsupported locations, controls, backslashes and unsafe relative paths are omitted. All query parameters, fragments and userinfo are dropped. Credential-free locators are intentionally **not usable presigned playback links**. This stricter local projection supplements FI masks; it does not claim reuse of an independently verified shared URL/credential sanitizer.
160
+ - The opt-in proxy recognizes coroutine functions named `start_room_composite_egress`, `start_web_egress`, `start_participant_egress`, `start_track_composite_egress`, `start_track_egress`, `stop_egress`, `update_stream`, and `update_layout`. This is a structural service contract, not a verified SDK-version matrix. Non-coroutine/decorated methods and unrelated attributes pass through; non-info responses produce no snapshot. Use the manual helper when recognition is unsupported.
161
+ - The wrapper freezes its parent at creation; an unbound wrapper stays capture-disabled even if a different task later has a current span. Rewrapping with omitted/identical options is idempotent; conflicting supplied bindings raise a value-free `ValueError`. No global room registry or response history is kept.
162
+ - Calls on the original service, other processes and server-side completion events are not intercepted. There is no polling or webhook server. Your application supplies final updates and distributed trace-context propagation. The API owner remains responsible for shutdown; special-method/context-manager protocols are not proxied.
163
+ - The envelope is not a promise that FI receives LiveKit Cloud's native root. The earlier Cloud-root limitation still applies. Native `AgentSession` parent inheritance and real SDK compatibility must be validated for the deployed mode.
164
+
165
+ The pinned development lock contains LiveKit Agents 1.8.6, API 1.2.1, protocol 1.1.27, FI 1.3.0 and OpenTelemetry SDK 1.44.0. Those pins are **verification targets, not executed compatibility results for this feature**. Fake-service tests do not establish SDK schemas, timestamp semantics, native-session linkage or a playback UI.
166
+
167
+ ## Compatibility
168
+
169
+ * **livekit-agents**: 1.x. 1.8 is the tested version (it needs Python 3.10 or later). Since 1.7, LiveKit names its content
170
+ attributes `lk.pii.*` (for example `lk.pii.user_input`); both those names and the earlier ones are mapped to `input.value`
171
+ and `output.value`.
172
+ * **Python**: 3.10 to 3.13.
173
+ * **fi-instrumentation-otel**: 1.3.0 or later; the recording helpers' privacy tests fail with 1.1.0. 1.3.0 needs Python 3.10
174
+ or later and sets the default base64 image cap to 0 (see its changelog). 1.0 moved span attributes from the `llm.*` names to the OpenTelemetry GenAI `gen_ai.*` names, and
175
+ renamed the `SpanAttributes` constants with them. Code that only calls `register()` is unaffected; if your code sets or reads
176
+ those attributes directly, see the
177
+ [mapping from the old names](https://github.com/future-agi/traceAI/blob/main/docs/OTEL_GENAI_SEMANTIC_CONVENTIONS.md#migration-from-openinference-llm).
178
+
179
+ ## Features
180
+
181
+ * **Automatic Attribute Mapping**: Converts LiveKit attributes (like `lk.user_input`, `gen_ai.usage.input_tokens`) to Future AGI standard attributes (`input.value`, `llm.tokenCount.prompt`).
182
+ * **Raw Data Preservation**: Keeps original LiveKit attributes and adds `raw.input` / `raw.output` for full debugging context.
183
+ * **Multiprocessing Support**: Designed to work with LiveKit's worker process model.
184
+ * **Event Parsing**: Extracts LLM outputs from `gen_ai.choice` events when available.
185
+
186
+ ## Examples
187
+
188
+ Check the `examples/` directory for agent examples (validate recording SDK compatibility before deployment):
189
+ * `simple_agent.py`: A basic voice assistant agent.
190
+ * `egress_agent.py`: An agent with opt-in Egress metadata capture, an explicit session envelope and bounded stop/close cleanup. The modern `LiveKitAPI().egress` request shape and native-session linkage require the deployment's SDK/mode validation.
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "traceAI-livekit"
3
- version = "0.1.3"
3
+ version = "0.2.0"
4
4
  description = "OpenTelemetry instrumentation for LiveKit"
5
5
  authors = ["Future AGI <no-reply@futureagi.com>"]
6
6
  readme = "README.md"
@@ -15,7 +15,6 @@ classifiers = [
15
15
  "Programming Language :: Python",
16
16
  "Programming Language :: Python :: 3",
17
17
  "Programming Language :: Python :: 3 :: Only",
18
- "Programming Language :: Python :: 3.9",
19
18
  "Programming Language :: Python :: 3.10",
20
19
  "Programming Language :: Python :: 3.11",
21
20
  "Programming Language :: Python :: 3.12",
@@ -27,8 +26,8 @@ classifiers = [
27
26
  ]
28
27
 
29
28
  [tool.poetry.dependencies]
30
- python = ">3.9,<3.14"
31
- fi-instrumentation-otel = ">=1.1.0"
29
+ python = ">=3.10,<3.14"
30
+ fi-instrumentation-otel = ">=1.3.0"
32
31
 
33
32
  [tool.poetry.group.dev.dependencies]
34
33
  # livekit-agents 1.8 is the version whose privacy filter the tests cover; it needs Python 3.10+.
@@ -19,7 +19,9 @@ from .integration import (
19
19
  flush_on_job_shutdown,
20
20
  )
21
21
 
22
- __version__ = "0.1.3" # x-release-please-version
22
+ from .recording import record_egress, wrap_egress_client
23
+
24
+ __version__ = "0.2.0" # x-release-please-version
23
25
 
24
26
  __all__ = [
25
27
  "enable_fi_attribute_mapping",
@@ -28,6 +30,8 @@ __all__ = [
28
30
  "create_mapped_http_exporter",
29
31
  "create_mapped_grpc_exporter",
30
32
  "flush_on_job_shutdown",
33
+ "record_egress",
34
+ "wrap_egress_client",
31
35
  # Exporters
32
36
  "MappedHTTPSpanExporter",
33
37
  "MappedGRPCSpanExporter",