taskiq-llm-queue 0.1.0a2__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.
Files changed (79) hide show
  1. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/CHANGELOG.md +20 -0
  2. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/PKG-INFO +22 -4
  3. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/README.md +413 -395
  4. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/STATUS.md +29 -20
  5. taskiq_llm_queue-0.2.0/docs/VALKEY_QUEUE_PLAN.md +149 -0
  6. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/pyproject.toml +141 -141
  7. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/samples/README.md +31 -0
  8. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/_version.py +5 -5
  9. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/broker.py +11 -2
  10. taskiq_llm_queue-0.2.0/src/taskiq_llm_queue/broker/valkey_queue.py +725 -0
  11. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/cli.py +719 -669
  12. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/client.py +54 -1
  13. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/config.py +25 -0
  14. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/envelope.py +2 -0
  15. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/failover.py +11 -0
  16. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/langchain/bedrock.py +8 -0
  17. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/langchain/openai.py +1 -0
  18. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/lifecycle.py +27 -2
  19. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/metrics_names.py +3 -0
  20. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/registry.py +14 -6
  21. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/tasks.py +4 -0
  22. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/.gitignore +0 -0
  23. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/LICENSE +0 -0
  24. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/NOTICE +0 -0
  25. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/STABILITY.md +0 -0
  26. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/ARCHITECTURE.md +0 -0
  27. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/DESIGN.md +0 -0
  28. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/EXAMPLES.md +0 -0
  29. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/RATE_LIMIT_PLAN.md +0 -0
  30. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/RELEASING.md +0 -0
  31. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/docs/TESTING.md +0 -0
  32. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/__init__.py +0 -0
  33. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/_logging.py +0 -0
  34. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/__init__.py +0 -0
  35. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/acker.py +0 -0
  36. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/batching.py +0 -0
  37. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/delivery.py +0 -0
  38. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/errors.py +0 -0
  39. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/events.py +0 -0
  40. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/filters.py +0 -0
  41. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/heartbeat.py +0 -0
  42. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/queue.py +0 -0
  43. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/s3_offload.py +0 -0
  44. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/scheduler.py +0 -0
  45. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/broker/serializer.py +0 -0
  46. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/context.py +0 -0
  47. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/core.py +0 -0
  48. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/errors.py +0 -0
  49. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/http_transport.py +0 -0
  50. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/keys.py +0 -0
  51. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/langchain/__init__.py +0 -0
  52. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/langchain/_client.py +0 -0
  53. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/metrics/__init__.py +0 -0
  54. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/metrics/emf.py +0 -0
  55. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/metrics/prometheus.py +0 -0
  56. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/model_settings.py +0 -0
  57. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/protocols.py +0 -0
  58. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/__init__.py +0 -0
  59. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/bedrock.py +0 -0
  60. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/classify.py +0 -0
  61. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/embeddings.py +0 -0
  62. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/hosts.py +0 -0
  63. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/providers/openai_compat.py +0 -0
  64. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/py.typed +0 -0
  65. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/__init__.py +0 -0
  66. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/_lua.py +0 -0
  67. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/breaker.py +0 -0
  68. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/claims.py +0 -0
  69. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/config_store.py +0 -0
  70. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/fallback.py +0 -0
  71. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/limiter.py +0 -0
  72. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/local_limiter.py +0 -0
  73. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/reliability/rate.py +0 -0
  74. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/results/__init__.py +0 -0
  75. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/results/backend.py +0 -0
  76. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/results/dispatcher.py +0 -0
  77. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/results/s3_backend.py +0 -0
  78. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/routing.py +0 -0
  79. {taskiq_llm_queue-0.1.0a2 → taskiq_llm_queue-0.2.0}/src/taskiq_llm_queue/routing_hooks.py +0 -0
@@ -6,6 +6,26 @@ a minor release may break the public API, with a deprecation announced one minor
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-11-02
10
+
11
+ ### Changed
12
+ - Development status Alpha. `llm-queue queues` takes subcommands (`list` default, `create`, `redrive`, `purge`); `queues`, `status` and `job send` use the configured queue backend. Job messages carry their name in the `llmq_job` label (`send_job` and `llm-queue job send`); older messages still match by args.
13
+ - Scenario harness: `SCN_QUEUE_BACKEND=valkey` runs every scenario on Valkey Streams against `valkey-scn-persist` (AOF `always`, `noeviction`); S20 and S33 are SQS-only, S33v Valkey-only.
14
+
15
+ ### Verified
16
+ - 869 tests (unit, integration and every scenario on SQS): 866 pass, 2 skip by design; S27 at its full 450K-call default exceeds what one laptop load generator offers (196K in 30 min) and passes at the gate load (6K/min × 3 min, rt queue wait p95 1.1 s). All scenarios pass on Valkey; samples 01-17 exit 0 on both backends; real Bedrock: S01, S03, S04, S05, S16, S17, S19, S22, S26, S28, S29, S40, S41 pass.
17
+
18
+ ### Fixed
19
+ - Jobs whose payload was offloaded to S3 were never run: the job filter read the job name from the args, which offload empties, so the message was released every 30 s until the DLQ. Jobs now carry the name in the `llmq_job` label (older messages still match by args). Scenario S24.
20
+ - The first sync call stalled the caller's event loop 0.2-0.6 s: every new `ProviderRegistry` re-scanned all installed packages' entry points, holding the GIL. The scan now runs once per process. The sync runner's session also reuses the main session's parsed botocore data. A first burst of sync calls on real AWS still stalls the loop 0.1-1.8 s once per pod while clients and HTTPS connections are built (S22 reports it as `cold_burst_lag_ms`); warm bursts stay under 60 ms.
21
+ - Tracing: `QueuedChatBedrockConverse` and `QueuedChatOpenAI` now default `name` to the parent class, so Langfuse / LangSmith generations keep the name `ChatBedrockConverse` / `ChatOpenAI`. After a fallback, `llm_output["model_name"]` is the Bedrock id that served the call, so tracers show and price the right model.
22
+
23
+ ### Added
24
+ - Valkey Streams queue backend: `queue_backend="valkey"` (`LLMQ_QUEUE_BACKEND`), optional `queue_redis_url` (`LLMQ_QUEUE_REDIS_URL`). `ValkeyQueueClient` (`broker/valkey_queue.py`) serves the SQS calls `HybridSQSBroker` makes (visibility, receipt-handle generations, delay, DLQ after `maxReceiveCount`, long polls woken by pub/sub), so the broker and everything above it are unchanged; `HybridSQSBroker(client_factory=...)` is the hook. CLI `queues create | list | redrive | purge`; gauges `llmq_queue_depth{queue,kind}` and `llmq_queue_oldest_seconds{queue}`. FIFO queues are refused. A client re-creates a queue it knew when its keys vanish (restart without persistence); release deadlines and idle re-checks are jittered so the releasing pod does not always win the message back; with the queues on the state Valkey, Valkey recovering also ends the queue failover windows (`FailoverSettings.queues_on_state_valkey`). All scenarios pass on Valkey except the SQS-only S20 / S33 (skipped; S33v measures Valkey). Plan and results: docs/VALKEY_QUEUE_PLAN.md.
25
+ - Scenarios S04 (LangChain / LangGraph drop-in: `ainvoke`, `abatch`, lanes from `request_context`, `bind_tools`, structured output, a LangGraph tool loop, sync `invoke`, queued embeddings), S24 (S3 offload through an in-process moto S3) and S33 (SQS call counts through a counting proxy: bg sends and acks batched, rt single, idle long polls bounded). They replace the three skipped placeholders.
26
+ - `SCN_AWS_LOW_VOLUME=1` lets `SCN_PROVIDER=aws` run scenarios marked `low-volume`. S10, S11, S18, S23, S37, S47 and S49 are now marked not AWS-safe, because they inject faults or read the fakellm call log.
27
+ - Sample `17_langgraph_rag_agent.py`: an app-style graph with queued embeddings for retrieval, a structured-output router, a tool loop with `fallback_policy="chain"`, a bg-lane summary node, `stream_mode="updates"` and a sync `.invoke()`.
28
+
9
29
  ## [0.1.0a2] - 2026-10-30
10
30
 
11
31
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: taskiq-llm-queue
3
- Version: 0.1.0a2
3
+ Version: 0.2.0
4
4
  Summary: An LLM request client with its own durable queue: taskiq + Amazon SQS, adaptive limits, retry and fallback.
5
5
  Project-URL: Homepage, https://github.com/techiewonk/taskiq-llm-queue
6
6
  Project-URL: Documentation, https://github.com/techiewonk/taskiq-llm-queue#readme
@@ -12,7 +12,7 @@ License-Expression: MIT
12
12
  License-File: LICENSE
13
13
  License-File: NOTICE
14
14
  Keywords: bedrock,langchain,langgraph,llm,openai,queue,rate-limit,sqs,taskiq
15
- Classifier: Development Status :: 2 - Pre-Alpha
15
+ Classifier: Development Status :: 3 - Alpha
16
16
  Classifier: Framework :: AsyncIO
17
17
  Classifier: Intended Audience :: Developers
18
18
  Classifier: Operating System :: OS Independent
@@ -49,7 +49,7 @@ Description-Content-Type: text/markdown
49
49
 
50
50
  # taskiq-llm-queue
51
51
 
52
- > **Status:** pre-alpha (`0.1.0a2`). Built and tested: the `LLMQueue` client, the SQS broker, adaptive limiter, claims, circuit breaker, retry and fallback, JSON and S3 result backends, Bedrock and OpenAI-compatible providers, LangChain/LangGraph adapters, canary routing, failover and the CLI. 761 tests on Valkey 8.1, ElasticMQ 1.7.1 and a fake LLM server; CI on Python 3.11–3.14. Breaking changes are still possible before 0.1 (see [CHANGELOG](https://github.com/techiewonk/taskiq-llm-queue/blob/main/CHANGELOG.md)).
52
+ > **Status:** alpha (`0.2.0`). Built and tested: the `LLMQueue` client, the queue broker on Amazon SQS or Valkey Streams, adaptive limiter, claims, circuit breaker, retry and fallback, JSON and S3 result backends, Bedrock and OpenAI-compatible providers, LangChain/LangGraph adapters, canary routing, failover and the CLI. 869 tests (unit, integration and the scenario harness) on Valkey 8.1, ElasticMQ 1.7.1 and a fake LLM server; CI on Python 3.11–3.14. Breaking changes are still possible before 0.1 (see [CHANGELOG](https://github.com/techiewonk/taskiq-llm-queue/blob/main/CHANGELOG.md)).
53
53
 
54
54
  An LLM request client with its own durable queue, built on [taskiq](https://github.com/taskiq-python/taskiq) and Amazon SQS.
55
55
 
@@ -168,6 +168,8 @@ await llmq.cancel("req-1") # not run if queued, stopped if running; caller
168
168
  | Setting | Default | Note |
169
169
  |---|---|---|
170
170
  | `aws_region` | `None` | Left to the AWS credential chain / IRSA |
171
+ | `queue_backend` (`LLMQ_QUEUE_BACKEND`) | `sqs` | `valkey` puts every queue on Valkey Streams; see [Queue backend](#queue-backend-sqs-or-valkey-streams) |
172
+ | `queue_redis_url` (`LLMQ_QUEUE_REDIS_URL`) | `None` | With `valkey`: a separate Valkey for the queues (else the state Valkey) |
171
173
  | `redis_url` or `redis_host`/`port`/`username`/`password`/`ssl` | localhost | `redis_password` is a `SecretStr` |
172
174
  | `redis_socket_timeout_secs` / `connect_timeout` / `health_check_interval` | 5 / 2 / 30 | Connection errors retried 3× with backoff |
173
175
  | `id_hash_salt` (`LLMQ_ID_HASH_SALT`) | library constant | **Set your own in production**; keys the BLAKE2b hash of session/user ids |
@@ -290,6 +292,22 @@ A custom provider implements `async call(op, params, target) -> dict` and `class
290
292
 
291
293
  A queued `bg`/`bulk` call waits at most 15 s for a slot, then goes back to the queue with a delay instead of holding a worker.
292
294
 
295
+ ### Queue backend: SQS or Valkey Streams
296
+
297
+ Queues live on Amazon SQS by default. `LLMQ_QUEUE_BACKEND=valkey` puts them on Valkey Streams instead, with the same queue names and the same delivery rules: visibility timeout with heartbeat, release with a delay, redelivery after a pod dies, a DLQ after `maxReceiveCount`, delays, expiry, strict lane priority and batched send / ack. The broker is unchanged: `ValkeyQueueClient` answers the SQS calls it makes ([docs/VALKEY_QUEUE_PLAN.md](docs/VALKEY_QUEUE_PLAN.md)).
298
+
299
+ | | SQS | Valkey Streams |
300
+ |---|---|---|
301
+ | Create queues | your infrastructure code | `llm-queue queues create [--visibility-secs 60 --max-receives 10 --retention-secs 345600]` |
302
+ | Where | AWS, multi-AZ, managed | `LLMQ_QUEUE_REDIS_URL`, or the state Valkey when unset |
303
+ | Valkey down | calls run direct; queued work stays in SQS | queue, limiter and results are down together; calls run direct |
304
+ | Backlog | cheap, days | Valkey memory: size it (bodies ≤ 200 KB; larger params by reference) |
305
+ | Metrics | CloudWatch | `llmq_queue_depth{queue,kind}`, `llmq_queue_oldest_seconds{queue}` from the pods |
306
+ | DLQ redrive | console / `StartMessageMoveTask` | `llm-queue queues redrive --queue <name>` |
307
+ | FIFO queues | yes | no (refused at boot) |
308
+
309
+ Use a dedicated Valkey with AOF (`everysec`) and Multi-AZ for queues in production: a failover can lose or repeat the last messages (handlers are idempotent, a loss shows as a caller timeout).
310
+
293
311
  ### Concurrency: queue depth vs bucket size
294
312
 
295
313
  The queue depth is unbounded: SQS holds every request that is not running. How many run at once is set by three knobs:
@@ -437,7 +455,7 @@ make down
437
455
  # Windows without make: uv run python tools/llmq_dev.py up | test | down
438
456
  ```
439
457
 
440
- Runnable examples are in [`samples/`](https://github.com/techiewonk/taskiq-llm-queue/tree/main/samples): quickstart, chat, embeddings, lanes, fallback on throttle, send-only and worker pods, LangChain, LangGraph, jobs, cancel and timeouts, sync calls, CLI, and `13_research_assistant_flow.py`, which replays a real app's flows locally or against real Bedrock (`--target aws`, settings from `.env` or AWS Secrets Manager). The stack, capability checks and test layers are described in [docs/TESTING.md](https://github.com/techiewonk/taskiq-llm-queue/blob/main/docs/TESTING.md); delivery-safety rules in [docs/DESIGN.md](https://github.com/techiewonk/taskiq-llm-queue/blob/main/docs/DESIGN.md).
458
+ Runnable examples are in [`samples/`](https://github.com/techiewonk/taskiq-llm-queue/tree/main/samples): quickstart, chat, embeddings, lanes, fallback on throttle, send-only and worker pods, LangChain, LangGraph, jobs, cancel and timeouts, sync calls, CLI, `13_research_assistant_flow.py` (a real app's flows locally or against real Bedrock: `--target aws`, settings from `.env` or AWS Secrets Manager), settings and buckets (14), retries and errors (15), rate limits and lane caps (16) and an app-style LangChain + LangGraph graph (17). Every sample reads `LLMQ_*` overrides, so `LLMQ_QUEUE_BACKEND=valkey` runs all of them on Valkey Streams ([samples/README.md](https://github.com/techiewonk/taskiq-llm-queue/blob/main/samples/README.md) "Settings"). The stack, capability checks and test layers are described in [docs/TESTING.md](https://github.com/techiewonk/taskiq-llm-queue/blob/main/docs/TESTING.md); delivery-safety rules in [docs/DESIGN.md](https://github.com/techiewonk/taskiq-llm-queue/blob/main/docs/DESIGN.md).
441
459
 
442
460
  ## Credits
443
461