Rag engine - #18
Conversation
- Updated electron package-lock.json - Removed frontend build output files (out/main/index.js, out/preload/index.js) - Removed frontend/src/features/health/HealthDashboard.tsx - Added backend/services/llm_manager/TODO.md
- Added PostgreSQL 16 Alpine Dockerfile with pgvector, pg_trgm, and pg_stat_statements - Created comprehensive init.sql with extension initialization and configuration - Consolidated schema into fiapply.sql with all tables, indexes, and views - Updated docker-compose.yml with PostgreSQL and pgAdmin services - Created database utility script (db_utils.sh) for common operations - Added comprehensive database documentation (README.md) - Updated main README to reflect PostgreSQL usage - Updated Makefile to launch pgAdmin instead of SQLite Web - Updated architecture and tech stack documentation - Removed SQLite references from documentation Database features: - pgvector for 384-dimensional embeddings and semantic search - pg_trgm for fuzzy text search with trigram similarity - pg_stat_statements for query performance monitoring - HNSW indexes for fast approximate nearest neighbor search - GIN indexes for efficient trigram-based text search - Materialized views for statistics - Auto-updating triggers for updated_at timestamps
- Replace SQLite database paths with PostgreSQL connection parameters - Update DATABASE_URL to use postgresql:// connection string format - Update RAG Engine database settings to use PostgreSQL environment variables
- Switch to PostgreSQL 17-alpine3.22 (from 16) - Update pgvector to v0.8.0 for compatibility - Skip LLVM bitcode generation (with_llvm=no) to avoid clang-19 dependency - All extensions (vector, pg_stat_statements, pg_trgm) working correctly - Successfully tested with full schema initialization
- Migrated all custom components from Archive/frontend to frontend/ - Installed and configured ShadCN UI with Tailwind CSS v3 - Migrated UI components: button, card, badge, separator, hover-card - Migrated pages: Dashboard, HealthDashboard, Landing - Added Redux Toolkit store with healthSlice - Configured React Router for navigation - Updated TypeScript paths and build configuration - Created Dockerfile for Electron frontend - Updated docker-compose.yml to reflect new frontend structure - Consolidated electron directory into frontend root - Removed old /electron directory in favor of /frontend as the Electron app
…being used solely for reference. Created test file, basic document upload. will add more sophisticated upload later, probably using minilm for now. we should probably use resume as initial doc score (run on up to 10000), then use cv / essays / career goals with colbert(heay) for doc score 2 (run on up to 100)
There was a problem hiding this comment.
RAG Engine is missing a core library, LangChain, which it should be built on; this will greatly lower development time & increase functionality. It must also receive & handle requests from AI Orchestrator, as well as send requests to LLM Manager for all LLM Interactions.
| import datetime | ||
| from typing import List, Dict, Any, Optional, Tuple | ||
| from dataclasses import dataclass | ||
| from enum import Enum | ||
| import psycopg2 | ||
|
|
||
| import ollama |
There was a problem hiding this comment.
Noticed you don't import gRPC nor LangChain anywhere; RAGEngine is meant to be built using LangChain, & also must invoke gRPC to make calls to LLM Manager. It must also receive requests from ai orchestrator.
LangChain will provide every possible operation & performance enhancement we'd need for RAG Engine. The challenge with this service is to use LangChain to handle requests from ai orchestrator & apply the correct LangChain functions to the request to generate a valuable response, as well as optimizing that process using LangChain's optimizations + supportive caching libraries.
No other service aside from LLM Manager should ever call OpenRouter or Ollama; RAG Engine must make all requests to LLMs by calling LLM Manager.
I'll add that arrow to the architecture diagram.
| ------------ | ||
|
|
||
| CREATE EXTENSION vector; | ||
| CREATE EXTENSION IF NOT EXISTS vector; |
There was a problem hiding this comment.
Remove; pg_vector is already initialized in init.sql along with the other plugins
| ------------ | ||
|
|
||
| CREATE EXTENSION vector; | ||
| CREATE EXTENSION IF NOT EXISTS vector; |
There was a problem hiding this comment.
Remove; pg_vector is already initialized in init.sql along with the other plugins
| updated_at TIMESTAMP DEFAULT NOW() | ||
| ); | ||
|
|
||
| CREATE TABLE IF NOT EXISTS user_documents ( |
There was a problem hiding this comment.
User documents should be placed in a separate model file, & user-document relationships should use a 1-to-many relationship to a general document table. This means users have a column which contains some int or string (hash) identifier corresponding to a document which is located in a separate table in a separate file.
Technically for now since this will be 100% local tho, a document can only ever belong to 1 user; the host user. However since we'll eventually add a cloud hosted option, setting it up to be distributed now will save us time down the line.
…nd matching of scholarships, along with tests
FOOincognita
left a comment
There was a problem hiding this comment.
Left comments in new rag.py file, which shows significant improvement compared to previous versions.
| password = config["password"], | ||
| host = config["host"], | ||
| port = config["port"] | ||
| def embed_pdf(self, pdfFilePath): |
There was a problem hiding this comment.
This comment addresses the entire file:
This looks significantly better. Here are a few candidates for improvements:
- Lazy loading & batch processing - Don't load entire PDFs into memory, & add docs in batches.
- This implementation's missing error handling; there are multiple failure points without recovery methods.
- Implement token-aware chunking rather than character-based.
- Currently can't efficiently filter by user, document type, etc.
- This monolithic class does too many things; refactor for separation of concerns.
- Implement caching, maybe LRU policy.
- Leverage async for non-blocking operations.
- Leverage structured logger in
/backend/shared/utils/logger.py
There was a problem hiding this comment.
I've included an example.py implementation which makes use of each of these improvements for reference (excluding the shared logger). Note that it's not meant to replace yours; it's just a handy reference.
…hing is async now :), will have to centralize from core/rag.py to server.py later once other smaller auxiliary services are made, after vectordbservice is finished, and then subsequently cut into several pieces
| #document embedding services | ||
| async def EmbedScholarshipBatch(self, request: rag_engine_pb2.EmbedScholarshipBatchRequest, | ||
| context: grpc.aio.ServicerContext) -> rag_engine_pb2.EmbedScholarshipBatchResponse: | ||
| print("Handler called!!") |
There was a problem hiding this comment.
Change print() usage to logs.debug() unless you want output in cout rather than cerr; both will appear in terminal.
| service VectorDBService { | ||
| // Initialize vector DB connection or collection | ||
| rpc Init(InitRequest) returns (InitResponse); | ||
|
|
||
| // Embed a batch of scholarships and store them | ||
| rpc EmbedScholarshipBatch(EmbedScholarshipBatchRequest) returns (EmbedScholarshipBatchResponse); | ||
| } | ||
|
|
||
| // ========================================= | ||
| // VectorDBService - Define RPC method requests and response schemas | ||
| // ========================================= | ||
|
|
||
| message InitRequest { | ||
| string connection_string = 1; // e.g. "postgresql://user:pass@localhost/db" | ||
| string collection_name = 2; | ||
| } | ||
|
|
||
| message InitResponse { | ||
| bool success = 1; | ||
| fi.common.v1.Error error = 2; | ||
| } | ||
|
|
||
| message EmbedScholarshipBatchRequest { | ||
| repeated Scholarship scholarships = 1; // Example dataclass converted below | ||
| } | ||
|
|
||
| message EmbedScholarshipBatchResponse { | ||
| int32 total_embedded = 1; | ||
| fi.common.v1.Error error = 2; | ||
| } | ||
|
|
||
| //may change | ||
| message Document { | ||
| string document_id = 1; | ||
| string content = 2; | ||
| google.protobuf.Struct metadata = 3; | ||
| } | ||
|
|
||
| message Scholarship { | ||
| // Unique scholarship identifier | ||
| int32 id = 1; | ||
|
|
||
| // Scholarship name | ||
| string name = 2; | ||
|
|
||
| // Scholarship description or summary | ||
| string description = 3; | ||
|
|
||
| // Minimum GPA required (e.g. 3.5) | ||
| double min_gpa = 4; | ||
|
|
||
| // Eligible majors (e.g. "Computer Science", "Engineering") | ||
| repeated string eligible_majors = 5; | ||
|
|
||
| // Citizenship requirement (optional, can be null) | ||
| string citizenship_required = 6; | ||
|
|
||
| // Location requirement or region (optional) | ||
| string location = 7; | ||
|
|
||
| // Award amount or range, kept as a string for flexibility | ||
| string award_amount = 8; | ||
|
|
||
| // Keywords for semantic search / classification | ||
| repeated string keywords = 9; | ||
| } No newline at end of file |
There was a problem hiding this comment.
Given these message types & service are meant to service database interactions rather than service-to-service communication, we should move it to its own proto file, maybe postgres.proto or something similar. Any other services which speak with the database will share this service to use when communicating with the DB.
| class DocumentType: | ||
| SCHOLARSHIP = "scholarship" | ||
| RESUME = "resume" | ||
| ESSAY = "essay" | ||
| TRANSCRIPT = "transcript" | ||
| FORM_CONTEXT = "form_context" | ||
| USER_PROFILE = "user_profile" |
There was a problem hiding this comment.
May benefit from inheriting from the base enum class:
class DocumentType(Enum):iirc, you should be able to maintain the associated string values, tho they'd also map to an int, making case matching significantly safer/easier (if you plan on using the definitions for case matching later on)
| import signal | ||
| import datetime | ||
| from backend.shared.utils import logs | ||
| from typing import List, Dict, Any, Optional, Tuple |
There was a problem hiding this comment.
While still valid, much of the Typing library is deprecated since Python 3.9+ excluding type Typing.Any. However, the use of the native type hint object is a valid native replacement for Typing.Any, tho contextually more ambiguous.
Conversions:
-
Typing.List$\rightarrow$ list -
Typing.Dict$\rightarrow$ dict -
Typing.Tuple$\rightarrow$ tuple -
Typing.Optional$\rightarrow$ <type> | None-
Example:
Optional[str]$\rightarrow$ str | None
-
Example:
| return rag_engine_pb2.InitResponse(error = e) | ||
|
|
||
| async def __getattr__(self, name): | ||
| print("gRPC called:", name) |
There was a problem hiding this comment.
Change print() usage to logs.debug() unless you want output in cout rather than cerr; both will appear in terminal.
OVERARCHING GOAL:
Support scholarship matching based upon user documents, attributes, and scholarship documents and attributes.
To this end, the following must be created: