feat(telemetry) Instrument traces with more attributes and make them available to OTEL users (#20237)

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Jerop Kipruto <jerop@google.com>
Co-authored-by: MD. MOHIBUR RAHMAN <35300157+mrpmohiburrahman@users.noreply.github.com>
Co-authored-by: Jeffrey Ying <jeffrey.ying86@live.com>
Co-authored-by: Bryan Morgan <bryanmorgan@google.com>
Co-authored-by: joshualitt <joshualitt@google.com>
Co-authored-by: Dev Randalpura <devrandalpura@google.com>
Co-authored-by: Google Admin <github-admin@google.com>
Co-authored-by: Ben Knutson <benknutson@google.com>
This commit is contained in:
heaventourist
2026-02-26 18:26:16 -08:00
committed by GitHub
parent 4b7ce1fe67
commit b1befee8fb
21 changed files with 903 additions and 136 deletions

View File

@@ -23,10 +23,30 @@ import type {
ToolInvocation,
ToolResult,
} from '../tools/tools.js';
import {
GeminiCliOperation,
GEN_AI_AGENT_DESCRIPTION,
GEN_AI_AGENT_NAME,
} from '../telemetry/constants.js';
import type { ToolRegistry } from 'src/tools/tool-registry.js';
vi.mock('./subagent-tool-wrapper.js');
// Mock runInDevTraceSpan
const runInDevTraceSpan = vi.hoisted(() =>
vi.fn(async (opts, fn) => {
const metadata = { attributes: opts.attributes || {} };
return fn({
metadata,
endSpan: vi.fn(),
});
}),
);
vi.mock('../telemetry/trace.js', () => ({
runInDevTraceSpan,
}));
const MockSubagentToolWrapper = vi.mocked(SubagentToolWrapper);
const testDefinition: LocalAgentDefinition = {
@@ -155,6 +175,25 @@ describe('SubAgentInvocation', () => {
abortSignal,
updateOutput,
);
expect(runInDevTraceSpan).toHaveBeenCalledWith(
expect.objectContaining({
operation: GeminiCliOperation.AgentCall,
attributes: expect.objectContaining({
[GEN_AI_AGENT_NAME]: testDefinition.name,
[GEN_AI_AGENT_DESCRIPTION]: testDefinition.description,
}),
}),
expect.any(Function),
);
// Verify metadata was set on the span
const spanCallback = vi.mocked(runInDevTraceSpan).mock.calls[0][1];
const mockMetadata = { input: undefined, output: undefined };
const mockSpan = { metadata: mockMetadata, endSpan: vi.fn() };
await spanCallback(mockSpan as Parameters<typeof spanCallback>[0]);
expect(mockMetadata.input).toBe(params);
expect(mockMetadata.output).toBe(mockResult);
});
describe('withUserHints', () => {

View File

@@ -20,6 +20,12 @@ import type { AgentDefinition, AgentInputs } from './types.js';
import { SubagentToolWrapper } from './subagent-tool-wrapper.js';
import { SchemaValidator } from '../utils/schemaValidator.js';
import { formatUserHintsForModel } from '../utils/fastAckHelper.js';
import { runInDevTraceSpan } from '../telemetry/trace.js';
import {
GeminiCliOperation,
GEN_AI_AGENT_DESCRIPTION,
GEN_AI_AGENT_NAME,
} from '../telemetry/constants.js';
export class SubagentTool extends BaseDeclarativeTool<AgentInputs, ToolResult> {
constructor(
@@ -167,7 +173,21 @@ class SubAgentInvocation extends BaseToolInvocation<AgentInputs, ToolResult> {
this.withUserHints(this.params),
);
return invocation.execute(signal, updateOutput);
return runInDevTraceSpan(
{
operation: GeminiCliOperation.AgentCall,
attributes: {
[GEN_AI_AGENT_NAME]: this.definition.name,
[GEN_AI_AGENT_DESCRIPTION]: this.definition.description,
},
},
async ({ metadata }) => {
metadata.input = this.params;
const result = await invocation.execute(signal, updateOutput);
metadata.output = result;
return result;
},
);
}
private withUserHints(agentArgs: AgentInputs): AgentInputs {