Skip to content

Depends does not await async dependency callables #229

Description

@rdwj

Describe the bug

Depends.__call__ (in kagenti_adk/server/dependencies.py, around line 48) invokes the dependency callable synchronously and passes its return value directly to the handler. If the callable is an async def, the handler receives an unawaited coroutine object instead of the resolved value.

Steps to Reproduce

from kagenti_adk.server.dependencies import Depends

async def get_my_service():
    service = await SomeAsyncFactory.create()
    return service

@server.agent()
async def my_agent(
    input: Message,
    context: RunContext,
    svc: Annotated[MyService, Depends(get_my_service)],
):
    # svc is a coroutine object, not a MyService instance
    await svc.do_something()  # AttributeError: 'coroutine' object has no attribute 'do_something'

Root Cause

In dependencies.py, the dependency callable is invoked with a plain function call:

result = self._callable(message, context, request_context)

When self._callable is async def, result is a coroutine that needs to be awaited. The existing extension servers avoid this because they're BaseExtensionServer subclasses with a lifespan() method, not plain async callables.

Expected Behavior

Depends.__call__ should detect when the callable returns a coroutine and await it:

result = self._callable(message, context, request_context)
if inspect.isawaitable(result):
    result = await result

Workaround

Use a synchronous callable that returns a lazy-initializing proxy. The proxy defers the async initialization to the first method call:

class _LazyProxy:
    def __init__(self, factory, context_id):
        self._factory = factory
        self._instance = None

    async def _resolve(self):
        if self._instance is None:
            self._instance = await self._factory.create()
        return self._instance

    async def do_something(self, *args, **kwargs):
        inst = await self._resolve()
        return await inst.do_something(*args, **kwargs)

def create_dependency(factory):
    def provider(message, context, request_context):
        return _LazyProxy(factory, context.context_id)  # sync return
    return provider

Additional Context

  • Found while implementing a MemoryStore extension for the ADK that requires async initialization (connecting to an external service)
  • The workaround is functional but adds boilerplate that wouldn't be needed if Depends handled async callables natively
  • ADK version: 0.8.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    Status
    New/ToDo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions