Technical Implementation and Security Design of an LLM-Based Personal Finance Assistant
This article outlines the technical architecture and security design of a personal finance assistant powered by Large Language Models (LLMs). It covers the system's read-only capability boundaries, a five-layer architecture, strict data normalization rules, and anomaly detection methods. Additionally, it details minimum privilege security designs, the distinction between interest calculation rates, and specific strategies to prevent LLM hallucinations in a financial context.
1. Background and Capability Boundaries
Personal finance management faces two long-standing challenges. First, accounts are scattered across different institutions, requiring users to log in separately to check balances and transactions, making manual expense aggregation time-consuming and prone to omissions. Second, some charges are highly隐蔽, such as duplicate charges from the same merchant, small fees, or quietly increasing subscription prices year over year, which ordinary users struggle to detect promptly.
Introducing natural language Q&A into this scenario can lower the barrier to entry for queries. A user can simply ask, “Where did my money go this month?” and the system will return a categorized summary. However, at the outset of the design, the capability boundaries must be clearly defined. This solution does exactly three things: reads user-authorized transaction data, calculates and explains the results, and flags anomalies worth verifying. The system does not execute transfers, does not manage funds, and does not provide investment advice.
2. Overall Architecture
The system can be broken down into five layers, each with a single responsibility to facilitate independent testing and replacement:
User Natural Language Query│▼[Intent Parsing Layer] Identify query type, time range, account scope│▼[Analysis & Calculation Layer] Classification, anomaly detection, interest calculation (deterministic code)│▼[Data Standardization Layer] Unify amount, time, currency, and merchant fields│▼[Read-only Data Access Layer] Read authorized account data│▼[Result Presentation Layer] Text explanation, detail list, charts, and calendar
The core principle is that numerical values like amounts must only be generated by deterministic calculations, while the language model is responsible for understanding the question and organizing the explanation.
3. Read-only Authorization and Security Design
1. Minimum Privilege
Access credentials are only granted query-level permissions. The architecture does not expose any interfaces for transfers, payments, or account modifications. Permissions must be strictly enforced at the gateway layer rather than relying on prompt constraints. Conceptual pseudocode is as follows:
ALLOWED_SCOPES = {"accounts:read", "transactions:read", "balances:read"}def call_data_source(token, scope, request):if scope not in ALLOWED_SCOPES:raise PermissionError("Operation exceeds read-only scope")return data_gateway.send(token, request)
2. Credential Protection
Authorization tokens are stored encrypted, with keys managed by a dedicated Key Management Service (KMS). Plaintext credentials must not appear in code, configuration files, or logs.
3. Data Minimization
Only the fields necessary for analysis are retained, with clearly defined retention periods. Users can revoke authorization at any time, and the system should synchronously delete cached data.
4. Log Desensitization
Account and card numbers are masked to retain only the last four digits. Amount details are not written into general runtime logs.
4. Transaction Data Normalization
Different institutions return data in vastly varying formats. Amounts might be indicated by positive/negative signs for income/expenses, or split into separate income and expense columns. Timestamps may include time zones or only be accurate to the date. Merchant names often夹杂 with regional codes and sequence numbers. Analyzing data without unified normalization leads to statistical discrepancies. Conceptual pseudocode is as follows:
def normalize(raw, account):return {"txn_id": make_stable_id(account.id, raw.external_id),"amount_minor": to_minor_units(raw.amount, raw.currency),"direction": "out" if raw.amount < 0 else "in","currency": raw.currency,"occurred_at": to_utc(raw.timestamp, raw.timezone),"merchant": clean_merchant_name(raw.description),"account_id": account.id,}
There are three key points here:
- Amounts are stored as integers in the smallest currency unit to avoid floating-point summation errors.
- Time is uniformly converted to Coordinated Universal Time (UTC) to prevent misalignment during cross-time-zone statistics.
- Transaction identifiers must be stable. The same transaction must not be counted multiple times during syncs; otherwise, duplicate charge detection might mistakenly flag a sync failure as an anomaly.
5. Expense Classification
Classification is typically implemented across three tiers:
- Merchant Mapping Table: Covers common merchants and fixed charges. It has the highest accuracy and should be matched first.
- Keyword Rules: Handles common descriptions not covered by the mapping table, such as transactions containing specific industry vocabulary.
- Statistical Model: Provides a category and confidence level for transactions missed by the first two tiers.
Transactions with a confidence level below a set threshold are not automatically categorized. Instead, they are flagged as “Pending Confirmation” for user correction. The correction results should be written back to the mapping table to ensure continuous improvement.
6. Anomaly Detection in Billing
Anomaly detection requires combining multiple signals to make accurate judgments.
Duplicate Charges: Identical merchant and amount appearing two or more times within a short time window (e.g., 24 hours), not belonging to known installments or regular payments. Conceptual pseudocode is as follows:
def find_duplicates(txns, window_hours=24):flagged = []for a, b in pairs_by_merchant_and_amount(txns):if abs(a.occurred_at - b.occurred_at) <= window_hours * 3600:flagged.append((a.txn_id, b.txn_id))return flagged
Abnormal Fees: Similar types of fees increasing significantly over a period, or the proportion of fees to total expenses exceeding the user’s historical average.
Incremental Price Hikes: For subscription-based merchants, the deduction amount for the same service is compared across cycles. An alert is triggered when the increase exceeds a set threshold, such as consecutive increases over two periods with a cumulative rise over 10%. The threshold must be configurable and should not be hardcoded.
Every flag must be accompanied by evidence, including relevant transaction IDs, times, and amounts. Providing conclusions without evidence makes it impossible for users to verify, rendering the check meaningless.
7. Interest Calculation and Annual Rates
Annual Percentage Rate (APR) is the nominal annual interest rate without considering compounding effects. Annual Percentage Yield (APY) accounts for compounding. The two methods yield different interest results and must be distinguished during explanation.
A common estimation method calculates interest based on the average daily balance:
Interest ≈ Average Daily Balance × APR ÷ 365 × Number of Accrual Days
Conceptual pseudocode is as follows:
def estimate_interest(daily_balances, apr):daily_rate = apr / 365return sum(bal * daily_rate for bal in daily_balances)
Actual interest accrual rules vary by product and contract, potentially involving grace periods, compounding frequency, and tiered rates. Results provided by the system should be explicitly labeled as “Estimates,” with a prompt reminding users to rely on the official statements provided by the institution.
8. The Role of LLMs and Hallucination Prevention
Large Language Models are well-suited for three types of tasks: understanding ambiguous questions (e.g., “Am I spending too much?”) and mapping them to specific calculation tasks; explaining professional terminology; and organizing the presentation of results.
They should not be responsible for calculating amounts or generating transaction records. Protective measures include:
- Every amount in the response must be traceable to a specific transaction ID.
- Numbers without a traceable source must not appear in the output.
- For judgments such as “whether expenses exceed income,” the code should first aggregate the total income and expenses, and the model is only responsible for describing the result.
9. Testing and Validation
- Reconciliation Testing: Using account statements provided by institutions or manually recorded data as a baseline to verify the accuracy of the system’s calculations.
