Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitlab/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
DD_REMOTE_CONFIGURATION_REFRESH_INTERVAL: 5s
DD_DOGSTATSD_NON_LOCAL_TRAFFIC: true
testagent:
name: registry.ddbuild.io/images/mirror/dd-apm-test-agent/ddapm-test-agent:v1.34.0
name: registry.ddbuild.io/images/mirror/dd-apm-test-agent/ddapm-test-agent:v1.36.0
alias: testagent
variables:
LOG_LEVEL: ERROR
Expand All @@ -26,6 +26,7 @@
DD_DISABLE_ERROR_RESPONSES: true
ENABLED_CHECKS: trace_content_length,trace_stall,meta_tracer_version_header,trace_count_header,trace_peer_service,trace_dd_service
SNAPSHOT_IGNORED_ATTRS: span_id,trace_id,parent_id,duration,start,metrics.system.pid,metrics.system.process_id,metrics.process_id,meta.runtime-id,meta._dd.p.tid,meta.pathway.hash,metrics._dd.tracer_kr,meta._dd.parent_id,meta.kafka.cluster_id
VCR_PROVIDER_MAP: azure_openai=https://llmobs-test-resource.openai.azure.com/
mongo:
name: registry.ddbuild.io/images/mirror/mongo:6.0.5
alias: mongo
Expand Down
3 changes: 2 additions & 1 deletion ddtrace/llmobs/_integrations/langchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
ANTHROPIC_PROVIDER_NAME = "anthropic"
BEDROCK_PROVIDER_NAME = "amazon_bedrock"
OPENAI_PROVIDER_NAME = "openai"
AZURE_OAI_PROVIDER_NAME = "azure"
VERTEXAI_PROVIDER_NAME = "vertexai"
GEMINI_PROVIDER_NAME = "google_palm"

Expand Down Expand Up @@ -189,7 +190,7 @@ def _llmobs_set_tags(
# only the llm interface for Gemini will get instrumented
elif model_provider.startswith(GEMINI_PROVIDER_NAME) and operation == "llm":
llmobs_integration = "google_generativeai"
elif model_provider.startswith(OPENAI_PROVIDER_NAME):
elif any(provider in model_provider for provider in (OPENAI_PROVIDER_NAME, AZURE_OAI_PROVIDER_NAME)):
llmobs_integration = "openai"
elif operation == "chat" and model_provider.startswith(ANTHROPIC_PROVIDER_NAME):
llmobs_integration = "anthropic"
Expand Down
3 changes: 2 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ services:
volumes:
- ddagent:/tmp/ddagent:rw
testagent:
image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.34.0
image: ghcr.io/datadog/dd-apm-test-agent/ddapm-test-agent:v1.36.0
ports:
- "127.0.0.1:9126:8126"
volumes:
Expand All @@ -131,6 +131,7 @@ services:
- LOG_LEVEL=WARNING
- SNAPSHOT_DIR=/snapshots
- VCR_CASSETTES_DIRECTORY=/cassettes
- VCR_PROVIDER_MAP=azure_openai=https://llmobs-test-resource.openai.azure.com/
- SNAPSHOT_CI=0
- DD_POOL_TRACE_CHECK_FAILURES=true
- DD_DISABLE_ERROR_RESPONSES=true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
fixes:
- |
LLM Observability: Resolves an issue where the ``langchain`` integration would incorrectly mark Azure OpenAI calls as duplicate llm operations even if the ``openai`` integration was enabled.
The ``langchain`` integration will trace Azure OpenAI spans as workflow spans if there is an equivalent llm span from the ``openai`` integration.
29 changes: 29 additions & 0 deletions tests/contrib/langchain/test_langchain_llmobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,11 @@ class TestTraceStructureWithLLMIntegrations(SubprocessTestCase):
DD_API_KEY="<not-a-real-key>",
)

azure_openai_env_config = dict(
OPENAI_API_VERSION="2024-12-01-preview",
AZURE_OPENAI_API_KEY=os.getenv("AZURE_OPENAI_API_KEY", "testing"),
)

anthropic_env_config = dict(
ANTHROPIC_API_KEY=os.getenv("ANTHROPIC_API_KEY", "testing"),
DD_API_KEY="<not-a-real-key>",
Expand Down Expand Up @@ -891,6 +896,11 @@ def _call_openai_llm(OpenAI):
llm = OpenAI(base_url="http://localhost:9126/vcr/openai")
llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?")

@staticmethod
def _call_azure_openai_chat(AzureChatOpenAI):
llm = AzureChatOpenAI(azure_endpoint="http://localhost:9126/vcr/azure_openai", deployment_name="gpt-4.1-mini")
llm.invoke("Can you explain what Descartes meant by 'I think, therefore I am'?")

@staticmethod
def _call_openai_embedding(OpenAIEmbeddings):
embedding = OpenAIEmbeddings(base_url="http://localhost:9126/vcr/openai")
Expand Down Expand Up @@ -924,6 +934,15 @@ def test_llmobs_with_openai_enabled(self):
self._call_openai_llm(OpenAI)
self._assert_trace_structure_from_writer_call_args(["workflow", "llm"])

@run_in_subprocess(env_overrides=azure_openai_env_config)
def test_llmobs_with_openai_enabled_azure(self):
from langchain_openai import AzureChatOpenAI

patch(langchain=True, openai=True)
LLMObs.enable(ml_app="<ml-app-name>", integrations_enabled=False)
self._call_azure_openai_chat(AzureChatOpenAI)
self._assert_trace_structure_from_writer_call_args(["workflow", "llm"])

@run_in_subprocess(env_overrides=openai_env_config)
def test_llmobs_with_openai_enabled_non_ascii_value(self):
"""Regression test to ensure that non-ascii text values for workflow spans are not encoded."""
Expand Down Expand Up @@ -966,6 +985,16 @@ def test_llmobs_with_openai_disabled(self):
self._call_openai_llm(OpenAI)
self._assert_trace_structure_from_writer_call_args(["llm"])

@run_in_subprocess(env_overrides=azure_openai_env_config)
def test_llmobs_with_openai_disabled_azure(self):
from langchain_openai import AzureChatOpenAI

patch(langchain=True)

LLMObs.enable(ml_app="<ml-app-name>", integrations_enabled=False)
self._call_azure_openai_chat(AzureChatOpenAI)
self._assert_trace_structure_from_writer_call_args(["llm"])

@run_in_subprocess(env_overrides=anthropic_env_config)
def test_llmobs_with_anthropic_enabled(self):
from langchain_anthropic import ChatAnthropic
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
interactions:
- request:
body: '{"messages":[{"content":"Can you explain what Descartes meant by ''I think,
therefore I am''?","role":"user"}],"model":"gpt-3.5-turbo","n":1,"stream":false,"temperature":0.7}'
headers:
? !!python/object/apply:multidict._multidict.istr
- Accept
: - application/json
? !!python/object/apply:multidict._multidict.istr
- Accept-Encoding
: - gzip, deflate
? !!python/object/apply:multidict._multidict.istr
- Connection
: - keep-alive
Content-Length:
- '172'
? !!python/object/apply:multidict._multidict.istr
- Content-Type
: - application/json
? !!python/object/apply:multidict._multidict.istr
- User-Agent
: - AzureOpenAI/Python 1.109.1
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Arch
: - arm64
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Async
: - 'false'
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Lang
: - python
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-OS
: - MacOS
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Package-Version
: - 1.109.1
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Runtime
: - CPython
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Runtime-Version
: - 3.11.13
? !!python/object/apply:multidict._multidict.istr
- x-stainless-retry-count
: - '0'
method: POST
uri: https://llmobs-test-resource.openai.azure.com/openai/deployments/gpt-4.1-mini/chat/completions?api-version=2024-12-01-preview
response:
body:
string: "{\"choices\":[{\"content_filter_results\":{\"hate\":{\"filtered\":false,\"severity\":\"safe\"},\"protected_material_code\":{\"filtered\":false,\"detected\":false},\"protected_material_text\":{\"filtered\":false,\"detected\":false},\"self_harm\":{\"filtered\":false,\"severity\":\"safe\"},\"sexual\":{\"filtered\":false,\"severity\":\"safe\"},\"violence\":{\"filtered\":false,\"severity\":\"safe\"}},\"finish_reason\":\"stop\",\"index\":0,\"logprobs\":null,\"message\":{\"annotations\":[],\"content\":\"Certainly!
The phrase **\\\"I think, therefore I am\\\"** (originally in Latin: *Cogito,
ergo sum*) was coined by the French philosopher Ren\xE9 Descartes. It appears
in his work *Discourse on the Method* (1637) and later in *Meditations on
First Philosophy* (1641).\\n\\n### What Descartes Meant:\\n\\n1. **Foundation
of Certainty:** \\n Descartes was searching for an undeniable foundation
for knowledge. He wanted to find something that could not be doubted, as many
beliefs could be mistaken.\\n\\n2. **Method of Doubt:** \\n He began by
doubting everything \u2014 the evidence of the senses, the existence of the
physical world, even mathematical truths \u2014 to see if anything remained
absolutely certain.\\n\\n3. **The Indubitable Truth:** \\n While doubting,
Descartes realized that the very act of doubting implied a thinking subject.
If he is doubting or thinking, then he must exist in some form to be doing
that thinking.\\n\\n4. **\\\"I think, therefore I am\\\":** \\n Therefore,
the one thing he could not doubt was that he exists as a thinking being. The
act of thinking itself proved his own existence. This statement became the
first principle in his philosophy.\\n\\n### In summary:\\n\\nDescartes meant
that the fact that you are consciously thinking is proof of your own existence.
Even if everything else is uncertain or illusory, the very experience of thought
confirms that there is a \\\"self\\\" doing the thinking. It\u2019s a foundational
claim about knowledge and existence.\\n\\nIf you want, I can also explain
how this idea influenced philosophy or its criticisms!\",\"refusal\":null,\"role\":\"assistant\"}}],\"created\":1760724000,\"id\":\"chatcmpl-CRj28rdRKqhnTBkcKCmXlz0vReldy\",\"model\":\"gpt-4.1-mini-2025-04-14\",\"object\":\"chat.completion\",\"prompt_filter_results\":[{\"prompt_index\":0,\"content_filter_results\":{\"hate\":{\"filtered\":false,\"severity\":\"safe\"},\"jailbreak\":{\"filtered\":false,\"detected\":false},\"self_harm\":{\"filtered\":false,\"severity\":\"safe\"},\"sexual\":{\"filtered\":false,\"severity\":\"safe\"},\"violence\":{\"filtered\":false,\"severity\":\"safe\"}}}],\"system_fingerprint\":\"fp_3dcd5944f5\",\"usage\":{\"completion_tokens\":342,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"audio_tokens\":0,\"reasoning_tokens\":0,\"rejected_prediction_tokens\":0},\"prompt_tokens\":24,\"prompt_tokens_details\":{\"audio_tokens\":0,\"cached_tokens\":0},\"total_tokens\":366}}\n"
headers:
Content-Length:
- '2780'
Content-Type:
- application/json
Date:
- Fri, 17 Oct 2025 18:00:13 GMT
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
apim-request-id:
- 381a6525-4f15-481a-8884-dd8ec1b0d6fc
azureml-model-session:
- d213-20251016082839
x-accel-buffering:
- 'no'
x-content-type-options:
- nosniff
x-ms-deployment-name:
- gpt-4.1-mini
x-ms-rai-invoked:
- 'true'
x-ms-region:
- East US 2
x-ratelimit-limit-requests:
- '250'
x-ratelimit-limit-tokens:
- '250000'
x-ratelimit-remaining-requests:
- '248'
x-ratelimit-remaining-tokens:
- '249979'
x-request-id:
- 8e52694a-22a0-42fa-9e2d-54539ebd2113
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
interactions:
- request:
body: '{"messages":[{"content":"Can you explain what Descartes meant by ''I think,
therefore I am''?","role":"user"}],"model":null,"stream":false}'
headers:
? !!python/object/apply:multidict._multidict.istr
- Accept
: - application/json
? !!python/object/apply:multidict._multidict.istr
- Accept-Encoding
: - gzip, deflate, zstd
? !!python/object/apply:multidict._multidict.istr
- Connection
: - keep-alive
Content-Length:
- '137'
? !!python/object/apply:multidict._multidict.istr
- Content-Type
: - application/json
? !!python/object/apply:multidict._multidict.istr
- User-Agent
: - langchain-partner-python-azure-openai
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Arch
: - arm64
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Async
: - 'false'
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Lang
: - python
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-OS
: - MacOS
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Package-Version
: - 1.109.1
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Raw-Response
: - 'true'
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Runtime
: - CPython
? !!python/object/apply:multidict._multidict.istr
- X-Stainless-Runtime-Version
: - 3.11.13
? !!python/object/apply:multidict._multidict.istr
- x-stainless-retry-count
: - '0'
method: POST
uri: https://llmobs-test-resource.openai.azure.com/openai/deployments/gpt-4.1-mini/chat/completions?api-version=2024-12-01-preview
response:
body:
string: "{\"choices\":[{\"content_filter_results\":{\"hate\":{\"filtered\":false,\"severity\":\"safe\"},\"protected_material_code\":{\"filtered\":false,\"detected\":false},\"protected_material_text\":{\"filtered\":false,\"detected\":false},\"self_harm\":{\"filtered\":false,\"severity\":\"safe\"},\"sexual\":{\"filtered\":false,\"severity\":\"safe\"},\"violence\":{\"filtered\":false,\"severity\":\"safe\"}},\"finish_reason\":\"stop\",\"index\":0,\"logprobs\":null,\"message\":{\"annotations\":[],\"content\":\"Certainly!
The phrase **\\\"I think, therefore I am\\\"** (originally in Latin: *Cogito,
ergo sum*) was coined by the French philosopher Ren\xE9 Descartes. It is a
fundamental element of Western philosophy and appears in his work *Meditations
on First Philosophy* (1641).\\n\\n**What Descartes meant:**\\n\\n1. **Starting
point of certainty:** Descartes was seeking an indubitable foundation for
knowledge. He embarked on a method of radical doubt, questioning everything
that could possibly be doubted\u2014his senses, the physical world, even mathematical
truths.\\n\\n2. **The act of thinking proves existence:** In the process of
doubting, he realized that the very act of doubting or thinking implies a
thinker. If he is doubting, then he must be thinking. If he is thinking, then
he must exist. Thus, the fact that he thinks is proof that he exists.\\n\\n3.
**Existence is confirmed through self-awareness:** This statement establishes
the self as a thinking thing (*res cogitans*). Descartes is not saying \\\"I
am a body,\\\" or \\\"I exist in the physical world,\\\" but rather affirming
the existence of the self as a conscious being\u2014one that thinks, doubts,
understands, wills, imagines, and senses.\\n\\n4. **Foundation for knowledge:**
From this fundamental truth, Descartes hoped to build further knowledge about
the world, God, and existence, by basing it on something certain and clear:
the existence of the self as a thinking entity.\\n\\nIn summary, **\\\"I think,
therefore I am\\\" means that the very act of thinking is proof enough of
one's existence and is the first principle of philosophy that cannot be doubted.**\",\"refusal\":null,\"role\":\"assistant\"}}],\"created\":1760724017,\"id\":\"chatcmpl-CRj2PkKuSFXMkYWzr4xxvmv5TtlQ9\",\"model\":\"gpt-4.1-mini-2025-04-14\",\"object\":\"chat.completion\",\"prompt_filter_results\":[{\"prompt_index\":0,\"content_filter_results\":{\"hate\":{\"filtered\":false,\"severity\":\"safe\"},\"jailbreak\":{\"filtered\":false,\"detected\":false},\"self_harm\":{\"filtered\":false,\"severity\":\"safe\"},\"sexual\":{\"filtered\":false,\"severity\":\"safe\"},\"violence\":{\"filtered\":false,\"severity\":\"safe\"}}}],\"system_fingerprint\":\"fp_3dcd5944f5\",\"usage\":{\"completion_tokens\":354,\"completion_tokens_details\":{\"accepted_prediction_tokens\":0,\"audio_tokens\":0,\"reasoning_tokens\":0,\"rejected_prediction_tokens\":0},\"prompt_tokens\":24,\"prompt_tokens_details\":{\"audio_tokens\":0,\"cached_tokens\":0},\"total_tokens\":378}}\n"
headers:
Content-Length:
- '2822'
Content-Type:
- application/json
Date:
- Fri, 17 Oct 2025 18:00:28 GMT
Strict-Transport-Security:
- max-age=31536000; includeSubDomains; preload
apim-request-id:
- ea05cb68-e4e4-4876-b0de-cca3a2bf4c63
azureml-model-session:
- d213-20251016082839
x-accel-buffering:
- 'no'
x-content-type-options:
- nosniff
x-ms-deployment-name:
- gpt-4.1-mini
x-ms-rai-invoked:
- 'true'
x-ms-region:
- East US 2
x-ratelimit-limit-requests:
- '250'
x-ratelimit-limit-tokens:
- '250000'
x-ratelimit-remaining-requests:
- '247'
x-ratelimit-remaining-tokens:
- '249962'
x-request-id:
- 33717a5b-b7e0-48d3-9347-0d8df2e5a48d
status:
code: 200
message: OK
version: 1
Loading