Skip to content

Commit 74d3fc1

Browse files
Daedaeliusolesho
andauthored
Added tracing metadata for LiteLLM provider (#86)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added comprehensive tracing and observability support for LLM calls and agent evaluations, enabling enhanced session grouping and request tracking. * Implemented automatic tracing metadata propagation throughout the API request and evaluation pipeline. * **Improvements** * Enhanced configuration resilience with automatic fallbacks for model endpoints and credentials. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Oleh Luchkiv <olesho@gmail.com>
1 parent 197f88f commit 74d3fc1

File tree

14 files changed

+163
-29
lines changed

14 files changed

+163
-29
lines changed

agent-server/nodejs/src/api-server.js

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -548,8 +548,11 @@ class APIServer {
548548
await new Promise(resolve => setTimeout(resolve, waitTimeout));
549549
}
550550

551+
// Extract tracing metadata for Langfuse integration
552+
const tracingMetadata = requestBody.metadata || {};
553+
551554
// Create a dynamic request for this request
552-
const request = this.createDynamicRequestNested(requestBody.input, nestedModelConfig);
555+
const request = this.createDynamicRequestNested(requestBody.input, nestedModelConfig, tracingMetadata);
553556

554557
// Execute the request on the new tab's DevTools client
555558
logger.info('Executing request on new tab', {
@@ -581,29 +584,35 @@ class APIServer {
581584
*/
582585
processNestedModelConfig(requestBody) {
583586
const defaults = this.configDefaults?.model || {};
587+
// Default LiteLLM endpoint from environment variable
588+
const defaultLiteLLMEndpoint = process.env.LITELLM_ENDPOINT;
584589

585590
// If nested format is provided, use it directly with fallbacks
586591
if (requestBody.model) {
587-
// Extract endpoint from each model tier, with fallback chain:
592+
// Helper to get endpoint with fallback chain:
588593
// 1. Try tier-specific endpoint (e.g., main_model.endpoint)
589594
// 2. Fall back to top-level endpoint (e.g., model.endpoint)
590-
// 3. Fall back to undefined (will use env var later)
591-
const mainEndpoint = requestBody.model.main_model?.endpoint || requestBody.model.endpoint;
592-
const miniEndpoint = requestBody.model.mini_model?.endpoint || requestBody.model.endpoint;
593-
const nanoEndpoint = requestBody.model.nano_model?.endpoint || requestBody.model.endpoint;
595+
// 3. Fall back to LITELLM_ENDPOINT env var (for litellm provider)
596+
const getEndpoint = (tierConfig) => {
597+
const explicitEndpoint = tierConfig?.endpoint || requestBody.model.endpoint;
598+
if (explicitEndpoint) return explicitEndpoint;
599+
// Use env var default for litellm provider
600+
if (tierConfig?.provider === 'litellm') return defaultLiteLLMEndpoint;
601+
return undefined;
602+
};
594603

595604
return {
596605
main_model: {
597606
...this.extractModelTierConfig('main', requestBody.model.main_model, defaults),
598-
endpoint: mainEndpoint
607+
endpoint: getEndpoint(requestBody.model.main_model)
599608
},
600609
mini_model: {
601610
...this.extractModelTierConfig('mini', requestBody.model.mini_model, defaults),
602-
endpoint: miniEndpoint
611+
endpoint: getEndpoint(requestBody.model.mini_model)
603612
},
604613
nano_model: {
605614
...this.extractModelTierConfig('nano', requestBody.model.nano_model, defaults),
606-
endpoint: nanoEndpoint
615+
endpoint: getEndpoint(requestBody.model.nano_model)
607616
}
608617
};
609618
}
@@ -633,10 +642,15 @@ class APIServer {
633642

634643
// If it's an object with provider/model/api_key, extract those fields
635644
if (typeof tierConfig === 'object' && tierConfig.provider) {
645+
// Get API key with fallback for litellm provider
646+
let apiKey = tierConfig.api_key;
647+
if (!apiKey && tierConfig.provider === 'litellm') {
648+
apiKey = process.env.LITELLM_API_KEY;
649+
}
636650
return {
637651
provider: tierConfig.provider,
638652
model: tierConfig.model,
639-
api_key: tierConfig.api_key
653+
api_key: apiKey
640654
// endpoint will be added by caller
641655
};
642656
}
@@ -750,9 +764,10 @@ class APIServer {
750764
* Create a dynamic evaluation object with nested model configuration
751765
* @param {string|Array<{role: string, content: string}>} input - Input message (string) or conversation array (OpenAI Responses API format)
752766
* @param {import('./types/model-config').ModelConfig} nestedModelConfig - Model configuration
767+
* @param {Object} tracingMetadata - Optional tracing metadata for Langfuse integration
753768
* @returns {import('./types/model-config').EvaluationRequest} Evaluation request object
754769
*/
755-
createDynamicRequestNested(input, nestedModelConfig) {
770+
createDynamicRequestNested(input, nestedModelConfig, tracingMetadata = {}) {
756771
const requestId = `api-req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
757772

758773
// Determine input format and structure accordingly
@@ -792,7 +807,10 @@ class APIServer {
792807
tags: ['api', 'dynamic'],
793808
priority: 'high',
794809
source: 'api'
795-
}
810+
},
811+
// Tracing metadata for Langfuse integration
812+
// Contains session_id, trace_id, eval_id, etc. from eval framework
813+
tracing: tracingMetadata
796814
};
797815
}
798816

agent-server/nodejs/src/lib/BrowserAgentServer.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,11 +730,19 @@ export class BrowserAgentServer extends EventEmitter {
730730
metadata: {
731731
tags: request.metadata?.tags || [],
732732
retries: request.settings?.retry_policy?.max_retries || 0
733-
}
733+
},
734+
// Forward tracing metadata for Langfuse session grouping
735+
tracing: request.tracing || {}
734736
},
735737
id: rpcId
736738
};
737739

740+
logger.debug('RPC request prepared with tracing:', {
741+
hasTracing: !!request.tracing,
742+
tracingKeys: request.tracing ? Object.keys(request.tracing) : [],
743+
sessionId: request.tracing?.session_id
744+
});
745+
738746
// Send RPC request
739747
const response = await connection.rpcClient.callMethod(
740748
connection.ws,

front_end/panels/ai_chat/LLM/LLMClient.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { GenericOpenAIProvider } from './GenericOpenAIProvider.js';
1717
import { CustomProviderManager } from '../core/CustomProviderManager.js';
1818
import { LLMResponseParser } from './LLMResponseParser.js';
1919
import { createLogger } from '../core/Logger.js';
20+
import { getCurrentTracingContext } from '../tracing/TracingConfig.js';
2021

2122
const logger = createLogger('LLMClient');
2223

@@ -48,6 +49,7 @@ export interface LLMCallRequest {
4849
temperature?: number;
4950
retryConfig?: Partial<RetryConfig>;
5051
agentName?: string; // Name of the calling agent for provider-specific routing
52+
tracingMetadata?: Record<string, any>; // Explicit tracing metadata for Langfuse integration
5153
}
5254

5355
/**
@@ -207,6 +209,38 @@ export class LLMClient {
207209
options.agentName = (request as any).agentName;
208210
}
209211

212+
// Get tracing metadata - prefer explicit request metadata over global context
213+
// This ensures metadata flows correctly even when async context is lost
214+
let tracingMetadata = request.tracingMetadata;
215+
216+
if (!tracingMetadata || Object.keys(tracingMetadata).length === 0) {
217+
// Fall back to global tracing context
218+
const tracingContext = getCurrentTracingContext();
219+
logger.info('LLMClient.call() - Checking tracing context (fallback):', {
220+
hasContext: !!tracingContext,
221+
hasMetadata: !!tracingContext?.metadata,
222+
metadataKeys: tracingContext?.metadata ? Object.keys(tracingContext.metadata) : [],
223+
sessionId: tracingContext?.metadata?.session_id,
224+
traceId: tracingContext?.metadata?.trace_id
225+
});
226+
if (tracingContext?.metadata && Object.keys(tracingContext.metadata).length > 0) {
227+
tracingMetadata = tracingContext.metadata;
228+
}
229+
} else {
230+
logger.info('LLMClient.call() - Using explicit tracingMetadata from request:', {
231+
metadataKeys: Object.keys(tracingMetadata),
232+
sessionId: tracingMetadata.session_id,
233+
traceId: tracingMetadata.trace_id
234+
});
235+
}
236+
237+
if (tracingMetadata && Object.keys(tracingMetadata).length > 0) {
238+
options.tracingMetadata = tracingMetadata;
239+
logger.info('Passing tracing metadata to provider:', tracingMetadata);
240+
} else {
241+
logger.info('No tracing metadata available');
242+
}
243+
210244
return provider.callWithMessages(request.model, messages, options);
211245
}
212246

front_end/panels/ai_chat/LLM/LLMTypes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,14 @@ export interface LLMCallOptions {
201201
reasoningLevel?: 'low' | 'medium' | 'high'; // For O-series models
202202
retryConfig?: Partial<RetryConfig>;
203203
agentName?: string; // Name of the calling agent for provider-specific routing
204+
// Tracing metadata for Langfuse integration via LiteLLM
205+
tracingMetadata?: {
206+
session_id?: string;
207+
trace_id?: string;
208+
generation_name?: string;
209+
tags?: string[];
210+
[key: string]: any;
211+
};
204212
}
205213

206214
/**

front_end/panels/ai_chat/LLM/LiteLLMProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ export class LiteLLMProvider extends LLMBaseProvider {
209209
payloadBody.tool_choice = options.tool_choice;
210210
}
211211

212+
// Add tracing metadata for Langfuse integration
213+
// LiteLLM forwards this to Langfuse callbacks
214+
if (options?.tracingMetadata) {
215+
payloadBody.metadata = options.tracingMetadata;
216+
}
217+
212218
logger.info('Request payload:', payloadBody);
213219

214220
const data = await this.makeAPIRequest(payloadBody);

front_end/panels/ai_chat/agent_framework/AgentRunner.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,8 @@ export class AgentRunner {
749749
tools: toolSchemas,
750750
temperature: temperature ?? 0,
751751
agentName: agentName, // Pass agent identity for provider-specific routing
752+
// Pass tracing metadata explicitly for Langfuse integration
753+
tracingMetadata: tracingContext?.metadata,
752754
});
753755

754756
// Complete the generation observation

front_end/panels/ai_chat/core/AgentNodes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,8 @@ export function createAgentNode(modelName: string, provider: LLMProvider, temper
239239
})),
240240
temperature: this.temperature,
241241
agentName: agentName,
242+
// Pass tracing metadata explicitly from state context for Langfuse integration
243+
tracingMetadata: state.context?.tracingContext?.metadata,
242244
});
243245

244246
// Parse the response

front_end/panels/ai_chat/core/AgentService.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -276,15 +276,15 @@ export class AgentService extends Common.ObjectWrapper.ObjectWrapper<{
276276
};
277277
}
278278

279-
// Default: provider requires apiKey
280-
if (!apiKey) {
279+
// Default: provider requires apiKey (unless in AUTOMATED_MODE where keys come dynamically)
280+
if (!apiKey && !BUILD_CONFIG.AUTOMATED_MODE) {
281281
logger.warn(`Provider ${provider} requires API key`);
282282
return null;
283283
}
284284

285285
return {
286286
provider,
287-
apiKey
287+
apiKey: apiKey || '' // Default to empty string for AUTOMATED_MODE
288288
};
289289
}
290290

@@ -597,7 +597,9 @@ export class AgentService extends Common.ObjectWrapper.ObjectWrapper<{
597597
tracingContext: {
598598
sessionId: existingContext?.sessionId || this.#sessionId,
599599
traceId,
600-
parentObservationId: parentObservationId
600+
parentObservationId: parentObservationId,
601+
// Forward metadata from evaluation context for Langfuse session grouping
602+
metadata: existingContext?.metadata
601603
},
602604
executionId: this.#executionId,
603605
abortSignal: this.#abortController?.signal,

front_end/panels/ai_chat/evaluation/EvaluationAgent.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,13 +321,23 @@ export class EvaluationAgent {
321321
tool: params.tool
322322
});
323323

324-
// Create a trace for this evaluation
325-
const traceId = `eval-${params.evaluationId}-${Date.now()}`;
326-
const sessionId = `eval-session-${Date.now()}`;
327-
const tracingContext: TracingContext = {
328-
traceId,
324+
// Use tracing metadata from request params (from eval framework via api-server)
325+
// The tracing field is now properly forwarded through BrowserAgentServer.executeRequest()
326+
const requestTracing = params.tracing || {};
327+
logger.info('Tracing metadata received:', {
328+
hasTracing: !!params.tracing,
329+
tracingKeys: Object.keys(requestTracing),
330+
sessionId: requestTracing.session_id,
331+
traceId: requestTracing.trace_id
332+
});
333+
const traceId = requestTracing.trace_id || `eval-${params.evaluationId}-${Date.now()}`;
334+
const sessionId = requestTracing.session_id || `eval-session-${Date.now()}`;
335+
const tracingContext: TracingContext = {
336+
traceId,
329337
sessionId,
330-
parentObservationId: undefined
338+
parentObservationId: undefined,
339+
// Include full tracing metadata for LLM calls
340+
metadata: requestTracing
331341
};
332342

333343
try {

front_end/panels/ai_chat/evaluation/EvaluationProtocol.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ export interface EvaluationParams {
9191
retries: number;
9292
priority?: 'low' | 'normal' | 'high';
9393
};
94+
// Tracing metadata for Langfuse session grouping
95+
tracing?: {
96+
session_id?: string;
97+
trace_id?: string;
98+
eval_id?: string;
99+
eval_name?: string;
100+
category?: string;
101+
tags?: string[];
102+
[key: string]: any;
103+
};
94104
}
95105

96106
export interface EvaluationSuccessResponse {

0 commit comments

Comments
 (0)