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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -1
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +17 -7
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +11 -4
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +6 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +18 -0
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +10 -0
- data/ext/datadog_profiling_native_extension/extconf.rb +7 -4
- data/ext/datadog_profiling_native_extension/http_transport.c +10 -5
- data/ext/libdatadog_api/crashtracker.c +5 -8
- data/ext/libdatadog_api/datadog_ruby_common.c +18 -0
- data/ext/libdatadog_api/datadog_ruby_common.h +10 -0
- data/ext/libdatadog_api/di.c +127 -0
- data/ext/libdatadog_api/extconf.rb +9 -4
- data/ext/libdatadog_api/init.c +5 -2
- data/ext/libdatadog_extconf_helpers.rb +46 -1
- data/lib/datadog/ai_guard/component.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +105 -2
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +41 -3
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +36 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +3 -1
- data/lib/datadog/ai_guard/evaluation/request.rb +14 -9
- data/lib/datadog/ai_guard/evaluation/result.rb +3 -1
- data/lib/datadog/ai_guard/evaluation.rb +37 -7
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard.rb +26 -8
- data/lib/datadog/appsec/autoload.rb +1 -1
- data/lib/datadog/appsec/component.rb +11 -7
- data/lib/datadog/appsec/configuration.rb +414 -1
- data/lib/datadog/appsec/contrib/devise/patches/signin_tracking_patch.rb +2 -1
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +6 -7
- data/lib/datadog/appsec/instrumentation/gateway.rb +0 -13
- data/lib/datadog/appsec/metrics/telemetry.rb +13 -1
- data/lib/datadog/appsec/monitor/gateway/watcher.rb +2 -0
- data/lib/datadog/appsec/security_engine/runner.rb +1 -1
- data/lib/datadog/appsec/trace_keeper.rb +18 -6
- data/lib/datadog/appsec/utils/http/media_type.rb +1 -2
- data/lib/datadog/appsec/utils/http/url_encoded.rb +3 -3
- data/lib/datadog/appsec.rb +5 -9
- data/lib/datadog/core/configuration/base.rb +17 -5
- data/lib/datadog/core/configuration/components.rb +22 -9
- data/lib/datadog/core/configuration/config_helper.rb +9 -0
- data/lib/datadog/core/configuration/option.rb +30 -5
- data/lib/datadog/core/configuration/option_definition.rb +38 -12
- data/lib/datadog/core/configuration/options.rb +40 -6
- data/lib/datadog/core/configuration/settings.rb +18 -0
- data/lib/datadog/core/configuration/supported_configurations.rb +3 -0
- data/lib/datadog/core/configuration.rb +1 -1
- data/lib/datadog/core/contrib/rails/railtie.rb +32 -0
- data/lib/datadog/core/contrib/rails/utils.rb +7 -3
- data/lib/datadog/core/crashtracking/component.rb +3 -3
- data/lib/datadog/core/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/core/environment/container.rb +2 -2
- data/lib/datadog/core/environment/ext.rb +1 -0
- data/lib/datadog/core/environment/identity.rb +25 -3
- data/lib/datadog/core/environment/process.rb +12 -0
- data/lib/datadog/core/feature_flags.rb +1 -1
- data/lib/datadog/core/metrics/client.rb +5 -5
- data/lib/datadog/core/remote/client.rb +1 -1
- data/lib/datadog/core/remote/component.rb +38 -21
- data/lib/datadog/core/runtime/metrics.rb +1 -1
- data/lib/datadog/core/telemetry/component.rb +3 -0
- data/lib/datadog/core/telemetry/emitter.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +2 -3
- data/lib/datadog/core/telemetry/event/app_extended_heartbeat.rb +32 -0
- data/lib/datadog/core/telemetry/event/app_started.rb +151 -169
- data/lib/datadog/core/telemetry/event.rb +1 -7
- data/lib/datadog/core/telemetry/ext.rb +1 -0
- data/lib/datadog/core/telemetry/transport/http/telemetry.rb +5 -0
- data/lib/datadog/core/telemetry/worker.rb +20 -0
- data/lib/datadog/core/transport/http.rb +2 -0
- data/lib/datadog/core/utils/only_once.rb +1 -1
- data/lib/datadog/core/utils/spawn_monkey_patch.rb +36 -0
- data/lib/datadog/core/utils.rb +1 -1
- data/lib/datadog/core/workers/async.rb +1 -1
- data/lib/datadog/core.rb +1 -2
- data/lib/datadog/data_streams/configuration.rb +40 -1
- data/lib/datadog/data_streams/pathway_context.rb +1 -1
- data/lib/datadog/data_streams/processor.rb +1 -1
- data/lib/datadog/data_streams.rb +1 -1
- data/lib/datadog/di/base.rb +8 -5
- data/lib/datadog/di/boot.rb +2 -4
- data/lib/datadog/di/code_tracker.rb +179 -1
- data/lib/datadog/di/component.rb +5 -1
- data/lib/datadog/di/configuration.rb +235 -2
- data/lib/datadog/di/instrumenter.rb +55 -29
- data/lib/datadog/di/probe_builder.rb +1 -1
- data/lib/datadog/di/probe_file_loader.rb +2 -2
- data/lib/datadog/di/probe_manager.rb +6 -6
- data/lib/datadog/di/probe_notification_builder.rb +110 -2
- data/lib/datadog/di/probe_notifier_worker.rb +2 -2
- data/lib/datadog/di/remote.rb +6 -6
- data/lib/datadog/di/transport/input.rb +3 -3
- data/lib/datadog/di.rb +81 -0
- data/lib/datadog/error_tracking/configuration.rb +55 -2
- data/lib/datadog/kit/enable_core_dumps.rb +1 -1
- data/lib/datadog/open_feature/component.rb +18 -1
- data/lib/datadog/open_feature/evaluation_engine.rb +2 -2
- data/lib/datadog/open_feature/hooks/flag_eval_hook.rb +49 -0
- data/lib/datadog/open_feature/metrics/flag_eval_metrics.rb +149 -0
- data/lib/datadog/open_feature/provider.rb +19 -1
- data/lib/datadog/open_feature/remote.rb +1 -1
- data/lib/datadog/open_feature/transport.rb +1 -1
- data/lib/datadog/opentelemetry/configuration/settings.rb +2 -0
- data/lib/datadog/opentelemetry/metrics.rb +3 -3
- data/lib/datadog/opentelemetry/sdk/configurator.rb +1 -1
- data/lib/datadog/opentelemetry/sdk/metrics_exporter.rb +1 -1
- data/lib/datadog/profiling/collectors/code_provenance.rb +36 -11
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +31 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +8 -2
- data/lib/datadog/profiling/collectors/info.rb +16 -3
- data/lib/datadog/profiling/component.rb +12 -4
- data/lib/datadog/profiling/exporter.rb +37 -12
- data/lib/datadog/profiling/ext.rb +0 -2
- data/lib/datadog/profiling/flush.rb +21 -12
- data/lib/datadog/profiling/http_transport.rb +12 -1
- data/lib/datadog/profiling/load_native_extension.rb +2 -2
- data/lib/datadog/profiling/profiler.rb +13 -5
- data/lib/datadog/profiling/scheduler.rb +2 -2
- data/lib/datadog/profiling/tasks/exec.rb +8 -3
- data/lib/datadog/profiling/tasks/help.rb +1 -0
- data/lib/datadog/profiling/tasks/setup.rb +2 -2
- data/lib/datadog/profiling.rb +1 -2
- data/lib/datadog/single_step_instrument.rb +1 -1
- data/lib/datadog/symbol_database/configuration.rb +65 -0
- data/lib/datadog/symbol_database/extractor.rb +915 -0
- data/lib/datadog/symbol_database/file_hash.rb +46 -0
- data/lib/datadog/symbol_database/logger.rb +43 -0
- data/lib/datadog/symbol_database/scope.rb +98 -0
- data/lib/datadog/symbol_database/service_version.rb +57 -0
- data/lib/datadog/symbol_database/symbol.rb +66 -0
- data/lib/datadog/symbol_database/transport/http/endpoint.rb +28 -0
- data/lib/datadog/symbol_database/transport/http.rb +45 -0
- data/lib/datadog/symbol_database/transport.rb +54 -0
- data/lib/datadog/symbol_database/uploader.rb +166 -0
- data/lib/datadog/symbol_database.rb +49 -0
- data/lib/datadog/tracing/buffer.rb +3 -3
- data/lib/datadog/tracing/component.rb +11 -0
- data/lib/datadog/tracing/configuration/settings.rb +2 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +5 -3
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/instrumentation.rb +20 -0
- data/lib/datadog/tracing/contrib/action_pack/action_dispatch/patcher.rb +3 -1
- data/lib/datadog/tracing/contrib/action_view/events/render_template.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/discard.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_at.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/enqueue_retry.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/perform.rb +1 -1
- data/lib/datadog/tracing/contrib/active_job/events/retry_stopped.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/render.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events/serialize.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +2 -2
- data/lib/datadog/tracing/contrib/active_record/events/instantiation.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/notifications/subscription.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/instrumentation.rb +1 -1
- data/lib/datadog/tracing/contrib/component.rb +1 -1
- data/lib/datadog/tracing/contrib/configurable.rb +18 -3
- data/lib/datadog/tracing/contrib/configuration/resolver.rb +7 -4
- data/lib/datadog/tracing/contrib/dalli/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/elasticsearch/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/excon/middleware.rb +2 -2
- data/lib/datadog/tracing/contrib/extensions.rb +9 -0
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +2 -2
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +5 -5
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +2 -2
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +2 -2
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +6 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/kafka/instrumentation/consumer.rb +2 -2
- data/lib/datadog/tracing/contrib/kafka/instrumentation/producer.rb +2 -2
- data/lib/datadog/tracing/contrib/karafka/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/subscribers.rb +3 -3
- data/lib/datadog/tracing/contrib/opensearch/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/presto/instrumentation.rb +3 -3
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/request_queue.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/log_injection.rb +1 -1
- data/lib/datadog/tracing/contrib/rails/patcher.rb +0 -1
- data/lib/datadog/tracing/contrib/rails/runner.rb +1 -1
- data/lib/datadog/tracing/contrib/rake/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/redis/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/tags.rb +1 -1
- data/lib/datadog/tracing/contrib/sidekiq/utils.rb +1 -1
- data/lib/datadog/tracing/contrib/status_range_matcher.rb +4 -0
- data/lib/datadog/tracing/contrib/stripe/request.rb +1 -1
- data/lib/datadog/tracing/contrib.rb +8 -0
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -1
- data/lib/datadog/tracing/distributed/baggage.rb +59 -5
- data/lib/datadog/tracing/distributed/datadog.rb +13 -11
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +1 -1
- data/lib/datadog/tracing/distributed/propagation.rb +2 -2
- data/lib/datadog/tracing/distributed/trace_context.rb +74 -32
- data/lib/datadog/tracing/event.rb +1 -1
- data/lib/datadog/tracing/metadata/tagging.rb +2 -2
- data/lib/datadog/tracing/pipeline.rb +1 -1
- data/lib/datadog/tracing/remote.rb +1 -1
- data/lib/datadog/tracing/sampling/ext.rb +2 -0
- data/lib/datadog/tracing/sampling/priority_sampler.rb +13 -0
- data/lib/datadog/tracing/sampling/rule.rb +1 -1
- data/lib/datadog/tracing/sampling/rule_sampler.rb +54 -25
- data/lib/datadog/tracing/sampling/span/rule_parser.rb +2 -2
- data/lib/datadog/tracing/span_operation.rb +4 -4
- data/lib/datadog/tracing/trace_operation.rb +53 -9
- data/lib/datadog/tracing/tracer.rb +29 -4
- data/lib/datadog/tracing/transport/io/client.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +1 -1
- data/lib/datadog/tracing/workers.rb +2 -1
- data/lib/datadog/version.rb +1 -1
- metadata +27 -12
- data/lib/datadog/ai_guard/configuration/settings.rb +0 -113
- data/lib/datadog/appsec/configuration/settings.rb +0 -423
- data/lib/datadog/data_streams/configuration/settings.rb +0 -49
- data/lib/datadog/di/configuration/settings.rb +0 -243
- 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
|
-
|
|
17
|
+
build_ai_guard_tool(message)
|
|
18
18
|
else
|
|
19
|
-
|
|
19
|
+
build_ai_guard_message(message)
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
AIGuard.evaluate(*ai_guard_messages
|
|
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
|
-
|
|
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:
|
|
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
|
-
|
|
65
|
-
|
|
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
|
data/lib/datadog/ai_guard/ext.rb
CHANGED
data/lib/datadog/ai_guard.rb
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
|
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
|
|
49
|
-
rescue
|
|
50
|
-
Datadog.logger.warn(
|
|
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
|
|
78
|
+
attr_reader :security_engine
|
|
74
79
|
|
|
75
|
-
def initialize(security_engine
|
|
80
|
+
def initialize(security_engine:)
|
|
76
81
|
@security_engine = security_engine
|
|
77
|
-
@telemetry = telemetry
|
|
78
82
|
end
|
|
79
83
|
|
|
80
84
|
def reconfigure!
|