Open Net Worth
Open Net Worth is an open-source personal AI wealth assistant you run yourself: one ledger for your whole financial life, worked by an AI agent you control.
Pregunta sobre tus boletas en lenguaje natural y recibe respuestas reales — sin dashboard, sin filtros que aprender.
Translation pending
FinanceTypeScript · JavaScriptGitHub @sebpost2OnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Conversational agent with real tool use over receipt data — the LLM picks the right SQL tool (list, aggregate, or detail) and answers in natural language.
Loading the full README.
Read the full README ↗Conversational agent that answers in natural language about Peruvian receipts and invoices. Showcases real tool use: the LLM decides which SQL tool to call (list_receipts, query_aggregates, get_receipt_detail), executes it against Neon Postgres, and responds with verifiable data. Reuses the same database as Invoice Extractor — extract first, then analyze.
Author: sebpost2
Live demo · No sign-up · 27 demo receipts preloaded
list_receipts({orderBy: "total_desc"}) → grabs the first id → get_receipt_detail({id}) → writes the answer.z.enum, z.string), the AI SDK validates inputs before execution, and TypeScript infers the types on the server.streamText + useChat).<details> component showing the exact input and output JSON. Visitors see what the model queried, not just what it said.get_receipt_detail, the receipt id is shown. If it comes from a list/aggregate, the scope is shown ("Based on N receipts"). No fabricated references.react-markdown + GFM.npm run seed (Tottus, Pardos, Sodimac, Inkafarma, etc.) plus the 3 real ones from the extractor — 27 receipts total for substantial questions.evals/ lock down the agent's behavior — single-step routing (ES + EN), multi-step list → detail chains, and no-tool refusal for greetings/off-topic. Run with npm run eval. A pre-eval guard verifies the duplicated prompt copy hasn't drifted from src/lib/chat-prompts.ts.| Layer | Technology |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Language | TypeScript |
| Styling | Tailwind CSS v4 (dark-first) |
| LLM SDK | Vercel AI SDK v6 (ai, @ai-sdk/groq, @ai-sdk/react) |
| Model | OpenAI gpt-oss-120b via Groq |
| Database | PostgreSQL (Neon, serverless) — shared with the extractor |
| ORM | Prisma 7 with @prisma/adapter-pg |
| Validation | Zod 4 |
| Markdown | react-markdown + remark-gfm |
| Rate limit | In-memory token bucket |
| LLM evals | Promptfoo (OSS) — 22 tool-selection cases under evals/ |
| Deploy | Vercel |
┌──────────┐ sendMessage ┌────────────────┐ streamText ┌──────┐
│ Client │ ─────────────> │ /api/chat │ ───────────────> │ Groq │
│ useChat │ │ (route handler)│ + tools (Zod) │ LLM │
└────┬─────┘ └────────┬───────┘ └───┬──┘
│ │ │
│ UIMessageStream │ │
│ <─────────────────────────────┤ ┌────────┐ tool call │
│ • text-delta │ │ Neon │ <────────────────┤
│ • tool-input-streaming │ │ DB │ │
│ • tool-output-available │ └────────┘ tool result ────┤
│ • text-end │ │
│ │ ... until stepCountIs(5) │
useChat({ transport: DefaultChatTransport }).streamText with the 3 tools and the Groq model.execute (Prisma query) and feeds the result back to the model.stopWhen: stepCountIs(5)) or write the final answer.| Tool | Input | Output | When the model picks it |
|---|---|---|---|
list_receipts | orderBy: "date_desc" | "date_asc" | "total_desc" | "total_asc" | Array of receipts with summary fields | "Show me my receipts", "which was the most expensive?" |
query_aggregates | metric: "total_spent" | "total_igv" | "total_subtotal" | "count" | "all" | Per-currency aggregates | "How much did I spend?", "How many receipts?", "How much VAT?" |
get_receipt_detail | id: string | Full receipt with items | "Give me the details of [id]", final step after identifying a receipt via a list |
This is the beginning of the README. Read the rest at the original. Read the full README ↗