spaik-sdk 0.6.2__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 (221) hide show
  1. spaik_sdk-0.6.2/.cursor/rules/global.mdc +32 -0
  2. spaik_sdk-0.6.2/.cursor/rules/post_run.mdc +14 -0
  3. spaik_sdk-0.6.2/.cursor/rules/repo_overview.mdc +84 -0
  4. spaik_sdk-0.6.2/.cursor/rules/testing-structure.mdc +54 -0
  5. spaik_sdk-0.6.2/.gitignore +41 -0
  6. spaik_sdk-0.6.2/Makefile +92 -0
  7. spaik_sdk-0.6.2/PKG-INFO +379 -0
  8. spaik_sdk-0.6.2/README.md +327 -0
  9. spaik_sdk-0.6.2/__init__.py.bak +20 -0
  10. spaik_sdk-0.6.2/env.example +28 -0
  11. spaik_sdk-0.6.2/kill.sh +39 -0
  12. spaik_sdk-0.6.2/py.typed +1 -0
  13. spaik_sdk-0.6.2/pyproject.toml +146 -0
  14. spaik_sdk-0.6.2/setup.sh +11 -0
  15. spaik_sdk-0.6.2/spaik_sdk/__init__.py +21 -0
  16. spaik_sdk-0.6.2/spaik_sdk/agent/__init__.py +0 -0
  17. spaik_sdk-0.6.2/spaik_sdk/agent/base_agent.py +249 -0
  18. spaik_sdk-0.6.2/spaik_sdk/attachments/__init__.py +22 -0
  19. spaik_sdk-0.6.2/spaik_sdk/attachments/builder.py +61 -0
  20. spaik_sdk-0.6.2/spaik_sdk/attachments/file_storage_provider.py +27 -0
  21. spaik_sdk-0.6.2/spaik_sdk/attachments/mime_types.py +118 -0
  22. spaik_sdk-0.6.2/spaik_sdk/attachments/models.py +63 -0
  23. spaik_sdk-0.6.2/spaik_sdk/attachments/provider_support.py +53 -0
  24. spaik_sdk-0.6.2/spaik_sdk/attachments/storage/__init__.py +0 -0
  25. spaik_sdk-0.6.2/spaik_sdk/attachments/storage/base_file_storage.py +32 -0
  26. spaik_sdk-0.6.2/spaik_sdk/attachments/storage/impl/__init__.py +0 -0
  27. spaik_sdk-0.6.2/spaik_sdk/attachments/storage/impl/local_file_storage.py +101 -0
  28. spaik_sdk-0.6.2/spaik_sdk/audio/__init__.py +12 -0
  29. spaik_sdk-0.6.2/spaik_sdk/audio/options.py +53 -0
  30. spaik_sdk-0.6.2/spaik_sdk/audio/providers/__init__.py +1 -0
  31. spaik_sdk-0.6.2/spaik_sdk/audio/providers/google_tts.py +77 -0
  32. spaik_sdk-0.6.2/spaik_sdk/audio/providers/openai_stt.py +71 -0
  33. spaik_sdk-0.6.2/spaik_sdk/audio/providers/openai_tts.py +111 -0
  34. spaik_sdk-0.6.2/spaik_sdk/audio/stt.py +61 -0
  35. spaik_sdk-0.6.2/spaik_sdk/audio/tts.py +124 -0
  36. spaik_sdk-0.6.2/spaik_sdk/config/credentials_provider.py +10 -0
  37. spaik_sdk-0.6.2/spaik_sdk/config/env.py +59 -0
  38. spaik_sdk-0.6.2/spaik_sdk/config/env_credentials_provider.py +7 -0
  39. spaik_sdk-0.6.2/spaik_sdk/config/get_credentials_provider.py +14 -0
  40. spaik_sdk-0.6.2/spaik_sdk/image_gen/__init__.py +9 -0
  41. spaik_sdk-0.6.2/spaik_sdk/image_gen/image_generator.py +83 -0
  42. spaik_sdk-0.6.2/spaik_sdk/image_gen/options.py +24 -0
  43. spaik_sdk-0.6.2/spaik_sdk/image_gen/providers/__init__.py +0 -0
  44. spaik_sdk-0.6.2/spaik_sdk/image_gen/providers/google.py +75 -0
  45. spaik_sdk-0.6.2/spaik_sdk/image_gen/providers/openai.py +60 -0
  46. spaik_sdk-0.6.2/spaik_sdk/llm/__init__.py +0 -0
  47. spaik_sdk-0.6.2/spaik_sdk/llm/cancellation_handle.py +10 -0
  48. spaik_sdk-0.6.2/spaik_sdk/llm/consumption/__init__.py +0 -0
  49. spaik_sdk-0.6.2/spaik_sdk/llm/consumption/consumption_estimate.py +26 -0
  50. spaik_sdk-0.6.2/spaik_sdk/llm/consumption/consumption_estimate_builder.py +113 -0
  51. spaik_sdk-0.6.2/spaik_sdk/llm/consumption/consumption_extractor.py +59 -0
  52. spaik_sdk-0.6.2/spaik_sdk/llm/consumption/token_usage.py +31 -0
  53. spaik_sdk-0.6.2/spaik_sdk/llm/converters.py +146 -0
  54. spaik_sdk-0.6.2/spaik_sdk/llm/cost/__init__.py +1 -0
  55. spaik_sdk-0.6.2/spaik_sdk/llm/cost/builtin_cost_provider.py +83 -0
  56. spaik_sdk-0.6.2/spaik_sdk/llm/cost/cost_estimate.py +8 -0
  57. spaik_sdk-0.6.2/spaik_sdk/llm/cost/cost_provider.py +28 -0
  58. spaik_sdk-0.6.2/spaik_sdk/llm/extract_error_message.py +37 -0
  59. spaik_sdk-0.6.2/spaik_sdk/llm/langchain_loop_manager.py +270 -0
  60. spaik_sdk-0.6.2/spaik_sdk/llm/langchain_service.py +196 -0
  61. spaik_sdk-0.6.2/spaik_sdk/llm/message_handler.py +188 -0
  62. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/__init__.py +1 -0
  63. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/block_manager.py +152 -0
  64. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/models.py +42 -0
  65. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/streaming_content_handler.py +157 -0
  66. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/streaming_event_handler.py +215 -0
  67. spaik_sdk-0.6.2/spaik_sdk/llm/streaming/streaming_state_manager.py +58 -0
  68. spaik_sdk-0.6.2/spaik_sdk/models/__init__.py +0 -0
  69. spaik_sdk-0.6.2/spaik_sdk/models/factories/__init__.py +0 -0
  70. spaik_sdk-0.6.2/spaik_sdk/models/factories/anthropic_factory.py +33 -0
  71. spaik_sdk-0.6.2/spaik_sdk/models/factories/base_model_factory.py +71 -0
  72. spaik_sdk-0.6.2/spaik_sdk/models/factories/google_factory.py +30 -0
  73. spaik_sdk-0.6.2/spaik_sdk/models/factories/ollama_factory.py +41 -0
  74. spaik_sdk-0.6.2/spaik_sdk/models/factories/openai_factory.py +50 -0
  75. spaik_sdk-0.6.2/spaik_sdk/models/llm_config.py +46 -0
  76. spaik_sdk-0.6.2/spaik_sdk/models/llm_families.py +7 -0
  77. spaik_sdk-0.6.2/spaik_sdk/models/llm_model.py +17 -0
  78. spaik_sdk-0.6.2/spaik_sdk/models/llm_wrapper.py +25 -0
  79. spaik_sdk-0.6.2/spaik_sdk/models/model_registry.py +156 -0
  80. spaik_sdk-0.6.2/spaik_sdk/models/providers/__init__.py +0 -0
  81. spaik_sdk-0.6.2/spaik_sdk/models/providers/anthropic_provider.py +29 -0
  82. spaik_sdk-0.6.2/spaik_sdk/models/providers/azure_provider.py +31 -0
  83. spaik_sdk-0.6.2/spaik_sdk/models/providers/base_provider.py +62 -0
  84. spaik_sdk-0.6.2/spaik_sdk/models/providers/google_provider.py +26 -0
  85. spaik_sdk-0.6.2/spaik_sdk/models/providers/ollama_provider.py +26 -0
  86. spaik_sdk-0.6.2/spaik_sdk/models/providers/openai_provider.py +26 -0
  87. spaik_sdk-0.6.2/spaik_sdk/models/providers/provider_type.py +90 -0
  88. spaik_sdk-0.6.2/spaik_sdk/orchestration/__init__.py +24 -0
  89. spaik_sdk-0.6.2/spaik_sdk/orchestration/base_orchestrator.py +238 -0
  90. spaik_sdk-0.6.2/spaik_sdk/orchestration/checkpoint.py +80 -0
  91. spaik_sdk-0.6.2/spaik_sdk/orchestration/models.py +103 -0
  92. spaik_sdk-0.6.2/spaik_sdk/prompt/__init__.py +0 -0
  93. spaik_sdk-0.6.2/spaik_sdk/prompt/get_prompt_loader.py +13 -0
  94. spaik_sdk-0.6.2/spaik_sdk/prompt/local_prompt_loader.py +21 -0
  95. spaik_sdk-0.6.2/spaik_sdk/prompt/prompt_loader.py +48 -0
  96. spaik_sdk-0.6.2/spaik_sdk/prompt/prompt_loader_mode.py +14 -0
  97. spaik_sdk-0.6.2/spaik_sdk/py.typed +1 -0
  98. spaik_sdk-0.6.2/spaik_sdk/recording/__init__.py +1 -0
  99. spaik_sdk-0.6.2/spaik_sdk/recording/base_playback.py +90 -0
  100. spaik_sdk-0.6.2/spaik_sdk/recording/base_recorder.py +50 -0
  101. spaik_sdk-0.6.2/spaik_sdk/recording/conditional_recorder.py +38 -0
  102. spaik_sdk-0.6.2/spaik_sdk/recording/impl/__init__.py +1 -0
  103. spaik_sdk-0.6.2/spaik_sdk/recording/impl/local_playback.py +76 -0
  104. spaik_sdk-0.6.2/spaik_sdk/recording/impl/local_recorder.py +85 -0
  105. spaik_sdk-0.6.2/spaik_sdk/recording/langchain_serializer.py +88 -0
  106. spaik_sdk-0.6.2/spaik_sdk/server/__init__.py +1 -0
  107. spaik_sdk-0.6.2/spaik_sdk/server/api/routers/__init__.py +0 -0
  108. spaik_sdk-0.6.2/spaik_sdk/server/api/routers/api_builder.py +149 -0
  109. spaik_sdk-0.6.2/spaik_sdk/server/api/routers/audio_router_factory.py +201 -0
  110. spaik_sdk-0.6.2/spaik_sdk/server/api/routers/file_router_factory.py +111 -0
  111. spaik_sdk-0.6.2/spaik_sdk/server/api/routers/thread_router_factory.py +284 -0
  112. spaik_sdk-0.6.2/spaik_sdk/server/api/streaming/__init__.py +0 -0
  113. spaik_sdk-0.6.2/spaik_sdk/server/api/streaming/format_sse_event.py +41 -0
  114. spaik_sdk-0.6.2/spaik_sdk/server/api/streaming/negotiate_streaming_response.py +8 -0
  115. spaik_sdk-0.6.2/spaik_sdk/server/api/streaming/streaming_negotiator.py +10 -0
  116. spaik_sdk-0.6.2/spaik_sdk/server/authorization/__init__.py +0 -0
  117. spaik_sdk-0.6.2/spaik_sdk/server/authorization/base_authorizer.py +64 -0
  118. spaik_sdk-0.6.2/spaik_sdk/server/authorization/base_user.py +13 -0
  119. spaik_sdk-0.6.2/spaik_sdk/server/authorization/dummy_authorizer.py +17 -0
  120. spaik_sdk-0.6.2/spaik_sdk/server/job_processor/__init__.py +0 -0
  121. spaik_sdk-0.6.2/spaik_sdk/server/job_processor/base_job_processor.py +8 -0
  122. spaik_sdk-0.6.2/spaik_sdk/server/job_processor/thread_job_processor.py +32 -0
  123. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/__init__.py +1 -0
  124. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/cancellation_publisher.py +7 -0
  125. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/cancellation_subscriber.py +38 -0
  126. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/event_publisher.py +13 -0
  127. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/impl/__init__.py +1 -0
  128. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/impl/local_cancellation_pubsub.py +48 -0
  129. spaik_sdk-0.6.2/spaik_sdk/server/pubsub/impl/signalr_publisher.py +36 -0
  130. spaik_sdk-0.6.2/spaik_sdk/server/queue/__init__.py +1 -0
  131. spaik_sdk-0.6.2/spaik_sdk/server/queue/agent_job_queue.py +27 -0
  132. spaik_sdk-0.6.2/spaik_sdk/server/queue/impl/__init__.py +1 -0
  133. spaik_sdk-0.6.2/spaik_sdk/server/queue/impl/azure_queue.py +24 -0
  134. spaik_sdk-0.6.2/spaik_sdk/server/response/__init__.py +0 -0
  135. spaik_sdk-0.6.2/spaik_sdk/server/response/agent_response_generator.py +39 -0
  136. spaik_sdk-0.6.2/spaik_sdk/server/response/response_generator.py +13 -0
  137. spaik_sdk-0.6.2/spaik_sdk/server/response/simple_agent_response_generator.py +14 -0
  138. spaik_sdk-0.6.2/spaik_sdk/server/services/__init__.py +0 -0
  139. spaik_sdk-0.6.2/spaik_sdk/server/services/thread_converters.py +113 -0
  140. spaik_sdk-0.6.2/spaik_sdk/server/services/thread_models.py +90 -0
  141. spaik_sdk-0.6.2/spaik_sdk/server/services/thread_service.py +91 -0
  142. spaik_sdk-0.6.2/spaik_sdk/server/storage/__init__.py +1 -0
  143. spaik_sdk-0.6.2/spaik_sdk/server/storage/base_thread_repository.py +51 -0
  144. spaik_sdk-0.6.2/spaik_sdk/server/storage/impl/__init__.py +0 -0
  145. spaik_sdk-0.6.2/spaik_sdk/server/storage/impl/in_memory_thread_repository.py +100 -0
  146. spaik_sdk-0.6.2/spaik_sdk/server/storage/impl/local_file_thread_repository.py +217 -0
  147. spaik_sdk-0.6.2/spaik_sdk/server/storage/thread_filter.py +166 -0
  148. spaik_sdk-0.6.2/spaik_sdk/server/storage/thread_metadata.py +53 -0
  149. spaik_sdk-0.6.2/spaik_sdk/thread/__init__.py +0 -0
  150. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/__init__.py +0 -0
  151. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/cli/__init__.py +0 -0
  152. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/cli/block_display.py +92 -0
  153. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/cli/display_manager.py +84 -0
  154. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/cli/live_cli.py +235 -0
  155. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/event_adapter.py +28 -0
  156. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/streaming_block_adapter.py +57 -0
  157. spaik_sdk-0.6.2/spaik_sdk/thread/adapters/sync_adapter.py +76 -0
  158. spaik_sdk-0.6.2/spaik_sdk/thread/models.py +224 -0
  159. spaik_sdk-0.6.2/spaik_sdk/thread/thread_container.py +468 -0
  160. spaik_sdk-0.6.2/spaik_sdk/tools/__init__.py +0 -0
  161. spaik_sdk-0.6.2/spaik_sdk/tools/impl/__init__.py +0 -0
  162. spaik_sdk-0.6.2/spaik_sdk/tools/impl/mcp_tool_provider.py +93 -0
  163. spaik_sdk-0.6.2/spaik_sdk/tools/impl/search_tool_provider.py +18 -0
  164. spaik_sdk-0.6.2/spaik_sdk/tools/tool_provider.py +131 -0
  165. spaik_sdk-0.6.2/spaik_sdk/tracing/__init__.py +13 -0
  166. spaik_sdk-0.6.2/spaik_sdk/tracing/agent_trace.py +72 -0
  167. spaik_sdk-0.6.2/spaik_sdk/tracing/get_trace_sink.py +15 -0
  168. spaik_sdk-0.6.2/spaik_sdk/tracing/local_trace_sink.py +23 -0
  169. spaik_sdk-0.6.2/spaik_sdk/tracing/trace_sink.py +19 -0
  170. spaik_sdk-0.6.2/spaik_sdk/tracing/trace_sink_mode.py +14 -0
  171. spaik_sdk-0.6.2/spaik_sdk/utils/__init__.py +0 -0
  172. spaik_sdk-0.6.2/spaik_sdk/utils/init_logger.py +24 -0
  173. spaik_sdk-0.6.2/tests/__init__.py +0 -0
  174. spaik_sdk-0.6.2/tests/conftest.py +0 -0
  175. spaik_sdk-0.6.2/tests/data/recordings/test_consumption_tracking/1.jsonl +6 -0
  176. spaik_sdk-0.6.2/tests/data/recordings/test_event_stream_basic/1.jsonl +6 -0
  177. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text/1.jsonl +6 -0
  178. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_claude-3-7-sonnet-latest/1.jsonl +6 -0
  179. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_claude-haiku-4-5-20251001/1.jsonl +6 -0
  180. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_claude-opus-4-5-20251101/1.jsonl +32 -0
  181. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_claude-sonnet-4-20250514/1.jsonl +6 -0
  182. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_claude-sonnet-4-5-20250929/1.jsonl +6 -0
  183. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gemini-2.5-flash/1.jsonl +6 -0
  184. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gemini-3-flash-preview/1.jsonl +19 -0
  185. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gemini-3-pro-preview/1.jsonl +20 -0
  186. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gpt-4.1/1.jsonl +6 -0
  187. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gpt-5.1/1.jsonl +31 -0
  188. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_gpt-5.2/1.jsonl +30 -0
  189. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_o4-mini/1.jsonl +6 -0
  190. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_text_with_cancellation/1.jsonl +6 -0
  191. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-3-7-sonnet-latest/1.jsonl +16 -0
  192. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-haiku-4-5-20251001/1.jsonl +16 -0
  193. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-1-20250805/1.jsonl +16 -0
  194. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-5-20251101/1.jsonl +53 -0
  195. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-20250514/1.jsonl +16 -0
  196. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-5-20250929/1.jsonl +16 -0
  197. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gemini-2.5-flash/1.jsonl +16 -0
  198. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gemini-3-flash-preview/1.jsonl +44 -0
  199. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-4.1/1.jsonl +16 -0
  200. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-5/1.jsonl +16 -0
  201. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1/1.jsonl +142 -0
  202. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1-codex/1.jsonl +64 -0
  203. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2/1.jsonl +58 -0
  204. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2-pro/1.jsonl +46 -0
  205. spaik_sdk-0.6.2/tests/data/recordings/test_get_response_with_tool_call_o4-mini/1.jsonl +16 -0
  206. spaik_sdk-0.6.2/tests/data/recordings/test_get_structured_response/1.json +4 -0
  207. spaik_sdk-0.6.2/tests/data/recordings/test_mystery_streaming_issue/1.jsonl +6 -0
  208. spaik_sdk-0.6.2/tests/integration/__init__.py +0 -0
  209. spaik_sdk-0.6.2/tests/integration/test_cost_tracking_integration.py +79 -0
  210. spaik_sdk-0.6.2/tests/integration/test_mcp_tool_provider.py +68 -0
  211. spaik_sdk-0.6.2/tests/manual/__init__.py +0 -0
  212. spaik_sdk-0.6.2/tests/manual/test_search.py +22 -0
  213. spaik_sdk-0.6.2/tests/unit/__init__.py +0 -0
  214. spaik_sdk-0.6.2/tests/unit/spaik_sdk/agent/test_base_agent.py +277 -0
  215. spaik_sdk-0.6.2/tests/unit/spaik_sdk/llm/streaming/test_streaming_event_handler.py +221 -0
  216. spaik_sdk-0.6.2/tests/unit/spaik_sdk/models/__init__.py +0 -0
  217. spaik_sdk-0.6.2/tests/unit/spaik_sdk/models/test_model_registry.py +76 -0
  218. spaik_sdk-0.6.2/tests/unit/spaik_sdk/orchestration/__init__.py +0 -0
  219. spaik_sdk-0.6.2/tests/unit/spaik_sdk/orchestration/test_base_orchestrator.py +289 -0
  220. spaik_sdk-0.6.2/tests/unit/spaik_sdk/tools/impl/__init__.py +0 -0
  221. spaik_sdk-0.6.2/uv.lock +3254 -0
@@ -0,0 +1,32 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ # General
7
+
8
+ this project is a helper SDK for building various kinds of agentic + ai solutions
9
+
10
+ main goal is to give good DX and make it fast and easy to build POCs that also are of good quality
11
+ and something you can easily get to production as well
12
+
13
+
14
+ # style stuff:
15
+
16
+ - use absolute imports
17
+ - comments are a code smell, avoid docstrings too if not adding anything
18
+ - empty __init__.py unless otherwise specified
19
+ - no star imports
20
+ - always use types
21
+ - use uv and ruff
22
+
23
+
24
+ # logging
25
+
26
+ this is the way to obtain the logger:
27
+
28
+ ```
29
+ from spaik_sdk.utils.init_logger import init_logger
30
+
31
+ logger = init_logger(__name__)
32
+ ```
@@ -0,0 +1,14 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+
7
+ After you have done something substantial (eg. touching multiple files):
8
+
9
+ First, take a look at the code you wrote. Does it look clean an elegant with good separation of concerns and small files and small methods. If not, go clean it up. Also make sure you don't have any inline imports etc smelly stuff.
10
+
11
+ Then run this command to make sure everything is ok:
12
+
13
+ `make lint-fix && make typecheck && make test-unit`
14
+
@@ -0,0 +1,84 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ # Agent SDK - Repository Overview
7
+
8
+ This is a Python-based Agent SDK for building various kinds of agentic + AI solutions. The main goal is to provide good DX (Developer Experience) and make it fast and easy to build POCs that are also of good quality and can easily transition to production.
9
+
10
+ ## Core Purpose & Philosophy
11
+
12
+ - **Helper SDK** for building agentic + AI solutions
13
+ - **Fast POC Development** that scales to production quality
14
+ - **Good DX** with clean abstractions and patterns
15
+ - **Multi-LLM Support** with unified interface
16
+ - **Streaming & Interactive** capabilities built-in
17
+
18
+ ## Project Structure & Key Components
19
+
20
+ ### Entry Points
21
+ - **[main.py](mdc:main.py)** - Main application entry point, demonstrates DemoAgent and MinimalAgent usage
22
+ - **[setup.sh](mdc:setup.sh)** - Development environment setup script
23
+
24
+ ### Core Architecture Modules
25
+
26
+ #### **Agent System** (`app/agent/`)
27
+ - **[app/agent/base_agent.py](mdc:app/agent/base_agent.py)** - BaseAgent class providing foundation for all agents
28
+ - **[app/demo_agents/](mdc:app/demo_agents)** - Example agent implementations (DemoAgent, MinimalAgent)
29
+
30
+ #### **LLM Integration** (`app/models/`)
31
+ Multi-provider LLM support with factory pattern:
32
+ - **Supported Providers**: Anthropic (Claude), OpenAI (GPT), Google (Gemini), Azure AI Foundry
33
+ - **[app/models/llm_types.py](mdc:app/models/llm_types.py)** - Model enums and provider types
34
+ - **[app/models/llm_config.py](mdc:app/models/llm_config.py)** - LLM configuration dataclass
35
+ - **[app/models/factories/](mdc:app/models/factories)** - Model factory implementations per provider
36
+ - **[app/models/providers/](mdc:app/models/providers)** - Provider-specific API integrations
37
+
38
+ #### **LangChain Service Layer** (`app/llm/`)
39
+ - **[app/llm/langchain_service.py](mdc:app/llm/langchain_service.py)** - Core service wrapping LangChain functionality
40
+ - **[app/llm/message_handler.py](mdc:app/llm/message_handler.py)** - Conversation history and message processing
41
+ - **[app/llm/streaming/](mdc:app/llm/streaming)** - Streaming response handling components
42
+
43
+ #### **Thread Management** (`app/thread/`)
44
+ - **[app/thread/thread_container.py](mdc:app/thread/thread_container.py)** - Core conversation state management
45
+ - **[app/thread/models.py](mdc:app/thread/models.py)** - Message, block, and event data models
46
+ - **[app/thread/adapters/](mdc:app/thread/adapters)** - Different interaction interfaces (sync, CLI)
47
+
48
+ #### **Tool System** (`app/tools/`)
49
+ - **[app/tools/tool_provider.py](mdc:app/tools/tool_provider.py)** - Abstract base for creating agent tools
50
+ - Supports LangChain BaseTool integration with Pydantic schemas
51
+
52
+ #### **Configuration & Infrastructure**
53
+ - **[config/env.py](mdc:config/env.py)** - Environment variable management
54
+ - **[app/prompt/prompt_loader.py](mdc:app/prompt/prompt_loader.py)** - Static prompt loading from markdown
55
+ - **[app/tracing/agent_trace.py](mdc:app/tracing/agent_trace.py)** - Execution tracing for debugging
56
+ - **[app/utils/init_logger.py](mdc:app/utils/init_logger.py)** - Logging utilities
57
+
58
+ ## Key Technologies & Dependencies
59
+
60
+ - **Python 3.10+** with modern async/await patterns
61
+ - **LangChain** - Primary framework for LLM orchestration
62
+ - **LangGraph** - For stateful, multi-actor applications
63
+ - **FastAPI** - Web framework for API endpoints
64
+ - **Pydantic** - Data validation and settings management
65
+ - **uv** - Fast Python package manager and runner
66
+ - **ruff** - Fast Python linter and formatter
67
+
68
+ ## Architectural Patterns
69
+
70
+ 1. **Agent-Based Architecture** - BaseAgent foundation with concrete implementations
71
+ 2. **Factory Pattern** - For LLM provider instantiation
72
+ 3. **Provider Pattern** - Unified interface across different LLM APIs
73
+ 4. **Event-Driven** - ThreadContainer publishes events for real-time updates
74
+ 5. **Streaming-First** - Built-in support for streaming responses
75
+ 6. **Tool Integration** - LangChain tool ecosystem integration
76
+ 7. **Modular Design** - Clear separation of concerns across modules
77
+
78
+ ## Development Standards
79
+
80
+ - **Absolute imports** - No relative imports
81
+ - **Type annotations** - Always use types
82
+ - **Minimal comments** - Code should be self-documenting
83
+ - **Empty `__init__.py`** - Unless otherwise specified
84
+ - **uv + ruff** - Package management and linting
@@ -0,0 +1,54 @@
1
+ ---
2
+ description:
3
+ globs:
4
+ alwaysApply: true
5
+ ---
6
+ # Testing Structure
7
+
8
+ ## Unit Tests
9
+ - Location: `tests/unit/{folder path to file being tested}/test_{filename}.py`
10
+ - Example: For `spaik_sdk/agent/base_agent.py` → `tests/unit/spaik_sdk/agent/test_base_agent.py`
11
+ - Use `@pytest.mark.unit` decorator
12
+ - Mock external dependencies
13
+
14
+ ## Integration Tests
15
+ - Location: `tests/integration/` (freeform structure)
16
+ - Use `@pytest.mark.integration` decorator
17
+ - Test complete workflows and component interactions
18
+
19
+ ## Test Configuration
20
+ - Main config in [pyproject.toml](mdc:pyproject.toml) under `[tool.pytest.ini_options]`
21
+ - Shared fixtures in [tests/conftest.py](mdc:tests/conftest.py)
22
+ - Test utilities in [tests/utils.py](mdc:tests/utils.py)
23
+
24
+ ## Running Tests
25
+ - Use [Makefile](mdc:Makefile) commands: `make test`, `make test-unit`, `make test-integration`
26
+ - Or directly: `uv run pytest`
27
+
28
+
29
+ ## Philosophy
30
+ - test the public interface, not the implementation and how its coded
31
+ - don't add stupid do-nothing tests that just check completely trivial aspects
32
+
33
+ Example offender:
34
+ ```
35
+ def test_valid_config_creation(self):
36
+ """Test creating a valid LLMConfig."""
37
+ config = LLMConfig(
38
+ model=LLMModel.CLAUDE_3_HAIKU,
39
+ provider_type=ProviderType.ANTHROPIC,
40
+ reasoning=True,
41
+ tool_usage=False
42
+ )
43
+
44
+ assert config.model == LLMModel.CLAUDE_3_HAIKU
45
+ assert config.provider_type == ProviderType.ANTHROPIC
46
+ assert config.reasoning is True
47
+ assert config.tool_usage is False
48
+ ```
49
+
50
+ The above is 100% pollution in the codebase and gives us absolutely no guarantees of things working.
51
+
52
+ Focus on corner cases and things that might actually break. Do NOT aim for 100% coverage or any such nonsense.
53
+
54
+ We want to test things that might actually break and test them well (not just the happy paths)
@@ -0,0 +1,41 @@
1
+ # Dependencies
2
+ node_modules
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Production
7
+ # dist
8
+ build
9
+
10
+ # Testing
11
+ coverage
12
+
13
+ # Misc
14
+ .DS_Store
15
+ .env
16
+ .env.local
17
+ .env.development.local
18
+ .env.test.local
19
+ .env.production.local
20
+
21
+ # Logs
22
+ npm-debug.log*
23
+ yarn-debug.log*
24
+ yarn-error.log*
25
+
26
+ # Editor
27
+ .vscode/*
28
+ !.vscode/extensions.json
29
+ .idea
30
+ *.suo
31
+ *.ntvs*
32
+ *.njsproj
33
+ *.sln
34
+ *.sw?
35
+ venv
36
+ __pycache__
37
+ .aider*
38
+
39
+ traces/*
40
+ data/*
41
+ recordings/*
@@ -0,0 +1,92 @@
1
+ # Makefile for Agent SDK Testing
2
+
3
+ .PHONY: test test-unit test-integration test-watch test-cov test-fast help clean lint lint-fix typecheck
4
+
5
+ # Default target
6
+ help:
7
+ @echo "Available targets:"
8
+ @echo " test - Run all tests"
9
+ @echo " test-unit - Run only unit tests"
10
+ @echo " test-unit-single - Run single unit test by pattern"
11
+ @echo " test-integration - Run only integration tests"
12
+ @echo " test-fast - Run fast tests (exclude slow tests)"
13
+ @echo " test-watch - Run tests in watch mode"
14
+ @echo " test-cov - Run tests with coverage report"
15
+ @echo " test-html - Run tests with HTML coverage report"
16
+ @echo " lint - Run linting checks"
17
+ @echo " lint-fix - Run linting with auto-fix"
18
+ @echo " typecheck - Run type checking with mypy"
19
+ @echo " clean - Clean test artifacts"
20
+
21
+ # Run all tests
22
+ test:
23
+ uv run python3 -m pytest tests/
24
+
25
+ # Run only unit tests
26
+ test-unit:
27
+ uv run python3 -m pytest tests/unit/ -m "unit" --no-cov
28
+
29
+ # Run single unit test by pattern
30
+ test-unit-single:
31
+ @if [ -z "$(PATTERN)" ]; then echo "Usage: make test-unit-single PATTERN=test_name_pattern"; exit 1; fi
32
+ LOG_LEVEL=DEBUG uv run python3 -m pytest tests/unit/ -m "unit" --no-cov -k "$(PATTERN)" -v
33
+
34
+ # Run only integration tests
35
+ test-integration:
36
+ uv run python3 -m pytest tests/integration/ -m "integration"
37
+
38
+ # Run fast tests (exclude slow ones)
39
+ test-fast:
40
+ uv run python3 -m pytest tests/ -m "not slow"
41
+
42
+ # Run tests in watch mode (requires pytest-watch)
43
+ test-watch:
44
+ uv run pytest-watch --clear
45
+
46
+ # Run tests with coverage
47
+ test-cov:
48
+ uv run python3 -m pytest --cov=spaik_sdk --cov-report=term-missing
49
+
50
+ # Run tests with HTML coverage report
51
+ test-html:
52
+ uv run python3 -m pytest --cov=spaik_sdk --cov-report=html
53
+ @echo "Coverage report generated in htmlcov/index.html"
54
+
55
+ # Run specific test file
56
+ test-file:
57
+ @if [ -z "$(FILE)" ]; then echo "Usage: make test-file FILE=path/to/test_file.py"; exit 1; fi
58
+ uv run python3 -m pytest $(FILE) -v
59
+
60
+ # Run tests matching a pattern
61
+ test-pattern:
62
+ @if [ -z "$(PATTERN)" ]; then echo "Usage: make test-pattern PATTERN=test_name_pattern"; exit 1; fi
63
+ uv run python3 -m pytest -k "$(PATTERN)" -v
64
+
65
+ # Run linting checks
66
+ lint:
67
+ uv run ruff check .
68
+
69
+ # Run linting with auto-fix
70
+ lint-fix:
71
+ uv run ruff check --fix --unsafe-fixes .
72
+ uv run ruff format .
73
+
74
+ # Run type checking
75
+ typecheck:
76
+ uv run ty check spaik_sdk tests
77
+
78
+ # Clean test artifacts
79
+ clean:
80
+ rm -rf .pytest_cache/
81
+ rm -rf htmlcov/
82
+ rm -rf .coverage
83
+ find . -type d -name __pycache__ -exec rm -rf {} +
84
+ find . -type f -name "*.pyc" -delete
85
+
86
+ # Debug tests (run with verbose output and no capture)
87
+ test-debug:
88
+ uv run python3 -m pytest tests/ -v -s --tb=long
89
+
90
+ # Run tests and generate JUnit XML (for CI)
91
+ test-ci:
92
+ uv run python3 -m pytest tests/ --junitxml=test-results.xml --cov=spaik_sdk --cov-report=xml
@@ -0,0 +1,379 @@
1
+ Metadata-Version: 2.4
2
+ Name: spaik-sdk
3
+ Version: 0.6.2
4
+ Summary: Python SDK for building AI agents with multi-LLM support, streaming, and production-ready infrastructure
5
+ Project-URL: Homepage, https://github.com/siilisolutions/spaik-sdk
6
+ Project-URL: Repository, https://github.com/siilisolutions/spaik-sdk
7
+ Project-URL: Documentation, https://github.com/siilisolutions/spaik-sdk#readme
8
+ Author-email: Siili Solutions Oyj <info@siili.com>
9
+ License-Expression: MIT
10
+ Keywords: agents,ai,anthropic,claude,gpt,langchain,llm,openai,streaming
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: >=3.10
20
+ Requires-Dist: aioconsole>=0.8.1
21
+ Requires-Dist: azure-storage-blob
22
+ Requires-Dist: cryptography>=41.0.0
23
+ Requires-Dist: dotenv>=0.9.9
24
+ Requires-Dist: fastapi>=0.115.12
25
+ Requires-Dist: httpx>=0.25.0
26
+ Requires-Dist: langchain-anthropic>=1.3.0
27
+ Requires-Dist: langchain-core>=1.2.0
28
+ Requires-Dist: langchain-google-genai>=4.0.0
29
+ Requires-Dist: langchain-mcp-adapters>=0.2.1
30
+ Requires-Dist: langchain-ollama>=0.3.0
31
+ Requires-Dist: langchain-openai>=1.1.0
32
+ Requires-Dist: langchain-tavily>=0.2.15
33
+ Requires-Dist: langchain>=1.2.0
34
+ Requires-Dist: langgraph>=1.0.0
35
+ Requires-Dist: mcp>=1.9.2
36
+ Requires-Dist: pandas-stubs
37
+ Requires-Dist: pandas>=2.0.3
38
+ Requires-Dist: pyjwt>=2.8.0
39
+ Requires-Dist: pytest-asyncio>=0.21.1
40
+ Requires-Dist: pytest-cov>=4.1.0
41
+ Requires-Dist: pytest-mock>=3.11.1
42
+ Requires-Dist: pytest>=7.4.0
43
+ Requires-Dist: requests>=2.31.0
44
+ Requires-Dist: restrictedpython>=8.0
45
+ Requires-Dist: rich>=14.0.0
46
+ Requires-Dist: uvicorn>=0.33.0
47
+ Provides-Extra: dev
48
+ Requires-Dist: black; extra == 'dev'
49
+ Requires-Dist: mypy; extra == 'dev'
50
+ Requires-Dist: ruff; extra == 'dev'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # Spaik SDK
54
+
55
+ Python SDK for building AI agents with multi-LLM support, streaming, and production infrastructure.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ pip install spaik-sdk
61
+ ```
62
+
63
+ ## Quick Start
64
+
65
+ ```python
66
+ from spaik_sdk.agent.base_agent import BaseAgent
67
+
68
+ class MyAgent(BaseAgent):
69
+ pass
70
+
71
+ agent = MyAgent(system_prompt="You are a helpful assistant.")
72
+ print(agent.get_response_text("Hello!"))
73
+ ```
74
+
75
+ ## Features
76
+
77
+ - **Multi-LLM Support**: OpenAI, Anthropic, Google, Azure, Ollama
78
+ - **Unified API**: Same interface across all providers
79
+ - **Streaming**: Real-time response streaming via SSE
80
+ - **Tools**: Function calling with LangChain integration
81
+ - **Structured Output**: Pydantic model responses
82
+ - **Server**: FastAPI with thread persistence, auth, file uploads
83
+ - **Audio**: Text-to-speech and speech-to-text
84
+ - **Cost Tracking**: Token usage and cost estimation
85
+
86
+ ## Agent API
87
+
88
+ ### Basic Response Methods
89
+
90
+ ```python
91
+ from spaik_sdk.agent.base_agent import BaseAgent
92
+ from spaik_sdk.models.model_registry import ModelRegistry
93
+
94
+ agent = MyAgent(
95
+ system_prompt="You are helpful.",
96
+ llm_model=ModelRegistry.CLAUDE_4_SONNET
97
+ )
98
+
99
+ # Sync - text only
100
+ text = agent.get_response_text("Hello")
101
+
102
+ # Sync - full message with blocks
103
+ message = agent.get_response("Hello")
104
+ print(message.get_text_content())
105
+
106
+ # Async
107
+ message = await agent.get_response_async("Hello")
108
+ ```
109
+
110
+ ### Streaming
111
+
112
+ ```python
113
+ # Token stream
114
+ async for chunk in agent.get_response_stream("Write a story"):
115
+ print(chunk, end="", flush=True)
116
+
117
+ # Event stream (for SSE)
118
+ async for event in agent.get_event_stream("Write a story"):
119
+ if event.get_event_type() == "StreamingUpdated":
120
+ print(event.content, end="")
121
+ ```
122
+
123
+ ### Structured Output
124
+
125
+ ```python
126
+ from pydantic import BaseModel
127
+
128
+ class Recipe(BaseModel):
129
+ name: str
130
+ ingredients: list[str]
131
+ steps: list[str]
132
+
133
+ recipe = agent.get_structured_response("Give me a pasta recipe", Recipe)
134
+ print(recipe.name)
135
+ ```
136
+
137
+ ### Interactive CLI
138
+
139
+ ```python
140
+ agent.run_cli() # Starts interactive chat in terminal
141
+ ```
142
+
143
+ ## Tools
144
+
145
+ ```python
146
+ from spaik_sdk.tools.tool_provider import ToolProvider, BaseTool, tool
147
+
148
+ class WeatherTools(ToolProvider):
149
+ def get_tools(self) -> list[BaseTool]:
150
+ @tool
151
+ def get_weather(city: str) -> str:
152
+ """Get current weather for a city."""
153
+ return f"Sunny, 22°C in {city}"
154
+
155
+ @tool
156
+ def get_forecast(city: str, days: int = 3) -> str:
157
+ """Get weather forecast."""
158
+ return f"{days}-day forecast for {city}: Sunny"
159
+
160
+ return [get_weather, get_forecast]
161
+
162
+ class WeatherAgent(BaseAgent):
163
+ def get_tool_providers(self) -> list[ToolProvider]:
164
+ return [WeatherTools()]
165
+
166
+ agent = WeatherAgent(system_prompt="You provide weather info.")
167
+ print(agent.get_response_text("What's the weather in Tokyo?"))
168
+ ```
169
+
170
+ ### Built-in Tool Providers
171
+
172
+ ```python
173
+ from spaik_sdk.tools.impl.search_tool_provider import SearchToolProvider
174
+ from spaik_sdk.tools.impl.mcp_tool_provider import MCPToolProvider
175
+
176
+ class MyAgent(BaseAgent):
177
+ def get_tool_providers(self):
178
+ return [
179
+ SearchToolProvider(), # Web search (Tavily)
180
+ MCPToolProvider(server), # MCP server tools
181
+ ]
182
+ ```
183
+
184
+ ## Models
185
+
186
+ ```python
187
+ from spaik_sdk.models.model_registry import ModelRegistry
188
+
189
+ # Anthropic
190
+ ModelRegistry.CLAUDE_4_SONNET
191
+ ModelRegistry.CLAUDE_4_OPUS
192
+ ModelRegistry.CLAUDE_4_5_SONNET
193
+ ModelRegistry.CLAUDE_4_5_OPUS
194
+
195
+ # OpenAI
196
+ ModelRegistry.GPT_4_1
197
+ ModelRegistry.GPT_4O
198
+ ModelRegistry.O4_MINI
199
+
200
+ # Google
201
+ ModelRegistry.GEMINI_2_5_FLASH
202
+ ModelRegistry.GEMINI_2_5_PRO
203
+
204
+ # Aliases
205
+ ModelRegistry.from_name("sonnet") # CLAUDE_4_SONNET
206
+ ModelRegistry.from_name("gpt 4.1") # GPT_4_1
207
+ ModelRegistry.from_name("gemini 2.5") # GEMINI_2_5_FLASH
208
+
209
+ # Custom model
210
+ from spaik_sdk.models.llm_model import LLMModel
211
+ from spaik_sdk.models.llm_families import LLMFamilies
212
+
213
+ custom = LLMModel(
214
+ family=LLMFamilies.OPENAI,
215
+ name="gpt-4-custom",
216
+ reasoning=False
217
+ )
218
+ ModelRegistry.register_custom(custom)
219
+ ```
220
+
221
+ ## FastAPI Server
222
+
223
+ ```python
224
+ from contextlib import asynccontextmanager
225
+ from fastapi import FastAPI
226
+ from fastapi.middleware.cors import CORSMiddleware
227
+ from spaik_sdk.agent.base_agent import BaseAgent
228
+ from spaik_sdk.server.api.routers.api_builder import ApiBuilder
229
+
230
+ class MyAgent(BaseAgent):
231
+ pass
232
+
233
+ @asynccontextmanager
234
+ async def lifespan(app: FastAPI):
235
+ agent = MyAgent(system_prompt="You are helpful.")
236
+ api_builder = ApiBuilder.local(agent=agent)
237
+
238
+ app.include_router(api_builder.build_thread_router())
239
+ app.include_router(api_builder.build_file_router())
240
+ app.include_router(api_builder.build_audio_router())
241
+ yield
242
+
243
+ app = FastAPI(lifespan=lifespan)
244
+ app.add_middleware(
245
+ CORSMiddleware,
246
+ allow_origins=["*"],
247
+ allow_methods=["*"],
248
+ allow_headers=["*"],
249
+ )
250
+ ```
251
+
252
+ ### API Endpoints
253
+
254
+ Thread management:
255
+ - `POST /threads` - Create thread
256
+ - `GET /threads` - List threads
257
+ - `GET /threads/{id}` - Get thread with messages
258
+ - `POST /threads/{id}/messages/stream` - Send message (SSE)
259
+ - `DELETE /threads/{id}` - Delete thread
260
+ - `POST /threads/{id}/cancel` - Cancel generation
261
+
262
+ Files:
263
+ - `POST /files` - Upload file
264
+ - `GET /files/{id}` - Download file
265
+
266
+ Audio:
267
+ - `POST /audio/speech` - Text to speech
268
+ - `POST /audio/transcribe` - Speech to text
269
+
270
+ ### Production Setup
271
+
272
+ ```python
273
+ from spaik_sdk.server.storage.impl.local_file_thread_repository import LocalFileThreadRepository
274
+ from spaik_sdk.server.authorization.base_authorizer import BaseAuthorizer
275
+
276
+ # Custom repository and auth
277
+ api_builder = ApiBuilder.stateful(
278
+ repository=LocalFileThreadRepository(base_path="./data"),
279
+ authorizer=MyAuthorizer(),
280
+ agent=agent,
281
+ )
282
+ ```
283
+
284
+ ## Orchestration
285
+
286
+ Code-first workflow orchestration without graph DSLs:
287
+
288
+ ```python
289
+ from spaik_sdk.orchestration import BaseOrchestrator, OrchestratorEvent
290
+ from dataclasses import dataclass
291
+ from typing import AsyncIterator
292
+
293
+ @dataclass
294
+ class State:
295
+ items: list[str]
296
+
297
+ @dataclass
298
+ class Result:
299
+ count: int
300
+
301
+ class MyOrchestrator(BaseOrchestrator[State, Result]):
302
+ async def run(self) -> AsyncIterator[OrchestratorEvent[Result]]:
303
+ state = State(items=[])
304
+
305
+ # Run step with automatic status events
306
+ async for event in self.step("fetch", "Fetching data", self.fetch, state):
307
+ yield event
308
+ if event.result:
309
+ state = event.result
310
+
311
+ # Progress updates
312
+ for i, item in enumerate(state.items):
313
+ yield self.progress("process", i + 1, len(state.items))
314
+ await self.process(item)
315
+
316
+ yield self.ok(Result(count=len(state.items)))
317
+
318
+ async def fetch(self, state: State) -> State:
319
+ return State(items=["a", "b", "c"])
320
+
321
+ async def process(self, item: str):
322
+ pass
323
+
324
+ # Run
325
+ orchestrator = MyOrchestrator()
326
+ result = orchestrator.run_sync()
327
+ ```
328
+
329
+ ## Configuration
330
+
331
+ Environment variables:
332
+
333
+ ```bash
334
+ # LLM Providers (at least one required)
335
+ ANTHROPIC_API_KEY=sk-ant-...
336
+ OPENAI_API_KEY=sk-...
337
+ GOOGLE_API_KEY=...
338
+
339
+ # Optional
340
+ AZURE_API_KEY=...
341
+ AZURE_ENDPOINT=https://your-resource.openai.azure.com/
342
+ DEFAULT_MODEL=claude-sonnet-4-20250514
343
+ ```
344
+
345
+ ## Development
346
+
347
+ ```bash
348
+ # Setup
349
+ uv sync
350
+
351
+ # Tests
352
+ make test # All
353
+ make test-unit # Unit only
354
+ make test-integration # Integration only
355
+ make test-unit-single PATTERN=name # Single test
356
+
357
+ # Quality
358
+ make lint # Check linting
359
+ make lint-fix # Fix linting
360
+ make typecheck # Type check
361
+ ```
362
+
363
+ ## Message Structure
364
+
365
+ Messages contain blocks of different types:
366
+
367
+ ```python
368
+ from spaik_sdk.thread.models import MessageBlockType
369
+
370
+ # Block types
371
+ MessageBlockType.PLAIN # Regular text
372
+ MessageBlockType.REASONING # Chain of thought
373
+ MessageBlockType.TOOL_USE # Tool call
374
+ MessageBlockType.ERROR # Error message
375
+ ```
376
+
377
+ ## License
378
+
379
+ MIT - Copyright (c) 2025 Siili Solutions Oyj