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.

Files changed (176) hide show
  1. ddtrace/_logger.py +5 -6
  2. ddtrace/_trace/product.py +1 -1
  3. ddtrace/_trace/sampling_rule.py +25 -33
  4. ddtrace/_trace/trace_handlers.py +12 -50
  5. ddtrace/_trace/utils_botocore/span_tags.py +48 -0
  6. ddtrace/_version.py +2 -2
  7. ddtrace/appsec/_asm_request_context.py +3 -1
  8. ddtrace/appsec/_constants.py +7 -0
  9. ddtrace/appsec/_handlers.py +11 -0
  10. ddtrace/appsec/_iast/_listener.py +12 -2
  11. ddtrace/appsec/_processor.py +1 -1
  12. ddtrace/contrib/integration_registry/registry.yaml +10 -0
  13. ddtrace/contrib/internal/aiobotocore/patch.py +8 -0
  14. ddtrace/contrib/internal/avro/__init__.py +17 -0
  15. ddtrace/contrib/internal/azure_functions/patch.py +23 -12
  16. ddtrace/contrib/internal/azure_functions/utils.py +14 -0
  17. ddtrace/contrib/internal/boto/patch.py +14 -0
  18. ddtrace/contrib/internal/botocore/__init__.py +153 -0
  19. ddtrace/contrib/internal/botocore/services/bedrock.py +3 -27
  20. ddtrace/contrib/internal/django/patch.py +31 -8
  21. ddtrace/contrib/{_freezegun.py → internal/freezegun/__init__.py} +1 -1
  22. ddtrace/contrib/internal/google_genai/_utils.py +2 -2
  23. ddtrace/contrib/internal/google_genai/patch.py +7 -7
  24. ddtrace/contrib/internal/google_generativeai/patch.py +7 -5
  25. ddtrace/contrib/internal/langchain/patch.py +11 -443
  26. ddtrace/contrib/internal/langchain/utils.py +0 -26
  27. ddtrace/contrib/internal/logbook/patch.py +1 -2
  28. ddtrace/contrib/internal/logging/patch.py +4 -7
  29. ddtrace/contrib/internal/loguru/patch.py +1 -3
  30. ddtrace/contrib/internal/openai_agents/patch.py +44 -1
  31. ddtrace/contrib/internal/protobuf/__init__.py +17 -0
  32. ddtrace/contrib/internal/pytest/__init__.py +62 -0
  33. ddtrace/contrib/internal/pytest/_plugin_v2.py +13 -4
  34. ddtrace/contrib/internal/pytest_bdd/__init__.py +23 -0
  35. ddtrace/contrib/internal/pytest_benchmark/__init__.py +3 -0
  36. ddtrace/contrib/internal/structlog/patch.py +2 -4
  37. ddtrace/contrib/internal/unittest/__init__.py +36 -0
  38. ddtrace/contrib/internal/vertexai/patch.py +7 -5
  39. ddtrace/ext/ci.py +20 -0
  40. ddtrace/ext/git.py +66 -11
  41. ddtrace/internal/_encoding.cp312-win_amd64.pyd +0 -0
  42. ddtrace/internal/_encoding.pyi +1 -1
  43. ddtrace/internal/_rand.cp312-win_amd64.pyd +0 -0
  44. ddtrace/internal/_tagset.cp312-win_amd64.pyd +0 -0
  45. ddtrace/internal/_threads.cp312-win_amd64.pyd +0 -0
  46. ddtrace/internal/ci_visibility/encoder.py +126 -49
  47. ddtrace/internal/ci_visibility/utils.py +4 -4
  48. ddtrace/internal/core/__init__.py +5 -2
  49. ddtrace/internal/datadog/profiling/dd_wrapper-unknown-amd64.dll +0 -0
  50. ddtrace/internal/datadog/profiling/dd_wrapper-unknown-amd64.lib +0 -0
  51. ddtrace/internal/datadog/profiling/ddup/_ddup.cp312-win_amd64.pyd +0 -0
  52. ddtrace/internal/datadog/profiling/ddup/_ddup.cp312-win_amd64.pyd.lib +0 -0
  53. ddtrace/internal/datadog/profiling/ddup/dd_wrapper-unknown-amd64.dll +0 -0
  54. ddtrace/internal/datadog/profiling/ddup/dd_wrapper-unknown-amd64.lib +0 -0
  55. ddtrace/internal/endpoints.py +76 -0
  56. ddtrace/internal/native/_native.cp312-win_amd64.pyd +0 -0
  57. ddtrace/internal/schema/processor.py +6 -2
  58. ddtrace/internal/telemetry/metrics_namespaces.cp312-win_amd64.pyd +0 -0
  59. ddtrace/internal/telemetry/writer.py +18 -0
  60. ddtrace/internal/test_visibility/coverage_lines.py +4 -4
  61. ddtrace/internal/writer/writer.py +24 -11
  62. ddtrace/llmobs/_constants.py +3 -0
  63. ddtrace/llmobs/_experiment.py +75 -10
  64. ddtrace/llmobs/_integrations/bedrock.py +4 -0
  65. ddtrace/llmobs/_integrations/bedrock_agents.py +5 -1
  66. ddtrace/llmobs/_integrations/crewai.py +52 -3
  67. ddtrace/llmobs/_integrations/gemini.py +7 -7
  68. ddtrace/llmobs/_integrations/google_genai.py +10 -10
  69. ddtrace/llmobs/_integrations/{google_genai_utils.py → google_utils.py} +103 -7
  70. ddtrace/llmobs/_integrations/langchain.py +29 -20
  71. ddtrace/llmobs/_integrations/openai_agents.py +145 -0
  72. ddtrace/llmobs/_integrations/pydantic_ai.py +67 -26
  73. ddtrace/llmobs/_integrations/utils.py +68 -158
  74. ddtrace/llmobs/_integrations/vertexai.py +8 -8
  75. ddtrace/llmobs/_llmobs.py +83 -14
  76. ddtrace/llmobs/_telemetry.py +20 -5
  77. ddtrace/llmobs/_utils.py +27 -0
  78. ddtrace/profiling/_threading.cp312-win_amd64.pyd +0 -0
  79. ddtrace/profiling/collector/_memalloc.cp312-win_amd64.pyd +0 -0
  80. ddtrace/profiling/collector/_task.cp312-win_amd64.pyd +0 -0
  81. ddtrace/profiling/collector/_traceback.cp312-win_amd64.pyd +0 -0
  82. ddtrace/profiling/collector/stack.cp312-win_amd64.pyd +0 -0
  83. ddtrace/settings/_config.py +1 -2
  84. ddtrace/settings/asm.py +9 -2
  85. ddtrace/settings/profiling.py +0 -9
  86. ddtrace/vendor/psutil/_psutil_windows.cp312-win_amd64.pyd +0 -0
  87. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/METADATA +1 -1
  88. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/RECORD +171 -177
  89. ddtrace/contrib/_avro.py +0 -17
  90. ddtrace/contrib/_botocore.py +0 -153
  91. ddtrace/contrib/_protobuf.py +0 -17
  92. ddtrace/contrib/_pytest.py +0 -62
  93. ddtrace/contrib/_pytest_bdd.py +0 -23
  94. ddtrace/contrib/_pytest_benchmark.py +0 -3
  95. ddtrace/contrib/_unittest.py +0 -36
  96. /ddtrace/contrib/{_aiobotocore.py → internal/aiobotocore/__init__.py} +0 -0
  97. /ddtrace/contrib/{_aiohttp_jinja2.py → internal/aiohttp_jinja2/__init__.py} +0 -0
  98. /ddtrace/contrib/{_aiomysql.py → internal/aiomysql/__init__.py} +0 -0
  99. /ddtrace/contrib/{_aiopg.py → internal/aiopg/__init__.py} +0 -0
  100. /ddtrace/contrib/{_aioredis.py → internal/aioredis/__init__.py} +0 -0
  101. /ddtrace/contrib/{_algoliasearch.py → internal/algoliasearch/__init__.py} +0 -0
  102. /ddtrace/contrib/{_anthropic.py → internal/anthropic/__init__.py} +0 -0
  103. /ddtrace/contrib/{_aredis.py → internal/aredis/__init__.py} +0 -0
  104. /ddtrace/contrib/{_asyncio.py → internal/asyncio/__init__.py} +0 -0
  105. /ddtrace/contrib/{_asyncpg.py → internal/asyncpg/__init__.py} +0 -0
  106. /ddtrace/contrib/{_aws_lambda.py → internal/aws_lambda/__init__.py} +0 -0
  107. /ddtrace/contrib/{_azure_functions.py → internal/azure_functions/__init__.py} +0 -0
  108. /ddtrace/contrib/{_azure_servicebus.py → internal/azure_servicebus/__init__.py} +0 -0
  109. /ddtrace/contrib/{_boto.py → internal/boto/__init__.py} +0 -0
  110. /ddtrace/contrib/{_cassandra.py → internal/cassandra/__init__.py} +0 -0
  111. /ddtrace/contrib/{_consul.py → internal/consul/__init__.py} +0 -0
  112. /ddtrace/contrib/{_coverage.py → internal/coverage/__init__.py} +0 -0
  113. /ddtrace/contrib/{_crewai.py → internal/crewai/__init__.py} +0 -0
  114. /ddtrace/contrib/{_django.py → internal/django/__init__.py} +0 -0
  115. /ddtrace/contrib/{_dogpile_cache.py → internal/dogpile_cache/__init__.py} +0 -0
  116. /ddtrace/contrib/{_dramatiq.py → internal/dramatiq/__init__.py} +0 -0
  117. /ddtrace/contrib/{_elasticsearch.py → internal/elasticsearch/__init__.py} +0 -0
  118. /ddtrace/contrib/{_fastapi.py → internal/fastapi/__init__.py} +0 -0
  119. /ddtrace/contrib/{_flask.py → internal/flask/__init__.py} +0 -0
  120. /ddtrace/contrib/{_futures.py → internal/futures/__init__.py} +0 -0
  121. /ddtrace/contrib/{_gevent.py → internal/gevent/__init__.py} +0 -0
  122. /ddtrace/contrib/{_google_genai.py → internal/google_genai/__init__.py} +0 -0
  123. /ddtrace/contrib/{_google_generativeai.py → internal/google_generativeai/__init__.py} +0 -0
  124. /ddtrace/contrib/{_graphql.py → internal/graphql/__init__.py} +0 -0
  125. /ddtrace/contrib/{_grpc.py → internal/grpc/__init__.py} +0 -0
  126. /ddtrace/contrib/{_gunicorn.py → internal/gunicorn/__init__.py} +0 -0
  127. /ddtrace/contrib/{_httplib.py → internal/httplib/__init__.py} +0 -0
  128. /ddtrace/contrib/{_httpx.py → internal/httpx/__init__.py} +0 -0
  129. /ddtrace/contrib/{_jinja2.py → internal/jinja2/__init__.py} +0 -0
  130. /ddtrace/contrib/{_kafka.py → internal/kafka/__init__.py} +0 -0
  131. /ddtrace/contrib/{_kombu.py → internal/kombu/__init__.py} +0 -0
  132. /ddtrace/contrib/{_langchain.py → internal/langchain/__init__.py} +0 -0
  133. /ddtrace/contrib/{_langgraph.py → internal/langgraph/__init__.py} +0 -0
  134. /ddtrace/contrib/{_litellm.py → internal/litellm/__init__.py} +0 -0
  135. /ddtrace/contrib/{_logbook.py → internal/logbook/__init__.py} +0 -0
  136. /ddtrace/contrib/{_logging.py → internal/logging/__init__.py} +0 -0
  137. /ddtrace/contrib/{_loguru.py → internal/loguru/__init__.py} +0 -0
  138. /ddtrace/contrib/{_mako.py → internal/mako/__init__.py} +0 -0
  139. /ddtrace/contrib/{_mariadb.py → internal/mariadb/__init__.py} +0 -0
  140. /ddtrace/contrib/{_mcp.py → internal/mcp/__init__.py} +0 -0
  141. /ddtrace/contrib/{_molten.py → internal/molten/__init__.py} +0 -0
  142. /ddtrace/contrib/{_mongoengine.py → internal/mongoengine/__init__.py} +0 -0
  143. /ddtrace/contrib/{_mysql.py → internal/mysql/__init__.py} +0 -0
  144. /ddtrace/contrib/{_mysqldb.py → internal/mysqldb/__init__.py} +0 -0
  145. /ddtrace/contrib/{_openai.py → internal/openai/__init__.py} +0 -0
  146. /ddtrace/contrib/{_openai_agents.py → internal/openai_agents/__init__.py} +0 -0
  147. /ddtrace/contrib/{_psycopg.py → internal/psycopg/__init__.py} +0 -0
  148. /ddtrace/contrib/{_pydantic_ai.py → internal/pydantic_ai/__init__.py} +0 -0
  149. /ddtrace/contrib/{_pymemcache.py → internal/pymemcache/__init__.py} +0 -0
  150. /ddtrace/contrib/{_pymongo.py → internal/pymongo/__init__.py} +0 -0
  151. /ddtrace/contrib/{_pymysql.py → internal/pymysql/__init__.py} +0 -0
  152. /ddtrace/contrib/{_pynamodb.py → internal/pynamodb/__init__.py} +0 -0
  153. /ddtrace/contrib/{_pyodbc.py → internal/pyodbc/__init__.py} +0 -0
  154. /ddtrace/contrib/{_redis.py → internal/redis/__init__.py} +0 -0
  155. /ddtrace/contrib/{_rediscluster.py → internal/rediscluster/__init__.py} +0 -0
  156. /ddtrace/contrib/{_rq.py → internal/rq/__init__.py} +0 -0
  157. /ddtrace/contrib/{_sanic.py → internal/sanic/__init__.py} +0 -0
  158. /ddtrace/contrib/{_selenium.py → internal/selenium/__init__.py} +0 -0
  159. /ddtrace/contrib/{_snowflake.py → internal/snowflake/__init__.py} +0 -0
  160. /ddtrace/contrib/{_sqlite3.py → internal/sqlite3/__init__.py} +0 -0
  161. /ddtrace/contrib/{_starlette.py → internal/starlette/__init__.py} +0 -0
  162. /ddtrace/contrib/{_structlog.py → internal/structlog/__init__.py} +0 -0
  163. /ddtrace/contrib/{_subprocess.py → internal/subprocess/__init__.py} +0 -0
  164. /ddtrace/contrib/{_urllib.py → internal/urllib/__init__.py} +0 -0
  165. /ddtrace/contrib/{_urllib3.py → internal/urllib3/__init__.py} +0 -0
  166. /ddtrace/contrib/{_vertexai.py → internal/vertexai/__init__.py} +0 -0
  167. /ddtrace/contrib/{_vertica.py → internal/vertica/__init__.py} +0 -0
  168. /ddtrace/contrib/{_webbrowser.py → internal/webbrowser/__init__.py} +0 -0
  169. /ddtrace/contrib/{_yaaredis.py → internal/yaaredis/__init__.py} +0 -0
  170. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/WHEEL +0 -0
  171. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/entry_points.txt +0 -0
  172. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE +0 -0
  173. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.Apache +0 -0
  174. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/LICENSE.BSD3 +0 -0
  175. {ddtrace-3.11.0rc1.dist-info → ddtrace-3.11.0rc3.dist-info}/licenses/NOTICE +0 -0
  176. {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
- meta["metadata"] = span._get_ctx_item(METADATA) or {}
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
- project_name: Optional[str] = None,
612
- tags: Optional[List[str]] = None,
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 a parameter ``input_data`` and optionally ``config``.
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 project_name: The name of the project to associate with the experiment. If not provided, defaults to the
623
- configured value set via environment variable `DD_LLMOBS_PROJECT_NAME`
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=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
 
@@ -36,16 +36,17 @@ class LLMObsTelemetryMetrics:
36
36
  USER_PROCESSOR_CALLED = "user_processor_called"
37
37
 
38
38
 
39
- def _find_integration_from_tags(tags):
40
- integration_tag = next((tag for tag in tags if tag.startswith("integration:")), None)
41
- if not integration_tag:
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 integration_tag.split("integration:")[-1]
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 = _find_integration_from_tags(event.get("tags", []))
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
  """
@@ -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=LogInjectionState.STRUCTURED,
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
- # As a first step, only Threat Management in monitoring mode should be enabled in AWS Lambda
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
 
@@ -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",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ddtrace
3
- Version: 3.11.0rc1
3
+ Version: 3.11.0rc3
4
4
  Summary: Datadog APM client library
5
5
  Author-email: "Datadog, Inc." <dev@datadoghq.com>
6
6
  License: LICENSE.BSD3