datadog 2.43.0 → 2.44.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 +36 -5
- data/ext/datadog_profiling_native_extension/collectors_idle_sampling_helper.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.c +6 -7
- data/ext/datadog_profiling_native_extension/collectors_stack.h +1 -1
- data/ext/datadog_profiling_native_extension/collectors_thread_context.c +65 -31
- data/ext/datadog_profiling_native_extension/collectors_thread_context.h +3 -2
- data/ext/datadog_profiling_native_extension/extconf.rb +4 -2
- data/ext/datadog_profiling_native_extension/private_vm_api_access.c +25 -0
- data/ext/datadog_profiling_native_extension/private_vm_api_access.h +1 -0
- data/ext/datadog_profiling_native_extension/profiling.c +1 -0
- data/ext/datadog_profiling_native_extension/stack_recorder.c +4 -4
- data/ext/datadog_profiling_native_extension/time_helpers.h +3 -3
- data/ext/libdatadog_api/di.c +8 -3
- data/ext/libdatadog_api/extconf.rb +1 -1
- data/ext/libdatadog_api/feature_flags.c +8 -0
- data/ext/libdatadog_api/init.c +3 -1
- data/ext/libdatadog_api/otel_thread_context.c +38 -17
- 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 +4 -60
- 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 +25 -11
- data/lib/datadog/ai_guard/ext.rb +1 -0
- data/lib/datadog/ai_guard/{api_client.rb → http_client.rb} +6 -4
- 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/core/configuration/components.rb +9 -1
- data/lib/datadog/core/configuration/settings.rb +5 -2
- data/lib/datadog/core/configuration/supported_configurations.rb +8 -0
- data/lib/datadog/core/remote/client/capabilities.rb +10 -4
- data/lib/datadog/core/remote/component.rb +9 -2
- data/lib/datadog/core/remote/worker.rb +8 -0
- data/lib/datadog/di/instrumenter.rb +1 -1
- data/lib/datadog/di/probe_manager.rb +10 -5
- data/lib/datadog/di/proc_responder.rb +1 -1
- 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 +57 -1
- 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 +118 -0
- data/lib/datadog/open_feature/native_evaluator.rb +1 -14
- data/lib/datadog/open_feature/remote.rb +5 -5
- data/lib/datadog/opentelemetry/signal_configuration.rb +13 -9
- data/lib/datadog/profiling/collectors/cpu_and_wall_time_worker.rb +2 -2
- data/lib/datadog/profiling/collectors/idle_sampling_helper.rb +5 -7
- data/lib/datadog/symbol_database/extractor.rb +84 -102
- data/lib/datadog/tracing/component.rb +2 -0
- data/lib/datadog/tracing/configuration/ext.rb +1 -0
- data/lib/datadog/tracing/configuration/settings.rb +6 -0
- data/lib/datadog/tracing/context.rb +24 -2
- data/lib/datadog/tracing/context_provider.rb +9 -5
- data/lib/datadog/tracing/contrib/grape/endpoint.rb +32 -5
- data/lib/datadog/tracing/metadata/ext.rb +3 -0
- data/lib/datadog/tracing/otel_thread_context.rb +76 -11
- data/lib/datadog/tracing/tracer.rb +15 -2
- data/lib/datadog/tracing/transport/native.rb +1 -1
- data/lib/datadog/tracing/transport/trace_formatter.rb +13 -3
- data/lib/datadog/tracing/transport/traces.rb +1 -1
- data/lib/datadog/version.rb +1 -1
- metadata +23 -7
- data/lib/datadog/ai_guard/evaluation/content_builder.rb +0 -31
|
@@ -45,6 +45,7 @@ uint64_t native_thread_id_for(VALUE thread);
|
|
|
45
45
|
void ddtrace_thread_list(VALUE result_array);
|
|
46
46
|
bool is_thread_alive(VALUE thread);
|
|
47
47
|
VALUE thread_name_for(VALUE thread);
|
|
48
|
+
void self_test_thread_name_for(void);
|
|
48
49
|
|
|
49
50
|
int ddtrace_rb_profile_frames(VALUE thread, int start, int limit, frame_info *stack_buffer);
|
|
50
51
|
|
|
@@ -103,6 +103,7 @@ void DDTRACE_EXPORT Init_datadog_profiling_native_extension(void) {
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
static VALUE native_working_p(DDTRACE_UNUSED VALUE _self) {
|
|
106
|
+
self_test_thread_name_for();
|
|
106
107
|
self_test_clock_id();
|
|
107
108
|
self_test_current_fiber_for();
|
|
108
109
|
self_test_mn_enabled();
|
|
@@ -539,10 +539,6 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
|
539
539
|
stack_recorder_state *state;
|
|
540
540
|
TypedData_Get_Struct(recorder_instance, stack_recorder_state, &stack_recorder_typed_data, state);
|
|
541
541
|
|
|
542
|
-
ddog_Timespec finish_timestamp = system_epoch_now_timespec();
|
|
543
|
-
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
544
|
-
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
545
|
-
|
|
546
542
|
if (state->thread_context_collector_instance != Qnil) {
|
|
547
543
|
thread_context_collector_on_serialize(state->thread_context_collector_instance);
|
|
548
544
|
}
|
|
@@ -555,6 +551,10 @@ static VALUE _native_serialize(DDTRACE_UNUSED VALUE _self, VALUE recorder_instan
|
|
|
555
551
|
heap_recorder_prepare_iteration(state->heap_recorder);
|
|
556
552
|
long heap_iteration_prep_time_ns = monotonic_wall_time_now_ns(DO_NOT_RAISE_ON_FAILURE) - heap_iteration_prep_start_time_ns;
|
|
557
553
|
|
|
554
|
+
ddog_Timespec finish_timestamp = system_epoch_now_timespec();
|
|
555
|
+
// Need to do this while still holding the Global VM Lock; see comments on method for why
|
|
556
|
+
serializer_set_start_timestamp_for_next_profile(state, finish_timestamp);
|
|
557
|
+
|
|
558
558
|
// We'll release the Global VM Lock while we're calling serialize, so that the Ruby VM can continue to work while this
|
|
559
559
|
// is pending
|
|
560
560
|
call_serialize_without_gvl_arguments args = {
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
#include "extconf.h"
|
|
8
8
|
#include "ruby_helpers.h"
|
|
9
9
|
|
|
10
|
-
#define SECONDS_AS_NS(value) (value *
|
|
11
|
-
#define MILLIS_AS_NS(value)
|
|
12
|
-
#define MICROS_AS_NS(value)
|
|
10
|
+
#define SECONDS_AS_NS(value) ((value) * 1000L * 1000L * 1000L)
|
|
11
|
+
#define MILLIS_AS_NS(value) ((value) * 1000L * 1000L)
|
|
12
|
+
#define MICROS_AS_NS(value) ((value) * 1000L)
|
|
13
13
|
|
|
14
14
|
typedef enum { RAISE_ON_FAILURE, DO_NOT_RAISE_ON_FAILURE } raise_on_failure_setting;
|
|
15
15
|
|
data/ext/libdatadog_api/di.c
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
#include "datadog_ruby_common.h"
|
|
4
4
|
|
|
5
|
+
// rb_iseq is CRuby-specific
|
|
6
|
+
#ifndef TRUFFLERUBY
|
|
7
|
+
|
|
5
8
|
// Prototypes for Ruby functions declared in internal Ruby headers.
|
|
6
9
|
// rb_iseqw_new wraps an internal iseq pointer into a Ruby-visible
|
|
7
10
|
// RubyVM::InstructionSequence object.
|
|
@@ -207,7 +210,9 @@ void di_init(VALUE datadog_module) {
|
|
|
207
210
|
rb_define_singleton_method(di_module, "enter_probe", enter_probe, 0);
|
|
208
211
|
rb_define_singleton_method(di_module, "leave_probe", leave_probe, 0);
|
|
209
212
|
rb_define_singleton_method(di_module, "hash?", is_hash, 1);
|
|
210
|
-
#ifdef HAVE_RB_ISEQ_TYPE
|
|
211
|
-
|
|
212
|
-
#endif
|
|
213
|
+
#ifdef HAVE_RB_ISEQ_TYPE
|
|
214
|
+
rb_define_singleton_method(di_module, "iseq_type", iseq_type, 1);
|
|
215
|
+
#endif
|
|
213
216
|
}
|
|
217
|
+
|
|
218
|
+
#endif // TRUFFLERUBY
|
|
@@ -24,7 +24,7 @@ end
|
|
|
24
24
|
if ENV["DD_NO_EXTENSION"].to_s.strip.downcase == "true"
|
|
25
25
|
skip_building_extension!("the `DD_NO_EXTENSION` environment variable is/was set to `true` during installation")
|
|
26
26
|
end
|
|
27
|
-
skip_building_extension!("current Ruby VM is not supported")
|
|
27
|
+
skip_building_extension!("current Ruby VM is not supported") unless %w[ruby truffleruby].include?(RUBY_ENGINE)
|
|
28
28
|
skip_building_extension!("Microsoft Windows is not supported") if Gem.win_platform?
|
|
29
29
|
|
|
30
30
|
libdatadog_issue = Datadog::LibdatadogExtconfHelpers.load_libdatadog_or_get_issue
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
// Forward declarations
|
|
14
14
|
static VALUE configuration_new(VALUE klass, VALUE json_str);
|
|
15
15
|
static void configuration_free(void *ptr);
|
|
16
|
+
static VALUE configuration_get_observe_full_evaluation_data(VALUE self);
|
|
16
17
|
static VALUE configuration_get_assignment(
|
|
17
18
|
VALUE self, VALUE flag_key, VALUE expected_type, VALUE context);
|
|
18
19
|
|
|
@@ -94,6 +95,7 @@ void feature_flags_init(VALUE core_module) {
|
|
|
94
95
|
rb_undef_alloc_func(configuration_class);
|
|
95
96
|
rb_define_singleton_method(configuration_class, "new", configuration_new, 1);
|
|
96
97
|
rb_define_method(configuration_class, "get_assignment", configuration_get_assignment, 3);
|
|
98
|
+
rb_define_method(configuration_class, "observe_full_evaluation_data", configuration_get_observe_full_evaluation_data, 0);
|
|
97
99
|
|
|
98
100
|
rb_gc_register_address(&resolution_details_class);
|
|
99
101
|
resolution_details_class = rb_define_class_under(feature_flags_module, "ResolutionDetails", rb_cObject);
|
|
@@ -137,6 +139,12 @@ static VALUE configuration_new(VALUE klass, VALUE json_str) {
|
|
|
137
139
|
return TypedData_Wrap_Struct(klass, &configuration_data_type, result.ok);
|
|
138
140
|
}
|
|
139
141
|
|
|
142
|
+
static VALUE configuration_get_observe_full_evaluation_data(VALUE self) {
|
|
143
|
+
ddog_ffe_Handle_Configuration configuration =
|
|
144
|
+
(ddog_ffe_Handle_Configuration)rb_check_typeddata(self, &configuration_data_type);
|
|
145
|
+
return ddog_ffe_configuration_get_observe_full_evaluation_data(configuration) ? Qtrue : Qfalse;
|
|
146
|
+
}
|
|
147
|
+
|
|
140
148
|
static void configuration_free(void *ptr) {
|
|
141
149
|
ddog_ffe_Handle_Configuration config = (ddog_ffe_Handle_Configuration)ptr;
|
|
142
150
|
ddog_ffe_configuration_drop(&config);
|
data/ext/libdatadog_api/init.c
CHANGED
|
@@ -25,7 +25,9 @@ void DDTRACE_EXPORT Init_libdatadog_api(void) {
|
|
|
25
25
|
library_config_init(core_module);
|
|
26
26
|
ddsketch_init(core_module);
|
|
27
27
|
feature_flags_init(core_module);
|
|
28
|
-
|
|
28
|
+
#ifndef TRUFFLERUBY
|
|
29
|
+
di_init(datadog_module);
|
|
30
|
+
#endif
|
|
29
31
|
|
|
30
32
|
VALUE tracing_module = rb_define_module_under(datadog_module, "Tracing");
|
|
31
33
|
trace_exporter_init(tracing_module);
|
|
@@ -4,7 +4,12 @@
|
|
|
4
4
|
#include "datadog_ruby_common.h"
|
|
5
5
|
#include "otel_thread_context.h"
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
// Relies on a :fiber_switch TracePoint that TruffleRuby does not support yet
|
|
8
|
+
#if defined(__linux__) && !defined(TRUFFLERUBY)
|
|
9
|
+
#define OTEL_THREAD_CONTEXT_SUPPORTED
|
|
10
|
+
#endif
|
|
11
|
+
|
|
12
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
8
13
|
#include <datadog/otel-thread-ctx.h>
|
|
9
14
|
extern __thread const uint8_t *otel_thread_ctx_v1;
|
|
10
15
|
|
|
@@ -32,18 +37,18 @@ static VALUE native_read(VALUE _self);
|
|
|
32
37
|
void otel_thread_context_init(VALUE tracing_module) {
|
|
33
38
|
fiber_context_slot = rb_intern("__dd_otel_fiber_context");
|
|
34
39
|
|
|
35
|
-
VALUE
|
|
40
|
+
VALUE otel_thread_context_class = rb_define_class_under(tracing_module, "OTelThreadContext", rb_cObject);
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
rb_define_method(otel_thread_context_class, "_native_enable", native_enable, 0);
|
|
43
|
+
rb_define_method(otel_thread_context_class, "_native_set", native_set, 3);
|
|
44
|
+
rb_define_method(otel_thread_context_class, "_native_clear", native_clear, 0);
|
|
45
|
+
rb_define_method(otel_thread_context_class, "_native_supported?", native_supported_p, 0);
|
|
41
46
|
|
|
42
|
-
VALUE testing_module = rb_define_module_under(
|
|
47
|
+
VALUE testing_module = rb_define_module_under(otel_thread_context_class, "Testing");
|
|
43
48
|
rb_define_singleton_method(testing_module, "_native_read", native_read, 0);
|
|
44
49
|
}
|
|
45
50
|
|
|
46
|
-
#ifdef
|
|
51
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
47
52
|
static ddog_ThreadContextHandle *get_fiber_handle_for(VALUE thread) {
|
|
48
53
|
VALUE obj = rb_thread_local_aref(thread, fiber_context_slot);
|
|
49
54
|
|
|
@@ -56,6 +61,22 @@ void otel_thread_context_init(VALUE tracing_module) {
|
|
|
56
61
|
return get_fiber_handle_for(rb_thread_current());
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
static void pack_span_id(VALUE id, uint8_t bytes[8]) {
|
|
65
|
+
// Tracing::Utils.next_id caps generated span IDs at (1 << 62) - 1, which fits
|
|
66
|
+
// in a Fixnum on 64-bit Ruby. Packing them directly avoids rb_integer_pack's
|
|
67
|
+
// generic conversion and format checks on each context update.
|
|
68
|
+
// https://github.com/DataDog/dd-trace-rb/blob/0b10368e0a2940c1f4d50c9739b9973b4f8bc32a/lib/datadog/tracing/utils.rb#L20-L37
|
|
69
|
+
if (FIXNUM_P(id)) {
|
|
70
|
+
uint64_t number = (uint64_t) FIX2LONG(id);
|
|
71
|
+
for (int i = 7; i >= 0; i--) {
|
|
72
|
+
bytes[i] = (uint8_t) number;
|
|
73
|
+
number >>= 8;
|
|
74
|
+
}
|
|
75
|
+
} else {
|
|
76
|
+
rb_integer_pack(id, bytes, 1, sizeof(uint64_t), 0, INTEGER_PACK_BIG_ENDIAN);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
59
80
|
static void store_current_fiber_handle(ddog_ThreadContextHandle *handle) {
|
|
60
81
|
VALUE obj = TypedData_Wrap_Struct(rb_cObject, &otel_ctx_handle_t, handle);
|
|
61
82
|
rb_thread_local_aset(rb_thread_current(), fiber_context_slot, obj);
|
|
@@ -137,7 +158,7 @@ void otel_thread_context_init(VALUE tracing_module) {
|
|
|
137
158
|
#endif
|
|
138
159
|
|
|
139
160
|
static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
|
|
140
|
-
#ifdef
|
|
161
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
141
162
|
if (otel_context_enabled) return Qtrue;
|
|
142
163
|
otel_context_enabled = true;
|
|
143
164
|
|
|
@@ -163,7 +184,7 @@ static VALUE native_enable(DDTRACE_UNUSED VALUE _self) {
|
|
|
163
184
|
}
|
|
164
185
|
|
|
165
186
|
static VALUE native_supported_p(DDTRACE_UNUSED VALUE _self) {
|
|
166
|
-
#ifdef
|
|
187
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
167
188
|
return Qtrue;
|
|
168
189
|
#else
|
|
169
190
|
return Qfalse;
|
|
@@ -176,17 +197,17 @@ static VALUE native_set(
|
|
|
176
197
|
DDTRACE_UNUSED VALUE span_id,
|
|
177
198
|
DDTRACE_UNUSED VALUE local_root_span_id
|
|
178
199
|
) {
|
|
179
|
-
#ifdef
|
|
200
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
180
201
|
if (!otel_context_enabled) return Qfalse;
|
|
181
202
|
|
|
182
203
|
uint8_t trace_id_bytes[16];
|
|
183
204
|
uint8_t span_id_bytes[8];
|
|
184
205
|
uint8_t local_root_span_id_bytes[8];
|
|
185
206
|
|
|
186
|
-
|
|
187
|
-
rb_integer_pack(trace_id, trace_id_bytes, sizeof(
|
|
188
|
-
|
|
189
|
-
|
|
207
|
+
// Note: We use 2 words of size 8, as `rb_integer_pack` has a fast path for it (vs 16 bytes of size 1)
|
|
208
|
+
rb_integer_pack(trace_id, trace_id_bytes, 2, sizeof(uint64_t), 0, INTEGER_PACK_BIG_ENDIAN);
|
|
209
|
+
pack_span_id(span_id, span_id_bytes);
|
|
210
|
+
pack_span_id(local_root_span_id, local_root_span_id_bytes);
|
|
190
211
|
|
|
191
212
|
ddog_ThreadContextHandle *handle = get_current_fiber_handle();
|
|
192
213
|
|
|
@@ -207,7 +228,7 @@ static VALUE native_set(
|
|
|
207
228
|
}
|
|
208
229
|
|
|
209
230
|
static VALUE native_clear(DDTRACE_UNUSED VALUE _self) {
|
|
210
|
-
#ifdef
|
|
231
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
211
232
|
if (!otel_context_enabled) return Qfalse;
|
|
212
233
|
|
|
213
234
|
ddog_ThreadContextHandle *detached = ddog_otel_thread_ctx_detach();
|
|
@@ -220,7 +241,7 @@ static VALUE native_clear(DDTRACE_UNUSED VALUE _self) {
|
|
|
220
241
|
}
|
|
221
242
|
|
|
222
243
|
static VALUE native_read(DDTRACE_UNUSED VALUE _self) {
|
|
223
|
-
#ifdef
|
|
244
|
+
#ifdef OTEL_THREAD_CONTEXT_SUPPORTED
|
|
224
245
|
const uint8_t *raw = otel_thread_ctx_v1;
|
|
225
246
|
if (!raw) return Qnil;
|
|
226
247
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "rubygems"
|
|
4
4
|
require "pathname"
|
|
5
|
+
require "shellwords"
|
|
5
6
|
|
|
6
7
|
module Datadog
|
|
7
8
|
# Contains a bunch of shared helpers that get used during building of extensions that link to libdatadog
|
|
@@ -10,7 +11,7 @@ module Datadog
|
|
|
10
11
|
module LibdatadogExtconfHelpers
|
|
11
12
|
# Used to make sure the correct gem version gets loaded, as extconf.rb does not get run with "bundle exec" and thus
|
|
12
13
|
# may see multiple libdatadog versions. See https://github.com/DataDog/dd-trace-rb/pull/2531 for the horror story.
|
|
13
|
-
LIBDATADOG_VERSION = "~>
|
|
14
|
+
LIBDATADOG_VERSION = "~> 44.0.1.1.0"
|
|
14
15
|
|
|
15
16
|
# Used as an workaround for a limitation with how dynamic linking works in environments where the datadog gem and
|
|
16
17
|
# libdatadog are moved after the extension gets compiled.
|
|
@@ -116,7 +117,10 @@ module Datadog
|
|
|
116
117
|
extconf_folder:,
|
|
117
118
|
libdatadog_pkgconfig_folder: Libdatadog.pkgconfig_folder,
|
|
118
119
|
gem_dir: Gem.dir,
|
|
119
|
-
logger: Logging
|
|
120
|
+
logger: Logging,
|
|
121
|
+
target_incflags: $INCFLAGS,
|
|
122
|
+
target_ldflags: $LDFLAGS,
|
|
123
|
+
target_libs: $libs
|
|
120
124
|
)
|
|
121
125
|
return unless libdatadog_pkgconfig_folder
|
|
122
126
|
|
|
@@ -124,10 +128,9 @@ module Datadog
|
|
|
124
128
|
libdir = "#{libdatadog_pkgconfig_folder}/../../lib"
|
|
125
129
|
includedir = "#{libdatadog_pkgconfig_folder}/../../include"
|
|
126
130
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
$libs << " -ldatadog_profiling"
|
|
131
|
+
target_incflags << " -I#{Shellwords.escape(includedir)}"
|
|
132
|
+
target_ldflags << " -L#{Shellwords.escape(libdir)} -Wl,-rpath,#{Shellwords.escape(libdir)}"
|
|
133
|
+
target_libs << " -ldatadog_profiling"
|
|
131
134
|
|
|
132
135
|
# Add extra relative rpaths using $ORIGIN to handle environments where gems are moved after installation.
|
|
133
136
|
# The excessive escaping is needed to get these special characters through Make and the shell untouched.
|
|
@@ -141,10 +144,12 @@ module Datadog
|
|
|
141
144
|
libdatadog_pkgconfig_folder: libdatadog_pkgconfig_folder,
|
|
142
145
|
),
|
|
143
146
|
]
|
|
144
|
-
extra_relative_rpaths.each { |folder|
|
|
147
|
+
extra_relative_rpaths.each { |folder| target_ldflags << " -Wl,-rpath,$$$\\\\{ORIGIN\\}/#{Shellwords.escape(folder)}" }
|
|
145
148
|
|
|
146
|
-
logger.message
|
|
147
|
-
|
|
149
|
+
# includedir/libdir/ldflags might have a `%` in there and `logger.message` bottoms out on `printf` so we need to
|
|
150
|
+
# use the `message("%s", ...)` format otherwise the logger will complain
|
|
151
|
+
logger.message("%s", "linking with libdatadog (include=#{includedir}, lib=#{libdir})\n")
|
|
152
|
+
logger.message("%s", "[datadog] $LDFLAGS were set to: #{target_ldflags.inspect}\n")
|
|
148
153
|
|
|
149
154
|
true
|
|
150
155
|
end
|
|
@@ -1,37 +1,46 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
require_relative "ext"
|
|
6
|
+
require_relative "http_client"
|
|
4
7
|
require_relative "evaluation"
|
|
5
8
|
require_relative "evaluation/request"
|
|
9
|
+
require_relative "evaluation/response"
|
|
10
|
+
require_relative "evaluation/client"
|
|
6
11
|
require_relative "evaluation/result"
|
|
12
|
+
require_relative "evaluation/outcome"
|
|
7
13
|
require_relative "evaluation/no_op_result"
|
|
8
14
|
require_relative "evaluation/message"
|
|
9
15
|
require_relative "evaluation/tool_call"
|
|
10
16
|
require_relative "evaluation/content_part"
|
|
11
|
-
require_relative "evaluation/
|
|
12
|
-
require_relative "
|
|
17
|
+
require_relative "evaluation/message_builder"
|
|
18
|
+
require_relative "redaction"
|
|
19
|
+
require_relative "redaction/result"
|
|
20
|
+
require_relative "redaction/replacements"
|
|
21
|
+
require_relative "metrics/telemetry"
|
|
13
22
|
|
|
14
23
|
module Datadog
|
|
15
24
|
module AIGuard
|
|
16
25
|
# Component for API Guard product
|
|
17
26
|
class Component
|
|
18
|
-
attr_reader :
|
|
27
|
+
attr_reader :http_client, :logger, :telemetry
|
|
19
28
|
|
|
20
29
|
def self.build(settings, logger:, telemetry:)
|
|
21
30
|
return unless settings.respond_to?(:ai_guard) && settings.ai_guard.enabled
|
|
22
31
|
|
|
23
|
-
|
|
32
|
+
http_client = HTTPClient.new(
|
|
24
33
|
endpoint: settings.ai_guard.endpoint,
|
|
25
34
|
api_key: settings.api_key,
|
|
26
35
|
application_key: settings.ai_guard.app_key,
|
|
27
36
|
timeout: settings.ai_guard.timeout_ms / 1_000
|
|
28
37
|
)
|
|
29
38
|
|
|
30
|
-
new(
|
|
39
|
+
new(http_client, logger: logger, telemetry: telemetry)
|
|
31
40
|
end
|
|
32
41
|
|
|
33
|
-
def initialize(
|
|
34
|
-
@
|
|
42
|
+
def initialize(http_client, logger:, telemetry:)
|
|
43
|
+
@http_client = http_client
|
|
35
44
|
@logger = logger
|
|
36
45
|
@telemetry = telemetry
|
|
37
46
|
end
|
|
@@ -6,6 +6,8 @@ module Datadog
|
|
|
6
6
|
# This module contains constants for AI Guard component
|
|
7
7
|
module Ext
|
|
8
8
|
ENV_AI_GUARD_ENABLED = "DD_AI_GUARD_ENABLED"
|
|
9
|
+
ENV_AI_GUARD_REDACTION_ENABLED = "DD_AI_GUARD_REDACTION_ENABLED"
|
|
10
|
+
|
|
9
11
|
ENV_AI_GUARD_ENDPOINT = "DD_AI_GUARD_ENDPOINT"
|
|
10
12
|
ENV_AI_GUARD_TIMEOUT = "DD_AI_GUARD_TIMEOUT"
|
|
11
13
|
ENV_AI_GUARD_MAX_CONTENT_SIZE = "DD_AI_GUARD_MAX_CONTENT_SIZE"
|
|
@@ -34,6 +34,19 @@ module Datadog
|
|
|
34
34
|
o.default false
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
# Redact sensitive data from evaluated messages.
|
|
38
|
+
#
|
|
39
|
+
# When enabled, values flagged by AI Guard are replaced before the messages are
|
|
40
|
+
# stored in the span or returned to the caller. Disable to skip redaction entirely.
|
|
41
|
+
#
|
|
42
|
+
# @default `DD_AI_GUARD_REDACTION_ENABLED`, otherwise `true`
|
|
43
|
+
# @return [Boolean]
|
|
44
|
+
option :redaction_enabled do |o|
|
|
45
|
+
o.type :bool
|
|
46
|
+
o.env Ext::ENV_AI_GUARD_REDACTION_ENABLED
|
|
47
|
+
o.default true
|
|
48
|
+
end
|
|
49
|
+
|
|
37
50
|
define_method(:instrument) do |integration_name|
|
|
38
51
|
return unless enabled # steep:ignore
|
|
39
52
|
|
|
@@ -4,74 +4,67 @@ module Datadog
|
|
|
4
4
|
module AIGuard
|
|
5
5
|
module Contrib
|
|
6
6
|
module RubyLLM
|
|
7
|
-
#
|
|
7
|
+
# Protects tool calls before RubyLLM executes them locally
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
8
10
|
module ChatInstrumentation
|
|
9
|
-
|
|
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
|
-
build_ai_guard_tool(message)
|
|
18
|
-
else
|
|
19
|
-
build_ai_guard_message(message)
|
|
20
|
-
end
|
|
21
|
-
end
|
|
11
|
+
private
|
|
22
12
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
13
|
+
def provider_completion(usage_recorder:, stream_tracker: nil, &block)
|
|
14
|
+
@_datadog_ai_guard_evaluate = true
|
|
15
|
+
super
|
|
16
|
+
ensure
|
|
17
|
+
@_datadog_ai_guard_evaluate = false
|
|
18
|
+
end
|
|
27
19
|
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
def preprocessed_messages(list = messages)
|
|
21
|
+
return super unless @_datadog_ai_guard_evaluate
|
|
30
22
|
|
|
31
|
-
|
|
32
|
-
when ::RubyLLM::Content
|
|
33
|
-
AIGuard.message(role: message.role) do |m|
|
|
34
|
-
m.text(content.text.to_s) if content.text
|
|
23
|
+
adapter = MessageAdapter.new(list)
|
|
35
24
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
25
|
+
begin
|
|
26
|
+
converted_messages = adapter.to_ai_guard
|
|
27
|
+
rescue JSON::JSONError
|
|
28
|
+
Metrics::Telemetry.report_error
|
|
29
|
+
return super
|
|
30
|
+
rescue => e
|
|
31
|
+
AIGuard.telemetry&.report(e, description: "AI Guard: Failed to convert RubyLLM messages")
|
|
32
|
+
return super
|
|
51
33
|
end
|
|
52
34
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
end
|
|
61
|
-
AIGuard.tool(tool_call_id: message.tool_call_id, content: content)
|
|
35
|
+
evaluation = AIGuard.evaluate(*converted_messages)
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
redacted_messages = adapter.apply_redactions(evaluation.messages)
|
|
39
|
+
rescue JSON::JSONError => e
|
|
40
|
+
AIGuard.telemetry&.report(e, description: "AI Guard: Failed to apply RubyLLM redaction")
|
|
41
|
+
return super
|
|
62
42
|
end
|
|
43
|
+
|
|
44
|
+
super(redacted_messages)
|
|
63
45
|
end
|
|
64
46
|
|
|
65
|
-
def
|
|
66
|
-
|
|
47
|
+
def execute_pending_tool_calls(response)
|
|
48
|
+
response_index = messages.index { |message| message == response }
|
|
49
|
+
return super unless response_index
|
|
67
50
|
|
|
68
|
-
|
|
69
|
-
|
|
51
|
+
adapter = MessageAdapter.new(messages)
|
|
52
|
+
begin
|
|
53
|
+
converted_messages = adapter.to_ai_guard
|
|
54
|
+
rescue JSON::JSONError
|
|
55
|
+
Metrics::Telemetry.report_error
|
|
56
|
+
return super
|
|
57
|
+
end
|
|
70
58
|
|
|
71
|
-
|
|
72
|
-
|
|
59
|
+
evaluation = AIGuard.evaluate(*converted_messages)
|
|
60
|
+
begin
|
|
61
|
+
redacted_messages = adapter.apply_redactions(evaluation.messages)
|
|
62
|
+
rescue JSON::JSONError => e
|
|
63
|
+
AIGuard.telemetry&.report(e, description: "AI Guard: Failed to apply RubyLLM redaction")
|
|
64
|
+
return super
|
|
65
|
+
end
|
|
73
66
|
|
|
74
|
-
super
|
|
67
|
+
super(redacted_messages[response_index])
|
|
75
68
|
end
|
|
76
69
|
end
|
|
77
70
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "redaction_change_set"
|
|
4
|
+
|
|
5
|
+
module Datadog
|
|
6
|
+
module AIGuard
|
|
7
|
+
module Contrib
|
|
8
|
+
module RubyLLM
|
|
9
|
+
# RubyLLM messages paired with their AI Guard representation
|
|
10
|
+
#
|
|
11
|
+
# @api private
|
|
12
|
+
class MessageAdapter
|
|
13
|
+
def initialize(messages)
|
|
14
|
+
@ruby_llm_messages = messages
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_ai_guard
|
|
18
|
+
@ai_guard_messages ||= @ruby_llm_messages.map { |message| build_ai_guard_message(message) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def apply_redactions(redacted_messages)
|
|
22
|
+
ai_guard_messages = to_ai_guard
|
|
23
|
+
return @ruby_llm_messages if redacted_messages == ai_guard_messages
|
|
24
|
+
|
|
25
|
+
@ruby_llm_messages.each_with_index.map do |message, index|
|
|
26
|
+
original_message = ai_guard_messages[index]
|
|
27
|
+
redacted_message = redacted_messages[index]
|
|
28
|
+
next message if redacted_message == original_message
|
|
29
|
+
|
|
30
|
+
RedactionChangeSet.new(original_message, redacted_message).apply_to(message)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build_ai_guard_message(message)
|
|
37
|
+
builder = Evaluation::MessageBuilder.new
|
|
38
|
+
|
|
39
|
+
# NOTE: Hash block arguments are misinterpreted by Steep after `nil.to_h`
|
|
40
|
+
# steep:ignore:start
|
|
41
|
+
message.tool_calls.to_h.each do |id, tool_call|
|
|
42
|
+
builder.tool_call(name: tool_call.name, id: id, arguments: tool_call.arguments)
|
|
43
|
+
end
|
|
44
|
+
# steep:ignore:end
|
|
45
|
+
|
|
46
|
+
if message.attachments.empty?
|
|
47
|
+
content = message.content
|
|
48
|
+
else
|
|
49
|
+
builder.text(message.content) if message.content && !message.content.empty?
|
|
50
|
+
|
|
51
|
+
message.attachments.each do |attachment|
|
|
52
|
+
next if attachment.provider_file?
|
|
53
|
+
|
|
54
|
+
case attachment.type
|
|
55
|
+
when :text
|
|
56
|
+
builder.text(attachment.content)
|
|
57
|
+
when :image
|
|
58
|
+
# NOTE: Local images are Base64-encoded here by `#for_llm`
|
|
59
|
+
# and again when RubyLLM builds the provider request
|
|
60
|
+
url = attachment.url? ? attachment.source.to_s : attachment.for_llm
|
|
61
|
+
builder.image_url(url)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
content = builder.content
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
Evaluation::Message.new(
|
|
69
|
+
role: message.role, content: content, tool_calls: builder.tool_calls, tool_call_id: message.tool_call_id
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|