fix: make docker compose build and start the API

The image context was backend/, so COPY frontend/ failed. Build from the
repo root, place Python modules on the import path, and pin packages that
exist on PyPI. The web process no longer loads the LLM at startup.
This commit is contained in:
lgcosta committed 2026-11-01 12:00:08 -03:00
1 parent 67739dcf7e
commit 7af7ba530d
19 files changed
+342 -250

No files matched your search

+8
View File
@@ -0,0 +1,8 @@
.git
.superpowers
PDF
**/*.gguf
**/__pycache__
**/.venv
**/.env
models
+5 -5
View File
@@ -174,7 +174,7 @@ docker-compose up -d
After starting the services, index the 6 existing books:
```bash
docker-compose run --rm web python backend/scripts/index_books.py
docker compose run --rm web python scripts/index_books.py
```
This will:
@@ -443,10 +443,10 @@ ls -lh PDF/
```bash
# Método 1: Via container
docker-compose run --rm web python backend/scripts/index_books.py
docker compose run --rm web python scripts/index_books.py
# Método 2: Com limite de logs
docker-compose run --rm web python backend/scripts/index_books.py 2>&1 | tee index.log
docker compose run --rm web python scripts/index_books.py 2>&1 | tee index.log
```
### 3. Monitorar Progresso
@@ -485,7 +485,7 @@ rm -rf backend/index/*
rm backend/schedules.json
# Reindexar
docker-compose run --rm web python backend/scripts/index_books.py
docker compose run --rm web python scripts/index_books.py
```
## 🎮 Uso da Aplicação
@@ -750,7 +750,7 @@ ls -lh PDF/
chmod -R 755 PDF/
# Reindexar
docker-compose run --rm web python backend/scripts/index_books.py 2>&1 | tee reindex.log
docker compose run --rm web python scripts/index_books.py 2>&1 | tee reindex.log
```
### Comandos de Diagnóstico
+1 -1
View File
@@ -671,7 +671,7 @@ docker-compose build
docker-compose up -d
# Indexar livros
docker-compose run --rm web python backend/scripts/index_books.py
docker compose run --rm web python scripts/index_books.py
```
### Produção - Single Server
+1 -1
View File
@@ -185,7 +185,7 @@ docker-compose ps
### 5. Indexar os Livros
```bash
docker-compose run --rm web python backend/scripts/index_books.py
docker compose run --rm web python scripts/index_books.py
```
Isso irá:
+10 -15
View File
@@ -2,32 +2,27 @@ FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y \
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
&& rm -rf /var/lib/apt/lists/*
# Create directories
RUN mkdir -p /app/uploads /app/index /app/models
RUN mkdir -p /app/uploads /app/index /app/models /app/books
# Copy requirements
COPY requirements.txt .
# Install Python dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Set environment variables
ENV GPT4ALL_MODEL=llama-3.2-3b-instruct-q4_0.gguf
ENV PYTHONPATH=/app
ENV GPT4ALL_MODEL=/app/models/llama-3.2-3b-instruct-q4_0.gguf
ENV UPLOAD_DIR=/app/uploads
ENV BOOKS_DIR=/app/books
ENV INDEX_DIR=/app/index
ENV SCHEDULES_PATH=/app/schedules.json
# Copy application
COPY app/ ./app/
COPY frontend/ ./frontend/
COPY backend/app ./app
COPY backend/templates ./templates
COPY backend/scripts ./scripts
COPY frontend ./frontend
# Expose ports
EXPOSE 8000
# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
+111 -101
View File
@@ -1,24 +1,37 @@
from fastapi import FastAPI, File, UploadFile, HTTPException
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from app.services.rag_service import RAGService
from app.services.bot_service import BotService
from app.utils.schedule_manager import ScheduleManager
from app.models import ChatRequest, ChatResponse, BookInfo, UploadResponse, ScheduleInfo, ScheduleRequest
import os
import json
import os
from typing import List
# Initialize services
qdrant_service = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
rag_service = RAGService(qdrant_service, model_name=os.getenv("GPT4ALL_MODEL", "llama-3.2-3b-instruct-q4_0.gguf"))
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from app.models import (
BookInfo,
ChatRequest,
ChatResponse,
ScheduleInfo,
ScheduleRequest,
UploadResponse,
)
from app.services.bot_service import BotService
from app.services.rag_service import QdrantService, RAGService
from app.utils.schedule_manager import ScheduleManager
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
SCHEDULES_PATH = os.getenv("SCHEDULES_PATH", "/app/schedules.json")
UPLOAD_DIR = os.getenv("UPLOAD_DIR", "/app/uploads")
QDRANT_URL = os.getenv("QDRANT_URL", "http://qdrant:6333")
MODEL_PATH = os.getenv("GPT4ALL_MODEL", "/app/models/llama-3.2-3b-instruct-q4_0.gguf")
qdrant_service: QdrantService | None = None
rag_service: RAGService | None = None
bot_service = BotService(os.getenv("TELEGRAM_BOT_TOKEN", ""), os.getenv("TELEGRAM_CHAT_ID", ""))
schedule_manager = ScheduleManager(os.getenv("SCHEDULES_PATH", "/app/schedules.json"))
schedule_manager = ScheduleManager(SCHEDULES_PATH)
app = FastAPI(title="Espiritismo RAG API")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
@@ -27,128 +40,125 @@ app.add_middleware(
allow_headers=["*"],
)
# Mount static files
app.mount("/static", StaticFiles(directory="../frontend/static"), name="static")
app.mount(
"/static",
StaticFiles(directory=os.path.join(BASE_DIR, "frontend", "static")),
name="static",
)
def get_qdrant() -> QdrantService:
global qdrant_service
if qdrant_service is None:
qdrant_service = QdrantService(QDRANT_URL)
return qdrant_service
def get_rag() -> RAGService:
global rag_service
if rag_service is None:
rag_service = RAGService(get_qdrant(), model_name=MODEL_PATH)
return rag_service
# Load existing schedules from file
def load_schedules():
if os.path.exists("/app/schedules.json"):
with open("/app/schedules.json", "r") as f:
return json.load(f)
if os.path.isfile(SCHEDULES_PATH):
with open(SCHEDULES_PATH, "r", encoding="utf-8") as handle:
data = json.load(handle)
return data if isinstance(data, list) else []
return []
def save_schedules(schedules: list):
parent = os.path.dirname(SCHEDULES_PATH)
if parent:
os.makedirs(parent, exist_ok=True)
with open(SCHEDULES_PATH, "w", encoding="utf-8") as handle:
json.dump(schedules, handle, indent=2)
@app.get("/health")
async def health():
return {"status": "ok"}
@app.get("/", response_class=HTMLResponse)
async def index():
"""Serve the index page."""
with open("../backend/templates/index.html", "r") as f:
return f.read()
template = os.path.join(BASE_DIR, "templates", "index.html")
with open(template, "r", encoding="utf-8") as handle:
return handle.read()
@app.get("/api/books", response_model=List[BookInfo])
async def get_books():
"""Get list of books in the collection."""
books = qdrant_service.get_books()
books = get_qdrant().get_books()
return [{"title": book, "source": book} for book in books]
@app.post("/api/chat", response_model=ChatResponse)
async def chat(request: ChatRequest):
"""Query the RAG system."""
try:
result = rag_service.query(request.message, request.top_k)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
return get_rag().query(request.message, request.top_k)
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
@app.post("/api/upload", response_model=UploadResponse)
async def upload_pdf(file: UploadFile = File(...)):
"""Upload a new PDF file."""
upload_dir = "/app/uploads"
if not file.filename.endswith('.pdf'):
if not file.filename or not file.filename.endswith(".pdf"):
raise HTTPException(status_code=400, detail="Only PDF files are allowed")
file_path = os.path.join(upload_dir, file.filename)
os.makedirs(UPLOAD_DIR, exist_ok=True)
file_path = os.path.join(UPLOAD_DIR, os.path.basename(file.filename))
try:
with open(file_path, "wb") as f:
content = await file.read()
f.write(content)
content = await file.read()
with open(file_path, "wb") as handle:
handle.write(content)
return UploadResponse(message="File uploaded successfully", filename=file.filename)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc)) from exc
@app.get("/api/schedules", response_model=List[ScheduleInfo])
async def get_schedules():
"""Get all schedules."""
schedules = load_schedules()
return [ScheduleInfo(**s) for s in schedules]
return [ScheduleInfo(**item) for item in load_schedules()]
@app.post("/api/schedules", response_model=ScheduleInfo)
async def create_schedule(request: ScheduleRequest):
"""Create a new schedule."""
schedules = load_schedules()
new_schedule = {
"id": len(schedules) + 1,
"time": request.time,
"book": request.book,
"enabled": request.enabled,
}
schedules.append(new_schedule)
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
# Start celery beat if needed
new_schedule = schedule_manager.add_schedule(request.model_dump())
if not any(item.get("id") == new_schedule["id"] for item in schedules):
schedules.append(new_schedule)
save_schedules(schedules)
try:
bot_service.add_schedule(new_schedule)
except Exception as e:
print(f"Warning: Could not add schedule to bot: {e}")
except Exception as exc:
print(f"Warning: Could not add schedule to bot: {exc}")
return ScheduleInfo(**new_schedule)
@app.put("/api/schedules/{schedule_id}", response_model=ScheduleInfo)
async def update_schedule(schedule_id: int, request: ScheduleRequest):
"""Update an existing schedule."""
schedules = load_schedules()
updated = schedule_manager.update_schedule(schedule_id, request.model_dump())
if updated is None:
raise HTTPException(status_code=404, detail="Schedule not found")
save_schedules(schedule_manager.list_schedules())
try:
bot_service.update_schedule(updated)
except Exception as exc:
print(f"Warning: Could not update schedule in bot: {exc}")
return ScheduleInfo(**updated)
for i, s in enumerate(schedules):
if s["id"] == schedule_id:
schedules[i] = {
"id": schedule_id,
"time": request.time,
"book": request.book,
"enabled": request.enabled,
}
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
try:
bot_service.update_schedule(schedules[i])
except Exception as e:
print(f"Warning: Could not update schedule in bot: {e}")
return ScheduleInfo(**schedules[i])
raise HTTPException(status_code=404, detail="Schedule not found")
@app.delete("/api/schedules/{schedule_id}")
async def delete_schedule(schedule_id: int):
"""Delete a schedule."""
schedules = load_schedules()
for i, s in enumerate(schedules):
if s["id"] == schedule_id:
deleted = schedules.pop(i)
with open("/app/schedules.json", "w") as f:
json.dump(schedules, f, indent=2)
try:
bot_service.remove_schedule(deleted)
except Exception as e:
print(f"Warning: Could not remove schedule from bot: {e}")
return {"message": "Schedule deleted"}
raise HTTPException(status_code=404, detail="Schedule not found")
current = schedule_manager.get_schedule(schedule_id)
if current is None or not schedule_manager.delete_schedule(schedule_id):
raise HTTPException(status_code=404, detail="Schedule not found")
save_schedules(schedule_manager.list_schedules())
try:
bot_service.remove_schedule(current)
except Exception as exc:
print(f"Warning: Could not remove schedule from bot: {exc}")
return {"message": "Schedule deleted"}
-31
View File
@@ -1,31 +0,0 @@
from pydantic import BaseModel
from typing import Optional, List, Dict
class ChatRequest(BaseModel):
message: str
top_k: int = 3
class ChatResponse(BaseModel):
query: str
response: str
sources: List[Dict]
class BookInfo(BaseModel):
title: str
source: str
class UploadResponse(BaseModel):
message: str
filename: str
class ScheduleInfo(BaseModel):
id: int
time: str
book: Optional[str]
enabled: bool
next_run: Optional[str] = None
class ScheduleRequest(BaseModel):
time: str
book: Optional[str] = None
enabled: bool = True
+38 -1
View File
@@ -1 +1,38 @@
# Pydantic models package
from typing import Dict, List, Optional
from pydantic import BaseModel
class ChatRequest(BaseModel):
message: str
top_k: int = 3
class ChatResponse(BaseModel):
query: str
response: str
sources: List[Dict]
class BookInfo(BaseModel):
title: str
source: str
class UploadResponse(BaseModel):
message: str
filename: str
class ScheduleInfo(BaseModel):
id: int
time: str
book: Optional[str] = None
enabled: bool
next_run: Optional[str] = None
class ScheduleRequest(BaseModel):
time: str
book: Optional[str] = None
enabled: bool = True
@@ -1,25 +1,16 @@
import telebot
from telebot import types
import os
import json
from datetime import datetime, timedelta
from typing import List, Dict, Optional
from celery import Celery
import os
import random
from typing import Dict, List
celery = Celery(
'tasks',
broker=os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0"),
backend=os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/0"),
)
import telebot
class BotService:
def __init__(self, token: str, chat_id: str):
self.bot = telebot.TeleBot(token)
self.chat_id = chat_id
# Register handlers
self._register_handlers()
self.bot = telebot.TeleBot(token) if token else None
if self.bot is not None:
self._register_handlers()
# Store pending schedules
self.pending_schedules: List[Dict] = []
@@ -81,11 +72,13 @@ Direcione uma pergunta sobre espiritismo para eu responder!
def _query_rag_service(self, question: str, chat_id: int):
"""Query RAG service and send response."""
try:
from app.services.rag_service import RAGService
from app.services.qdrant_service import QdrantService
from app.services.rag_service import QdrantService, RAGService
qdrant = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
rag = RAGService(qdrant, model_name=os.getenv("GPT4ALL_MODEL", "llama-3.2-3b-instruct-q4_0.gguf"))
rag = RAGService(
qdrant,
model_name=os.getenv("GPT4ALL_MODEL", "/app/models/llama-3.2-3b-instruct-q4_0.gguf"),
)
result = rag.query(question, top_k=3)
@@ -160,14 +153,23 @@ Direcione uma pergunta sobre espiritismo para eu responder!
def start_polling(self):
"""Start the bot polling."""
if self.bot is None:
print("TELEGRAM_BOT_TOKEN ausente; polling não iniciado")
return
print("Starting Telegram bot polling...")
self.bot.infinity_polling(timeout=10, long_polling_timeout=5)
@celery.task
def send_scheduled_message(self, schedule: Dict):
"""Send scheduled message to Telegram."""
try:
response = rag.query(schedule.get('book', ''), top_k=3)
from app.services.rag_service import QdrantService, RAGService
qdrant = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
rag = RAGService(
qdrant,
model_name=os.getenv("GPT4ALL_MODEL", "/app/models/llama-3.2-3b-instruct-q4_0.gguf"),
)
response = rag.query(schedule.get('book') or 'espiritismo', top_k=3)
message = f"""
⏰ Mensagem Agendada - {schedule.get('book', 'Todos os livros')}
@@ -176,11 +178,13 @@ Direcione uma pergunta sobre espiritismo para eu responder!
📚 Fontes:
{self._format_sources(response['sources'])}
"""
if self.bot is None:
print("TELEGRAM_BOT_TOKEN ausente; mensagem agendada não enviada")
return
self.bot.send_message(self.chat_id, message)
except Exception as e:
print(f"Error sending scheduled message: {e}")
@celery.task
def generate_daily_content(self):
"""Generate daily content for all books."""
books = [
@@ -204,4 +208,7 @@ Direcione uma pergunta sobre espiritismo para eu responder!
Este conteúdo foi gerado automaticamente pelo Sistema RAG Espiritismo.
"""
if self.bot is None:
print("TELEGRAM_BOT_TOKEN ausente; conteúdo diário não enviado")
return
self.bot.send_message(self.chat_id, message)
@@ -2,7 +2,7 @@ from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
import os
from typing import List, Dict
from .embeddings import EmbeddingService
from app.utils.embeddings import EmbeddingService
class PDFProcessor:
def __init__(self, embedding_service: EmbeddingService):
@@ -4,6 +4,7 @@ import numpy as np
from typing import List, Dict, Optional
import os
import json
import time
class QdrantService:
def __init__(self, url: str, collection_name: str = "espiritismo_books"):
@@ -15,7 +16,17 @@ class QdrantService:
def _ensure_collection(self):
"""Create collection if it doesn't exist."""
collections = self.client.get_collections().collections
last_error = None
for attempt in range(15):
try:
collections = self.client.get_collections().collections
break
except Exception as exc:
last_error = exc
print(f"Qdrant indisponível ({attempt + 1}/15): {exc}")
time.sleep(2)
else:
raise RuntimeError(f"Não foi possível conectar ao Qdrant: {last_error}")
collection_exists = any(
c.name == self.collection_name for c in collections
@@ -39,7 +50,7 @@ class QdrantService:
for i, chunk in enumerate(chunks):
point = PointStruct(
id=i,
id=chunk.get("id") or i,
vector=chunk["embedding"],
payload={
"content": chunk["content"],
@@ -55,8 +66,10 @@ class QdrantService:
)
print(f"Added {len(points)} chunks to Qdrant")
def search(self, query_embedding: List[float], top_k: int = 3) -> List[Dict]:
def search(self, query_embedding, top_k: int = 3) -> List[Dict]:
"""Search for similar chunks."""
if hasattr(query_embedding, "tolist"):
query_embedding = query_embedding.tolist()
results = self.client.search(
collection_name=self.collection_name,
query_vector=query_embedding,
@@ -100,11 +113,26 @@ class RAGService:
def __init__(self, qdrant_service: QdrantService, model_name: str = "llama-3.2-3b-instruct-q4_0.gguf"):
self.qdrant = qdrant_service
self.model_name = model_name
self.llm = None
# Setup GPT4All
from gpt4all import GPT4All
def _get_llm(self):
if self.llm is None:
if not os.path.isfile(self.model_name):
raise FileNotFoundError(
f"Modelo não encontrado em {self.model_name}. "
"Baixe llama-3.2-3b-instruct-q4_0.gguf para backend/models/."
)
from gpt4all import GPT4All
self.llm = GPT4All(model_name=model_name, device="cpu")
model_dir = os.path.dirname(self.model_name) or "."
model_file = os.path.basename(self.model_name)
self.llm = GPT4All(
model_name=model_file,
model_path=model_dir,
device="cpu",
allow_download=False,
)
return self.llm
def generate_response(self, query: str, context: List[str], top_k: int = 3) -> str:
"""Generate a response based on the query and context."""
@@ -121,11 +149,8 @@ Instruções:
- Quando citar passagens, mencione o livro e capítulo (se disponível)
- Se não souber a resposta, diga que não encontrou na base de dados"""
response = self.llm.generate(
system_prompt,
query,
max_tokens=500,
)
prompt = f"{system_prompt}\n\nPergunta: {query}\nResposta:"
response = self._get_llm().generate(prompt, max_tokens=500)
return response
@@ -149,5 +174,8 @@ Instruções:
"query": query,
"response": response,
"context": context,
"sources": [chunk["metadata"] for chunk in chunks],
"sources": [
{"metadata": chunk["metadata"], "score": chunk["score"]}
for chunk in chunks
],
}
@@ -31,17 +31,19 @@ def save_schedules(schedules: List[Dict]):
json.dump(schedules, f, indent=2)
@celery.task
def send_scheduled_message(schedule_id: int):
"""Send scheduled message to Telegram."""
def send_scheduled_message(schedule_id: int = None):
"""Send one schedule, or every enabled schedule when id is omitted."""
schedules = load_schedules()
schedule = next((s for s in schedules if s['id'] == schedule_id), None)
if schedule_id is None:
selected = [item for item in schedules if item.get("enabled", True)]
else:
selected = [item for item in schedules if item.get("id") == schedule_id and item.get("enabled", True)]
if not schedule:
if not selected:
print(f"Schedule {schedule_id} not found")
return
if schedule.get('enabled', True):
# Send to bot service
for schedule in selected:
try:
bot_service.send_scheduled_message(schedule)
print(f"Sent scheduled message: {schedule['time']}")
File renamed without changes.
File renamed without changes.
View File
Whitespace-only changes.
+3 -2
View File
@@ -2,10 +2,9 @@ fastapi==0.115.0
uvicorn[standard]==0.32.0
pydantic==2.9.2
python-multipart==0.0.9
gpt4all==2.4.6
gpt4all==2.8.2
langchain==0.3.7
langchain-community==0.3.5
langchain-experimental==0.0.73
sentence-transformers==2.7.0
pytelegrambotapi==4.26.0
celery==5.4.0
@@ -14,3 +13,5 @@ python-dotenv==1.0.1
python-dateutil==2.9.0.post0
aiofiles==24.1.0
qdrant-client==1.11.1
pypdf==5.1.0
langchain-text-splitters==0.3.2
+43 -27
View File
@@ -1,51 +1,67 @@
from app.services.pdf_processor import PDFProcessor
from app.utils.embeddings import EmbeddingService
from app.utils.schedule_manager import ScheduleManager
import os
import json
import os
import uuid
from app.services.pdf_processor import PDFProcessor
from app.services.rag_service import QdrantService
from app.utils.embeddings import EmbeddingService
def book_dirs() -> list[str]:
paths = [
os.getenv("BOOKS_DIR", "/app/books"),
os.getenv("UPLOAD_DIR", "/app/uploads"),
]
seen = []
for path in paths:
if path and path not in seen and os.path.isdir(path):
seen.append(path)
return seen
def main():
# Setup services
embedding_service = EmbeddingService()
pdf_processor = PDFProcessor(embedding_service)
qdrant = QdrantService(os.getenv("QDRANT_URL", "http://qdrant:6333"))
# Load books from PDF directory
pdf_dir = "/app/uploads"
books = os.listdir(pdf_dir)
books = [f for f in books if f.endswith('.pdf')]
books = []
for directory in book_dirs():
for name in sorted(os.listdir(directory)):
if name.lower().endswith(".pdf"):
books.append(os.path.join(directory, name))
print(f"Found {len(books)} books")
os.makedirs(os.getenv("INDEX_DIR", "/app/index"), exist_ok=True)
# Schedule manager
schedule_manager = ScheduleManager("/app/schedules.json")
# Process each book
for book_name in books:
book_path = os.path.join(pdf_dir, book_name)
for book_path in books:
book_name = os.path.basename(book_path)
book_metadata = {
"title": book_name.replace(".pdf", "").replace("-", " ").replace("_", " "),
"author": "Desconhecido",
"type": "book",
}
print(f"Processing: {book_name}")
# Load and split PDF
chunks = pdf_processor.load_and_split(book_path, book_metadata)
# Embed chunks
chunks_with_embeddings = pdf_processor.embed_chunks(chunks)
for chunk in chunks_with_embeddings:
chunk["id"] = str(uuid.uuid4())
# Save chunks to JSON for reference
chunks_file = f"/app/index/{book_name}.json"
os.makedirs("/app/index", exist_ok=True)
qdrant.add_chunks(chunks_with_embeddings)
with open(chunks_file, 'w') as f:
json.dump(chunks_with_embeddings, f, indent=2)
print(f"Saved {len(chunks)} chunks to {chunks_file}")
chunks_file = os.path.join(os.getenv("INDEX_DIR", "/app/index"), f"{book_name}.json")
with open(chunks_file, "w", encoding="utf-8") as handle:
json.dump(
[
{"content": chunk["content"], "metadata": chunk["metadata"]}
for chunk in chunks_with_embeddings
],
handle,
ensure_ascii=False,
)
print(f"Indexed {len(chunks_with_embeddings)} chunks from {book_name}")
print("Indexing complete!")
if __name__ == "__main__":
main()
+1
View File
@@ -0,0 +1 @@
[]
+44 -26
View File
@@ -1,8 +1,6 @@
version: '3.8'
services:
qdrant:
image: qdrant/qdrant:latest
image: docker.io/qdrant/qdrant:latest
container_name: espiritismo-qdrant
ports:
- "6333:6333"
@@ -11,7 +9,7 @@ services:
restart: unless-stopped
redis:
image: redis:7-alpine
image: docker.io/library/redis:7-alpine
container_name: espiritismo-redis
ports:
- "6379:6379"
@@ -20,52 +18,71 @@ services:
restart: unless-stopped
web:
build: ./backend
build:
context: .
dockerfile: backend/Dockerfile
container_name: espiritismo-web
ports:
- "8000:8000"
volumes:
- ./PDF:/app/uploads:ro
- ./backend/schedules.json:/app/schedules.json
- ./PDF:/app/books:ro
- uploads_data:/app/uploads
- ./data/schedules.json:/app/schedules.json
- ./backend/models:/app/models
environment:
- QDRANT_URL=http://qdrant:6333
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
QDRANT_URL: http://qdrant:6333
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
GPT4ALL_MODEL: /app/models/llama-3.2-3b-instruct-q4_0.gguf
BOOKS_DIR: /app/books
UPLOAD_DIR: /app/uploads
SCHEDULES_PATH: /app/schedules.json
depends_on:
- qdrant
- redis
restart: unless-stopped
celery:
build: ./backend
build:
context: .
dockerfile: backend/Dockerfile
container_name: espiritismo-celery
command: celery -A app.tasks.celery_tasks worker --loglevel=info
volumes:
- ./PDF:/app/uploads:ro
- ./backend/schedules.json:/app/schedules.json
- ./PDF:/app/books:ro
- uploads_data:/app/uploads
- ./data/schedules.json:/app/schedules.json
- ./backend/models:/app/models
environment:
- QDRANT_URL=http://qdrant:6333
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
QDRANT_URL: http://qdrant:6333
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
GPT4ALL_MODEL: /app/models/llama-3.2-3b-instruct-q4_0.gguf
BOOKS_DIR: /app/books
UPLOAD_DIR: /app/uploads
SCHEDULES_PATH: /app/schedules.json
depends_on:
- qdrant
- redis
restart: unless-stopped
celery-beat:
build: ./backend
build:
context: .
dockerfile: backend/Dockerfile
command: celery -A app.tasks.celery_tasks beat --loglevel=info
volumes:
- ./backend/schedules.json:/app/schedules.json
- ./data/schedules.json:/app/schedules.json
environment:
- CELERY_BROKER_URL=redis://redis:6379/0
- CELERY_RESULT_BACKEND=redis://redis:6379/0
- TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN}
- TELEGRAM_CHAT_ID=${TELEGRAM_CHAT_ID}
CELERY_BROKER_URL: redis://redis:6379/0
CELERY_RESULT_BACKEND: redis://redis:6379/0
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN:-}
TELEGRAM_CHAT_ID: ${TELEGRAM_CHAT_ID:-}
SCHEDULES_PATH: /app/schedules.json
depends_on:
- redis
restart: unless-stopped
@@ -73,3 +90,4 @@ services:
volumes:
qdrant_data:
redis_data:
uploads_data: