Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/handlers/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
TOOL_NAME,
TOOL_PARAMETERS,
} from "@arizeai/openinference-semantic-conventions"
import { ATTR_GEN_AI_AGENT_NAME } from "@opentelemetry/semantic-conventions/incubating"
import {
agentAttrs,
errorSummary,
Expand Down Expand Up @@ -123,6 +124,7 @@ export function handleMessageUpdated(e: EventMessageUpdated, ctx: HandlerContext
const outputText = ctx.messageOutputs.get(msgKey)
msgSpan.setAttributes({
[AGENT_NAME]: agentName,
[ATTR_GEN_AI_AGENT_NAME]: agentName,
"agent.type": agentType,
[LLM_TOKEN_COUNT_PROMPT]: assistant.tokens.input,
[LLM_TOKEN_COUNT_COMPLETION]: assistant.tokens.output,
Expand Down Expand Up @@ -449,6 +451,7 @@ export function startMessageSpan(
[OPENINFERENCE_SPAN_KIND]: OpenInferenceSpanKind.LLM,
[SESSION_ID]: sessionID,
[AGENT_NAME]: agentName,
[ATTR_GEN_AI_AGENT_NAME]: agentName,
"agent.type": agentType,
[LLM_SYSTEM]: providerID,
[LLM_PROVIDER]: providerID,
Expand Down
20 changes: 20 additions & 0 deletions tests/handlers/spans.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@ describe("message (LLM) spans", () => {
expect(tracer.spans[0]!.attributes[LLM_MODEL_NAME]).toBe("claude-sonnet-4")
})

// Asserted as a literal key, not via the semconv constant the source imports:
// this string is the wire format Gen AI consumers match on, so the test has to
// fail if the constant is renamed upstream.
test("startMessageSpan mirrors the agent name onto gen_ai.agent.name", () => {
const { ctx, tracer } = makeCtx()
startMessageSpan("ses_1", "msg_1", "user_1", "claude-sonnet-4", "anthropic", 1000, ctx, "plan")
expect(tracer.spans[0]!.attributes["gen_ai.agent.name"]).toBe(tracer.spans[0]!.attributes[AGENT_NAME])
})

test("startMessageSpan is a no-op when span already exists for sessionID:messageID", () => {
const { ctx, tracer } = makeCtx()
startMessageSpan("ses_1", "msg_1", "user_1", "claude", "anthropic", 1000, ctx)
Expand Down Expand Up @@ -434,6 +443,17 @@ describe("message (LLM) spans", () => {
expect(span.attributes["agent.type"]).toBe("subagent")
})

test("handleMessageUpdated mirrors the agent name onto gen_ai.agent.name", () => {
const { ctx, tracer } = makeCtx()
startMessageSpan("ses_1", "msg_1", "user_1", "claude-3-5-sonnet", "anthropic", 1000, ctx)
ctx.sessionTotals.set("ses_1", { startMs: 0, tokens: 0, cost: 0, messages: 0, agent: "review", agentType: "subagent" })
handleMessageUpdated(makeAssistantMessageUpdated({ id: "msg_1" }), ctx)
const span = tracer.spans[0]!
expect(span.attributes["gen_ai.agent.name"]).toBe("review")
// the OpenInference key is mirrored, never replaced
expect(span.attributes[AGENT_NAME]).toBe("review")
})

test("handleMessageUpdated no-ops span handling when no span exists for messageID", () => {
const { ctx, tracer } = makeCtx()
const spansBefore = tracer.spans.length
Expand Down
Loading