Demo for Google Gemini 3.1 Pro and LlamaCloud.
Clone the repository:
git clone https://github.com/run-llama/receipts-analyzer
cd receipts-analyzerExport the necessary environment variables:
export LLAMA_CLOUD_API_KEY="..."
export GOOGLE_API_KEY="..."
export FRONTEND_URL="http://localhost:3000"Or change the API keys in the configuration file (not recommended).
Now you can install the backend as a uv project and start the API server:
cd backend
uv pip install .
webserver-runThis will run the API server on port 8000 locally. Change port and host by specifying the --port and --host options from command line.
Create a .env file for the frontend where to store the VITE_BACKEND_URL:
cd frontend
echo "VITE_BACKEND_URL=http://localhost:8000" >> .envYou can then install the dependencies:
bun install # you can also use npm, pnpm or yarnAnd start the frontend application on port 3000 locally:
bun --bun run dev # you can also use npm, pnpm or yarnFirst, you need to upload a certain number of receipts (as PNG pictures) related to a specific month. Once you have enough receipts belonging to a month, you can clear the file and select that month to generate a report.
The report will contain a "General Overview" section, a "Trends" section and a "Suggestions" sections, which will be generated by Google Gemini 3.1 Pro.
You can use the receipts in data as an example.
File upload is managed with a LlamaIndex Agent Workflow, and consists of first uploading the receipt to LlamaCloud and parsing it, and then upload the parsed content and file details to a local SQLite database.
┌─────────────────────────────────────────────────────────────────────────────┐
│ FILE STORAGE WORKFLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌──────────────┐
│ InputEvent │
│ │
│ • file │
│ • month │
└──────┬───────┘
│
▼
┌────────────────────────────────┐
│ upload_file_to_llamacloud() │
│ │
│ • Upload file to LlamaCloud │
│ • Set file_id, path, month │
└────────┬──────────────┬────────┘
│ │
┌────────▼────────┐ │
│ Success │ │ Error
│ FileUploadedEvent│ │
└────────┬────────┘ │
│ │
▼ │
┌────────────────────┐ │
│ parse_file() │ │
│ │ │
│ • Parse document │ │
│ • Extract markdown│ │
└────┬──────────┬────┘ │
│ │ │
┌────────▼────┐ │ │
│ Success │ │ Error │
│FileParsedEvent │ │
└────────┬────┘ │ │
│ │ │
▼ │ │
┌────────────────┐ │ │
│store_file_to_db│ │ │
│ │ │ │
│ • Create table │ │ │
│ • Check exists │ │ │
│ • Insert/Update│ │ │
└────────┬───────┘ │ │
│ │ │
▼ ▼ ▼
┌────────────────────────────┐
│ FileStoredEvent (END) │
│ │
│ • error: str | None │
└────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ STATE TRACKING │
├─────────────────────────────────────────────────────────────────────────────┤
│ WorkflowState: │
│ • month: str ─── Month selected by user │
│ • path: str ─── Original file path/name │
│ • file_id: str ─── LlamaCloud file identifier │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ EVENT FLOW LEGEND │
├─────────────────────────────────────────────────────────────────────────────┤
│ ═══► Normal flow • InputEvent: Starts workflow │
│ ─ ─► Error path • FileUploadedEvent: File ready to parse │
│ • FileParsedEvent: Content extracted │
│ • FileStoredEvent: Terminal state (success/fail)│
└─────────────────────────────────────────────────────────────────────────────┘
Report generation is also achieved through a LlamaIndex Agent Workflow, and loads from the SQLite database the files from a specific month, generates a prompt by concatenating the files and generates an analysis using Gemini 3.1 Pro.
┌─────────────────────────────────────────────────────────────────────────────┐
│ RECEIPTS ANALYSIS WORKFLOW │
└─────────────────────────────────────────────────────────────────────────────┘
┌────────────────────┐
│ StartAnalysisEvent │
│ │
│ • month (literal) │
└──────────┬─────────┘
│
▼
┌─────────────────────────────────┐
│ get_files() │
│ │
│ • Create table if not exists │
│ • Query files by month │
│ • Validate non-empty result │
└─────────┬──────────────┬────────┘
│ │
┌────────▼────────┐ │
│ Success │ │ Error
│ FilesEvent │ │ • No files found
│ • files[] │ │ • DB error
│ • month │ │
└────────┬────────┘ │
│ │
▼ │
┌──────────────────────┐ │
│ create_prompt() │ │
│ │ │
│ • Build header │ │
│ • Concatenate files │ │
│ • Validate content │ │
└─────┬───────────┬────┘ │
│ │ │
┌────────▼─────┐ │ │
│ Success │ │ Error│
│ PromptEvent │ │ • No text content
│ • prompt │ │ │
└────────┬─────┘ │ │
│ │ │
▼ │ │
┌───────────────────┐│ │
│generate_analysis()││ │
│ ││ │
│ • Call LLM ││ │
│ • Structured JSON ││ │
│ • Validate schema ││ │
└────────┬──────────┘│ │
│ │ │
▼ ▼ ▼
┌──────────────────────────────┐
│ OutputEvent (END) │
│ │
│ • output: ReceiptsAnalysis │
│ • error: str | None │
└──────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ DATA FLOW │
├─────────────────────────────────────────────────────────────────────────────┤
│ Month → DB Query → Files → Prompt Building → LLM Analysis → Structured JSON│
│ │
│ FilesEvent carries: │
│ • files: Sequence[File] ─── Retrieved from SQLite │
│ • month: str ─── Selected month for context │
│ │
│ PromptEvent carries: │
│ • prompt: str ─── Formatted receipts content │
│ │
│ OutputEvent carries: │
│ • output: ReceiptsAnalysis ─── Parsed expense analysis │
│ • error: str | None ─── Any failure message │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ LLM CONFIGURATION │
├─────────────────────────────────────────────────────────────────────────────┤
│ Model: Google Gemini (DEFAULT_GOOGLE_MODEL) │
│ Features: │
│ ✓ Thinking mode enabled (THINKING_LEVEL) │
│ ✓ Structured JSON output (ReceiptsAnalysis schema) │
│ ✓ System prompt for expense analysis (SYSTEM_PROMPT) │
└─────────────────────────────────────────────────────────────────────────────┘