sf-queue-sdk 0.3.0__tar.gz → 0.3.1b225__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.
Files changed (33) hide show
  1. sf_queue_sdk-0.3.1b225/PKG-INFO +782 -0
  2. sf_queue_sdk-0.3.1b225/README.md +774 -0
  3. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/pyproject.toml +1 -1
  4. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/__init__.py +11 -1
  5. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/client.py +4 -0
  6. sf_queue_sdk-0.3.1b225/queue_sdk/generated/queue/email/v1/email_pb2.py +47 -0
  7. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/queue/email/v1/email_pb2.pyi +26 -4
  8. sf_queue_sdk-0.3.1b225/queue_sdk/generated/queue/prompt_ingest/v1/prompt_ingest_pb2.py +43 -0
  9. sf_queue_sdk-0.3.1b225/queue_sdk/generated/queue/prompt_ingest/v1/prompt_ingest_pb2.pyi +76 -0
  10. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.py +3 -3
  11. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.py +3 -3
  12. sf_queue_sdk-0.3.1b225/queue_sdk/queues/document_upload_queue.py +241 -0
  13. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/email_queue.py +28 -4
  14. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/pdf_to_image_queue.py +18 -16
  15. sf_queue_sdk-0.3.1b225/queue_sdk/queues/prompt_ingest_queue.py +162 -0
  16. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/redis_client.py +10 -0
  17. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/types.py +96 -0
  18. sf_queue_sdk-0.3.0/PKG-INFO +0 -477
  19. sf_queue_sdk-0.3.0/README.md +0 -469
  20. sf_queue_sdk-0.3.0/queue_sdk/generated/queue/email/v1/email_pb2.py +0 -45
  21. sf_queue_sdk-0.3.0/queue_sdk/generated/queue/workflow/v1/workflow_pb2.py +0 -47
  22. sf_queue_sdk-0.3.0/queue_sdk/generated/queue/workflow/v1/workflow_pb2.pyi +0 -52
  23. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/.gitignore +0 -0
  24. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/__init__.py +0 -0
  25. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/queue/tool_assignment/v1/tool_assignment_pb2.pyi +0 -0
  26. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/generated/queue/topic_assignment/v1/topic_assignment_pb2.pyi +0 -0
  27. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/query.py +0 -0
  28. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/__init__.py +0 -0
  29. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/base_queue.py +0 -0
  30. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/recommendation_queue.py +0 -0
  31. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/tool_assignment_queue.py +0 -0
  32. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/queues/topic_assignment_queue.py +0 -0
  33. {sf_queue_sdk-0.3.0 → sf_queue_sdk-0.3.1b225}/queue_sdk/sanitize.py +0 -0
@@ -0,0 +1,782 @@
1
+ Metadata-Version: 2.4
2
+ Name: sf-queue-sdk
3
+ Version: 0.3.1b225
4
+ Summary: Python SDK for sf-queue - Redis-based queue system
5
+ Requires-Python: >=3.11
6
+ Requires-Dist: redis>=5.0.0
7
+ Description-Content-Type: text/markdown
8
+
9
+ # sf-queue-sdk
10
+
11
+ Python SDK for sf-queue. Enqueues emails via Redis Streams with optional blocking confirmation from the Go consumer service.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ pip install sf-queue-sdk
17
+ ```
18
+
19
+ ## Setup
20
+
21
+ ```python
22
+ from queue_sdk import QueueClient
23
+
24
+ client = QueueClient(
25
+ redis_url="redis://localhost:6379",
26
+ redis_password="your-password",
27
+ environment="staging", # prefixes stream names: staging:{email}
28
+ )
29
+ ```
30
+
31
+ ## Single Email
32
+
33
+ ### Fire and forget
34
+
35
+ Enqueues the email and returns immediately. Does not wait for the consumer to process it.
36
+
37
+ ```python
38
+ result = client.email.send(
39
+ to="user@example.com",
40
+ preview="Welcome to StudyFetch!",
41
+ subject="Welcome to StudyFetch!",
42
+ paragraphs=[
43
+ "Hey there,",
44
+ "Welcome to the StudyFetch community!",
45
+ "Thanks for joining us.",
46
+ ],
47
+ button={
48
+ "text": "Go to Platform",
49
+ "href": "https://www.studyfetch.com/platform",
50
+ },
51
+ )
52
+
53
+ print("Enqueued:", result.message_id)
54
+ ```
55
+
56
+ ### Send and wait for confirmation
57
+
58
+ Enqueues the email and blocks until the Go consumer processes it (or timeout).
59
+
60
+ ```python
61
+ result = client.email.send_and_wait(
62
+ to="user@example.com",
63
+ preview="Reset your password",
64
+ subject="StudyFetch: Reset Your Password",
65
+ paragraphs=[
66
+ "Hi There,",
67
+ "Click the button below to reset your password.",
68
+ ],
69
+ button={
70
+ "text": "Reset Password",
71
+ "href": "https://www.studyfetch.com/reset?token=abc",
72
+ },
73
+ timeout=30, # optional, default 30s
74
+ )
75
+
76
+ print(result.success) # True or False
77
+ print(result.message_id) # request ID
78
+ print(result.error) # error message if failed
79
+ ```
80
+
81
+ ## Batch Email
82
+
83
+ Send the same email content to multiple recipients (up to 100). The Go consumer sends to each recipient individually.
84
+
85
+ ### Fire and forget
86
+
87
+ ```python
88
+ result = client.email.send_batch(
89
+ to=[
90
+ "student1@example.com",
91
+ "student2@example.com",
92
+ "student3@example.com",
93
+ ],
94
+ preview="You have been invited to join a class!",
95
+ subject="StudyFetch: Class Invitation",
96
+ paragraphs=[
97
+ "Hi There,",
98
+ 'You have been invited to join "Intro to CS" on StudyFetch!',
99
+ "Click the button below to accept the invite.",
100
+ ],
101
+ button={
102
+ "text": "Accept Invite",
103
+ "href": "https://www.studyfetch.com/invite/abc",
104
+ },
105
+ )
106
+
107
+ print("Enqueued:", result.message_id)
108
+ ```
109
+
110
+ ### Send and wait for confirmation
111
+
112
+ ```python
113
+ result = client.email.send_batch_and_wait(
114
+ to=[
115
+ "student1@example.com",
116
+ "student2@example.com",
117
+ "student3@example.com",
118
+ ],
119
+ preview="You have been invited to join a class!",
120
+ subject="StudyFetch: Class Invitation",
121
+ paragraphs=[
122
+ "Hi There,",
123
+ 'You have been invited to join "Intro to CS" on StudyFetch!',
124
+ "Click the button below to accept the invite.",
125
+ ],
126
+ button={
127
+ "text": "Accept Invite",
128
+ "href": "https://www.studyfetch.com/invite/abc",
129
+ },
130
+ timeout=30,
131
+ )
132
+
133
+ print(result.success) # True if at least some sent
134
+ print(result.message_id) # request ID
135
+ print(result.total) # 3
136
+ print(result.successful) # number sent successfully
137
+ print(result.failed) # number that failed
138
+ print(result.error) # error message if all failed
139
+ ```
140
+
141
+ ## All Email Fields
142
+
143
+ | Field | Type | Required | Description |
144
+ |-------|------|----------|-------------|
145
+ | `to` | `str` | Yes (single) | Recipient email address |
146
+ | `to` | `list[str]` | Yes (batch) | List of recipient emails (max 100) |
147
+ | `preview` | `str` | Yes | Preview text shown in email clients |
148
+ | `subject` | `str` | Yes | Email subject line |
149
+ | `paragraphs` | `list[str]` | Yes | Body content as paragraph strings |
150
+ | `button` | `{"text": str, "href": str}` | No | Call-to-action button |
151
+ | `reply_to` | `str` | No | Reply-to email address |
152
+ | `image` | `{"src": str, "alt"?: str, "width"?: int, "height"?: int}` | No | Image in email body |
153
+ | `service` | `str` | No | Service name for routing (e.g. `"sparkles"`). Routes to service-specific credentials, from address, and template. Defaults to the base config when omitted. |
154
+ | `template` | `str` | No | Template name override (e.g. `"sparkles"`). Overrides the service's default template. Falls back to `"studyfetch"` when omitted. |
155
+ | `brand` | `{"name"?: str, "logoUrl"?: str, "url"?: str, "accentColor"?: str}` | No | Per-workspace whitelabel branding (Honen template). `name` sets the wordmark/footer text **and** the From display name (the sending address and Resend key are unchanged, preserving the verified domain). Also overrides the logo, footer + header + unsubscribe link base, and CTA color. Any omitted field falls back to the default Honen brand. |
156
+
157
+ ## Optional Fields
158
+
159
+ ```python
160
+ # With all optional fields
161
+ client.email.send(
162
+ to="support@studyfetch.com",
163
+ preview="Support Request",
164
+ subject="StudyFetch: Support Request",
165
+ paragraphs=["Hi There,", "You received a support request.", issue],
166
+ reply_to="requester@example.com",
167
+ image={
168
+ "src": "https://example.com/logo.png",
169
+ "alt": "Logo",
170
+ "width": 150,
171
+ "height": 50,
172
+ },
173
+ )
174
+ ```
175
+
176
+ ## Multi-Service Routing
177
+
178
+ Use `service` to route emails through a different service's credentials and branding. The consumer resolves the service name to its configured Resend API key, from address, and default template. Use `template` to override the template for a specific email.
179
+
180
+ ```python
181
+ # Send via a different service's email account and template
182
+ client.email.send(
183
+ to="user@example.com",
184
+ preview="Welcome!",
185
+ subject="Welcome!",
186
+ paragraphs=["Welcome to the platform!"],
187
+ service="sparkles",
188
+ )
189
+
190
+ # Override the template for a specific email
191
+ client.email.send(
192
+ to="user@example.com",
193
+ preview="Welcome!",
194
+ subject="Welcome!",
195
+ paragraphs=["Welcome to the platform!"],
196
+ service="sparkles",
197
+ template="sparkles",
198
+ )
199
+ ```
200
+
201
+ ## Whitelabel Branding
202
+
203
+ Pass `brand` to render the Honen template with a workspace's own identity. Any
204
+ omitted field falls back to the default Honen brand, so unbranded emails are
205
+ unaffected.
206
+
207
+ ```python
208
+ client.email.send(
209
+ to="member@acme.com",
210
+ preview="You have been invited",
211
+ subject="Join the Acme workspace",
212
+ paragraphs=["You have been invited to Acme."],
213
+ button={"text": "Accept invite", "href": "https://learn.acme.com/invite/abc"},
214
+ brand={
215
+ "name": "Acme", # wordmark + From display name ("Acme <noreply@...>")
216
+ "logoUrl": "https://cdn.acme.com/logo.png",
217
+ "url": "https://learn.acme.com", # header/footer/unsubscribe link base
218
+ "accentColor": "#0043FF", # CTA button color
219
+ },
220
+ )
221
+ ```
222
+
223
+ The `name` also becomes the From display name: the email arrives from
224
+ `Acme <noreply@honen.studyfetch.com>` instead of `Honen <noreply@...>`. Only the
225
+ display label changes — the verified sending address and the service's Resend
226
+ key stay the same, so DKIM/SPF alignment is unaffected. Omit `name` (or the whole
227
+ `brand`) to keep the default Honen From.
228
+
229
+ ## Migrating from sendEmail / sendBatchEmail
230
+
231
+ The SDK is a drop-in replacement. Field names match the existing functions:
232
+
233
+ ```python
234
+ # BEFORE
235
+ send_email(to=to, preview=preview, subject=subject, paragraphs=paragraphs, button=button)
236
+
237
+ # AFTER (fire and forget)
238
+ client.email.send(to=to, preview=preview, subject=subject, paragraphs=paragraphs, button=button)
239
+
240
+ # AFTER (wait for confirmation)
241
+ client.email.send_and_wait(to=to, preview=preview, subject=subject, paragraphs=paragraphs, button=button)
242
+
243
+ # BEFORE (batch)
244
+ send_batch_email(to=[...], preview=preview, subject=subject, paragraphs=paragraphs, button=button)
245
+
246
+ # AFTER (batch, fire and forget)
247
+ client.email.send_batch(to=[...], preview=preview, subject=subject, paragraphs=paragraphs, button=button)
248
+
249
+ # AFTER (batch, wait for confirmation)
250
+ client.email.send_batch_and_wait(to=[...], preview=preview, subject=subject, paragraphs=paragraphs, button=button)
251
+ ```
252
+
253
+ ## Methods and Response Types
254
+
255
+ | Method | Return Type | Fields |
256
+ |--------|-------------|--------|
257
+ | `send()` | `SendResult` | `message_id` |
258
+ | `send_and_wait()` | `EmailResponse` | `success`, `message_id`, `error?`, `processed_at?` |
259
+ | `send_batch()` | `SendResult` | `message_id` |
260
+ | `send_batch_and_wait()` | `BatchEmailResponse` | `success`, `message_id`, `error?`, `processed_at?`, `total`, `successful`, `failed` |
261
+
262
+ ## Topic Assignment
263
+
264
+ ### Fire and forget
265
+
266
+ ```python
267
+ result = client.topic_assignment.send(
268
+ topic_vector_id="507f1f77bcf86cd799439011",
269
+ topic_id="topic_abc123",
270
+ user_id="user_xyz",
271
+ current_assignment=None,
272
+ new_assignment="faiss_1422.0",
273
+ )
274
+ print("Enqueued:", result.message_id)
275
+ ```
276
+
277
+ ### Send and wait for confirmation
278
+
279
+ ```python
280
+ result = client.topic_assignment.send_and_wait(
281
+ topic_vector_id="507f1f77bcf86cd799439011",
282
+ topic_id="topic_abc123",
283
+ user_id="user_xyz",
284
+ current_assignment=None,
285
+ new_assignment="faiss_1422.0",
286
+ timeout=30,
287
+ )
288
+ print(result.success)
289
+ print(result.message_id)
290
+ ```
291
+
292
+ ### Batch (fire and forget)
293
+
294
+ ```python
295
+ result = client.topic_assignment.send_batch(
296
+ assignments=[
297
+ {
298
+ "topic_vector_id": "507f1f77bcf86cd799439011",
299
+ "topic_id": "topic_abc123",
300
+ "user_id": "user_xyz",
301
+ "current_assignment": None,
302
+ "new_assignment": "faiss_1422.0",
303
+ },
304
+ {
305
+ "topic_vector_id": "507f1f77bcf86cd799439012",
306
+ "topic_id": "topic_def456",
307
+ "user_id": "user_xyz",
308
+ "current_assignment": "faiss_1422",
309
+ "new_assignment": "faiss_1422.1",
310
+ },
311
+ ],
312
+ )
313
+ ```
314
+
315
+ ### Batch (wait for confirmation)
316
+
317
+ ```python
318
+ result = client.topic_assignment.send_batch_and_wait(
319
+ assignments=[
320
+ {
321
+ "topic_vector_id": "507f1f77bcf86cd799439011",
322
+ "topic_id": "topic_abc123",
323
+ "user_id": "user_xyz",
324
+ "current_assignment": None,
325
+ "new_assignment": "faiss_1422.0",
326
+ },
327
+ ],
328
+ timeout=30,
329
+ )
330
+ print(result.success)
331
+ print(result.total)
332
+ print(result.successful)
333
+ print(result.failed)
334
+ ```
335
+
336
+ ### Topic Assignment Fields
337
+
338
+ | Field | Type | Required | Description |
339
+ |-------|------|----------|-------------|
340
+ | `topic_vector_id` | `str` | Yes | MongoDB _id of the topic_vector |
341
+ | `topic_id` | `str` | Yes | The topicId field |
342
+ | `user_id` | `str` | Yes | The userId who owns the topic |
343
+ | `current_assignment` | `Optional[str]` | Yes | Current cluster_id (None for first-time) |
344
+ | `new_assignment` | `str` | Yes | The new cluster_id |
345
+
346
+ ## Tool Assignment
347
+
348
+ ### Fire and forget
349
+
350
+ ```python
351
+ result = client.tool_assignment.send(
352
+ tool_id="507f1f77bcf86cd799439011",
353
+ user_id="user_xyz",
354
+ topic_id="topic_abc123",
355
+ current_assignment=None,
356
+ new_assignment="faiss_1422.0",
357
+ )
358
+ ```
359
+
360
+ ### Send and wait for confirmation
361
+
362
+ ```python
363
+ result = client.tool_assignment.send_and_wait(
364
+ tool_id="507f1f77bcf86cd799439011",
365
+ user_id="user_xyz",
366
+ topic_id="topic_abc123",
367
+ current_assignment=None,
368
+ new_assignment="faiss_1422.0",
369
+ timeout=30,
370
+ )
371
+ ```
372
+
373
+ ### Batch (fire and forget)
374
+
375
+ ```python
376
+ result = client.tool_assignment.send_batch(
377
+ assignments=[
378
+ {
379
+ "tool_id": "507f1f77bcf86cd799439011",
380
+ "user_id": "user_xyz",
381
+ "topic_id": "topic_abc123",
382
+ "current_assignment": None,
383
+ "new_assignment": "faiss_1422.0",
384
+ },
385
+ {
386
+ "tool_id": "507f1f77bcf86cd799439012",
387
+ "user_id": "user_xyz",
388
+ "topic_id": "topic_abc123",
389
+ "current_assignment": "faiss_1422",
390
+ "new_assignment": "faiss_1422.1",
391
+ },
392
+ ],
393
+ )
394
+ ```
395
+
396
+ ### Batch (wait for confirmation)
397
+
398
+ ```python
399
+ result = client.tool_assignment.send_batch_and_wait(
400
+ assignments=[
401
+ {"tool_id": "...", "user_id": "...", "topic_id": "...", "current_assignment": None, "new_assignment": "..."},
402
+ ],
403
+ timeout=30,
404
+ )
405
+ ```
406
+
407
+ ### Tool Assignment Fields
408
+
409
+ | Field | Type | Required | Description |
410
+ |-------|------|----------|-------------|
411
+ | `tool_id` | `str` | Yes | MongoDB _id of the tool document |
412
+ | `user_id` | `str` | Yes | The userId who owns the tool |
413
+ | `topic_id` | `str` | Yes | The topicId the tool belongs to |
414
+ | `current_assignment` | `Optional[str]` | Yes | Current cluster_id (None for new embeddings) |
415
+ | `new_assignment` | `str` | Yes | The cluster_id to assign to |
416
+
417
+ ## Recommendations
418
+
419
+ ### Fire and forget
420
+
421
+ ```python
422
+ result = client.recommendations.create(
423
+ user_id="user_xyz",
424
+ topic_id="topic_abc123",
425
+ limit=10, # optional
426
+ )
427
+ print("Enqueued:", result.message_id)
428
+ ```
429
+
430
+ ### Create and wait for result
431
+
432
+ ```python
433
+ result = client.recommendations.create_and_wait(
434
+ user_id="user_xyz",
435
+ topic_id="topic_abc123",
436
+ limit=10, # optional
437
+ timeout=30,
438
+ )
439
+ print(result.success)
440
+ print(result.message_id)
441
+ ```
442
+
443
+ ### Batch (fire and forget)
444
+
445
+ ```python
446
+ result = client.recommendations.create_batch([
447
+ {"user_id": "user_1", "topic_id": "topic_1", "limit": 10},
448
+ {"user_id": "user_2", "topic_id": "topic_2"},
449
+ ])
450
+ print("Enqueued:", result.message_id)
451
+ ```
452
+
453
+ ### Batch (wait for confirmation)
454
+
455
+ ```python
456
+ result = client.recommendations.create_batch_and_wait(
457
+ [
458
+ {"user_id": "user_1", "topic_id": "topic_1"},
459
+ {"user_id": "user_2", "topic_id": "topic_2"},
460
+ ],
461
+ timeout=60,
462
+ )
463
+ print(result.success)
464
+ print(result.total)
465
+ print(result.successful)
466
+ print(result.failed)
467
+ ```
468
+
469
+ ### Recommendation Fields
470
+
471
+ | Field | Type | Required | Description |
472
+ |-------|------|----------|-------------|
473
+ | `user_id` | `str` | Yes | User requesting recommendations |
474
+ | `topic_id` | `str` | Yes | Topic context for recommendations |
475
+ | `limit` | `Optional[int]` | No | Max recommendations to return |
476
+
477
+ ## PDF to Image Conversion
478
+
479
+ Convert PDFs, videos, and other documents to images. The Go `pdf-to-image-queue` worker downloads the source file, converts it, uploads the result to storage, and returns the image URL.
480
+
481
+ ### Fire and forget
482
+
483
+ ```python
484
+ result = client.pdf_to_image.convert(
485
+ url="https://example.com/document.pdf",
486
+ material_id="mat_abc123",
487
+ # page=1, # optional
488
+ # format="webp", # optional
489
+ # quality=80, # optional
490
+ )
491
+
492
+ print("Enqueued:", result.message_id)
493
+ ```
494
+
495
+ ### Convert and wait for result
496
+
497
+ ```python
498
+ result = client.pdf_to_image.convert_and_wait(
499
+ url="https://example.com/document.pdf",
500
+ material_id="mat_abc123",
501
+ # page=1, # optional
502
+ # format="webp", # optional
503
+ # quality=80, # optional
504
+ # timeout=30, # optional
505
+ )
506
+
507
+ print(result.success) # True or False
508
+ print(result.image_url) # URL of the converted image
509
+ print(result.format) # "webp" or "png"
510
+ print(result.filename) # output filename
511
+ print(result.error) # error message if failed
512
+ ```
513
+
514
+ ### Convert Fields
515
+
516
+ | Field | Type | Required | Description |
517
+ |-------|------|----------|-------------|
518
+ | `url` | `str` | Yes | URL of the source file (PDF, video, etc.) |
519
+ | `material_id` | `str` | Yes | ID of the material to associate with the conversion |
520
+ | `page` | `Optional[int]` | No | PDF page number to convert (default: `1`, must be a positive integer) |
521
+ | `format` | `Optional[str]` | No | Output image format: `"png"` or `"webp"` (default: `"webp"`) |
522
+ | `quality` | `Optional[int]` | No | Image quality 1-100 (default: `80`, only applies to webp) |
523
+
524
+ ### Convert Response
525
+
526
+ | Field | Type | Description |
527
+ |-------|------|-------------|
528
+ | `success` | `bool` | Whether the conversion succeeded |
529
+ | `message_id` | `str` | Request ID |
530
+ | `image_url` | `Optional[str]` | URL of the uploaded image |
531
+ | `format` | `Optional[str]` | Output format used |
532
+ | `filename` | `Optional[str]` | Output filename |
533
+ | `error` | `Optional[str]` | Error message if `success` is `False` |
534
+ | `processed_at` | `Optional[str]` | ISO timestamp of when the worker processed the request |
535
+
536
+ ## Document Upload
537
+
538
+ Enqueue document processing jobs for the Python `document-upload-queue` worker. The worker runs a multi-step pipeline: PDF chunking, embedding, clustering, LLM title generation, and study plan assembly. This is fire-and-forget only -- the worker processes asynchronously and pushes results to S3.
539
+
540
+ ### Fire and forget
541
+
542
+ ```python
543
+ result = client.document_upload.send(
544
+ uploaded_file_id="file_abc123",
545
+ study_set_id="set_xyz",
546
+ user_id="user_123",
547
+ text_extract_s3_key="uploadedfiles/file_abc123/textContent",
548
+ pdf_s3_key="uploads/document.pdf",
549
+ file_type="application/pdf",
550
+ effective_file_type="application/pdf",
551
+ filename="Chapter 5 - Thermodynamics.pdf",
552
+ material_title="Thermodynamics",
553
+ url="https://storage.example.com/uploads/document.pdf",
554
+ generate_notes=True,
555
+ generate_study_path=True,
556
+ )
557
+
558
+ print("Enqueued:", result.message_id)
559
+ ```
560
+
561
+ ### With optional fields
562
+
563
+ ```python
564
+ result = client.document_upload.send(
565
+ uploaded_file_id="file_abc123",
566
+ study_set_id="set_xyz",
567
+ user_id="user_123",
568
+ text_extract_s3_key="uploadedfiles/file_abc123/textContent",
569
+ pdf_s3_key="uploads/document.pdf",
570
+ file_type="application/pdf",
571
+ effective_file_type="application/pdf",
572
+ filename="Chapter 5 - Thermodynamics.pdf",
573
+ material_title="Thermodynamics",
574
+ url="https://storage.example.com/uploads/document.pdf",
575
+ generate_notes=True,
576
+ generate_study_path=True,
577
+ splitting="auto",
578
+ language="en",
579
+ use_markdown_ocr=False,
580
+ flashcard_amount=12,
581
+ skill_level="undergraduate",
582
+ typeof_notes="comprehensive",
583
+ material_group_id="group_123",
584
+ folder_id="folder_456",
585
+ upload_batch_id="batch_789",
586
+ )
587
+ ```
588
+
589
+ ### Document Upload Fields
590
+
591
+ | Field | Type | Required | Description |
592
+ |-------|------|----------|-------------|
593
+ | `uploaded_file_id` | `str` | Yes | ID of the uploaded file record |
594
+ | `study_set_id` | `str` | Yes | Study set to associate the processed material with |
595
+ | `user_id` | `str` | Yes | User who uploaded the document |
596
+ | `text_extract_s3_key` | `str` | Yes | S3 key for extracted text content |
597
+ | `pdf_s3_key` | `str` | Yes | S3 key of the source PDF |
598
+ | `file_type` | `str` | Yes | MIME type of the uploaded file |
599
+ | `effective_file_type` | `str` | Yes | Resolved file type after any conversion |
600
+ | `filename` | `str` | Yes | Original filename |
601
+ | `material_title` | `str` | Yes | Display title for the material |
602
+ | `url` | `str` | Yes | Download URL for the source file |
603
+ | `generate_notes` | `bool` | Yes | Whether to generate study notes |
604
+ | `generate_study_path` | `bool` | Yes | Whether to generate a study path |
605
+ | `splitting` | `str` | No | Splitting strategy (default: `"auto"`) |
606
+ | `language` | `str` | No | Target language code (default: `"en"`) |
607
+ | `use_markdown_ocr` | `bool` | No | Use Markdown OCR for image interpretation (default: `False`) |
608
+ | `flashcard_amount` | `int` | No | Number of flashcards to generate (default: `12`) |
609
+ | `new_url` | `Optional[str]` | No | Alternative URL if file was re-uploaded |
610
+ | `typeof_notes` | `Optional[str]` | No | Note generation mode (`"comprehensive"`, `"detailed"`, `"summarized"`) |
611
+ | `skill_level` | `Optional[str]` | No | User's skill level for content adaptation |
612
+ | `features` | `Optional[dict]` | No | Feature flags for optional processing steps |
613
+ | `selected_questions` | `Optional[list]` | No | Pre-selected questions for the material |
614
+ | `material_group_id` | `Optional[str]` | No | Material group for organization |
615
+ | `folder_id` | `Optional[str]` | No | Folder for organization |
616
+ | `upload_batch_id` | `Optional[str]` | No | Batch ID when multiple files uploaded together |
617
+ | `youtube_title` | `Optional[str]` | No | Title override for YouTube video transcripts |
618
+
619
+ ### Response Type
620
+
621
+ | Method | Return Type | Fields |
622
+ |--------|-------------|--------|
623
+ | `send()` | `SendResult` | `message_id` |
624
+ | `get_status(request_id)` | `DocumentUploadStatus` | See below |
625
+
626
+ ### Status Tracking
627
+
628
+ After enqueueing a document upload, use `get_status()` to poll the processing progress. The `send()` method returns a `message_id` that you pass to `get_status()`.
629
+
630
+ ```python
631
+ result = client.document_upload.send(
632
+ uploaded_file_id="file_abc123",
633
+ study_set_id="set_xyz",
634
+ user_id="user_123",
635
+ text_extract_s3_key="uploadedfiles/file_abc123/textContent",
636
+ pdf_s3_key="uploads/document.pdf",
637
+ file_type="application/pdf",
638
+ effective_file_type="application/pdf",
639
+ filename="Chapter 5 - Thermodynamics.pdf",
640
+ material_title="Thermodynamics",
641
+ url="https://storage.example.com/uploads/document.pdf",
642
+ generate_notes=True,
643
+ generate_study_path=True,
644
+ )
645
+
646
+ # Poll for progress
647
+ status = client.document_upload.get_status(result.message_id)
648
+ print(f"Status: {status.status}") # "queued", "processing", "completed", or "failed"
649
+ print(f"Step: {status.step} ({status.step_number}/{status.total_steps})")
650
+ print(f"Progress: {status.percent_complete}%")
651
+ print(f"Detail: {status.detail}") # e.g. "Generated notes for topic 3/7"
652
+ print(f"Pipeline: {status.pipeline_type}") # e.g. "pdf", "youtube", "image"
653
+ ```
654
+
655
+ ### DocumentUploadStatus Fields
656
+
657
+ | Field | Type | Description |
658
+ |-------|------|-------------|
659
+ | `request_id` | `str` | The request ID from `send()` |
660
+ | `status` | `str` | `"queued"`, `"processing"`, `"completed"`, `"failed"`, or `"unknown"` |
661
+ | `step` | `str` | Current step name (e.g. `"downloading"`, `"embedding"`, `"generating_notes"`) |
662
+ | `step_number` | `int` | Current step index (1-based) |
663
+ | `total_steps` | `int` | Total steps for this pipeline type |
664
+ | `percent_complete` | `int` | Overall progress 0-100 |
665
+ | `pipeline_type` | `str` | Pipeline chosen after routing (e.g. `"pdf"`, `"youtube"`, `"image"`) |
666
+ | `detail` | `str` | Sub-step detail (e.g. `"Generated notes for topic 3/7"`) |
667
+ | `started_at` | `str` | ISO timestamp when processing started |
668
+ | `updated_at` | `str` | ISO timestamp of last status update |
669
+ | `error` | `str` | Error message if status is `"failed"` |
670
+
671
+ Status data is stored in Redis with a 1-hour TTL, reset on every update. If `get_status()` is called with an unknown request ID or after the TTL expires, it returns `status="unknown"`.
672
+
673
+ ## Prompt Ingest (LLM Call Logging)
674
+
675
+ Send LLM prompt records to the prompt-analyze-api consumer for storage and analysis. This is designed as a fire-and-forget call from within your LiteLLM router (or any LLM orchestration layer) so that logging never blocks inference.
676
+
677
+ ### Fire and forget
678
+
679
+ ```python
680
+ result = client.prompt_ingest.send(
681
+ call_type="chat",
682
+ request_payload={
683
+ "format": "chat",
684
+ "messages": [
685
+ {"role": "system", "content": "You are a helpful tutor."},
686
+ {"role": "user", "content": "Explain photosynthesis."},
687
+ ],
688
+ },
689
+ status="success",
690
+ model_requested="gpt-4o",
691
+ model_deployed="gpt-4o-2024-08-06",
692
+ provider="openai",
693
+ params={"temperature": 0.7},
694
+ metadata={"user_id": "user_abc", "session_id": "sess_123"},
695
+ tags=["production", "tutor"],
696
+ response_payload={
697
+ "format": "chat",
698
+ "message": {"role": "assistant", "content": "Photosynthesis is..."},
699
+ },
700
+ usage={"input_tokens": 45, "output_tokens": 120, "total_tokens": 165},
701
+ latency_ms=1200,
702
+ cost_usd=0.003,
703
+ content_hash="a1b2c3d4...",
704
+ )
705
+
706
+ print("Enqueued:", result.message_id)
707
+ ```
708
+
709
+ ### Send and wait for confirmation
710
+
711
+ ```python
712
+ result = client.prompt_ingest.send_and_wait(
713
+ call_type="chat",
714
+ request_payload={"format": "chat", "messages": [...]},
715
+ status="success",
716
+ model_requested="gpt-4o",
717
+ timeout=10,
718
+ )
719
+
720
+ print(result.success)
721
+ print(result.message_id)
722
+ ```
723
+
724
+ ### Prompt Ingest Fields
725
+
726
+ | Field | Type | Required | Description |
727
+ |-------|------|----------|-------------|
728
+ | `call_type` | `str` | Yes | Type of LLM call: `"chat"`, `"text_completion"`, or `"responses"` |
729
+ | `request_payload` | `dict` | Yes | Structured request (messages, prompt, or input) |
730
+ | `status` | `str` | Yes | `"success"` or `"failure"` |
731
+ | `request_id` | `Optional[str]` | No | Unique call ID (e.g. litellm_call_id) |
732
+ | `model_requested` | `Optional[str]` | No | Model name from the request |
733
+ | `model_deployed` | `Optional[str]` | No | Actual model/deployment used |
734
+ | `provider` | `Optional[str]` | No | Provider name (e.g. `"openai"`, `"anthropic"`) |
735
+ | `params` | `Optional[dict]` | No | Model params (temperature, top_p, etc.) |
736
+ | `metadata` | `Optional[dict]` | No | Arbitrary metadata (user_id, session, etc.) |
737
+ | `tags` | `Optional[list[str]]` | No | Filterable tags |
738
+ | `response_payload` | `Optional[dict]` | No | Structured response |
739
+ | `response_raw` | `Optional[dict]` | No | Full raw provider response |
740
+ | `usage` | `Optional[dict]` | No | Token usage (input_tokens, output_tokens, total_tokens) |
741
+ | `error` | `Optional[str]` | No | Error message (when status is `"failure"`) |
742
+ | `latency_ms` | `Optional[int]` | No | End-to-end call duration in milliseconds |
743
+ | `cost_usd` | `Optional[float]` | No | Estimated cost in USD |
744
+ | `content_hash` | `Optional[str]` | No | SHA-256 hash of normalized input text |
745
+
746
+ ### Response Types
747
+
748
+ | Method | Return Type | Fields |
749
+ |--------|-------------|--------|
750
+ | `send()` | `SendResult` | `message_id` |
751
+ | `send_and_wait()` | `QueueResponse` | `success`, `message_id`, `error?`, `processed_at?` |
752
+
753
+ ## Query (Direct Key Lookups)
754
+
755
+ Read values directly from Redis by key. These are plain `GET` / `MGET` calls, not queue operations.
756
+
757
+ ### Get a single key
758
+
759
+ ```python
760
+ value = client.query.get("mykey")
761
+ print(value) # str or None
762
+ ```
763
+
764
+ ### Get multiple keys
765
+
766
+ ```python
767
+ values = client.query.get_many(["key1", "key2", "key3"])
768
+ print(values) # list[str | None]
769
+ ```
770
+
771
+ ### Query Methods
772
+
773
+ | Method | Return Type | Description |
774
+ |--------|-------------|-------------|
775
+ | `get(key)` | `Optional[str]` | Fetch a single key via Redis GET |
776
+ | `get_many(keys)` | `list[Optional[str]]` | Fetch multiple keys via Redis MGET |
777
+
778
+ ## Cleanup
779
+
780
+ ```python
781
+ client.disconnect()
782
+ ```