Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions agent-server/nodejs/src/api-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,8 +548,11 @@ class APIServer {
await new Promise(resolve => setTimeout(resolve, waitTimeout));
}

// Extract tracing metadata for Langfuse integration
const tracingMetadata = requestBody.metadata || {};
Comment on lines +551 to +552
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for frontend protocol definition
echo "=== Frontend EvaluationProtocol.ts (lines around 107-116) ==="
if [ -f "front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts" ]; then
  sed -n '100,130p' "front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts" | cat -n
else
  echo "File not found at that path, searching for it..."
  fd "EvaluationProtocol.ts" --exec cat -n {} \; | head -50
fi

echo -e "\n=== Backend api-server.js - search for metadata/tracing field ==="
rg -n "metadata|tracing" agent-server/nodejs/src/api-server.js -A 2 -B 2 | head -100

echo -e "\n=== Backend api-server.js lines 551-560 ==="
if [ -f "agent-server/nodejs/src/api-server.js" ]; then
  sed -n '545,560p' "agent-server/nodejs/src/api-server.js" | cat -n
fi

Repository: BrowserOperator/browser-operator-core

Length of output: 3960


Extract tracing metadata from requestBody.tracing, not requestBody.metadata.

The frontend protocol (EvaluationProtocol.ts) defines two separate fields:

  • metadata: tags, retries, priority (lines 103-107)
  • tracing: session_id, trace_id, eval_id, etc. for Langfuse (lines 108-117)

Line 552 incorrectly extracts from requestBody.metadata instead of requestBody.tracing. This causes all tracing metadata (session IDs, trace IDs, etc.) sent by the frontend to be lost. Change line 552 to:

const tracingMetadata = requestBody.tracing || {};
🤖 Prompt for AI Agents
In agent-server/nodejs/src/api-server.js around lines 551 to 552, the code
extracts tracing metadata from requestBody.metadata which is incorrect; update
the extraction to read from requestBody.tracing instead (i.e., replace the
metadata reference with tracing) so tracing fields (session_id, trace_id,
eval_id, etc.) sent by the frontend are preserved and used for Langfuse
integration.


// Create a dynamic request for this request
const request = this.createDynamicRequestNested(requestBody.input, nestedModelConfig);
const request = this.createDynamicRequestNested(requestBody.input, nestedModelConfig, tracingMetadata);

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

// If nested format is provided, use it directly with fallbacks
if (requestBody.model) {
// Extract endpoint from each model tier, with fallback chain:
// Helper to get endpoint with fallback chain:
// 1. Try tier-specific endpoint (e.g., main_model.endpoint)
// 2. Fall back to top-level endpoint (e.g., model.endpoint)
// 3. Fall back to undefined (will use env var later)
const mainEndpoint = requestBody.model.main_model?.endpoint || requestBody.model.endpoint;
const miniEndpoint = requestBody.model.mini_model?.endpoint || requestBody.model.endpoint;
const nanoEndpoint = requestBody.model.nano_model?.endpoint || requestBody.model.endpoint;
// 3. Fall back to LITELLM_ENDPOINT env var (for litellm provider)
const getEndpoint = (tierConfig) => {
const explicitEndpoint = tierConfig?.endpoint || requestBody.model.endpoint;
if (explicitEndpoint) return explicitEndpoint;
// Use env var default for litellm provider
if (tierConfig?.provider === 'litellm') return defaultLiteLLMEndpoint;
return undefined;
};

return {
main_model: {
...this.extractModelTierConfig('main', requestBody.model.main_model, defaults),
endpoint: mainEndpoint
endpoint: getEndpoint(requestBody.model.main_model)
},
mini_model: {
...this.extractModelTierConfig('mini', requestBody.model.mini_model, defaults),
endpoint: miniEndpoint
endpoint: getEndpoint(requestBody.model.mini_model)
},
nano_model: {
...this.extractModelTierConfig('nano', requestBody.model.nano_model, defaults),
endpoint: nanoEndpoint
endpoint: getEndpoint(requestBody.model.nano_model)
}
};
}
Expand Down Expand Up @@ -633,10 +642,15 @@ class APIServer {

// If it's an object with provider/model/api_key, extract those fields
if (typeof tierConfig === 'object' && tierConfig.provider) {
// Get API key with fallback for litellm provider
let apiKey = tierConfig.api_key;
if (!apiKey && tierConfig.provider === 'litellm') {
apiKey = process.env.LITELLM_API_KEY;
}
return {
provider: tierConfig.provider,
model: tierConfig.model,
api_key: tierConfig.api_key
api_key: apiKey
// endpoint will be added by caller
};
}
Expand Down Expand Up @@ -750,9 +764,10 @@ class APIServer {
* Create a dynamic evaluation object with nested model configuration
* @param {string|Array<{role: string, content: string}>} input - Input message (string) or conversation array (OpenAI Responses API format)
* @param {import('./types/model-config').ModelConfig} nestedModelConfig - Model configuration
* @param {Object} tracingMetadata - Optional tracing metadata for Langfuse integration
* @returns {import('./types/model-config').EvaluationRequest} Evaluation request object
*/
createDynamicRequestNested(input, nestedModelConfig) {
createDynamicRequestNested(input, nestedModelConfig, tracingMetadata = {}) {
const requestId = `api-req-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;

// Determine input format and structure accordingly
Expand Down Expand Up @@ -792,7 +807,10 @@ class APIServer {
tags: ['api', 'dynamic'],
priority: 'high',
source: 'api'
}
},
// Tracing metadata for Langfuse integration
// Contains session_id, trace_id, eval_id, etc. from eval framework
tracing: tracingMetadata
};
}

Expand Down
10 changes: 9 additions & 1 deletion agent-server/nodejs/src/lib/BrowserAgentServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -730,11 +730,19 @@ export class BrowserAgentServer extends EventEmitter {
metadata: {
tags: request.metadata?.tags || [],
retries: request.settings?.retry_policy?.max_retries || 0
}
},
// Forward tracing metadata for Langfuse session grouping
tracing: request.tracing || {}
},
id: rpcId
};

logger.debug('RPC request prepared with tracing:', {
hasTracing: !!request.tracing,
tracingKeys: request.tracing ? Object.keys(request.tracing) : [],
sessionId: request.tracing?.session_id
});

// Send RPC request
const response = await connection.rpcClient.callMethod(
connection.ws,
Expand Down
34 changes: 34 additions & 0 deletions front_end/panels/ai_chat/LLM/LLMClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { GenericOpenAIProvider } from './GenericOpenAIProvider.js';
import { CustomProviderManager } from '../core/CustomProviderManager.js';
import { LLMResponseParser } from './LLMResponseParser.js';
import { createLogger } from '../core/Logger.js';
import { getCurrentTracingContext } from '../tracing/TracingConfig.js';

const logger = createLogger('LLMClient');

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

/**
Expand Down Expand Up @@ -207,6 +209,38 @@ export class LLMClient {
options.agentName = (request as any).agentName;
}

// Get tracing metadata - prefer explicit request metadata over global context
// This ensures metadata flows correctly even when async context is lost
let tracingMetadata = request.tracingMetadata;

if (!tracingMetadata || Object.keys(tracingMetadata).length === 0) {
// Fall back to global tracing context
const tracingContext = getCurrentTracingContext();
logger.info('LLMClient.call() - Checking tracing context (fallback):', {
hasContext: !!tracingContext,
hasMetadata: !!tracingContext?.metadata,
metadataKeys: tracingContext?.metadata ? Object.keys(tracingContext.metadata) : [],
sessionId: tracingContext?.metadata?.session_id,
traceId: tracingContext?.metadata?.trace_id
});
if (tracingContext?.metadata && Object.keys(tracingContext.metadata).length > 0) {
tracingMetadata = tracingContext.metadata;
}
} else {
logger.info('LLMClient.call() - Using explicit tracingMetadata from request:', {
metadataKeys: Object.keys(tracingMetadata),
sessionId: tracingMetadata.session_id,
traceId: tracingMetadata.trace_id
});
}

if (tracingMetadata && Object.keys(tracingMetadata).length > 0) {
options.tracingMetadata = tracingMetadata;
logger.info('Passing tracing metadata to provider:', tracingMetadata);
} else {
logger.info('No tracing metadata available');
}

return provider.callWithMessages(request.model, messages, options);
}

Expand Down
8 changes: 8 additions & 0 deletions front_end/panels/ai_chat/LLM/LLMTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,14 @@ export interface LLMCallOptions {
reasoningLevel?: 'low' | 'medium' | 'high'; // For O-series models
retryConfig?: Partial<RetryConfig>;
agentName?: string; // Name of the calling agent for provider-specific routing
// Tracing metadata for Langfuse integration via LiteLLM
tracingMetadata?: {
session_id?: string;
trace_id?: string;
generation_name?: string;
tags?: string[];
[key: string]: any;
};
}

/**
Expand Down
6 changes: 6 additions & 0 deletions front_end/panels/ai_chat/LLM/LiteLLMProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,12 @@ export class LiteLLMProvider extends LLMBaseProvider {
payloadBody.tool_choice = options.tool_choice;
}

// Add tracing metadata for Langfuse integration
// LiteLLM forwards this to Langfuse callbacks
if (options?.tracingMetadata) {
payloadBody.metadata = options.tracingMetadata;
}

logger.info('Request payload:', payloadBody);

const data = await this.makeAPIRequest(payloadBody);
Expand Down
2 changes: 2 additions & 0 deletions front_end/panels/ai_chat/agent_framework/AgentRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,8 @@ export class AgentRunner {
tools: toolSchemas,
temperature: temperature ?? 0,
agentName: agentName, // Pass agent identity for provider-specific routing
// Pass tracing metadata explicitly for Langfuse integration
tracingMetadata: tracingContext?.metadata,
});

// Complete the generation observation
Expand Down
2 changes: 2 additions & 0 deletions front_end/panels/ai_chat/core/AgentNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,8 @@ export function createAgentNode(modelName: string, provider: LLMProvider, temper
})),
temperature: this.temperature,
agentName: agentName,
// Pass tracing metadata explicitly from state context for Langfuse integration
tracingMetadata: state.context?.tracingContext?.metadata,
});

// Parse the response
Expand Down
10 changes: 6 additions & 4 deletions front_end/panels/ai_chat/core/AgentService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,15 +276,15 @@ export class AgentService extends Common.ObjectWrapper.ObjectWrapper<{
};
}

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

return {
provider,
apiKey
apiKey: apiKey || '' // Default to empty string for AUTOMATED_MODE
};
}

Expand Down Expand Up @@ -597,7 +597,9 @@ export class AgentService extends Common.ObjectWrapper.ObjectWrapper<{
tracingContext: {
sessionId: existingContext?.sessionId || this.#sessionId,
traceId,
parentObservationId: parentObservationId
parentObservationId: parentObservationId,
// Forward metadata from evaluation context for Langfuse session grouping
metadata: existingContext?.metadata
},
executionId: this.#executionId,
abortSignal: this.#abortController?.signal,
Expand Down
22 changes: 16 additions & 6 deletions front_end/panels/ai_chat/evaluation/EvaluationAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,13 +321,23 @@ export class EvaluationAgent {
tool: params.tool
});

// Create a trace for this evaluation
const traceId = `eval-${params.evaluationId}-${Date.now()}`;
const sessionId = `eval-session-${Date.now()}`;
const tracingContext: TracingContext = {
traceId,
// Use tracing metadata from request params (from eval framework via api-server)
// The tracing field is now properly forwarded through BrowserAgentServer.executeRequest()
const requestTracing = params.tracing || {};
logger.info('Tracing metadata received:', {
hasTracing: !!params.tracing,
tracingKeys: Object.keys(requestTracing),
sessionId: requestTracing.session_id,
traceId: requestTracing.trace_id
});
const traceId = requestTracing.trace_id || `eval-${params.evaluationId}-${Date.now()}`;
const sessionId = requestTracing.session_id || `eval-session-${Date.now()}`;
const tracingContext: TracingContext = {
traceId,
sessionId,
parentObservationId: undefined
parentObservationId: undefined,
// Include full tracing metadata for LLM calls
metadata: requestTracing
};

try {
Expand Down
10 changes: 10 additions & 0 deletions front_end/panels/ai_chat/evaluation/EvaluationProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,16 @@ export interface EvaluationParams {
retries: number;
priority?: 'low' | 'normal' | 'high';
};
// Tracing metadata for Langfuse session grouping
tracing?: {
session_id?: string;
trace_id?: string;
eval_id?: string;
eval_name?: string;
category?: string;
tags?: string[];
[key: string]: any;
};
}

export interface EvaluationSuccessResponse {
Expand Down
24 changes: 18 additions & 6 deletions front_end/panels/ai_chat/evaluation/remote/EvaluationAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,13 +426,25 @@ export class EvaluationAgent {
tool: params.tool
});

// Create a trace for this evaluation
const traceId = `eval-${params.evaluationId}-${Date.now()}`;
const sessionId = `eval-session-${Date.now()}`;
const tracingContext: TracingContext = {
traceId,
// Use tracing metadata from request params (from eval framework via api-server)
// The tracing field is now properly forwarded through BrowserAgentServer.executeRequest()
const requestTracing = params.tracing || {};
logger.info('Tracing metadata received:', {
hasTracing: !!params.tracing,
tracingKeys: Object.keys(requestTracing),
sessionId: requestTracing.session_id,
traceId: requestTracing.trace_id
});

// Create a trace for this evaluation - use tracing from request if available
const traceId = requestTracing.trace_id || `eval-${params.evaluationId}-${Date.now()}`;
const sessionId = requestTracing.session_id || `eval-session-${Date.now()}`;
const tracingContext: TracingContext = {
traceId,
sessionId,
parentObservationId: undefined
parentObservationId: undefined,
// Include full tracing metadata for LLM calls (session grouping in Langfuse)
metadata: requestTracing
};
const orchestratorDescriptor = await this.orchestratorDescriptorPromise;

Expand Down
10 changes: 10 additions & 0 deletions front_end/panels/ai_chat/evaluation/remote/EvaluationProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ export interface EvaluationParams {
retries: number;
priority?: 'low' | 'normal' | 'high';
};
// Tracing metadata for Langfuse session grouping
tracing?: {
session_id?: string;
trace_id?: string;
eval_id?: string;
eval_name?: string;
category?: string;
tags?: string[];
trace_name?: string;
};
}

export interface EvaluationSuccessResponse {
Expand Down
2 changes: 2 additions & 0 deletions front_end/panels/ai_chat/tools/LLMTracingWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export async function callLLMWithTracing(
messages: llmCallConfig.messages,
systemPrompt: llmCallConfig.systemPrompt || '',
temperature: llmCallConfig.temperature,
// Pass tracing metadata explicitly for Langfuse integration
tracingMetadata: tracingContext?.metadata,
...llmCallConfig.options
});

Expand Down
10 changes: 10 additions & 0 deletions front_end/panels/ai_chat/tracing/TracingProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ export interface TracingContext {
agentType: string;
iterationCount?: number;
};
// Tracing metadata from eval framework for Langfuse integration
metadata?: {
session_id?: string;
trace_id?: string;
eval_id?: string;
eval_name?: string;
category?: string;
tags?: string[];
[key: string]: any;
};
}

export interface TraceMetadata {
Expand Down