Building Stateful Agent with Apache Pinot and Google Gen AI
In the era of Large Language Models (LLMs), building agents that can reason over data is a powerful paradigm. However, for an agent to be…
Building Stateful Agent with Apache Pinot and Google Gen AI
In the era of Large Language Models (LLMs), building agents that can reason over data is a powerful paradigm. However, for an agent to be truly useful in a business context, it needs two critical capabilities: access to real-time analytical data and stateful memory of past interactions.
In this post, we explore how to build a Transaction Analyzer Agent that combines the low-latency analytical power of Apache Pinot with the reasoning capabilities of Google’s Gemini models.
The Goal
We will create an agent that can:
- Analyze Transactions: Answer questions like “What are the last 10 transactions for the user X?” or “What is the average price of these orders?” by querying a Pinot database directly.
- Maintain Context: Remember previous user queries and its own answers (e.g., “Can you provide more details about the retrieved transaction?”) to provide a natural, conversational experience.

The Stack
- Apache Pinot: Used as the storage engine for both the high-volume transaction data and the agent’s conversation memory.
- Google Gen AI: The brain of the agent, responsible for natural language understanding and tool orchestration.
- Python: The glue code that connects the LLM with Pinot using the
pinotdbclient.
By the end of this post, you’ll see how we architected this solution, set up the Pinot infrastructure, and implemented the agent’s memory persistence logic.
Architecture Overview
The system architecture is designed to be simple yet robust, separating the storage concerns from the reasoning logic.
Data Layer (Apache Pinot)
Apache Pinot serves as the backbone for our data needs. It hosts two distinct tables:
**transaction**: A table containing the raw transaction records. This table is optimized for analytical queries, allowing the agent to perform aggregations (like calculating average price) or precise lookups in milliseconds.**memory**: A dedicated table for storing the conversation history. Each turn of the conversation (user inputs and agent responses) is ingested into this table, ordered by timestamp.
Application Layer
The agent logic is implemented in Python, leveraging the Google Gen AI SDK. It acts as the orchestrator:
- It is configured with a custom tool (query_pinot) that gives it permission to execute SQL queries against the Pinot Broker.
- It manages the conversation loop, ensuring that every interaction is recorded.
Interaction Flow
When a user interacts with the agent, the following sequence occurs:
- User Query: The user sends a message (e.g., “Show me the last 5 orders”).
- Context Retrieval: Before processing, the agent queries the memory table in Pinot to fetch the last 20 messages. This ensures the agent knows the current context.
- Analysis & Tool Use: The Gemini model analyzes the user query + context. If it needs data, it calls the query_pinot tool to fetch transaction details from the
transactiontable. - Memory Persistence: The agent’s final response is generated. Crucially, both the user’s query and the agent’s response are immediately saved back to the memory table in Pinot.
- Response: The final answer is delivered to the user.

Interaction Flow
This loop ensures that every subsequent query builds upon the history of the entire session.
Github repository: https://github.com/shrutimantri/stateful-agents-with-pinot
Pinot Infrastructure
A key part of making an agent intelligent is the infrastructure it relies on. For this project, we used Apache Pinot — a distributed OLAP data store — to handle both analytical data and conversational state.
Setting Up Pinot
At its core, a Pinot cluster consists of:
- Controller: Manages the cluster state and resource configuration.
- Broker: Receives queries from clients and scatters them across servers.
- Server: Hosts the data segments and executes queries.
In our setup, we used a local instance of Pinot 1.4.0. The initialization script ( init.py) ensures that the tables are clean and correctly configured before the agent starts.
Once the Pinot installation is complete, run the following commands that creates the vurtual environment, sets up env variables, and runs the init.py script:
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
export PINOT_URL=http://localhost:9000
python init.py
Transaction Persistence
The transaction table stores historical e-commerce data. To make this data queryable by the agent, we defined a schema that treats the transaction time as a LONG epoch timestamp.
- Schema: Focuses on dimensions like
userId,itemDescription, andcountry, with atscolumn for time. - Table Configuration: An
OFFLINEtable type designed for high-performance batch analytics.
Memory Persistence
The memory table is what makes the agent “stateful”. The memory data grows with every user interaction.
Schema:
session_id: To uniquely identify different user conversations.role: Eitheruserormodelto distinguish who said what.content: The actual text of the message.ts: The exact time of the interaction.
Table Configuration: Also an OFFLINE table, we implemented a custom ingestion path to ensure new conversation turns are added to Pinot in near real-time.
By defining these two tables, we’ve given our agent a complete view of both the “World” (Transactions) and the “Conversation” (Memory).
Implementation Details
With our Pinot infrastructure in place, let’s dive into how we actually populated the tables and built the agent intelligence.
Data Ingestion and Date Parsing
One of the most common challenges in analytical data stores is handling diverse date formats. For our transaction data, we used a custom data generation script (datagen.py) that performs the following:
- CSV Processing: Reads raw transaction data.
- Date Normalization: Parses human-readable date strings (e.g.,
12/01/2010 08:26) and converts them into milliseconds since epoch. This is crucial for Pinot'sdateTimeFieldSpecs, allowing for efficient time-based filtering. - Segment Creation: Uses Pinot’s ingestion tools to convert this clean data into
segmentsand push them to the controller.
Run the data generator using the following command:
python datagen/datagen.py
The Agent Brain (Gemini + Tool Use)
The core of our agent is built on Gemini 2.0 Flash. What makes it an “agent” rather than just a chatbot is its ability to use tools. We defined a Python function query_pinot() and exposed it to the model.
When the agent receives a request like “What are the last 5 orders for user X?”, the following happens:
- Reasoning: The model recognizes it needs specific data and decides to call
query_pinot. - Execution: It generates a precise SQL query (e.g.,
SELECT * FROM transaction WHERE userId = 'X' ORDER BY ts DESC LIMIT 5). - Integration: The resulting data is returned to the model, which then summarizes it into a natural language response.
Managing Conversation State
To stay “in context,” the agent must remember what happened earlier in the conversation. We implemented this using a dual-write and single-read strategy:
- Read (Context Retrieval): At the start of every message, the agent fetches the last 20 messages from the Pinot memory table based on the
session_id. - Dual-Write (Persistence):
- Local Fallback: For immediate consistency, we write the latest messages to a local JSON file.
- Pinot Ingestion: Every response is also pushed to the Pinot memory table using the Controller’s
/ingestFromFileREST endpoint. This ensures that even if our local script restarts, the conversation history is safely archived in our data store.
Demo & Results
To verify our stateful agent, we ran a series of conversational tests.
Query 1: Data Retrieval
The first step was to see if the agent could correctly use its tools to fetch data.
- User Question: “List the last 10 transactions for the userId 324429”
- Agent Logic: The agent queried the
transactiontable in Pinot, sorting byts DESC. - Result: It successfully listed the transaction IDs, items purchased, and timestamps for that user.
Query 2: Contextual Recall (The “Memory” Test)
This is where the stateful memory really shines. Instead of asking for data by ID again, we asked a follow-up question.
- User Question: “Can you just tell me the details of the second last order?”
- Agent Logic: The agent didn’t need to re-query the transaction table for all data. It looked into its conversation history (retrieved from the memory table), identified the orders listed in the previous turn, and accurately picked the “second last” one.
- Result: “The second last order has transactionId 6336605… and was made in the United Kingdom.”
Playing the demo
In order to play the demo yourself, run the following commands:
export GOOGLE_API_KEY=<YOUR_API_KEY>
export PINOT_HOST=localhost
export PINOT_BROKER_PORT=8000
export PINOT_URL=http://localhost:9000
python analyzer/agent.py
You can generate the Google API key from the AI Studio API Key’s page.
Conclusion
Building stateful agents requires more than just a smart LLM; it requires a robust data infrastructure. By using Apache Pinot as both an analytical engine and a conversational memory store, we achieved:
- Low-Latency Analytics: Fast access to historical transaction data.
- State Management: A persistent conversational state that survives application restarts.
- Scalability: A platform capable of handling millions of transactions and thousands of concurrent agent sessions.
Future Work
While this demo uses Offline tables for simplicity, a production-grade implementation would leverage:
- Real-time Tables: To ingest memory via Kafka for lower latency.
- Upsert Feature: To allow updating transactions if needed.
- StarTree Cloud: For a managed experience in scaling Pinot clusters.
Hope this post gave you good insights into building stateful, data-driven agents!
메타데이터
- post_id
- 4a50f72cc660
- slug
- building-stateful-agent-with-apache-pinot-and-google-gen-ai-4a50f72cc660
- url
- https://medium.com/@shruti1810/building-stateful-agent-with-apache-pinot-and-google-gen-ai-4a50f72cc660
- canonical_url
- https://medium.com/@shruti1810/building-stateful-agent-with-apache-pinot-and-google-gen-ai-4a50f72cc660
- author_url
- https://medium.com/@shruti1810
- status
- ok
- fetched_at
- 2026-06-21 19:25:17