fix(diagnostics-otel): classify model calls as client spans (#104211)

Model-call spans wrap outbound provider inference requests, so classify them as CLIENT independently of the optional GenAI naming convention. Generic tool spans remain unchanged because their shared events can represent local work.

Release note: operators filtering dashboards or alerts on span kind will see model-call spans move from INTERNAL to CLIENT.

Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
Ted Li
2026-07-18 18:21:45 -07:00
committed by GitHub
co-authored by Peter Steinberger
parent c3adaa3195
commit 50e81ce493
2 changed files with 3 additions and 2 deletions
@@ -210,7 +210,7 @@ export function modelCallSpanName(evt: {
}
export function modelCallSpanKind(): SpanKind | undefined {
return emitLatestGenAiSemconv() ? SpanKind.CLIENT : undefined;
return SpanKind.CLIENT;
}
export function addUpstreamRequestIdSpanEvent(
@@ -2881,7 +2881,7 @@ describe("diagnostics-otel service", () => {
expect(Object.hasOwn(modelOptions?.attributes ?? {}, "openclaw.runId")).toBe(false);
expect(Object.hasOwn(modelOptions?.attributes ?? {}, "openclaw.sessionKey")).toBe(false);
expect(modelOptions?.startTime).toBeTypeOf("number");
expect(Object.hasOwn(modelOptions ?? {}, "kind")).toBe(false);
expect(modelOptions?.kind).toBe(2);
expect(modelCall?.[2]).toBeUndefined();
const harnessCall = startedSpanCall("openclaw.harness.run");
@@ -2918,6 +2918,7 @@ describe("diagnostics-otel service", () => {
expect(Object.hasOwn(toolOptions?.attributes ?? {}, "openclaw.runId")).toBe(false);
expect(Object.hasOwn(toolOptions?.attributes ?? {}, "openclaw.sessionKey")).toBe(false);
expect(toolOptions?.startTime).toBeTypeOf("number");
expect(Object.hasOwn(toolOptions ?? {}, "kind")).toBe(false);
expect(toolCall?.[2]).toBeUndefined();
const modelCallDuration = lastHistogramRecord("openclaw.model_call.duration_ms");