spaik-sdk 0.6.3__tar.gz → 0.6.5__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.3 → spaik_sdk-0.6.5}/PKG-INFO +1 -1
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/pyproject.toml +1 -1
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/agent/base_agent.py +10 -5
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/config/env.py +11 -13
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/google_factory.py +5 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/openai_factory.py +11 -5
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/llm_model.py +1 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/model_registry.py +3 -3
- spaik_sdk-0.6.5/spaik_sdk/models/providers/azure_provider.py +55 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/routers/audio_router_factory.py +1 -1
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/authorization/base_authorizer.py +1 -1
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tracing/__init__.py +4 -1
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tracing/agent_trace.py +5 -1
- spaik_sdk-0.6.5/spaik_sdk/tracing/get_trace_sink.py +77 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tracing/local_trace_sink.py +8 -1
- spaik_sdk-0.6.5/spaik_sdk/tracing/noop_trace_sink.py +21 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tracing/trace_sink.py +11 -1
- spaik_sdk-0.6.5/spaik_sdk/tracing/trace_sink_mode.py +28 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/unit/spaik_sdk/agent/test_base_agent.py +60 -0
- spaik_sdk-0.6.5/tests/unit/spaik_sdk/models/factories/test_google_factory.py +144 -0
- spaik_sdk-0.6.5/tests/unit/spaik_sdk/models/factories/test_openai_factory.py +235 -0
- spaik_sdk-0.6.5/tests/unit/spaik_sdk/tools/impl/__init__.py +0 -0
- spaik_sdk-0.6.5/tests/unit/spaik_sdk/tracing/__init__.py +0 -0
- spaik_sdk-0.6.5/tests/unit/spaik_sdk/tracing/test_tracing.py +424 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/uv.lock +437 -441
- spaik_sdk-0.6.3/spaik_sdk/models/providers/azure_provider.py +0 -31
- spaik_sdk-0.6.3/spaik_sdk/tracing/get_trace_sink.py +0 -15
- spaik_sdk-0.6.3/spaik_sdk/tracing/trace_sink_mode.py +0 -14
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/.cursor/rules/global.mdc +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/.cursor/rules/post_run.mdc +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/.cursor/rules/repo_overview.mdc +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/.cursor/rules/testing-structure.mdc +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/.gitignore +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/Makefile +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/README.md +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/__init__.py.bak +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/env.example +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/kill.sh +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/py.typed +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/setup.sh +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/agent/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/builder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/file_storage_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/mime_types.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/models.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/provider_support.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/storage/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/storage/base_file_storage.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/storage/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/attachments/storage/impl/local_file_storage.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/options.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/providers/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/providers/google_tts.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/providers/openai_stt.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/providers/openai_tts.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/stt.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/audio/tts.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/config/credentials_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/config/env_credentials_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/config/get_credentials_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/image_generator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/options.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/providers/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/providers/google.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/image_gen/providers/openai.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/cancellation_handle.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/consumption/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/consumption/consumption_estimate.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/consumption/consumption_estimate_builder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/consumption/consumption_extractor.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/consumption/token_usage.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/converters.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/cost/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/cost/builtin_cost_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/cost/cost_estimate.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/cost/cost_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/extract_error_message.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/langchain_loop_manager.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/langchain_service.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/message_handler.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/block_manager.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/models.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/streaming_content_handler.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/streaming_event_handler.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/llm/streaming/streaming_state_manager.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/anthropic_factory.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/base_model_factory.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/factories/ollama_factory.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/llm_config.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/llm_families.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/llm_wrapper.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/anthropic_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/base_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/google_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/ollama_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/openai_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/models/providers/provider_type.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/orchestration/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/orchestration/base_orchestrator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/orchestration/checkpoint.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/orchestration/models.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/prompt/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/prompt/get_prompt_loader.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/prompt/local_prompt_loader.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/prompt/prompt_loader.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/prompt/prompt_loader_mode.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/py.typed +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/base_playback.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/base_recorder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/conditional_recorder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/impl/local_playback.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/impl/local_recorder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/recording/langchain_serializer.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/routers/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/routers/api_builder.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/routers/file_router_factory.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/routers/thread_router_factory.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/streaming/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/streaming/format_sse_event.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/streaming/negotiate_streaming_response.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/api/streaming/streaming_negotiator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/authorization/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/authorization/base_user.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/authorization/dummy_authorizer.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/job_processor/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/job_processor/base_job_processor.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/job_processor/thread_job_processor.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/cancellation_publisher.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/cancellation_subscriber.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/event_publisher.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/impl/local_cancellation_pubsub.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/pubsub/impl/signalr_publisher.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/queue/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/queue/agent_job_queue.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/queue/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/queue/impl/azure_queue.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/response/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/response/agent_response_generator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/response/response_generator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/response/simple_agent_response_generator.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/services/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/services/thread_converters.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/services/thread_models.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/services/thread_service.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/base_thread_repository.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/impl/in_memory_thread_repository.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/impl/local_file_thread_repository.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/thread_filter.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/server/storage/thread_metadata.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/cli/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/cli/block_display.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/cli/display_manager.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/cli/live_cli.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/event_adapter.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/streaming_block_adapter.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/adapters/sync_adapter.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/models.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/thread/thread_container.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tools/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tools/impl/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tools/impl/mcp_tool_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tools/impl/search_tool_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/tools/tool_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/utils/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/spaik_sdk/utils/init_logger.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/conftest.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_consumption_tracking/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_event_stream_basic/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_claude-3-7-sonnet-latest/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_claude-haiku-4-5-20251001/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_claude-opus-4-5-20251101/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_claude-sonnet-4-20250514/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_claude-sonnet-4-5-20250929/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gemini-2.5-flash/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gemini-3-flash-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gemini-3-pro-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gpt-4.1/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gpt-5.1/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_gpt-5.2/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_o4-mini/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_text_with_cancellation/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-3-7-sonnet-latest/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-haiku-4-5-20251001/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-1-20250805/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-opus-4-5-20251101/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-20250514/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_claude-sonnet-4-5-20250929/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gemini-2.5-flash/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gemini-3-flash-preview/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-4.1/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-5/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.1-codex/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_gpt-5.2-pro/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_response_with_tool_call_o4-mini/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_get_structured_response/1.json +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/data/recordings/test_mystery_streaming_issue/1.jsonl +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/integration/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/integration/test_cost_tracking_integration.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/integration/test_mcp_tool_provider.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/manual/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/manual/test_search.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/unit/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/unit/spaik_sdk/llm/streaming/test_streaming_event_handler.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/unit/spaik_sdk/models/__init__.py +0 -0
- {spaik_sdk-0.6.3/tests/unit/spaik_sdk/orchestration → spaik_sdk-0.6.5/tests/unit/spaik_sdk/models/factories}/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/tests/unit/spaik_sdk/models/test_model_registry.py +0 -0
- {spaik_sdk-0.6.3/tests/unit/spaik_sdk/tools/impl → spaik_sdk-0.6.5/tests/unit/spaik_sdk/orchestration}/__init__.py +0 -0
- {spaik_sdk-0.6.3 → spaik_sdk-0.6.5}/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.5
|
|
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,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
|
|
|
@@ -16,7 +17,6 @@ from spaik_sdk.llm.cost.cost_provider import CostProvider
|
|
|
16
17
|
from spaik_sdk.llm.langchain_service import LangChainService
|
|
17
18
|
from spaik_sdk.models.llm_config import LLMConfig
|
|
18
19
|
from spaik_sdk.models.llm_model import LLMModel
|
|
19
|
-
from spaik_sdk.models.providers.provider_type import ProviderType
|
|
20
20
|
from spaik_sdk.prompt.get_prompt_loader import get_prompt_loader
|
|
21
21
|
from spaik_sdk.prompt.prompt_loader import PromptLoader
|
|
22
22
|
from spaik_sdk.prompt.prompt_loader_mode import PromptLoaderMode
|
|
@@ -57,9 +57,16 @@ class BaseAgent(ABC):
|
|
|
57
57
|
cost_provider: Optional[CostProvider] = None,
|
|
58
58
|
):
|
|
59
59
|
logger.debug("Initializing BaseAgent")
|
|
60
|
+
# Generate unique instance ID for trace correlation
|
|
61
|
+
self.agent_instance_id: str = str(uuid.uuid4())
|
|
60
62
|
self.prompt_loader = prompt_loader or get_prompt_loader(prompt_loader_mode)
|
|
61
63
|
self.system_prompt = system_prompt or self._get_system_prompt(system_prompt_args, system_prompt_version)
|
|
62
|
-
self.trace = trace or AgentTrace(
|
|
64
|
+
self.trace = trace or AgentTrace(
|
|
65
|
+
self.system_prompt,
|
|
66
|
+
self.__class__.__name__,
|
|
67
|
+
trace_sink=trace_sink,
|
|
68
|
+
agent_instance_id=self.agent_instance_id,
|
|
69
|
+
)
|
|
63
70
|
self.thread_container = thread_container or ThreadContainer(self.system_prompt)
|
|
64
71
|
self.tools = tools or self._create_tools(tool_providers)
|
|
65
72
|
self.llm_config = llm_config or self.create_llm_config(llm_model, reasoning)
|
|
@@ -139,11 +146,9 @@ class BaseAgent(ABC):
|
|
|
139
146
|
if llm_model is None:
|
|
140
147
|
llm_model = self.get_llm_model()
|
|
141
148
|
|
|
142
|
-
provider_type = ProviderType.from_family(llm_model.family)
|
|
143
|
-
|
|
144
149
|
return LLMConfig(
|
|
145
150
|
model=llm_model,
|
|
146
|
-
provider_type=
|
|
151
|
+
provider_type=env_config.get_provider_type(),
|
|
147
152
|
reasoning=reasoning if reasoning is not None else llm_model.reasoning,
|
|
148
153
|
tool_usage=len(self.tools) > 0,
|
|
149
154
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
|
-
from typing import
|
|
2
|
+
from typing import Optional as OptionalType
|
|
3
3
|
|
|
4
4
|
from spaik_sdk.models.llm_model import LLMModel
|
|
5
5
|
from spaik_sdk.models.model_registry import ModelRegistry
|
|
@@ -15,16 +15,6 @@ class EnvConfig:
|
|
|
15
15
|
raise ValueError(f"Environment variable {key} is required but not set")
|
|
16
16
|
return value
|
|
17
17
|
|
|
18
|
-
def get_azure_keys(self) -> Dict[str, str]:
|
|
19
|
-
return {
|
|
20
|
-
"api_key": self.get_key("AZURE_API_KEY"),
|
|
21
|
-
"api_version": self.get_key("AZURE_API_VERSION"),
|
|
22
|
-
"endpoint": self.get_key("AZURE_ENDPOINT"),
|
|
23
|
-
"o3-mini_deployment": self.get_key("AZURE_O3_MINI_DEPLOYMENT", required=False),
|
|
24
|
-
"gpt-4_1_deployment": self.get_key("AZURE_GPT_4_1_DEPLOYMENT", required=False),
|
|
25
|
-
"gpt-4o_deployment": self.get_key("AZURE_GPT_4O_DEPLOYMENT", required=False),
|
|
26
|
-
}
|
|
27
|
-
|
|
28
18
|
def get_default_model(self) -> LLMModel:
|
|
29
19
|
return ModelRegistry.from_name(self.get_key("DEFAULT_MODEL"))
|
|
30
20
|
|
|
@@ -46,8 +36,16 @@ class EnvConfig:
|
|
|
46
36
|
def get_prompt_loader_mode(self) -> PromptLoaderMode:
|
|
47
37
|
return PromptLoaderMode.from_name(self.get_key("PROMPT_LOADER_MODE", "local"))
|
|
48
38
|
|
|
49
|
-
def get_trace_sink_mode(self) -> TraceSinkMode:
|
|
50
|
-
|
|
39
|
+
def get_trace_sink_mode(self) -> OptionalType[TraceSinkMode]:
|
|
40
|
+
"""Get the trace sink mode from environment variable.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
TraceSinkMode.LOCAL if TRACE_SINK_MODE=local,
|
|
44
|
+
TraceSinkMode.NOOP if TRACE_SINK_MODE=noop,
|
|
45
|
+
None if TRACE_SINK_MODE is not set or empty (allows global/default behavior).
|
|
46
|
+
"""
|
|
47
|
+
mode_str = self.get_key("TRACE_SINK_MODE", "", required=False)
|
|
48
|
+
return TraceSinkMode.from_name(mode_str)
|
|
51
49
|
|
|
52
50
|
def get_credentials_provider_type(self) -> str:
|
|
53
51
|
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)
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import Any, Collection, Dict
|
|
3
|
+
|
|
4
|
+
from langchain_core.language_models.chat_models import BaseChatModel
|
|
5
|
+
from langchain_openai import AzureChatOpenAI
|
|
6
|
+
|
|
7
|
+
from spaik_sdk.models.factories.openai_factory import OpenAIModelFactory
|
|
8
|
+
from spaik_sdk.models.llm_config import LLMConfig
|
|
9
|
+
from spaik_sdk.models.llm_model import LLMModel
|
|
10
|
+
from spaik_sdk.models.providers.base_provider import BaseProvider
|
|
11
|
+
|
|
12
|
+
# Model name -> Environment variable for Azure deployment name
|
|
13
|
+
AZURE_DEPLOYMENT_ENV_VARS: Dict[str, str] = {
|
|
14
|
+
"gpt-4.1": "AZURE_GPT_4_1_DEPLOYMENT",
|
|
15
|
+
"gpt-4o": "AZURE_GPT_4O_DEPLOYMENT",
|
|
16
|
+
"o4-mini": "AZURE_O4_MINI_DEPLOYMENT",
|
|
17
|
+
"o4-mini-2025-04-16": "AZURE_O4_MINI_2025_04_16_DEPLOYMENT",
|
|
18
|
+
"gpt-5": "AZURE_GPT_5_DEPLOYMENT",
|
|
19
|
+
"gpt-5-mini": "AZURE_GPT_5_MINI_DEPLOYMENT",
|
|
20
|
+
"gpt-5-nano": "AZURE_GPT_5_NANO_DEPLOYMENT",
|
|
21
|
+
"gpt-5.1": "AZURE_GPT_5_1_DEPLOYMENT",
|
|
22
|
+
"gpt-5.1-codex": "AZURE_GPT_5_1_CODEX_DEPLOYMENT",
|
|
23
|
+
"gpt-5.1-codex-mini": "AZURE_GPT_5_1_CODEX_MINI_DEPLOYMENT",
|
|
24
|
+
"gpt-5.1-codex-max": "AZURE_GPT_5_1_CODEX_MAX_DEPLOYMENT",
|
|
25
|
+
"gpt-5.2": "AZURE_GPT_5_2_DEPLOYMENT",
|
|
26
|
+
"gpt-5.2-pro": "AZURE_GPT_5_2_PRO_DEPLOYMENT",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class AzureProvider(BaseProvider):
|
|
31
|
+
def get_supported_models(self) -> Collection[LLMModel]:
|
|
32
|
+
return OpenAIModelFactory.MODELS
|
|
33
|
+
|
|
34
|
+
def get_model_config(self, config: LLMConfig) -> Dict[str, Any]:
|
|
35
|
+
return {
|
|
36
|
+
"api_key": self._get_required_env("AZURE_API_KEY"),
|
|
37
|
+
"api_version": self._get_required_env("AZURE_API_VERSION"),
|
|
38
|
+
"azure_endpoint": self._get_required_env("AZURE_ENDPOINT"),
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
def create_langchain_model(self, config: LLMConfig, full_config: Dict[str, Any]) -> BaseChatModel:
|
|
42
|
+
full_config["deployment_name"] = self._get_deployment_name(config.model.name)
|
|
43
|
+
return AzureChatOpenAI(**full_config)
|
|
44
|
+
|
|
45
|
+
def _get_deployment_name(self, model_name: str) -> str:
|
|
46
|
+
env_var = AZURE_DEPLOYMENT_ENV_VARS.get(model_name)
|
|
47
|
+
if not env_var:
|
|
48
|
+
raise ValueError(f"Model '{model_name}' not supported on Azure. Add it to AZURE_DEPLOYMENT_ENV_VARS.")
|
|
49
|
+
return os.environ.get(env_var, model_name)
|
|
50
|
+
|
|
51
|
+
def _get_required_env(self, key: str) -> str:
|
|
52
|
+
value = os.environ.get(key)
|
|
53
|
+
if not value:
|
|
54
|
+
raise ValueError(f"Environment variable {key} is required but not set")
|
|
55
|
+
return value
|
|
@@ -178,7 +178,7 @@ class AudioRouterFactory:
|
|
|
178
178
|
try:
|
|
179
179
|
audio_bytes = await file.read()
|
|
180
180
|
filename = file.filename or "audio.webm"
|
|
181
|
-
|
|
181
|
+
|
|
182
182
|
logger.info(f"STT request: language={language}, filename={filename}, size={len(audio_bytes)}")
|
|
183
183
|
|
|
184
184
|
options = STTOptions(
|
|
@@ -54,7 +54,7 @@ class BaseAuthorizer(ABC, Generic[TUser]):
|
|
|
54
54
|
|
|
55
55
|
async def can_read_file(self, user: TUser, file_metadata: "FileMetadata") -> bool:
|
|
56
56
|
"""Check if user has permission to read a file.
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
By default, users can read files they own, or files owned by 'system' (agent-generated).
|
|
59
59
|
"""
|
|
60
60
|
return file_metadata.owner_id == user.get_id() or file_metadata.owner_id == "system"
|
|
@@ -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,4 @@ 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(name, trace_content, self.system_prompt, self.agent_instance_id)
|
|
@@ -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
|