diff --git a/README.md b/README.md index 3724443688..ff4edb2da6 100644 --- a/README.md +++ b/README.md @@ -60,13 +60,20 @@ If you already have OpenTelemetry instrumented, you can just add any of our inst ## 🚀 Getting Started + The easiest way to get started is to use our SDK. For a complete guide, go to our [docs](https://traceloop.com/docs/openllmetry/getting-started-python). -Install the SDK: +Install the SDK with all instrumentations: + +```bash +pip install traceloop-sdk[full] +``` + +Or, to install only the instrumentations you need (for example, OpenAI and LangChain): ```bash -pip install traceloop-sdk +pip install traceloop-sdk[openai,langchain] ``` Then, to start instrumenting your code, just add this line to your code: diff --git a/packages/traceloop-sdk/README.md b/packages/traceloop-sdk/README.md index af482a5318..240fee3181 100644 --- a/packages/traceloop-sdk/README.md +++ b/packages/traceloop-sdk/README.md @@ -2,6 +2,41 @@ Traceloop’s Python SDK allows you to easily start monitoring and debugging your LLM execution. Tracing is done in a non-intrusive way, built on top of OpenTelemetry. You can choose to export the traces to Traceloop, or to your existing observability stack. + +## Installation + + +You can now install only the integrations you need, or groups of them: + +- All LLM providers: + ```bash + pip install traceloop-sdk[llm] + ``` +- All frameworks: + ```bash + pip install traceloop-sdk[frameworks] + ``` +- All vector stores: + ```bash + pip install traceloop-sdk[vectorstores] + ``` +- All cloud providers: + ```bash + pip install traceloop-sdk[cloud] + ``` +- Everything: + ```bash + pip install traceloop-sdk[all] + ``` +- Or any combination, e.g.: + ```bash + pip install traceloop-sdk[openai,chromadb] + ``` + +This keeps your install minimal and fast! + +--- + ```python Traceloop.init(app_name="joke_generation_service") diff --git a/packages/traceloop-sdk/pyproject.toml b/packages/traceloop-sdk/pyproject.toml index a2c9f17761..716e1da813 100644 --- a/packages/traceloop-sdk/pyproject.toml +++ b/packages/traceloop-sdk/pyproject.toml @@ -29,41 +29,6 @@ opentelemetry-api = "^1.28.0" opentelemetry-sdk = "^1.28.0" opentelemetry-exporter-otlp-proto-http = "^1.28.0" opentelemetry-exporter-otlp-proto-grpc = "^1.28.0" -opentelemetry-instrumentation-logging = ">=0.50b0" -opentelemetry-instrumentation-requests = ">=0.50b0" -opentelemetry-instrumentation-sqlalchemy = ">=0.50b0" -opentelemetry-instrumentation-urllib3 = ">=0.50b0" -opentelemetry-instrumentation-threading = ">=0.50b0" -opentelemetry-instrumentation-redis = ">=0.50b0" -opentelemetry-semantic-conventions-ai = "0.4.11" -opentelemetry-instrumentation-mistralai = { path = "../opentelemetry-instrumentation-mistralai", develop = true } -opentelemetry-instrumentation-openai = { path = "../opentelemetry-instrumentation-openai", develop = true } -opentelemetry-instrumentation-openai-agents = { path = "../opentelemetry-instrumentation-openai-agents", develop = true } -opentelemetry-instrumentation-ollama = { path = "../opentelemetry-instrumentation-ollama", develop = true } -opentelemetry-instrumentation-anthropic = { path = "../opentelemetry-instrumentation-anthropic", develop = true } -opentelemetry-instrumentation-cohere = { path = "../opentelemetry-instrumentation-cohere", develop = true } -opentelemetry-instrumentation-crewai = { path = "../opentelemetry-instrumentation-crewai", develop = true } -opentelemetry-instrumentation-google-generativeai = { path = "../opentelemetry-instrumentation-google-generativeai", develop = true } -opentelemetry-instrumentation-pinecone = { path = "../opentelemetry-instrumentation-pinecone", develop = true } -opentelemetry-instrumentation-qdrant = { path = "../opentelemetry-instrumentation-qdrant", develop = true } -opentelemetry-instrumentation-langchain = { path = "../opentelemetry-instrumentation-langchain", develop = true } -opentelemetry-instrumentation-lancedb = { path = "../opentelemetry-instrumentation-lancedb", develop = true } -opentelemetry-instrumentation-chromadb = { path = "../opentelemetry-instrumentation-chromadb", develop = true } -opentelemetry-instrumentation-transformers = { path = "../opentelemetry-instrumentation-transformers", develop = true } -opentelemetry-instrumentation-together = { path = "../opentelemetry-instrumentation-together", develop = true } -opentelemetry-instrumentation-llamaindex = { path = "../opentelemetry-instrumentation-llamaindex", develop = true } -opentelemetry-instrumentation-milvus = { path = "../opentelemetry-instrumentation-milvus", develop = true } -opentelemetry-instrumentation-haystack = { path = "../opentelemetry-instrumentation-haystack", develop = true } -opentelemetry-instrumentation-bedrock = { path = "../opentelemetry-instrumentation-bedrock", develop = true } -opentelemetry-instrumentation-sagemaker = { path = "../opentelemetry-instrumentation-sagemaker", develop = true } -opentelemetry-instrumentation-replicate = { path = "../opentelemetry-instrumentation-replicate", develop = true } -opentelemetry-instrumentation-vertexai = { path = "../opentelemetry-instrumentation-vertexai", develop = true } -opentelemetry-instrumentation-watsonx = { path = "../opentelemetry-instrumentation-watsonx", develop = true } -opentelemetry-instrumentation-weaviate = { path = "../opentelemetry-instrumentation-weaviate", develop = true } -opentelemetry-instrumentation-alephalpha = { path = "../opentelemetry-instrumentation-alephalpha", develop = true } -opentelemetry-instrumentation-marqo = { path = "../opentelemetry-instrumentation-marqo", develop = true } -opentelemetry-instrumentation-groq = { path = "../opentelemetry-instrumentation-groq", develop = true } -opentelemetry-instrumentation-mcp = { path = "../opentelemetry-instrumentation-mcp", develop = true } colorama = "^0.4.6" tenacity = ">=8.2.3, <10.0" pydantic = ">=1" @@ -89,5 +54,146 @@ langchain = "^0.2.5" langchain-openai = "^0.1.15" [build-system] + [tool.poetry.extras] +llm = [ + "../opentelemetry-instrumentation-openai", + "../opentelemetry-instrumentation-anthropic", + "../opentelemetry-instrumentation-cohere", + "../opentelemetry-instrumentation-groq", + "../opentelemetry-instrumentation-ollama", + "../opentelemetry-instrumentation-mistralai", + "../opentelemetry-instrumentation-bedrock", + "../opentelemetry-instrumentation-sagemaker", + "../opentelemetry-instrumentation-replicate", + "../opentelemetry-instrumentation-vertexai", + "../opentelemetry-instrumentation-watsonx", + "../opentelemetry-instrumentation-google-generativeai", + "../opentelemetry-instrumentation-alephalpha" +] +frameworks = [ + "../opentelemetry-instrumentation-langchain", + "../opentelemetry-instrumentation-llamaindex", + "../opentelemetry-instrumentation-crewai", + "../opentelemetry-instrumentation-haystack", + "../opentelemetry-instrumentation-transformers", + "../opentelemetry-instrumentation-together" +] +vectorstores = [ + "../opentelemetry-instrumentation-pinecone", + "../opentelemetry-instrumentation-qdrant", + "../opentelemetry-instrumentation-weaviate", + "../opentelemetry-instrumentation-chromadb", + "../opentelemetry-instrumentation-lancedb", + "../opentelemetry-instrumentation-milvus", + "../opentelemetry-instrumentation-marqo" +] +cloud = [ + "../opentelemetry-instrumentation-bedrock", + "../opentelemetry-instrumentation-vertexai", + "../opentelemetry-instrumentation-sagemaker", + "../opentelemetry-instrumentation-watsonx", + "../opentelemetry-instrumentation-google-generativeai" +] +minimal = [ + "opentelemetry-api", + "opentelemetry-sdk", + "opentelemetry-exporter-otlp-proto-http", + "opentelemetry-exporter-otlp-proto-grpc", + "colorama", + "tenacity", + "pydantic", + "jinja2", + "deprecated", + "posthog", + "aiohttp" +] +all = [ + "llm", + "frameworks", + "vectorstores", + "cloud", + "logging", + "requests", + "sqlalchemy", + "urllib3", + "threading", + "redis", + "semantic-conventions-ai", + "../opentelemetry-instrumentation-openai-agents", + "../opentelemetry-instrumentation-mcp", + "../opentelemetry-instrumentation-groq" +] +logging = ["opentelemetry-instrumentation-logging"] +requests = ["opentelemetry-instrumentation-requests"] +sqlalchemy = ["opentelemetry-instrumentation-sqlalchemy"] +urllib3 = ["opentelemetry-instrumentation-urllib3"] +threading = ["opentelemetry-instrumentation-threading"] +redis = ["opentelemetry-instrumentation-redis"] +semantic-conventions-ai = ["opentelemetry-semantic-conventions-ai"] +mistralai = ["../opentelemetry-instrumentation-mistralai"] +openai = ["../opentelemetry-instrumentation-openai"] +openai-agents = ["../opentelemetry-instrumentation-openai-agents"] +ollama = ["../opentelemetry-instrumentation-ollama"] +anthropic = ["../opentelemetry-instrumentation-anthropic"] +cohere = ["../opentelemetry-instrumentation-cohere"] +crewai = ["../opentelemetry-instrumentation-crewai"] +google-generativeai = ["../opentelemetry-instrumentation-google-generativeai"] +pinecone = ["../opentelemetry-instrumentation-pinecone"] +qdrant = ["../opentelemetry-instrumentation-qdrant"] +langchain = ["../opentelemetry-instrumentation-langchain"] +lancedb = ["../opentelemetry-instrumentation-lancedb"] +chromadb = ["../opentelemetry-instrumentation-chromadb"] +transformers = ["../opentelemetry-instrumentation-transformers"] +together = ["../opentelemetry-instrumentation-together"] +llamaindex = ["../opentelemetry-instrumentation-llamaindex"] +milvus = ["../opentelemetry-instrumentation-milvus"] +haystack = ["../opentelemetry-instrumentation-haystack"] +bedrock = ["../opentelemetry-instrumentation-bedrock"] +sagemaker = ["../opentelemetry-instrumentation-sagemaker"] +replicate = ["../opentelemetry-instrumentation-replicate"] +vertexai = ["../opentelemetry-instrumentation-vertexai"] +watsonx = ["../opentelemetry-instrumentation-watsonx"] +weaviate = ["../opentelemetry-instrumentation-weaviate"] +alephalpha = ["../opentelemetry-instrumentation-alephalpha"] +marqo = ["../opentelemetry-instrumentation-marqo"] +groq = ["../opentelemetry-instrumentation-groq"] +mcp = ["../opentelemetry-instrumentation-mcp"] +full = [ + "opentelemetry-instrumentation-logging", + "opentelemetry-instrumentation-requests", + "opentelemetry-instrumentation-sqlalchemy", + "opentelemetry-instrumentation-urllib3", + "opentelemetry-instrumentation-threading", + "opentelemetry-instrumentation-redis", + "opentelemetry-semantic-conventions-ai", + "../opentelemetry-instrumentation-mistralai", + "../opentelemetry-instrumentation-openai", + "../opentelemetry-instrumentation-openai-agents", + "../opentelemetry-instrumentation-ollama", + "../opentelemetry-instrumentation-anthropic", + "../opentelemetry-instrumentation-cohere", + "../opentelemetry-instrumentation-crewai", + "../opentelemetry-instrumentation-google-generativeai", + "../opentelemetry-instrumentation-pinecone", + "../opentelemetry-instrumentation-qdrant", + "../opentelemetry-instrumentation-langchain", + "../opentelemetry-instrumentation-lancedb", + "../opentelemetry-instrumentation-chromadb", + "../opentelemetry-instrumentation-transformers", + "../opentelemetry-instrumentation-together", + "../opentelemetry-instrumentation-llamaindex", + "../opentelemetry-instrumentation-milvus", + "../opentelemetry-instrumentation-haystack", + "../opentelemetry-instrumentation-bedrock", + "../opentelemetry-instrumentation-sagemaker", + "../opentelemetry-instrumentation-replicate", + "../opentelemetry-instrumentation-vertexai", + "../opentelemetry-instrumentation-watsonx", + "../opentelemetry-instrumentation-weaviate", + "../opentelemetry-instrumentation-alephalpha", + "../opentelemetry-instrumentation-marqo", + "../opentelemetry-instrumentation-groq", + "../opentelemetry-instrumentation-mcp" +] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" diff --git a/packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py b/packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py index b7fab46364..313c7563ea 100644 --- a/packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py +++ b/packages/traceloop-sdk/traceloop/sdk/tracing/tracing.py @@ -559,20 +559,22 @@ def init_instrumentations( def init_openai_instrumentor( should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str], str] ): - try: - if is_package_installed("openai"): - Telemetry().capture("instrumentation:openai:init") - from opentelemetry.instrumentation.openai import OpenAIInstrumentor - instrumentor = OpenAIInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - enrich_assistant=should_enrich_metrics, - get_common_metrics_attributes=metrics_common_attributes, - upload_base64_image=base64_image_uploader, - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + from traceloop.sdk.utils import require_dependency + require_dependency("openai", "llm") + try: + Telemetry().capture("instrumentation:openai:init") + from opentelemetry.instrumentation.openai import OpenAIInstrumentor + + instrumentor = OpenAIInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + enrich_assistant=should_enrich_metrics, + get_common_metrics_attributes=metrics_common_attributes, + upload_base64_image=base64_image_uploader, + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing OpenAI instrumentor: {e}") @@ -583,20 +585,22 @@ def init_openai_instrumentor( def init_anthropic_instrumentor( should_enrich_metrics: bool, base64_image_uploader: Callable[[str, str, str], str] ): - try: - if is_package_installed("anthropic"): - Telemetry().capture("instrumentation:anthropic:init") - from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor - instrumentor = AnthropicInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - enrich_token_usage=should_enrich_metrics, - get_common_metrics_attributes=metrics_common_attributes, - upload_base64_image=base64_image_uploader, - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + from traceloop.sdk.utils import require_dependency + require_dependency("anthropic", "llm") + try: + Telemetry().capture("instrumentation:anthropic:init") + from opentelemetry.instrumentation.anthropic import AnthropicInstrumentor + + instrumentor = AnthropicInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + enrich_token_usage=should_enrich_metrics, + get_common_metrics_attributes=metrics_common_attributes, + upload_base64_image=base64_image_uploader, + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Anthropic instrumentor: {e}") Telemetry().log_exception(e) @@ -604,17 +608,19 @@ def init_anthropic_instrumentor( def init_cohere_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("cohere", "llm") try: - if is_package_installed("cohere"): - Telemetry().capture("instrumentation:cohere:init") - from opentelemetry.instrumentation.cohere import CohereInstrumentor + Telemetry().capture("instrumentation:cohere:init") + from opentelemetry.instrumentation.cohere import CohereInstrumentor - instrumentor = CohereInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = CohereInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Cohere instrumentor: {e}") Telemetry().log_exception(e) @@ -622,17 +628,19 @@ def init_cohere_instrumentor(): def init_pinecone_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("pinecone", "vectorstores") try: - if is_package_installed("pinecone"): - Telemetry().capture("instrumentation:pinecone:init") - from opentelemetry.instrumentation.pinecone import PineconeInstrumentor + Telemetry().capture("instrumentation:pinecone:init") + from opentelemetry.instrumentation.pinecone import PineconeInstrumentor - instrumentor = PineconeInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = PineconeInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Pinecone instrumentor: {e}") Telemetry().log_exception(e) @@ -640,17 +648,19 @@ def init_pinecone_instrumentor(): def init_qdrant_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("qdrant-client", "vectorstores") try: - if is_package_installed("qdrant_client") or is_package_installed("qdrant-client"): - Telemetry().capture("instrumentation:qdrant:init") - from opentelemetry.instrumentation.qdrant import QdrantInstrumentor + Telemetry().capture("instrumentation:qdrant:init") + from opentelemetry.instrumentation.qdrant import QdrantInstrumentor - instrumentor = QdrantInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = QdrantInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Qdrant instrumentor: {e}") Telemetry().log_exception(e) @@ -696,17 +706,19 @@ def init_google_generativeai_instrumentor(): def init_haystack_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("haystack", "frameworks") try: - if is_package_installed("haystack"): - Telemetry().capture("instrumentation:haystack:init") - from opentelemetry.instrumentation.haystack import HaystackInstrumentor + Telemetry().capture("instrumentation:haystack:init") + from opentelemetry.instrumentation.haystack import HaystackInstrumentor - instrumentor = HaystackInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = HaystackInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Haystack instrumentor: {e}") Telemetry().log_exception(e) @@ -714,17 +726,19 @@ def init_haystack_instrumentor(): def init_langchain_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("langchain", "frameworks") try: - if is_package_installed("langchain") or is_package_installed("langgraph"): - Telemetry().capture("instrumentation:langchain:init") - from opentelemetry.instrumentation.langchain import LangchainInstrumentor + Telemetry().capture("instrumentation:langchain:init") + from opentelemetry.instrumentation.langchain import LangchainInstrumentor - instrumentor = LangchainInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = LangchainInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing LangChain instrumentor: {e}") Telemetry().log_exception(e) @@ -788,17 +802,19 @@ def init_transformers_instrumentor(): def init_together_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("together", "frameworks") try: - if is_package_installed("together"): - Telemetry().capture("instrumentation:together:init") - from opentelemetry.instrumentation.together import TogetherAiInstrumentor + Telemetry().capture("instrumentation:together:init") + from opentelemetry.instrumentation.together import TogetherAiInstrumentor - instrumentor = TogetherAiInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = TogetherAiInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing TogetherAI instrumentor: {e}") Telemetry().log_exception(e) @@ -806,17 +822,19 @@ def init_together_instrumentor(): def init_llama_index_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("llama-index", "frameworks") try: - if is_package_installed("llama-index") or is_package_installed("llama_index"): - Telemetry().capture("instrumentation:llamaindex:init") - from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor + Telemetry().capture("instrumentation:llamaindex:init") + from opentelemetry.instrumentation.llamaindex import LlamaIndexInstrumentor - instrumentor = LlamaIndexInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = LlamaIndexInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing LlamaIndex instrumentor: {e}") Telemetry().log_exception(e) @@ -824,17 +842,19 @@ def init_llama_index_instrumentor(): def init_milvus_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("pymilvus", "vectorstores") try: - if is_package_installed("pymilvus"): - Telemetry().capture("instrumentation:milvus:init") - from opentelemetry.instrumentation.milvus import MilvusInstrumentor + Telemetry().capture("instrumentation:milvus:init") + from opentelemetry.instrumentation.milvus import MilvusInstrumentor - instrumentor = MilvusInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = MilvusInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Milvus instrumentor: {e}") Telemetry().log_exception(e) @@ -887,17 +907,19 @@ def init_pymysql_instrumentor(): def init_bedrock_instrumentor(should_enrich_metrics: bool): + + from traceloop.sdk.utils import require_dependency + require_dependency("boto3", "cloud") try: - if is_package_installed("boto3"): - from opentelemetry.instrumentation.bedrock import BedrockInstrumentor + from opentelemetry.instrumentation.bedrock import BedrockInstrumentor - instrumentor = BedrockInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - enrich_token_usage=should_enrich_metrics, - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = BedrockInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + enrich_token_usage=should_enrich_metrics, + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Bedrock instrumentor: {e}") Telemetry().log_exception(e) @@ -905,16 +927,18 @@ def init_bedrock_instrumentor(should_enrich_metrics: bool): def init_sagemaker_instrumentor(should_enrich_metrics: bool): + + from traceloop.sdk.utils import require_dependency + require_dependency("boto3", "cloud") try: - if is_package_installed("boto3"): - from opentelemetry.instrumentation.sagemaker import SageMakerInstrumentor + from opentelemetry.instrumentation.sagemaker import SageMakerInstrumentor - instrumentor = SageMakerInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = SageMakerInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing SageMaker instrumentor: {e}") Telemetry().log_exception(e) @@ -940,17 +964,19 @@ def init_replicate_instrumentor(): def init_vertexai_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("google-cloud-aiplatform", "cloud") try: - if is_package_installed("google-cloud-aiplatform"): - Telemetry().capture("instrumentation:vertexai:init") - from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor + Telemetry().capture("instrumentation:vertexai:init") + from opentelemetry.instrumentation.vertexai import VertexAIInstrumentor - instrumentor = VertexAIInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = VertexAIInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.warning(f"Error initializing Vertex AI instrumentor: {e}") Telemetry().log_exception(e) @@ -958,19 +984,19 @@ def init_vertexai_instrumentor(): def init_watsonx_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("ibm-watsonx-ai", "cloud") try: - if is_package_installed("ibm-watsonx-ai") or is_package_installed( - "ibm_watson_machine_learning" - ): - Telemetry().capture("instrumentation:watsonx:init") - from opentelemetry.instrumentation.watsonx import WatsonxInstrumentor + Telemetry().capture("instrumentation:watsonx:init") + from opentelemetry.instrumentation.watsonx import WatsonxInstrumentor - instrumentor = WatsonxInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = WatsonxInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.warning(f"Error initializing Watsonx instrumentor: {e}") Telemetry().log_exception(e) @@ -978,17 +1004,19 @@ def init_watsonx_instrumentor(): def init_weaviate_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("weaviate", "vectorstores") try: - if is_package_installed("weaviate"): - Telemetry().capture("instrumentation:weaviate:init") - from opentelemetry.instrumentation.weaviate import WeaviateInstrumentor + Telemetry().capture("instrumentation:weaviate:init") + from opentelemetry.instrumentation.weaviate import WeaviateInstrumentor - instrumentor = WeaviateInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = WeaviateInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.warning(f"Error initializing Weaviate instrumentor: {e}") Telemetry().log_exception(e) @@ -1014,17 +1042,19 @@ def init_alephalpha_instrumentor(): def init_marqo_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("marqo", "vectorstores") try: - if is_package_installed("marqo"): - Telemetry().capture("instrumentation:marqo:init") - from opentelemetry.instrumentation.marqo import MarqoInstrumentor + Telemetry().capture("instrumentation:marqo:init") + from opentelemetry.instrumentation.marqo import MarqoInstrumentor - instrumentor = MarqoInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = MarqoInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing marqo instrumentor: {e}") Telemetry().log_exception(e) @@ -1065,17 +1095,19 @@ def init_redis_instrumentor(): def init_groq_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("groq", "llm") try: - if is_package_installed("groq"): - Telemetry().capture("instrumentation:groq:init") - from opentelemetry.instrumentation.groq import GroqInstrumentor + Telemetry().capture("instrumentation:groq:init") + from opentelemetry.instrumentation.groq import GroqInstrumentor - instrumentor = GroqInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = GroqInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing Groq instrumentor: {e}") Telemetry().log_exception(e) @@ -1083,17 +1115,19 @@ def init_groq_instrumentor(): def init_crewai_instrumentor(): + + from traceloop.sdk.utils import require_dependency + require_dependency("crewai", "frameworks") try: - if is_package_installed("crewai"): - Telemetry().capture("instrumentation:crewai:init") - from opentelemetry.instrumentation.crewai import CrewAIInstrumentor + Telemetry().capture("instrumentation:crewai:init") + from opentelemetry.instrumentation.crewai import CrewAIInstrumentor - instrumentor = CrewAIInstrumentor( - exception_logger=lambda e: Telemetry().log_exception(e), - ) - if not instrumentor.is_instrumented_by_opentelemetry: - instrumentor.instrument() - return True + instrumentor = CrewAIInstrumentor( + exception_logger=lambda e: Telemetry().log_exception(e), + ) + if not instrumentor.is_instrumented_by_opentelemetry: + instrumentor.instrument() + return True except Exception as e: logging.error(f"Error initializing CrewAI instrumentor: {e}") Telemetry().log_exception(e) diff --git a/packages/traceloop-sdk/traceloop/sdk/utils/__init__.py b/packages/traceloop-sdk/traceloop/sdk/utils/__init__.py index 36c1aaf976..f0f5b03c9b 100644 --- a/packages/traceloop-sdk/traceloop/sdk/utils/__init__.py +++ b/packages/traceloop-sdk/traceloop/sdk/utils/__init__.py @@ -1,3 +1,4 @@ + def cameltosnake(camel_string: str) -> str: if not camel_string: return "" @@ -6,21 +7,41 @@ def cameltosnake(camel_string: str) -> str: else: return f"{camel_string[0]}{cameltosnake(camel_string[1:])}" - def camel_to_snake(s): if len(s) <= 1: return s.lower() - return cameltosnake(s[0].lower() + s[1:]) - def is_notebook(): try: from IPython import get_ipython - ip = get_ipython() if ip is None: return False return True except Exception: return False + +# --- Optional dependency utility --- +from .package_check import is_package_installed + +def require_dependency(package: str, extra: str = None): + """ + Raise an ImportError with a clear message if a required optional dependency is missing. + + Usage: + from traceloop.sdk.utils import require_dependency + require_dependency("openai", "llm") + + This will raise: + ImportError: Missing optional dependency 'openai'. Please install with 'traceloop-sdk[llm]' to use this feature. + """ + if not is_package_installed(package): + if extra: + raise ImportError( + f"Missing optional dependency '{package}'. Please install with 'traceloop-sdk[{extra}]' to use this feature." + ) + else: + raise ImportError( + f"Missing optional dependency '{package}'. Please install the appropriate extra to use this feature." + )