datadog 2.23.0 → 2.28.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 (215) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -2
  3. data/ext/datadog_profiling_native_extension/clock_id_from_pthread.c +2 -1
  4. data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +100 -29
  5. data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +2 -2
  6. data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +3 -2
  7. data/ext/datadog_profiling_native_extension/collectors_stack.c +23 -10
  8. data/ext/datadog_profiling_native_extension/collectors_thread_context.c +16 -12
  9. data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +239 -0
  10. data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +48 -1
  11. data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +41 -0
  12. data/ext/datadog_profiling_native_extension/encoded_profile.c +2 -1
  13. data/ext/datadog_profiling_native_extension/extconf.rb +4 -1
  14. data/ext/datadog_profiling_native_extension/heap_recorder.c +24 -24
  15. data/ext/datadog_profiling_native_extension/http_transport.c +10 -4
  16. data/ext/datadog_profiling_native_extension/libdatadog_helpers.c +3 -22
  17. data/ext/datadog_profiling_native_extension/libdatadog_helpers.h +0 -5
  18. data/ext/datadog_profiling_native_extension/private_vm_api_access.c +21 -8
  19. data/ext/datadog_profiling_native_extension/private_vm_api_access.h +4 -0
  20. data/ext/datadog_profiling_native_extension/profiling.c +22 -15
  21. data/ext/datadog_profiling_native_extension/ruby_helpers.c +55 -44
  22. data/ext/datadog_profiling_native_extension/ruby_helpers.h +17 -5
  23. data/ext/datadog_profiling_native_extension/setup_signal_handler.c +8 -2
  24. data/ext/datadog_profiling_native_extension/setup_signal_handler.h +3 -0
  25. data/ext/datadog_profiling_native_extension/stack_recorder.c +16 -16
  26. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.c +2 -1
  27. data/ext/datadog_profiling_native_extension/unsafe_api_calls_check.h +5 -2
  28. data/ext/libdatadog_api/crashtracker.c +5 -8
  29. data/ext/libdatadog_api/datadog_ruby_common.c +48 -1
  30. data/ext/libdatadog_api/datadog_ruby_common.h +41 -0
  31. data/ext/libdatadog_api/ddsketch.c +4 -8
  32. data/ext/libdatadog_api/feature_flags.c +5 -5
  33. data/ext/libdatadog_api/helpers.h +27 -0
  34. data/ext/libdatadog_api/init.c +4 -0
  35. data/ext/libdatadog_extconf_helpers.rb +1 -1
  36. data/lib/datadog/ai_guard/api_client.rb +82 -0
  37. data/lib/datadog/ai_guard/component.rb +42 -0
  38. data/lib/datadog/ai_guard/configuration/ext.rb +17 -0
  39. data/lib/datadog/ai_guard/configuration/settings.rb +110 -0
  40. data/lib/datadog/ai_guard/configuration.rb +11 -0
  41. data/lib/datadog/ai_guard/contrib/integration.rb +37 -0
  42. data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +42 -0
  43. data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +41 -0
  44. data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +30 -0
  45. data/lib/datadog/ai_guard/evaluation/message.rb +25 -0
  46. data/lib/datadog/ai_guard/evaluation/no_op_result.rb +34 -0
  47. data/lib/datadog/ai_guard/evaluation/request.rb +81 -0
  48. data/lib/datadog/ai_guard/evaluation/result.rb +43 -0
  49. data/lib/datadog/ai_guard/evaluation/tool_call.rb +18 -0
  50. data/lib/datadog/ai_guard/evaluation.rb +72 -0
  51. data/lib/datadog/ai_guard/ext.rb +16 -0
  52. data/lib/datadog/ai_guard.rb +155 -0
  53. data/lib/datadog/appsec/api_security/endpoint_collection/rails_collector.rb +8 -1
  54. data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +9 -2
  55. data/lib/datadog/appsec/component.rb +1 -1
  56. data/lib/datadog/appsec/context.rb +5 -4
  57. data/lib/datadog/appsec/contrib/active_record/integration.rb +1 -1
  58. data/lib/datadog/appsec/contrib/active_record/patcher.rb +1 -1
  59. data/lib/datadog/appsec/contrib/excon/integration.rb +1 -1
  60. data/lib/datadog/appsec/contrib/excon/patcher.rb +1 -1
  61. data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +47 -12
  62. data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +32 -15
  63. data/lib/datadog/appsec/contrib/rails/patcher.rb +10 -2
  64. data/lib/datadog/appsec/contrib/rest_client/integration.rb +1 -1
  65. data/lib/datadog/appsec/contrib/rest_client/patcher.rb +1 -1
  66. data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +50 -14
  67. data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +5 -4
  68. data/lib/datadog/appsec/contrib/sinatra/integration.rb +1 -1
  69. data/lib/datadog/appsec/contrib/sinatra/patcher.rb +4 -4
  70. data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +1 -1
  71. data/lib/datadog/appsec/ext.rb +2 -0
  72. data/lib/datadog/appsec/metrics/collector.rb +8 -3
  73. data/lib/datadog/appsec/metrics/exporter.rb +7 -0
  74. data/lib/datadog/appsec/metrics/telemetry.rb +7 -2
  75. data/lib/datadog/appsec/metrics.rb +5 -5
  76. data/lib/datadog/appsec/remote.rb +7 -14
  77. data/lib/datadog/appsec/security_engine/engine.rb +3 -3
  78. data/lib/datadog/appsec/security_engine/result.rb +2 -1
  79. data/lib/datadog/appsec/security_engine/runner.rb +2 -2
  80. data/lib/datadog/appsec/utils/http/media_type.rb +37 -23
  81. data/lib/datadog/appsec.rb +7 -1
  82. data/lib/datadog/core/configuration/components.rb +7 -0
  83. data/lib/datadog/core/configuration/config_helper.rb +1 -1
  84. data/lib/datadog/core/configuration/deprecations.rb +2 -2
  85. data/lib/datadog/core/configuration/option_definition.rb +4 -2
  86. data/lib/datadog/core/configuration/options.rb +8 -5
  87. data/lib/datadog/core/configuration/settings.rb +31 -3
  88. data/lib/datadog/core/configuration/supported_configurations.rb +10 -1
  89. data/lib/datadog/core/crashtracking/tag_builder.rb +6 -0
  90. data/lib/datadog/core/environment/cgroup.rb +52 -25
  91. data/lib/datadog/core/environment/container.rb +140 -46
  92. data/lib/datadog/core/environment/ext.rb +1 -0
  93. data/lib/datadog/core/environment/process.rb +9 -1
  94. data/lib/datadog/core/error.rb +6 -6
  95. data/lib/datadog/core/knuth_sampler.rb +57 -0
  96. data/lib/datadog/core/pin.rb +4 -0
  97. data/lib/datadog/core/rate_limiter.rb +9 -1
  98. data/lib/datadog/core/remote/client.rb +14 -6
  99. data/lib/datadog/core/remote/component.rb +6 -4
  100. data/lib/datadog/core/remote/configuration/content.rb +15 -2
  101. data/lib/datadog/core/remote/configuration/digest.rb +14 -7
  102. data/lib/datadog/core/remote/configuration/repository.rb +1 -1
  103. data/lib/datadog/core/remote/configuration/target.rb +13 -6
  104. data/lib/datadog/core/remote/transport/config.rb +3 -16
  105. data/lib/datadog/core/remote/transport/http/config.rb +4 -44
  106. data/lib/datadog/core/remote/transport/http/negotiation.rb +0 -39
  107. data/lib/datadog/core/remote/transport/http.rb +13 -24
  108. data/lib/datadog/core/remote/transport/negotiation.rb +7 -16
  109. data/lib/datadog/core/runtime/metrics.rb +11 -1
  110. data/lib/datadog/core/semaphore.rb +1 -4
  111. data/lib/datadog/core/telemetry/component.rb +52 -13
  112. data/lib/datadog/core/telemetry/event/app_started.rb +36 -1
  113. data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +27 -4
  114. data/lib/datadog/core/telemetry/logger.rb +2 -0
  115. data/lib/datadog/core/telemetry/logging.rb +20 -2
  116. data/lib/datadog/core/telemetry/metrics_manager.rb +9 -0
  117. data/lib/datadog/core/telemetry/request.rb +17 -3
  118. data/lib/datadog/core/telemetry/transport/http/telemetry.rb +2 -32
  119. data/lib/datadog/core/telemetry/transport/http.rb +21 -16
  120. data/lib/datadog/core/telemetry/transport/telemetry.rb +3 -10
  121. data/lib/datadog/core/telemetry/worker.rb +88 -32
  122. data/lib/datadog/core/transport/ext.rb +2 -0
  123. data/lib/datadog/core/transport/http/api/endpoint.rb +9 -4
  124. data/lib/datadog/core/transport/http/api/instance.rb +4 -21
  125. data/lib/datadog/core/transport/http/builder.rb +9 -5
  126. data/lib/datadog/core/transport/http/client.rb +19 -8
  127. data/lib/datadog/core/transport/http.rb +22 -19
  128. data/lib/datadog/core/transport/response.rb +12 -1
  129. data/lib/datadog/core/transport/transport.rb +90 -0
  130. data/lib/datadog/core/utils/only_once_successful.rb +2 -0
  131. data/lib/datadog/core/utils/safe_dup.rb +2 -2
  132. data/lib/datadog/core/utils/sequence.rb +2 -0
  133. data/lib/datadog/core/utils/time.rb +1 -1
  134. data/lib/datadog/core/workers/async.rb +10 -1
  135. data/lib/datadog/core/workers/interval_loop.rb +44 -3
  136. data/lib/datadog/core/workers/polling.rb +2 -0
  137. data/lib/datadog/core/workers/queue.rb +100 -1
  138. data/lib/datadog/data_streams/processor.rb +1 -1
  139. data/lib/datadog/data_streams/transport/http/stats.rb +1 -36
  140. data/lib/datadog/data_streams/transport/http.rb +5 -6
  141. data/lib/datadog/data_streams/transport/stats.rb +3 -17
  142. data/lib/datadog/di/boot.rb +4 -2
  143. data/lib/datadog/di/configuration/settings.rb +22 -0
  144. data/lib/datadog/di/contrib/active_record.rb +30 -5
  145. data/lib/datadog/di/el/compiler.rb +8 -4
  146. data/lib/datadog/di/error.rb +5 -0
  147. data/lib/datadog/di/instrumenter.rb +26 -7
  148. data/lib/datadog/di/logger.rb +2 -2
  149. data/lib/datadog/di/probe_builder.rb +2 -1
  150. data/lib/datadog/di/probe_file_loader/railtie.rb +1 -1
  151. data/lib/datadog/di/probe_manager.rb +37 -31
  152. data/lib/datadog/di/probe_notification_builder.rb +15 -2
  153. data/lib/datadog/di/probe_notifier_worker.rb +5 -5
  154. data/lib/datadog/di/redactor.rb +8 -1
  155. data/lib/datadog/di/remote.rb +89 -84
  156. data/lib/datadog/di/transport/diagnostics.rb +7 -35
  157. data/lib/datadog/di/transport/http/diagnostics.rb +1 -31
  158. data/lib/datadog/di/transport/http/input.rb +1 -31
  159. data/lib/datadog/di/transport/http.rb +28 -17
  160. data/lib/datadog/di/transport/input.rb +7 -34
  161. data/lib/datadog/di.rb +61 -5
  162. data/lib/datadog/error_tracking/filters.rb +2 -2
  163. data/lib/datadog/kit/appsec/events/v2.rb +2 -3
  164. data/lib/datadog/open_feature/evaluation_engine.rb +2 -1
  165. data/lib/datadog/open_feature/remote.rb +3 -10
  166. data/lib/datadog/open_feature/transport.rb +9 -11
  167. data/lib/datadog/opentelemetry/api/baggage.rb +1 -1
  168. data/lib/datadog/opentelemetry/configuration/settings.rb +2 -2
  169. data/lib/datadog/opentelemetry/metrics.rb +21 -14
  170. data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +5 -8
  171. data/lib/datadog/profiling/collectors/code_provenance.rb +27 -2
  172. data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +3 -2
  173. data/lib/datadog/profiling/collectors/info.rb +5 -4
  174. data/lib/datadog/profiling/component.rb +25 -11
  175. data/lib/datadog/profiling/exporter.rb +4 -0
  176. data/lib/datadog/profiling/ext/dir_monkey_patches.rb +18 -0
  177. data/lib/datadog/profiling/ext/exec_monkey_patch.rb +32 -0
  178. data/lib/datadog/profiling/flush.rb +3 -0
  179. data/lib/datadog/profiling/http_transport.rb +4 -1
  180. data/lib/datadog/profiling/profiler.rb +3 -5
  181. data/lib/datadog/profiling/scheduler.rb +8 -7
  182. data/lib/datadog/profiling/tag_builder.rb +1 -0
  183. data/lib/datadog/tracing/contrib/extensions.rb +10 -2
  184. data/lib/datadog/tracing/contrib/karafka/patcher.rb +31 -32
  185. data/lib/datadog/tracing/contrib/status_range_matcher.rb +2 -1
  186. data/lib/datadog/tracing/contrib/utils/quantization/hash.rb +3 -1
  187. data/lib/datadog/tracing/contrib/waterdrop/patcher.rb +6 -3
  188. data/lib/datadog/tracing/contrib/waterdrop.rb +4 -0
  189. data/lib/datadog/tracing/diagnostics/environment_logger.rb +1 -1
  190. data/lib/datadog/tracing/distributed/baggage.rb +3 -2
  191. data/lib/datadog/tracing/remote.rb +1 -9
  192. data/lib/datadog/tracing/sampling/priority_sampler.rb +3 -1
  193. data/lib/datadog/tracing/sampling/rate_sampler.rb +8 -19
  194. data/lib/datadog/tracing/span.rb +1 -1
  195. data/lib/datadog/tracing/span_event.rb +2 -2
  196. data/lib/datadog/tracing/span_operation.rb +20 -9
  197. data/lib/datadog/tracing/trace_operation.rb +44 -6
  198. data/lib/datadog/tracing/tracer.rb +42 -16
  199. data/lib/datadog/tracing/transport/http/traces.rb +2 -50
  200. data/lib/datadog/tracing/transport/http.rb +15 -9
  201. data/lib/datadog/tracing/transport/io/client.rb +1 -1
  202. data/lib/datadog/tracing/transport/traces.rb +6 -66
  203. data/lib/datadog/tracing/workers/trace_writer.rb +5 -0
  204. data/lib/datadog/tracing/writer.rb +1 -0
  205. data/lib/datadog/version.rb +2 -2
  206. data/lib/datadog.rb +1 -0
  207. metadata +33 -19
  208. data/lib/datadog/core/remote/transport/http/api.rb +0 -53
  209. data/lib/datadog/core/telemetry/transport/http/api.rb +0 -43
  210. data/lib/datadog/core/transport/http/api/spec.rb +0 -36
  211. data/lib/datadog/data_streams/transport/http/api.rb +0 -33
  212. data/lib/datadog/data_streams/transport/http/client.rb +0 -21
  213. data/lib/datadog/di/transport/http/api.rb +0 -42
  214. data/lib/datadog/opentelemetry/api/baggage.rbs +0 -26
  215. data/lib/datadog/tracing/transport/http/api.rb +0 -44
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'api_client'
4
+ require_relative 'evaluation'
5
+ require_relative 'evaluation/request'
6
+ require_relative 'evaluation/result'
7
+ require_relative 'evaluation/no_op_result'
8
+ require_relative 'evaluation/message'
9
+ require_relative 'evaluation/tool_call'
10
+ require_relative 'ext'
11
+
12
+ module Datadog
13
+ module AIGuard
14
+ # Component for API Guard product
15
+ class Component
16
+ attr_reader :api_client, :logger
17
+
18
+ def self.build(settings, logger:, telemetry:)
19
+ return unless settings.respond_to?(:ai_guard) && settings.ai_guard.enabled
20
+
21
+ api_client = APIClient.new(
22
+ endpoint: settings.ai_guard.endpoint,
23
+ api_key: settings.api_key,
24
+ application_key: settings.ai_guard.app_key,
25
+ timeout: settings.ai_guard.timeout_ms / 1_000
26
+ )
27
+
28
+ new(api_client, logger: logger, telemetry: telemetry)
29
+ end
30
+
31
+ def initialize(api_client, logger:, telemetry:)
32
+ @api_client = api_client
33
+ @logger = logger
34
+ @telemetry = telemetry
35
+ end
36
+
37
+ def shutdown!
38
+ # no-op
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Configuration
6
+ # This module contains constants for AI Guard component
7
+ module Ext
8
+ ENV_AI_GUARD_ENABLED = "DD_AI_GUARD_ENABLED"
9
+ ENV_AI_GUARD_ENDPOINT = "DD_AI_GUARD_ENDPOINT"
10
+ ENV_AI_GUARD_TIMEOUT = "DD_AI_GUARD_TIMEOUT"
11
+ ENV_AI_GUARD_MAX_CONTENT_SIZE = "DD_AI_GUARD_MAX_CONTENT_SIZE"
12
+ ENV_AI_GUARD_MAX_MESSAGES_LENGTH = "DD_AI_GUARD_MAX_MESSAGES_LENGTH"
13
+ ENV_APP_KEY = "DD_APP_KEY"
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+ require_relative "ext"
5
+
6
+ module Datadog
7
+ module AIGuard
8
+ module Configuration
9
+ # AI Guard specific settings
10
+ module Settings
11
+ def self.extended(base)
12
+ base = base.singleton_class unless base.is_a?(Class)
13
+ add_settings!(base)
14
+ end
15
+
16
+ def self.add_settings!(base)
17
+ base.class_eval do
18
+ # AI Guard specific configurations.
19
+ # @public_api
20
+ settings :ai_guard do
21
+ # Enable AI Guard.
22
+ #
23
+ # You can use this option to skip calls to AI Guard API without having to remove library as a whole.
24
+ #
25
+ # @default `DD_AI_GUARD_ENABLED`, otherwise `false`
26
+ # @return [Boolean]
27
+ option :enabled do |o|
28
+ o.type :bool
29
+ o.env Ext::ENV_AI_GUARD_ENABLED
30
+ o.default false
31
+ end
32
+
33
+ define_method(:instrument) do |integration_name|
34
+ return unless enabled # steep:ignore
35
+
36
+ if (registered_integration = Datadog::AIGuard::Contrib::Integration.registry[integration_name])
37
+ klass = registered_integration.klass
38
+ if klass.loaded? && klass.compatible?
39
+ instance = klass.new
40
+ instance.patcher.patch unless instance.patcher.patched?
41
+ end
42
+ end
43
+ end
44
+
45
+ # AI Guard API endpoint path.
46
+ #
47
+ # @default `DD_AI_GUARD_ENDPOINT`, otherwise `nil`
48
+ # @return [String, nil]
49
+ option :endpoint do |o|
50
+ o.type :string, nilable: true
51
+ o.env Ext::ENV_AI_GUARD_ENDPOINT
52
+
53
+ o.setter do |value|
54
+ next unless value
55
+
56
+ uri = URI(value.to_s)
57
+ raise ArgumentError, "Please provide an absolute URI that includes a protocol" unless uri.absolute?
58
+
59
+ uri.to_s.delete_suffix("/")
60
+ end
61
+ end
62
+
63
+ # Datadog Application key.
64
+ #
65
+ # @default `DD_APP_KEY` environment variable, otherwise `nil`
66
+ # @return [String, nil]
67
+ option :app_key do |o|
68
+ o.type :string, nilable: true
69
+ o.env Ext::ENV_APP_KEY
70
+ end
71
+
72
+ # Request timeout in milliseconds.
73
+ #
74
+ # @default `DD_AI_GUARD_TIMEOUT`, otherwise 10 000 ms
75
+ # @return [Integer]
76
+ option :timeout_ms do |o|
77
+ o.type :int
78
+ o.env Ext::ENV_AI_GUARD_TIMEOUT
79
+ o.default 10_000
80
+ end
81
+
82
+ # Maximum content size in bytes.
83
+ # Content that exceeds the maximum allowed size is truncated before
84
+ # being stored in the current span context.
85
+ #
86
+ # @default `DD_AI_GUARD_MAX_CONTENT_SIZE`, otherwise 524 228 bytes
87
+ # @return [Integer]
88
+ option :max_content_size_bytes do |o|
89
+ o.type :int
90
+ o.env Ext::ENV_AI_GUARD_MAX_CONTENT_SIZE
91
+ o.default 512 * 1024
92
+ end
93
+
94
+ # Maximum number of messages.
95
+ # Older messages are omitted once the message limit is reached.
96
+ #
97
+ # @default `DD_AI_GUARD_MAX_MESSAGES_LENGTH`, otherwise 16 messages
98
+ # @return [Integer]
99
+ option :max_messages_length do |o|
100
+ o.type :int
101
+ o.env Ext::ENV_AI_GUARD_MAX_MESSAGES_LENGTH
102
+ o.default 16
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ # Configuration module for AI Guard
6
+ module Configuration
7
+ end
8
+ end
9
+ end
10
+
11
+ require_relative "configuration/settings"
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Contrib
6
+ # Base provides features that are shared across all integrations
7
+ module Integration
8
+ RegisteredIntegration = Struct.new(:name, :klass, :options)
9
+
10
+ @registry = {}
11
+
12
+ # Class-level methods for Integration
13
+ module ClassMethods
14
+ def register_as(name, options = {})
15
+ Integration.register(self, name, options)
16
+ end
17
+
18
+ def compatible?
19
+ true
20
+ end
21
+ end
22
+
23
+ def self.included(base)
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ def self.register(integration, name, options)
28
+ @registry[name] = RegisteredIntegration.new(name, integration, options)
29
+ end
30
+
31
+ def self.registry
32
+ @registry
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Contrib
6
+ module RubyLLM
7
+ # module that gets prepended to RubyLLM::Chat
8
+ module ChatInstrumentation
9
+ class << self
10
+ def evaluate!(messages)
11
+ ai_guard_messages = messages.flat_map do |message|
12
+ if message.tool_call?
13
+ message.tool_calls.map do |tool_call_id, tool_call|
14
+ AIGuard.assistant(id: tool_call_id, tool_name: tool_call.name, arguments: tool_call.arguments.to_s)
15
+ end
16
+ elsif message.tool_result?
17
+ AIGuard.tool(tool_call_id: message.tool_call_id, content: message.content)
18
+ else
19
+ AIGuard.message(role: message.role, content: message.content)
20
+ end
21
+ end
22
+
23
+ AIGuard.evaluate(*ai_guard_messages, allow_raise: true)
24
+ end
25
+ end
26
+
27
+ def complete(&block)
28
+ Datadog::AIGuard::Contrib::RubyLLM::ChatInstrumentation.evaluate!(messages)
29
+
30
+ super
31
+ end
32
+
33
+ def handle_tool_calls(response, &block)
34
+ Datadog::AIGuard::Contrib::RubyLLM::ChatInstrumentation.evaluate!(messages)
35
+
36
+ super
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../integration"
4
+ require_relative "patcher"
5
+
6
+ module Datadog
7
+ module AIGuard
8
+ module Contrib
9
+ module RubyLLM
10
+ # This class provides helper methods that are used when patching RubyLLM
11
+ class Integration
12
+ include Datadog::AIGuard::Contrib::Integration
13
+
14
+ MINIMUM_VERSION = Gem::Version.new("1.0.0")
15
+
16
+ register_as :ruby_llm, auto_patch: false
17
+
18
+ def self.version
19
+ Gem.loaded_specs["ruby_llm"]&.version
20
+ end
21
+
22
+ def self.loaded?
23
+ !defined?(::RubyLLM).nil?
24
+ end
25
+
26
+ def self.compatible?
27
+ super && !!(version&.>= MINIMUM_VERSION)
28
+ end
29
+
30
+ def self.auto_instrument?
31
+ false
32
+ end
33
+
34
+ def patcher
35
+ Patcher
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "chat_instrumentation"
4
+
5
+ module Datadog
6
+ module AIGuard
7
+ module Contrib
8
+ module RubyLLM
9
+ # AIGuard patcher module for RubyLLM
10
+ module Patcher
11
+ module_function
12
+
13
+ def patched?
14
+ !!Patcher.instance_variable_get(:@patched)
15
+ end
16
+
17
+ def target_version
18
+ Integration.version
19
+ end
20
+
21
+ def patch
22
+ ::RubyLLM::Chat.prepend(ChatInstrumentation)
23
+
24
+ Patcher.instance_variable_set(:@patched, true)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Message class for AI Guard
7
+ class Message
8
+ attr_reader :role, :content, :tool_call, :tool_call_id
9
+
10
+ def initialize(role:, content: nil, tool_call: nil, tool_call_id: nil)
11
+ raise ArgumentError, "Role must be set to a non-empty value" if role.to_s.empty?
12
+
13
+ @role = role.to_sym
14
+ @content = content
15
+ @tool_call = tool_call
16
+ @tool_call_id = tool_call_id
17
+
18
+ if @tool_call && !@tool_call.is_a?(ToolCall)
19
+ raise ArgumentError, "Expected an instance of #{ToolCall.name} for :tool_call argument"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Class for emulating AI Guard evaluation result when AI Guard is disabled.
7
+ class NoOpResult
8
+ attr_reader :action, :reason, :tags
9
+
10
+ def initialize
11
+ @action = Result::ALLOW_ACTION
12
+ @reason = "AI Guard is disabled"
13
+ @tags = []
14
+ end
15
+
16
+ def allow?
17
+ true
18
+ end
19
+
20
+ def deny?
21
+ false
22
+ end
23
+
24
+ def abort?
25
+ false
26
+ end
27
+
28
+ def blocking_enabled?
29
+ false
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Request builds the request body from an array of messages and processes the response
7
+ class Request
8
+ REQUEST_PATH = "/evaluate"
9
+
10
+ attr_reader :serialized_messages
11
+
12
+ def initialize(messages)
13
+ @serialized_messages = serialize_messages(messages)
14
+ end
15
+
16
+ def perform
17
+ api_client = AIGuard.api_client
18
+
19
+ # This should never happen, as we are only calling this method when AI Guard is enabled,
20
+ # and this means the API Client was not initialized properly.
21
+ #
22
+ # Please report this at https://github.com/datadog/dd-trace-rb/blob/master/CONTRIBUTING.md#found-a-bug
23
+ raise "AI Guard API Client not initialized" unless api_client
24
+
25
+ raw_response = api_client.post(REQUEST_PATH, body: build_request_body)
26
+
27
+ Result.new(raw_response)
28
+ end
29
+
30
+ private
31
+
32
+ def build_request_body
33
+ {
34
+ data: {
35
+ attributes: {
36
+ messages: @serialized_messages,
37
+ meta: {
38
+ service: Datadog.configuration.service,
39
+ env: Datadog.configuration.env
40
+ }
41
+ }
42
+ }
43
+ }
44
+ end
45
+
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
56
+ end
57
+
58
+ def serialize_message(message)
59
+ if message.tool_call
60
+ {
61
+ role: message.role,
62
+ tool_calls: [
63
+ {
64
+ id: message.tool_call.id,
65
+ function: {
66
+ name: message.tool_call.tool_name,
67
+ arguments: message.tool_call.arguments
68
+ }
69
+ }
70
+ ]
71
+ }
72
+ elsif message.tool_call_id
73
+ {role: message.role, tool_call_id: message.tool_call_id, content: message.content}
74
+ else
75
+ {role: message.role, content: message.content}
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Wrapper class for evaluation API response
7
+ class Result
8
+ ALLOW_ACTION = "ALLOW"
9
+ DENY_ACTION = "DENY"
10
+ ABORT_ACTION = "ABORT"
11
+
12
+ attr_reader :action, :reason, :tags
13
+
14
+ def initialize(raw_response)
15
+ attributes = raw_response.fetch("data").fetch("attributes")
16
+
17
+ @action = attributes.fetch("action")
18
+ @reason = attributes.fetch("reason")
19
+ @tags = attributes.fetch("tags")
20
+ @is_blocking_enabled = attributes.fetch("is_blocking_enabled")
21
+ rescue KeyError => e
22
+ raise AIGuardClientError, "Missing key: \"#{e.key}\""
23
+ end
24
+
25
+ def allow?
26
+ action == ALLOW_ACTION
27
+ end
28
+
29
+ def deny?
30
+ action == DENY_ACTION
31
+ end
32
+
33
+ def abort?
34
+ action == ABORT_ACTION
35
+ end
36
+
37
+ def blocking_enabled?
38
+ !!@is_blocking_enabled
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ module Evaluation
6
+ # Tool call class for AI Guard
7
+ class ToolCall
8
+ attr_reader :tool_name, :id, :arguments
9
+
10
+ def initialize(tool_name, id:, arguments:)
11
+ @tool_name = tool_name
12
+ @id = id
13
+ @arguments = arguments
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ # module that contains a function for performing AI Guard Evaluation request
6
+ # and creating `ai_guard` span with required tags
7
+ module Evaluation
8
+ class << self
9
+ def perform(messages, allow_raise: false)
10
+ raise ArgumentError, "Messages must not be empty" if messages&.empty?
11
+
12
+ Tracing.trace(Ext::SPAN_NAME) do |span, trace|
13
+ if (last_message = messages.last)
14
+ if last_message.tool_call
15
+ span.set_tag(Ext::TARGET_TAG, "tool")
16
+ span.set_tag(Ext::TOOL_NAME_TAG, last_message.tool_call.tool_name)
17
+ elsif last_message.tool_call_id
18
+ span.set_tag(Ext::TARGET_TAG, "tool")
19
+
20
+ if (tool_call_message = messages.find { |m| m.tool_call&.id == last_message.tool_call_id })
21
+ span.set_tag(Ext::TOOL_NAME_TAG, tool_call_message.tool_call.tool_name) # steep:ignore
22
+ end
23
+ else
24
+ span.set_tag(Ext::TARGET_TAG, "prompt")
25
+ end
26
+ end
27
+
28
+ request = Request.new(messages)
29
+ result = request.perform
30
+
31
+ span.set_tag(Ext::ACTION_TAG, result.action)
32
+ span.set_tag(Ext::REASON_TAG, result.reason)
33
+
34
+ span.set_metastruct_tag(
35
+ Ext::METASTRUCT_TAG,
36
+ {
37
+ messages: truncate_content(request.serialized_messages),
38
+ attack_categories: result.tags
39
+ }
40
+ )
41
+
42
+ if allow_raise && (result.deny? || result.abort?) && result.blocking_enabled?
43
+ span.set_tag(Ext::BLOCKED_TAG, true)
44
+ raise AIGuardAbortError.new(action: result.action, reason: result.reason, tags: result.tags)
45
+ end
46
+
47
+ result
48
+ end
49
+ end
50
+
51
+ def perform_no_op
52
+ AIGuard.logger&.warn("AI Guard is disabled, messages were not evaluated")
53
+
54
+ NoOpResult.new
55
+ end
56
+
57
+ private
58
+
59
+ def truncate_content(serialized_messages)
60
+ serialized_messages.map do |message| # steep:ignore
61
+ next message unless message[:content]
62
+
63
+ {
64
+ **message,
65
+ content: message[:content].byteslice(0, Datadog.configuration.ai_guard.max_content_size_bytes)
66
+ }
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Datadog
4
+ module AIGuard
5
+ # AI Guard specific constants
6
+ module Ext
7
+ SPAN_NAME = "ai_guard"
8
+ TARGET_TAG = "ai_guard.target"
9
+ TOOL_NAME_TAG = "ai_guard.tool_name"
10
+ ACTION_TAG = "ai_guard.action"
11
+ REASON_TAG = "ai_guard.reason"
12
+ BLOCKED_TAG = "ai_guard.blocked"
13
+ METASTRUCT_TAG = "ai_guard"
14
+ end
15
+ end
16
+ end