Spaces:
Sleeping
Sleeping
'changes'
Browse files- app/__pycache__/config.cpython-312.pyc +0 -0
- app/__pycache__/database.cpython-312.pyc +0 -0
- app/__pycache__/main.cpython-312.pyc +0 -0
- app/__pycache__/qdrant_client.cpython-312.pyc +0 -0
- app/config.py +3 -6
- app/qdrant_client.py +2 -9
- app/routes/__pycache__/chat.cpython-312.pyc +0 -0
- app/routes/chat.py +6 -13
- app/schemas/__pycache__/chat.cpython-312.pyc +0 -0
- app/services/__pycache__/embeddings_service.cpython-312.pyc +0 -0
- app/services/__pycache__/openai_service.cpython-312.pyc +0 -0
- app/services/__pycache__/rag_service.cpython-312.pyc +0 -0
- app/services/embeddings_service.py +8 -1
- app/services/openai_service.py +8 -1
- app/services/rag_service.py +8 -5
- scripts/ingest_content.py +5 -14
app/__pycache__/config.cpython-312.pyc
CHANGED
|
Binary files a/app/__pycache__/config.cpython-312.pyc and b/app/__pycache__/config.cpython-312.pyc differ
|
|
|
app/__pycache__/database.cpython-312.pyc
ADDED
|
Binary file (980 Bytes). View file
|
|
|
app/__pycache__/main.cpython-312.pyc
ADDED
|
Binary file (1.62 kB). View file
|
|
|
app/__pycache__/qdrant_client.cpython-312.pyc
ADDED
|
Binary file (1.81 kB). View file
|
|
|
app/config.py
CHANGED
|
@@ -3,18 +3,15 @@ from pydantic_settings import BaseSettings
|
|
| 3 |
|
| 4 |
class Settings(BaseSettings):
|
| 5 |
OPENAI_API_KEY: str
|
| 6 |
-
GEMINI_API_KEY: str
|
| 7 |
-
|
| 8 |
-
AI_PROVIDER: str = "gemini"
|
| 9 |
DATABASE_URL: str = os.getenv("DATABASE_URL", "")
|
| 10 |
NEON_DATABASE_URL: str = os.getenv("NEON_DATABASE_URL", "")
|
| 11 |
QDRANT_URL: str = os.getenv("QDRANT_URL", "http://localhost:6333")
|
| 12 |
QDRANT_API_KEY: str = os.getenv("QDRANT_API_KEY", "")
|
| 13 |
OPENAI_MODEL_CHAT: str = "gpt-4o-mini"
|
| 14 |
OPENAI_MODEL_EMBEDDING: str = "text-embedding-3-small"
|
| 15 |
-
GEMINI_MODEL_CHAT: str = "gemini-2.5-flash"
|
| 16 |
-
GEMINI_MODEL_EMBEDDING: str = "embedding-001"
|
| 17 |
-
GEMINI_OPENAI_COMPATIBLE_BASE_URL: str = "https://generativelanguage.googleapis.com/v1beta/openai/"
|
| 18 |
|
| 19 |
class Config:
|
| 20 |
env_file = ".env"
|
|
|
|
| 3 |
|
| 4 |
class Settings(BaseSettings):
|
| 5 |
OPENAI_API_KEY: str
|
| 6 |
+
GEMINI_API_KEY: str = ""
|
| 7 |
+
GEMINI_OPENAI_COMPATIBLE_BASE_URL: str = "https://generativelanguage.googleapis.com/v1beta"
|
| 8 |
+
AI_PROVIDER: str = "openai" # "openai" or "gemini"
|
| 9 |
DATABASE_URL: str = os.getenv("DATABASE_URL", "")
|
| 10 |
NEON_DATABASE_URL: str = os.getenv("NEON_DATABASE_URL", "")
|
| 11 |
QDRANT_URL: str = os.getenv("QDRANT_URL", "http://localhost:6333")
|
| 12 |
QDRANT_API_KEY: str = os.getenv("QDRANT_API_KEY", "")
|
| 13 |
OPENAI_MODEL_CHAT: str = "gpt-4o-mini"
|
| 14 |
OPENAI_MODEL_EMBEDDING: str = "text-embedding-3-small"
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
class Config:
|
| 17 |
env_file = ".env"
|
app/qdrant_client.py
CHANGED
|
@@ -11,13 +11,6 @@ qdrant_client = QdrantClient(
|
|
| 11 |
|
| 12 |
COLLECTION_NAME = "book_embeddings"
|
| 13 |
|
| 14 |
-
def get_vector_size():
|
| 15 |
-
"""Get the appropriate vector size based on the AI provider"""
|
| 16 |
-
if settings.AI_PROVIDER.lower() == "gemini":
|
| 17 |
-
return 768 # Gemini embedding dimension
|
| 18 |
-
else:
|
| 19 |
-
return 1536 # OpenAI text-embedding-3-small dimension
|
| 20 |
-
|
| 21 |
def init_qdrant_collection():
|
| 22 |
"""Initialize Qdrant collection if it doesn't exist"""
|
| 23 |
try:
|
|
@@ -30,11 +23,11 @@ def init_qdrant_collection():
|
|
| 30 |
qdrant_client.create_collection(
|
| 31 |
collection_name=COLLECTION_NAME,
|
| 32 |
vectors_config=VectorParams(
|
| 33 |
-
size=
|
| 34 |
distance=Distance.COSINE
|
| 35 |
)
|
| 36 |
)
|
| 37 |
-
print(f"✅ Created Qdrant collection: {COLLECTION_NAME}
|
| 38 |
else:
|
| 39 |
print(f"✅ Qdrant collection already exists: {COLLECTION_NAME}")
|
| 40 |
except Exception as e:
|
|
|
|
| 11 |
|
| 12 |
COLLECTION_NAME = "book_embeddings"
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
def init_qdrant_collection():
|
| 15 |
"""Initialize Qdrant collection if it doesn't exist"""
|
| 16 |
try:
|
|
|
|
| 23 |
qdrant_client.create_collection(
|
| 24 |
collection_name=COLLECTION_NAME,
|
| 25 |
vectors_config=VectorParams(
|
| 26 |
+
size=1536, # OpenAI text-embedding-3-small dimension
|
| 27 |
distance=Distance.COSINE
|
| 28 |
)
|
| 29 |
)
|
| 30 |
+
print(f"✅ Created Qdrant collection: {COLLECTION_NAME}")
|
| 31 |
else:
|
| 32 |
print(f"✅ Qdrant collection already exists: {COLLECTION_NAME}")
|
| 33 |
except Exception as e:
|
app/routes/__pycache__/chat.cpython-312.pyc
ADDED
|
Binary file (3.29 kB). View file
|
|
|
app/routes/chat.py
CHANGED
|
@@ -3,9 +3,8 @@ from qdrant_client import QdrantClient
|
|
| 3 |
from app.qdrant_client import get_qdrant_client
|
| 4 |
from app.schemas.chat import ChatRequest, ChatResponse, ChatSelectionRequest
|
| 5 |
from app.services.rag_service import RAGService
|
| 6 |
-
from app.services.embeddings_service import
|
| 7 |
-
from app.services.openai_service import
|
| 8 |
-
from app.config import settings
|
| 9 |
import logging
|
| 10 |
|
| 11 |
logger = logging.getLogger(__name__)
|
|
@@ -15,15 +14,9 @@ router = APIRouter(prefix="/api", tags=["chat"])
|
|
| 15 |
def get_rag_service(
|
| 16 |
qdrant_client: QdrantClient = Depends(get_qdrant_client)
|
| 17 |
):
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
ai_service = GeminiOpenAIService()
|
| 22 |
-
else:
|
| 23 |
-
embeddings_service = EmbeddingsService()
|
| 24 |
-
ai_service = OpenAIService()
|
| 25 |
-
|
| 26 |
-
return RAGService(qdrant_client, embeddings_service, ai_service)
|
| 27 |
|
| 28 |
@router.post("/chat", response_model=ChatResponse)
|
| 29 |
async def chat(
|
|
@@ -34,7 +27,7 @@ async def chat(
|
|
| 34 |
# Retrieve context from vector database
|
| 35 |
context = await rag_service.retrieve_context(request.question, top_k=3)
|
| 36 |
|
| 37 |
-
# Generate response using
|
| 38 |
answer = await rag_service.generate_response(request.question, context)
|
| 39 |
|
| 40 |
# Extract sources from context
|
|
|
|
| 3 |
from app.qdrant_client import get_qdrant_client
|
| 4 |
from app.schemas.chat import ChatRequest, ChatResponse, ChatSelectionRequest
|
| 5 |
from app.services.rag_service import RAGService
|
| 6 |
+
from app.services.embeddings_service import get_embeddings_service
|
| 7 |
+
from app.services.openai_service import get_openai_service
|
|
|
|
| 8 |
import logging
|
| 9 |
|
| 10 |
logger = logging.getLogger(__name__)
|
|
|
|
| 14 |
def get_rag_service(
|
| 15 |
qdrant_client: QdrantClient = Depends(get_qdrant_client)
|
| 16 |
):
|
| 17 |
+
embeddings_service = get_embeddings_service()
|
| 18 |
+
openai_service = get_openai_service()
|
| 19 |
+
return RAGService(qdrant_client, embeddings_service, openai_service)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
@router.post("/chat", response_model=ChatResponse)
|
| 22 |
async def chat(
|
|
|
|
| 27 |
# Retrieve context from vector database
|
| 28 |
context = await rag_service.retrieve_context(request.question, top_k=3)
|
| 29 |
|
| 30 |
+
# Generate response using OpenAI
|
| 31 |
answer = await rag_service.generate_response(request.question, context)
|
| 32 |
|
| 33 |
# Extract sources from context
|
app/schemas/__pycache__/chat.cpython-312.pyc
ADDED
|
Binary file (1.47 kB). View file
|
|
|
app/services/__pycache__/embeddings_service.cpython-312.pyc
ADDED
|
Binary file (2.62 kB). View file
|
|
|
app/services/__pycache__/openai_service.cpython-312.pyc
CHANGED
|
Binary files a/app/services/__pycache__/openai_service.cpython-312.pyc and b/app/services/__pycache__/openai_service.cpython-312.pyc differ
|
|
|
app/services/__pycache__/rag_service.cpython-312.pyc
ADDED
|
Binary file (2.63 kB). View file
|
|
|
app/services/embeddings_service.py
CHANGED
|
@@ -34,4 +34,11 @@ class GeminiEmbeddingsService:
|
|
| 34 |
input=[text],
|
| 35 |
model=self.model
|
| 36 |
)
|
| 37 |
-
return response.data[0].embedding
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
input=[text],
|
| 35 |
model=self.model
|
| 36 |
)
|
| 37 |
+
return response.data[0].embedding
|
| 38 |
+
|
| 39 |
+
# Factory function to get the appropriate embeddings service
|
| 40 |
+
def get_embeddings_service():
|
| 41 |
+
if settings.AI_PROVIDER.lower() == "gemini":
|
| 42 |
+
return GeminiEmbeddingsService()
|
| 43 |
+
else:
|
| 44 |
+
return EmbeddingsService()
|
app/services/openai_service.py
CHANGED
|
@@ -43,4 +43,11 @@ class GeminiOpenAIService:
|
|
| 43 |
model=self.model,
|
| 44 |
messages=messages
|
| 45 |
)
|
| 46 |
-
return response.choices[0].message.content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
model=self.model,
|
| 44 |
messages=messages
|
| 45 |
)
|
| 46 |
+
return response.choices[0].message.content
|
| 47 |
+
|
| 48 |
+
# Factory function to get the appropriate OpenAI service
|
| 49 |
+
def get_openai_service():
|
| 50 |
+
if settings.AI_PROVIDER.lower() == "gemini":
|
| 51 |
+
return GeminiOpenAIService()
|
| 52 |
+
else:
|
| 53 |
+
return OpenAIService()
|
app/services/rag_service.py
CHANGED
|
@@ -3,18 +3,22 @@ from qdrant_client import QdrantClient
|
|
| 3 |
from qdrant_client.models import NamedVector
|
| 4 |
from typing import List
|
| 5 |
|
|
|
|
| 6 |
from app.services.openai_service import OpenAIService, GeminiOpenAIService
|
| 7 |
from app.services.embeddings_service import EmbeddingsService, GeminiEmbeddingsService
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
class RAGService:
|
| 10 |
-
def __init__(self, qdrant_client: QdrantClient, embeddings_service:
|
| 11 |
self.qdrant_client = qdrant_client
|
| 12 |
self.embeddings_service = embeddings_service
|
| 13 |
-
self.
|
| 14 |
self.collection_name = os.getenv("QDRANT_COLLECTION_NAME", "book_embeddings")
|
| 15 |
|
| 16 |
async def retrieve_context(self, query: str, top_k: int = 3) -> List[str]:
|
| 17 |
-
# Handle both OpenAI and Gemini embeddings
|
| 18 |
query_vector = await self.embeddings_service.create_embedding(query)
|
| 19 |
|
| 20 |
search_result = self.qdrant_client.search(
|
|
@@ -33,6 +37,5 @@ class RAGService:
|
|
| 33 |
Question: {query}
|
| 34 |
|
| 35 |
Answer:"""
|
| 36 |
-
|
| 37 |
-
response = await self.ai_service.get_chat_response(full_prompt)
|
| 38 |
return response
|
|
|
|
| 3 |
from qdrant_client.models import NamedVector
|
| 4 |
from typing import List
|
| 5 |
|
| 6 |
+
# We'll use the base classes for type hints
|
| 7 |
from app.services.openai_service import OpenAIService, GeminiOpenAIService
|
| 8 |
from app.services.embeddings_service import EmbeddingsService, GeminiEmbeddingsService
|
| 9 |
|
| 10 |
+
# Type alias for either service
|
| 11 |
+
AIEmbeddingService = EmbeddingsService | GeminiEmbeddingsService
|
| 12 |
+
AIOpenAIService = OpenAIService | GeminiOpenAIService
|
| 13 |
+
|
| 14 |
class RAGService:
|
| 15 |
+
def __init__(self, qdrant_client: QdrantClient, embeddings_service: AIEmbeddingService, openai_service: AIOpenAIService):
|
| 16 |
self.qdrant_client = qdrant_client
|
| 17 |
self.embeddings_service = embeddings_service
|
| 18 |
+
self.openai_service = openai_service
|
| 19 |
self.collection_name = os.getenv("QDRANT_COLLECTION_NAME", "book_embeddings")
|
| 20 |
|
| 21 |
async def retrieve_context(self, query: str, top_k: int = 3) -> List[str]:
|
|
|
|
| 22 |
query_vector = await self.embeddings_service.create_embedding(query)
|
| 23 |
|
| 24 |
search_result = self.qdrant_client.search(
|
|
|
|
| 37 |
Question: {query}
|
| 38 |
|
| 39 |
Answer:"""
|
| 40 |
+
response = await self.openai_service.get_chat_response(full_prompt)
|
|
|
|
| 41 |
return response
|
scripts/ingest_content.py
CHANGED
|
@@ -11,9 +11,8 @@ from dotenv import load_dotenv
|
|
| 11 |
import sys
|
| 12 |
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
| 13 |
|
| 14 |
-
from app.services.embeddings_service import
|
| 15 |
-
from app.qdrant_client import get_qdrant_client
|
| 16 |
-
from app.config import settings
|
| 17 |
|
| 18 |
load_dotenv(dotenv_path=Path(__file__).resolve().parent.parent / ".env")
|
| 19 |
|
|
@@ -36,15 +35,12 @@ def chunk_text(text: str, chunk_size: int = 1000, overlap: int = 200) -> list[st
|
|
| 36 |
async def ingest_content(
|
| 37 |
docs_path: Path,
|
| 38 |
qdrant_client: QdrantClient,
|
| 39 |
-
embeddings_service
|
| 40 |
collection_name: str,
|
| 41 |
):
|
| 42 |
-
# Determine vector size based on the embedding service
|
| 43 |
-
vector_size = get_vector_size()
|
| 44 |
-
|
| 45 |
qdrant_client.recreate_collection(
|
| 46 |
collection_name=collection_name,
|
| 47 |
-
vectors_config=VectorParams(size=
|
| 48 |
)
|
| 49 |
|
| 50 |
points = []
|
|
@@ -96,12 +92,7 @@ if __name__ == "__main__":
|
|
| 96 |
args = parser.parse_args()
|
| 97 |
|
| 98 |
qdrant_client = get_qdrant_client()
|
| 99 |
-
|
| 100 |
-
# Choose the appropriate embedding service based on AI_PROVIDER setting
|
| 101 |
-
if settings.AI_PROVIDER.lower() == "gemini":
|
| 102 |
-
embeddings_service = GeminiEmbeddingsService()
|
| 103 |
-
else:
|
| 104 |
-
embeddings_service = EmbeddingsService()
|
| 105 |
|
| 106 |
# Run the async ingestion
|
| 107 |
import asyncio
|
|
|
|
| 11 |
import sys
|
| 12 |
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
|
| 13 |
|
| 14 |
+
from app.services.embeddings_service import get_embeddings_service
|
| 15 |
+
from app.qdrant_client import get_qdrant_client
|
|
|
|
| 16 |
|
| 17 |
load_dotenv(dotenv_path=Path(__file__).resolve().parent.parent / ".env")
|
| 18 |
|
|
|
|
| 35 |
async def ingest_content(
|
| 36 |
docs_path: Path,
|
| 37 |
qdrant_client: QdrantClient,
|
| 38 |
+
embeddings_service,
|
| 39 |
collection_name: str,
|
| 40 |
):
|
|
|
|
|
|
|
|
|
|
| 41 |
qdrant_client.recreate_collection(
|
| 42 |
collection_name=collection_name,
|
| 43 |
+
vectors_config=VectorParams(size=1536, distance=Distance.COSINE), # OpenAI embeddings size
|
| 44 |
)
|
| 45 |
|
| 46 |
points = []
|
|
|
|
| 92 |
args = parser.parse_args()
|
| 93 |
|
| 94 |
qdrant_client = get_qdrant_client()
|
| 95 |
+
embeddings_service = get_embeddings_service()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
# Run the async ingestion
|
| 98 |
import asyncio
|