Skip to content

Id generator

ID Generation in PyAgenity

ID generators produce stable, traceable identifiers for runs, messages, tool calls, and background tasks. PyAgenity ships multiple strategies and lets you inject or override them to match infrastructure needs (UUIDs, integers, sortable timestamps, short IDs, async factories, etc.).

Why It Matters

  • Correlate logs, events, and persisted state across services
  • Generate sortable or compact IDs for databases and analytics
  • Produce deterministic or mock values during tests
  • Support multi-tenant naming or custom sharding schemes

Built-in Generators

Class ID Type Output Shape Typical Use
UUIDGenerator string 36-char UUID4 General purpose globally unique IDs
ShortIDGenerator string 8-char base62 Human-friendly references, URLs
HexIDGenerator string 32 hex chars Compact cryptographic-looking IDs
IntIDGenerator integer 32-bit random int Lightweight numeric handles (careful with collisions at scale)
BigIntIDGenerator bigint ~19–20 digit time-based Time-sortable inserts and range queries
TimestampIDGenerator integer ~16–17 digit microsecond Ordered events, temporal indexing
AsyncIDGenerator string UUID4 (async) Async pipelines needing awaitable generation
DefaultIDGenerator string (empty) "" sentinel Lets framework fall back to default UUID strategy

All implement BaseIDGenerator:

class BaseIDGenerator(ABC):
    @property
    @abstractmethod
    def id_type(self) -> IDType: ...

    @abstractmethod
    def generate(self) -> str | int | Awaitable[str | int]: ...

IDType enum: STRING, INTEGER, BIGINT.

How the Framework Uses Generators

During StateGraph.compile(), an ID generator is available in the DI container (BaseIDGenerator) and a concrete generated value also registers as generated_id plus metadata generated_id_type. These are consumed by publishers, checkpointers, and execution handlers to stamp events and state snapshots.

If the active generator returns an empty string (the DefaultIDGenerator case), the runtime substitutes a UUID4 automatically—so you always get a usable identifier.

Injecting the Generator

from injectq import Inject
from pyagenity.utils.id_generator import BigIntIDGenerator, BaseIDGenerator

async def node(state, config, id_gen: BaseIDGenerator = Inject[BaseIDGenerator]):
    run_local_id = id_gen.generate()
    print("Run ID: ", run_local_id)
    return state

To supply a custom generator:

from injectq import InjectQ
from pyagenity.graph import StateGraph
from pyagenity.utils.id_generator import BaseIDGenerator, IDType

class PrefixedUUIDGenerator(BaseIDGenerator):
    @property
    def id_type(self):
        return IDType.STRING

    def generate(self) -> str:
        import uuid
        return f"agent-{uuid.uuid4()}"

container = InjectQ.get_instance()
container.bind(BaseIDGenerator, PrefixedUUIDGenerator())

graph = StateGraph(container=container)

Async Generation

If generate() returns an awaitable (e.g. AsyncIDGenerator), the runtime awaits it transparently when producing generated_id—your nodes/tools still see a resolved value.

Selecting an ID Shape

Requirement Recommended Generator Rationale
Strict global uniqueness UUIDGenerator Standard, collision-resistant
Ordered inserts (time-series) BigIntIDGenerator or TimestampIDGenerator Monotonic-ish ordering simplifies pruning/range scans
Readable short handles ShortIDGenerator Compact for logs and URLs
Deterministic prefixing Custom (e.g. PrefixedUUIDGenerator) Adds tenant/app metadata
Cryptic fixed-length tokens HexIDGenerator Clean hex aesthetic

Testing Strategies

Provide a fake deterministic generator to stabilise assertions:

class FixedIDGenerator(BaseIDGenerator):
    @property
    def id_type(self):
        return IDType.STRING
    def generate(self):
        return "fixed-id"

container.bind(BaseIDGenerator, FixedIDGenerator())

Pitfalls

  • Avoid IntIDGenerator for very high concurrency without uniqueness safeguards.
  • Don’t put non-serialisable objects in generated IDs (stick to primitives/strings).
  • If you shard by prefix, document the format so downstream analytics can parse it.

Extending

Implement BaseIDGenerator, bind it in the container, and (optionally) expose environment-based toggles (e.g. switch to short IDs in tests, full UUID in production).


See also: Thread Name Generation (thread-level naming) and Response Conversion (message identity mapping).