datadog 2.30.0 → 2.32.0

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.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +44 -1
  3. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +17 -7
  4. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +11 -4
  5. data/ext/datadog_profiling_native_extension/collectors_thread_context.h +6 -0
  6. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
  7. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
  8. data/ext/datadog_profiling_native_extension/extconf.rb +7 -4
  9. data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
  10. data/ext/libdatadog_api/crashtracker.c +5 -8
  11. data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
  12. data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
  13. data/ext/libdatadog_api/di.c +127 -0
  14. data/ext/libdatadog_api/extconf.rb +9 -4
  15. data/ext/libdatadog_api/init.c +5 -2
  16. data/ext/libdatadog_extconf_helpers.rb +46 -1
  17. data/lib/datadog/ai_guard/component.rb +2 -0
  18. data/lib/datadog/ai_guard/configuration.rb +105 -2
  19. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
  20. data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
  21. data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
  22. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
  23. data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
  24. data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
  25. data/lib/datadog/ai_guard/evaluation.rb +37 -7
  26. data/lib/datadog/ai_guard/ext.rb +1 -0
  27. data/lib/datadog/ai_guard.rb +26 -8
  28. data/lib/datadog/appsec/autoload.rb +1 -1
  29. data/lib/datadog/appsec/component.rb +11 -7
  30. data/lib/datadog/appsec/configuration.rb +414 -1
  31. data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
  32. data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +6 -7
  33. data/lib/datadog/appsec/instrumentation/gateway.rb +0 -13
  34. data/lib/datadog/appsec/metrics/telemetry.rb +13 -1
  35. data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -0
  36. data/lib/datadog/appsec/security_engine/runner.rb +1 -1
  37. data/lib/datadog/appsec/trace_keeper.rb +18 -6
  38. data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
  39. data/lib/datadog/appsec/utils/http/url_encoded.rb +3 -3
  40. data/lib/datadog/appsec.rb +5 -9
  41. data/lib/datadog/core/configuration/base.rb +17 -5
  42. data/lib/datadog/core/configuration/components.rb +22 -9
  43. data/lib/datadog/core/configuration/config_helper.rb +9 -0
  44. data/lib/datadog/core/configuration/option.rb +30 -5
  45. data/lib/datadog/core/configuration/option_definition.rb +38 -12
  46. data/lib/datadog/core/configuration/options.rb +40 -6
  47. data/lib/datadog/core/configuration/settings.rb +18 -0
  48. data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
  49. data/lib/datadog/core/configuration.rb +1 -1
  50. data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
  51. data/lib/datadog/core/contrib/rails/utils.rb +7 -3
  52. data/lib/datadog/core/crashtracking/component.rb +3 -3
  53. data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
  54. data/lib/datadog/core/environment/container.rb +2 -2
  55. data/lib/datadog/core/environment/ext.rb +1 -0
  56. data/lib/datadog/core/environment/identity.rb +25 -3
  57. data/lib/datadog/core/environment/process.rb +12 -0
  58. data/lib/datadog/core/feature_flags.rb +1 -1
  59. data/lib/datadog/core/metrics/client.rb +5 -5
  60. data/lib/datadog/core/remote/client.rb +1 -1
  61. data/lib/datadog/core/remote/component.rb +38 -21
  62. data/lib/datadog/core/runtime/metrics.rb +1 -1
  63. data/lib/datadog/core/telemetry/component.rb +3 -0
  64. data/lib/datadog/core/telemetry/emitter.rb +1 -1
  65. data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
  66. data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
  67. data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
  68. data/lib/datadog/core/telemetry/event.rb +1 -7
  69. data/lib/datadog/core/telemetry/ext.rb +1 -0
  70. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
  71. data/lib/datadog/core/telemetry/worker.rb +20 -0
  72. data/lib/datadog/core/transport/http.rb +2 -0
  73. data/lib/datadog/core/utils/only_once.rb +1 -1
  74. data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
  75. data/lib/datadog/core/utils.rb +1 -1
  76. data/lib/datadog/core/workers/async.rb +1 -1
  77. data/lib/datadog/core.rb +1 -2
  78. data/lib/datadog/data_streams/configuration.rb +40 -1
  79. data/lib/datadog/data_streams/pathway_context.rb +1 -1
  80. data/lib/datadog/data_streams/processor.rb +1 -1
  81. data/lib/datadog/data_streams.rb +1 -1
  82. data/lib/datadog/di/base.rb +8 -5
  83. data/lib/datadog/di/boot.rb +2 -4
  84. data/lib/datadog/di/code_tracker.rb +179 -1
  85. data/lib/datadog/di/component.rb +5 -1
  86. data/lib/datadog/di/configuration.rb +235 -2
  87. data/lib/datadog/di/instrumenter.rb +55 -29
  88. data/lib/datadog/di/probe_builder.rb +1 -1
  89. data/lib/datadog/di/probe_file_loader.rb +2 -2
  90. data/lib/datadog/di/probe_manager.rb +6 -6
  91. data/lib/datadog/di/probe_notification_builder.rb +110 -2
  92. data/lib/datadog/di/probe_notifier_worker.rb +2 -2
  93. data/lib/datadog/di/remote.rb +6 -6
  94. data/lib/datadog/di/transport/input.rb +3 -3
  95. data/lib/datadog/di.rb +81 -0
  96. data/lib/datadog/error_tracking/configuration.rb +55 -2
  97. data/lib/datadog/kit/enable_core_dumps.rb +1 -1
  98. data/lib/datadog/open_feature/component.rb +18 -1
  99. data/lib/datadog/open_feature/evaluation_engine.rb +2 -2
  100. data/lib/datadog/open_feature/hooks/flag_eval_hook.rb +49 -0
  101. data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
  102. data/lib/datadog/open_feature/provider.rb +19 -1
  103. data/lib/datadog/open_feature/remote.rb +1 -1
  104. data/lib/datadog/open_feature/transport.rb +1 -1
  105. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
  106. data/lib/datadog/opentelemetry/metrics.rb +3 -3
  107. data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
  108. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +1 -1
  109. data/lib/datadog/profiling/collectors/code_provenance.rb +36 -11
  110. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +31 -2
  111. data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +8 -2
  112. data/lib/datadog/profiling/collectors/info.rb +16 -3
  113. data/lib/datadog/profiling/component.rb +12 -4
  114. data/lib/datadog/profiling/exporter.rb +37 -12
  115. data/lib/datadog/profiling/ext.rb +0 -2
  116. data/lib/datadog/profiling/flush.rb +21 -12
  117. data/lib/datadog/profiling/http_transport.rb +12 -1
  118. data/lib/datadog/profiling/load_native_extension.rb +2 -2
  119. data/lib/datadog/profiling/profiler.rb +13 -5
  120. data/lib/datadog/profiling/scheduler.rb +2 -2
  121. data/lib/datadog/profiling/tasks/exec.rb +8 -3
  122. data/lib/datadog/profiling/tasks/help.rb +1 -0
  123. data/lib/datadog/profiling/tasks/setup.rb +2 -2
  124. data/lib/datadog/profiling.rb +1 -2
  125. data/lib/datadog/single_step_instrument.rb +1 -1
  126. data/lib/datadog/symbol_database/configuration.rb +65 -0
  127. data/lib/datadog/symbol_database/extractor.rb +915 -0
  128. data/lib/datadog/symbol_database/file_hash.rb +46 -0
  129. data/lib/datadog/symbol_database/logger.rb +43 -0
  130. data/lib/datadog/symbol_database/scope.rb +98 -0
  131. data/lib/datadog/symbol_database/service_version.rb +57 -0
  132. data/lib/datadog/symbol_database/symbol.rb +66 -0
  133. data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
  134. data/lib/datadog/symbol_database/transport/http.rb +45 -0
  135. data/lib/datadog/symbol_database/transport.rb +54 -0
  136. data/lib/datadog/symbol_database/uploader.rb +166 -0
  137. data/lib/datadog/symbol_database.rb +49 -0
  138. data/lib/datadog/tracing/buffer.rb +3 -3
  139. data/lib/datadog/tracing/component.rb +11 -0
  140. data/lib/datadog/tracing/configuration/settings.rb +2 -1
  141. data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -3
  142. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
  143. data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
  144. data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
  145. data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
  146. data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
  147. data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
  148. data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
  149. data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
  150. data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
  151. data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
  152. data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
  153. data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
  154. data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
  155. data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
  156. data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
  157. data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
  158. data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
  159. data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
  160. data/lib/datadog/tracing/contrib/component.rb +1 -1
  161. data/lib/datadog/tracing/contrib/configurable.rb +18 -3
  162. data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -4
  163. data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
  164. data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
  165. data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
  166. data/lib/datadog/tracing/contrib/extensions.rb +9 -0
  167. data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
  168. data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
  169. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
  170. data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +2 -2
  171. data/lib/datadog/tracing/contrib/http/instrumentation.rb +3 -3
  172. data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -2
  173. data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +3 -3
  174. data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
  175. data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
  176. data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
  177. data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +3 -3
  178. data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
  179. data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
  180. data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
  181. data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
  182. data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
  183. data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
  184. data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
  185. data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
  186. data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
  187. data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
  188. data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
  189. data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
  190. data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
  191. data/lib/datadog/tracing/contrib.rb +8 -0
  192. data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
  193. data/lib/datadog/tracing/distributed/baggage.rb +59 -5
  194. data/lib/datadog/tracing/distributed/datadog.rb +13 -11
  195. data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -1
  196. data/lib/datadog/tracing/distributed/propagation.rb +2 -2
  197. data/lib/datadog/tracing/distributed/trace_context.rb +74 -32
  198. data/lib/datadog/tracing/event.rb +1 -1
  199. data/lib/datadog/tracing/metadata/tagging.rb +2 -2
  200. data/lib/datadog/tracing/pipeline.rb +1 -1
  201. data/lib/datadog/tracing/remote.rb +1 -1
  202. data/lib/datadog/tracing/sampling/ext.rb +2 -0
  203. data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
  204. data/lib/datadog/tracing/sampling/rule.rb +1 -1
  205. data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
  206. data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
  207. data/lib/datadog/tracing/span_operation.rb +4 -4
  208. data/lib/datadog/tracing/trace_operation.rb +53 -9
  209. data/lib/datadog/tracing/tracer.rb +29 -4
  210. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  211. data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
  212. data/lib/datadog/tracing/workers.rb +2 -1
  213. data/lib/datadog/version.rb +1 -1
  214. metadata +27 -12
  215. data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
  216. data/lib/datadog/appsec/configuration/settings.rb +0 -423
  217. data/lib/datadog/data_streams/configuration/settings.rb +0 -49
  218. data/lib/datadog/di/configuration/settings.rb +0 -243
  219. data/lib/datadog/error_tracking/configuration/settings.rb +0 -63
@@ -7,6 +7,8 @@ require_relative 'evaluation/result'
7
7
  require_relative 'evaluation/no_op_result'
8
8
  require_relative 'evaluation/message'
9
9
  require_relative 'evaluation/tool_call'
10
+ require_relative 'evaluation/content_part'
11
+ require_relative 'evaluation/content_builder'
10
12
  require_relative 'ext'
11
13
 
12
14
  module Datadog
@@ -1,11 +1,114 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "uri"
4
+ require_relative "configuration/ext"
5
+
3
6
  module Datadog
4
7
  module AIGuard
5
8
  # Configuration module for AI Guard
6
9
  module Configuration
10
+ # AI Guard specific settings
11
+ module Settings
12
+ def self.extended(base)
13
+ base = base.singleton_class unless base.is_a?(Class)
14
+ add_settings!(base)
15
+ end
16
+
17
+ def self.add_settings!(base)
18
+ base.class_eval do
19
+ # AI Guard specific configurations.
20
+ # @public_api
21
+ #
22
+ # Steep does not update `self` for this `class_eval` block.
23
+ # @type self: Datadog::Core::Configuration::Base::_DslContext
24
+ settings :ai_guard do
25
+ # Enable AI Guard.
26
+ #
27
+ # You can use this option to skip calls to AI Guard API without having to remove library as a whole.
28
+ #
29
+ # @default `DD_AI_GUARD_ENABLED`, otherwise `false`
30
+ # @return [Boolean]
31
+ option :enabled do |o|
32
+ o.type :bool
33
+ o.env Ext::ENV_AI_GUARD_ENABLED
34
+ o.default false
35
+ end
36
+
37
+ define_method(:instrument) do |integration_name|
38
+ return unless enabled # steep:ignore
39
+
40
+ if (registered_integration = Datadog::AIGuard::Contrib::Integration.registry[integration_name])
41
+ klass = registered_integration.klass
42
+ if klass.loaded? && klass.compatible?
43
+ instance = klass.new
44
+ instance.patcher.patch unless instance.patcher.patched?
45
+ end
46
+ end
47
+ end
48
+
49
+ # AI Guard API endpoint path.
50
+ #
51
+ # @default `DD_AI_GUARD_ENDPOINT`, otherwise `nil`
52
+ # @return [String, nil]
53
+ option :endpoint do |o|
54
+ o.type :string, nilable: true
55
+ o.env Ext::ENV_AI_GUARD_ENDPOINT
56
+
57
+ o.setter do |value|
58
+ next unless value
59
+
60
+ uri = URI(value.to_s)
61
+ raise ArgumentError, "Please provide an absolute URI that includes a protocol" unless uri.absolute?
62
+
63
+ uri.to_s.delete_suffix("/")
64
+ end
65
+ end
66
+
67
+ # Datadog Application key.
68
+ #
69
+ # @default `DD_APP_KEY` environment variable, otherwise `nil`
70
+ # @return [String, nil]
71
+ option :app_key do |o|
72
+ o.type :string, nilable: true
73
+ o.env Ext::ENV_APP_KEY
74
+ end
75
+
76
+ # Request timeout in milliseconds.
77
+ #
78
+ # @default `DD_AI_GUARD_TIMEOUT`, otherwise 10 000 ms
79
+ # @return [Integer]
80
+ option :timeout_ms do |o|
81
+ o.type :int
82
+ o.env Ext::ENV_AI_GUARD_TIMEOUT
83
+ o.default 10_000
84
+ end
85
+
86
+ # Maximum content size in bytes.
87
+ # Content that exceeds the maximum allowed size is truncated before
88
+ # being stored in the current span context.
89
+ #
90
+ # @default `DD_AI_GUARD_MAX_CONTENT_SIZE`, otherwise 524 228 bytes
91
+ # @return [Integer]
92
+ option :max_content_size_bytes do |o|
93
+ o.type :int
94
+ o.env Ext::ENV_AI_GUARD_MAX_CONTENT_SIZE
95
+ o.default 512 * 1024
96
+ end
97
+
98
+ # Maximum number of messages.
99
+ # Older messages are omitted once the message limit is reached.
100
+ #
101
+ # @default `DD_AI_GUARD_MAX_MESSAGES_LENGTH`, otherwise 16 messages
102
+ # @return [Integer]
103
+ option :max_messages_length do |o|
104
+ o.type :int
105
+ o.env Ext::ENV_AI_GUARD_MAX_MESSAGES_LENGTH
106
+ o.default 16
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
7
112
  end
8
113
  end
9
114
  end
10
-
11
- require_relative "configuration/settings"
@@ -14,13 +14,51 @@ module Datadog
14
14
  AIGuard.assistant(id: tool_call_id, tool_name: tool_call.name, arguments: tool_call.arguments.to_s)
15
15
  end
16
16
  elsif message.tool_result?
17
- AIGuard.tool(tool_call_id: message.tool_call_id, content: message.content)
17
+ build_ai_guard_tool(message)
18
18
  else
19
- AIGuard.message(role: message.role, content: message.content)
19
+ build_ai_guard_message(message)
20
20
  end
21
21
  end
22
22
 
23
- AIGuard.evaluate(*ai_guard_messages, allow_raise: true)
23
+ AIGuard.evaluate(*ai_guard_messages)
24
+ end
25
+
26
+ private
27
+
28
+ def build_ai_guard_message(message)
29
+ content = message.content
30
+
31
+ case content
32
+ when ::RubyLLM::Content
33
+ AIGuard.message(role: message.role) do |m|
34
+ m.text(content.text.to_s) if content.text
35
+
36
+ # Calling attachment.for_llm triggers lazy loading of file contents.
37
+ # The result is memoized, so providers won't re-read.
38
+ content.attachments.each do |attachment|
39
+ case attachment.type
40
+ when :image
41
+ m.image_url(attachment.for_llm)
42
+ when :text
43
+ m.text(attachment.for_llm)
44
+ end
45
+ # Skip :pdf, :audio, :video, :unknown — not supported by AIGuard
46
+ end
47
+ end
48
+ else
49
+ AIGuard.message(role: message.role, content: content)
50
+ end
51
+ end
52
+
53
+ def build_ai_guard_tool(message)
54
+ content = message.content
55
+ # Tools can return Content or Content::Raw objects (e.g. with attachments),
56
+ # but AIGuard.tool expects a String. Extract text when content is a Content object.
57
+ case content
58
+ when ::RubyLLM::Content
59
+ content = content.text.to_s
60
+ end
61
+ AIGuard.tool(tool_call_id: message.tool_call_id, content: content)
24
62
  end
25
63
  end
26
64
 
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Builder for collecting content parts inside a message block.
7
+ #
8
+ # Used via the block form of {Datadog::AIGuard.message}:
9
+ #
10
+ # Datadog::AIGuard.message(role: :user) do |m|
11
+ # m.text("What's in this image?")
12
+ # m.image_url("https://example.com/img.png")
13
+ # end
14
+ class ContentBuilder
15
+ attr_reader :parts
16
+
17
+ def initialize
18
+ @parts = []
19
+ end
20
+
21
+ def text(text)
22
+ @parts << ContentPart::Text.new(text)
23
+ end
24
+
25
+ def image_url(url)
26
+ @parts << ContentPart::ImageURL.new(url)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Namespace for content part types used in multi-modal messages.
7
+ module ContentPart
8
+ # A text content part.
9
+ class Text
10
+ attr_reader :text
11
+
12
+ def initialize(text)
13
+ @text = text
14
+ end
15
+
16
+ def type
17
+ :text
18
+ end
19
+ end
20
+
21
+ # An image URL content part. Accepts an absolute URL or a base64 data URI.
22
+ class ImageURL
23
+ attr_reader :url
24
+
25
+ def initialize(url)
26
+ @url = url
27
+ end
28
+
29
+ def type
30
+ :image_url
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -5,12 +5,14 @@ module Datadog
5
5
  module Evaluation
6
6
  # Class for emulating AI Guard evaluation result when AI Guard is disabled.
7
7
  class NoOpResult
8
- attr_reader :action, :reason, :tags
8
+ attr_reader :action, :reason, :tags, :sds_findings, :tag_probabilities
9
9
 
10
10
  def initialize
11
11
  @action = Result::ALLOW_ACTION
12
12
  @reason = "AI Guard is disabled"
13
13
  @tags = []
14
+ @sds_findings = []
15
+ @tag_probabilities = {}
14
16
  end
15
17
 
16
18
  def allow?
@@ -44,15 +44,7 @@ module Datadog
44
44
  end
45
45
 
46
46
  def serialize_messages(messages)
47
- serialized_messages = []
48
-
49
- messages.each do |message|
50
- serialized_messages << serialize_message(message)
51
-
52
- break if serialized_messages.count == Datadog.configuration.ai_guard.max_messages_length
53
- end
54
-
55
- serialized_messages
47
+ messages.map { |message| serialize_message(message) }
56
48
  end
57
49
 
58
50
  def serialize_message(message)
@@ -69,12 +61,25 @@ module Datadog
69
61
  }
70
62
  ]
71
63
  }
64
+ elsif message.content.is_a?(::Array)
65
+ {role: message.role, content: serialize_content_parts(message.content)}
72
66
  elsif message.tool_call_id
73
67
  {role: message.role, tool_call_id: message.tool_call_id, content: message.content}
74
68
  else
75
69
  {role: message.role, content: message.content}
76
70
  end
77
71
  end
72
+
73
+ def serialize_content_parts(parts)
74
+ parts.map do |part|
75
+ case part
76
+ when ContentPart::Text
77
+ {type: "text", text: part.text}
78
+ when ContentPart::ImageURL
79
+ {type: "image_url", image_url: {url: part.url}}
80
+ end
81
+ end
82
+ end
78
83
  end
79
84
  end
80
85
  end
@@ -9,7 +9,7 @@ module Datadog
9
9
  DENY_ACTION = "DENY"
10
10
  ABORT_ACTION = "ABORT"
11
11
 
12
- attr_reader :action, :reason, :tags
12
+ attr_reader :action, :reason, :tags, :sds_findings, :tag_probabilities
13
13
 
14
14
  def initialize(raw_response)
15
15
  attributes = raw_response.fetch("data").fetch("attributes")
@@ -17,7 +17,9 @@ module Datadog
17
17
  @action = attributes.fetch("action")
18
18
  @reason = attributes.fetch("reason")
19
19
  @tags = attributes.fetch("tags")
20
+ @tag_probabilities = attributes.fetch("tag_probs")
20
21
  @is_blocking_enabled = attributes.fetch("is_blocking_enabled")
22
+ @sds_findings = attributes.fetch("sds_findings", [])
21
23
  rescue KeyError => e
22
24
  raise AIGuardClientError, "Missing key: \"#{e.key}\""
23
25
  end
@@ -6,10 +6,17 @@ module Datadog
6
6
  # and creating `ai_guard` span with required tags
7
7
  module Evaluation
8
8
  class << self
9
- def perform(messages, allow_raise: false)
9
+ def perform(messages, allow_raise: true)
10
10
  raise ArgumentError, "Messages must not be empty" if messages&.empty?
11
11
 
12
12
  Tracing.trace(Ext::SPAN_NAME) do |span, trace|
13
+ trace.keep!
14
+ trace.set_tag(
15
+ Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
16
+ Tracing::Sampling::Ext::Decision::AI_GUARD
17
+ )
18
+ trace.set_tag(Ext::EVENT_TAG, true)
19
+
13
20
  if (last_message = messages.last)
14
21
  if last_message.tool_call
15
22
  span.set_tag(Ext::TARGET_TAG, "tool")
@@ -34,8 +41,10 @@ module Datadog
34
41
  span.set_metastruct_tag(
35
42
  Ext::METASTRUCT_TAG,
36
43
  {
37
- messages: truncate_content(request.serialized_messages),
38
- attack_categories: result.tags
44
+ messages: truncate_content(truncate_messages(request.serialized_messages)),
45
+ attack_categories: result.tags,
46
+ sds: result.sds_findings,
47
+ tag_probs: result.tag_probabilities
39
48
  }
40
49
  )
41
50
 
@@ -56,14 +65,35 @@ module Datadog
56
65
 
57
66
  private
58
67
 
68
+ def truncate_messages(serialized_messages)
69
+ max_length = Datadog.configuration.ai_guard.max_messages_length
70
+ serialized_messages.first(max_length)
71
+ end
72
+
73
+ # Truncates content in serialized messages to stay within the configured byte limit.
74
+ # For multi-modal messages, only text parts are truncated; image URLs are left intact.
59
75
  def truncate_content(serialized_messages)
76
+ max_bytes = Datadog.configuration.ai_guard.max_content_size_bytes
77
+
60
78
  serialized_messages.map do |message| # steep:ignore
61
79
  next message unless message[:content]
62
80
 
63
- {
64
- **message,
65
- content: message[:content].byteslice(0, Datadog.configuration.ai_guard.max_content_size_bytes)
66
- }
81
+ if message[:content].is_a?(::Array)
82
+ serialized_content = message[:content].map do |part|
83
+ if part[:text]
84
+ {**part, text: part[:text].to_s.byteslice(0, max_bytes)}
85
+ else
86
+ part
87
+ end
88
+ end
89
+
90
+ {**message, content: serialized_content}
91
+ else
92
+ {
93
+ **message,
94
+ content: message[:content].byteslice(0, max_bytes)
95
+ }
96
+ end
67
97
  end
68
98
  end
69
99
  end
@@ -10,6 +10,7 @@ module Datadog
10
10
  ACTION_TAG = "ai_guard.action"
11
11
  REASON_TAG = "ai_guard.reason"
12
12
  BLOCKED_TAG = "ai_guard.blocked"
13
+ EVENT_TAG = "ai_guard.event"
13
14
  METASTRUCT_TAG = "ai_guard"
14
15
  end
15
16
  end
@@ -10,7 +10,7 @@ module Datadog
10
10
  module AIGuard
11
11
  Core::Configuration::Settings.extend(Configuration::Settings)
12
12
 
13
- # This error is raised when user passes `allow_raise: true` to Evaluation.perform
13
+ # This error is raised when `allow_raise` is set to true (the default) in Evaluation.perform
14
14
  # and AI Guard considers the messages not safe. Intended to be rescued by the user.
15
15
  #
16
16
  # WARNING: This name must not change, since front-end is using it.
@@ -68,13 +68,14 @@ module Datadog
68
68
  # One or more message objects to be evaluated.
69
69
  # @param allow_raise [Boolean]
70
70
  # Whether this method may raise an exception when evaluation result is not ALLOW.
71
+ # Defaults to true.
71
72
  #
72
73
  # @return [Datadog::AIGuard::Evaluation::Result]
73
74
  # The result of AI Guard evaluation.
74
75
  # @raise [Datadog::AIGuard::AIGuardAbortError]
75
76
  # If the evaluation results in DENY or ABORT action and `allow_raise` is set to true
76
77
  # @public_api
77
- def evaluate(*messages, allow_raise: false)
78
+ def evaluate(*messages, allow_raise: true)
78
79
  if enabled?
79
80
  Evaluation.perform(messages, allow_raise: allow_raise)
80
81
  else
@@ -84,25 +85,42 @@ module Datadog
84
85
 
85
86
  # Builds a generic evaluation message.
86
87
  #
87
- # Example:
88
+ # Accepts either a string content or a block for multi-modal content parts:
88
89
  #
89
90
  # ```
91
+ # # String content:
90
92
  # Datadog::AIGuard.message(role: :user, content: "Hello, assistant")
93
+ #
94
+ # # Multi-modal content with block:
95
+ # Datadog::AIGuard.message(role: :user) do |m|
96
+ # m.text("What's in this image?")
97
+ # m.image_url("https://example.com/img.png")
98
+ # end
91
99
  # ```
92
100
  #
93
101
  # @param role [Symbol]
94
102
  # The role associated with the message.
95
103
  # Must be one of `:assistant`, `:tool`, `:system`, `:developer`, or `:user`.
96
- # @param content [String]
97
- # The textual content of the message.
104
+ # @param content [String, nil]
105
+ # The textual content of the message. Cannot be combined with a block.
106
+ # @yield [builder] A block for building multi-modal content parts.
107
+ # @yieldparam builder [Datadog::AIGuard::Evaluation::ContentBuilder]
98
108
  #
99
109
  # @return [Datadog::AIGuard::Evaluation::Message]
100
110
  # A new message instance with the given role and content.
101
111
  # @raise [ArgumentError]
102
- # If an invalid role is provided.
112
+ # If both content and a block are provided, or if an invalid role is provided.
103
113
  # @public_api
104
- def message(role:, content:)
105
- Evaluation::Message.new(role: role, content: content)
114
+ def message(role:, content: nil)
115
+ if block_given?
116
+ raise ArgumentError, "Cannot pass both content and a block" if content
117
+
118
+ builder = Evaluation::ContentBuilder.new
119
+ yield builder
120
+ Evaluation::Message.new(role: role, content: builder.parts)
121
+ else
122
+ Evaluation::Message.new(role: role, content: content)
123
+ end
106
124
  end
107
125
 
108
126
  # Builds an assistant message representing a tool call initiated by the model.
@@ -7,7 +7,7 @@ if %w[1 true].include?((Datadog::DATADOG_ENV['DD_APPSEC_ENABLED'] || '').downcas
7
7
  rescue => e
8
8
  Kernel.warn(
9
9
  '[datadog] AppSec failed to instrument. No security check will be performed. error: ' \
10
- " #{e.class.name} #{e.message}"
10
+ " #{e.class}: #{e.message}"
11
11
  )
12
12
  end
13
13
  end
@@ -45,10 +45,15 @@ module Datadog
45
45
  settings.appsec.instrument(:devise) unless devise_integration.patcher.patched?
46
46
 
47
47
  security_engine = SecurityEngine::Engine.new(appsec_settings: settings.appsec, telemetry: telemetry)
48
- new(security_engine: security_engine, telemetry: telemetry)
49
- rescue
50
- Datadog.logger.warn('AppSec is disabled, see logged errors above')
51
-
48
+ new(security_engine: security_engine)
49
+ rescue => e
50
+ Datadog.logger.warn("AppSec is disabled: #{e.class}: #{e.message}; there may be additional logged errors above")
51
+
52
+ # Not reporting to telemetry here because some of the rescued exceptions
53
+ # have already been reported by the code that raised them
54
+ # (e.g. SecurityEngine::Engine.new reports WAF init failures).
55
+ # TODO: reconsider whether telemetry reporting belongs here
56
+ # (single catch-all) or in the downstream code (as it is now).
52
57
  nil
53
58
  end
54
59
 
@@ -70,11 +75,10 @@ module Datadog
70
75
  end
71
76
  end
72
77
 
73
- attr_reader :security_engine, :telemetry
78
+ attr_reader :security_engine
74
79
 
75
- def initialize(security_engine:, telemetry:)
80
+ def initialize(security_engine:)
76
81
  @security_engine = security_engine
77
- @telemetry = telemetry
78
82
  end
79
83
 
80
84
  def reconfigure!