Goal
Allow KI handler functions to declare dependencies (e.g. database connections, HTTP clients) that are automatically injected by the framework at call time — similar to FastAPI's dependency injection system.
Motivation
Currently handlers only receive (binding_set, info). Any external resource (DB connection, config, HTTP client) must be a global or closure. DI makes handlers testable and decoupled.
Design
Use a Depends() marker in Annotated type hints:
from typing import Annotated
from src import Depends
def get_db() -> MyDatabase:
return MyDatabase(url="...")
@kb.answer_ki(name="...", graph_pattern="...")
def handler(
binding_set: list[PersonBinding],
info: KnowledgeInteractionInfo,
db: Annotated[MyDatabase, Depends(get_db)],
) -> list[PersonBinding]:
return db.query(binding_set)
Behaviour
- The framework inspects handler signatures at registration time, identifies
Depends()-annotated parameters, and resolves them at call time.
- Dependency factories are sync-only for now (async support deferred to a later issue).
- Dependency factories can themselves declare
Depends() parameters — nested/transitive resolution is supported.
- Caching is configurable via
Depends(factory, cache=True/False):
cache=True (default): factory is called once per handling-loop call and the result is reused across nested dependencies in the same call.
cache=False: factory is called fresh every time it is needed.
Out of scope
- Async dependency factories (document this as a known limitation, to be addressed in a follow-up issue).
- Request-scoped caching beyond a single KI call.
Goal
Allow KI handler functions to declare dependencies (e.g. database connections, HTTP clients) that are automatically injected by the framework at call time — similar to FastAPI's dependency injection system.
Motivation
Currently handlers only receive
(binding_set, info). Any external resource (DB connection, config, HTTP client) must be a global or closure. DI makes handlers testable and decoupled.Design
Use a
Depends()marker inAnnotatedtype hints:Behaviour
Depends()-annotated parameters, and resolves them at call time.Depends()parameters — nested/transitive resolution is supported.Depends(factory, cache=True/False):cache=True(default): factory is called once per handling-loop call and the result is reused across nested dependencies in the same call.cache=False: factory is called fresh every time it is needed.Out of scope