# 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).