diff --git a/src/google/adk/agents/base_agent.py b/src/google/adk/agents/base_agent.py index 3d0a14d480..127f55da87 100644 --- a/src/google/adk/agents/base_agent.py +++ b/src/google/adk/agents/base_agent.py @@ -115,6 +115,12 @@ class MyAgent(BaseAgent): Agent name cannot be "user", since it's reserved for end-user's input. """ + version: str = '' + """The agent's version. + + Version of the agent being invoked. Used to identify the Agent involved in telemetry. + """ + description: str = '' """Description about the agent's capability. @@ -678,6 +684,7 @@ def __create_kwargs( kwargs: Dict[str, Any] = { 'name': config.name, + 'version': config.version, 'description': config.description, } if config.sub_agents: diff --git a/src/google/adk/agents/base_agent_config.py b/src/google/adk/agents/base_agent_config.py index 3859cb3550..9dca68c5e6 100644 --- a/src/google/adk/agents/base_agent_config.py +++ b/src/google/adk/agents/base_agent_config.py @@ -55,6 +55,10 @@ class BaseAgentConfig(BaseModel): name: str = Field(description='Required. The name of the agent.') + version: str = Field( + default='', description='Optional. The version of the agent.' + ) + description: str = Field( default='', description='Optional. The description of the agent.' ) diff --git a/src/google/adk/telemetry/tracing.py b/src/google/adk/telemetry/tracing.py index 707bc31396..8b777ea8af 100644 --- a/src/google/adk/telemetry/tracing.py +++ b/src/google/adk/telemetry/tracing.py @@ -82,6 +82,8 @@ USER_CONTENT_ELIDED = '' +GEN_AI_AGENT_VERSION = 'gen_ai.agent.version' + # Needed to avoid circular imports if TYPE_CHECKING: from ..agents.base_agent import BaseAgent @@ -155,6 +157,7 @@ def trace_agent_invocation( span.set_attribute(GEN_AI_AGENT_DESCRIPTION, agent.description) span.set_attribute(GEN_AI_AGENT_NAME, agent.name) + span.set_attribute(GEN_AI_AGENT_VERSION, agent.version) span.set_attribute(GEN_AI_CONVERSATION_ID, ctx.session.id) @@ -455,6 +458,7 @@ def use_generate_content_span( USER_ID: invocation_context.session.user_id, 'gcp.vertex.agent.event_id': model_response_event.id, 'gcp.vertex.agent.invocation_id': invocation_context.invocation_id, + GEN_AI_AGENT_VERSION: invocation_context.agent.version, } if ( _is_gemini_agent(invocation_context.agent) @@ -489,6 +493,7 @@ async def use_inference_span( USER_ID: invocation_context.session.user_id, 'gcp.vertex.agent.event_id': model_response_event.id, 'gcp.vertex.agent.invocation_id': invocation_context.invocation_id, + GEN_AI_AGENT_VERSION: invocation_context.agent.version, } if ( _is_gemini_agent(invocation_context.agent) diff --git a/tests/unittests/telemetry/test_spans.py b/tests/unittests/telemetry/test_spans.py index 3c061e42a3..d372c1a424 100644 --- a/tests/unittests/telemetry/test_spans.py +++ b/tests/unittests/telemetry/test_spans.py @@ -24,6 +24,7 @@ from google.adk.models.llm_response import LlmResponse from google.adk.sessions.in_memory_session_service import InMemorySessionService from google.adk.telemetry.tracing import ADK_CAPTURE_MESSAGE_CONTENT_IN_SPANS +from google.adk.telemetry.tracing import GEN_AI_AGENT_VERSION from google.adk.telemetry.tracing import trace_agent_invocation from google.adk.telemetry.tracing import trace_call_llm from google.adk.telemetry.tracing import trace_inference_result @@ -119,6 +120,32 @@ async def test_trace_agent_invocation(mock_span_fixture): mock.call('gen_ai.operation.name', 'invoke_agent'), mock.call('gen_ai.agent.description', agent.description), mock.call('gen_ai.agent.name', agent.name), + mock.call(GEN_AI_AGENT_VERSION, ''), + mock.call( + 'gen_ai.conversation.id', + invocation_context.session.id, + ), + ] + mock_span_fixture.set_attribute.assert_has_calls( + expected_calls, any_order=True + ) + assert mock_span_fixture.set_attribute.call_count == len(expected_calls) + +@pytest.mark.asyncio +async def test_trace_agent_invocation_with_version(mock_span_fixture): + """Test trace_agent_invocation sets span attributes correctly when version is provided.""" + agent = LlmAgent(name='test_llm_agent', model='gemini-pro') + agent.description = 'Test agent description' + agent.version = '1.0.0' + invocation_context = await _create_invocation_context(agent) + + trace_agent_invocation(mock_span_fixture, agent, invocation_context) + + expected_calls = [ + mock.call('gen_ai.operation.name', 'invoke_agent'), + mock.call('gen_ai.agent.description', agent.description), + mock.call('gen_ai.agent.name', agent.name), + mock.call(GEN_AI_AGENT_VERSION, agent.version), mock.call( 'gen_ai.conversation.id', invocation_context.session.id, @@ -767,6 +794,7 @@ async def test_generate_content_span( mock_span.set_attributes.assert_called_once_with({ GEN_AI_AGENT_NAME: invocation_context.agent.name, + GEN_AI_AGENT_VERSION: '', GEN_AI_CONVERSATION_ID: invocation_context.session.id, USER_ID: invocation_context.session.user_id, 'gcp.vertex.agent.event_id': 'event-123', @@ -1087,6 +1115,7 @@ async def test_generate_content_span_with_experimental_semconv( mock_span.set_attributes.assert_called_once_with({ GEN_AI_AGENT_NAME: invocation_context.agent.name, + GEN_AI_AGENT_VERSION: '', GEN_AI_CONVERSATION_ID: invocation_context.session.id, USER_ID: invocation_context.session.user_id, 'gcp.vertex.agent.event_id': 'event-123',