latent.agents.retriever¶
@retriever — observability decorator for document retrieval.
Mirrors the @tool decorator pattern: wrap an async function so the
framework can register it as a typed primitive AND emit structured events
around its invocation. The decorator solves the duck-typing gap previously
present in agent-studio's TracedStream — RetrieverStart / RetrieverEnd
events now live in latent.events and the trace pipeline can dispatch
them via isinstance like every other event.
How events reach the stream¶
A decorated retriever IS an async generator: it yields its events
directly. Tool authors who chain retrievers drain them with async for
and forward events into their own (also async-generator) tool body. The
framework's @tool decorator unifies coroutine and async-generator
tools so ReActAgent async fors every tool uniformly — events
forward into the agent's stream and the final non-event yield is the
tool result.
@retriever implies @tool: the decorator stamps _tool_meta so a
bare decorated retriever is passable to ReActAgent(tools=[...])
directly; no separate @tool wrapper is needed for the common case.
No ContextVar, no side-channel — events emit where the decorator's
wrapper runs, directly visible at the call site. This is intentional:
the older ContextVar approach hid the data flow and made testing
awkward; the async-generator pattern is the same shape Python uses
elsewhere for streaming (FastAPI streaming responses, async iterables
in httpx, etc.).
Decorator contract¶
The decorated function must:
- be an
async def(the framework awaits it) - take a positional or keyword argument named
query(the search input) - return an iterable of objects with either a
contentattribute (e.g.RetrievedChunk) or bestr-convertible
A retriever name is taken from the function's __name__ by default;
override with @retriever(name="my-retriever").
Calling pattern inside a tool body¶
The decorated retriever is an async generator. Iterate with async for,
distinguish events from the result by type:
@retriever
async def search_kb(query: str, k: int = 4):
return await chroma.retrieve(query, k=k)
@tool
async def search_knowledge_base(query: str):
chunks = None
async for item in search_kb(query):
if isinstance(item, AgentEvent):
yield item # forward events to the agent stream
else:
chunks = item # last non-event yield = retriever result
yield format_chunks(chunks) # tool's final yield = ToolResult.output
Tools that don't call retrievers stay plain coroutines; the @tool / ReActAgent layer detects async-generator tools and handles either shape.
Calling outside an agent loop¶
Decorated retrievers called from a notebook / script / test still work — iterate the generator and treat the last item as the result:
async for item in search_kb("hi"):
if not isinstance(item, AgentEvent):
chunks = item
Functions¶
retriever¶
Decorator that turns an async retrieval function into an async
generator emitting :class:RetrieverStart / :class:RetrieverEnd
events around the underlying call.
Supports both @retriever and @retriever(name="…") forms.
@retriever implies @tool: the decorated function is also
registered as an LLM-callable tool. Pass it to ReActAgent(tools=...)
directly — no separate @tool wrapper needed for the common case
(retriever whose return value IS the tool output). For the composition
case (one tool fanning out to multiple retrievers + post-processing),
write a separate @tool-decorated async generator that drains the
@retriever-decorated building blocks. Both decorators can stack.
Args:
fn: The async retrieval function to wrap. Implicitly provided
when used as @retriever without parentheses.
name: Override the retriever name in emitted RetrieverStart
/ RetrieverEnd events. Defaults to fn.__name__. Does
NOT affect the tool name exposed to the LLM — that always
comes from fn.__name__ via the tool schema.
Returns:
An async-generator function carrying __is_retriever__ (for
framework introspection) and _tool_meta (so ReActAgent
registers it as a tool).