Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

feat(llmobs): llm datasets and experiments #12918

Open
wants to merge 72 commits into
base: main
Choose a base branch
from
Open

feat(llmobs): llm datasets and experiments #12918

wants to merge 72 commits into from

Conversation

jjxct
Copy link
Contributor

@jjxct jjxct commented Mar 26, 2025

Checklist

  • PR author has checked that all the criteria below are met
  • The PR description includes an overview of the change
  • The PR description articulates the motivation for the change
  • The change includes tests OR the PR description describes a testing strategy
  • The PR description notes risks associated with the change, if any
  • Newly-added code is easy to change
  • The change follows the library release note guidelines
  • The change includes or references documentation updates if necessary
  • Backport labels are set (if applicable)

Reviewer Checklist

  • Reviewer has checked that all the criteria below are met
  • Title is accurate
  • All changes are related to the pull request's stated goal
  • Avoids breaking API changes
  • Testing strategy adequately addresses listed risks
  • Newly-added code is easy to change
  • Release note makes sense to a user of the library
  • If necessary, author has acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment
  • Backport labels are set in a manner that is consistent with the release branch maintenance policy

@jjxct jjxct requested a review from a team as a code owner March 26, 2025 20:54
Copy link
Contributor

CODEOWNERS have been resolved as:

ddtrace/llmobs/experimentation/__init__.py                              @DataDog/ml-observability
ddtrace/llmobs/experimentation/_config.py                               @DataDog/ml-observability
ddtrace/llmobs/experimentation/_dataset.py                              @DataDog/ml-observability
ddtrace/llmobs/experimentation/_decorators.py                           @DataDog/ml-observability
ddtrace/llmobs/experimentation/_experiment.py                           @DataDog/ml-observability
ddtrace/llmobs/experimentation/utils/_exceptions.py                     @DataDog/ml-observability
ddtrace/llmobs/experimentation/utils/_http.py                           @DataDog/ml-observability
ddtrace/llmobs/experimentation/utils/_ui.py                             @DataDog/ml-observability
tests/llmobs/experiments_cassettes/test_dataset_pull.yaml               @DataDog/ml-observability
tests/llmobs/experiments_cassettes/test_dataset_pull_dne.yaml           @DataDog/ml-observability
tests/llmobs/test_experimentation_config.py                             @DataDog/ml-observability
tests/llmobs/test_experimentation_dataset.py                            @DataDog/ml-observability
tests/llmobs/test_experimentation_decorators.py                         @DataDog/ml-observability
tests/llmobs/test_experimentation_experiment.py                         @DataDog/ml-observability
ddtrace/llmobs/_constants.py                                            @DataDog/ml-observability
ddtrace/llmobs/_llmobs.py                                               @DataDog/ml-observability
ddtrace/llmobs/_utils.py                                                @DataDog/ml-observability
ddtrace/llmobs/_writer.py                                               @DataDog/ml-observability
tests/llmobs/test_utils.py                                              @DataDog/ml-observability

# Derived values
def get_api_base_url() -> str:
"""Get the base URL for API requests."""
if get_site().endswith("datadoghq.com"):

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

The string
datadoghq.com
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 6 days ago

To fix the problem, we need to parse the URL and check the hostname properly instead of using a simple string method. This can be done using the urlparse function from the urllib.parse module. By extracting the hostname and then performing the check, we can ensure that the URL is correctly validated.

The best way to fix the problem without changing existing functionality is to modify the get_api_base_url and get_base_url functions to use urlparse for extracting and validating the hostname. This ensures that the check is performed on the actual hostname rather than any part of the URL string.

Suggested changeset 1
ddtrace/llmobs/experimentation/_config.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/ddtrace/llmobs/experimentation/_config.py b/ddtrace/llmobs/experimentation/_config.py
--- a/ddtrace/llmobs/experimentation/_config.py
+++ b/ddtrace/llmobs/experimentation/_config.py
@@ -28,5 +28,8 @@
     """Get the base URL for API requests."""
-    if get_site().endswith("datadoghq.com"):
-        return f"https://api.{get_site()}"
-    elif get_site().endswith("datad0g.com"):
+    from urllib.parse import urlparse
+    site = get_site()
+    hostname = urlparse(f"https://{site}").hostname
+    if hostname and hostname.endswith("datadoghq.com"):
+        return f"https://api.{site}"
+    elif hostname and hostname.endswith("datad0g.com"):
         return "https://dd.datad0g.com"
@@ -36,7 +39,9 @@
     """Get the base URL for the LLM Observability UI."""
-    if get_site() == "datadoghq.com":
+    site = get_site()
+    hostname = urlparse(f"https://{site}").hostname
+    if hostname == "datadoghq.com":
         return "https://app.datadoghq.com"
-    elif get_site().endswith("datadoghq.com"):
-        return f"https://{get_site()}"
-    elif get_site() == "datad0g.com":
+    elif hostname and hostname.endswith("datadoghq.com"):
+        return f"https://{site}"
+    elif hostname == "datad0g.com":
         return "https://dd.datad0g.com"
EOF
@@ -28,5 +28,8 @@
"""Get the base URL for API requests."""
if get_site().endswith("datadoghq.com"):
return f"https://api.{get_site()}"
elif get_site().endswith("datad0g.com"):
from urllib.parse import urlparse
site = get_site()
hostname = urlparse(f"https://{site}").hostname
if hostname and hostname.endswith("datadoghq.com"):
return f"https://api.{site}"
elif hostname and hostname.endswith("datad0g.com"):
return "https://dd.datad0g.com"
@@ -36,7 +39,9 @@
"""Get the base URL for the LLM Observability UI."""
if get_site() == "datadoghq.com":
site = get_site()
hostname = urlparse(f"https://{site}").hostname
if hostname == "datadoghq.com":
return "https://app.datadoghq.com"
elif get_site().endswith("datadoghq.com"):
return f"https://{get_site()}"
elif get_site() == "datad0g.com":
elif hostname and hostname.endswith("datadoghq.com"):
return f"https://{site}"
elif hostname == "datad0g.com":
return "https://dd.datad0g.com"
Copilot is powered by AI and may make mistakes. Always verify output.
"""Get the base URL for API requests."""
if get_site().endswith("datadoghq.com"):
return f"https://api.{get_site()}"
elif get_site().endswith("datad0g.com"):

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

The string
datad0g.com
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 6 days ago

To fix the problem, we need to ensure that the URL is properly parsed and its hostname is checked in a secure manner. Instead of using a simple string comparison, we should use the urlparse function from the urllib.parse module to extract the hostname and then perform the check.

  • Parse the URL using urlparse to extract the hostname.
  • Check if the hostname ends with the allowed domain.
  • Update the get_api_base_url and get_base_url functions to use this approach.
Suggested changeset 1
ddtrace/llmobs/experimentation/_config.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/ddtrace/llmobs/experimentation/_config.py b/ddtrace/llmobs/experimentation/_config.py
--- a/ddtrace/llmobs/experimentation/_config.py
+++ b/ddtrace/llmobs/experimentation/_config.py
@@ -26,7 +26,11 @@
 # Derived values
+from urllib.parse import urlparse
+
 def get_api_base_url() -> str:
     """Get the base URL for API requests."""
-    if get_site().endswith("datadoghq.com"):
-        return f"https://api.{get_site()}"
-    elif get_site().endswith("datad0g.com"):
+    site = get_site()
+    hostname = urlparse(f"https://{site}").hostname
+    if hostname and hostname.endswith("datadoghq.com"):
+        return f"https://api.{hostname}"
+    elif hostname and hostname.endswith("datad0g.com"):
         return "https://dd.datad0g.com"
@@ -36,7 +40,9 @@
     """Get the base URL for the LLM Observability UI."""
-    if get_site() == "datadoghq.com":
+    site = get_site()
+    hostname = urlparse(f"https://{site}").hostname
+    if hostname == "datadoghq.com":
         return "https://app.datadoghq.com"
-    elif get_site().endswith("datadoghq.com"):
-        return f"https://{get_site()}"
-    elif get_site() == "datad0g.com":
+    elif hostname and hostname.endswith("datadoghq.com"):
+        return f"https://{hostname}"
+    elif hostname == "datad0g.com":
         return "https://dd.datad0g.com"
EOF
@@ -26,7 +26,11 @@
# Derived values
from urllib.parse import urlparse

def get_api_base_url() -> str:
"""Get the base URL for API requests."""
if get_site().endswith("datadoghq.com"):
return f"https://api.{get_site()}"
elif get_site().endswith("datad0g.com"):
site = get_site()
hostname = urlparse(f"https://{site}").hostname
if hostname and hostname.endswith("datadoghq.com"):
return f"https://api.{hostname}"
elif hostname and hostname.endswith("datad0g.com"):
return "https://dd.datad0g.com"
@@ -36,7 +40,9 @@
"""Get the base URL for the LLM Observability UI."""
if get_site() == "datadoghq.com":
site = get_site()
hostname = urlparse(f"https://{site}").hostname
if hostname == "datadoghq.com":
return "https://app.datadoghq.com"
elif get_site().endswith("datadoghq.com"):
return f"https://{get_site()}"
elif get_site() == "datad0g.com":
elif hostname and hostname.endswith("datadoghq.com"):
return f"https://{hostname}"
elif hostname == "datad0g.com":
return "https://dd.datad0g.com"
Copilot is powered by AI and may make mistakes. Always verify output.
"""Get the base URL for the LLM Observability UI."""
if get_site() == "datadoghq.com":
return "https://app.datadoghq.com"
elif get_site().endswith("datadoghq.com"):

Check failure

Code scanning / CodeQL

Incomplete URL substring sanitization High

The string
datadoghq.com
may be at an arbitrary position in the sanitized URL.

Copilot Autofix

AI 6 days ago

To fix the problem, we need to ensure that the URL is properly parsed and the hostname is validated correctly. We will use the urlparse function from the urllib.parse module to parse the URL and then check if the hostname ends with the allowed domain. This approach ensures that the check is not bypassed by malicious URLs.

We will modify the get_base_url function to use urlparse for parsing the URL and validating the hostname.

Suggested changeset 1
ddtrace/llmobs/experimentation/_config.py

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/ddtrace/llmobs/experimentation/_config.py b/ddtrace/llmobs/experimentation/_config.py
--- a/ddtrace/llmobs/experimentation/_config.py
+++ b/ddtrace/llmobs/experimentation/_config.py
@@ -1,3 +1,3 @@
 import os
-
+from urllib.parse import urlparse
 from .._llmobs import LLMObs
@@ -36,7 +36,9 @@
     """Get the base URL for the LLM Observability UI."""
-    if get_site() == "datadoghq.com":
+    site = get_site()
+    if site == "datadoghq.com":
         return "https://app.datadoghq.com"
-    elif get_site().endswith("datadoghq.com"):
-        return f"https://{get_site()}"
-    elif get_site() == "datad0g.com":
+    parsed_url = urlparse(f"https://{site}")
+    if parsed_url.hostname and parsed_url.hostname.endswith("datadoghq.com"):
+        return f"https://{site}"
+    elif site == "datad0g.com":
         return "https://dd.datad0g.com"
EOF
@@ -1,3 +1,3 @@
import os

from urllib.parse import urlparse
from .._llmobs import LLMObs
@@ -36,7 +36,9 @@
"""Get the base URL for the LLM Observability UI."""
if get_site() == "datadoghq.com":
site = get_site()
if site == "datadoghq.com":
return "https://app.datadoghq.com"
elif get_site().endswith("datadoghq.com"):
return f"https://{get_site()}"
elif get_site() == "datad0g.com":
parsed_url = urlparse(f"https://{site}")
if parsed_url.hostname and parsed_url.hostname.endswith("datadoghq.com"):
return f"https://{site}"
elif site == "datad0g.com":
return "https://dd.datad0g.com"
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

github-actions bot commented Mar 26, 2025

Bootstrap import analysis

Comparison of import times between this PR and main.

Summary

The average import time in this PR is: 228 ± 2 ms.

The average import time in main is: 230 ± 2 ms.

The import time difference between this PR and main is: -2.17 ± 0.08 ms.

Import time breakdown

The following import paths have shrunk:

ddtrace.auto 1.962 ms (0.86%)
ddtrace.bootstrap.sitecustomize 1.296 ms (0.57%)
ddtrace.bootstrap.preload 1.296 ms (0.57%)
ddtrace.internal.products 1.296 ms (0.57%)
ddtrace.internal.remoteconfig.client 0.632 ms (0.28%)
ddtrace 0.666 ms (0.29%)

@pr-commenter
Copy link

pr-commenter bot commented Mar 26, 2025

Benchmarks

Benchmark execution time: 2025-03-27 19:10:23

Comparing candidate commit a6267c1 in PR branch llm-experiments with baseline commit 03d8cf9 in branch main.

Found 0 performance improvements and 0 performance regressions! Performance is the same for 498 metrics, 2 unstable metrics.

Comment on lines +101 to +104
else:
record = self._data[index].copy()
record.pop("record_id", None)
return record
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Quality Violation

else is not necessary since the if clause has a return (...read more)

If the code in the if branch returns a value, do not have the else branch present.

View in Datadog  Leave us feedback  Documentation

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants