spaik-sdk 0.6.3__tar.gz → 0.6.4__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 (229) hide show
  1. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/PKG-INFO +1 -1
  2. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/pyproject.toml +1 -1
  3. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/agent/base_agent.py +9 -1
  4. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/config/env.py +11 -2
  5. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/google_factory.py +5 -0
  6. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/openai_factory.py +11 -5
  7. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_model.py +1 -0
  8. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/model_registry.py +3 -3
  9. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tracing/__init__.py +4 -1
  10. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tracing/agent_trace.py +7 -1
  11. spaik_sdk-0.6.4/spaik_sdk/tracing/get_trace_sink.py +77 -0
  12. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tracing/local_trace_sink.py +8 -1
  13. spaik_sdk-0.6.4/spaik_sdk/tracing/noop_trace_sink.py +21 -0
  14. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tracing/trace_sink.py +11 -1
  15. spaik_sdk-0.6.4/spaik_sdk/tracing/trace_sink_mode.py +28 -0
  16. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/agent/test_base_agent.py +60 -0
  17. spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories/test_google_factory.py +144 -0
  18. spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories/test_openai_factory.py +235 -0
  19. spaik_sdk-0.6.4/tests/unit/spaik_sdk/tools/impl/__init__.py +0 -0
  20. spaik_sdk-0.6.4/tests/unit/spaik_sdk/tracing/__init__.py +0 -0
  21. spaik_sdk-0.6.4/tests/unit/spaik_sdk/tracing/test_tracing.py +424 -0
  22. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/uv.lock +322 -326
  23. spaik_sdk-0.6.3/spaik_sdk/tracing/get_trace_sink.py +0 -15
  24. spaik_sdk-0.6.3/spaik_sdk/tracing/trace_sink_mode.py +0 -14
  25. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/.cursor/rules/global.mdc +0 -0
  26. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/.cursor/rules/post_run.mdc +0 -0
  27. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/.cursor/rules/repo_overview.mdc +0 -0
  28. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/.cursor/rules/testing-structure.mdc +0 -0
  29. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/.gitignore +0 -0
  30. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/Makefile +0 -0
  31. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/README.md +0 -0
  32. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/__init__.py.bak +0 -0
  33. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/env.example +0 -0
  34. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/kill.sh +0 -0
  35. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/py.typed +0 -0
  36. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/setup.sh +0 -0
  37. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/__init__.py +0 -0
  38. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/agent/__init__.py +0 -0
  39. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/__init__.py +0 -0
  40. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/builder.py +0 -0
  41. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/file_storage_provider.py +0 -0
  42. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/mime_types.py +0 -0
  43. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/models.py +0 -0
  44. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/provider_support.py +0 -0
  45. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/__init__.py +0 -0
  46. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/base_file_storage.py +0 -0
  47. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/impl/__init__.py +0 -0
  48. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/impl/local_file_storage.py +0 -0
  49. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/__init__.py +0 -0
  50. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/options.py +0 -0
  51. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/__init__.py +0 -0
  52. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/google_tts.py +0 -0
  53. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/openai_stt.py +0 -0
  54. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/openai_tts.py +0 -0
  55. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/stt.py +0 -0
  56. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/audio/tts.py +0 -0
  57. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/config/credentials_provider.py +0 -0
  58. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/config/env_credentials_provider.py +0 -0
  59. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/config/get_credentials_provider.py +0 -0
  60. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/__init__.py +0 -0
  61. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/image_generator.py +0 -0
  62. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/options.py +0 -0
  63. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/__init__.py +0 -0
  64. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/google.py +0 -0
  65. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/openai.py +0 -0
  66. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/__init__.py +0 -0
  67. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/cancellation_handle.py +0 -0
  68. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/__init__.py +0 -0
  69. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_estimate.py +0 -0
  70. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_estimate_builder.py +0 -0
  71. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_extractor.py +0 -0
  72. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/token_usage.py +0 -0
  73. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/converters.py +0 -0
  74. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/__init__.py +0 -0
  75. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/builtin_cost_provider.py +0 -0
  76. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/cost_estimate.py +0 -0
  77. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/cost_provider.py +0 -0
  78. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/extract_error_message.py +0 -0
  79. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/langchain_loop_manager.py +0 -0
  80. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/langchain_service.py +0 -0
  81. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/message_handler.py +0 -0
  82. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/__init__.py +0 -0
  83. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/block_manager.py +0 -0
  84. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/models.py +0 -0
  85. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_content_handler.py +0 -0
  86. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_event_handler.py +0 -0
  87. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_state_manager.py +0 -0
  88. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/__init__.py +0 -0
  89. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/__init__.py +0 -0
  90. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/anthropic_factory.py +0 -0
  91. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/base_model_factory.py +0 -0
  92. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/ollama_factory.py +0 -0
  93. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_config.py +0 -0
  94. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_families.py +0 -0
  95. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_wrapper.py +0 -0
  96. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/__init__.py +0 -0
  97. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/anthropic_provider.py +0 -0
  98. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/azure_provider.py +0 -0
  99. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/base_provider.py +0 -0
  100. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/google_provider.py +0 -0
  101. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/ollama_provider.py +0 -0
  102. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/openai_provider.py +0 -0
  103. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/provider_type.py +0 -0
  104. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/__init__.py +0 -0
  105. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/base_orchestrator.py +0 -0
  106. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/checkpoint.py +0 -0
  107. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/models.py +0 -0
  108. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/prompt/__init__.py +0 -0
  109. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/prompt/get_prompt_loader.py +0 -0
  110. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/prompt/local_prompt_loader.py +0 -0
  111. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/prompt/prompt_loader.py +0 -0
  112. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/prompt/prompt_loader_mode.py +0 -0
  113. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/py.typed +0 -0
  114. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/__init__.py +0 -0
  115. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/base_playback.py +0 -0
  116. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/base_recorder.py +0 -0
  117. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/conditional_recorder.py +0 -0
  118. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/__init__.py +0 -0
  119. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/local_playback.py +0 -0
  120. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/local_recorder.py +0 -0
  121. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/recording/langchain_serializer.py +0 -0
  122. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/__init__.py +0 -0
  123. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/__init__.py +0 -0
  124. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/api_builder.py +0 -0
  125. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/audio_router_factory.py +0 -0
  126. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/file_router_factory.py +0 -0
  127. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/thread_router_factory.py +0 -0
  128. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/__init__.py +0 -0
  129. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/format_sse_event.py +0 -0
  130. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/negotiate_streaming_response.py +0 -0
  131. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/streaming_negotiator.py +0 -0
  132. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/__init__.py +0 -0
  133. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/base_authorizer.py +0 -0
  134. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/base_user.py +0 -0
  135. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/dummy_authorizer.py +0 -0
  136. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/__init__.py +0 -0
  137. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/base_job_processor.py +0 -0
  138. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/thread_job_processor.py +0 -0
  139. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/__init__.py +0 -0
  140. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/cancellation_publisher.py +0 -0
  141. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/cancellation_subscriber.py +0 -0
  142. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/event_publisher.py +0 -0
  143. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/__init__.py +0 -0
  144. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/local_cancellation_pubsub.py +0 -0
  145. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/signalr_publisher.py +0 -0
  146. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/__init__.py +0 -0
  147. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/agent_job_queue.py +0 -0
  148. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/impl/__init__.py +0 -0
  149. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/impl/azure_queue.py +0 -0
  150. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/response/__init__.py +0 -0
  151. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/response/agent_response_generator.py +0 -0
  152. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/response/response_generator.py +0 -0
  153. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/response/simple_agent_response_generator.py +0 -0
  154. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/services/__init__.py +0 -0
  155. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_converters.py +0 -0
  156. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_models.py +0 -0
  157. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_service.py +0 -0
  158. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/__init__.py +0 -0
  159. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/base_thread_repository.py +0 -0
  160. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/__init__.py +0 -0
  161. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/in_memory_thread_repository.py +0 -0
  162. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/local_file_thread_repository.py +0 -0
  163. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/thread_filter.py +0 -0
  164. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/thread_metadata.py +0 -0
  165. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/__init__.py +0 -0
  166. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/__init__.py +0 -0
  167. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/__init__.py +0 -0
  168. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/block_display.py +0 -0
  169. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/display_manager.py +0 -0
  170. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/live_cli.py +0 -0
  171. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/event_adapter.py +0 -0
  172. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/streaming_block_adapter.py +0 -0
  173. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/sync_adapter.py +0 -0
  174. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/models.py +0 -0
  175. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/thread/thread_container.py +0 -0
  176. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tools/__init__.py +0 -0
  177. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/__init__.py +0 -0
  178. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/mcp_tool_provider.py +0 -0
  179. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/search_tool_provider.py +0 -0
  180. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/tools/tool_provider.py +0 -0
  181. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/utils/__init__.py +0 -0
  182. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/spaik_sdk/utils/init_logger.py +0 -0
  183. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/__init__.py +0 -0
  184. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/conftest.py +0 -0
  185. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_consumption_tracking/1.jsonl +0 -0
  186. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_event_stream_basic/1.jsonl +0 -0
  187. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text/1.jsonl +0 -0
  188. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-3-7-sonnet-latest/1.jsonl +0 -0
  189. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-haiku-4-5-20251001/1.jsonl +0 -0
  190. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-opus-4-5-20251101/1.jsonl +0 -0
  191. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-sonnet-4-20250514/1.jsonl +0 -0
  192. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-sonnet-4-5-20250929/1.jsonl +0 -0
  193. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-2.5-flash/1.jsonl +0 -0
  194. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-3-flash-preview/1.jsonl +0 -0
  195. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-3-pro-preview/1.jsonl +0 -0
  196. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-4.1/1.jsonl +0 -0
  197. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-5.1/1.jsonl +0 -0
  198. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-5.2/1.jsonl +0 -0
  199. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_o4-mini/1.jsonl +0 -0
  200. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_with_cancellation/1.jsonl +0 -0
  201. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-3-7-sonnet-latest/1.jsonl +0 -0
  202. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-haiku-4-5-20251001/1.jsonl +0 -0
  203. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-1-20250805/1.jsonl +0 -0
  204. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-5-20251101/1.jsonl +0 -0
  205. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-20250514/1.jsonl +0 -0
  206. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-5-20250929/1.jsonl +0 -0
  207. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gemini-2.5-flash/1.jsonl +0 -0
  208. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gemini-3-flash-preview/1.jsonl +0 -0
  209. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-4.1/1.jsonl +0 -0
  210. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5/1.jsonl +0 -0
  211. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1/1.jsonl +0 -0
  212. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1-codex/1.jsonl +0 -0
  213. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2/1.jsonl +0 -0
  214. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2-pro/1.jsonl +0 -0
  215. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_o4-mini/1.jsonl +0 -0
  216. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_structured_response/1.json +0 -0
  217. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/data/recordings/test_mystery_streaming_issue/1.jsonl +0 -0
  218. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/integration/__init__.py +0 -0
  219. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/integration/test_cost_tracking_integration.py +0 -0
  220. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/integration/test_mcp_tool_provider.py +0 -0
  221. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/manual/__init__.py +0 -0
  222. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/manual/test_search.py +0 -0
  223. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/__init__.py +0 -0
  224. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/llm/streaming/test_streaming_event_handler.py +0 -0
  225. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/models/__init__.py +0 -0
  226. {spaik_sdk-0.6.3/tests/unit/spaik_sdk/orchestration → spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories}/__init__.py +0 -0
  227. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/models/test_model_registry.py +0 -0
  228. {spaik_sdk-0.6.3/tests/unit/spaik_sdk/tools/impl → spaik_sdk-0.6.4/tests/unit/spaik_sdk/orchestration}/__init__.py +0 -0
  229. {spaik_sdk-0.6.3 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/orchestration/test_base_orchestrator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: spaik-sdk
3
- Version: 0.6.3
3
+ Version: 0.6.4
4
4
  Summary: Python SDK for building AI agents with multi-LLM support, streaming, and production-ready infrastructure
5
5
  Project-URL: Homepage, https://github.com/siilisolutions/spaik-sdk
6
6
  Project-URL: Repository, https://github.com/siilisolutions/spaik-sdk
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "spaik-sdk"
3
- version = "0.6.3"
3
+ version = "0.6.4"
4
4
  description = "Python SDK for building AI agents with multi-LLM support, streaming, and production-ready infrastructure"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import uuid
2
3
  from abc import ABC
3
4
  from typing import Any, AsyncGenerator, Dict, List, Optional, Type, TypeVar
4
5
 
@@ -57,9 +58,16 @@ class BaseAgent(ABC):
57
58
  cost_provider: Optional[CostProvider] = None,
58
59
  ):
59
60
  logger.debug("Initializing BaseAgent")
61
+ # Generate unique instance ID for trace correlation
62
+ self.agent_instance_id: str = str(uuid.uuid4())
60
63
  self.prompt_loader = prompt_loader or get_prompt_loader(prompt_loader_mode)
61
64
  self.system_prompt = system_prompt or self._get_system_prompt(system_prompt_args, system_prompt_version)
62
- self.trace = trace or AgentTrace(self.system_prompt, self.__class__.__name__, trace_sink=trace_sink)
65
+ self.trace = trace or AgentTrace(
66
+ self.system_prompt,
67
+ self.__class__.__name__,
68
+ trace_sink=trace_sink,
69
+ agent_instance_id=self.agent_instance_id,
70
+ )
63
71
  self.thread_container = thread_container or ThreadContainer(self.system_prompt)
64
72
  self.tools = tools or self._create_tools(tool_providers)
65
73
  self.llm_config = llm_config or self.create_llm_config(llm_model, reasoning)
@@ -1,5 +1,6 @@
1
1
  import os
2
2
  from typing import Dict
3
+ from typing import Optional as OptionalType
3
4
 
4
5
  from spaik_sdk.models.llm_model import LLMModel
5
6
  from spaik_sdk.models.model_registry import ModelRegistry
@@ -46,8 +47,16 @@ class EnvConfig:
46
47
  def get_prompt_loader_mode(self) -> PromptLoaderMode:
47
48
  return PromptLoaderMode.from_name(self.get_key("PROMPT_LOADER_MODE", "local"))
48
49
 
49
- def get_trace_sink_mode(self) -> TraceSinkMode:
50
- return TraceSinkMode.from_name(self.get_key("TRACE_SINK_MODE", "local"))
50
+ def get_trace_sink_mode(self) -> OptionalType[TraceSinkMode]:
51
+ """Get the trace sink mode from environment variable.
52
+
53
+ Returns:
54
+ TraceSinkMode.LOCAL if TRACE_SINK_MODE=local,
55
+ TraceSinkMode.NOOP if TRACE_SINK_MODE=noop,
56
+ None if TRACE_SINK_MODE is not set or empty (allows global/default behavior).
57
+ """
58
+ mode_str = self.get_key("TRACE_SINK_MODE", "", required=False)
59
+ return TraceSinkMode.from_name(mode_str)
51
60
 
52
61
  def get_credentials_provider_type(self) -> str:
53
62
  return self.get_key("CREDENTIALS_PROVIDER_TYPE", "env")
@@ -22,6 +22,11 @@ class GoogleModelFactory(BaseModelFactory):
22
22
  if config.reasoning:
23
23
  model_config["thinking_budget"] = config.reasoning_budget_tokens
24
24
  model_config["include_thoughts"] = True
25
+ else:
26
+ # Gemini models have thinking enabled by default, so we must explicitly
27
+ # set thinking_budget=0 to disable it (omitting the parameter doesn't work)
28
+ model_config["thinking_budget"] = 0
29
+ model_config["include_thoughts"] = False
25
30
 
26
31
  # Handle streaming - Google models use disable_streaming instead of streaming
27
32
  if not config.streaming:
@@ -36,15 +36,21 @@ class OpenAIModelFactory(BaseModelFactory):
36
36
  if config.tool_usage:
37
37
  model_config["model_kwargs"] = {"parallel_tool_calls": True}
38
38
 
39
- # Add model-specific configurations for reasoning models
39
+ # Handle reasoning configuration based on user preference (config.reasoning)
40
+ # and model capability (config.model.reasoning)
40
41
  if config.model.reasoning:
41
- # Enable Responses API for reasoning models
42
+ # Model supports reasoning - check user preference
42
43
  model_config["use_responses_api"] = True
43
44
 
44
- # Configure reasoning through model_kwargs as per LangChain docs
45
- if config.reasoning_summary:
46
- model_config["model_kwargs"] = {"reasoning": {"effort": config.reasoning_effort, "summary": config.reasoning_summary}}
45
+ if config.reasoning:
46
+ # User wants reasoning enabled - use configured effort
47
+ if config.reasoning_summary:
48
+ model_config["model_kwargs"] = {"reasoning": {"effort": config.reasoning_effort, "summary": config.reasoning_summary}}
49
+ else:
50
+ # User wants reasoning disabled - use model's minimum effort level
51
+ model_config["model_kwargs"] = {"reasoning": {"effort": config.model.reasoning_min_effort}}
47
52
  else:
53
+ # Model doesn't support reasoning
48
54
  model_config["temperature"] = config.temperature
49
55
 
50
56
  return model_config
@@ -8,6 +8,7 @@ class LLMModel:
8
8
  family: str
9
9
  name: str
10
10
  reasoning: bool = True
11
+ reasoning_min_effort: str = "none" # Minimum reasoning effort: 'none' or 'minimal'
11
12
  prompt_caching: bool = False
12
13
 
13
14
  def __str__(self) -> str:
@@ -25,9 +25,9 @@ class ModelRegistry:
25
25
  GPT_4O = LLMModel(family=LLMFamilies.OPENAI, name="gpt-4o", reasoning=False, prompt_caching=True)
26
26
  O4_MINI = LLMModel(family=LLMFamilies.OPENAI, name="o4-mini")
27
27
  O4_MINI_APRIL_2025 = LLMModel(family=LLMFamilies.OPENAI, name="o4-mini-2025-04-16")
28
- GPT_5 = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5", reasoning=True, prompt_caching=True)
29
- GPT_5_MINI = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5-mini", reasoning=True, prompt_caching=True)
30
- GPT_5_NANO = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5-nano", reasoning=True, prompt_caching=True)
28
+ GPT_5 = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5", reasoning=True, reasoning_min_effort="minimal", prompt_caching=True)
29
+ GPT_5_MINI = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5-mini", reasoning=True, reasoning_min_effort="minimal", prompt_caching=True)
30
+ GPT_5_NANO = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5-nano", reasoning=True, reasoning_min_effort="minimal", prompt_caching=True)
31
31
  GPT_5_1 = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5.1", reasoning=True, prompt_caching=True)
32
32
  GPT_5_1_CODEX = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5.1-codex", reasoning=True, prompt_caching=True)
33
33
  GPT_5_1_CODEX_MINI = LLMModel(family=LLMFamilies.OPENAI, name="gpt-5.1-codex-mini", reasoning=True, prompt_caching=True)
@@ -1,6 +1,7 @@
1
1
  from spaik_sdk.tracing.agent_trace import AgentTrace
2
- from spaik_sdk.tracing.get_trace_sink import get_trace_sink
2
+ from spaik_sdk.tracing.get_trace_sink import configure_tracing, get_trace_sink
3
3
  from spaik_sdk.tracing.local_trace_sink import LocalTraceSink
4
+ from spaik_sdk.tracing.noop_trace_sink import NoOpTraceSink
4
5
  from spaik_sdk.tracing.trace_sink import TraceSink
5
6
  from spaik_sdk.tracing.trace_sink_mode import TraceSinkMode
6
7
 
@@ -8,6 +9,8 @@ __all__ = [
8
9
  "AgentTrace",
9
10
  "TraceSink",
10
11
  "LocalTraceSink",
12
+ "NoOpTraceSink",
11
13
  "TraceSinkMode",
14
+ "configure_tracing",
12
15
  "get_trace_sink",
13
16
  ]
@@ -1,5 +1,6 @@
1
1
  import json
2
2
  import time
3
+ import uuid
3
4
  from typing import Optional, Type
4
5
 
5
6
  from pydantic import BaseModel
@@ -15,12 +16,15 @@ class AgentTrace:
15
16
  system_prompt: str,
16
17
  save_name: Optional[str] = None,
17
18
  trace_sink: Optional[TraceSink] = None,
19
+ agent_instance_id: Optional[str] = None,
18
20
  ):
19
21
  self.system_prompt: str = system_prompt
20
22
  self._start_time_monotonic: float = time.monotonic()
21
23
  self._steps: list[tuple[float, str]] = []
22
24
  self.save_name: Optional[str] = save_name
23
25
  self._trace_sink: TraceSink = trace_sink or get_trace_sink()
26
+ # Generate UUID if not provided (backward compatibility)
27
+ self.agent_instance_id: str = agent_instance_id or str(uuid.uuid4())
24
28
 
25
29
  def add_step(self, step_content: str) -> None:
26
30
  current_time_monotonic: float = time.monotonic()
@@ -69,4 +73,6 @@ class AgentTrace:
69
73
 
70
74
  def save(self, name: str) -> None:
71
75
  trace_content = self.to_string(include_system_prompt=False)
72
- self._trace_sink.save_trace(name, trace_content, self.system_prompt)
76
+ self._trace_sink.save_trace(
77
+ name, trace_content, self.system_prompt, self.agent_instance_id
78
+ )
@@ -0,0 +1,77 @@
1
+ from typing import Optional
2
+
3
+ from spaik_sdk.tracing.local_trace_sink import LocalTraceSink
4
+ from spaik_sdk.tracing.noop_trace_sink import NoOpTraceSink
5
+ from spaik_sdk.tracing.trace_sink import TraceSink
6
+ from spaik_sdk.tracing.trace_sink_mode import TraceSinkMode
7
+
8
+ # Module-level storage for globally configured trace sink
9
+ _global_trace_sink: Optional[TraceSink] = None
10
+
11
+
12
+ def configure_tracing(sink: Optional[TraceSink]) -> None:
13
+ """Configure the global trace sink used by all agents.
14
+
15
+ Call this once at application startup to set a custom trace sink
16
+ that will be used by all subsequently created agents.
17
+
18
+ Resolution order:
19
+ 1. TRACE_SINK_MODE=local env var -> LocalTraceSink (escape hatch)
20
+ 2. TRACE_SINK_MODE=noop env var -> NoOpTraceSink
21
+ 3. Global sink set via this function -> the configured sink
22
+ 4. No configuration -> NoOpTraceSink (silent default)
23
+
24
+ Args:
25
+ sink: The TraceSink to use globally, or None to clear the global
26
+ configuration (reverts to default no-op behavior).
27
+
28
+ Example:
29
+ from spaik_sdk.tracing import configure_tracing, LocalTraceSink
30
+
31
+ # At application startup
32
+ configure_tracing(LocalTraceSink(traces_dir="my_traces"))
33
+
34
+ # Or with a custom sink for observability
35
+ configure_tracing(MyDatadogTraceSink())
36
+
37
+ # Clear global config
38
+ configure_tracing(None)
39
+ """
40
+ global _global_trace_sink
41
+ _global_trace_sink = sink
42
+
43
+
44
+ def get_trace_sink(mode: Optional[TraceSinkMode] = None) -> TraceSink:
45
+ """Get the appropriate trace sink based on configuration.
46
+
47
+ Resolution order:
48
+ 1. If mode parameter is provided, use that mode
49
+ 2. Check TRACE_SINK_MODE env var (LOCAL or NOOP override everything)
50
+ 3. Check for globally configured sink via configure_tracing()
51
+ 4. Default to NoOpTraceSink (silent no-op)
52
+
53
+ Args:
54
+ mode: Optional explicit mode to use (overrides all other config).
55
+
56
+ Returns:
57
+ The appropriate TraceSink instance.
58
+ """
59
+ # Lazy import to avoid circular dependency with env.py
60
+ from spaik_sdk.config.env import env_config
61
+
62
+ # If explicit mode parameter provided, use it
63
+ if mode is None:
64
+ mode = env_config.get_trace_sink_mode()
65
+
66
+ # Step 1-2: Check env var mode (LOCAL or NOOP)
67
+ if mode == TraceSinkMode.LOCAL:
68
+ return LocalTraceSink()
69
+ if mode == TraceSinkMode.NOOP:
70
+ return NoOpTraceSink()
71
+
72
+ # Step 3: Check global sink
73
+ if _global_trace_sink is not None:
74
+ return _global_trace_sink
75
+
76
+ # Step 4: Default to no-op
77
+ return NoOpTraceSink()
@@ -10,7 +10,14 @@ class LocalTraceSink(TraceSink):
10
10
  def __init__(self, traces_dir: Optional[str] = None):
11
11
  self.traces_dir = traces_dir or "traces"
12
12
 
13
- def save_trace(self, name: str, trace_content: str, system_prompt: str) -> None:
13
+ def save_trace(
14
+ self,
15
+ name: str,
16
+ trace_content: str,
17
+ system_prompt: str,
18
+ agent_instance_id: Optional[str] = None,
19
+ ) -> None:
20
+ # agent_instance_id is accepted but intentionally ignored - file naming is unchanged
14
21
  os.makedirs(self.traces_dir, exist_ok=True)
15
22
 
16
23
  trace_path = os.path.join(self.traces_dir, f"{name}.txt")
@@ -0,0 +1,21 @@
1
+ from typing import Optional
2
+
3
+ from spaik_sdk.tracing.trace_sink import TraceSink
4
+
5
+
6
+ class NoOpTraceSink(TraceSink):
7
+ """TraceSink implementation that does nothing.
8
+
9
+ Used as the default when no tracing is configured, ensuring traces
10
+ are silently discarded without any side effects.
11
+ """
12
+
13
+ def save_trace(
14
+ self,
15
+ name: str,
16
+ trace_content: str,
17
+ system_prompt: str,
18
+ agent_instance_id: Optional[str] = None,
19
+ ) -> None:
20
+ """Do nothing - silently discard the trace."""
21
+ pass
@@ -1,4 +1,5 @@
1
1
  from abc import ABC, abstractmethod
2
+ from typing import Optional
2
3
 
3
4
 
4
5
  class TraceSink(ABC):
@@ -8,12 +9,21 @@ class TraceSink(ABC):
8
9
  """
9
10
 
10
11
  @abstractmethod
11
- def save_trace(self, name: str, trace_content: str, system_prompt: str) -> None:
12
+ def save_trace(
13
+ self,
14
+ name: str,
15
+ trace_content: str,
16
+ system_prompt: str,
17
+ agent_instance_id: Optional[str] = None,
18
+ ) -> None:
12
19
  """Save a trace with its system prompt.
13
20
 
14
21
  Args:
15
22
  name: Identifier for the trace (e.g., agent class name)
16
23
  trace_content: The formatted trace content (without system prompt)
17
24
  system_prompt: The system prompt used for the agent
25
+ agent_instance_id: Optional UUID identifying the agent instance for correlation.
26
+ Custom sinks can use this to correlate traces in observability
27
+ backends (e.g., Datadog, OpenTelemetry).
18
28
  """
19
29
  pass
@@ -0,0 +1,28 @@
1
+ from enum import Enum
2
+ from typing import Optional
3
+
4
+
5
+ class TraceSinkMode(Enum):
6
+ LOCAL = "local"
7
+ NOOP = "noop"
8
+
9
+ @classmethod
10
+ def from_name(cls, name: Optional[str]) -> Optional["TraceSinkMode"]:
11
+ """Convert a string name to a TraceSinkMode.
12
+
13
+ Args:
14
+ name: The mode name ("local", "noop") or None/empty string.
15
+
16
+ Returns:
17
+ The corresponding TraceSinkMode, or None if name is empty/None.
18
+ Returns None for unrecognized values (silent fallthrough to default behavior).
19
+ """
20
+ if not name:
21
+ return None
22
+
23
+ for mode in cls:
24
+ if mode.value == name.lower():
25
+ return mode
26
+
27
+ # Unknown values fall through to None (let get_trace_sink handle default)
28
+ return None
@@ -1,4 +1,5 @@
1
1
  import time
2
+ import uuid
2
3
  from typing import List, Optional
3
4
 
4
5
  import pytest
@@ -275,3 +276,62 @@ class TestBaseAgent:
275
276
  # Verify cost estimation works
276
277
  cost = BuiltinCostProvider().get_cost_estimate(ModelRegistry.CLAUDE_4_SONNET, total_consumption)
277
278
  assert cost.cost > 0
279
+
280
+
281
+ @pytest.mark.unit
282
+ class TestBaseAgentInstanceId:
283
+ """Tests for BaseAgent agent_instance_id."""
284
+
285
+ def test_base_agent_has_agent_instance_id(self):
286
+ """BaseAgent has an agent_instance_id attribute that is a UUID."""
287
+ agent = ConcreteTestAgent(
288
+ recording_name="test_instance_id_has_id",
289
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
290
+ )
291
+
292
+ assert hasattr(agent, "agent_instance_id")
293
+ assert agent.agent_instance_id is not None
294
+ assert isinstance(agent.agent_instance_id, str)
295
+
296
+ # Should be a valid UUID format
297
+ parsed_uuid = uuid.UUID(agent.agent_instance_id)
298
+ assert str(parsed_uuid) == agent.agent_instance_id
299
+
300
+ def test_two_agents_have_different_instance_ids(self):
301
+ """Two BaseAgent instances have different instance IDs."""
302
+ agent1 = ConcreteTestAgent(
303
+ recording_name="test_instance_id_unique_1",
304
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
305
+ )
306
+ agent2 = ConcreteTestAgent(
307
+ recording_name="test_instance_id_unique_2",
308
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
309
+ )
310
+
311
+ assert agent1.agent_instance_id != agent2.agent_instance_id
312
+
313
+ def test_same_agent_class_different_instance_ids(self):
314
+ """Two agents created from same class have different instance IDs."""
315
+ # Create two agents of the exact same type with same parameters
316
+ agent1 = ConcreteTestAgent(
317
+ recording_name="test_same_class_different_id_1",
318
+ system_prompt="Same prompt",
319
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
320
+ )
321
+ agent2 = ConcreteTestAgent(
322
+ recording_name="test_same_class_different_id_2",
323
+ system_prompt="Same prompt",
324
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
325
+ )
326
+
327
+ assert agent1.agent_instance_id != agent2.agent_instance_id
328
+
329
+ def test_agent_instance_id_passed_to_trace(self):
330
+ """The agent_instance_id is passed to AgentTrace during construction."""
331
+ agent = ConcreteTestAgent(
332
+ recording_name="test_instance_id_in_trace",
333
+ llm_model=ModelRegistry.CLAUDE_4_SONNET,
334
+ )
335
+
336
+ # The agent's instance ID should match the trace's instance ID
337
+ assert agent.agent_instance_id == agent.trace.agent_instance_id
@@ -0,0 +1,144 @@
1
+ """Unit tests for GoogleModelFactory reasoning configuration."""
2
+
3
+ import pytest
4
+
5
+ from spaik_sdk.models.factories.google_factory import GoogleModelFactory
6
+ from spaik_sdk.models.llm_config import LLMConfig
7
+ from spaik_sdk.models.model_registry import ModelRegistry
8
+
9
+
10
+ @pytest.mark.unit
11
+ class TestGoogleModelFactoryReasoning:
12
+ """Tests for GoogleModelFactory reasoning configuration."""
13
+
14
+ @pytest.fixture
15
+ def factory(self):
16
+ return GoogleModelFactory()
17
+
18
+ @pytest.fixture
19
+ def gemini_model(self):
20
+ return ModelRegistry.GEMINI_2_5_FLASH
21
+
22
+ def test_reasoning_true_sets_thinking_budget(self, factory, gemini_model):
23
+ """When config.reasoning is True, thinking_budget is set to configured value."""
24
+ config = LLMConfig(
25
+ model=gemini_model,
26
+ reasoning=True,
27
+ reasoning_budget_tokens=8192,
28
+ )
29
+
30
+ model_config = factory.get_model_specific_config(config)
31
+
32
+ assert model_config["thinking_budget"] == 8192
33
+
34
+ def test_reasoning_true_sets_include_thoughts(self, factory, gemini_model):
35
+ """When config.reasoning is True, include_thoughts is set to True."""
36
+ config = LLMConfig(
37
+ model=gemini_model,
38
+ reasoning=True,
39
+ )
40
+
41
+ model_config = factory.get_model_specific_config(config)
42
+
43
+ assert model_config["include_thoughts"] is True
44
+
45
+ def test_reasoning_false_sets_thinking_budget_to_zero(self, factory, gemini_model):
46
+ """When config.reasoning is False, thinking_budget is explicitly set to 0."""
47
+ config = LLMConfig(
48
+ model=gemini_model,
49
+ reasoning=False,
50
+ )
51
+
52
+ model_config = factory.get_model_specific_config(config)
53
+
54
+ assert model_config["thinking_budget"] == 0
55
+
56
+ def test_reasoning_false_sets_include_thoughts_to_false(self, factory, gemini_model):
57
+ """When config.reasoning is False, include_thoughts is set to False."""
58
+ config = LLMConfig(
59
+ model=gemini_model,
60
+ reasoning=False,
61
+ )
62
+
63
+ model_config = factory.get_model_specific_config(config)
64
+
65
+ assert model_config["include_thoughts"] is False
66
+
67
+ @pytest.mark.parametrize(
68
+ "model",
69
+ [
70
+ ModelRegistry.GEMINI_2_5_FLASH,
71
+ ModelRegistry.GEMINI_2_5_PRO,
72
+ ModelRegistry.GEMINI_3_FLASH,
73
+ ModelRegistry.GEMINI_3_PRO,
74
+ ],
75
+ )
76
+ def test_reasoning_false_works_for_all_gemini_models(self, factory, model):
77
+ """When reasoning is False, all Gemini models get thinking_budget=0."""
78
+ config = LLMConfig(
79
+ model=model,
80
+ reasoning=False,
81
+ )
82
+
83
+ model_config = factory.get_model_specific_config(config)
84
+
85
+ assert model_config["thinking_budget"] == 0
86
+ assert model_config["include_thoughts"] is False
87
+
88
+ @pytest.mark.parametrize(
89
+ "model",
90
+ [
91
+ ModelRegistry.GEMINI_2_5_FLASH,
92
+ ModelRegistry.GEMINI_2_5_PRO,
93
+ ModelRegistry.GEMINI_3_FLASH,
94
+ ModelRegistry.GEMINI_3_PRO,
95
+ ],
96
+ )
97
+ def test_reasoning_true_works_for_all_gemini_models(self, factory, model):
98
+ """When reasoning is True, all Gemini models get the configured budget."""
99
+ budget = 4096
100
+ config = LLMConfig(
101
+ model=model,
102
+ reasoning=True,
103
+ reasoning_budget_tokens=budget,
104
+ )
105
+
106
+ model_config = factory.get_model_specific_config(config)
107
+
108
+ assert model_config["thinking_budget"] == budget
109
+ assert model_config["include_thoughts"] is True
110
+
111
+ def test_config_includes_model_name(self, factory, gemini_model):
112
+ """Model config always includes the model name."""
113
+ config_false = LLMConfig(model=gemini_model, reasoning=False)
114
+ config_true = LLMConfig(model=gemini_model, reasoning=True)
115
+
116
+ model_config_false = factory.get_model_specific_config(config_false)
117
+ model_config_true = factory.get_model_specific_config(config_true)
118
+
119
+ assert model_config_false["model"] == gemini_model.name
120
+ assert model_config_true["model"] == gemini_model.name
121
+
122
+ def test_config_includes_temperature(self, factory, gemini_model):
123
+ """Model config always includes temperature."""
124
+ config = LLMConfig(model=gemini_model, reasoning=False, temperature=0.7)
125
+
126
+ model_config = factory.get_model_specific_config(config)
127
+
128
+ assert model_config["temperature"] == 0.7
129
+
130
+ def test_streaming_disabled_adds_disable_streaming_flag(self, factory, gemini_model):
131
+ """When streaming is False, disable_streaming is set to True."""
132
+ config = LLMConfig(model=gemini_model, streaming=False)
133
+
134
+ model_config = factory.get_model_specific_config(config)
135
+
136
+ assert model_config["disable_streaming"] is True
137
+
138
+ def test_streaming_enabled_no_disable_streaming_flag(self, factory, gemini_model):
139
+ """When streaming is True, disable_streaming is not in config."""
140
+ config = LLMConfig(model=gemini_model, streaming=True)
141
+
142
+ model_config = factory.get_model_specific_config(config)
143
+
144
+ assert "disable_streaming" not in model_config