ddtrace 3.11.0rc1__cp312-cp312-win_amd64.whl → 3.11.0rc3__cp312-cp312-win_amd64.whl
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.
Potentially problematic release.
This version of ddtrace might be problematic. Click here for more details.
- ddtrace/_logger.py +5 -6
- ddtrace/_trace/product.py +1 -1
- ddtrace/_trace/sampling_rule.py +25 -33
- ddtrace/_trace/trace_handlers.py +12 -50
- ddtrace/_trace/utils_botocore/span_tags.py +48 -0
- ddtrace/_version.py +2 -2
- ddtrace/appsec/_asm_request_context.py +3 -1
- ddtrace/appsec/_constants.py +7 -0
- ddtrace/appsec/_handlers.py +11 -0
- ddtrace/appsec/_iast/_listener.py +12 -2
- ddtrace/appsec/_processor.py +1 -1
- ddtrace/contrib/integration_registry/registry.yaml +10 -0
- ddtrace/contrib/internal/aiobotocore/patch.py +8 -0
- ddtrace/contrib/internal/avro/__init__.py +17 -0
- ddtrace/contrib/internal/azure_functions/patch.py +23 -12
- ddtrace/contrib/internal/azure_functions/utils.py +14 -0
- ddtrace/contrib/internal/boto/patch.py +14 -0
- ddtrace/contrib/internal/botocore/__init__.py +153 -0
- ddtrace/contrib/internal/botocore/services/bedrock.py +3 -27
- ddtrace/contrib/internal/django/patch.py +31 -8
- ddtrace/contrib/{_freezegun.py → internal/freezegun/__init__.py} +1 -1
- ddtrace/contrib/internal/google_genai/_utils.py +2 -2
- ddtrace/contrib/internal/google_genai/patch.py +7 -7
- ddtrace/contrib/internal/google_generativeai/patch.py +7 -5
- ddtrace/contrib/internal/langchain/patch.py +11 -443
- ddtrace/contrib/internal/langchain/utils.py +0 -26
- ddtrace/contrib/internal/logbook/patch.py +1 -2
- ddtrace/contrib/internal/logging/patch.py +4 -7
- ddtrace/contrib/internal/loguru/patch.py +1 -3
- ddtrace/contrib/internal/openai_agents/patch.py +44 -1
- ddtrace/contrib/internal/protobuf/__init__.py +17 -0
- ddtrace/contrib/internal/pytest/__init__.py +62 -0
- ddtrace/contrib/internal/pytest/_plugin_v2.py +13 -4
- ddtrace/contrib/internal/pytest_bdd/__init__.py +23 -0
- ddtrace/contrib/internal/pytest_benchmark/__init__.py +3 -0
- ddtrace/contrib/internal/structlog/patch.py +2 -4
- ddtrace/contrib/internal/unittest/__init__.py +36 -0
- ddtrace/contrib/internal/vertexai/patch.py +7 -5
- ddtrace/ext/ci.py +20 -0
- ddtrace/ext/git.py +66 -11
- ddtrace/internal/_encoding.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/_encoding.pyi +1 -1
- ddtrace/internal/_rand.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/_tagset.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/_threads.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/ci_visibility/encoder.py +126 -49
- ddtrace/internal/ci_visibility/utils.py +4 -4
- ddtrace/internal/core/__init__.py +5 -2
- ddtrace/internal/datadog/profiling/dd_wrapper-unknown-amd64.dll +0 -0
- ddtrace/internal/datadog/profiling/dd_wrapper-unknown-amd64.lib +0 -0
- ddtrace/internal/datadog/profiling/ddup/_ddup.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/datadog/profiling/ddup/_ddup.cp312-win_amd64.pyd.lib +0 -0
- ddtrace/internal/datadog/profiling/ddup/dd_wrapper-unknown-amd64.dll +0 -0
- ddtrace/internal/datadog/profiling/ddup/dd_wrapper-unknown-amd64.lib +0 -0
- ddtrace/internal/endpoints.py +76 -0
- ddtrace/internal/native/_native.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/schema/processor.py +6 -2
- ddtrace/internal/telemetry/metrics_namespaces.cp312-win_amd64.pyd +0 -0
- ddtrace/internal/telemetry/writer.py +18 -0
- ddtrace/internal/test_visibility/coverage_lines.py +4 -4
- ddtrace/internal/writer/writer.py +24 -11
- ddtrace/llmobs/_constants.py +3 -0
- ddtrace/llmobs/_experiment.py +75 -10
- ddtrace/llmobs/_integrations/bedrock.py +4 -0
- ddtrace/llmobs/_integrations/bedrock_agents.py +5 -1
- ddtrace/llmobs/_integrations/crewai.py +52 -3
- ddtrace/llmobs/_integrations/gemini.py +7 -7
- ddtrace/llmobs/_integrations/google_genai.py +10 -10
- ddtrace/llmobs/_integrations/{google_genai_utils.py → google_utils.py} +103 -7
- ddtrace/llmobs/_integrations/langchain.py +29 -20
- ddtrace/llmobs/_integrations/openai_agents.py +145 -0
- ddtrace/llmobs/_integrations/pydantic_ai.py +67 -26
- ddtrace/llmobs/_integrations/utils.py +68 -158
- ddtrace/llmobs/_integrations/vertexai.py +8 -8
- ddtrace/llmobs/_llmobs.py +83 -14
- ddtrace/llmobs/_telemetry.py +20 -5
- ddtrace/llmobs/_utils.py +27 -0
- ddtrace/profiling/_threading.cp312-win_amd64.pyd +0 -0
- ddtrace/profiling/collector/_memalloc.cp312-win_amd64.pyd +0 -0
- ddtrace/profiling/collector/_task.cp312-win_amd64.pyd +0 -0
- ddtrace/profiling/collector/_traceback.cp312-win_amd64.pyd +0 -0
- ddtrace/profiling/collector/stack.cp312-win_amd64.pyd +0 -0
- ddtrace/settings/_config.py +1 -2
- ddtrace/settings/asm.py +9 -2
- ddtrace/settings/profiling.py +0 -9
- ddtrace/vendor/psutil/_psutil_windows.cp312-win_amd64.pyd +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/METADATA +1 -1
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/RECORD +171 -177
- ddtrace/contrib/_avro.py +0 -17
- ddtrace/contrib/_botocore.py +0 -153
- ddtrace/contrib/_protobuf.py +0 -17
- ddtrace/contrib/_pytest.py +0 -62
- ddtrace/contrib/_pytest_bdd.py +0 -23
- ddtrace/contrib/_pytest_benchmark.py +0 -3
- ddtrace/contrib/_unittest.py +0 -36
- /ddtrace/contrib/{_aiobotocore.py → internal/aiobotocore/__init__.py} +0 -0
- /ddtrace/contrib/{_aiohttp_jinja2.py → internal/aiohttp_jinja2/__init__.py} +0 -0
- /ddtrace/contrib/{_aiomysql.py → internal/aiomysql/__init__.py} +0 -0
- /ddtrace/contrib/{_aiopg.py → internal/aiopg/__init__.py} +0 -0
- /ddtrace/contrib/{_aioredis.py → internal/aioredis/__init__.py} +0 -0
- /ddtrace/contrib/{_algoliasearch.py → internal/algoliasearch/__init__.py} +0 -0
- /ddtrace/contrib/{_anthropic.py → internal/anthropic/__init__.py} +0 -0
- /ddtrace/contrib/{_aredis.py → internal/aredis/__init__.py} +0 -0
- /ddtrace/contrib/{_asyncio.py → internal/asyncio/__init__.py} +0 -0
- /ddtrace/contrib/{_asyncpg.py → internal/asyncpg/__init__.py} +0 -0
- /ddtrace/contrib/{_aws_lambda.py → internal/aws_lambda/__init__.py} +0 -0
- /ddtrace/contrib/{_azure_functions.py → internal/azure_functions/__init__.py} +0 -0
- /ddtrace/contrib/{_azure_servicebus.py → internal/azure_servicebus/__init__.py} +0 -0
- /ddtrace/contrib/{_boto.py → internal/boto/__init__.py} +0 -0
- /ddtrace/contrib/{_cassandra.py → internal/cassandra/__init__.py} +0 -0
- /ddtrace/contrib/{_consul.py → internal/consul/__init__.py} +0 -0
- /ddtrace/contrib/{_coverage.py → internal/coverage/__init__.py} +0 -0
- /ddtrace/contrib/{_crewai.py → internal/crewai/__init__.py} +0 -0
- /ddtrace/contrib/{_django.py → internal/django/__init__.py} +0 -0
- /ddtrace/contrib/{_dogpile_cache.py → internal/dogpile_cache/__init__.py} +0 -0
- /ddtrace/contrib/{_dramatiq.py → internal/dramatiq/__init__.py} +0 -0
- /ddtrace/contrib/{_elasticsearch.py → internal/elasticsearch/__init__.py} +0 -0
- /ddtrace/contrib/{_fastapi.py → internal/fastapi/__init__.py} +0 -0
- /ddtrace/contrib/{_flask.py → internal/flask/__init__.py} +0 -0
- /ddtrace/contrib/{_futures.py → internal/futures/__init__.py} +0 -0
- /ddtrace/contrib/{_gevent.py → internal/gevent/__init__.py} +0 -0
- /ddtrace/contrib/{_google_genai.py → internal/google_genai/__init__.py} +0 -0
- /ddtrace/contrib/{_google_generativeai.py → internal/google_generativeai/__init__.py} +0 -0
- /ddtrace/contrib/{_graphql.py → internal/graphql/__init__.py} +0 -0
- /ddtrace/contrib/{_grpc.py → internal/grpc/__init__.py} +0 -0
- /ddtrace/contrib/{_gunicorn.py → internal/gunicorn/__init__.py} +0 -0
- /ddtrace/contrib/{_httplib.py → internal/httplib/__init__.py} +0 -0
- /ddtrace/contrib/{_httpx.py → internal/httpx/__init__.py} +0 -0
- /ddtrace/contrib/{_jinja2.py → internal/jinja2/__init__.py} +0 -0
- /ddtrace/contrib/{_kafka.py → internal/kafka/__init__.py} +0 -0
- /ddtrace/contrib/{_kombu.py → internal/kombu/__init__.py} +0 -0
- /ddtrace/contrib/{_langchain.py → internal/langchain/__init__.py} +0 -0
- /ddtrace/contrib/{_langgraph.py → internal/langgraph/__init__.py} +0 -0
- /ddtrace/contrib/{_litellm.py → internal/litellm/__init__.py} +0 -0
- /ddtrace/contrib/{_logbook.py → internal/logbook/__init__.py} +0 -0
- /ddtrace/contrib/{_logging.py → internal/logging/__init__.py} +0 -0
- /ddtrace/contrib/{_loguru.py → internal/loguru/__init__.py} +0 -0
- /ddtrace/contrib/{_mako.py → internal/mako/__init__.py} +0 -0
- /ddtrace/contrib/{_mariadb.py → internal/mariadb/__init__.py} +0 -0
- /ddtrace/contrib/{_mcp.py → internal/mcp/__init__.py} +0 -0
- /ddtrace/contrib/{_molten.py → internal/molten/__init__.py} +0 -0
- /ddtrace/contrib/{_mongoengine.py → internal/mongoengine/__init__.py} +0 -0
- /ddtrace/contrib/{_mysql.py → internal/mysql/__init__.py} +0 -0
- /ddtrace/contrib/{_mysqldb.py → internal/mysqldb/__init__.py} +0 -0
- /ddtrace/contrib/{_openai.py → internal/openai/__init__.py} +0 -0
- /ddtrace/contrib/{_openai_agents.py → internal/openai_agents/__init__.py} +0 -0
- /ddtrace/contrib/{_psycopg.py → internal/psycopg/__init__.py} +0 -0
- /ddtrace/contrib/{_pydantic_ai.py → internal/pydantic_ai/__init__.py} +0 -0
- /ddtrace/contrib/{_pymemcache.py → internal/pymemcache/__init__.py} +0 -0
- /ddtrace/contrib/{_pymongo.py → internal/pymongo/__init__.py} +0 -0
- /ddtrace/contrib/{_pymysql.py → internal/pymysql/__init__.py} +0 -0
- /ddtrace/contrib/{_pynamodb.py → internal/pynamodb/__init__.py} +0 -0
- /ddtrace/contrib/{_pyodbc.py → internal/pyodbc/__init__.py} +0 -0
- /ddtrace/contrib/{_redis.py → internal/redis/__init__.py} +0 -0
- /ddtrace/contrib/{_rediscluster.py → internal/rediscluster/__init__.py} +0 -0
- /ddtrace/contrib/{_rq.py → internal/rq/__init__.py} +0 -0
- /ddtrace/contrib/{_sanic.py → internal/sanic/__init__.py} +0 -0
- /ddtrace/contrib/{_selenium.py → internal/selenium/__init__.py} +0 -0
- /ddtrace/contrib/{_snowflake.py → internal/snowflake/__init__.py} +0 -0
- /ddtrace/contrib/{_sqlite3.py → internal/sqlite3/__init__.py} +0 -0
- /ddtrace/contrib/{_starlette.py → internal/starlette/__init__.py} +0 -0
- /ddtrace/contrib/{_structlog.py → internal/structlog/__init__.py} +0 -0
- /ddtrace/contrib/{_subprocess.py → internal/subprocess/__init__.py} +0 -0
- /ddtrace/contrib/{_urllib.py → internal/urllib/__init__.py} +0 -0
- /ddtrace/contrib/{_urllib3.py → internal/urllib3/__init__.py} +0 -0
- /ddtrace/contrib/{_vertexai.py → internal/vertexai/__init__.py} +0 -0
- /ddtrace/contrib/{_vertica.py → internal/vertica/__init__.py} +0 -0
- /ddtrace/contrib/{_webbrowser.py → internal/webbrowser/__init__.py} +0 -0
- /ddtrace/contrib/{_yaaredis.py → internal/yaaredis/__init__.py} +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/WHEEL +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/entry_points.txt +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.Apache +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.BSD3 +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/NOTICE +0 -0
- {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/top_level.txt +0 -0
ddtrace/llmobs/_llmobs.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import csv
|
1
2
|
from dataclasses import dataclass
|
2
3
|
from dataclasses import field
|
3
4
|
import inspect
|
@@ -43,11 +44,14 @@ from ddtrace.internal.utils.formats import format_trace_id
|
|
43
44
|
from ddtrace.internal.utils.formats import parse_tags_str
|
44
45
|
from ddtrace.llmobs import _constants as constants
|
45
46
|
from ddtrace.llmobs import _telemetry as telemetry
|
47
|
+
from ddtrace.llmobs._constants import AGENT_MANIFEST
|
46
48
|
from ddtrace.llmobs._constants import ANNOTATIONS_CONTEXT_ID
|
47
49
|
from ddtrace.llmobs._constants import DECORATOR
|
50
|
+
from ddtrace.llmobs._constants import DEFAULT_PROJECT_NAME
|
48
51
|
from ddtrace.llmobs._constants import DISPATCH_ON_LLM_TOOL_CHOICE
|
49
52
|
from ddtrace.llmobs._constants import DISPATCH_ON_TOOL_CALL
|
50
53
|
from ddtrace.llmobs._constants import DISPATCH_ON_TOOL_CALL_OUTPUT_USED
|
54
|
+
from ddtrace.llmobs._constants import EXPERIMENT_CSV_FIELD_MAX_SIZE
|
51
55
|
from ddtrace.llmobs._constants import EXPERIMENT_EXPECTED_OUTPUT
|
52
56
|
from ddtrace.llmobs._constants import EXPERIMENT_ID_KEY
|
53
57
|
from ddtrace.llmobs._constants import INPUT_DOCUMENTS
|
@@ -77,8 +81,8 @@ from ddtrace.llmobs._constants import TAGS
|
|
77
81
|
from ddtrace.llmobs._context import LLMObsContextProvider
|
78
82
|
from ddtrace.llmobs._evaluators.runner import EvaluatorRunner
|
79
83
|
from ddtrace.llmobs._experiment import Dataset
|
84
|
+
from ddtrace.llmobs._experiment import DatasetRecord
|
80
85
|
from ddtrace.llmobs._experiment import DatasetRecordInputType
|
81
|
-
from ddtrace.llmobs._experiment import DatasetRecordRaw as DatasetRecord
|
82
86
|
from ddtrace.llmobs._experiment import Experiment
|
83
87
|
from ddtrace.llmobs._experiment import ExperimentConfigType
|
84
88
|
from ddtrace.llmobs._experiment import JSONType
|
@@ -168,7 +172,7 @@ class LLMObs(Service):
|
|
168
172
|
_instance = None # type: LLMObs
|
169
173
|
enabled = False
|
170
174
|
_app_key: str = os.getenv("DD_APP_KEY", "")
|
171
|
-
_project_name: str = os.getenv("DD_LLMOBS_PROJECT_NAME",
|
175
|
+
_project_name: str = os.getenv("DD_LLMOBS_PROJECT_NAME", DEFAULT_PROJECT_NAME)
|
172
176
|
|
173
177
|
def __init__(
|
174
178
|
self,
|
@@ -253,7 +257,10 @@ class LLMObs(Service):
|
|
253
257
|
if span_kind in ("llm", "embedding") and span._get_ctx_item(MODEL_NAME) is not None:
|
254
258
|
meta["model_name"] = span._get_ctx_item(MODEL_NAME)
|
255
259
|
meta["model_provider"] = (span._get_ctx_item(MODEL_PROVIDER) or "custom").lower()
|
256
|
-
|
260
|
+
metadata = span._get_ctx_item(METADATA) or {}
|
261
|
+
if span_kind == "agent" and span._get_ctx_item(AGENT_MANIFEST) is not None:
|
262
|
+
metadata["agent_manifest"] = span._get_ctx_item(AGENT_MANIFEST)
|
263
|
+
meta["metadata"] = metadata
|
257
264
|
|
258
265
|
input_type: Literal["value", "messages", ""] = ""
|
259
266
|
output_type: Literal["value", "messages", ""] = ""
|
@@ -509,12 +516,16 @@ class LLMObs(Service):
|
|
509
516
|
config._dd_site = site or config._dd_site
|
510
517
|
config._dd_api_key = api_key or config._dd_api_key
|
511
518
|
cls._app_key = app_key or cls._app_key
|
512
|
-
cls._project_name = project_name or cls._project_name
|
519
|
+
cls._project_name = project_name or cls._project_name or DEFAULT_PROJECT_NAME
|
513
520
|
config.env = env or config.env
|
514
521
|
config.service = service or config.service
|
515
522
|
config._llmobs_ml_app = ml_app or config._llmobs_ml_app
|
516
523
|
config._llmobs_instrumented_proxy_urls = instrumented_proxy_urls or config._llmobs_instrumented_proxy_urls
|
517
524
|
|
525
|
+
# FIXME: workaround to prevent noisy logs when using the experiments feature
|
526
|
+
if config._dd_api_key and cls._app_key and os.environ.get("DD_TRACE_ENABLED", "").lower() not in ["true", "1"]:
|
527
|
+
ddtrace.tracer.enabled = False
|
528
|
+
|
518
529
|
error = None
|
519
530
|
start_ns = time.time_ns()
|
520
531
|
try:
|
@@ -596,6 +607,67 @@ class LLMObs(Service):
|
|
596
607
|
ds.push()
|
597
608
|
return ds
|
598
609
|
|
610
|
+
@classmethod
|
611
|
+
def create_dataset_from_csv(
|
612
|
+
cls,
|
613
|
+
csv_path: str,
|
614
|
+
dataset_name: str,
|
615
|
+
input_data_columns: List[str],
|
616
|
+
expected_output_columns: List[str],
|
617
|
+
metadata_columns: List[str] = [],
|
618
|
+
csv_delimiter: str = ",",
|
619
|
+
description="",
|
620
|
+
) -> Dataset:
|
621
|
+
ds = cls._instance._dne_client.dataset_create(dataset_name, description)
|
622
|
+
|
623
|
+
# Store the original field size limit to restore it later
|
624
|
+
original_field_size_limit = csv.field_size_limit()
|
625
|
+
|
626
|
+
csv.field_size_limit(EXPERIMENT_CSV_FIELD_MAX_SIZE) # 10mb
|
627
|
+
|
628
|
+
try:
|
629
|
+
with open(csv_path, mode="r") as csvfile:
|
630
|
+
content = csvfile.readline().strip()
|
631
|
+
if not content:
|
632
|
+
raise ValueError("CSV file appears to be empty or header is missing.")
|
633
|
+
|
634
|
+
csvfile.seek(0)
|
635
|
+
|
636
|
+
rows = csv.DictReader(csvfile, delimiter=csv_delimiter)
|
637
|
+
|
638
|
+
if rows.fieldnames is None:
|
639
|
+
raise ValueError("CSV file appears to be empty or header is missing.")
|
640
|
+
|
641
|
+
header_columns = rows.fieldnames
|
642
|
+
missing_input_columns = [col for col in input_data_columns if col not in header_columns]
|
643
|
+
missing_output_columns = [col for col in expected_output_columns if col not in header_columns]
|
644
|
+
missing_metadata_columns = [col for col in metadata_columns if col not in metadata_columns]
|
645
|
+
|
646
|
+
if any(col not in header_columns for col in input_data_columns):
|
647
|
+
raise ValueError(f"Input columns not found in CSV header: {missing_input_columns}")
|
648
|
+
if any(col not in header_columns for col in expected_output_columns):
|
649
|
+
raise ValueError(f"Expected output columns not found in CSV header: {missing_output_columns}")
|
650
|
+
if any(col not in header_columns for col in metadata_columns):
|
651
|
+
raise ValueError(f"Metadata columns not found in CSV header: {missing_metadata_columns}")
|
652
|
+
|
653
|
+
for row in rows:
|
654
|
+
ds.append(
|
655
|
+
DatasetRecord(
|
656
|
+
input_data={col: row[col] for col in input_data_columns},
|
657
|
+
expected_output={col: row[col] for col in expected_output_columns},
|
658
|
+
metadata={col: row[col] for col in metadata_columns},
|
659
|
+
record_id="",
|
660
|
+
)
|
661
|
+
)
|
662
|
+
|
663
|
+
finally:
|
664
|
+
# Always restore the original field size limit
|
665
|
+
csv.field_size_limit(original_field_size_limit)
|
666
|
+
|
667
|
+
if len(ds) > 0:
|
668
|
+
ds.push()
|
669
|
+
return ds
|
670
|
+
|
599
671
|
@classmethod
|
600
672
|
def _delete_dataset(cls, dataset_id: str) -> None:
|
601
673
|
return cls._instance._dne_client.dataset_delete(dataset_id)
|
@@ -608,21 +680,19 @@ class LLMObs(Service):
|
|
608
680
|
dataset: Dataset,
|
609
681
|
evaluators: List[Callable[[DatasetRecordInputType, JSONType, JSONType], JSONType]],
|
610
682
|
description: str = "",
|
611
|
-
|
612
|
-
|
683
|
+
tags: Optional[Dict[str, str]] = None,
|
684
|
+
config: Optional[ExperimentConfigType] = None,
|
613
685
|
) -> Experiment:
|
614
686
|
"""Initializes an Experiment to run a task on a Dataset and evaluators.
|
615
687
|
|
616
688
|
:param name: The name of the experiment.
|
617
|
-
:param task: The task function to run. Must accept
|
689
|
+
:param task: The task function to run. Must accept parameters ``input_data`` and ``config``.
|
618
690
|
:param dataset: The dataset to run the experiment on, created with LLMObs.pull/create_dataset().
|
619
691
|
:param evaluators: A list of evaluator functions to evaluate the task output.
|
620
692
|
Must accept parameters ``input_data``, ``output_data``, and ``expected_output``.
|
621
693
|
:param description: A description of the experiment.
|
622
|
-
:param
|
623
|
-
|
624
|
-
or `LLMObs.enable(project_name=...)`.
|
625
|
-
:param tags: A list of string tags to associate with the experiment.
|
694
|
+
:param tags: A dictionary of string key-value tag pairs to associate with the experiment.
|
695
|
+
:param config: A configuration dictionary describing the experiment.
|
626
696
|
"""
|
627
697
|
if not callable(task):
|
628
698
|
raise TypeError("task must be a callable function.")
|
@@ -640,16 +710,15 @@ class LLMObs(Service):
|
|
640
710
|
required_params = ("input_data", "output_data", "expected_output")
|
641
711
|
if not all(param in params for param in required_params):
|
642
712
|
raise TypeError("Evaluator function must have parameters {}.".format(required_params))
|
643
|
-
if project_name is None:
|
644
|
-
project_name = cls._project_name
|
645
713
|
return Experiment(
|
646
714
|
name,
|
647
715
|
task,
|
648
716
|
dataset,
|
649
717
|
evaluators,
|
650
|
-
project_name=
|
718
|
+
project_name=cls._project_name,
|
651
719
|
tags=tags,
|
652
720
|
description=description,
|
721
|
+
config=config,
|
653
722
|
_llmobs_instance=cls._instance,
|
654
723
|
)
|
655
724
|
|
ddtrace/llmobs/_telemetry.py
CHANGED
@@ -36,16 +36,17 @@ class LLMObsTelemetryMetrics:
|
|
36
36
|
USER_PROCESSOR_CALLED = "user_processor_called"
|
37
37
|
|
38
38
|
|
39
|
-
def
|
40
|
-
|
41
|
-
if not
|
39
|
+
def _find_tag_value_from_tags(tags, tag_key):
|
40
|
+
tag_string = next((tag for tag in tags if tag.startswith(f"{tag_key}:")), None)
|
41
|
+
if not tag_string:
|
42
42
|
return None
|
43
|
-
return
|
43
|
+
return tag_string.split(f"{tag_key}:")[-1]
|
44
44
|
|
45
45
|
|
46
46
|
def _get_tags_from_span_event(event: LLMObsSpanEvent):
|
47
47
|
span_kind = event.get("meta", {}).get("span.kind", "")
|
48
|
-
integration =
|
48
|
+
integration = _find_tag_value_from_tags(event.get("tags", []), "integration")
|
49
|
+
ml_app = _find_tag_value_from_tags(event.get("tags", []), "ml_app")
|
49
50
|
autoinstrumented = integration is not None
|
50
51
|
error = event.get("status") == "error"
|
51
52
|
return [
|
@@ -53,6 +54,7 @@ def _get_tags_from_span_event(event: LLMObsSpanEvent):
|
|
53
54
|
("autoinstrumented", str(int(autoinstrumented))),
|
54
55
|
("error", str(int(error))),
|
55
56
|
("integration", integration if integration else "N/A"),
|
57
|
+
("ml_app", ml_app if ml_app else "N/A"),
|
56
58
|
]
|
57
59
|
|
58
60
|
|
@@ -125,6 +127,19 @@ def record_span_created(span: Span):
|
|
125
127
|
)
|
126
128
|
|
127
129
|
|
130
|
+
def record_bedrock_agent_span_event_created(span_event: LLMObsSpanEvent):
|
131
|
+
is_root_span = span_event["parent_id"] == ROOT_PARENT_ID
|
132
|
+
has_session_id = any("session_id" in tag for tag in span_event["tags"])
|
133
|
+
tags = _get_tags_from_span_event(span_event)
|
134
|
+
tags.extend([("has_session_id", str(int(has_session_id))), ("is_root_span", str(int(is_root_span)))])
|
135
|
+
model_provider = span_event["meta"]["metadata"].get("model_provider")
|
136
|
+
if model_provider is not None:
|
137
|
+
tags.append(("model_provider", model_provider))
|
138
|
+
telemetry_writer.add_count_metric(
|
139
|
+
namespace=TELEMETRY_NAMESPACE.MLOBS, name=LLMObsTelemetryMetrics.SPAN_FINISHED, value=1, tags=tuple(tags)
|
140
|
+
)
|
141
|
+
|
142
|
+
|
128
143
|
def record_span_event_raw_size(event: LLMObsSpanEvent, raw_event_size: int):
|
129
144
|
telemetry_writer.add_distribution_metric(
|
130
145
|
namespace=TELEMETRY_NAMESPACE.MLOBS,
|
ddtrace/llmobs/_utils.py
CHANGED
@@ -1,4 +1,6 @@
|
|
1
|
+
from dataclasses import asdict
|
1
2
|
from dataclasses import dataclass
|
3
|
+
from dataclasses import is_dataclass
|
2
4
|
import json
|
3
5
|
from typing import Dict
|
4
6
|
from typing import List
|
@@ -215,6 +217,25 @@ def safe_json(obj, ensure_ascii=True):
|
|
215
217
|
log.error("Failed to serialize object to JSON.", exc_info=True)
|
216
218
|
|
217
219
|
|
220
|
+
def load_data_value(value):
|
221
|
+
if isinstance(value, (list, tuple, set)):
|
222
|
+
return [load_data_value(item) for item in value]
|
223
|
+
elif isinstance(value, dict):
|
224
|
+
return {str(k): load_data_value(v) for k, v in value.items()}
|
225
|
+
elif hasattr(value, "model_dump"):
|
226
|
+
return value.model_dump()
|
227
|
+
elif is_dataclass(value):
|
228
|
+
return asdict(value)
|
229
|
+
elif isinstance(value, (int, float, str, bool)) or value is None:
|
230
|
+
return value
|
231
|
+
else:
|
232
|
+
value_str = safe_json(value)
|
233
|
+
try:
|
234
|
+
return json.loads(value_str)
|
235
|
+
except json.JSONDecodeError:
|
236
|
+
return value_str
|
237
|
+
|
238
|
+
|
218
239
|
def add_span_link(span: Span, span_id: str, trace_id: str, from_io: str, to_io: str) -> None:
|
219
240
|
current_span_links = span._get_ctx_item(SPAN_LINKS) or []
|
220
241
|
current_span_links.append(
|
@@ -234,6 +255,12 @@ def enforce_message_role(messages: List[Dict[str, str]]) -> List[Dict[str, str]]
|
|
234
255
|
return messages
|
235
256
|
|
236
257
|
|
258
|
+
def convert_tags_dict_to_list(tags: Dict[str, str]) -> List[str]:
|
259
|
+
if not tags:
|
260
|
+
return []
|
261
|
+
return [f"{key}:{value}" for key, value in tags.items()]
|
262
|
+
|
263
|
+
|
237
264
|
@dataclass
|
238
265
|
class ToolCall:
|
239
266
|
"""
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
Binary file
|
ddtrace/settings/_config.py
CHANGED
@@ -17,7 +17,6 @@ from ddtrace.internal.telemetry import telemetry_writer
|
|
17
17
|
from ddtrace.internal.telemetry import validate_otel_envs
|
18
18
|
from ddtrace.internal.utils.cache import cachedmethod
|
19
19
|
|
20
|
-
from .._logger import LogInjectionState
|
21
20
|
from .._logger import get_log_injection_state
|
22
21
|
from ..internal import gitmetadata
|
23
22
|
from ..internal.constants import _PROPAGATION_BEHAVIOR_DEFAULT
|
@@ -378,7 +377,7 @@ def _default_config() -> Dict[str, _ConfigItem]:
|
|
378
377
|
modifier=str,
|
379
378
|
),
|
380
379
|
"_logs_injection": _ConfigItem(
|
381
|
-
default=
|
380
|
+
default=True,
|
382
381
|
envs=["DD_LOGS_INJECTION"],
|
383
382
|
modifier=get_log_injection_state,
|
384
383
|
),
|
ddtrace/settings/asm.py
CHANGED
@@ -16,6 +16,7 @@ from ddtrace.appsec._constants import TELEMETRY_INFORMATION_NAME
|
|
16
16
|
from ddtrace.constants import APPSEC_ENV
|
17
17
|
from ddtrace.ext import SpanTypes
|
18
18
|
from ddtrace.internal import core
|
19
|
+
from ddtrace.internal.endpoints import HttpEndPointsCollection
|
19
20
|
from ddtrace.internal.serverless import in_aws_lambda
|
20
21
|
from ddtrace.settings._config import config as tracer_config
|
21
22
|
from ddtrace.settings._core import DDConfig
|
@@ -59,6 +60,9 @@ def build_libddwaf_filename() -> str:
|
|
59
60
|
return os.path.join(_DIRNAME, "appsec", "_ddwaf", "libddwaf", ARCHITECTURE, "lib", "libddwaf." + FILE_EXTENSION)
|
60
61
|
|
61
62
|
|
63
|
+
endpoint_collection = HttpEndPointsCollection()
|
64
|
+
|
65
|
+
|
62
66
|
class ASMConfig(DDConfig):
|
63
67
|
_asm_enabled = DDConfig.var(bool, APPSEC_ENV, default=False)
|
64
68
|
_asm_enabled_origin = APPSEC.ENABLED_ORIGIN_UNKNOWN
|
@@ -92,6 +96,10 @@ class ASMConfig(DDConfig):
|
|
92
96
|
_api_security_enabled = DDConfig.var(bool, API_SECURITY.ENV_VAR_ENABLED, default=True)
|
93
97
|
_api_security_sample_delay = DDConfig.var(float, API_SECURITY.SAMPLE_DELAY, default=30.0)
|
94
98
|
_api_security_parse_response_body = DDConfig.var(bool, API_SECURITY.PARSE_RESPONSE_BODY, default=True)
|
99
|
+
_api_security_endpoint_collection = DDConfig.var(bool, API_SECURITY.ENDPOINT_COLLECTION, default=True)
|
100
|
+
_api_security_endpoint_collection_limit = DDConfig.var(
|
101
|
+
int, API_SECURITY.ENDPOINT_COLLECTION_LIMIT, default=DEFAULT.ENDPOINT_COLLECTION_LIMIT
|
102
|
+
)
|
95
103
|
|
96
104
|
# internal state of the API security Manager service.
|
97
105
|
# updated in API Manager enable/disable
|
@@ -246,9 +254,8 @@ class ASMConfig(DDConfig):
|
|
246
254
|
self._asm_processed_span_types.add(SpanTypes.SERVERLESS)
|
247
255
|
self._asm_http_span_types.add(SpanTypes.SERVERLESS)
|
248
256
|
|
249
|
-
#
|
257
|
+
# Disable all features that are not supported in Lambda
|
250
258
|
tracer_config._remote_config_enabled = False
|
251
|
-
self._api_security_enabled = False
|
252
259
|
self._ep_enabled = False
|
253
260
|
self._iast_supported = False
|
254
261
|
|
ddtrace/settings/profiling.py
CHANGED
@@ -240,15 +240,6 @@ class ProfilingConfig(DDConfig):
|
|
240
240
|
help="Whether to enable debug assertions in the profiler code",
|
241
241
|
)
|
242
242
|
|
243
|
-
_force_legacy_exporter = DDConfig.v(
|
244
|
-
bool,
|
245
|
-
"_force_legacy_exporter",
|
246
|
-
default=False,
|
247
|
-
help_type="Boolean",
|
248
|
-
help="Exclusively used in testing environments to force the use of the legacy exporter. This parameter is "
|
249
|
-
"not for general use and will be removed in the near future.",
|
250
|
-
)
|
251
|
-
|
252
243
|
sample_pool_capacity = DDConfig.v(
|
253
244
|
int,
|
254
245
|
"sample_pool_capacity",
|
Binary file
|