Skip to main content
Retrieval Augmented Generation (RAG) is one of the keystones for efficient data processing and search in the age of AI agents. The goal is to surface information from a knowledge base relevant to a specific user query and provide curated context to the LLM. This is a complex topic with many variants. We will focus on the fundamental building blocks that any RAG pipeline needs. The document processing pipeline:
  1. Text extraction: Process complex document formats (PDF, CSV, etc.).
  2. Text splitting: Create meaningful chunks out of long pages of text.
  3. Embedding: Vectorize chunks (extract semantic meaning).
  4. Store: Insert chunks to a specialized database.
Retrieval:
  1. Embedding: Vectorize the user query.
  2. Search: Retrieve the document chunks most similar to the user query.

Steps

1

Start Agent Stack with Docling enabled!!

Use agentstack platform start --set docling.enabled=true to start Agent Stack with Docling.
2

Enable file uploads in your agent

Add the default_input_modes parameter to your agent decorator to allow users to upload files to your agent. This also specifies which file types users can upload.
3

Import the Platform API and Embedding Service extensions

Import PlatformApiExtensionServer, PlatformApiExtensionSpec, EmbeddingServiceExtensionServer and EmbeddingServiceExtensionSpec from agentstack_sdk.a2a.extensions.
4

Inject the extensions

Add parameters to your agent function using the Annotated type hints.
5

Implement document processing functions

Implement functions to handle text extraction, text splitting, embedding generation, and vector storing.
6

Implement query functions

Implement functions to generate embeddings for the user query and search the vector store for similar document chunks.
7

Put it all together

Put it all together in an agent that 1) processes uploaded documents and 2) answers questions about them.
8

Use an LLM to help form a response

The examples here skip this part and return details from the document. In practice, you’ll want to use an LLM to help form a response about the selected document chunks instead of returning the actual chunk text (i.e., implement an assistant and not just a search tool).
Let’s break down how each step can be implemented with the Agent Stack API.

Building blocks

Enable File Uploads

Add the default_input_modes parameter to your agent decorator to allow users to upload files to your agent. This also specifies which file types users can upload. Agent Stack uses docling for extracting text out of documents in various supported formats.

Platform and Embedding Extensions

Make sure you have the Platform API and Embedding Service extensions imported and injected in your agent parameters:
Next, let’s build a set of functions to process the documents which we will then use in the agent.

Text Extraction

To extract text from a File uploaded to the Platform API, simply use file.create_extraction() and wait for the result. After extraction is completed, the extraction object will contain extracted_files, which is a list of extracted files in different formats.

Extraction Formats

Text extraction produces two extraction formats and you can request either subset by passing formats to create_extraction (e.g., ["markdown"] if you only need plain text):
  • markdown: The extracted text formatted as Markdown (file.load_text_content())
  • vendor_specific_json: The Docling-specific JSON format containing document structure (file.load_json_content())
WARNING: The vendor_specific_json format is not generated for plain text or markdown files, as Docling does not support these formats as input.

Text Splitting

In this example we will use MarkdownTextSplitter from the langchain-text-splitters package. This will split a long document into reasonably sized chunks based on the Markdown header structure.

Embedding

Now we need to generate embeddings for each chunk using the embedding service. Similarly to LLM, Agent Stack implements OpenAI-compatible embedding API. You can use any preferred client, in this example we will use the embedding extension to create an AsyncOpenAI client:
Now we can use this client to generate embeddings for our chunks and create vector store items:

Store

Finally, to insert the prepared items, we need a function to create a vector store. For this we will need to know the dimension of the embeddings and model_id. Because the model is chosen by the embedding extension and we don’t know it in advance, we will create a test embedding request to calculate the dimension:
We can then add the prepared items using vector_store.add_documents, this will become clear in the final example.

Query vector store

Assuming we have our knowledge base of documents prepared, we can now easily search the store according to the user query. The following function will retrieve five document chunks most similar to the query embedding:

Putting all together

Having all the pieces in place, we can now build the agent.

Simple agent

This is a simplified agent that expects a message with one or more files attached as FilePart and a user query as TextPart. A new vector store is created for each message.
Instead of simply returning the output of the vector store, you would typically plug this as a tool into your favorite agentic framework.

Conversational agent

Having a new vector store for each message is not really a good practice. Typically, you would want to search through all documents uploaded in the conversation. Below is a version of the agent which will reuse the vector store across messages so you can ask multiple queries and or additional documents later on.

Next steps

To further improve the agent, learn how to use other parts of the platform such as LLMs, file uploads and conversations: