Async Knowledge Retrieval

Async-safe RAG for agents running under arun or a FastAPI handler

Async-safe RAG for agents running under arun or a FastAPI handler — knowledge search never blocks the event loop.

Diagram

Async Knowledge Retrieval

Yes

No

📋 Request

🔄 Orchestrator

⚡ Async store?

await store.search

asyncio.to_thread

✅ Documents

Quick Start

  1. Agent under arun

    An agent that searches knowledge inside arun() no longer blocks the event loop — concurrent requests stay responsive.

    python
    import asyncio
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Research Assistant",
        instructions="Answer questions using the knowledge base.",
        knowledge=["docs/handbook.pdf"],
    )
    
    async def main():
        # Two agents run concurrently; each RAG lookup is offloaded,
        # so the loop stays free instead of serialising the round trips.
        await asyncio.gather(
            agent.arun("Summarise the onboarding policy"),
            agent.arun("What is the refund window?"),
        )
    
    asyncio.run(main())
  2. Orchestrator (library authors)

    Call aretrieve_knowledge / aadd_knowledge directly when you build on the persistence layer.

    python
    import asyncio
    from praisonai.persistence import PersistenceOrchestrator
    from praisonai.persistence.knowledge.base import KnowledgeDocument
    
    orchestrator = PersistenceOrchestrator.from_env()
    
    async def main():
        # Add documents (embeddings already computed)
        await orchestrator.aadd_knowledge(
            documents=[
                KnowledgeDocument(
                    id="doc-1",
                    content="Refunds are processed within 30 days.",
                    embedding=[0.12, 0.85, 0.03],
                )
            ],
            collection="policies",
        )
    
        # Retrieve without blocking the loop
        docs = await orchestrator.aretrieve_knowledge(
            query_embedding=[0.10, 0.80, 0.05],
            collection="policies",
            limit=3,
        )
        for doc in docs:
            print(doc.content)
    
    asyncio.run(main())
  3. Streaming + knowledge

    Stream tokens from an agent that also retrieves knowledge — the retrieved context is normalized the same way as the sync path.

    python
    from praisonaiagents import Agent
    
    agent = Agent(
        name="Research Assistant",
        instructions="Answer using the knowledge base.",
        knowledge=["docs/handbook.pdf"],
    )
    
    for chunk in agent.start_stream("Summarise the onboarding policy"):
        print(chunk, end="", flush=True)

How It Works

aretrieve_knowledge and aadd_knowledge inspect the underlying store and pick the right path automatically.

Diagram
StoreOrchestratorAgentStoreOrchestratorAgentalt[store.search is a coroutine][sync store]aretrieve_knowledge(embedding)await store.search(…)asyncio.to_thread(store.search, …)documentsdocuments
Store typePath taken
Native async (search / upsert is a coroutine)Awaited directly
Sync storeOffloaded via asyncio.to_thread(...) so the loop stays free

Every real vector backend issues a network round trip on search. From an async agent, the sync retrieve_knowledge would block the loop for the whole round trip and serialise every other concurrent request. The a* methods align RAG with the existing async conversation hooks (aon_message, aon_agent_start).


When to Use a* vs Sync

Diagram

Yes

No

Inside arun or a
FastAPI handler?

aretrieve_knowledge
aadd_knowledge

retrieve_knowledge
add_knowledge

Use the a* variants whenever an event loop is running. In a plain sync script, the existing retrieve_knowledge / add_knowledge methods are the right choice.


Configuration Options

aretrieve_knowledge(query_embedding, collection="default", limit=5, filters=None)

OptionTypeDefaultDescription
query_embeddingList[float](required)Query vector to search against
collectionstr"default"Collection to search
limitint5Max documents to return
filtersOptional[Dict[str, Any]]NoneMetadata filters

aadd_knowledge(documents, collection="default")

OptionTypeDefaultDescription
documentsList[KnowledgeDocument](required)Documents with embeddings to upsert
collectionstr"default"Collection to write to

Best Practices

Use a* variants inside arun() or a web handler

Any code path that already runs on an event loop — agent.arun(...), a FastAPI route, a background async task — should call aretrieve_knowledge / aadd_knowledge. The sync methods would block the loop for the full network round trip.

Sync stores are still safe

A sync knowledge store plugged into an async agent is not rejected — it is offloaded to a thread via asyncio.to_thread(...). You do not need a native async backend to benefit.

Concurrency is real — verify it

Two concurrent RAG calls against a store with a 0.3 s round trip finish in ~0.3 s, not 0.6 s. Fire two aretrieve_knowledge calls with asyncio.gather and confirm they overlap.

Async, streaming, and sync now use the same normalization

Since PR #4887, .chat(), .achat(), .start_stream(), and .astart(..., stream=True) all route knowledge retrieval through _get_knowledge_context(). The SearchResult dataclass, legacy dict, and plain list shapes are handled in one place — no per-path guardrails needed. Switch an agent between sync, async, and streaming freely; knowledge injection behaves identically.


Related