diff --git a/packages/components/src/handler.test.ts b/packages/components/src/handler.test.ts new file mode 100644 index 000000000..333b2cba8 --- /dev/null +++ b/packages/components/src/handler.test.ts @@ -0,0 +1,51 @@ +import { getPhoenixTracer } from './handler' + +jest.mock('@opentelemetry/exporter-trace-otlp-proto', () => { + return { + ProtoOTLPTraceExporter: jest.fn().mockImplementation((args) => { + return { args } + }) + } +}) + +import { OTLPTraceExporter as ProtoOTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-proto' + +describe('URL Handling For Phoenix Tracer', () => { + const apiKey = 'test-api-key' + const projectName = 'test-project-name' + + const makeOptions = (baseUrl: string) => ({ + baseUrl, + apiKey, + projectName, + enableCallback: false + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + const cases: [string, string][] = [ + ['http://localhost:6006', 'http://localhost:6006/v1/traces'], + ['http://localhost:6006/v1/traces', 'http://localhost:6006/v1/traces'], + ['https://app.phoenix.arize.com', 'https://app.phoenix.arize.com/v1/traces'], + ['https://app.phoenix.arize.com/v1/traces', 'https://app.phoenix.arize.com/v1/traces'], + ['https://app.phoenix.arize.com/s/my-space', 'https://app.phoenix.arize.com/s/my-space/v1/traces'], + ['https://app.phoenix.arize.com/s/my-space/v1/traces', 'https://app.phoenix.arize.com/s/my-space/v1/traces'], + ['https://my-phoenix.com/my-slug', 'https://my-phoenix.com/my-slug/v1/traces'], + ['https://my-phoenix.com/my-slug/v1/traces', 'https://my-phoenix.com/my-slug/v1/traces'] + ] + + it.each(cases)('baseUrl %s - exporterUrl %s', (input, expected) => { + getPhoenixTracer(makeOptions(input)) + expect(ProtoOTLPTraceExporter).toHaveBeenCalledWith( + expect.objectContaining({ + url: expected, + headers: expect.objectContaining({ + api_key: apiKey, + authorization: `Bearer ${apiKey}` + }) + }) + ) + }) +}) diff --git a/packages/components/src/handler.ts b/packages/components/src/handler.ts index fc885d9ad..bed8c41f0 100644 --- a/packages/components/src/handler.ts +++ b/packages/components/src/handler.ts @@ -1,4 +1,5 @@ import { Logger } from 'winston' +import { URL } from 'url' import { v4 as uuidv4 } from 'uuid' import { Client } from 'langsmith' import CallbackHandler from 'langfuse-langchain' @@ -91,14 +92,27 @@ interface PhoenixTracerOptions { enableCallback?: boolean } -function getPhoenixTracer(options: PhoenixTracerOptions): Tracer | undefined { +export function getPhoenixTracer(options: PhoenixTracerOptions): Tracer | undefined { const SEMRESATTRS_PROJECT_NAME = 'openinference.project.name' try { + const parsedURL = new URL(options.baseUrl) + const baseEndpoint = `${parsedURL.protocol}//${parsedURL.host}` + + // Remove trailing slashes + let path = parsedURL.pathname.replace(/\/$/, '') + + // Remove any existing /v1/traces suffix + path = path.replace(/\/v1\/traces$/, '') + + const exporterUrl = `${baseEndpoint}${path}/v1/traces` + const exporterHeaders = { + api_key: options.apiKey || '', + authorization: `Bearer ${options.apiKey || ''}` + } + const traceExporter = new ProtoOTLPTraceExporter({ - url: `${options.baseUrl}/v1/traces`, - headers: { - api_key: options.apiKey - } + url: exporterUrl, + headers: exporterHeaders }) const tracerProvider = new NodeTracerProvider({ resource: new Resource({