|
|
# Riksdagen Sökplattform – Teknisk Översikt |
|
|
|
|
|
En plattform för att utforska riksdagsdebatter och metadata. Backenden bygger på FastAPI, ArangoDB (med ArangoSearch), samt ChromaDB för vektorsök i chatten. Frontenden är en React/Vite-applikation skriven i TypeScript. LLM-funktionalitet kopplas in via `_llm`-paketet. |
|
|
|
|
|
--- |
|
|
|
|
|
## 📋 Innehållsförteckning |
|
|
|
|
|
1. [Systemöversikt](#systemöversikt) |
|
|
2. [Projektstruktur](#projektstruktur) |
|
|
3. [Backend-arkitektur](#backend-arkitektur) |
|
|
4. [Frontend-arkitektur](#frontend-arkitektur) |
|
|
5. [Dataflöden](#dataflöden) |
|
|
6. [Viktiga Python- och TypeScript-komponenter](#viktiga-python--och-typescript-komponenter) |
|
|
7. [ArangoDB och ArangoSearch](#arangodb-och-arangosearch) |
|
|
8. [ChromaDB för vektorsök](#chromadb-för-vektorsök) |
|
|
9. [LLM-integration](#llm-integration) |
|
|
10. [Utvecklingsflöde](#utvecklingsflöde) |
|
|
11. [Felsökning och tips](#felsökning-och-tips) |
|
|
|
|
|
--- |
|
|
|
|
|
## 🏗️ Systemöversikt |
|
|
|
|
|
``` |
|
|
┌─────────────┐ HTTP ┌──────────────┐ Python ┌───────────────┐ |
|
|
│ Browser │ ─────────▶ │ FastAPI │ ───────────▶ │ ArangoDB │ |
|
|
│ (React) │ ◀───────── │ backend │ ◀─────────── │ (ArangoSearch) │ |
|
|
└─────────────┘ └──────┬───────┘ └───────────────┘ |
|
|
│ |
|
|
│ embeddings / metadata |
|
|
▼ |
|
|
┌──────────────┐ |
|
|
│ ChromaDB │ |
|
|
└──────┬───────┘ |
|
|
│ |
|
|
▼ |
|
|
┌──────────────┐ |
|
|
│ _llm │ (OpenAI API-kompatibel klient) |
|
|
└──────────────┘ |
|
|
``` |
|
|
|
|
|
- **ArangoDB**: primär databas. Sök görs via ArangoSearch-vyer och AQL; inga relationsfrågor används. |
|
|
- **ChromaDB**: håller embeddings för chatten och mer avancerad semantisk sökning. |
|
|
- **LLM**: genererar chattsvar med hjälp av `_llm.LLM` (kopplad till t.ex. vLLM/OpenAI API). |
|
|
|
|
|
--- |
|
|
|
|
|
## 📁 Projektstruktur |
|
|
|
|
|
``` |
|
|
riksdagen/ |
|
|
├── arango_client.py # Initierar Arango-klienten (används av backend & scripts) |
|
|
├── backend/ |
|
|
│ ├── app.py # FastAPI-app, routerregistrering |
|
|
│ ├── search.py # SearchService med ArangoSearch-frågor |
|
|
│ ├── chat.py # Chat-endpoints (LLM + Chroma) |
|
|
│ ├── vector.py # Hjälpfunktioner för vektorsök (omfattar Chroma-anrop) |
|
|
│ └── ... # Övriga routrar, scheman och konfigurationer |
|
|
├── frontend/ |
|
|
│ ├── src/ |
|
|
│ │ ├── App.tsx # Inträde, växlar mellan sök/chat-lägen |
|
|
│ │ ├── api.ts # Axios-klient mot `/api/*` |
|
|
│ │ ├── types.ts # TypeScript-typer för API-svar |
|
|
│ │ ├── components/ # UI-komponenter (SearchPanel, ChatPanel m.fl.) |
|
|
│ │ └── styles.css # Globala stilar |
|
|
│ └── vite.config.ts |
|
|
├── scripts/ |
|
|
│ ├── ingest_arguments.py # Exempel: läser in data till ArangoDB |
|
|
│ └── build_embeddings.py # Skapar embeddings och för in i Chroma |
|
|
├── _arango/ |
|
|
│ ├── _arango.py # Wrapper kring python-arango (förbättrad initiering) |
|
|
│ ├── queries/ # Samling av AQL/ArangoSearch-frågor |
|
|
│ └── helpers.py # Hjälpfunktioner för AQL-byggande |
|
|
├── _chromadb/ |
|
|
│ ├── client.py # Instansierar ChromaDB-klient |
|
|
│ ├── collections.py # Hanterar samlingar (persistens, insättning, sök) |
|
|
│ └── embeddings.py # Hjälp för integrering mot embeddingsgenerator |
|
|
├── _llm/ |
|
|
│ └── llm.py # LLM-wrapper (OpenAI API-kompatibelt gränssnitt) |
|
|
└── README.md |
|
|
``` |
|
|
|
|
|
> **Tips:** Utforska `_arango`, `_chromadb` och `_llm` för att förstå hur databaskopplingar och LLM-anrop är kapslade. |
|
|
|
|
|
--- |
|
|
|
|
|
## 🔧 Backend-arkitektur |
|
|
|
|
|
### `backend/app.py` |
|
|
|
|
|
- Skapar FastAPI-instansen och exponerar endpoints under `/api`. |
|
|
- Registrerar t.ex. `search_router`, `chat_router`, `meta_router`. |
|
|
- Konfigurerar CORS så att frontenden kan nå backenden under utveckling och produktion. |
|
|
|
|
|
### `backend/search.py` |
|
|
|
|
|
- Innehåller klassen **`SearchService`**. |
|
|
- `SearchService` använder `arango_client` och `_arango`-hjälpare för att köra ArangoSearch-frågor. |
|
|
- Vanliga metoder (namn kan skilja något, läs filen för exakt signatur): |
|
|
- `search(...)`: bygger en ArangoSearch AQL-query med filter (parti, år, talare) samt scoring via `BM25()` eller `TFIDF()`. |
|
|
- `get_metadata(...)`: hämtar distinkta värden till frontendens filter. |
|
|
- `get_document_by_id(...)`: används för detaljerad visning/snippets. |
|
|
|
|
|
### `backend/chat.py` |
|
|
|
|
|
- Hanterar endpointen `POST /api/chat`. |
|
|
- Steg i chatthanteringen: |
|
|
1. Plockar senaste användarfrågan. |
|
|
2. Anropar ChromaDB (via `_chromadb`) för semantisk återhämtning. |
|
|
3. Kompletterar kontext med Arango-sökresultat om relevant. |
|
|
4. Bygger prompt och anropar `_llm.LLM`. |
|
|
5. Returnerar AI-svaret plus metadata (t.ex. vilka dokument som användes). |
|
|
- Har ofta även `POST /api/vector-search` som låter frontenden testa Chroma-resultat separat. |
|
|
|
|
|
### `backend/vector.py` (eller motsvarande modul) |
|
|
|
|
|
- Innehåller hjälpfunktioner för att: |
|
|
- Normalisera text. |
|
|
- Skicka query till ChromaDB och hämta top-k embeddings-matchningar. |
|
|
- Eventuellt uppdatera/återskapa Chroma-samlingar. |
|
|
|
|
|
--- |
|
|
|
|
|
## ⚛️ Frontend-arkitektur |
|
|
|
|
|
- **`App.tsx`**: växlar mellan sök- och chat-läge (t.ex. via `const [mode, setMode] = useState<'search' | 'chat'>('search')`). |
|
|
- **`api.ts`**: axios-klient som exporterar: |
|
|
- `searchTalks(params)` → `POST /api/search`. |
|
|
- `chat(messages, strategy)` → `POST /api/chat`. |
|
|
- `vectorSearch(query)` → `POST /api/vector-search`. |
|
|
- `getMeta()` → `GET /api/meta`. |
|
|
- **`components/SearchPanel.tsx`**: |
|
|
- Kontrollerade inputs för fritext, år, parti m.m. |
|
|
- Använder `react-query` (`useMutation/useQuery`) för att trigga backend-anrop. |
|
|
- Visar resultat i `ResultsTable.tsx`. |
|
|
- **`components/ChatPanel.tsx`**: |
|
|
- Håller konversation i state (lista av `{ role, content }`). |
|
|
- Skickar hela historiken till chat-endpointen. |
|
|
- Renderar streamen av svar. |
|
|
- **`types.ts`**: |
|
|
- Speglar backendens Pydantic-scheman med TypeScript-interfaces (`SearchRequest`, `SearchResponse`, `TalkResult`, `ChatRequest`, etc.). |
|
|
- Bra översättning mellan Python-modeller och TypeScript. |
|
|
|
|
|
--- |
|
|
|
|
|
## 🔄 Dataflöden |
|
|
|
|
|
### Sökning via ArangoSearch |
|
|
|
|
|
1. **Frontend (`SearchPanel.tsx`)** samlar användarens filter och anropar `searchTalks` i `frontend/src/api.ts`. |
|
|
2. **FastAPI (`backend/search.py`)** tar emot POST `/api/search`, instansierar `SearchService` och skickar vidare parametrar. |
|
|
3. **`SearchService.search`** bygger en AQL-fråga mot ArangoSearch-vyn (t.ex. `talks_view`) där filter och scoring läggs till. Se `_arango/queries/search_view.py` för den faktiska queryn. |
|
|
```aql |
|
|
FOR doc IN talks_view |
|
|
SEARCH ANALYZER(doc.anforande_text, "text_sv") %PHRASE(@query)% |
|
|
FILTER (@party == null OR doc.party == @party) |
|
|
FILTER (@year == null OR doc.year == @year) |
|
|
SORT BM25(doc) DESC |
|
|
LIMIT @offset, @limit |
|
|
RETURN MERGE(doc, { snippet: doc.preview }) |
|
|
``` |
|
|
4. **ArangoDB** returnerar matchande dokument. Backend formaterar svaren (färger, snippet) och skickar JSON tillbaka. |
|
|
5. **Frontend** renderar resultatet i `ResultsTable.tsx`, uppdaterar statistikpaneler och hanterar paginering. |
|
|
|
|
|
### Chattflöde med LLM och Chroma |
|
|
|
|
|
1. **Frontend (`ChatPanel.tsx`)** skickar konversationen till `POST /api/chat`. |
|
|
2. **`backend/chat.py`**: |
|
|
- Extraherar senaste användarfrågan. |
|
|
- Hämtar relevanta chunkar via Chroma (`_chromadb.collections.get_collection(...).query(...)`). |
|
|
- Optionellt kompletterar kontexten med `SearchService.search` för ArangoSearch-träffar. |
|
|
- Bygger prompten, anropar `_llm.LLM(model="vllm")` och returnerar svaret. |
|
|
3. **Frontend** lägger till AI-svaret i konversationen och visar källor/metadata för transparens. |
|
|
|
|
|
--- |
|
|
|
|
|
## 🧩 Viktiga Python- och TypeScript-komponenter |
|
|
|
|
|
| Fil | Funktion | |
|
|
|-----|----------| |
|
|
| `arango_client.py` | Initierar Arango-klienten med anslutningsuppgifter från miljön. | |
|
|
| `_arango/_arango.py` | Wrapper som kapslar uppkoppling och hjälpfunktioner mot ArangoDB och views. | |
|
|
| `_arango/queries/*` | Samling återskapbara AQL-snippets för sök, metadata och detaljhämtning. | |
|
|
| `_chromadb/client.py` | Skapar ChromaDB-klienten (lokal DuckDB + Parquet som standard). | |
|
|
| `_chromadb/collections.py` | Hjälper till att skapa/hämta Chroma-samlingar, lägga in chunkar och göra `query`. | |
|
|
| `_llm/llm.py` | OpenAI-kompatibel klient. Använd `LLM(model='vllm')` och `chat(...)` för generering. | |
|
|
| `backend/search.py` | Innehåller `SearchService` och API-routes för sök, bygger AQL mot ArangoSearch. | |
|
|
| `backend/chat.py` | Chat- och vector-endpoints. Kopplar ihop Chroma-resultat med LLM-svar. | |
|
|
| `frontend/src/api.ts` | Axios-klient för att prata med backenden (`searchTalks`, `chat`, `vectorSearch`, `getMeta`). | |
|
|
| `frontend/src/components/*` | UI-komponenter i React/TypeScript för sök, resultat, chat, statistik. | |
|
|
|
|
|
--- |
|
|
|
|
|
## 🗄️ ArangoDB och ArangoSearch |
|
|
|
|
|
- **Kollektioner**: rådata om tal, talare och debatter lagras i Arango-kollektioner. |
|
|
- **Views**: exempelvis `talks_view` indexerar textfält med analysatorn `text_sv`. Alla fritextfrågor går via vyn. |
|
|
- **AQL-byggstenar**: |
|
|
- `FOR doc IN talks_view` |
|
|
- `SEARCH` med `ANALYZER`, `PHRASE` eller token-matchning. |
|
|
- `FILTER` för parti, år, kategori och talare. |
|
|
- `SORT BM25(doc)` eller `SORT doc.datum` beroende på begärd sortering. |
|
|
- `LIMIT @offset, @limit` för paginering. |
|
|
- **Metadata**: hämtas via dedikerade queries i `_arango/queries/meta.py` (t.ex. `COLLECT party = doc.party RETURN DISTINCT party`). |
|
|
|
|
|
--- |
|
|
|
|
|
## 🧠 ChromaDB för vektorsök |
|
|
|
|
|
- Embeddings genereras via `scripts/build_embeddings.py` som: |
|
|
1. Läser dokument från Arango via `arango_client`. |
|
|
2. Delar text i chunkar (`_chromadb/embeddings.py`). |
|
|
3. Beräknar embeddings (vanligen med `_llm` eller annan embeddingsgenerator). |
|
|
4. Sparar chunkar + metadata i en Chroma-samling. |
|
|
- Chatten hämtar liknande chunkar med `collection.query(query_texts=[...], n_results=top_k)` och använder resultaten i prompten. |
|
|
|
|
|
--- |
|
|
|
|
|
## 🔧 Utvecklingsflöde |
|
|
|
|
|
| Steg | Befallning | Kommentar | |
|
|
|------|------------|-----------| |
|
|
| Starta backend i utvecklingsläge | `uvicorn backend.app:app --reload --port 8000` | Kör i projektroten. | |
|
|
| Starta frontend i dev-läge | `cd frontend && npm run dev` | Öppna http://localhost:5173. | |
|
|
| Bygg frontend för produktion | `make frontend` | Skapar `frontend/dist`. | |
|
|
| Ladda om nginx (om du serverar statiskt) | `sudo systemctl reload nginx` | Efter ny frontend-build. | |
|
|
| Kör embeddings-script | `python -m scripts.build_embeddings --limit 500` | Uppdaterar Chroma-samlingen. | |
|
|
| Kör Arango-inmatning | `python -m scripts.ingest_arguments` | Läser in tal/metadata i Arango. | |
|
|
|
|
|
--- |
|
|
|
|
|
## 🧪 Felsökning och tips |
|
|
|
|
|
- **Arango-anslutning**: kör `python arango_client.py` eller `arangosh --server.endpoint tcp://127.0.0.1:8529`. |
|
|
- **Kontrollera AQL**: använd ArangoDB Web UI eller `arangosh` för att testa samma query som `SearchService` byggde. |
|
|
- **Chroma-status**: kontrollera `persist_directory` i `_chromadb/client.py`; ta bort katalogen om du behöver återskapa samlingen. |
|
|
- **Embeddings**: kör `python -m scripts.build_embeddings --limit 20` för att uppdatera Chroma när nytt material importeras. |
|
|
- **LLM-svar**: logga prompt och kontext i `backend/chat.py` om svaren känns irrelevanta. |
|
|
- **React/TypeScript**: använd `useQuery` för GET, `useMutation` för POST och typsätt API-kontrakt via `frontend/src/types.ts`. |
|
|
|
|
|
--- |
|
|
|
|
|
## 🤖 LLM-integration |
|
|
|
|
|
- `_llm/llm.py` är huvudgränssnittet. Grundmönster: |
|
|
|
|
|
```python |
|
|
from _llm import LLM |
|
|
|
|
|
def generate_answer(prompt: str) -> str: |
|
|
llm = LLM(model="vllm") |
|
|
messages = [ |
|
|
{"role": "system", "content": "Du är en assistent som förklarar riksdagsdebatter sakligt."}, |
|
|
{"role": "user", "content": prompt}, |
|
|
] |
|
|
response = llm.chat(messages=messages) |
|
|
return response |
|
|
``` |
|
|
|
|
|
- Miljövariabler (`CHAT_MODEL`, `LLM_BASE_URL`, `LLM_API_KEY`) styr målmodell och endpoint. |
|
|
- Chat-endpointen täcker redan vanlig användning. För experiment: återanvänd samma mönster men bygg tydlig prompt av kontexten du hämtar. |
|
|
|
|
|
> Undvik onödiga `try/except`. Fel bubbla upp så att du ser den faktiska orsaken (t.ex. nätverksproblem, fel API-nyckel). |
|
|
|
|
|
|