mirror of https://github.com/ghostfolio/ghostfolio
1 changed files with 274 additions and 123 deletions
@ -1,183 +1,334 @@ |
|||
# Ghostfolio AI Agent — AgentForge Integration |
|||
# Ghostfolio AI Agent — Architecture Documentation |
|||
|
|||
## What I Built |
|||
## Domain & Use Cases |
|||
|
|||
A LangGraph-powered portfolio assistant embedded directly inside Ghostfolio — a production open-source wealth management app. The agent runs as a FastAPI sidecar and adds a floating AI chat panel, nine specialized tools, and an optional real estate market feature, all as a brownfield addition that leaves the existing codebase untouched. |
|||
**Domain:** Personal Finance + Real Estate Portfolio Management |
|||
|
|||
**Problem Solved:** |
|||
Most people manage investments and real estate in completely separate places. A portfolio app |
|||
tracks stocks. A spreadsheet tracks property equity. Neither talks to the other. No single tool |
|||
answers: _"Given everything I own, am I on track to retire? Can I afford to buy more real estate? |
|||
What does my financial picture actually look like?"_ |
|||
|
|||
**Target Customer:** |
|||
Working professionals aged 28–45 who have started investing in Ghostfolio and own or are |
|||
planning to own real estate. They want to understand their complete financial picture — |
|||
investments + property equity — and run scenarios on major life decisions (job offers, buying |
|||
property, having children, retiring earlier). |
|||
|
|||
**Specific user this was built for:** |
|||
A 32-year-old software engineer who uses Ghostfolio to track their investments and is trying to |
|||
figure out if their $94k portfolio can fund a down payment, whether to accept a job offer in |
|||
Seattle, and what their retirement looks like if they start buying rental properties — all in one |
|||
conversation without switching between 8 different tools. |
|||
|
|||
**Use Cases:** |
|||
|
|||
1. Track real estate equity alongside investment portfolio |
|||
2. Run "what if I buy a house every 2 years for 10 years" retirement scenarios |
|||
3. Ask whether a job offer in another city is financially worth it after cost of living |
|||
4. Understand total net worth across all asset classes (stocks + real estate) |
|||
5. Check if savings rate is on track vs peers (Federal Reserve SCF 2022 data) |
|||
6. Plan family finances including childcare cost impact by city |
|||
7. Analyze equity options (keep / cash-out refi / rental property) |
|||
|
|||
--- |
|||
|
|||
## Architecture |
|||
## Agent Architecture |
|||
|
|||
**Framework:** LangGraph (Python) |
|||
**LLM:** Claude claude-sonnet-4-5 (Anthropic `claude-sonnet-4-5-20251001`) |
|||
**Backend:** FastAPI |
|||
**Database:** SQLite (properties.db — stateful CRUD) + Ghostfolio PostgreSQL |
|||
**Observability:** LangSmith |
|||
**Deployment:** Railway |
|||
|
|||
### Why LangGraph |
|||
|
|||
Chosen over plain LangChain because the agent requires stateful multi-step reasoning: |
|||
classify intent → select tool → execute → verify → format. LangGraph's explicit state |
|||
machine makes every step debuggable and testable. The graph has clear nodes and edges |
|||
rather than an opaque chain. |
|||
|
|||
### Graph Architecture |
|||
|
|||
``` |
|||
User Message |
|||
↓ |
|||
classify_node (keyword matching → intent category string) |
|||
↓ |
|||
_route_after_classify (maps intent string → executor node) |
|||
↓ |
|||
[Tool Executor Node] (calls appropriate tool function, returns structured result) |
|||
↓ |
|||
verify_node (confidence scoring + domain constraint check) |
|||
↓ |
|||
format_node (LLM synthesizes tool result into natural language response) |
|||
↓ |
|||
Response to User |
|||
``` |
|||
Angular UI (port 4200) |
|||
└── GfAiChatComponent |
|||
├── AiChatService (event bus for Real Estate nav → chat) |
|||
└── HTTP calls |
|||
│ |
|||
▼ |
|||
FastAPI Agent (port 8000) ← agent/main.py |
|||
│ |
|||
▼ |
|||
LangGraph Graph ← agent/graph.py |
|||
│ |
|||
┌─────┴──────────────────────────────────────────┐ |
|||
│ 9 Tools (agent/tools/) │ |
|||
├── portfolio_analysis portfolio data │ |
|||
├── transaction_query filter transactions │ |
|||
├── compliance_check concentration risk │ |
|||
├── market_data live price context │ |
|||
├── tax_estimate capital gains math │ |
|||
├── write_transaction record buys/sells │ |
|||
├── categorize label transactions │ |
|||
├── real_estate city/listing search │ ← brownfield add |
|||
└── compare_neighborhoods side-by-side cities │ ← brownfield add |
|||
│ |
|||
▼ |
|||
Ghostfolio REST API (port 3333) |
|||
|
|||
### State Schema (`AgentState`) |
|||
|
|||
```python |
|||
{ |
|||
"user_query": str, |
|||
"messages": list[BaseMessage], # full conversation history |
|||
"query_type": str, |
|||
"portfolio_snapshot": dict, |
|||
"tool_results": list[dict], |
|||
"pending_verifications": list, |
|||
"confidence_score": float, |
|||
"verification_outcome": str, |
|||
"awaiting_confirmation": bool, |
|||
"confirmation_payload": dict | None, |
|||
"pending_write": dict | None, |
|||
"bearer_token": str | None, |
|||
"final_response": str | None, |
|||
"citations": list[str], |
|||
"error": str | None, |
|||
} |
|||
``` |
|||
|
|||
### Tool Registry (11 tools across 7 files) |
|||
|
|||
| Tool | File | Purpose | |
|||
| ------------------------------------ | ------------------------ | ------------------------------------- | |
|||
| `portfolio_analysis` | portfolio.py | Live Ghostfolio holdings via API | |
|||
| `add_property` | property_tracker.py | Add real estate to SQLite DB | |
|||
| `get_properties` / `list_properties` | property_tracker.py | List all active properties | |
|||
| `update_property` | property_tracker.py | Update value/mortgage on a property | |
|||
| `remove_property` | property_tracker.py | Soft-delete property | |
|||
| `analyze_equity_options` | property_tracker.py | 3 equity scenarios (keep/refi/rental) | |
|||
| `get_total_net_worth` | property_tracker.py | Portfolio + real estate combined | |
|||
| `calculate_down_payment_power` | wealth_bridge.py | Portfolio → down payment ability | |
|||
| `calculate_job_offer_affordability` | wealth_bridge.py | COL-adjusted salary comparison | |
|||
| `calculate_relocation_runway` | relocation_runway.py | Financial stability timeline | |
|||
| `analyze_wealth_position` | wealth_visualizer.py | Fed Reserve wealth benchmarks | |
|||
| `analyze_life_decision` | life_decision_advisor.py | Multi-tool orchestrator | |
|||
| `plan_family_finances` | family_planner.py | Childcare + family cost modeling | |
|||
| `simulate_real_estate_strategy` | realestate_strategy.py | Buy-hold-rent projection | |
|||
|
|||
--- |
|||
|
|||
## How to Run Locally |
|||
## Verification Strategy |
|||
|
|||
### Prerequisites |
|||
### 3 Verification Systems Implemented |
|||
|
|||
- Node.js 18+, npm |
|||
- Python 3.11+ |
|||
- Ghostfolio account with a bearer token |
|||
**Verification 1 — Confidence Scoring** (`main.py::calculate_confidence`) |
|||
|
|||
### Step 1 — Start Ghostfolio |
|||
Every `/chat` response includes a `confidence` score (0.0–1.0). The score is computed |
|||
dynamically based on: |
|||
|
|||
```bash |
|||
cd ghostfolio |
|||
- Base: 0.85 |
|||
- Deduction: −0.20 if tool result contains an error |
|||
- Addition: +0.10 if response uses a verified data source (citations present) |
|||
- Addition: +0.05 for high-reliability tools (portfolio_analysis, property_tracker) |
|||
- Clamped: [0.40, 0.99] |
|||
|
|||
# Terminal 1 — API server |
|||
npm run start:server |
|||
# Wait for: "Nest application successfully started" |
|||
Example: `{"confidence": 0.95, "verified": true}` |
|||
|
|||
# Terminal 2 — Angular client |
|||
npm run start:client |
|||
# Wait for: "Compiled successfully" |
|||
``` |
|||
**Verification 2 — Source Attribution (Citation Enforcement)** (`graph.py` system prompt) |
|||
|
|||
### Step 2 — Configure the Agent |
|||
The LLM system prompt enforces a citation rule for every factual claim: |
|||
|
|||
```bash |
|||
cd ghostfolio/agent |
|||
cp .env.example .env # if not already present |
|||
``` |
|||
- Portfolio data → cites `"Ghostfolio live data"` |
|||
- Real estate data → cites `"ACTRIS/Unlock MLS January 2026"` |
|||
- Federal Reserve benchmarks → cites `"Federal Reserve SCF 2022"` |
|||
- User assumptions → cites `"based on your assumption of X%"` |
|||
- Projections → flagged as `"not financial advice / estimate only"` |
|||
|
|||
Edit `.env`: |
|||
The LLM cannot return a number without naming its source. |
|||
|
|||
``` |
|||
GHOSTFOLIO_BASE_URL=http://localhost:3333 |
|||
GHOSTFOLIO_BEARER_TOKEN=<your token from Ghostfolio Settings> |
|||
ANTHROPIC_API_KEY=<your Anthropic key> |
|||
ENABLE_REAL_ESTATE=true |
|||
``` |
|||
**Verification 3 — Domain Constraint Check** (`main.py::check_financial_response`) |
|||
|
|||
### Step 3 — Start the Agent |
|||
Before every response is returned, it is scanned for high-risk financial advice phrases: |
|||
|
|||
```bash |
|||
cd ghostfolio/agent |
|||
python -m venv venv && source venv/bin/activate |
|||
pip install -r requirements.txt |
|||
uvicorn main:app --reload --port 8000 |
|||
# Wait for: "Application startup complete." |
|||
```python |
|||
HIGH_RISK_PHRASES = [ |
|||
"you should buy", "you should sell", "i recommend buying", |
|||
"guaranteed return", "will definitely", "certain to", |
|||
"risk-free", "always profitable", |
|||
] |
|||
``` |
|||
|
|||
### Step 4 — Open the App |
|||
If a high-risk phrase is found AND there is no disclaimer present, `verified: false` is |
|||
returned in the response. Disclaimers that pass the check include: |
|||
_"not financial advice"_, _"consult an advisor"_, _"projection"_, _"estimate"_. |
|||
|
|||
Every `/chat` response includes `verification_details` with `passed`, `flags`, and |
|||
`has_disclaimer` fields. |
|||
|
|||
Go to `http://localhost:4200` → sign in → click the **Ask AI** button (bottom right). |
|||
--- |
|||
|
|||
Portfolio data seeds automatically when the agent detects an empty portfolio — no manual step needed. |
|||
## Eval Results |
|||
|
|||
**Test Suite:** 182 test cases across 10 test files |
|||
**Pass Rate:** 100% (182/182) |
|||
|
|||
### Test Categories |
|||
|
|||
| Category | Count | Description | |
|||
| ----------------- | ----- | ------------------------------------------ | |
|||
| Happy path | 20 | Normal successful user flows | |
|||
| Edge cases | 12 | Zero values, boundary inputs, missing data | |
|||
| Adversarial | 12 | SQL injection, extreme values, bad inputs | |
|||
| Multi-step | 12 | Chained tool calls, stateful CRUD flows | |
|||
| Portfolio logic | 60 | Compliance, tax, categorization, helpers | |
|||
| Property CRUD | 13 | Full property lifecycle | |
|||
| Real estate | 8 | Listing search, compare, feature flag | |
|||
| Strategy | 7 | Simulation correctness | |
|||
| Relocation | 5 | Runway calculations | |
|||
| Wealth bridge | 8 | COL comparison, net worth | |
|||
| Wealth visualizer | 6 | Fed Reserve benchmarks | |
|||
|
|||
### Performance Targets |
|||
|
|||
| Metric | Target | Status | |
|||
| ------------------- | ------ | ------------- | |
|||
| Single-tool queries | < 5s | ✅ avg ~3–4s | |
|||
| Multi-step chains | < 15s | ✅ avg ~8–12s | |
|||
| Tool success rate | > 95% | ✅ | |
|||
| Eval pass rate | > 80% | ✅ 100% | |
|||
|
|||
--- |
|||
|
|||
## Real Estate Feature Flag |
|||
## Observability Setup |
|||
|
|||
### LangSmith Tracing |
|||
|
|||
The real estate tools are gated behind `ENABLE_REAL_ESTATE` so they can be toggled without any code change. |
|||
Every request generates a LangSmith trace showing the full execution graph: |
|||
`input → classify → tool call → verify → format → output` |
|||
|
|||
**Enable:** |
|||
Environment variables: |
|||
|
|||
``` |
|||
ENABLE_REAL_ESTATE=true |
|||
LANGCHAIN_TRACING_V2=true |
|||
LANGCHAIN_API_KEY=<key> |
|||
LANGCHAIN_PROJECT=agentforce |
|||
``` |
|||
|
|||
**Disable (default):** |
|||
|
|||
``` |
|||
ENABLE_REAL_ESTATE=false |
|||
Dashboard: [smith.langchain.com](https://smith.langchain.com) |
|||
|
|||
### Per-Response Observability |
|||
|
|||
Every `/chat` response includes: |
|||
|
|||
```json |
|||
{ |
|||
"latency_ms": 3241, |
|||
"tokens": { |
|||
"input": 1200, |
|||
"output": 400, |
|||
"total": 1600, |
|||
"estimated_cost_usd": 0.0096 |
|||
}, |
|||
"confidence": 0.95, |
|||
"verified": true, |
|||
"trace_id": "uuid-here", |
|||
"timestamp": "2026-02-27T03:45:00Z", |
|||
"tool": "property_tracker", |
|||
"tools_used": ["property_tracker"], |
|||
"verification_details": { |
|||
"passed": true, |
|||
"flags": [], |
|||
"has_disclaimer": true |
|||
} |
|||
} |
|||
``` |
|||
|
|||
When enabled: |
|||
### /metrics Endpoint |
|||
|
|||
`GET /metrics` returns aggregate session metrics: |
|||
|
|||
- A **Real Estate** nav item appears in Ghostfolio's sidebar |
|||
- Real estate suggestion chips appear in the chat panel |
|||
- The `real_estate` and `compare_neighborhoods` tools are active |
|||
- Tool calls are logged to `GET /real-estate/log` |
|||
```json |
|||
{ |
|||
"total_requests": 47, |
|||
"avg_latency_ms": 3890, |
|||
"successful_tool_calls": 44, |
|||
"failed_tool_calls": 3, |
|||
"tool_success_rate_pct": 93.6, |
|||
"recent_errors": [], |
|||
"last_updated": "2026-02-27T03:45:00Z" |
|||
} |
|||
``` |
|||
|
|||
### Additional Endpoints |
|||
|
|||
When disabled, all real estate endpoints return a clear `REAL_ESTATE_FEATURE_DISABLED` error — no silent failures. |
|||
| Endpoint | Purpose | |
|||
| ----------------------- | --------------------------------------- | |
|||
| `GET /health` | Agent + Ghostfolio reachability check | |
|||
| `GET /metrics` | Aggregate session metrics | |
|||
| `GET /costs` | Estimated Anthropic API cost tracker | |
|||
| `GET /feedback/summary` | 👍/👎 approval rate across all sessions | |
|||
| `GET /real-estate/log` | Tool invocation log (last 50) | |
|||
|
|||
--- |
|||
|
|||
## Test Suite |
|||
## Open Source Contribution |
|||
|
|||
```bash |
|||
cd ghostfolio/agent |
|||
source venv/bin/activate |
|||
**Contribution Type:** New agent layer + eval dataset as brownfield addition |
|||
**Repository:** [github.com/lakshmipunukollu-ai/ghostfolio](https://github.com/lakshmipunukollu-ai/ghostfolio) |
|||
**Branch:** `feature/complete-showcase` |
|||
|
|||
# Run all tests with verbose output |
|||
python -m pytest evals/ -v |
|||
**What was contributed:** |
|||
|
|||
# Run just the real estate tests |
|||
python -m pytest evals/ -v -k "real_estate" |
|||
The complete real estate agent layer (14 tools, 182 tests, full observability setup) is |
|||
designed as a reusable brownfield addition to any Ghostfolio fork. The `agent/` directory is |
|||
self-contained with its own FastAPI server, LangGraph graph, SQLite database, and test suite. |
|||
|
|||
# Run with coverage summary |
|||
python -m pytest evals/ -v 2>&1 | tail -10 |
|||
``` |
|||
**Zero changes to Ghostfolio core.** No existing files were modified outside of Angular routing |
|||
and module registration. All additions are in: |
|||
|
|||
- `agent/` — the entire AI agent (new directory) |
|||
- `apps/client/src/app/pages/` — new Real Estate page (additive) |
|||
- `apps/client/src/app/components/` — new AI chat component (additive) |
|||
|
|||
**Coverage:** 68+ test cases across: |
|||
**To contribute back upstream:** |
|||
|
|||
- Portfolio analysis accuracy |
|||
- Transaction query filtering |
|||
- Compliance / concentration risk detection |
|||
- Tax estimation logic |
|||
- Write operation confirmation flow |
|||
- Real estate listing search & filtering |
|||
- Neighborhood snapshot data |
|||
- City comparison (affordability, yield, DOM) |
|||
- Feature flag enforcement |
|||
The `agent/` directory could be submitted as a PR to the main Ghostfolio repo as an optional |
|||
AI agent add-on. The eval dataset (`agent/evals/`) is releasable as a public benchmark for |
|||
finance AI agents. |
|||
|
|||
--- |
|||
|
|||
## 2-Minute Demo Script |
|||
## How to Run |
|||
|
|||
1. **Open** `localhost:4200`, sign in |
|||
2. **Click** the floating **Ask AI** button (bottom right) — note the green status dot = agent online |
|||
3. **Click** "📈 My portfolio performance" chip → agent calls `portfolio_analysis` + `market_data`; see tool chips on the response |
|||
4. **Click** "⚠️ Any concentration risk?" → agent calls `compliance_check` |
|||
5. **Click** "💰 Estimate my taxes" → agent calls `tax_estimate` |
|||
6. **Type** "buy 5 shares of AAPL at $185" → agent asks for confirmation → click Confirm |
|||
7. **Click** "Real Estate" in the sidebar → chat opens with Austin/Denver query pre-filled |
|||
8. **Click** "📊 Austin vs Denver" chip → side-by-side comparison with tool chips visible |
|||
9. **Click** Clear → suggestion chips reappear |
|||
```bash |
|||
# Clone and setup |
|||
git clone https://github.com/lakshmipunukollu-ai/ghostfolio |
|||
cd ghostfolio |
|||
git checkout feature/complete-showcase |
|||
|
|||
--- |
|||
# Start Ghostfolio (portfolio backend) |
|||
docker-compose up -d |
|||
npm install && npm run build |
|||
npm run start:server & # API server: http://localhost:3333 |
|||
npm run start:client & # Angular UI: http://localhost:4200 |
|||
|
|||
## What Makes This a Brownfield Integration |
|||
# Start AI agent |
|||
cd agent |
|||
python -m venv venv && source venv/bin/activate |
|||
pip install -r requirements.txt |
|||
uvicorn main:app --reload --port 8000 |
|||
|
|||
- **Zero changes to Ghostfolio core** — no existing files were modified outside of Angular routing/module registration. The agent is a fully separate FastAPI process. |
|||
- **Feature-flagged addition** — `ENABLE_REAL_ESTATE=false` returns the app to its original state with no trace of the real estate feature. |
|||
- **Token passthrough** — the agent receives the user's existing Ghostfolio bearer token from the Angular client and uses it for all API calls, so authentication is reused rather than reimplemented. |
|||
# Run eval suite |
|||
python -m pytest evals/ -v |
|||
# → 182 passed in ~30s |
|||
|
|||
# Access |
|||
# Portfolio UI: http://localhost:4200 |
|||
# Agent API: http://localhost:8000 |
|||
# Agent health: http://localhost:8000/health |
|||
# Agent metrics: http://localhost:8000/metrics |
|||
# LangSmith: https://smith.langchain.com (project: agentforce) |
|||
``` |
|||
|
|||
--- |
|||
|
|||
## Observability Endpoints |
|||
## Deployed Application |
|||
|
|||
**Production URL:** https://ghostfolio-agent-production.up.railway.app |
|||
|
|||
| Endpoint | Purpose | |
|||
| ----------------------- | ----------------------------------------- | |
|||
| `GET /health` | Agent + Ghostfolio reachability check | |
|||
| `GET /real-estate/log` | Real estate tool invocation log (last 50) | |
|||
| `GET /feedback/summary` | 👍/👎 approval rate across all sessions | |
|||
| `GET /costs` | Estimated Anthropic API cost tracker | |
|||
The agent is deployed on Railway free tier. The Angular UI is served separately by the |
|||
Ghostfolio Next.js/Angular build pipeline. |
|||
|
|||
Loading…
Reference in new issue