datadog 2.39.0 → 2.47.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 +269 -44
- data/README.md +3 -0
- data/ext/datadog_profiling_native_extension/NativeExtensionDesign.md +12 -24
- data/ext/datadog_profiling_native_extension/collectors_cpu_and_wall_time_worker.c +256 -193
- data/ext/datadog_profiling_native_extension/collectors_discrete_dynamic_sampler.c +1 -5
- data/ext/datadog_profiling_native_extension/collectors_gc_profiling_helper.c +0 -2
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +19 -20
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.h +5 -1
- data/ext/datadog_profiling_native_extension/collectors_stack.c +267 -171
- data/ext/datadog_profiling_native_extension/collectors_stack.h +17 -3
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +307 -217
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +10 -5
- data/ext/datadog_profiling_native_extension/crashtracking_runtime_stacks.c +5 -4
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.c +19 -11
- data/ext/datadog_profiling_native_extension/datadog_ruby_common.h +7 -10
- data/ext/datadog_profiling_native_extension/extconf.rb +67 -99
- data/ext/datadog_profiling_native_extension/heap_recorder.c +353 -257
- data/ext/datadog_profiling_native_extension/heap_recorder.h +50 -31
- data/ext/datadog_profiling_native_extension/native_extension_helpers.rb +3 -27
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +853 -496
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +42 -15
- data/ext/datadog_profiling_native_extension/profiling.c +30 -23
- data/ext/datadog_profiling_native_extension/ruby_helpers.c +4 -80
- data/ext/datadog_profiling_native_extension/ruby_helpers.h +4 -7
- data/ext/datadog_profiling_native_extension/setup_signal_handler.c +0 -2
- data/ext/datadog_profiling_native_extension/stack_recorder.c +116 -89
- data/ext/datadog_profiling_native_extension/stack_recorder.h +12 -5
- data/ext/datadog_profiling_native_extension/time_helpers.c +2 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +15 -15
- data/ext/libdatadog_api/datadog_ruby_common.c +19 -11
- data/ext/libdatadog_api/datadog_ruby_common.h +7 -10
- data/ext/libdatadog_api/di.c +43 -3
- data/ext/libdatadog_api/extconf.rb +6 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +9 -1
- data/ext/libdatadog_api/otel_thread_context.c +274 -0
- data/ext/libdatadog_api/otel_thread_context.h +5 -0
- data/ext/libdatadog_api/process_discovery.c +15 -1
- data/ext/libdatadog_api/trace_exporter.c +2078 -0
- data/ext/libdatadog_api/trace_exporter.h +5 -0
- data/ext/libdatadog_extconf_helpers.rb +14 -9
- data/lib/datadog/ai_guard/component.rb +17 -8
- data/lib/datadog/ai_guard/configuration/ext.rb +2 -0
- data/lib/datadog/ai_guard/configuration.rb +13 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/chat_instrumentation.rb +48 -55
- data/lib/datadog/ai_guard/contrib/ruby_llm/integration.rb +1 -1
- data/lib/datadog/ai_guard/contrib/ruby_llm/message_adapter.rb +76 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/patcher.rb +1 -0
- data/lib/datadog/ai_guard/contrib/ruby_llm/redaction_change_set.rb +135 -0
- data/lib/datadog/ai_guard/evaluation/client.rb +50 -0
- data/lib/datadog/ai_guard/evaluation/content_part.rb +12 -0
- data/lib/datadog/ai_guard/evaluation/message.rb +25 -7
- data/lib/datadog/ai_guard/evaluation/message_builder.rb +31 -0
- data/lib/datadog/ai_guard/evaluation/no_op_result.rb +6 -6
- data/lib/datadog/ai_guard/evaluation/outcome.rb +24 -0
- data/lib/datadog/ai_guard/evaluation/request.rb +8 -64
- data/lib/datadog/ai_guard/evaluation/response.rb +33 -0
- data/lib/datadog/ai_guard/evaluation/result.rb +11 -18
- data/lib/datadog/ai_guard/evaluation/tool_call.rb +15 -2
- data/lib/datadog/ai_guard/evaluation.rb +36 -14
- data/lib/datadog/ai_guard/ext.rb +4 -1
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +7 -5
- data/lib/datadog/ai_guard/metrics/telemetry.rb +43 -0
- data/lib/datadog/ai_guard/redaction/replacements.rb +97 -0
- data/lib/datadog/ai_guard/redaction/result.rb +29 -0
- data/lib/datadog/ai_guard/redaction.rb +80 -0
- data/lib/datadog/ai_guard.rb +58 -57
- data/lib/datadog/appsec/actions_handler/serializable_backtrace.rb +21 -42
- data/lib/datadog/appsec/api_security/endpoint_collection/grape_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/rails_route_serializer.rb +1 -1
- data/lib/datadog/appsec/api_security/endpoint_collection/sinatra_route_serializer.rb +1 -1
- data/lib/datadog/appsec/assets/blocked.html +1 -108
- data/lib/datadog/appsec/configuration.rb +11 -2
- data/lib/datadog/appsec/context.rb +1 -1
- data/lib/datadog/appsec/contrib/active_record/instrumentation.rb +1 -1
- data/lib/datadog/appsec/contrib/aws_lambda/waf_addresses.rb +2 -2
- data/lib/datadog/appsec/contrib/devise/configuration.rb +1 -1
- data/lib/datadog/appsec/contrib/devise/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/excon/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/faraday/ssrf_detection_middleware.rb +2 -2
- data/lib/datadog/appsec/contrib/graphql/gateway/watcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/ext.rb +2 -2
- data/lib/datadog/appsec/contrib/rack/gateway/watcher.rb +2 -2
- data/lib/datadog/appsec/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/appsec/contrib/rack/request_body_middleware.rb +6 -3
- data/lib/datadog/appsec/contrib/rails/gateway/watcher.rb +30 -3
- data/lib/datadog/appsec/contrib/rails/patches/render_to_body_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/rails/request_middleware.rb +2 -1
- data/lib/datadog/appsec/contrib/rest_client/request_ssrf_detection_patch.rb +2 -2
- data/lib/datadog/appsec/contrib/sinatra/gateway/watcher.rb +15 -3
- data/lib/datadog/appsec/contrib/sinatra/patches/json_patch.rb +8 -7
- data/lib/datadog/appsec/contrib/sinatra/request_middleware.rb +2 -1
- data/lib/datadog/appsec/processor/rule_loader.rb +12 -12
- data/lib/datadog/appsec/response.rb +1 -1
- data/lib/datadog/appsec/route_normalizer/route_pattern.rb +3 -3
- data/lib/datadog/appsec/security_engine/engine.rb +5 -4
- data/lib/datadog/core/configuration/components.rb +50 -19
- data/lib/datadog/core/configuration/components_state.rb +8 -1
- data/lib/datadog/core/configuration/option_definition.rb +1 -1
- data/lib/datadog/core/configuration/options.rb +1 -1
- data/lib/datadog/core/configuration/settings.rb +38 -43
- data/lib/datadog/core/configuration/supported_configurations.rb +18 -2
- data/lib/datadog/core/crashtracking/component.rb +5 -1
- data/lib/datadog/core/diagnostics/environment_logger.rb +36 -1
- data/lib/datadog/core/environment/gc.rb +1 -1
- data/lib/datadog/core/environment/vm_cache.rb +1 -1
- data/lib/datadog/core/environment/yjit.rb +2 -2
- data/lib/datadog/core/metrics/helpers.rb +1 -1
- data/lib/datadog/core/metrics/options.rb +2 -2
- data/lib/datadog/core/process_discovery.rb +6 -2
- data/lib/datadog/core/remote/client/capabilities.rb +32 -21
- data/lib/datadog/core/remote/client.rb +45 -4
- data/lib/datadog/core/remote/component.rb +30 -7
- data/lib/datadog/core/remote/configuration/repository.rb +2 -2
- data/lib/datadog/core/remote/dispatcher.rb +47 -6
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/core/telemetry/configuration_value.rb +38 -0
- data/lib/datadog/core/telemetry/event/app_client_configuration_change.rb +3 -1
- data/lib/datadog/core/telemetry/event/app_endpoints_loaded.rb +1 -1
- data/lib/datadog/core/telemetry/event/app_started.rb +5 -24
- data/lib/datadog/core/telemetry/event/generate_metrics.rb +1 -1
- data/lib/datadog/core/telemetry/event/log.rb +2 -2
- data/lib/datadog/core/telemetry/event/synth_app_client_configuration_change.rb +1 -1
- data/lib/datadog/core/telemetry/metric.rb +2 -2
- data/lib/datadog/core/telemetry/request.rb +2 -2
- data/lib/datadog/core/transport/http.rb +1 -1
- data/lib/datadog/core/transport/transport.rb +1 -1
- data/lib/datadog/core/utils/at_fork_monkey_patch.rb +170 -18
- data/lib/datadog/core/utils/sequence.rb +3 -6
- data/lib/datadog/core/utils/time.rb +16 -54
- data/lib/datadog/core/workers/runtime_metrics.rb +1 -1
- data/lib/datadog/data_streams/pathway_context.rb +20 -22
- data/lib/datadog/data_streams/processor.rb +45 -14
- data/lib/datadog/di/boot.rb +1 -0
- data/lib/datadog/di/capture_expression_evaluator/result.rb +30 -0
- data/lib/datadog/di/capture_expression_evaluator.rb +39 -9
- data/lib/datadog/di/component.rb +108 -3
- data/lib/datadog/di/configuration.rb +53 -2
- data/lib/datadog/di/context.rb +7 -2
- data/lib/datadog/di/contrib/active_record.rb +8 -2
- data/lib/datadog/di/el/compiler.rb +88 -12
- data/lib/datadog/di/el/evaluator.rb +82 -2
- data/lib/datadog/di/el/expression.rb +13 -2
- data/lib/datadog/di/evaluation_failure.rb +28 -0
- data/lib/datadog/di/guardrails_telemetry/reason.rb +87 -0
- data/lib/datadog/di/guardrails_telemetry.rb +203 -0
- data/lib/datadog/di/instrumenter.rb +106 -15
- data/lib/datadog/di/logger.rb +32 -10
- data/lib/datadog/di/probe.rb +9 -1
- data/lib/datadog/di/probe_builder.rb +7 -6
- data/lib/datadog/di/probe_manager.rb +56 -8
- data/lib/datadog/di/probe_notification_builder.rb +31 -28
- data/lib/datadog/di/probe_notifier_worker.rb +56 -6
- data/lib/datadog/di/proc_responder.rb +1 -1
- data/lib/datadog/di/remote.rb +49 -5
- data/lib/datadog/di/serializer.rb +172 -55
- data/lib/datadog/di/snapshot_event.rb +38 -0
- data/lib/datadog/di/transport/http.rb +13 -3
- data/lib/datadog/di/transport/input.rb +32 -7
- data/lib/datadog/error_tracking/component.rb +1 -1
- data/lib/datadog/kit/appsec/events/v2.rb +2 -2
- data/lib/datadog/open_feature/activation.rb +268 -0
- data/lib/datadog/open_feature/agentless/configuration_source.rb +246 -0
- data/lib/datadog/open_feature/agentless/parser.rb +43 -0
- data/lib/datadog/open_feature/agentless/response.rb +19 -0
- data/lib/datadog/open_feature/agentless/transport.rb +103 -0
- data/lib/datadog/open_feature/component.rb +85 -13
- data/lib/datadog/open_feature/configuration/agentless_endpoint.rb +90 -0
- data/lib/datadog/open_feature/configuration/source/resolution.rb +22 -0
- data/lib/datadog/open_feature/configuration/source.rb +43 -0
- data/lib/datadog/open_feature/configuration.rb +124 -0
- data/lib/datadog/open_feature/evaluation_engine.rb +29 -3
- data/lib/datadog/open_feature/exposures/batch_builder.rb +1 -1
- data/lib/datadog/open_feature/exposures/event.rb +15 -8
- data/lib/datadog/open_feature/ext.rb +19 -0
- data/lib/datadog/open_feature/flag_evaluation/aggregator.rb +236 -80
- data/lib/datadog/open_feature/flag_evaluation/writer.rb +179 -68
- data/lib/datadog/open_feature/hooks/evaluation_telemetry_opt_out_hook.rb +25 -0
- data/lib/datadog/open_feature/hooks/flag_eval_evp_hook.rb +24 -21
- data/lib/datadog/open_feature/native_evaluator.rb +20 -6
- data/lib/datadog/open_feature/noop_evaluator.rb +5 -0
- data/lib/datadog/open_feature/provider.rb +315 -10
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/open_feature/transport.rb +5 -0
- data/lib/datadog/open_feature.rb +31 -1
- data/lib/datadog/opentelemetry/sdk/propagator.rb +1 -1
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/opentelemetry/trace.rb +3 -0
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +10 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/profiling/collectors/info.rb +40 -13
- data/lib/datadog/profiling/collectors/thread_context.rb +6 -17
- data/lib/datadog/profiling/component.rb +33 -39
- data/lib/datadog/profiling/exporter.rb +10 -10
- data/lib/datadog/profiling/tasks/exec.rb +1 -1
- data/lib/datadog/profiling.rb +4 -0
- data/lib/datadog/symbol_database/extractor.rb +89 -107
- data/lib/datadog/tracing/component.rb +44 -2
- data/lib/datadog/tracing/configuration/dynamic/option.rb +4 -0
- data/lib/datadog/tracing/configuration/dynamic.rb +5 -3
- data/lib/datadog/tracing/configuration/ext.rb +4 -0
- data/lib/datadog/tracing/configuration/settings.rb +36 -0
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +17 -5
- data/lib/datadog/tracing/contrib/action_cable/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_mailer/events.rb +1 -1
- data/lib/datadog/tracing/contrib/action_pack/action_controller/instrumentation.rb +2 -2
- data/lib/datadog/tracing/contrib/action_view/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_model_serializers/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/active_record/events/sql.rb +1 -0
- data/lib/datadog/tracing/contrib/active_record/events.rb +1 -1
- data/lib/datadog/tracing/contrib/active_support/cache/events/cache.rb +2 -2
- data/lib/datadog/tracing/contrib/aws/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/aws/services.rb +1 -1
- data/lib/datadog/tracing/contrib/elasticsearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/ethon/easy_patch.rb +10 -5
- data/lib/datadog/tracing/contrib/excon/middleware.rb +10 -3
- data/lib/datadog/tracing/contrib/ext.rb +3 -3
- data/lib/datadog/tracing/contrib/faraday/middleware.rb +10 -4
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/client.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/datadog_interceptor/server.rb +1 -1
- data/lib/datadog/tracing/contrib/grpc/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/hanami/router_tracing.rb +2 -1
- data/lib/datadog/tracing/contrib/http/instrumentation.rb +16 -1
- data/lib/datadog/tracing/contrib/httpclient/instrumentation.rb +8 -2
- data/lib/datadog/tracing/contrib/httprb/instrumentation.rb +10 -4
- data/lib/datadog/tracing/contrib/kafka/events/connection/request.rb +4 -0
- data/lib/datadog/tracing/contrib/kafka/events/produce_operation/send_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events/producer/deliver_messages.rb +7 -2
- data/lib/datadog/tracing/contrib/kafka/events.rb +1 -1
- data/lib/datadog/tracing/contrib/karafka/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/mongodb/parsers.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/ext.rb +1 -1
- data/lib/datadog/tracing/contrib/opensearch/quantize.rb +1 -1
- data/lib/datadog/tracing/contrib/patchable.rb +1 -1
- data/lib/datadog/tracing/contrib/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/que/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/racecar/events.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/header_tagging.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/patcher.rb +1 -1
- data/lib/datadog/tracing/contrib/rack/trace_proxy_middleware.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/configuration/resolver.rb +1 -1
- data/lib/datadog/tracing/contrib/redis/quantize.rb +36 -2
- data/lib/datadog/tracing/contrib/redis/trace_middleware.rb +9 -3
- data/lib/datadog/tracing/contrib/rest_client/request_patch.rb +10 -3
- data/lib/datadog/tracing/contrib/sequel/database.rb +1 -5
- data/lib/datadog/tracing/contrib/sequel/jdbc_connection_string.rb +140 -0
- data/lib/datadog/tracing/contrib/sequel/utils.rb +50 -63
- data/lib/datadog/tracing/contrib/sidekiq/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/contrib/sinatra/configuration/settings.rb +1 -1
- data/lib/datadog/tracing/contrib/sneakers/tracer.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/hash_formatter.rb +1 -1
- data/lib/datadog/tracing/contrib/utils/quantization/http.rb +108 -3
- data/lib/datadog/tracing/contrib/waterdrop/distributed/propagation.rb +1 -1
- data/lib/datadog/tracing/correlation.rb +2 -2
- data/lib/datadog/tracing/diagnostics/environment_logger.rb +3 -2
- data/lib/datadog/tracing/distributed/baggage.rb +1 -3
- data/lib/datadog/tracing/distributed/datadog.rb +3 -3
- data/lib/datadog/tracing/distributed/propagation.rb +3 -0
- data/lib/datadog/tracing/distributed/propagation_policy.rb +5 -0
- data/lib/datadog/tracing/distributed/trace_context.rb +14 -271
- data/lib/datadog/tracing/distributed/trace_state/datadog.rb +233 -0
- data/lib/datadog/tracing/distributed/trace_state/ext.rb +44 -0
- data/lib/datadog/tracing/distributed/trace_state/open_telemetry.rb +156 -0
- data/lib/datadog/tracing/distributed/trace_state.rb +121 -0
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/metadata/tagging.rb +1 -1
- data/lib/datadog/tracing/otel_thread_context.rb +97 -0
- data/lib/datadog/tracing/remote.rb +236 -37
- data/lib/datadog/tracing/sampling/rule_sampler.rb +2 -0
- data/lib/datadog/tracing/span.rb +1 -1
- data/lib/datadog/tracing/span_operation.rb +1 -1
- data/lib/datadog/tracing/sync_writer.rb +8 -3
- data/lib/datadog/tracing/trace_digest.rb +22 -4
- data/lib/datadog/tracing/trace_operation.rb +22 -10
- data/lib/datadog/tracing/tracer.rb +28 -7
- data/lib/datadog/tracing/transport/http/telemetry.rb +94 -0
- data/lib/datadog/tracing/transport/http/traces.rb +2 -0
- data/lib/datadog/tracing/transport/io/traces.rb +1 -1
- data/lib/datadog/tracing/transport/native/response.rb +71 -0
- data/lib/datadog/tracing/transport/native/telemetry.rb +17 -0
- data/lib/datadog/tracing/transport/native.rb +395 -0
- data/lib/datadog/tracing/transport/span_events_negotiation.rb +41 -0
- data/lib/datadog/tracing/transport/telemetry.rb +62 -0
- data/lib/datadog/tracing/transport/trace_formatter.rb +14 -4
- data/lib/datadog/tracing/transport/traces.rb +85 -38
- data/lib/datadog/tracing/writer.rb +17 -4
- data/lib/datadog/version.rb +1 -1
- metadata +51 -13
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
- data/lib/datadog/appsec/utils/hash_coercion.rb +0 -23
- data/lib/datadog/tracing/distributed/datadog_tags_codec.rb +0 -69
|
@@ -5,14 +5,18 @@ 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, :sds_findings, :
|
|
8
|
+
attr_reader :action, :reason, :tags, :sds_findings, :redaction_replacements,
|
|
9
|
+
:tag_probabilities, :messages
|
|
9
10
|
|
|
10
|
-
def initialize
|
|
11
|
+
def initialize(messages)
|
|
11
12
|
@action = Result::ALLOW_ACTION
|
|
12
13
|
@reason = "AI Guard is disabled"
|
|
14
|
+
|
|
13
15
|
@tags = []
|
|
16
|
+
@messages = messages
|
|
14
17
|
@sds_findings = []
|
|
15
18
|
@tag_probabilities = {}
|
|
19
|
+
@redaction_replacements = []
|
|
16
20
|
end
|
|
17
21
|
|
|
18
22
|
def allow?
|
|
@@ -26,10 +30,6 @@ module Datadog
|
|
|
26
30
|
def abort?
|
|
27
31
|
false
|
|
28
32
|
end
|
|
29
|
-
|
|
30
|
-
def blocking_enabled?
|
|
31
|
-
false
|
|
32
|
-
end
|
|
33
33
|
end
|
|
34
34
|
end
|
|
35
35
|
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
module Evaluation
|
|
6
|
+
# Internal outcome of an evaluation request
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Outcome
|
|
10
|
+
attr_reader :result, :redaction
|
|
11
|
+
|
|
12
|
+
def initialize(result:, redaction:, blocking_enabled:)
|
|
13
|
+
@result = result
|
|
14
|
+
@redaction = redaction
|
|
15
|
+
@blocking_enabled = blocking_enabled
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def block?
|
|
19
|
+
@blocking_enabled && (result.deny? || result.abort?)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -3,83 +3,27 @@
|
|
|
3
3
|
module Datadog
|
|
4
4
|
module AIGuard
|
|
5
5
|
module Evaluation
|
|
6
|
-
# Request builds the request body
|
|
6
|
+
# Request builds the request body for the AI Guard /evaluate endpoint
|
|
7
7
|
class Request
|
|
8
8
|
REQUEST_PATH = "/evaluate"
|
|
9
9
|
|
|
10
|
-
attr_reader :serialized_messages
|
|
11
|
-
|
|
12
10
|
def initialize(messages)
|
|
13
|
-
@
|
|
11
|
+
@messages = messages
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
def
|
|
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
|
|
14
|
+
def body
|
|
33
15
|
{
|
|
34
16
|
data: {
|
|
35
17
|
attributes: {
|
|
36
|
-
messages: @
|
|
18
|
+
messages: @messages.map(&:to_h),
|
|
37
19
|
meta: {
|
|
38
20
|
service: Datadog.configuration.service,
|
|
39
|
-
env: Datadog.configuration.env
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
21
|
+
env: Datadog.configuration.env,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
43
25
|
}
|
|
44
26
|
end
|
|
45
|
-
|
|
46
|
-
def serialize_messages(messages)
|
|
47
|
-
messages.map { |message| serialize_message(message) }
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def serialize_message(message)
|
|
51
|
-
if message.tool_call
|
|
52
|
-
{
|
|
53
|
-
role: message.role,
|
|
54
|
-
tool_calls: [
|
|
55
|
-
{
|
|
56
|
-
id: message.tool_call.id,
|
|
57
|
-
function: {
|
|
58
|
-
name: message.tool_call.tool_name,
|
|
59
|
-
arguments: message.tool_call.arguments
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
]
|
|
63
|
-
}
|
|
64
|
-
elsif message.content.is_a?(::Array)
|
|
65
|
-
{role: message.role, content: serialize_content_parts(message.content)}
|
|
66
|
-
elsif message.tool_call_id
|
|
67
|
-
{role: message.role, tool_call_id: message.tool_call_id, content: message.content}
|
|
68
|
-
else
|
|
69
|
-
{role: message.role, content: message.content}
|
|
70
|
-
end
|
|
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
|
|
83
27
|
end
|
|
84
28
|
end
|
|
85
29
|
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
module Evaluation
|
|
6
|
+
# Wrapper class for evaluation API response
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Response
|
|
10
|
+
attr_reader :action, :reason, :tags, :sds_findings, :tag_probabilities,
|
|
11
|
+
:redaction_replacements
|
|
12
|
+
|
|
13
|
+
def initialize(raw_response)
|
|
14
|
+
attributes = raw_response.fetch("data").fetch("attributes")
|
|
15
|
+
|
|
16
|
+
@action = attributes.fetch("action")
|
|
17
|
+
@reason = attributes.fetch("reason")
|
|
18
|
+
@tags = attributes.fetch("tags")
|
|
19
|
+
@tag_probabilities = attributes.fetch("tag_probs")
|
|
20
|
+
@is_blocking_enabled = attributes.fetch("is_blocking_enabled")
|
|
21
|
+
@sds_findings = attributes.fetch("sds_findings", [])
|
|
22
|
+
@redaction_replacements = attributes.fetch("redaction_replacements", [])
|
|
23
|
+
rescue KeyError => e
|
|
24
|
+
raise AIGuardClientError, "Missing key: \"#{e.key}\""
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def blocking_enabled?
|
|
28
|
+
!!@is_blocking_enabled
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -3,25 +3,22 @@
|
|
|
3
3
|
module Datadog
|
|
4
4
|
module AIGuard
|
|
5
5
|
module Evaluation
|
|
6
|
-
# Wrapper class for evaluation API response
|
|
7
6
|
class Result
|
|
8
7
|
ALLOW_ACTION = "ALLOW"
|
|
9
8
|
DENY_ACTION = "DENY"
|
|
10
9
|
ABORT_ACTION = "ABORT"
|
|
11
10
|
|
|
12
|
-
attr_reader :action, :reason, :tags, :sds_findings,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@action =
|
|
18
|
-
@reason =
|
|
19
|
-
@tags =
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
-
rescue KeyError => e
|
|
24
|
-
raise AIGuardClientError, "Missing key: \"#{e.key}\""
|
|
11
|
+
attr_reader :messages, :action, :reason, :tags, :sds_findings,
|
|
12
|
+
:tag_probabilities, :redaction_replacements
|
|
13
|
+
|
|
14
|
+
def initialize(messages, action:, reason:, tags:, sds_findings:, tag_probabilities:, redaction_replacements:)
|
|
15
|
+
@messages = messages
|
|
16
|
+
@action = action
|
|
17
|
+
@reason = reason
|
|
18
|
+
@tags = tags
|
|
19
|
+
@sds_findings = sds_findings
|
|
20
|
+
@tag_probabilities = tag_probabilities
|
|
21
|
+
@redaction_replacements = redaction_replacements
|
|
25
22
|
end
|
|
26
23
|
|
|
27
24
|
def allow?
|
|
@@ -35,10 +32,6 @@ module Datadog
|
|
|
35
32
|
def abort?
|
|
36
33
|
action == ABORT_ACTION
|
|
37
34
|
end
|
|
38
|
-
|
|
39
|
-
def blocking_enabled?
|
|
40
|
-
!!@is_blocking_enabled
|
|
41
|
-
end
|
|
42
35
|
end
|
|
43
36
|
end
|
|
44
37
|
end
|
|
@@ -3,14 +3,27 @@
|
|
|
3
3
|
module Datadog
|
|
4
4
|
module AIGuard
|
|
5
5
|
module Evaluation
|
|
6
|
-
#
|
|
6
|
+
# Represents a tool invocation requested by an assistant message
|
|
7
7
|
class ToolCall
|
|
8
8
|
attr_reader :tool_name, :id, :arguments
|
|
9
9
|
|
|
10
10
|
def initialize(tool_name, id:, arguments:)
|
|
11
11
|
@tool_name = tool_name
|
|
12
12
|
@id = id
|
|
13
|
-
@arguments =
|
|
13
|
+
@arguments =
|
|
14
|
+
case arguments
|
|
15
|
+
when String then arguments
|
|
16
|
+
when Hash then JSON.generate(arguments)
|
|
17
|
+
else raise ArgumentError, "Tool call arguments must be a String or Hash"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def copy(arguments:)
|
|
22
|
+
ToolCall.new(tool_name, id: id, arguments: arguments)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_h
|
|
26
|
+
{id: id, function: {name: tool_name, arguments: arguments}}
|
|
14
27
|
end
|
|
15
28
|
end
|
|
16
29
|
end
|
|
@@ -15,6 +15,7 @@ module Datadog
|
|
|
15
15
|
Tracing::Metadata::Ext::Distributed::TAG_DECISION_MAKER,
|
|
16
16
|
Tracing::Sampling::Ext::Decision::AI_GUARD
|
|
17
17
|
)
|
|
18
|
+
trace.set_distributed_source(Ext::PRODUCT_BIT)
|
|
18
19
|
trace.set_tag(Ext::EVENT_TAG, true)
|
|
19
20
|
trace.set_tag(Ext::TRACE_EXECUTED_TAG, "1")
|
|
20
21
|
|
|
@@ -24,37 +25,51 @@ module Datadog
|
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
if (last_message = messages.last)
|
|
27
|
-
if last_message.
|
|
28
|
+
if (tool_call = last_message.tool_calls.first)
|
|
28
29
|
span.set_tag(Ext::TARGET_TAG, "tool")
|
|
29
|
-
span.set_tag(Ext::TOOL_NAME_TAG,
|
|
30
|
+
span.set_tag(Ext::TOOL_NAME_TAG, tool_call.tool_name)
|
|
30
31
|
elsif last_message.tool_call_id
|
|
31
32
|
span.set_tag(Ext::TARGET_TAG, "tool")
|
|
32
33
|
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
messages.reverse_each do |message|
|
|
35
|
+
# @type var tool_call: ToolCall?
|
|
36
|
+
tool_call = message.tool_calls.find { |call| call.id == last_message.tool_call_id }
|
|
37
|
+
break span.set_tag(Ext::TOOL_NAME_TAG, tool_call.tool_name) if tool_call
|
|
35
38
|
end
|
|
36
39
|
else
|
|
37
40
|
span.set_tag(Ext::TARGET_TAG, "prompt")
|
|
38
41
|
end
|
|
39
42
|
end
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
outcome =
|
|
45
|
+
begin
|
|
46
|
+
Client.evaluate(messages)
|
|
47
|
+
rescue
|
|
48
|
+
Metrics::Telemetry.report_error
|
|
49
|
+
raise
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
result = outcome.result
|
|
53
|
+
redaction = outcome.redaction
|
|
43
54
|
|
|
44
55
|
span.set_tag(Ext::ACTION_TAG, result.action)
|
|
45
56
|
span.set_tag(Ext::REASON_TAG, result.reason)
|
|
57
|
+
span.set_tag(Ext::REDACTED_TAG, redaction.redacted?) if redaction.performed?
|
|
46
58
|
|
|
47
59
|
span.set_metastruct_tag(
|
|
48
60
|
Ext::METASTRUCT_TAG,
|
|
49
61
|
{
|
|
50
|
-
messages: truncate_content(truncate_messages(
|
|
62
|
+
messages: truncate_content(truncate_messages(result.messages).map(&:to_h)),
|
|
51
63
|
attack_categories: result.tags,
|
|
52
64
|
sds: result.sds_findings,
|
|
53
|
-
tag_probs: result.tag_probabilities
|
|
65
|
+
tag_probs: result.tag_probabilities,
|
|
54
66
|
}
|
|
55
67
|
)
|
|
56
68
|
|
|
57
|
-
|
|
69
|
+
blocked = allow_raise && outcome.block?
|
|
70
|
+
Metrics::Telemetry.report_evaluation(outcome, blocked: blocked)
|
|
71
|
+
|
|
72
|
+
if blocked
|
|
58
73
|
span.set_tag(Ext::BLOCKED_TAG, true)
|
|
59
74
|
raise AIGuardAbortError.new(action: result.action, reason: result.reason, tags: result.tags)
|
|
60
75
|
end
|
|
@@ -63,10 +78,10 @@ module Datadog
|
|
|
63
78
|
end
|
|
64
79
|
end
|
|
65
80
|
|
|
66
|
-
def perform_no_op
|
|
81
|
+
def perform_no_op(messages)
|
|
67
82
|
AIGuard.logger&.warn("AI Guard is disabled, messages were not evaluated")
|
|
68
83
|
|
|
69
|
-
NoOpResult.new
|
|
84
|
+
NoOpResult.new(messages)
|
|
70
85
|
end
|
|
71
86
|
|
|
72
87
|
private
|
|
@@ -81,13 +96,13 @@ module Datadog
|
|
|
81
96
|
def truncate_content(serialized_messages)
|
|
82
97
|
max_bytes = Datadog.configuration.ai_guard.max_content_size_bytes
|
|
83
98
|
|
|
84
|
-
serialized_messages.map do |message|
|
|
99
|
+
serialized_messages.map do |message|
|
|
85
100
|
next message unless message[:content]
|
|
86
101
|
|
|
87
102
|
if message[:content].is_a?(::Array)
|
|
88
103
|
serialized_content = message[:content].map do |part|
|
|
89
104
|
if part[:text]
|
|
90
|
-
{**part, text: part[:text].to_s
|
|
105
|
+
{**part, text: truncate_content_value(part[:text].to_s, max_bytes)}
|
|
91
106
|
else
|
|
92
107
|
part
|
|
93
108
|
end
|
|
@@ -97,11 +112,18 @@ module Datadog
|
|
|
97
112
|
else
|
|
98
113
|
{
|
|
99
114
|
**message,
|
|
100
|
-
content: message[:content]
|
|
115
|
+
content: truncate_content_value(message[:content], max_bytes),
|
|
101
116
|
}
|
|
102
117
|
end
|
|
103
118
|
end
|
|
104
119
|
end
|
|
120
|
+
|
|
121
|
+
def truncate_content_value(content, max_bytes)
|
|
122
|
+
truncated = content.byteslice(0, max_bytes).to_s
|
|
123
|
+
return truncated if truncated.valid_encoding?
|
|
124
|
+
|
|
125
|
+
truncated.scrub("")
|
|
126
|
+
end
|
|
105
127
|
end
|
|
106
128
|
end
|
|
107
129
|
end
|
data/lib/datadog/ai_guard/ext.rb
CHANGED
|
@@ -4,12 +4,15 @@ module Datadog
|
|
|
4
4
|
module AIGuard
|
|
5
5
|
# AI Guard specific constants
|
|
6
6
|
module Ext
|
|
7
|
+
PRODUCT_BIT = 0x20
|
|
8
|
+
|
|
7
9
|
SPAN_NAME = "ai_guard"
|
|
8
10
|
TARGET_TAG = "ai_guard.target"
|
|
9
11
|
TOOL_NAME_TAG = "ai_guard.tool_name"
|
|
10
12
|
ACTION_TAG = "ai_guard.action"
|
|
11
13
|
REASON_TAG = "ai_guard.reason"
|
|
12
14
|
BLOCKED_TAG = "ai_guard.blocked"
|
|
15
|
+
REDACTED_TAG = "ai_guard.redacted"
|
|
13
16
|
EVENT_TAG = "ai_guard.event"
|
|
14
17
|
|
|
15
18
|
METASTRUCT_TAG = "ai_guard"
|
|
@@ -22,7 +25,7 @@ module Datadog
|
|
|
22
25
|
TRACE_ANOMALY_DETECTION_TAGS = [
|
|
23
26
|
TRACE_HTTP_USERAGENT_TAG,
|
|
24
27
|
TRACE_HTTP_CLIENT_IP_TAG,
|
|
25
|
-
TRACE_NETWORK_CLIENT_IP_TAG
|
|
28
|
+
TRACE_NETWORK_CLIENT_IP_TAG,
|
|
26
29
|
].freeze
|
|
27
30
|
end
|
|
28
31
|
end
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "uri"
|
|
4
|
-
require "net/http"
|
|
5
4
|
require "json"
|
|
5
|
+
require "net/http"
|
|
6
6
|
|
|
7
7
|
module Datadog
|
|
8
8
|
module AIGuard
|
|
9
|
-
#
|
|
10
|
-
# Uses net/http to perform
|
|
11
|
-
|
|
9
|
+
# HTTP client for the AI Guard API
|
|
10
|
+
# Uses net/http to perform requests, raises on client and server errors
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
13
|
+
class HTTPClient
|
|
12
14
|
DEFAULT_SITE = "app.datadoghq.com"
|
|
13
15
|
DEFAULT_PATH = "/api/v2/ai-guard"
|
|
14
16
|
|
|
@@ -32,7 +34,7 @@ module Datadog
|
|
|
32
34
|
"DD-AI-GUARD-VERSION": Datadog::VERSION::STRING,
|
|
33
35
|
"DD-AI-GUARD-SOURCE": "SDK",
|
|
34
36
|
"DD-AI-GUARD-LANGUAGE": "ruby",
|
|
35
|
-
"content-type": "application/json"
|
|
37
|
+
"content-type": "application/json",
|
|
36
38
|
}.freeze
|
|
37
39
|
end
|
|
38
40
|
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
# Namespace for metrics
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module Metrics
|
|
9
|
+
# A module for telemetry metrics reporting
|
|
10
|
+
module Telemetry
|
|
11
|
+
NAMESPACE = "ai_guard"
|
|
12
|
+
CLIENT_ERROR = "client_error"
|
|
13
|
+
REDACTION_ERROR = "redaction_error"
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def report_evaluation(outcome, blocked:)
|
|
18
|
+
telemetry = AIGuard.telemetry
|
|
19
|
+
return unless telemetry
|
|
20
|
+
|
|
21
|
+
result = outcome.result
|
|
22
|
+
redaction = outcome.redaction
|
|
23
|
+
|
|
24
|
+
tags = {action: result.action, block: blocked.to_s, error: "false"}
|
|
25
|
+
tags[:redacted] = redaction.redacted?.to_s if redaction.performed?
|
|
26
|
+
|
|
27
|
+
telemetry.inc(NAMESPACE, "requests", 1, tags: tags)
|
|
28
|
+
if redaction.failures_count.positive?
|
|
29
|
+
telemetry.inc(NAMESPACE, "error", redaction.failures_count, tags: {type: REDACTION_ERROR})
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def report_error
|
|
34
|
+
telemetry = AIGuard.telemetry
|
|
35
|
+
return unless telemetry
|
|
36
|
+
|
|
37
|
+
telemetry.inc(NAMESPACE, "requests", 1, tags: {error: "true"})
|
|
38
|
+
telemetry.inc(NAMESPACE, "error", 1, tags: {type: CLIENT_ERROR})
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "forwardable"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AIGuard
|
|
7
|
+
module Redaction
|
|
8
|
+
# Converts raw backend redaction replacements into a conflict-free
|
|
9
|
+
# path-to-replacement map
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class Replacements
|
|
13
|
+
extend Forwardable
|
|
14
|
+
|
|
15
|
+
TEXT_PATH_PATTERN = /\Amessages\[([0-9]+)\]\.content\[([0-9]+)\]\.text\z/
|
|
16
|
+
CONTENT_PATH_PATTERN = /\Amessages\[([0-9]+)\]\.content\z/
|
|
17
|
+
ARGUMENTS_PATH_PATTERN = /\Amessages\[([0-9]+)\]\.tool_calls\[([0-9]+)\]\.function\.arguments\z/
|
|
18
|
+
|
|
19
|
+
def_delegators :@replacements, :each, :empty?
|
|
20
|
+
|
|
21
|
+
attr_reader :failures_count
|
|
22
|
+
|
|
23
|
+
def initialize(raw_replacements)
|
|
24
|
+
@failures_count = 0
|
|
25
|
+
@replacements = build(raw_replacements)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# NOTE: Identical duplicates collapse into a single entry,
|
|
31
|
+
# conflicting replacements remove the path and record one failure,
|
|
32
|
+
# and malformed or unsupported entries are skipped and counted individually
|
|
33
|
+
#
|
|
34
|
+
# Examples
|
|
35
|
+
#
|
|
36
|
+
# translates this
|
|
37
|
+
#
|
|
38
|
+
# [
|
|
39
|
+
# {"path" => "messages[0].content", "replacement" => "<redacted>"},
|
|
40
|
+
# {"path" => "messages[1].content[2].text", "replacement" => "<redacted>"},
|
|
41
|
+
# {"path" => "messages[2].tool_calls[0].function.arguments", "replacement" => "{}"}
|
|
42
|
+
# ]
|
|
43
|
+
#
|
|
44
|
+
# into this
|
|
45
|
+
#
|
|
46
|
+
# {
|
|
47
|
+
# [0, :content] => "<redacted>",
|
|
48
|
+
# [1, :text, 2] => "<redacted>",
|
|
49
|
+
# [2, :arguments, 0] => "{}"
|
|
50
|
+
# }
|
|
51
|
+
def build(raw_replacements)
|
|
52
|
+
unless raw_replacements.is_a?(::Array)
|
|
53
|
+
@failures_count += 1
|
|
54
|
+
return {}
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
conflicting = {}
|
|
58
|
+
raw_replacements.each_with_object({}) do |entry, replacements|
|
|
59
|
+
next @failures_count += 1 unless entry.is_a?(::Hash)
|
|
60
|
+
|
|
61
|
+
raw_path = entry["path"]
|
|
62
|
+
replacement = entry["replacement"]
|
|
63
|
+
|
|
64
|
+
if !raw_path.is_a?(::String) || raw_path.empty? || !replacement.is_a?(::String)
|
|
65
|
+
next @failures_count += 1
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @type var path: Replacements::path?
|
|
69
|
+
path =
|
|
70
|
+
case raw_path
|
|
71
|
+
when CONTENT_PATH_PATTERN
|
|
72
|
+
[Regexp.last_match(1).to_i, :content]
|
|
73
|
+
when TEXT_PATH_PATTERN
|
|
74
|
+
[Regexp.last_match(1).to_i, :text, Regexp.last_match(2).to_i]
|
|
75
|
+
when ARGUMENTS_PATH_PATTERN
|
|
76
|
+
[Regexp.last_match(1).to_i, :arguments, Regexp.last_match(2).to_i]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
next @failures_count += 1 unless path
|
|
80
|
+
next if conflicting.key?(path)
|
|
81
|
+
|
|
82
|
+
if replacements.key?(path)
|
|
83
|
+
next if replacements[path] == replacement
|
|
84
|
+
|
|
85
|
+
replacements.delete(path)
|
|
86
|
+
conflicting[path] = true
|
|
87
|
+
|
|
88
|
+
next @failures_count += 1
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
replacements[path] = replacement
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
module Redaction
|
|
6
|
+
# A result of applying redaction replacements
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
9
|
+
class Result
|
|
10
|
+
attr_reader :messages, :applied_count, :failures_count
|
|
11
|
+
|
|
12
|
+
def initialize(messages, applied_count:, failures_count:, performed: true)
|
|
13
|
+
@messages = messages
|
|
14
|
+
@applied_count = applied_count
|
|
15
|
+
@failures_count = failures_count
|
|
16
|
+
@performed = performed
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def performed?
|
|
20
|
+
@performed
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def redacted?
|
|
24
|
+
applied_count.positive?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Datadog
|
|
4
|
+
module AIGuard
|
|
5
|
+
# Sensitive data redaction for evaluation messages
|
|
6
|
+
#
|
|
7
|
+
# @api private
|
|
8
|
+
module Redaction
|
|
9
|
+
class << self
|
|
10
|
+
def skip(messages)
|
|
11
|
+
Redaction::Result.new(messages, applied_count: 0, failures_count: 0, performed: false)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def perform(messages, replacements:)
|
|
15
|
+
redaction_replacements = Replacements.new(replacements)
|
|
16
|
+
failures_count = redaction_replacements.failures_count
|
|
17
|
+
|
|
18
|
+
if redaction_replacements.empty?
|
|
19
|
+
return Redaction::Result.new(messages, applied_count: 0, failures_count: failures_count)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
applied_count = 0
|
|
23
|
+
redacted_messages = messages.dup
|
|
24
|
+
|
|
25
|
+
redaction_replacements.each do |path, replacement|
|
|
26
|
+
index = path[0]
|
|
27
|
+
redacted_message = redact(redacted_messages[index], path: path, replacement: replacement)
|
|
28
|
+
|
|
29
|
+
next failures_count += 1 unless redacted_message
|
|
30
|
+
|
|
31
|
+
redacted_messages[index] = redacted_message
|
|
32
|
+
applied_count += 1
|
|
33
|
+
rescue
|
|
34
|
+
failures_count += 1
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
Redaction::Result.new(
|
|
38
|
+
redacted_messages, applied_count: applied_count, failures_count: failures_count
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def redact(message, path:, replacement:)
|
|
45
|
+
return unless message.is_a?(Evaluation::Message)
|
|
46
|
+
|
|
47
|
+
_, kind, index = path
|
|
48
|
+
|
|
49
|
+
case kind
|
|
50
|
+
when :content
|
|
51
|
+
return unless message.content.is_a?(::String)
|
|
52
|
+
|
|
53
|
+
message.copy(content: replacement)
|
|
54
|
+
when :text
|
|
55
|
+
# @type var index: Integer
|
|
56
|
+
content = message.content
|
|
57
|
+
return unless content.is_a?(::Array)
|
|
58
|
+
|
|
59
|
+
part = content[index]
|
|
60
|
+
return if !part.is_a?(Evaluation::ContentPart::Text) || !part.text.is_a?(::String)
|
|
61
|
+
|
|
62
|
+
redacted_content = ::Array.new(content)
|
|
63
|
+
redacted_content[index] = part.copy(text: replacement)
|
|
64
|
+
|
|
65
|
+
message.copy(content: redacted_content)
|
|
66
|
+
when :arguments
|
|
67
|
+
# @type var index: Integer
|
|
68
|
+
tool_call = message.tool_calls[index]
|
|
69
|
+
return unless tool_call && tool_call.arguments.is_a?(::String)
|
|
70
|
+
|
|
71
|
+
redacted_tool_calls = ::Array.new(message.tool_calls)
|
|
72
|
+
redacted_tool_calls[index] = tool_call.copy(arguments: replacement)
|
|
73
|
+
|
|
74
|
+
message.copy(tool_calls: redacted_tool_calls)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|