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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {WIDGET_COLUMN_LABELS} from 'sentry/views/dashboards/utils/prebuiltConfig
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields, SpanFunction} from 'sentry/views/insights/types';

const MCP_SERVER_FILTER = `${SpanFields.SPAN_OP}:mcp.server`;
const MCP_SERVER_FILTER = `${SpanFields.NAME}:mcp.server`;
const MCP_TOOL_FILTER = `${MCP_SERVER_FILTER} has:${SpanFields.MCP_TOOL_NAME}`;
const MCP_RESOURCE_FILTER = `${MCP_SERVER_FILTER} has:${SpanFields.MCP_RESOURCE_URI}`;
const MCP_PROMPT_FILTER = `${MCP_SERVER_FILTER} has:${SpanFields.MCP_PROMPT_NAME}`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {WIDGET_COLUMN_LABELS} from 'sentry/views/dashboards/utils/prebuiltConfig
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields, SpanFunction} from 'sentry/views/insights/types';

const MCP_PROMPT_FILTER = `${SpanFields.SPAN_OP}:mcp.server has:${SpanFields.MCP_PROMPT_NAME}`;
const MCP_PROMPT_FILTER = `${SpanFields.NAME}:mcp.server has:${SpanFields.MCP_PROMPT_NAME}`;

const FIRST_ROW_WIDGETS = spaceWidgetsEquallyOnRow(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {WIDGET_COLUMN_LABELS} from 'sentry/views/dashboards/utils/prebuiltConfig
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields, SpanFunction} from 'sentry/views/insights/types';

const MCP_RESOURCE_FILTER = `${SpanFields.SPAN_OP}:mcp.server has:${SpanFields.MCP_RESOURCE_URI}`;
const MCP_RESOURCE_FILTER = `${SpanFields.NAME}:mcp.server has:${SpanFields.MCP_RESOURCE_URI}`;

const FIRST_ROW_WIDGETS = spaceWidgetsEquallyOnRow(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {WIDGET_COLUMN_LABELS} from 'sentry/views/dashboards/utils/prebuiltConfig
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields, SpanFunction} from 'sentry/views/insights/types';

const MCP_TOOL_FILTER = `${SpanFields.SPAN_OP}:mcp.server has:${SpanFields.MCP_TOOL_NAME}`;
const MCP_TOOL_FILTER = `${SpanFields.NAME}:mcp.server has:${SpanFields.MCP_TOOL_NAME}`;

const FIRST_ROW_WIDGETS = spaceWidgetsEquallyOnRow(
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ const BodyCell = memo(function BodyCell({
},
],
query: `gen_ai.tool.name:"${dataRow.tool}"`,
field: ['span.description', 'gen_ai.tool.output', 'span.duration', 'timestamp'],
field: ['span.name', 'gen_ai.tool.output', 'span.duration', 'timestamp'],
});

switch (column.key) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,7 @@ export function useAgentSpanSearchProps() {
},
searchSource: 'agent-monitoring',

replaceRawSearchKeys: hasRawSearchReplacement
? ['span.description', 'span.name']
: undefined,
replaceRawSearchKeys: hasRawSearchReplacement ? ['span.name'] : undefined,
matchKeySuggestions: [
{key: 'trace', valuePattern: /^[0-9a-fA-F]{32}$/},
{key: 'id', valuePattern: /^[0-9a-fA-F]{16}$/},
Expand Down
10 changes: 6 additions & 4 deletions static/app/views/insights/pages/agents/utils/aiTraceNodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {EventTransaction} from 'sentry/types/event';
import {prettifyAttributeName} from 'sentry/views/explore/components/traceItemAttributes/utils';
import type {TraceItemResponseAttribute} from 'sentry/views/explore/hooks/useTraceItemDetails';
import {
getGenAiOperationTypeFromSpanOp,
getGenAiOperationTypeFromSpanName,
getIsAiAgentSpan,
getIsAiGenerationSpan,
getIsExecuteToolSpan,
Expand Down Expand Up @@ -57,16 +57,18 @@ export function ensureAttributeObject(

/**
* Returns the `gen_ai.operation.type` for a given trace node.
* If the attribute is not present it will deduce it from the `span.op`
* If the attribute is not present it will deduce it from the `span.name`
*
* **Note:** To keep the complexity manageable, this logic does not work for the edge case of transactions without `span.op` on the old data model.
* **Note:** To keep the complexity manageable, this logic does not work for the edge case of transactions without `span.name` on the old data model.
*/
export function getGenAiOpType(node: BaseNode): string | undefined {
const attributeObject = node.attributes;

return (
(attributeObject?.[SpanFields.GEN_AI_OPERATION_TYPE] as string | undefined) ??
getGenAiOperationTypeFromSpanOp(node.op)
getGenAiOperationTypeFromSpanName(
node.value && 'name' in node.value ? (node.value.name as string) : undefined
)
);
}

Expand Down
53 changes: 53 additions & 0 deletions static/app/views/insights/pages/agents/utils/query.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {GenAiOperationType, getGenAiOperationTypeFromSpanName} from './query';

describe('getGenAiOperationTypeFromSpanName', () => {
it('returns undefined for undefined input', () => {
expect(getGenAiOperationTypeFromSpanName(undefined)).toBeUndefined();
});

it('returns undefined for empty string', () => {
expect(getGenAiOperationTypeFromSpanName('')).toBeUndefined();
});

it('returns undefined for non-gen_ai span names', () => {
expect(getGenAiOperationTypeFromSpanName('http.client')).toBeUndefined();
expect(getGenAiOperationTypeFromSpanName('db.query')).toBeUndefined();
expect(getGenAiOperationTypeFromSpanName('mcp.server')).toBeUndefined();
});

it('returns AGENT for gen_ai.invoke_agent', () => {
expect(getGenAiOperationTypeFromSpanName('gen_ai.invoke_agent')).toBe(
GenAiOperationType.AGENT
);
});

it('returns AGENT for gen_ai.create_agent', () => {
expect(getGenAiOperationTypeFromSpanName('gen_ai.create_agent')).toBe(
GenAiOperationType.AGENT
);
});

it('returns TOOL for gen_ai.execute_tool', () => {
expect(getGenAiOperationTypeFromSpanName('gen_ai.execute_tool')).toBe(
GenAiOperationType.TOOL
);
});

it('returns HANDOFF for gen_ai.handoff', () => {
expect(getGenAiOperationTypeFromSpanName('gen_ai.handoff')).toBe(
GenAiOperationType.HANDOFF
);
});

it('returns AI_CLIENT for other gen_ai span names', () => {
expect(getGenAiOperationTypeFromSpanName('gen_ai.chat')).toBe(
GenAiOperationType.AI_CLIENT
);
expect(getGenAiOperationTypeFromSpanName('gen_ai.completion')).toBe(
GenAiOperationType.AI_CLIENT
);
expect(getGenAiOperationTypeFromSpanName('gen_ai.embeddings')).toBe(
GenAiOperationType.AI_CLIENT
);
});
});
12 changes: 6 additions & 6 deletions static/app/views/insights/pages/agents/utils/query.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,20 +75,20 @@ export enum GenAiOperationType {
}

// Should be used only when we don't have the gen_ai.operation.type attribute available
export const getGenAiOperationTypeFromSpanOp = (
spanOp?: string
export const getGenAiOperationTypeFromSpanName = (
spanName?: string
): GenAiOperationType | undefined => {
if (!spanOp?.startsWith('gen_ai.')) {
if (!spanName?.startsWith('gen_ai.')) {
return undefined;
}

if (['gen_ai.invoke_agent', 'gen_ai.create_agent'].includes(spanOp)) {
if (['gen_ai.invoke_agent', 'gen_ai.create_agent'].includes(spanName)) {
return GenAiOperationType.AGENT;
}
if (spanOp === 'gen_ai.execute_tool') {
if (spanName === 'gen_ai.execute_tool') {
return GenAiOperationType.TOOL;
}
if (spanOp === 'gen_ai.handoff') {
if (spanName === 'gen_ai.handoff') {
return GenAiOperationType.HANDOFF;
}
return GenAiOperationType.AI_CLIENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-1',
trace: 'trace-1',
Expand Down Expand Up @@ -81,8 +80,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-output',
trace: 'trace-output',
Expand Down Expand Up @@ -123,8 +121,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-2',
trace: 'trace-2',
Expand Down Expand Up @@ -161,8 +158,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-3',
trace: 'trace-3',
Expand Down Expand Up @@ -201,8 +197,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-ts',
trace: 'trace-ts',
Expand Down Expand Up @@ -247,20 +242,18 @@ describe('useConversation', () => {
expect(queryArg).not.toHaveProperty('environment');
});

it('falls back to span.name when span.description is empty', async () => {
it('uses span.name for description and name fields', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-name-fallback/`,
url: `/organizations/${organization.slug}/ai-conversations/conv-name/`,
body: [
{
'gen_ai.conversation.id': 'conv-name-fallback',
'gen_ai.conversation.id': 'conv-name',
parent_span: 'parent-1',
'precise.finish_ts': 1000.5,
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': '',
'span.name': 'My AI Agent',
'span.op': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-name',
trace: 'trace-name',
Expand All @@ -270,7 +263,7 @@ describe('useConversation', () => {
});

const {result} = renderHookWithProviders(
() => useConversation({conversationId: 'conv-name-fallback'}),
() => useConversation({conversationId: 'conv-name'}),
{organization}
);

Expand All @@ -285,44 +278,6 @@ describe('useConversation', () => {
expect(value?.name).toBe('My AI Agent');
});

it('prefers span.description over span.name when both exist', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-both/`,
body: [
{
'gen_ai.conversation.id': 'conv-both',
parent_span: 'parent-1',
'precise.finish_ts': 1000.5,
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.name': 'My AI Agent',
'span.op': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-both',
trace: 'trace-both',
'gen_ai.operation.type': 'ai_client',
},
],
});

const {result} = renderHookWithProviders(
() => useConversation({conversationId: 'conv-both'}),
{organization}
);

await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});

expect(result.current.nodes).toHaveLength(1);
const node = result.current.nodes[0];
const value = node?.value as {description?: string; name?: string};
expect(value?.description).toBe('AI generation');
expect(value?.name).toBe('AI generation');
});

it('sorts nodes by start timestamp for AI spans list', async () => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/ai-conversations/conv-sort/`,
Expand All @@ -334,8 +289,7 @@ describe('useConversation', () => {
'precise.start_ts': 1001.0,
project: 'test-project',
'project.id': 1,
'span.description': 'Second by start, first by end',
'span.op': 'gen_ai.generate',
'span.name': 'Second by start, first by end',
'span.status': 'ok',
span_id: 'span-b',
trace: 'trace-sort',
Expand All @@ -348,8 +302,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'First by start, second by end',
'span.op': 'gen_ai.generate',
'span.name': 'First by start, second by end',
'span.status': 'ok',
span_id: 'span-a',
trace: 'trace-sort',
Expand Down Expand Up @@ -384,8 +337,7 @@ describe('useConversation', () => {
'precise.start_ts': 1000.0,
project: 'test-project',
'project.id': 1,
'span.description': 'AI generation',
'span.op': 'gen_ai.generate',
'span.name': 'gen_ai.generate',
'span.status': 'ok',
span_id: 'span-ai',
trace: 'trace-1',
Expand All @@ -398,8 +350,7 @@ describe('useConversation', () => {
'precise.start_ts': 1001.0,
project: 'test-project',
'project.id': 1,
'span.description': 'HTTP request',
'span.op': 'http.client',
'span.name': 'http.client',
'span.status': 'ok',
span_id: 'span-http',
trace: 'trace-1',
Expand Down
Loading
Loading