zizkadb-livekit 0.1.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,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: zizkadb-livekit
3
+ Version: 0.1.0
4
+ Summary: LiveKit Agents observer for ZizkaDB — one session per call, transcript from LiveKit (no audio stored)
5
+ License-Expression: AGPL-3.0-or-later
6
+ Project-URL: Repository, https://github.com/Zizka-ai/ZizkaDB
7
+ Project-URL: Documentation, https://db.zizka.ai/docs
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: zizkadb-sdk>=0.2.7
11
+ Requires-Dist: livekit-agents>=1.3.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
15
+
16
+ # ZizkaDB + LiveKit Agents
17
+
18
+ **PyPI:** [zizkadb-livekit 0.1.0](https://pypi.org/project/zizkadb-livekit/) · depends on `zizkadb-sdk>=0.2.7` + `livekit-agents>=1.3.0`
19
+
20
+ Log voice calls to ZizkaDB with the same **Activity → Sessions / Events** model as text agents.
21
+
22
+ - **One `session_id` per call** (use LiveKit room name or job id)
23
+ - **Transcript only** — text copied from LiveKit `make_session_report()` (no audio in ZizkaDB)
24
+ - **Backend events** — `tool_call`, `tool_result`, `error`, pipeline events from the report
25
+ - **Causal lineage** — `parent_id` chains for Why? in the dashboard
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install "zizkadb-livekit>=0.1.0"
31
+ ```
32
+
33
+ Start ZizkaDB (Docker quickstart):
34
+
35
+ ```bash
36
+ curl -fsSL https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh | bash
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ import os
43
+ from livekit.agents import AgentServer, JobContext, AgentSession, Agent
44
+ from zizkadb import ZizkaDB
45
+ from zizkadb_livekit import ZizkaDBLiveKitObserver
46
+
47
+ server = AgentServer()
48
+
49
+ async def on_session_end(ctx: JobContext) -> None:
50
+ await observer.ingest_session_report(ctx)
51
+
52
+ @server.rtc_session(agent_name="support-voice", on_session_end=on_session_end)
53
+ async def entrypoint(ctx: JobContext):
54
+ await ctx.connect()
55
+
56
+ db = ZizkaDB(host=os.getenv("ZIZKADB_HOST", "http://localhost:8000"))
57
+ global observer
58
+ observer = ZizkaDBLiveKitObserver(
59
+ db,
60
+ agent=os.getenv("ZIZKADB_AGENT", "support-voice"),
61
+ session_id=f"call_{ctx.room.name}",
62
+ )
63
+ await observer.log_session_started(room=ctx.room.name, job_id=ctx.job.id)
64
+
65
+ session = AgentSession(...)
66
+ observer.attach(session, job_ctx=ctx) # optional realtime turns
67
+
68
+ await session.start(agent=Agent(instructions="..."), room=ctx.room)
69
+ ```
70
+
71
+ Open **http://localhost:3001** → Activity → your agent → Sessions.
72
+
73
+ ## Event types
74
+
75
+ | Event | Source |
76
+ |-------|--------|
77
+ | `session_started` | Call connect |
78
+ | `user_message` | LiveKit user transcript |
79
+ | `assistant_response` | LiveKit agent transcript |
80
+ | `tool_call` / `tool_result` | Tools + report pipeline events |
81
+ | `error` | Failures |
82
+ | `session_ended` | Post-call ingest |
83
+
84
+ Manual helpers: `log_tool_call`, `log_tool_result`, `log_error`.
85
+
86
+ ## Environment
87
+
88
+ ```bash
89
+ export ZIZKADB_HOST=http://localhost:8000 # self-host
90
+ export ZIZKADB_AGENT=support-voice
91
+ # Cloud: export ZIZKADB_API_KEY=zizkadb_live_...
92
+ ```
93
+
94
+ See [examples/livekit-agent](../../examples/livekit-agent/) for a full sample.
@@ -0,0 +1,79 @@
1
+ # ZizkaDB + LiveKit Agents
2
+
3
+ **PyPI:** [zizkadb-livekit 0.1.0](https://pypi.org/project/zizkadb-livekit/) · depends on `zizkadb-sdk>=0.2.7` + `livekit-agents>=1.3.0`
4
+
5
+ Log voice calls to ZizkaDB with the same **Activity → Sessions / Events** model as text agents.
6
+
7
+ - **One `session_id` per call** (use LiveKit room name or job id)
8
+ - **Transcript only** — text copied from LiveKit `make_session_report()` (no audio in ZizkaDB)
9
+ - **Backend events** — `tool_call`, `tool_result`, `error`, pipeline events from the report
10
+ - **Causal lineage** — `parent_id` chains for Why? in the dashboard
11
+
12
+ ## Install
13
+
14
+ ```bash
15
+ pip install "zizkadb-livekit>=0.1.0"
16
+ ```
17
+
18
+ Start ZizkaDB (Docker quickstart):
19
+
20
+ ```bash
21
+ curl -fsSL https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh | bash
22
+ ```
23
+
24
+ ## Usage
25
+
26
+ ```python
27
+ import os
28
+ from livekit.agents import AgentServer, JobContext, AgentSession, Agent
29
+ from zizkadb import ZizkaDB
30
+ from zizkadb_livekit import ZizkaDBLiveKitObserver
31
+
32
+ server = AgentServer()
33
+
34
+ async def on_session_end(ctx: JobContext) -> None:
35
+ await observer.ingest_session_report(ctx)
36
+
37
+ @server.rtc_session(agent_name="support-voice", on_session_end=on_session_end)
38
+ async def entrypoint(ctx: JobContext):
39
+ await ctx.connect()
40
+
41
+ db = ZizkaDB(host=os.getenv("ZIZKADB_HOST", "http://localhost:8000"))
42
+ global observer
43
+ observer = ZizkaDBLiveKitObserver(
44
+ db,
45
+ agent=os.getenv("ZIZKADB_AGENT", "support-voice"),
46
+ session_id=f"call_{ctx.room.name}",
47
+ )
48
+ await observer.log_session_started(room=ctx.room.name, job_id=ctx.job.id)
49
+
50
+ session = AgentSession(...)
51
+ observer.attach(session, job_ctx=ctx) # optional realtime turns
52
+
53
+ await session.start(agent=Agent(instructions="..."), room=ctx.room)
54
+ ```
55
+
56
+ Open **http://localhost:3001** → Activity → your agent → Sessions.
57
+
58
+ ## Event types
59
+
60
+ | Event | Source |
61
+ |-------|--------|
62
+ | `session_started` | Call connect |
63
+ | `user_message` | LiveKit user transcript |
64
+ | `assistant_response` | LiveKit agent transcript |
65
+ | `tool_call` / `tool_result` | Tools + report pipeline events |
66
+ | `error` | Failures |
67
+ | `session_ended` | Post-call ingest |
68
+
69
+ Manual helpers: `log_tool_call`, `log_tool_result`, `log_error`.
70
+
71
+ ## Environment
72
+
73
+ ```bash
74
+ export ZIZKADB_HOST=http://localhost:8000 # self-host
75
+ export ZIZKADB_AGENT=support-voice
76
+ # Cloud: export ZIZKADB_API_KEY=zizkadb_live_...
77
+ ```
78
+
79
+ See [examples/livekit-agent](../../examples/livekit-agent/) for a full sample.
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zizkadb-livekit"
7
+ version = "0.1.0"
8
+ description = "LiveKit Agents observer for ZizkaDB — one session per call, transcript from LiveKit (no audio stored)"
9
+ readme = "README.md"
10
+ license = "AGPL-3.0-or-later"
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "zizkadb-sdk>=0.2.7",
14
+ "livekit-agents>=1.3.0",
15
+ ]
16
+
17
+ [project.optional-dependencies]
18
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.23"]
19
+
20
+ [project.urls]
21
+ Repository = "https://github.com/Zizka-ai/ZizkaDB"
22
+ Documentation = "https://db.zizka.ai/docs"
23
+
24
+ [tool.setuptools.packages.find]
25
+ where = ["."]
26
+ include = ["zizkadb_livekit*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ from zizkadb.telemetry import ping_on_install
2
+
3
+ from .observer import ZizkaDBLiveKitObserver
4
+
5
+ ping_on_install(sdk="livekit")
6
+
7
+ __all__ = ["ZizkaDBLiveKitObserver"]
@@ -0,0 +1,103 @@
1
+ """Parse LiveKit SessionReport dicts for ZizkaDB event ingest."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+
8
+ def normalize_report(report: dict[str, Any]) -> dict[str, Any]:
9
+ """Accept make_session_report().to_dict() or an equivalent structure."""
10
+ if not isinstance(report, dict):
11
+ raise TypeError("report must be a dict from SessionReport.to_dict()")
12
+ return report
13
+
14
+
15
+ def chat_items(report: dict[str, Any]) -> list[dict[str, Any]]:
16
+ history = report.get("chat_history") or {}
17
+ if isinstance(history, list):
18
+ items = history
19
+ elif isinstance(history, dict):
20
+ items = history.get("items") or []
21
+ else:
22
+ items = []
23
+ out: list[dict[str, Any]] = []
24
+ for raw in items:
25
+ if not isinstance(raw, dict):
26
+ continue
27
+ item = dict(raw)
28
+ item_id = item.get("id") or item.get("item_id")
29
+ if item_id is not None:
30
+ item["id"] = str(item_id)
31
+ out.append(item)
32
+ out.sort(key=_sort_key)
33
+ return out
34
+
35
+
36
+ def backend_events(report: dict[str, Any]) -> list[dict[str, Any]]:
37
+ """Non-transcript pipeline events from the session report."""
38
+ rows: list[dict[str, Any]] = []
39
+ for raw in report.get("events") or []:
40
+ if not isinstance(raw, dict):
41
+ continue
42
+ if raw.get("type") == "metrics_collected":
43
+ continue
44
+ rows.append(dict(raw))
45
+ rows.sort(key=_sort_key)
46
+ return rows
47
+
48
+
49
+ def item_text(item: dict[str, Any]) -> str:
50
+ content = item.get("content")
51
+ if isinstance(content, str):
52
+ return content
53
+ if isinstance(content, list):
54
+ parts: list[str] = []
55
+ for part in content:
56
+ if isinstance(part, str):
57
+ parts.append(part)
58
+ elif isinstance(part, dict):
59
+ text = part.get("text") or part.get("content")
60
+ if text:
61
+ parts.append(str(text))
62
+ return "\n".join(parts)
63
+ if content is not None:
64
+ return str(content)
65
+ return ""
66
+
67
+
68
+ def item_role(item: dict[str, Any]) -> str:
69
+ role = (item.get("role") or item.get("speaker") or "unknown").lower()
70
+ if role in ("assistant", "agent"):
71
+ return "assistant"
72
+ if role in ("user", "human"):
73
+ return "user"
74
+ return role
75
+
76
+
77
+ def zizka_event_for_role(role: str) -> str | None:
78
+ if role == "user":
79
+ return "user_message"
80
+ if role == "assistant":
81
+ return "assistant_response"
82
+ return None
83
+
84
+
85
+ def zizka_event_for_report_event(ev: dict[str, Any]) -> str:
86
+ et = (ev.get("type") or ev.get("event") or "livekit_event").lower()
87
+ if "tool" in et and "result" in et:
88
+ return "tool_result"
89
+ if "tool" in et or "function" in et:
90
+ return "tool_call"
91
+ if "error" in et or "fail" in et:
92
+ return "error"
93
+ if "api" in et or "http" in et:
94
+ return "api_call"
95
+ return "livekit_event"
96
+
97
+
98
+ def _sort_key(row: dict[str, Any]) -> float:
99
+ for key in ("created_at", "timestamp", "started_at"):
100
+ val = row.get(key)
101
+ if isinstance(val, (int, float)):
102
+ return float(val)
103
+ return 0.0
@@ -0,0 +1,315 @@
1
+ """
2
+ LiveKit Agents → ZizkaDB observer.
3
+
4
+ Maps each voice call to one ZizkaDB session (session_id) and logs transcript
5
+ turns plus backend events. Transcript text is copied from LiveKit's session
6
+ report — ZizkaDB does not record or store audio.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import logging
12
+ from typing import Any, Callable
13
+
14
+ from zizkadb import ZizkaDB
15
+ from zizkadb.models import LogResult
16
+
17
+ from ._report import (
18
+ backend_events,
19
+ chat_items,
20
+ item_role,
21
+ item_text,
22
+ normalize_report,
23
+ zizka_event_for_report_event,
24
+ zizka_event_for_role,
25
+ )
26
+
27
+ log = logging.getLogger(__name__)
28
+
29
+ SessionIdFactory = Callable[[Any], str]
30
+
31
+
32
+ class ZizkaDBLiveKitObserver:
33
+ """
34
+ Log LiveKit voice calls to ZizkaDB using the same Activity model as text agents:
35
+ one session_id per call, transcript as user_message / assistant_response events.
36
+
37
+ Usage:
38
+ db = ZizkaDB(host="http://localhost:8000")
39
+ observer = ZizkaDBLiveKitObserver(db, agent="voice-support", session_id=ctx.room.name)
40
+ await observer.log_session_started(room=ctx.room.name, job_id=ctx.job.id)
41
+ observer.attach(session) # optional realtime turns
42
+ # in on_session_end:
43
+ await observer.ingest_session_report(ctx)
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ db: ZizkaDB,
49
+ agent: str,
50
+ session_id: str,
51
+ *,
52
+ session_id_from: SessionIdFactory | None = None,
53
+ ) -> None:
54
+ self.db = db
55
+ self.agent = agent
56
+ self.session_id = session_id
57
+ self.session_id_from = session_id_from
58
+ self.last_event_id: str | None = None
59
+ self._seen_item_ids: set[str] = set()
60
+ self._session_started = False
61
+ self._session_ended = False
62
+ self._realtime_attached = False
63
+ self._last_result: LogResult | None = None
64
+ self._report_ingested = False
65
+
66
+ async def log_session_started(
67
+ self,
68
+ *,
69
+ room: str | None = None,
70
+ job_id: str | None = None,
71
+ **extra: Any,
72
+ ) -> LogResult:
73
+ if self._session_started:
74
+ assert self._last_result is not None
75
+ return self._last_result
76
+ data: dict[str, Any] = {"source": "livekit", **extra}
77
+ if room is not None:
78
+ data["room"] = room
79
+ if job_id is not None:
80
+ data["job_id"] = job_id
81
+ result = await self._log(
82
+ event="session_started",
83
+ data=data,
84
+ parent_id=None,
85
+ )
86
+ self._session_started = True
87
+ return result
88
+
89
+ async def log_session_ended(
90
+ self,
91
+ *,
92
+ turn_count: int | None = None,
93
+ duration_s: float | None = None,
94
+ livekit_job_id: str | None = None,
95
+ **extra: Any,
96
+ ) -> LogResult:
97
+ data: dict[str, Any] = {"source": "livekit", **extra}
98
+ if turn_count is not None:
99
+ data["turn_count"] = turn_count
100
+ if duration_s is not None:
101
+ data["duration_s"] = duration_s
102
+ if livekit_job_id is not None:
103
+ data["livekit_job_id"] = livekit_job_id
104
+ result = await self._log(
105
+ event="session_ended",
106
+ data=data,
107
+ parent_id=self.last_event_id,
108
+ )
109
+ self._session_ended = True
110
+ return result
111
+
112
+ def attach(self, session: Any, job_ctx: Any | None = None) -> None:
113
+ if self._realtime_attached:
114
+ return
115
+
116
+ if job_ctx is not None and self.session_id_from is not None:
117
+ self.session_id = self.session_id_from(job_ctx)
118
+
119
+ def on_conversation_item_added(ev: Any) -> None:
120
+ try:
121
+ import asyncio
122
+
123
+ asyncio.get_running_loop().create_task(self._on_conversation_item(ev))
124
+ except RuntimeError:
125
+ log.debug("conversation_item_added outside running loop; skipped")
126
+
127
+ if hasattr(session, "on"):
128
+ session.on("conversation_item_added", on_conversation_item_added)
129
+ self._realtime_attached = True
130
+
131
+ async def ingest_session_report(self, ctx: Any) -> list[LogResult]:
132
+ make_report = getattr(ctx, "make_session_report", None)
133
+ if make_report is None:
134
+ raise TypeError("ctx must provide make_session_report() (LiveKit JobContext)")
135
+ report = make_report()
136
+ to_dict = getattr(report, "to_dict", None)
137
+ if to_dict is None:
138
+ raise TypeError("SessionReport must provide to_dict()")
139
+ return await self.ingest_report(to_dict())
140
+
141
+ async def ingest_report(self, report: dict[str, Any]) -> list[LogResult]:
142
+ report = normalize_report(report)
143
+ results: list[LogResult] = []
144
+ backfill_only = self._report_ingested
145
+
146
+ if not self._session_started:
147
+ results.append(
148
+ await self.log_session_started(
149
+ room=report.get("room"),
150
+ job_id=report.get("job_id"),
151
+ room_id=report.get("room_id"),
152
+ )
153
+ )
154
+
155
+ for item in chat_items(report):
156
+ item_id = item.get("id")
157
+ if item_id and item_id in self._seen_item_ids:
158
+ continue
159
+ role = item_role(item)
160
+ event_type = zizka_event_for_role(role)
161
+ if event_type is None:
162
+ continue
163
+ text = item_text(item)
164
+ if not text.strip():
165
+ continue
166
+ data: dict[str, Any] = {"content": text}
167
+ if item.get("interrupted") is not None:
168
+ data["interrupted"] = item["interrupted"]
169
+ if item.get("created_at") is not None:
170
+ data["created_at"] = item["created_at"]
171
+ metadata = {"source": "livekit"}
172
+ if item_id:
173
+ metadata["livekit_item_id"] = item_id
174
+ results.append(
175
+ await self._log(
176
+ event=event_type,
177
+ data=data,
178
+ parent_id=self.last_event_id,
179
+ metadata=metadata,
180
+ )
181
+ )
182
+ if item_id:
183
+ self._seen_item_ids.add(item_id)
184
+
185
+ if not backfill_only:
186
+ for ev in backend_events(report):
187
+ event_type = zizka_event_for_report_event(ev)
188
+ ev_type = ev.get("type") or ev.get("event")
189
+ data = {k: v for k, v in ev.items() if k not in ("type", "event")}
190
+ if ev_type:
191
+ data["livekit_type"] = ev_type
192
+ results.append(
193
+ await self._log(
194
+ event=event_type,
195
+ data=data,
196
+ parent_id=self.last_event_id,
197
+ metadata={"source": "livekit", "livekit_event": True},
198
+ )
199
+ )
200
+
201
+ if not self._session_ended:
202
+ results.append(
203
+ await self.log_session_ended(
204
+ turn_count=len(chat_items(report)),
205
+ livekit_job_id=report.get("job_id"),
206
+ )
207
+ )
208
+
209
+ self._report_ingested = True
210
+
211
+ return results
212
+
213
+ async def log_tool_call(
214
+ self,
215
+ tool: str,
216
+ *,
217
+ args: dict[str, Any] | None = None,
218
+ parent_id: str | None = None,
219
+ **extra: Any,
220
+ ) -> LogResult:
221
+ data: dict[str, Any] = {"tool": tool, **extra}
222
+ if args is not None:
223
+ data["args"] = args
224
+ return await self._log(
225
+ event="tool_call",
226
+ data=data,
227
+ parent_id=parent_id or self.last_event_id,
228
+ )
229
+
230
+ async def log_tool_result(
231
+ self,
232
+ tool: str,
233
+ *,
234
+ output: Any = None,
235
+ parent_id: str | None = None,
236
+ **extra: Any,
237
+ ) -> LogResult:
238
+ data: dict[str, Any] = {"tool": tool, **extra}
239
+ if output is not None:
240
+ data["output"] = output
241
+ return await self._log(
242
+ event="tool_result",
243
+ data=data,
244
+ parent_id=parent_id or self.last_event_id,
245
+ )
246
+
247
+ async def log_error(
248
+ self,
249
+ message: str,
250
+ *,
251
+ stage: str | None = None,
252
+ parent_id: str | None = None,
253
+ **extra: Any,
254
+ ) -> LogResult:
255
+ data: dict[str, Any] = {"error": message, **extra}
256
+ if stage is not None:
257
+ data["stage"] = stage
258
+ return await self._log(
259
+ event="error",
260
+ data=data,
261
+ parent_id=parent_id or self.last_event_id,
262
+ )
263
+
264
+ async def _on_conversation_item(self, ev: Any) -> None:
265
+ item = getattr(ev, "item", None)
266
+ if item is None:
267
+ return
268
+ item_id = getattr(item, "id", None) or getattr(item, "item_id", None)
269
+ if item_id is not None:
270
+ item_id = str(item_id)
271
+ if item_id in self._seen_item_ids:
272
+ return
273
+ role = item_role(
274
+ {
275
+ "role": getattr(item, "role", None),
276
+ "content": getattr(item, "content", None),
277
+ }
278
+ )
279
+ event_type = zizka_event_for_role(role)
280
+ if event_type is None:
281
+ return
282
+ text = item_text({"content": getattr(item, "content", "")})
283
+ if not text.strip():
284
+ return
285
+ metadata = {"source": "livekit", "realtime": True}
286
+ if item_id:
287
+ metadata["livekit_item_id"] = item_id
288
+ await self._log(
289
+ event=event_type,
290
+ data={"content": text},
291
+ parent_id=self.last_event_id,
292
+ metadata=metadata,
293
+ )
294
+ if item_id:
295
+ self._seen_item_ids.add(item_id)
296
+
297
+ async def _log(
298
+ self,
299
+ *,
300
+ event: str,
301
+ data: dict[str, Any],
302
+ parent_id: str | None,
303
+ metadata: dict[str, Any] | None = None,
304
+ ) -> LogResult:
305
+ result = await self.db.log(
306
+ agent=self.agent,
307
+ event=event,
308
+ data=data,
309
+ parent_id=parent_id,
310
+ session_id=self.session_id,
311
+ metadata=metadata,
312
+ )
313
+ self.last_event_id = result.event_id
314
+ self._last_result = result
315
+ return result
@@ -0,0 +1,94 @@
1
+ Metadata-Version: 2.4
2
+ Name: zizkadb-livekit
3
+ Version: 0.1.0
4
+ Summary: LiveKit Agents observer for ZizkaDB — one session per call, transcript from LiveKit (no audio stored)
5
+ License-Expression: AGPL-3.0-or-later
6
+ Project-URL: Repository, https://github.com/Zizka-ai/ZizkaDB
7
+ Project-URL: Documentation, https://db.zizka.ai/docs
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: zizkadb-sdk>=0.2.7
11
+ Requires-Dist: livekit-agents>=1.3.0
12
+ Provides-Extra: dev
13
+ Requires-Dist: pytest>=8.0; extra == "dev"
14
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
15
+
16
+ # ZizkaDB + LiveKit Agents
17
+
18
+ **PyPI:** [zizkadb-livekit 0.1.0](https://pypi.org/project/zizkadb-livekit/) · depends on `zizkadb-sdk>=0.2.7` + `livekit-agents>=1.3.0`
19
+
20
+ Log voice calls to ZizkaDB with the same **Activity → Sessions / Events** model as text agents.
21
+
22
+ - **One `session_id` per call** (use LiveKit room name or job id)
23
+ - **Transcript only** — text copied from LiveKit `make_session_report()` (no audio in ZizkaDB)
24
+ - **Backend events** — `tool_call`, `tool_result`, `error`, pipeline events from the report
25
+ - **Causal lineage** — `parent_id` chains for Why? in the dashboard
26
+
27
+ ## Install
28
+
29
+ ```bash
30
+ pip install "zizkadb-livekit>=0.1.0"
31
+ ```
32
+
33
+ Start ZizkaDB (Docker quickstart):
34
+
35
+ ```bash
36
+ curl -fsSL https://raw.githubusercontent.com/Zizka-ai/ZizkaDB/main/scripts/quickstart-remote.sh | bash
37
+ ```
38
+
39
+ ## Usage
40
+
41
+ ```python
42
+ import os
43
+ from livekit.agents import AgentServer, JobContext, AgentSession, Agent
44
+ from zizkadb import ZizkaDB
45
+ from zizkadb_livekit import ZizkaDBLiveKitObserver
46
+
47
+ server = AgentServer()
48
+
49
+ async def on_session_end(ctx: JobContext) -> None:
50
+ await observer.ingest_session_report(ctx)
51
+
52
+ @server.rtc_session(agent_name="support-voice", on_session_end=on_session_end)
53
+ async def entrypoint(ctx: JobContext):
54
+ await ctx.connect()
55
+
56
+ db = ZizkaDB(host=os.getenv("ZIZKADB_HOST", "http://localhost:8000"))
57
+ global observer
58
+ observer = ZizkaDBLiveKitObserver(
59
+ db,
60
+ agent=os.getenv("ZIZKADB_AGENT", "support-voice"),
61
+ session_id=f"call_{ctx.room.name}",
62
+ )
63
+ await observer.log_session_started(room=ctx.room.name, job_id=ctx.job.id)
64
+
65
+ session = AgentSession(...)
66
+ observer.attach(session, job_ctx=ctx) # optional realtime turns
67
+
68
+ await session.start(agent=Agent(instructions="..."), room=ctx.room)
69
+ ```
70
+
71
+ Open **http://localhost:3001** → Activity → your agent → Sessions.
72
+
73
+ ## Event types
74
+
75
+ | Event | Source |
76
+ |-------|--------|
77
+ | `session_started` | Call connect |
78
+ | `user_message` | LiveKit user transcript |
79
+ | `assistant_response` | LiveKit agent transcript |
80
+ | `tool_call` / `tool_result` | Tools + report pipeline events |
81
+ | `error` | Failures |
82
+ | `session_ended` | Post-call ingest |
83
+
84
+ Manual helpers: `log_tool_call`, `log_tool_result`, `log_error`.
85
+
86
+ ## Environment
87
+
88
+ ```bash
89
+ export ZIZKADB_HOST=http://localhost:8000 # self-host
90
+ export ZIZKADB_AGENT=support-voice
91
+ # Cloud: export ZIZKADB_API_KEY=zizkadb_live_...
92
+ ```
93
+
94
+ See [examples/livekit-agent](../../examples/livekit-agent/) for a full sample.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ zizkadb_livekit/__init__.py
4
+ zizkadb_livekit/_report.py
5
+ zizkadb_livekit/observer.py
6
+ zizkadb_livekit.egg-info/PKG-INFO
7
+ zizkadb_livekit.egg-info/SOURCES.txt
8
+ zizkadb_livekit.egg-info/dependency_links.txt
9
+ zizkadb_livekit.egg-info/requires.txt
10
+ zizkadb_livekit.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ zizkadb-sdk>=0.2.7
2
+ livekit-agents>=1.3.0
3
+
4
+ [dev]
5
+ pytest>=8.0
6
+ pytest-asyncio>=0.23
@@ -0,0 +1 @@
1
+ zizkadb_livekit