chore: switch to DeepSeek API + sentence-transformers for free deployment

- Replace OpenAI LLM with DeepSeek (deepseek-chat) via OpenAI-compatible client
- Replace OpenAI embeddings with local sentence-transformers (all-MiniLM-L6-v2)
- Pre-download embedding model during Docker build
- Add .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Power BI Dev
2026-06-04 09:04:21 +07:00
parent f62a711f5d
commit fddd3644ae
7 changed files with 27 additions and 15 deletions
+3 -3
View File
@@ -1,8 +1,8 @@
TELEGRAM_BOT_TOKEN=isi_token_dari_botfather TELEGRAM_BOT_TOKEN=isi_token_dari_botfather
OPENAI_API_KEY=isi_api_key_openai DEEPSEEK_API_KEY=isi_api_key_deepseek
OPENAI_MODEL=gpt-4o DEEPSEEK_MODEL=deepseek-chat
OPENAI_EMBEDDING_MODEL=text-embedding-3-small
ADMIN_TELEGRAM_ID=isi_telegram_id_anda ADMIN_TELEGRAM_ID=isi_telegram_id_anda
DB_PATH=/app/data/second_brain.db DB_PATH=/app/data/second_brain.db
CHROMA_PATH=/app/data/chromadb CHROMA_PATH=/app/data/chromadb
UPLOAD_PATH=/app/data/uploads UPLOAD_PATH=/app/data/uploads
EMBEDDING_MODEL=all-MiniLM-L6-v2
+7
View File
@@ -0,0 +1,7 @@
.env
data/*
!data/.gitkeep
__pycache__/
*.pyc
*.pyo
.DS_Store
+3
View File
@@ -10,6 +10,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
COPY requirements.txt . COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt
# Pre-download embedding model agar tidak diunduh ulang saat runtime
RUN python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"
COPY src/ ./src/ COPY src/ ./src/
CMD ["python", "src/main.py"] CMD ["python", "src/main.py"]
+1
View File
@@ -4,3 +4,4 @@ python-dotenv==1.0.1
chromadb==0.5.3 chromadb==0.5.3
pdfplumber==0.11.0 pdfplumber==0.11.0
python-docx==1.1.2 python-docx==1.1.2
sentence-transformers==3.0.1
+4 -3
View File
@@ -4,10 +4,11 @@ from dotenv import load_dotenv
load_dotenv() load_dotenv()
TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"] TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] DEEPSEEK_API_KEY = os.environ["DEEPSEEK_API_KEY"]
OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") DEEPSEEK_MODEL = os.getenv("DEEPSEEK_MODEL", "deepseek-chat")
OPENAI_EMBEDDING_MODEL = os.getenv("OPENAI_EMBEDDING_MODEL", "text-embedding-3-small") DEEPSEEK_BASE_URL = "https://api.deepseek.com"
ADMIN_TELEGRAM_ID = int(os.environ["ADMIN_TELEGRAM_ID"]) ADMIN_TELEGRAM_ID = int(os.environ["ADMIN_TELEGRAM_ID"])
DB_PATH = os.getenv("DB_PATH", "/app/data/second_brain.db") DB_PATH = os.getenv("DB_PATH", "/app/data/second_brain.db")
CHROMA_PATH = os.getenv("CHROMA_PATH", "/app/data/chromadb") CHROMA_PATH = os.getenv("CHROMA_PATH", "/app/data/chromadb")
UPLOAD_PATH = os.getenv("UPLOAD_PATH", "/app/data/uploads") UPLOAD_PATH = os.getenv("UPLOAD_PATH", "/app/data/uploads")
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "all-MiniLM-L6-v2")
+4 -4
View File
@@ -2,13 +2,13 @@ from pathlib import Path
from telegram import Update from telegram import Update
from telegram.ext import ContextTypes from telegram.ext import ContextTypes
from openai import AsyncOpenAI from openai import AsyncOpenAI
from config import OPENAI_API_KEY, OPENAI_MODEL, ADMIN_TELEGRAM_ID from config import DEEPSEEK_API_KEY, DEEPSEEK_BASE_URL, DEEPSEEK_MODEL, ADMIN_TELEGRAM_ID
import database as db import database as db
import rag import rag
import document as doc_processor import document as doc_processor
from guard import is_academic_topic, REJECT_MESSAGE from guard import is_academic_topic, REJECT_MESSAGE
_client = AsyncOpenAI(api_key=OPENAI_API_KEY) _client = AsyncOpenAI(api_key=DEEPSEEK_API_KEY, base_url=DEEPSEEK_BASE_URL)
ACADEMIC_SYSTEM_PROMPT = ( ACADEMIC_SYSTEM_PROMPT = (
"Anda adalah asisten akademik personal untuk dosen di perguruan tinggi Indonesia. " "Anda adalah asisten akademik personal untuk dosen di perguruan tinggi Indonesia. "
@@ -128,7 +128,7 @@ async def ask(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.chat.send_action("typing") await update.message.chat.send_action("typing")
# Guard: hanya izinkan topik akademik # Guard: hanya izinkan topik akademik
if not await is_academic_topic(question, _client, OPENAI_MODEL): if not await is_academic_topic(question, _client, DEEPSEEK_MODEL):
await update.message.reply_text(REJECT_MESSAGE) await update.message.reply_text(REJECT_MESSAGE)
return return
@@ -154,7 +154,7 @@ async def ask(update: Update, context: ContextTypes.DEFAULT_TYPE):
try: try:
response = await _client.chat.completions.create( response = await _client.chat.completions.create(
model=OPENAI_MODEL, model=DEEPSEEK_MODEL,
messages=messages, messages=messages,
max_tokens=1500, max_tokens=1500,
) )
+5 -5
View File
@@ -1,10 +1,10 @@
import asyncio import asyncio
import chromadb import chromadb
from openai import AsyncOpenAI from sentence_transformers import SentenceTransformer
from config import CHROMA_PATH, OPENAI_API_KEY, OPENAI_EMBEDDING_MODEL from config import CHROMA_PATH, EMBEDDING_MODEL
_client = AsyncOpenAI(api_key=OPENAI_API_KEY)
_chroma = chromadb.PersistentClient(path=CHROMA_PATH) _chroma = chromadb.PersistentClient(path=CHROMA_PATH)
_embedding_model = SentenceTransformer(EMBEDDING_MODEL)
def _get_collection(name: str): def _get_collection(name: str):
@@ -12,8 +12,8 @@ def _get_collection(name: str):
async def _embed(text: str) -> list: async def _embed(text: str) -> list:
resp = await _client.embeddings.create(model=OPENAI_EMBEDDING_MODEL, input=text) embedding = await asyncio.to_thread(_embedding_model.encode, text)
return resp.data[0].embedding return embedding.tolist()
async def add_memory(user_id: int, memory_id: int, content: str, scope: str = "private"): async def add_memory(user_id: int, memory_id: int, content: str, scope: str = "private"):