daguito-sdk 0.3.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,445 @@
1
+ Metadata-Version: 2.3
2
+ Name: daguito-sdk
3
+ Version: 0.3.0
4
+ Summary: Official Python SDK for Daguito — conversational AI flows, streaming webhooks, and RAG.
5
+ Keywords: daguito,ai,llm,rag,agents,streaming,chatbot
6
+ Author: Daguito
7
+ Author-email: Daguito <daguito.contact@gmail.com>
8
+ License: MIT
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Classifier: Typing :: Typed
19
+ Requires-Dist: httpx>=0.27,<1.0
20
+ Requires-Dist: websockets>=12.0,<14.0
21
+ Requires-Dist: pytest>=8.0 ; extra == 'dev'
22
+ Requires-Dist: pytest-asyncio>=0.23 ; extra == 'dev'
23
+ Requires-Dist: mypy>=1.10 ; extra == 'dev'
24
+ Requires-Dist: ruff>=0.5 ; extra == 'dev'
25
+ Requires-Python: >=3.10
26
+ Project-URL: Homepage, https://daguito.com
27
+ Project-URL: Source, https://github.com/daguito/daguito
28
+ Project-URL: Issues, https://github.com/daguito/daguito/issues
29
+ Provides-Extra: dev
30
+ Description-Content-Type: text/markdown
31
+
32
+ <p align="center">
33
+ <a href="https://daguito.com" target="_blank">
34
+ <img src="https://raw.githubusercontent.com/daguitocontact-lang/daguito-python/main/assets/logo.png" alt="Daguito" width="160" />
35
+ </a>
36
+ </p>
37
+
38
+ <h1 align="center">daguito (Python SDK)</h1>
39
+
40
+ <p align="center">
41
+ Official Python SDK for the
42
+ <a href="https://daguito.com">Daguito</a>
43
+ conversational AI platform —
44
+ text, voice, image, and multimodal agent flows.
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://pypi.org/project/daguito-sdk/"><img src="https://img.shields.io/pypi/v/daguito-sdk.svg?style=flat-square&color=0a0a0a" alt="pypi version" /></a>
49
+ <a href="https://pypi.org/project/daguito-sdk/"><img src="https://img.shields.io/pypi/dm/daguito-sdk.svg?style=flat-square&color=0a0a0a" alt="pypi downloads" /></a>
50
+ <a href="https://github.com/daguitocontact-lang/daguito-python/blob/main/LICENSE"><img src="https://img.shields.io/pypi/l/daguito-sdk.svg?style=flat-square&color=0a0a0a" alt="license" /></a>
51
+ <a href="https://pypi.org/project/daguito-sdk/"><img src="https://img.shields.io/pypi/pyversions/daguito-sdk.svg?style=flat-square&color=0a0a0a" alt="python versions" /></a>
52
+ </p>
53
+
54
+ ---
55
+
56
+ Async-first. Python 3.10+. Built on `httpx` + `websockets`. Type-hinted everywhere. Mirrors the [TypeScript SDK](https://github.com/daguitocontact-lang/js-sdk) feature-for-feature.
57
+
58
+ ```bash
59
+ uv add daguito-sdk
60
+ # or
61
+ pip install daguito-sdk
62
+ ```
63
+
64
+ > Package name is `daguito-sdk`. Import name is `daguito` — same pattern as `scikit-learn` / `sklearn`.
65
+
66
+ ## What you get
67
+
68
+ - **`run_webhook()`** — one-shot HTTP call to a webhook flow. Wait, get the result.
69
+ - **`WebhookStreamSession`** — long-lived WebSocket for streaming flows. Token streaming, node lifecycle, custom emits. `async with`-friendly, plus `async for event in session.events()`.
70
+ - **`@session.tool(...)`** — register OpenAI-style tools the LLM can invoke. Your handler runs locally, its return value is fed back to the model as the tool result.
71
+ - **`scope={...}` on the session** — server-enforced metadata filter for KB searches. Isolates a conversation to its own ingested files without trusting the LLM with UUIDs.
72
+ - **`KnowledgeSession`** — Knowledge Base ingest + search over the same `sk_dgt_...` API key as the dashboard.
73
+ - **Typed event payloads** — every WS event is a dataclass (`NodeTokenEvent`, `FlowCompletedEvent`, etc.) so editors autocomplete attributes.
74
+
75
+ ## Authentication
76
+
77
+ | Surface | Auth | Best for |
78
+ | ------------------------ | --------------------- | --------------------------------------------------- |
79
+ | Webhook (`sk_wh_...`) | Token issued per-flow | Server-to-server, FastAPI/Django backends, scripts |
80
+ | Knowledge (`sk_dgt_...`) | Org-scoped API key | Ingest + search against your own KB |
81
+
82
+ Create webhooks and API keys from your Daguito dashboard.
83
+
84
+ ## Quick start
85
+
86
+ ### One-shot webhook
87
+
88
+ ```python
89
+ import asyncio
90
+ from daguito import run_webhook, WebhookRunInput
91
+
92
+ async def main():
93
+ result = await run_webhook(WebhookRunInput(
94
+ api_url="https://api.daguito.com",
95
+ token="sk_wh_...",
96
+ input={"question": "What is the price of BTC?"},
97
+ ))
98
+ print(result.output)
99
+
100
+ asyncio.run(main())
101
+ ```
102
+
103
+ Need a synchronous flavor for scripts that aren't in an event loop? Use `run_webhook_sync`:
104
+
105
+ ```python
106
+ from daguito import run_webhook_sync, WebhookRunInput
107
+ result = run_webhook_sync(WebhookRunInput(api_url=..., token=..., input={...}))
108
+ ```
109
+
110
+ ### Streaming webhook (text agent)
111
+
112
+ ```python
113
+ import asyncio
114
+ from daguito import (
115
+ WebhookStreamSession,
116
+ WebhookStreamOptions,
117
+ text_message,
118
+ )
119
+
120
+ async def main():
121
+ opts = WebhookStreamOptions(
122
+ api_url="https://api.daguito.com",
123
+ webhook_id="wh_abc123",
124
+ token="sk_wh_...",
125
+ )
126
+ async with WebhookStreamSession(opts) as session:
127
+ await session.send(text_message("Hola, ¿qué tal?"))
128
+
129
+ async for event_type, payload in session.events():
130
+ if event_type == "node.token":
131
+ print(payload.text, end="", flush=True)
132
+ elif event_type == "node.completed":
133
+ print(f"\n✓ {payload.node_id} ({payload.duration_ms}ms)")
134
+ elif event_type == "flow.completed":
135
+ print(f"\nDone in {payload.elapsed_ms}ms")
136
+ break
137
+ elif event_type == "flow.failed":
138
+ print(f"\nFlow failed: {payload.error}")
139
+ break
140
+
141
+ asyncio.run(main())
142
+ ```
143
+
144
+ Prefer callbacks? You can also `session.on("node.token", listener)` like the JS SDK — but async iteration is the idiomatic Python pattern and plays nicely with FastAPI's `StreamingResponse`.
145
+
146
+ ### Streaming with images
147
+
148
+ ```python
149
+ from daguito import image_url_message, image_multi_message, media_key_message
150
+
151
+ # Hosted on a public URL (works on streaming-webhook surface)
152
+ await session.send(image_url_message(image_url="https://...", text="Describe this"))
153
+
154
+ # Multiple images
155
+ await session.send(image_multi_message(image_urls=[url1, url2], text="Compare"))
156
+
157
+ # Pre-uploaded (you handled the upload elsewhere)
158
+ await session.send(media_key_message(
159
+ kind="image",
160
+ media_key="media/.../abc.jpg",
161
+ mime_type="image/jpeg",
162
+ size_bytes=234_567,
163
+ ))
164
+ ```
165
+
166
+ > Need to upload a `File` directly from Python? Upload it through your own backend's presigned URL endpoint, then pass the `media_key` to `media_key_message()`. The streaming surface does not mint presigned URLs.
167
+
168
+ ### Per-conversation scope (server-enforced KB filter)
169
+
170
+ When you ingest documents that belong to a specific conversation, patient, or workspace, you usually want the chat to only see chunks that match. Pass a `scope` on the session and Daguito **forces** every `search_knowledge_base` call to apply it as a metadata filter — server-side, before Milvus runs the search. The LLM never sees the scope values, so it can't accidentally widen the search, hallucinate a UUID, or leak data across conversations.
171
+
172
+ ```python
173
+ import uuid
174
+ from daguito import (
175
+ WebhookStreamSession,
176
+ WebhookStreamOptions,
177
+ KnowledgeSession,
178
+ KnowledgeSessionOptions,
179
+ IngestTextInput,
180
+ text_message,
181
+ )
182
+
183
+ consultation_uuid = str(uuid.uuid4())
184
+
185
+ # 1. Ingest tagged with the scope key
186
+ async with KnowledgeSession(KnowledgeSessionOptions(...)) as kb:
187
+ await kb.ingest_text(IngestTextInput(
188
+ text=lab_results_text,
189
+ metadata={"consultation_uuid": consultation_uuid, "kind": "lab"},
190
+ ))
191
+
192
+ # 2. Open the chat scoped to that consultation
193
+ opts = WebhookStreamOptions(
194
+ api_url="https://api.daguito.com",
195
+ webhook_id="wh_abc123",
196
+ token="sk_wh_...",
197
+ scope={"consultation_uuid": consultation_uuid},
198
+ )
199
+
200
+ async with WebhookStreamSession(opts) as session:
201
+ await session.send(text_message("¿Cómo están las bilirrubinas?"))
202
+ # → search_knowledge_base is forced to filter by consultation_uuid
203
+ # → the LLM can't see chunks from other consultations
204
+ ```
205
+
206
+ Scope values must be primitives (`str`, `int`, `float`, `bool`). You can stack multiple keys at once (`{"consultation_uuid": "...", "tenant_id": "..."}`) — every search is filtered by all of them.
207
+
208
+ The LLM can still pass its own `metadata_filter` (e.g. to narrow by `document_type`), but **server scope always wins on key conflict** — a hallucinated value can't widen the result set.
209
+
210
+ ### Client-side tools (OpenAI-style function calling)
211
+
212
+ Register tools that the LLM can invoke during a session. Your Python handler
213
+ runs locally, and its return value is fed back to the model as the tool
214
+ result — so the LLM continues its reply with the actual data your code
215
+ produced, not a placeholder.
216
+
217
+ ```python
218
+ import asyncio
219
+ from daguito import (
220
+ WebhookStreamSession,
221
+ WebhookStreamOptions,
222
+ text_message,
223
+ )
224
+
225
+ async def main():
226
+ opts = WebhookStreamOptions(
227
+ api_url="https://api.daguito.com",
228
+ webhook_id="wh_abc123",
229
+ token="sk_wh_...",
230
+ )
231
+ async with WebhookStreamSession(opts) as session:
232
+
233
+ @session.tool(
234
+ name="update_soap_section",
235
+ description="Updates a SOAP section with new clinical information.",
236
+ parameters={
237
+ "type": "object",
238
+ "properties": {
239
+ "section": {
240
+ "type": "string",
241
+ "enum": ["subjective", "objective", "assessment", "plan"],
242
+ },
243
+ "subsection": {"type": "string"},
244
+ "content": {"type": "string"},
245
+ "action": {
246
+ "type": "string",
247
+ "enum": ["add", "update", "append"],
248
+ },
249
+ },
250
+ "required": ["section", "subsection", "content", "action"],
251
+ },
252
+ )
253
+ async def update_soap(args: dict) -> dict:
254
+ # Run your business logic here — write to your DB, call an API,
255
+ # whatever. The returned value goes back to the LLM as the
256
+ # tool result.
257
+ soap_id = await db.update_soap(args)
258
+ return {"success": True, "soap_id": soap_id}
259
+
260
+ await session.send(text_message(
261
+ "Add this to the plan: paracetamol 500mg every 8 hours for 5 days."
262
+ ))
263
+
264
+ async for event_type, payload in session.events():
265
+ if event_type == "node.token":
266
+ print(payload.text, end="", flush=True)
267
+ elif event_type == "flow.completed":
268
+ break
269
+
270
+ asyncio.run(main())
271
+ ```
272
+
273
+ Tools follow the **exact same shape as OpenAI function calling**
274
+ (`name`, `description`, `parameters` as a JSON Schema). They're sent to the
275
+ flow on every turn via `base_input.client_tools`, merged with whatever
276
+ tools the flow already declared statically, and the LLM picks the best
277
+ one for the job.
278
+
279
+ The handler can be sync or async. Throw an exception to surface a failure
280
+ to the LLM with a typed error.
281
+
282
+ ### Knowledge Search
283
+
284
+ ```python
285
+ from daguito import (
286
+ KnowledgeSession,
287
+ KnowledgeSessionOptions,
288
+ IngestTextInput,
289
+ SearchInput,
290
+ )
291
+
292
+ opts = KnowledgeSessionOptions(
293
+ api_url="https://api.daguito.com",
294
+ api_key="sk_dgt_...",
295
+ default_source_id="src_abc123",
296
+ )
297
+
298
+ async with KnowledgeSession(opts) as kb:
299
+ # Ingest text — chunks + embeds + indexes server-side
300
+ await kb.ingest_text(IngestTextInput(
301
+ text="MacBook Pro M4 Max with 64GB RAM...",
302
+ metadata={"category": "laptop", "price_usd": 3499},
303
+ ))
304
+
305
+ # Search — vector + keyword hybrid
306
+ result = await kb.search(SearchInput(query="laptops para video", top_k=3))
307
+ for hit in result.hits:
308
+ print(hit.score, hit.content, hit.metadata)
309
+ ```
310
+
311
+ The `api_key` controls scopes. Mint one from the dashboard with `kb:read` and/or `kb:write` actions, optionally limited to specific KBs.
312
+
313
+ ## FastAPI streaming example
314
+
315
+ Stream tokens from a Daguito flow straight to the browser via Server-Sent Events:
316
+
317
+ ```python
318
+ from fastapi import FastAPI
319
+ from fastapi.responses import StreamingResponse
320
+ from daguito import WebhookStreamSession, WebhookStreamOptions, text_message
321
+
322
+ app = FastAPI()
323
+
324
+ @app.post("/chat")
325
+ async def chat(message: str):
326
+ async def event_stream():
327
+ async with WebhookStreamSession(WebhookStreamOptions(
328
+ api_url="https://api.daguito.com",
329
+ webhook_id="wh_abc123",
330
+ token="sk_wh_...",
331
+ )) as session:
332
+ await session.send(text_message(message))
333
+
334
+ async for event_type, payload in session.events():
335
+ if event_type == "node.token":
336
+ yield f"data: {payload.text}\n\n"
337
+ elif event_type == "node.emit" and payload.kind == "tool_call":
338
+ # The agent invoked a tool the client owns. Forward it so
339
+ # the frontend (or this backend) can execute it locally.
340
+ yield f"event: tool_call\ndata: {payload.data}\n\n"
341
+ elif event_type == "flow.completed":
342
+ yield "event: done\ndata: ok\n\n"
343
+ return
344
+
345
+ return StreamingResponse(event_stream(), media_type="text/event-stream")
346
+ ```
347
+
348
+ ## Runtime support
349
+
350
+ | Module | Python 3.10+ | asyncio | Notes |
351
+ | --------- | ------------ | ------- | ------------------------------------------- |
352
+ | `daguito` | ✅ | ✅ | `httpx` + `websockets`. No native deps. |
353
+
354
+ Works on **CPython** and **PyPy**. Plays well with FastAPI, Starlette, aiohttp, Django Channels, anyio-based stacks, and any async runtime. The synchronous `run_webhook_sync()` helper is provided for scripts and Jupyter notebooks that don't have a running event loop.
355
+
356
+ ## Event reference
357
+
358
+ ### `WebhookStreamSession` events
359
+
360
+ | Event | Payload class | When |
361
+ | ---------------- | --------------------- | ----------------------------- |
362
+ | `ready` | `ReadyEvent` | Socket authenticated |
363
+ | `closed` | `ClosedEvent` | Transport closed |
364
+ | `node.started` | `NodeStartedEvent` | Engine entered a node |
365
+ | `node.token` | `NodeTokenEvent` | LLM streaming token |
366
+ | `node.completed` | `NodeCompletedEvent` | Node finished |
367
+ | `node.failed` | `NodeFailedEvent` | Node errored |
368
+ | `node.emit` | `NodeEmitEvent` | Custom telemetry from a node |
369
+ | `flow.completed` | `FlowCompletedEvent` | Engine finished |
370
+ | `flow.failed` | `FlowFailedEvent` | Engine errored |
371
+ | `error` | `ErrorEvent` | Protocol-level error |
372
+
373
+ Each payload is a Python dataclass — fields are typed, so editors autocomplete and type checkers catch typos.
374
+
375
+ ## Multimodal cheat sheet
376
+
377
+ | Modality | Webhook stream | Knowledge ingest |
378
+ | ------------------------------- | ------------------------- | ------------------------------- |
379
+ | `text` | ✅ | ✅ (`ingest_text`) |
380
+ | `image` (URL) | ✅ (`image_url_message`) | extract OCR text first |
381
+ | `image` (pre-uploaded mediaKey) | ✅ (`media_key_message`) | — |
382
+ | `image-multi` | ✅ | — |
383
+ | `audio` (mediaKey) | ✅ | transcribe first, ingest text |
384
+ | `document` (mediaKey) | ✅ | extract text first, ingest text |
385
+ | `form-response` | ✅ | — |
386
+ | Knowledge Base search | ✅ via flow tool | ✅ (`KnowledgeSession.search`) |
387
+
388
+ The Python SDK does not handle file extraction (PDF → text, image → OCR, audio → transcript). Use whatever tool fits your stack (Azure Document Intelligence, AssemblyAI, Tesseract, etc.) and feed the resulting text into `KnowledgeSession.ingest_text()`. The Daguito flow's `search_knowledge_base` tool then surfaces it to the model.
389
+
390
+ ## Testing from a script
391
+
392
+ The fastest way to verify your install works is to point the streaming session at any flow and dump events to stdout:
393
+
394
+ ```python
395
+ import asyncio
396
+ import os
397
+ from daguito import WebhookStreamSession, WebhookStreamOptions, text_message
398
+
399
+ async def main():
400
+ opts = WebhookStreamOptions(
401
+ api_url=os.environ["DAGUITO_API_URL"],
402
+ webhook_id=os.environ["DAGUITO_WEBHOOK_ID"],
403
+ token=os.environ["DAGUITO_WEBHOOK_TOKEN"],
404
+ )
405
+ async with WebhookStreamSession(opts) as session:
406
+ await session.send(text_message("ping"))
407
+ async for event_type, payload in session.events():
408
+ print(event_type, payload)
409
+ if event_type in ("flow.completed", "flow.failed"):
410
+ break
411
+
412
+ asyncio.run(main())
413
+ ```
414
+
415
+ ```bash
416
+ DAGUITO_API_URL=https://api.daguito.com \
417
+ DAGUITO_WEBHOOK_ID=wh_abc123 \
418
+ DAGUITO_WEBHOOK_TOKEN=sk_wh_... \
419
+ python smoke.py
420
+ ```
421
+
422
+ ## Typing
423
+
424
+ Every public symbol has full type hints, including the dataclass payloads emitted by streaming events. The package ships a `py.typed` marker so `mypy` / `pyright` pick the types up automatically.
425
+
426
+ ```python
427
+ from daguito import (
428
+ WebhookStreamSession,
429
+ WebhookStreamOptions,
430
+ NodeTokenEvent,
431
+ FlowCompletedEvent,
432
+ )
433
+ ```
434
+
435
+ ## Resources
436
+
437
+ - 🌐 [daguito.com](https://daguito.com) — landing & dashboard
438
+ - 📚 [docs.daguito.com](https://docs.daguito.com) — full API and flow reference
439
+ - 💬 [TypeScript SDK](https://github.com/daguitocontact-lang/js-sdk) — same surface, different runtime
440
+ - 🐛 [Issues](https://github.com/daguitocontact-lang/daguito-python/issues) — bug reports & feature requests
441
+ - 📦 [Source](https://github.com/daguitocontact-lang/daguito-python) — Python SDK repo
442
+
443
+ ## License
444
+
445
+ MIT © [Daguito, LLC](https://daguito.com)