spaik-sdk 0.6.2__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.
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/PKG-INFO +4 -2
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/README.md +3 -1
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/pyproject.toml +1 -1
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/agent/base_agent.py +9 -1
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/config/env.py +11 -2
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/google_factory.py +5 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/openai_factory.py +11 -5
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_model.py +1 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/model_registry.py +3 -3
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tracing/__init__.py +4 -1
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tracing/agent_trace.py +7 -1
- spaik_sdk-0.6.4/spaik_sdk/tracing/get_trace_sink.py +77 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tracing/local_trace_sink.py +8 -1
- spaik_sdk-0.6.4/spaik_sdk/tracing/noop_trace_sink.py +21 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tracing/trace_sink.py +11 -1
- spaik_sdk-0.6.4/spaik_sdk/tracing/trace_sink_mode.py +28 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/agent/test_base_agent.py +60 -0
- spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories/test_google_factory.py +144 -0
- spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories/test_openai_factory.py +235 -0
- spaik_sdk-0.6.4/tests/unit/spaik_sdk/tools/impl/__init__.py +0 -0
- spaik_sdk-0.6.4/tests/unit/spaik_sdk/tracing/__init__.py +0 -0
- spaik_sdk-0.6.4/tests/unit/spaik_sdk/tracing/test_tracing.py +424 -0
- spaik_sdk-0.6.4/uv.lock +3816 -0
- spaik_sdk-0.6.2/spaik_sdk/tracing/get_trace_sink.py +0 -15
- spaik_sdk-0.6.2/spaik_sdk/tracing/trace_sink_mode.py +0 -14
- spaik_sdk-0.6.2/uv.lock +0 -3254
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/.cursor/rules/global.mdc +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/.cursor/rules/post_run.mdc +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/.cursor/rules/repo_overview.mdc +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/.cursor/rules/testing-structure.mdc +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/.gitignore +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/Makefile +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/__init__.py.bak +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/env.example +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/kill.sh +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/py.typed +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/setup.sh +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/agent/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/builder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/file_storage_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/mime_types.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/models.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/provider_support.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/base_file_storage.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/attachments/storage/impl/local_file_storage.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/options.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/google_tts.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/openai_stt.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/providers/openai_tts.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/stt.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/audio/tts.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/config/credentials_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/config/env_credentials_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/config/get_credentials_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/image_generator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/options.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/google.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/image_gen/providers/openai.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/cancellation_handle.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_estimate.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_estimate_builder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/consumption_extractor.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/consumption/token_usage.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/converters.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/builtin_cost_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/cost_estimate.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/cost/cost_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/extract_error_message.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/langchain_loop_manager.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/langchain_service.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/message_handler.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/block_manager.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/models.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_content_handler.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_event_handler.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/llm/streaming/streaming_state_manager.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/anthropic_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/base_model_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/factories/ollama_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_config.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_families.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/llm_wrapper.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/anthropic_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/azure_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/base_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/google_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/ollama_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/openai_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/models/providers/provider_type.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/base_orchestrator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/checkpoint.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/orchestration/models.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/prompt/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/prompt/get_prompt_loader.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/prompt/local_prompt_loader.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/prompt/prompt_loader.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/prompt/prompt_loader_mode.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/py.typed +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/base_playback.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/base_recorder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/conditional_recorder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/local_playback.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/impl/local_recorder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/recording/langchain_serializer.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/api_builder.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/audio_router_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/file_router_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/routers/thread_router_factory.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/format_sse_event.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/negotiate_streaming_response.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/api/streaming/streaming_negotiator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/base_authorizer.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/base_user.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/authorization/dummy_authorizer.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/base_job_processor.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/job_processor/thread_job_processor.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/cancellation_publisher.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/cancellation_subscriber.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/event_publisher.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/local_cancellation_pubsub.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/pubsub/impl/signalr_publisher.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/agent_job_queue.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/queue/impl/azure_queue.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/response/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/response/agent_response_generator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/response/response_generator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/response/simple_agent_response_generator.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/services/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_converters.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_models.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/services/thread_service.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/base_thread_repository.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/in_memory_thread_repository.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/impl/local_file_thread_repository.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/thread_filter.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/server/storage/thread_metadata.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/block_display.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/display_manager.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/cli/live_cli.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/event_adapter.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/streaming_block_adapter.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/adapters/sync_adapter.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/models.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/thread/thread_container.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tools/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/mcp_tool_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tools/impl/search_tool_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/tools/tool_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/utils/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/spaik_sdk/utils/init_logger.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/conftest.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_consumption_tracking/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_event_stream_basic/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-3-7-sonnet-latest/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-haiku-4-5-20251001/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-opus-4-5-20251101/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-sonnet-4-20250514/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_claude-sonnet-4-5-20250929/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-2.5-flash/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-3-flash-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gemini-3-pro-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-4.1/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-5.1/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_gpt-5.2/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_o4-mini/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_text_with_cancellation/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-3-7-sonnet-latest/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-haiku-4-5-20251001/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-1-20250805/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-5-20251101/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-20250514/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-5-20250929/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gemini-2.5-flash/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gemini-3-flash-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-4.1/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1-codex/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2-pro/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_response_with_tool_call_o4-mini/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_get_structured_response/1.json +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/data/recordings/test_mystery_streaming_issue/1.jsonl +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/integration/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/integration/test_cost_tracking_integration.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/integration/test_mcp_tool_provider.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/manual/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/manual/test_search.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/unit/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/llm/streaming/test_streaming_event_handler.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/models/__init__.py +0 -0
- {spaik_sdk-0.6.2/tests/unit/spaik_sdk/orchestration → spaik_sdk-0.6.4/tests/unit/spaik_sdk/models/factories}/__init__.py +0 -0
- {spaik_sdk-0.6.2 → spaik_sdk-0.6.4}/tests/unit/spaik_sdk/models/test_model_registry.py +0 -0
- {spaik_sdk-0.6.2/tests/unit/spaik_sdk/tools/impl → spaik_sdk-0.6.4/tests/unit/spaik_sdk/orchestration}/__init__.py +0 -0
- {spaik_sdk-0.6.2 → 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
|
+
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
|
|
@@ -54,6 +54,8 @@ Description-Content-Type: text/markdown
|
|
|
54
54
|
|
|
55
55
|
Python SDK for building AI agents with multi-LLM support, streaming, and production infrastructure.
|
|
56
56
|
|
|
57
|
+
Spaik SDK is an open-source project developed by engineers at Siili Solutions Oyj. This is not an official Siili product.
|
|
58
|
+
|
|
57
59
|
## Installation
|
|
58
60
|
|
|
59
61
|
```bash
|
|
@@ -376,4 +378,4 @@ MessageBlockType.ERROR # Error message
|
|
|
376
378
|
|
|
377
379
|
## License
|
|
378
380
|
|
|
379
|
-
MIT - Copyright (c)
|
|
381
|
+
MIT - Copyright (c) 2026 Siili Solutions Oyj
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Python SDK for building AI agents with multi-LLM support, streaming, and production infrastructure.
|
|
4
4
|
|
|
5
|
+
Spaik SDK is an open-source project developed by engineers at Siili Solutions Oyj. This is not an official Siili product.
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -324,4 +326,4 @@ MessageBlockType.ERROR # Error message
|
|
|
324
326
|
|
|
325
327
|
## License
|
|
326
328
|
|
|
327
|
-
MIT - Copyright (c)
|
|
329
|
+
MIT - Copyright (c) 2026 Siili Solutions Oyj
|
|
@@ -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(
|
|
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
|
-
|
|
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
|
-
#
|
|
39
|
+
# Handle reasoning configuration based on user preference (config.reasoning)
|
|
40
|
+
# and model capability (config.model.reasoning)
|
|
40
41
|
if config.model.reasoning:
|
|
41
|
-
#
|
|
42
|
+
# Model supports reasoning - check user preference
|
|
42
43
|
model_config["use_responses_api"] = True
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
@@ -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(
|
|
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(
|
|
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(
|
|
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
|