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
17 changes: 10 additions & 7 deletions src/google/adk/a2a/converters/event_converter.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
from typing import Optional
import uuid

from google.adk.platform import time as platform_time
from google.adk.platform import uuid as platform_uuid

from a2a.server.events import Event as A2AEvent
from a2a.types import DataPart
from a2a.types import Message
Expand Down Expand Up @@ -254,7 +257,7 @@ def convert_a2a_task_to_event(
invocation_id=(
invocation_context.invocation_id
if invocation_context
else str(uuid.uuid4())
else platform_uuid.new_uuid()
),
author=author or "a2a agent",
branch=invocation_context.branch if invocation_context else None,
Expand Down Expand Up @@ -299,7 +302,7 @@ def convert_a2a_message_to_event(
invocation_id=(
invocation_context.invocation_id
if invocation_context
else str(uuid.uuid4())
else platform_uuid.new_uuid()
),
author=author or "a2a agent",
branch=invocation_context.branch if invocation_context else None,
Expand Down Expand Up @@ -349,7 +352,7 @@ def convert_a2a_message_to_event(
invocation_id=(
invocation_context.invocation_id
if invocation_context
else str(uuid.uuid4())
else platform_uuid.new_uuid()
),
author=author or "a2a agent",
branch=invocation_context.branch if invocation_context else None,
Expand Down Expand Up @@ -408,7 +411,7 @@ def convert_event_to_a2a_message(

if output_parts:
return Message(
message_id=str(uuid.uuid4()), role=role, parts=output_parts
message_id=platform_uuid.new_uuid(), role=role, parts=output_parts
)

except Exception as e:
Expand Down Expand Up @@ -449,7 +452,7 @@ def _create_error_status_event(
status=TaskStatus(
state=TaskState.failed,
message=Message(
message_id=str(uuid.uuid4()),
message_id=platform_uuid.new_uuid(),
role=Role.agent,
parts=[TextPart(text=error_message)],
metadata={
Expand All @@ -458,7 +461,7 @@ def _create_error_status_event(
if event.error_code
else {},
),
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
),
final=False,
)
Expand Down Expand Up @@ -486,7 +489,7 @@ def _create_status_update_event(
status = TaskStatus(
state=TaskState.working,
message=message,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
)

if any(
Expand Down
17 changes: 10 additions & 7 deletions src/google/adk/a2a/executor/a2a_agent_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
from typing import Optional
import uuid

from google.adk.platform import time as platform_time
from google.adk.platform import uuid as platform_uuid

from a2a.server.agent_execution import AgentExecutor
from a2a.server.agent_execution.context import RequestContext
from a2a.server.events.event_queue import EventQueue
Expand Down Expand Up @@ -143,7 +146,7 @@ async def execute(
status=TaskStatus(
state=TaskState.submitted,
message=context.message,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
),
context_id=context.context_id,
final=False,
Expand All @@ -162,9 +165,9 @@ async def execute(
task_id=context.task_id,
status=TaskStatus(
state=TaskState.failed,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
message=Message(
message_id=str(uuid.uuid4()),
message_id=platform_uuid.new_uuid(),
role=Role.agent,
parts=[TextPart(text=str(e))],
),
Expand Down Expand Up @@ -208,7 +211,7 @@ async def _handle_request(
task_id=context.task_id,
status=TaskStatus(
state=TaskState.working,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
),
context_id=context.context_id,
final=False,
Expand Down Expand Up @@ -247,7 +250,7 @@ async def _handle_request(
last_chunk=True,
context_id=context.context_id,
artifact=Artifact(
artifact_id=str(uuid.uuid4()),
artifact_id=platform_uuid.new_uuid(),
parts=task_result_aggregator.task_status_message.parts,
),
)
Expand All @@ -258,7 +261,7 @@ async def _handle_request(
task_id=context.task_id,
status=TaskStatus(
state=TaskState.completed,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
),
context_id=context.context_id,
final=True,
Expand All @@ -270,7 +273,7 @@ async def _handle_request(
task_id=context.task_id,
status=TaskStatus(
state=task_result_aggregator.task_state,
timestamp=datetime.now(timezone.utc).isoformat(),
timestamp=datetime.fromtimestamp(platform_time.get_time(), tz=timezone.utc).isoformat(),
message=task_result_aggregator.task_status_message,
),
context_id=context.context_id,
Expand Down
5 changes: 3 additions & 2 deletions src/google/adk/agents/invocation_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

from typing import Any
from typing import Optional
import uuid

from google.adk.platform import uuid as platform_uuid

from google.genai import types
from pydantic import BaseModel
Expand Down Expand Up @@ -409,4 +410,4 @@ def _find_matching_function_call(


def new_invocation_context_id() -> str:
return "e-" + str(uuid.uuid4())
return "e-" + platform_uuid.new_uuid()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are multiple uuid.uuid4() and time.time() call sites throughout the SDK that are not changed in this PR. Is that intentional?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the intention is to only change the things we need to limit the PR. While we could swap out all instances, we don't need to. Now the real question is, did I get all the ones we need? I am not positive but this batch is a good start for the most common paths I see.

open to taking more of a blanket approach and change more than we need, open to thoughts...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having two patterns side-by-side is a maintenance trap. A new contributor won't know which to use, and nobody will want to audit whether their code is "on the workflow path" before picking one.

A single rule is much easier to follow than "use them only if your code might run in a Temporal workflow." The cost of converting the remaining sites is low and it eliminates the ambiguity entirely.

Honestly it would be best to

  1. Convert all remaining call sites in this PR
  2. Add to create a linting rule to prevent drift (this can be a follow up)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't disagree, will do a further audit of other locations and look into a linting rule 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be resolved, suggestion on the mechanism for lint rule?

4 changes: 3 additions & 1 deletion src/google/adk/agents/remote_a2a_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
from urllib.parse import urlparse
import uuid

from google.adk.platform import uuid as platform_uuid

from a2a.client import Client as A2AClient
from a2a.client import ClientEvent as A2AClientEvent
from a2a.client.card_resolver import A2ACardResolver
Expand Down Expand Up @@ -549,7 +551,7 @@ async def _run_async_impl(
return

a2a_request = A2AMessage(
message_id=str(uuid.uuid4()),
message_id=platform_uuid.new_uuid(),
parts=message_parts,
role="user",
context_id=context_id,
Expand Down
4 changes: 3 additions & 1 deletion src/google/adk/artifacts/base_artifact_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
from typing import Any
from typing import Optional

from google.adk.platform import time as platform_time

from google.genai import types
from pydantic import alias_generators
from pydantic import BaseModel
Expand Down Expand Up @@ -47,7 +49,7 @@ class ArtifactVersion(BaseModel):
description="Optional user-supplied metadata stored with the artifact.",
)
create_time: float = Field(
default_factory=lambda: datetime.now().timestamp(),
default_factory=lambda: platform_time.get_time(),
description=(
"Unix timestamp (seconds) when the version record was created."
),
Expand Down
8 changes: 4 additions & 4 deletions src/google/adk/events/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

from __future__ import annotations

from datetime import datetime
from typing import Optional
import uuid

from google.adk.platform import time as platform_time
from google.adk.platform import uuid as platform_uuid
from google.genai import types
from pydantic import alias_generators
from pydantic import ConfigDict
Expand Down Expand Up @@ -70,7 +70,7 @@ class Event(LlmResponse):
# Do not assign the ID. It will be assigned by the session.
id: str = ''
"""The unique identifier of the event."""
timestamp: float = Field(default_factory=lambda: datetime.now().timestamp())
timestamp: float = Field(default_factory=lambda: platform_time.get_time())
"""The timestamp of the event."""

def model_post_init(self, __context):
Expand Down Expand Up @@ -125,4 +125,4 @@ def has_trailing_code_execution_result(

@staticmethod
def new_id():
return str(uuid.uuid4())
return platform_uuid.new_uuid()
4 changes: 3 additions & 1 deletion src/google/adk/flows/llm_flows/_code_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import copy
import dataclasses
import datetime

from google.adk.platform import time as platform_time
import logging
import os
import re
Expand Down Expand Up @@ -288,7 +290,7 @@ async def _run_post_processor(
if part.inline_data.display_name:
file_name = part.inline_data.display_name
else:
now = datetime.datetime.now().astimezone()
now = datetime.datetime.fromtimestamp(platform_time.get_time()).astimezone()
timestamp = now.strftime('%Y%m%d_%H%M%S')
file_extension = part.inline_data.mime_type.split('/')[-1]
file_name = f'{timestamp}.{file_extension}'
Expand Down
4 changes: 3 additions & 1 deletion src/google/adk/flows/llm_flows/audio_cache_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import logging
import time

from google.adk.platform import time as platform_time
from typing import TYPE_CHECKING

from google.genai import types
Expand Down Expand Up @@ -70,7 +72,7 @@ def cache_audio(
raise ValueError("cache_type must be either 'input' or 'output'")

audio_entry = RealtimeCacheEntry(
role=role, data=audio_blob, timestamp=time.time()
role=role, data=audio_blob, timestamp=platform_time.get_time()
)
cache.append(audio_entry)

Expand Down
4 changes: 3 additions & 1 deletion src/google/adk/flows/llm_flows/base_llm_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
from abc import ABC
import asyncio
import datetime

from google.adk.platform import time as platform_time
import inspect
import logging
from typing import AsyncGenerator
Expand Down Expand Up @@ -839,7 +841,7 @@ async def _run_one_step_async(
async for event in agen:
# Update the mutable event id to avoid conflict
model_response_event.id = Event.new_id()
model_response_event.timestamp = datetime.datetime.now().timestamp()
model_response_event.timestamp = platform_time.get_time()
yield event

async def _preprocess_async(
Expand Down
4 changes: 2 additions & 2 deletions src/google/adk/flows/llm_flows/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
from typing import Dict
from typing import Optional
from typing import TYPE_CHECKING
import uuid

from google.adk.platform import uuid as platform_uuid
from google.genai import types

from ...agents.active_streaming_tool import ActiveStreamingTool
Expand Down Expand Up @@ -175,7 +175,7 @@ def run_async_tool_in_new_loop():


def generate_client_function_call_id() -> str:
return f'{AF_FUNCTION_CALL_ID_PREFIX}{uuid.uuid4()}'
return f'{AF_FUNCTION_CALL_ID_PREFIX}{platform_uuid.new_uuid()}'


def populate_client_function_call_id(model_response_event: Event) -> None:
Expand Down
4 changes: 3 additions & 1 deletion src/google/adk/flows/llm_flows/transcription_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import logging
import time

from google.adk.platform import time as platform_time
from typing import TYPE_CHECKING

from google.genai import types
Expand Down Expand Up @@ -89,7 +91,7 @@ async def _create_and_save_transcription_event(
author=author,
input_transcription=transcription if is_input else None,
output_transcription=transcription if not is_input else None,
timestamp=time.time(),
timestamp=platform_time.get_time(),
)

# Save transcription event to session
Expand Down
44 changes: 44 additions & 0 deletions src/google/adk/platform/time.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Platform module for abstracting system time generation."""

from contextvars import ContextVar
import time
from typing import Callable

_default_time_provider: Callable[[], float] = time.time
_time_provider_context_var: ContextVar[Callable[[], float]] = (
ContextVar("time_provider", default=_default_time_provider)
)


def set_time_provider(provider: Callable[[], float]) -> None:
"""Sets the provider for the current time.

Args:
provider: A callable that returns the current time in seconds since the
epoch.
"""
_time_provider_context_var.set(provider)


def reset_time_provider() -> None:
"""Resets the time provider to its default implementation."""
_time_provider_context_var.set(_default_time_provider)


def get_time() -> float:
"""Returns the current time in seconds since the epoch."""
return _time_provider_context_var.get()()
43 changes: 43 additions & 0 deletions src/google/adk/platform/uuid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Platform module for abstracting unique ID generation."""

from contextvars import ContextVar
import uuid
from typing import Callable

_default_id_provider: Callable[[], str] = lambda: str(uuid.uuid4())
_id_provider_context_var: ContextVar[Callable[[], str]] = (
ContextVar("id_provider", default=_default_id_provider)
)


def set_id_provider(provider: Callable[[], str]) -> None:
"""Sets the provider for generating unique IDs.

Args:
provider: A callable that returns a unique ID string.
"""
_id_provider_context_var.set(provider)


def reset_id_provider() -> None:
"""Resets the ID provider to its default implementation."""
_id_provider_context_var.set(_default_id_provider)


def new_uuid() -> str:
"""Returns a new unique ID."""
return _id_provider_context_var.get()()
Loading