Skip to content
Merged
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
32 changes: 7 additions & 25 deletions agents/src/telemetry/otel_http_exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,6 @@ export class SimpleOTLPHttpLogExporter {
private readonly config: SimpleOTLPHttpLogExporterConfig | SimpleOTLPHttpLogExporterUrlConfig;
private jwt: string | null = null;

private static readonly FORCE_DOUBLE_KEYS = new Set([
'transcriptConfidence',
'transcriptionDelay',
'endOfTurnDelay',
'onUserTurnCompletedDelay',
'llmNodeTtft',
'ttsNodeTtfb',
'e2eLatency',
]);

constructor(config: SimpleOTLPHttpLogExporterConfig | SimpleOTLPHttpLogExporterUrlConfig) {
this.config = config;
}
Expand Down Expand Up @@ -136,7 +126,7 @@ export class SimpleOTLPHttpLogExporter {
private buildPayload(records: SimpleLogRecord[]): object {
const resourceAttrs = Object.entries(this.config.resourceAttributes).map(([key, value]) => ({
key,
value: this.convertValue(value, key),
value: this.convertValue(value),
}));

if (!this.config.resourceAttributes['service.name']) {
Expand All @@ -146,7 +136,7 @@ export class SimpleOTLPHttpLogExporter {
const scopeAttrs = this.config.scopeAttributes
? Object.entries(this.config.scopeAttributes).map(([key, value]) => ({
key,
value: this.convertValue(value, key),
value: this.convertValue(value),
}))
: [];

Expand Down Expand Up @@ -188,26 +178,18 @@ export class SimpleOTLPHttpLogExporter {
): Array<{ key: string; value: unknown }> {
return Object.entries(attrs).map(([key, value]) => ({
key,
value: this.convertValue(value, key),
value: this.convertValue(value),
}));
}

private convertValue(value: unknown, path: string = ''): unknown {
private convertValue(value: unknown): unknown {
if (value === null || value === undefined) {
return { stringValue: '' };
}
if (typeof value === 'string') {
return { stringValue: value };
}
if (typeof value === 'number') {
const leafKey =
path
.split('.')
.pop()
?.replace(/\[\d+\]$/, '') ?? path;
if (SimpleOTLPHttpLogExporter.FORCE_DOUBLE_KEYS.has(leafKey)) {
return { doubleValue: value };
}
return Number.isInteger(value) ? { intValue: String(value) } : { doubleValue: value };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Integer metrics change wire types

When public callers submit integer-valued metrics under the seven camelCase keys, convertValue now emits integers instead of doubles. OTLP backends can reject them or split those metrics across incompatible types.

Prompt for agents
Restore stable double encoding for the seven camelCase metric keys accepted by SimpleOTLPHttpLogExporter. The class and SimpleLogRecord are exported from agents/src/telemetry/index.ts, so downstream callers can supply these keys even though current internal chat-item serialization uses snake_case. The fix must preserve key context through resource, scope, record, array, object, and toJSON conversion paths, or replace that mechanism with an equivalent normalization that keeps these established attributes as OTLP doubleValue when their numeric value is an integer. Add a public-exporter regression test covering direct and nested integer-valued attributes.
Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

}
if (typeof value === 'boolean') {
Expand All @@ -216,21 +198,21 @@ export class SimpleOTLPHttpLogExporter {
if (Array.isArray(value)) {
return {
arrayValue: {
values: value.map((v, i) => this.convertValue(v, `${path}[${i}]`)),
values: value.map((v) => this.convertValue(v)),
},
};
}
if (typeof value === 'object') {
// Honor `toJSON()` like `JSON.stringify` does.
const toJSON = (value as { toJSON?: unknown }).toJSON;
if (typeof toJSON === 'function') {
return this.convertValue((value as { toJSON(): unknown }).toJSON(), path);
return this.convertValue((value as { toJSON(): unknown }).toJSON());
}
return {
kvlistValue: {
values: Object.entries(value as Record<string, unknown>).map(([k, v]) => ({
key: k,
value: this.convertValue(v, path ? `${path}.${k}` : k),
value: this.convertValue(v),
})),
},
};
Expand Down
Loading