Skip to content

Commit a733fee

Browse files
rarguelloFgenesor
authored andcommitted
fix(llmobs): include missing tags and make SubmitEvaluationFromSpan more flexible (#4050)
1 parent e72e553 commit a733fee

File tree

9 files changed

+240
-35
lines changed

9 files changed

+240
-35
lines changed

instrumentation/testutils/testtracer/testtracer.go

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ type WaitCondition func(*Payloads) bool
9393

9494
// TestTracer is an inspectable tracer useful for tests.
9595
type TestTracer struct {
96+
startError error
9697
payloads *Payloads
9798
roundTripper *mockTransport
9899
}
@@ -136,8 +137,10 @@ func Start(t testing.TB, opts ...Option) *TestTracer {
136137
}, cfg.TracerStartOpts...)
137138

138139
err := tracer.Start(startOpts...)
139-
require.NoError(t, err)
140-
140+
if cfg.RequireNoError {
141+
require.NoError(t, err)
142+
}
143+
tt.startError = err
141144
return tt
142145
}
143146

@@ -146,6 +149,7 @@ type config struct {
146149
AgentInfoResponse AgentInfo
147150
RequestDelay time.Duration
148151
MockResponse MockResponseFunc
152+
RequireNoError bool
149153
}
150154

151155
func defaultConfig() *config {
@@ -154,6 +158,7 @@ func defaultConfig() *config {
154158
AgentInfoResponse: AgentInfo{},
155159
RequestDelay: 0,
156160
MockResponse: nil,
161+
RequireNoError: true,
157162
}
158163
}
159164

@@ -190,6 +195,14 @@ func WithMockResponses(mr MockResponseFunc) Option {
190195
}
191196
}
192197

198+
// WithRequireNoTracerStartError allows to customize the behavior for the Start function. By default, it calls require.NoError
199+
// on the error returned by tracer.Start, but that can be changed by using this option with false as argument.
200+
func WithRequireNoTracerStartError(requireNoErr bool) Option {
201+
return func(cfg *config) {
202+
cfg.RequireNoError = requireNoErr
203+
}
204+
}
205+
193206
// collectPayloads runs in a goroutine and collects payloads from the channel
194207
func (tt *TestTracer) collectPayloads(payloadChan <-chan any) {
195208
for payload := range payloadChan {
@@ -206,6 +219,11 @@ func (tt *TestTracer) collectPayloads(payloadChan <-chan any) {
206219
}
207220
}
208221

222+
// StartError returns the error from tracer.Start.
223+
func (tt *TestTracer) StartError() error {
224+
return tt.startError
225+
}
226+
209227
// Stop stops the tracer. It should be called after the test finishes.
210228
func (tt *TestTracer) Stop() {
211229
tt.roundTripper.Stop()
@@ -328,31 +346,25 @@ func (rt *mockTransport) handleRequest(r *http.Request) *http.Response {
328346
return rt.emptyResponse(r)
329347
}
330348

331-
var (
332-
resp *http.Response
333-
respOverwritten = false
334-
)
349+
var resp *http.Response
350+
335351
if rt.mockResponse != nil {
336352
resp = rt.mockResponse(r)
337-
respOverwritten = true
338-
}
339-
if resp == nil {
340-
resp = rt.emptyResponse(r)
353+
if resp != nil {
354+
return resp
355+
}
341356
}
357+
resp = rt.emptyResponse(r)
342358

343359
switch r.URL.Path {
344360
case "/v0.4/traces":
345361
rt.handleTraces(r)
346362
case "/info":
347-
if !respOverwritten {
348-
resp = rt.handleInfo(r)
349-
}
363+
resp = rt.handleInfo(r)
350364
case "/evp_proxy/v2/api/v2/llmobs", "/api/v2/llmobs":
351365
rt.handleLLMObsSpanEvents(r)
352366
case "/evp_proxy/v2/api/intake/llm-obs/v2/eval-metric", "/api/intake/llm-obs/v2/eval-metric":
353367
rt.handleLLMObsEvalMetrics(r)
354-
case "/v0.7/config", "/telemetry/proxy/api/v2/apmtelemetry":
355-
// known cases, no need to log these
356368
default:
357369
logWarn := true
358370
for _, p := range noLogPaths {

internal/llmobs/llmobs.go

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var (
3636
errLLMObsNotEnabled = errors.New("LLMObs is not enabled. Ensure the tracer has been started with the option tracer.WithLLMObsEnabled(true) or set DD_LLMOBS_ENABLED=true")
3737
errAgentlessRequiresAPIKey = errors.New("LLMOBs agentless mode requires a valid API key - set the DD_API_KEY env variable to configure one")
3838
errMLAppRequired = errors.New("ML App is required for sending LLM Observability data")
39+
errAgentModeNotSupported = errors.New("DD_LLMOBS_AGENTLESS_ENABLED has been configured to false but the agent is not available or does not support LLMObs")
3940
)
4041

4142
const (
@@ -139,15 +140,22 @@ type LLMObs struct {
139140
}
140141

141142
func newLLMObs(cfg *config.Config, tracer Tracer) (*LLMObs, error) {
143+
agentSupportsLLMObs := cfg.AgentFeatures.EVPProxyV2
144+
if !agentSupportsLLMObs {
145+
log.Debug("llmobs: agent not available or does not support llmobs")
146+
}
142147
if cfg.AgentlessEnabled != nil {
148+
if !*cfg.AgentlessEnabled && !agentSupportsLLMObs {
149+
return nil, errAgentModeNotSupported
150+
}
143151
cfg.ResolvedAgentlessEnabled = *cfg.AgentlessEnabled
144152
} else {
145153
// if agentlessEnabled is not set and evp_proxy is supported in the agent, default to use the agent
146-
cfg.ResolvedAgentlessEnabled = !cfg.AgentFeatures.EVPProxyV2
154+
cfg.ResolvedAgentlessEnabled = !agentSupportsLLMObs
147155
if cfg.ResolvedAgentlessEnabled {
148-
log.Debug("llmobs: DD_LLMOBS_AGENTLESS_ENABLED not set, defaulting to true since agent mode is supported")
156+
log.Debug("llmobs: DD_LLMOBS_AGENTLESS_ENABLED not set, defaulting to agentless mode")
149157
} else {
150-
log.Debug("llmobs: DD_LLMOBS_AGENTLESS_ENABLED not set, defaulting to false since agent mode is not supported")
158+
log.Debug("llmobs: DD_LLMOBS_AGENTLESS_ENABLED not set, defaulting to agent mode")
151159
}
152160
}
153161

@@ -531,6 +539,11 @@ func (l *LLMObs) llmobsSpanEvent(span *Span) *transport.LLMObsSpanEvent {
531539
tags["ddtrace.version"] = version.Tag
532540
tags["language"] = "go"
533541

542+
sessionID := span.propagatedSessionID()
543+
if sessionID != "" {
544+
tags["session_id"] = sessionID
545+
}
546+
534547
errTag := "0"
535548
if span.error != nil {
536549
errTag = "1"
@@ -556,7 +569,7 @@ func (l *LLMObs) llmobsSpanEvent(span *Span) *transport.LLMObsSpanEvent {
556569
SpanID: spanID,
557570
TraceID: span.llmTraceID,
558571
ParentID: parentID,
559-
SessionID: span.propagatedSessionID(),
572+
SessionID: sessionID,
560573
Tags: tagsSlice,
561574
Name: span.name,
562575
StartNS: span.startTime.UnixNano(),
@@ -711,7 +724,6 @@ func (l *LLMObs) SubmitEvaluation(cfg EvaluationConfig) error {
711724
if mlApp == "" {
712725
mlApp = l.Config.MLApp
713726
}
714-
715727
timestampMS := cfg.TimestampMS
716728
if timestampMS == 0 {
717729
timestampMS = time.Now().UnixMilli()
@@ -731,12 +743,20 @@ func (l *LLMObs) SubmitEvaluation(cfg EvaluationConfig) error {
731743
}
732744
}
733745

746+
tags := make([]string, 0, len(cfg.Tags)+1)
747+
for _, tag := range cfg.Tags {
748+
if !strings.HasPrefix(tag, "ddtrace.version:") {
749+
tags = append(tags, tag)
750+
}
751+
}
752+
tags = append(tags, fmt.Sprintf("ddtrace.version:%s", version.Tag))
753+
734754
metric := &transport.LLMObsMetric{
735755
JoinOn: joinOn,
736756
Label: cfg.Label,
737757
MLApp: mlApp,
738758
TimestampMS: timestampMS,
739-
Tags: cfg.Tags,
759+
Tags: tags,
740760
}
741761

742762
if cfg.CategoricalValue != nil {

0 commit comments

Comments
 (0)