Spaces:
Sleeping
Sleeping
File size: 5,177 Bytes
b2f4ef1 ba9f2db b2f4ef1 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | # Architecture β FinanceFlow AI
## System overview
A two-tier app: a React/Vite single-page frontend talks to a FastAPI backend over
a small REST/JSON API. The backend parses documents into chunks, ranks them with a
TF-IDF retriever, builds a grounded answer with citations, and persists everything
in SQLite. No external services are required to run it.
```mermaid
flowchart LR
U[Browser - React/Vite] -->|/api JSON| API[FastAPI backend]
API --> ING[Ingestion: parse -> chunk]
API --> RET[Retrieval: TF-IDF rank]
API --> ANS[Answering: abstain + extract + cite]
ANS -. optional .-> LLM[(OpenAI / Anthropic)]
ING --> DB[(SQLite)]
RET --> DB
API --> DB
```
In Docker, the frontend is served by nginx which proxies `/api` to the backend
container; in local dev, Vite proxies `/api` to `http://127.0.0.1:8000`.
## Backend modules (`backend/app/`)
| Module | Responsibility |
| ------ | -------------- |
| `main.py` | FastAPI app, all endpoints, CORS, logging setup, global exception handler |
| `config.py` | Env-driven settings with safe defaults (answer mode, thresholds, limits) |
| `database.py` | SQLAlchemy engine/session + SQLite `PRAGMA foreign_keys=ON` |
| `models.py` | ORM models: `Document`, `Chunk`, `QA` |
| `schemas.py` | Pydantic request/response models (typed I/O, Swagger) |
| `parsing.py` | Text extraction: TXT (whole file) and PDF (per page, via `pypdf`) |
| `chunking.py` | Overlapping word-window chunking |
| `retrieval.py` | Tokenizer + TF-IDF ranker (`rank_chunks`) β offline first stage |
| `reranking.py` | Optional cross-encoder reranking (FlashRank) of the TF-IDF candidates; graceful fallback to TF-IDF order when unavailable |
| `answering.py` | Abstention gates, focused extractive answer, citation builder |
| `llm.py` | Optional OpenAI/Anthropic synthesis with graceful fallback |
| `ingest.py` | Shared parse β chunk β persist pipeline |
| `evaluation.py` | Golden-dataset runner + metrics |
| `eval_cli.py` | `python -m app.eval_cli` |
| `reset_db.py` | `python -m app.reset_db` (drop + recreate) |
| `logging_config.py` | One-line logging setup |
## Frontend modules (`frontend/src/`)
| Area | Files |
| ---- | ----- |
| Shell | `App.jsx` (sidebar + view routing + demo orchestration) |
| Navigation | `components/Sidebar.jsx` |
| Views | `components/views/Overview.jsx`, `Ask.jsx`, `Evaluation.jsx` |
| Feature components | `DocumentCard.jsx`, `EvidencePanel.jsx`, `MetricCard.jsx`, `Upload.jsx` |
| UI primitives | `components/ui/Button.jsx`, `Card.jsx`, `Badge.jsx`, `Field.jsx`, `States.jsx` |
| Misc | `lib/icons.jsx`, `api.js`, `styles.css` |
## Database schema
```mermaid
erDiagram
DOCUMENTS ||--o{ CHUNKS : has
DOCUMENTS ||--o{ QA_HISTORY : has
DOCUMENTS {
int id PK
string filename
string file_type
string status
text error
int num_chunks
datetime created_at
}
CHUNKS {
int id PK
int document_id FK
int chunk_index
int page
text text
}
QA_HISTORY {
int id PK
int document_id FK
text question
text answer
int abstained
text citations_json
datetime created_at
}
```
Deleting a `Document` cascades to its `Chunk` and `QA` rows (ORM cascade +
SQLite foreign-key pragma).
## Document processing flow
```mermaid
flowchart TD
A[POST /documents/upload] --> B{Validate type + size}
B -- invalid --> E[400 / 413 error]
B -- ok --> C[Write to temp file]
C --> D[parse: extract pages]
D --> F[chunk: overlapping word windows]
F --> G{Any chunks?}
G -- no --> H[status = failed, error stored]
G -- yes --> I[Store chunks, status = processed]
C --> J[Delete temp file in finally]
```
The raw upload is parsed in a temp file and then discarded β only extracted
chunks are persisted.
## Retrieval flow
```mermaid
flowchart LR
Q[Question] --> T[Tokenize - drop stopwords]
C[Document chunks] --> IDF[Build IDF over chunks]
T --> S[Score chunk = sum tf*idf]
IDF --> S
S --> N[Normalize by sqrt chunk length]
N --> K[Take top-K above threshold]
```
## Ask β answer β citation flow
```mermaid
flowchart TD
A[POST /ask] --> V{Document processed?}
V -- no --> ERR[400/404]
V -- yes --> R[rank_chunks top-K]
R --> G1{score above threshold?}
G1 -- no --> AB[Abstain message, no citations]
G1 -- yes --> G2{at least 2 query terms matched?}
G2 -- no --> AB
G2 -- yes --> CIT[Build focused citations]
CIT --> M{LLM mode + key?}
M -- yes --> LLM[Synthesize from context, strict prompt]
M -- no --> EX[Focused extractive span]
LLM --> SAVE[Persist QA + return]
EX --> SAVE
AB --> SAVE
```
## Evaluation flow
```mermaid
flowchart LR
GD[golden_dataset.json] --> SEED[Ensure sample docs ingested]
SEED --> LOOP[For each question: answer_question]
LOOP --> CLS{Expectation vs outcome}
CLS --> MET[Metrics: coverage, abstentions, unsupported, evidence-match]
MET --> OUT[UI cards + CLI report]
```
`unsupported_answer_count > 0` makes the CLI exit non-zero, so CI can gate on it.
|