An LLM Personal Finance Assistant: A Read-Only Integration Architecture
A practical engineering guide to building an AI personal finance assistant on read-only bank data: least-privilege token design, tool whitelisting, account aggregation and normalization, intent-to-query-plan orchestration, deterministic number verification to resist hallucination, privacy and compliance controls, and a staged rollout plan with common pitfalls.
1. Background: From reading statements to interrogating them
Traditional personal finance tools are essentially a query interface plus a charting dashboard. The user has to know in advance what they want to look at, where in the menus it lives, and which dimensions to filter on. What large language models change is the interaction paradigm: users stop learning an interface and start stating intent directly.
One pattern is common in the industry and worth examining closely: an AI assistant opens a limited beta of a personal finance module to a subset of subscribers. After linking a bank account, users can ask natural-language questions about balances, transaction history, and spending structure. Two design constraints here are easy to overlook but critical:
- Read-only access only. The assistant cannot transfer money, cannot initiate payments, and cannot invest on the user’s behalf.
- No transactional loop. It does not issue credit cards, does not offer loans, and does not operate its own deposit products.
Neither constraint is a missing feature. They are a deliberate contraction of the architecture. They determine the attack surface, the compliance boundary, and the trust model of the entire system, and they are the premise for every design decision that follows.
From an engineering point of view, the requirements fall into four categories of problem:
| Problem type | Example user question | Technical nature |
|---|---|---|
| Factual lookup | “How much did I spend this month?” | Precise aggregation query |
| Structural analysis | “Which categories did most of the money go to?” | Classification and grouping + statistics |
| Anomaly detection | “Were there any duplicate charges?” | Rule engine + pattern detection |
| Concept explanation | “What does APR mean? How much interest did I pay this year?” | Terminology knowledge + a follow-up query |
Translating “natural language” directly into “database query” is the first technical challenge, and it is also the foundation on which every later challenge rests.
2. Permission model: read-only is the design starting point, not a patch applied afterwards
2.1 Physical isolation through least privilege
The most common mistake is to first obtain full account authorization and then judge at the application layer that “the user only wants to look around.” The correct approach is to make privilege physically unreachable:
User → consent screen (read-only scope only)↓Authorization service → issues a read-only access token↓Data aggregation layer ← the token's permission set contains no write operation↓Analysis service / LLM orchestration layer
The key point is that the token reaching the orchestration layer and the model side is itself incapable of writing. Even if the prompt is injected and the model is induced to emit a tool call such as “transfer money for me,” the backend will still reject it during authorization, because the corresponding operation is simply not in the permission set. In defense in depth, this is the most valuable single layer.
2.2 Tool whitelisting: what the model can do is defined by the schema
The boundary of what a large language model can reach should be explicitly defined by tool descriptions, not “requested” through a system prompt. A schematic tool declaration:
{"name": "query_transactions","description": "Query transaction details by time range and account, read-only","parameters": {"type": "object","properties": {"accounts": { "type": "array", "items": { "type": "string" } },"start_date": { "type": "string", "format": "date" },"end_date": { "type": "string", "format": "date" },"categories": { "type": "array", "items": { "type": "string" } }}}}
Note that the schema contains no tools such as transfer, pay, create_order, or modify_recipient. Even if the model “wants” to do it, it cannot — an unreachable capability is safer than a constrained one.
2.3 The dual-control idea applied to sensitive operations
In a read-only system, the highest-risk “write” operations are actually changes to the user’s own configuration: rebinding an account, revoking consent, or deleting data. These should be handled so that they:
- run on a completely separate service path from data queries;
- require a second confirmation from the user (re-authentication of identity);
- are all written to tamper-evident audit logs.
3. Account data aggregation and standardization
3.1 The aggregation layer is the foundation of correctness
Read-only access does not mean simple. A user may have a savings account, a credit card, and several debit cards, coming from different institutions. The aggregation layer has to deal with:
- Heterogeneous multi-source data: field names, amount signs, and time formats differ across institutions. A credit card purchase may appear as a negative number in the transaction stream, or as a positive number representing consumption of the credit limit.
- Incremental synchronization: full pulls are both slow and wasteful, so synchronization is typically incremental based on a cursor or a time watermark, supplemented by periodic full reconciliation to repair drift.
- Idempotent deduplication: the same transaction appearing repeatedly across syncs is normal. Deduplication should use (institution ID + account number + transaction serial number) as the business primary key.
3.2 The unified model after normalization
All external data should enter an internal unified model before being exposed, so that the model side never has to deal with dirty data:
{"account_id": "acc_xxx","institution": "bank_a","account_type": "credit | debit | savings","currency": "USD","transactions": [{"txn_id": "unique business key","posted_at": "2026-03-14T09:22:00Z","amount_minor": -2350, # minor currency units, to avoid floating-point error"direction": "debit","merchant_raw": "raw merchant string","category": "groceries", # classification result"category_confidence": 0.82}]}
Two engineering details that are easy to overlook:
- Store and compute amounts as integers in minor currency units. Using floating point for money produces sub-cent errors when large amounts are summed, and financial scenarios are sensitive at the cent level.
- Keep the raw fields. Classification is inference; the raw merchant string is the evidence for verification. If a user questions a classification result, the system must be able to trace back to the original record.
3.3 Classification: a hybrid strategy beats a pure model
Do not hand transaction classification over to an LLM for per-item judgment. It is expensive and unstable. The common industry approach is layered:
- Rule layer: merchant category codes supplied by the institution, plus keyword mapping tables, covering most deterministic cases;
- Model layer: handling the long tail that rules miss, outputting a category plus a confidence score;
- Human feedback layer: mappings the user has corrected enter a per-user override table, so that the same user benefits from repeated learning.
Low-confidence items should be marked as “needs confirmation” in the results rather than presented as a definite conclusion.
4. The LLM orchestration layer: from intent to query plan
4.1 Semantic parsing and parameter extraction
When a user says “this month,” the model has to resolve it into a concrete date range. This step should produce a structured intermediate representation rather than generating SQL directly:
plan = {"intent": "spend_analysis","time_range": {"start": "2026-03-01", "end": "2026-03-31"},"dimensions": ["category"],"accounts": "all"}
The benefit is that an intermediate representation can be validated by code, while natural language cannot be reliably validated. Whether the date range is valid, whether the accounts belong to the current user, whether the dimensions are supported — all of this is checked on the server side, and the model takes no part in authorization decisions.
4.2 Terminology questions go through knowledge retrieval
Conceptual questions about APR, compounding, reserve requirements, and the like should take a retrieval-augmented path rather than relying on the model’s parametric memory:
- Retrieve the entry from a curated, reviewed knowledge base;
- Combine it with the user’s context (for example, “the interest you incurred this year”) to trigger a data query;
- Explain the term first, then give the number.
The order matters. Leading with a number makes users misread the amount without a conceptual framework; explaining first and then quantifying matches how people learn and reduces the risk of misunderstanding.
4.3 Anomaly detection: rules first, model as backstop
Questions like “were there any unusual charges” can be covered efficiently by a deterministic rule engine:
Detection rules:- Multiple same-amount charges from the same merchant in a short period → suspected duplicate charge- Unusual items in the fees / interest categories → suspected fees- Same merchant, same period, amount up more than a threshold vs. the previous period → suspected silent price increase- A merchant that has been dormant for a long time suddenly active → suspected fraud
The value of the model here is translating matched items into explanations the user can understand and ranking them by importance, not replacing the rules themselves.
5. Traceable numbers: the core mechanism against hallucination
Financial scenarios tolerate zero error. Four engineering constraints must be enforced.
5.1 All numbers come from queries, not from generation
The model’s job is to organize language, not to produce values. Every amount that appears in an answer should be traceable to a specific query result set. In practice, aggregate results are injected into the context as structured data, and the model is required to cite only the given data.
5.2 Second-pass computation check
Independently re-verify the model’s key conclusions:
def verify_summary(model_answer, query_result):"""Verify the total provided by the model using deterministic computation."""actual_total = sum(t.amount_minor for t in query_result.transactions)if abs(model_answer.total_minor - actual_total) > 0:raise InconsistencyError("Model total does not match the line items")# Verify that category shares sum to 1if abs(sum(model_answer.category_shares) - 1.0) > 1e-6:raise InconsistencyError("Category shares are not normalized")return model_answer
This step is a very low-cost, very high-return backstop: whether an amount is right can be judged by code far more reliably than by a model.
5.3 Expandable details
Any aggregate number should support drilling down to the transactions that make it up. This is both a verification tool and a way to build trust — users do not have to believe the model, they can count for themselves.
5.4 An explicit statement of capability boundaries
The system should consistently express three points in appropriate places: read-only, not investment advice, and results may contain errors. The last point in particular should always appear together with a note such as “for significant amounts, please refer to your bank’s records.” A model’s output carries far more confidence than its actual accuracy justifies, and this is a bias that product copy has to compensate for.
6. Output artifacts: making data actionable
A read-only system can still generate artifacts, because the artifacts themselves do not touch money:
- Periodic charts: weekly and monthly spending trends, category breakdowns;
- Bill calendar: recurring charges laid out on a timeline at their expected dates, surfacing future cash-flow pressure in advance;
- Custom dashboards: user-selected combinations of dimensions, adjustable at any time.
From an engineering standpoint, it is worth separating artifact generation from data querying, and storing chart configuration as a declarative description rather than as rendered images. That way, when a user says “pull last week out on its own,” the system only needs to modify the configuration and re-render, without re-running the aggregation query.
7. Privacy, security, and compliance
Financial data is highly sensitive personal information, and the architecture should cover at least the following:
- Encryption in transit and at rest: end-to-end encryption, sensitive fields encrypted before persistence, keys held by a dedicated key management service with no plaintext keys on the application side;
- Minimal retention: raw credentials are never stored; tokens are stored encrypted and can be revoked at any time; intermediate results needed for analysis have a short lifecycle;
- Auditing and observability: every data access is written to an audit log (who, when, which account was accessed, which tools were called), and logs feed a logging service with monitoring and alerting, including alerts on abnormal access frequency and privilege-escalation attempts;
- Data boundaries on the model side: context sent to the model should be field-trimmed and de-identified, providing only the fields needed to complete the task; it should be clearly recorded whether the data is used for training, and this should be disclosed to users;
- No advertising and no commercial recommendations: the finance section carries no ads and pushes no sponsored content, to avoid coupling “analysis conclusions” with “commercial interest.” This looks like a product principle, but it is in fact an architectural constraint — it means no conversion-oriented ranking factor exists anywhere in the computation path that produces the analysis.
8. Implementation recommendations and common pitfalls
Proceed in four phases:
- Get read-only working first: complete account linking and read-only consent, build a purely structured query interface, and validate data quality;
- Layer natural language on top: add intent parsing on top of the existing query capability, with tools reusing the already-validated query endpoints;
- Add analysis and explanation: introduce classification, anomaly detection, and a terminology knowledge base;
- Add artifacts and dashboards: build visualization and custom configuration last.
Several high-frequency pitfalls:
- Letting the model generate query statements directly. It creates both efficiency risk and injection and privilege-escalation risk. The model should output a constrained intermediate representation, and the server should translate it into queries.
- Computing money with floating point. Sub-cent errors get amplified in financial scenarios into a collapse of user trust.
- Putting confidence into user-facing copy. The classification model has a confidence score, but telling the user “probably” amounts to giving no answer at all. Low-confidence items should be explicitly marked as needing confirmation.
- Not signaling synchronization state. The aggregation layer may lag behind real-time bank data, and answers should reflect the cutoff time of the data.
- Ignoring the revocation path. Consent revocation, account unlinking, and deletion of historical data have to be designed from the start; retrofitting them later is extremely expensive.
9. Summary
The technical substance of this kind of product is not “being able to hold a conversation.” It is how to safely stack a probabilistic component on top of a data domain that is highly sensitive and zero-tolerance for error. The answer comes down to three orthogonal design principles: make access read-only and physically unreachable; make data normalized, deduplicable, and reconcilable; make output numbers traceable, conclusions drillable, and boundaries explicitly declared.
Under this division of labor, the large language model handles intent understanding, terminology explanation, and organizing conclusions, while deterministic querying and computation are left to code. This division both controls hallucination risk and makes the system’s correctness verifiable by engineering means — which is the most reliable way to bring generative capability into a serious business domain.
