מדריך מלא: הקמת AI לקוד על שרת CPU בלבד עם RAG + שמירת שאלות/תשובות ב-MySQL (PHP 8.3 / Python / JS / SQL)
מדריך מלא: הקמת AI לקוד על שרת CPU בלבד עם RAG + שמירת שאלות/תשובות ב-MySQL (PHP 8.3 / Python / JS / SQL)
מטרה: לבנות “עוזר מתכנת” שמבין את הקוד שלך באמת, עונה עם הקשר מהפרויקט (RAG), מציע תיקונים מלאים, ושומר כל שאלה/תשובה במסד נתונים.
הגבלות CPU בלבד: בלי GPU לא מאמנים מודלים ענקיים בפועל. במקום זה משתמשים ב-RAG כדי “להאכיל” את העוזר בקוד אמיתי מתוך הפרויקט. זה נותן תוצאות פרקטיות ומקצועיות במיוחד לקוד.
תוכן עניינים
- דרישות והכנות
- מבנה תיקיות מומלץ
- התקנת llama.cpp
- הורדת מודל GGUF לקוד
- הרצת שרת מודל (llama-server)
- התקנת רכיבי RAG (Chroma + Embeddings) בלי venv
- יצירת טבלת MySQL ללוגים
- קובץ הגדרות .env (DB user/pass/name)
- הרשאות קריאה לקוד שלך (ACL)
- סקריפט אינדוקס: ingest_repo.py
- אינדוקס בפועל + בדיקת Retrieval
- שרת RAG API: rag_api.py
- הרצה 24/7 עם screen + לוגים
- פתיחת פורטים (UFW)
- בדיקות curl + אימות שמירה ב-MySQL
- העלאת הפרויקט ל-GitHub כריפו חדש
- טיפים לשדרוג איכות לקוד
1) דרישות והכנות
- Ubuntu Server
- ללא GPU
- RAM ~ 20GB
- MySQL 8.0 קיים (DB קיים – אתה רק מזין שם/משתמש/סיסמה)
- נתיב הקוד לאינדוקס:
/home/raviti/htdocs/raviti.net/code/ - נתיבים קיימים אצלך:
/root/llama.cpp/root/models/gguf/root/ai_coder/root/hf
2) יצירת מבנה פרויקט (ai_coder)
המערכת עצמה תהיה ב-/root/ai_coder. הקוד של האתר נשאר במקום שלו – אנחנו רק קוראים אותו.
mkdir -p /root/ai_coder/{rag_data,logs}
cd /root/ai_coder
3) התקנת llama.cpp (אם עדיין לא מותקן)
apt update
apt -y install git build-essential cmake curl jq
cd /root
git clone [https://github.com/ggml-org/llama.cpp](https://github.com/ggml-org/llama.cpp)
cd llama.cpp
cmake -B build
cmake –build build -j
4) הורדת מודל GGUF שמצטיין בקוד (מומלץ ל-CPU)
המלצה: DeepSeek-Coder 6.7B Instruct בפורמט GGUF, בקוואנטיזציה Q4_K_M.
נוריד את המודל לתיקייה: /root/models/gguf
pip3 install -U huggingface-hub
mkdir -p /root/models/gguf
cd /root/models/gguf
huggingface-cli download TheBloke/deepseek-coder-6.7B-instruct-GGUF
deepseek-coder-6.7b-instruct.Q4_K_M.gguf
–local-dir . –local-dir-use-symlinks False
בדיקה שהקובץ קיים:
ls -lah /root/models/gguf
5) הרצת שרת מודל (llama-server) – API מקומי
השרת ירוץ על פורט 8001.
cd /root/llama.cpp
./build/bin/llama-server
-m /root/models/gguf/deepseek-coder-6.7b-instruct.Q4_K_M.gguf
–host 0.0.0.0 –port 8001
-c 4096
אם איטי מדי: החלף ל--c 2048 לשיפור מהירות.
6) התקנת רכיבי RAG (ללא venv) + קאש HuggingFace
נגדיר קאש ל-HF כדי לא למלא את הבית:
export HF_HOME=/root/hf
echo 'export HF_HOME=/root/hf' >> /root/.bashrc
התקנת ספריות:
pip3 install -U fastapi uvicorn requests pymysql chromadb sentence-transformers
7) MySQL: יצירת טבלת לוגים לשאלות/תשובות
תריץ פעם אחת בתוך מסד הנתונים שכבר יצרת (MySQL 8.0):
CREATE TABLE IF NOT EXISTS ai_queries (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
client_ip VARCHAR(64) NULL,
user_agent VARCHAR(255) NULL,
model_name VARCHAR(190) NOT NULL,
question MEDIUMTEXT NOT NULL,
answer MEDIUMTEXT NOT NULL,
retrieved_sources MEDIUMTEXT NULL,
latency_ms INT NULL,
PRIMARY KEY (id),
INDEX idx_created_at (created_at),
INDEX idx_model_name (model_name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
8) יצירת קובץ .env עם פרטי MySQL + קונפיג RAG
חשוב: את הקובץ הזה לא מעלים לגיט, כי יש בו סיסמאות.
כאן אתה מכניס את DB_NAME, DB_USER, DB_PASS.
cat > /root/ai_coder/.env <<'EOF'
# MySQL 8.0
DB_HOST=127.0.0.1
DB_NAME=PUT_DB_NAME_HERE
DB_USER=PUT_DB_USER_HERE
DB_PASS=PUT_DB_PASS_HERE
# llama.cpp server
LLAMA_SERVER=[http://127.0.0.1:8001](http://127.0.0.1:8001)
MODEL_NAME=deepseek-coder-6.7b-q4km
# RAG
REPO_DIR=/home/raviti/htdocs/raviti.net/code/
CHROMA_DIR=/root/ai_coder/rag_data
COLLECTION=codebase
TOP_K=6
# Embeddings (CPU friendly)
EMBED_MODEL=BAAI/bge-small-en-v1.5
# Chunking מותאם PHP/Python/JS/SQL
CHUNK_LINES=110
OVERLAP_LINES=25
MAX_FILE_BYTES=900000
EOF
9) הרשאות קריאה לנתיב הקוד (ACL) – אם יש Permission denied
בדיקת גישה:
ls -lah /home/raviti/htdocs/raviti.net/code/
אם קיבלת Permission denied, תן הרשאות קריאה ל-root באמצעות ACL:
apt -y install acl
setfacl -R -m u:root:rx /home/raviti/htdocs/raviti.net/code/
setfacl -R -d -m u:root:rx /home/raviti/htdocs/raviti.net/code/
getfacl /home/raviti/htdocs/raviti.net/code/ | head
10) סקריפט אינדוקס RAG: ingest_repo.py (גרסת PRO ל-PHP/Python/JS/SQL)
# =========================================================
# אינדוקס קוד ל-RAG (Chroma)
# מותאם: PHP 8.3 / Python / JS / SQL (MySQL 8.0)
# Author: pablo guides
# =========================================================
import os
import hashlib
from pathlib import Path
import chromadb
from sentence_transformers import SentenceTransformer
# ————————-
# הגדרות כלליות (מה-ENV)
# ————————-
CHROMA_DIR = os.getenv("CHROMA_DIR", "/root/ai_coder/rag_data")
EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5")
COLLECTION = os.getenv("COLLECTION", "codebase")
# תאימות לאחור
REPO_DIR = os.getenv("REPO_DIR", "/home/raviti/htdocs/raviti.net/code/")
# חדש: כמה מקורות קוד, מופרדים בפסיק
CODE_SOURCES = os.getenv("CODE_SOURCES", REPO_DIR)
CHUNK_LINES = int(os.getenv("CHUNK_LINES", "110"))
OVERLAP_LINES = int(os.getenv("OVERLAP_LINES", "25"))
MAX_FILE_BYTES = int(os.getenv("MAX_FILE_BYTES", "900000"))
# ————————-
# סיומות קבצים רלוונטיות
# ————————-
ALLOWED_EXT = {
".php", ".phtml", ".inc",
".py",
".js", ".mjs", ".cjs",
".sql",
".md", ".txt", ".json", ".yml", ".yaml",
".toml", ".ini", ".conf", ".sh"
}
# ————————-
# תיקיות לדילוג
# ————————-
SKIP_DIR_NAMES = {
".git", ".svn",
"node_modules", "vendor",
"dist", "build", ".next",
"coverage", "logs", "tmp", "cache",
".idea", ".vscode"
}
def is_binary(path: Path) -> bool:
try:
with path.open("rb") as f:
b = f.read(2048)
return b"\x00" in b
except Exception:
return True
def should_skip(path: Path) -> bool:
# דילוג על תיקיות כבדות
for part in path.parts:
if part in SKIP_DIR_NAMES:
return True
# דילוג על קבצים גדולים מדי
try:
if path.stat().st_size > MAX_FILE_BYTES:
return True
except Exception:
return True
# דילוג על בינאריים
if is_binary(path):
return True
# קבצים מותרים
if path.suffix.lower() in ALLOWED_EXT:
return False
# חריגים שימושיים
if path.name.lower() in {".env.example", "env.example"}:
return False
return True
def file_hash(p: Path) -> str:
h = hashlib.sha1()
h.update(str(p).encode("utf-8", "ignore"))
h.update(str(p.stat().st_mtime_ns).encode("utf-8", "ignore"))
return h.hexdigest()
def chunk_lines(lines, chunk=110, overlap=25):
i = 0
n = len(lines)
while i < n:
j = min(n, i + chunk)
yield i, j, "".join(lines[i:j])
if j == n:
break
i = max(0, j – overlap)
def main():
sources = [s.strip() for s in CODE_SOURCES.split(",") if s.strip()]
print("====================================")
print("RAG CODE INGEST START")
print("CODE_SOURCES:", sources)
print("CHROMA_DIR:", CHROMA_DIR)
print("EMBED_MODEL:", EMBED_MODEL)
print("COLLECTION:", COLLECTION)
print("CHUNK_LINES:", CHUNK_LINES, "OVERLAP_LINES:", OVERLAP_LINES)
print("MAX_FILE_BYTES:", MAX_FILE_BYTES)
print("====================================")
model = SentenceTransformer(EMBED_MODEL)
client = chromadb.PersistentClient(path=CHROMA_DIR)
col = client.get_or_create_collection(name=COLLECTION)
scanned = 0
skipped = 0
added = 0
for src in sources:
root = Path(src)
if not root.exists():
print("SKIP missing source:", src)
continue
for p in root.rglob("*"):
if not p.is_file():
continue
scanned += 1
if should_skip(p):
skipped += 1
continue
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
skipped += 1
continue
lines = text.splitlines(keepends=True)
fh = file_hash(p)
for a, b, chunk in chunk_lines(lines, CHUNK_LINES, OVERLAP_LINES):
chunk = chunk.strip()
if len(chunk) < 80:
continue
doc_id = hashlib.sha1(
f"{fh}:{a}:{b}".encode("utf-8", "ignore")
).hexdigest()
meta = {
"source_type": "code",
"path": str(p),
"start_line": a + 1,
"end_line": b
}
emb = model.encode(chunk, normalize_embeddings=True).tolist()
col.add(
ids=[doc_id],
documents=[chunk],
metadatas=[meta],
embeddings=[emb]
)
added += 1
print("✅ אינדוקס הסתיים")
print("קבצים נסרקו:", scanned)
print("דולגו:", skipped)
print("קטעים נוספו:", added)
if __name__ == "__main__":
main()
11) אינדוקס בפועל (מומלץ להתחיל מאינדקס נקי)
cd /root/ai_coder
rm -rf /root/ai_coder/rag_data
mkdir -p /root/ai_coder/rag_data
export $(grep -v '^#' .env | xargs)
python3 ingest_repo.py
12) בדיקת Retrieval בלבד (לוודא שהוא באמת שולף קבצים מהפרויקט)
nano /root/ai_coder/test_retrieval.py
# =========================================================
# בדיקת RAG Retrieval בלבד (Chroma)
# Author: pablo guides
# =========================================================
import os
import chromadb
from sentence_transformers import SentenceTransformer
# ————————-
# הגדרות
# ————————-
CHROMA_DIR = os.getenv("CHROMA_DIR", "/root/ai_coder/rag_data")
EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5")
COLLECTION = os.getenv("COLLECTION", "codebase")
# שאלה לבדיקה
question = "איפה מוגדר חיבור PDO ל-MySQL ואיך להוסיף utf8mb4?"
# ————————-
# חיבור ל-Chroma
# ————————-
client = chromadb.PersistentClient(path=CHROMA_DIR)
collection = client.get_or_create_collection(name=COLLECTION)
# ————————-
# Embedding לשאלה
# ————————-
embedder = SentenceTransformer(EMBED_MODEL)
query_embedding = embedder.encode(
question,
normalize_embeddings=True
).tolist()
# ————————-
# שליפה (Retrieval)
# ————————-
result = collection.query(
query_embeddings=[query_embedding],
n_results=6
)
documents = result.get("documents", [[]])[0]
metadatas = result.get("metadatas", [[]])[0]
# ————————-
# הדפסה
# ————————-
print("\n==============================")
print("RAG RETRIEVAL TEST")
print("QUESTION:", question)
print("==============================\n")
for i, (doc, meta) in enumerate(zip(documents, metadatas), start=1):
path = meta.get("path", "N/A")
start_line = meta.get("start_line", "?")
end_line = meta.get("end_line", "?")
print(f"—– {i} —–")
print(f"FILE: {path}")
print(f"LINES: {start_line}-{end_line}")
print("CONTENT:")
print(doc[:400])
print()
הרצה:
cd /root/ai_coder
export $(grep -v '^#' .env | xargs)
python3 test_retrieval.py
13) שרת RAG API: rag_api.py (כולל שמירה ל-MySQL)
cat > /root/ai_coder/rag_api.py <<'PY'
# =========================================================
# RAG API לקוד (CPU) + שמירת שאלות/תשובות ב-MySQL 8.0
# Author: pablo guides
# =========================================================
import os
import time
import json
import requests
import pymysql
import chromadb
from sentence_transformers import SentenceTransformer
from fastapi import FastAPI, Request
from pydantic import BaseModel
def load_env(path="/root/ai_coder/.env"):
if not os.path.exists(path):
return
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip())
load_env()
DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
DB_NAME = os.getenv("DB_NAME", "")
DB_USER = os.getenv("DB_USER", "")
DB_PASS = os.getenv("DB_PASS", "")
LLAMA_SERVER = os.getenv("LLAMA_SERVER", "[http://127.0.0.1:8001](http://127.0.0.1:8001)")
MODEL_NAME = os.getenv("MODEL_NAME", "deepseek-coder-6.7b-q4km")
CHROMA_DIR = os.getenv("CHROMA_DIR", "/root/ai_coder/rag_data")
EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5")
COLLECTION = os.getenv("COLLECTION", "codebase")
TOP_K = int(os.getenv("TOP_K", "6"))
SYSTEM_POLICY = (
"אתה עוזר מומחה תכנות ברמת פרודקשן.\n"
"שפות היעד: PHP 8.3, Python 3, JavaScript, SQL (MySQL 8.0).\n"
"\n"
"כללים:\n"
"1) ענה בעברית, קצר ומדויק. תן שלבים.\n"
"2) השתמש בקטעי הקוד שסופקו (RAG) וציין נתיבי קבצים.\n"
"3) כשאתה מציע תיקון: תן קוד מלא לפונקציה/לקובץ + איפה לשים.\n"
"4) ב-PHP: PDO + Prepared Statements, טיפול שגיאות, PHP 8.3.\n"
"5) ב-MySQL 8.0: SQL תקין + אינדקסים כשצריך.\n"
"6) אם חסר מידע קריטי — שאל שאלה משלימה אחת בלבד.\n"
"7) אם אינך בטוח — אמור 'אני לא בטוח' והצע בדיקת אימות.\n"
"8) אל תמציא עובדות/קבצים שלא קיימים.\n"
)
app = FastAPI()
class AskBody(BaseModel):
question: str
def db_conn():
return pymysql.connect(
host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME,
charset="utf8mb4", autocommit=True,
cursorclass=pymysql.cursors.DictCursor
)
chroma = chromadb.PersistentClient(path=CHROMA_DIR)
col = chroma.get_or_create_collection(name=COLLECTION)
embedder = SentenceTransformer(EMBED_MODEL)
def retrieve(question: str):
qemb = embedder.encode(question, normalize_embeddings=True).tolist()
res = col.query(query_embeddings=[qemb], n_results=TOP_K)
"`
docs = res.get("documents", [[]])[0]
metas = res.get("metadatas", [[]])[0]
sources = []
context_parts = []
for d, m in zip(docs, metas):
path = m.get("path", "")
a = m.get("start_line", "")
b = m.get("end_line", "")
sources.append(f"{path}:{a}-{b}")
context_parts.append(f"[{path}:{a}-{b}]\n{d}\n")
return "\n".join(context_parts), sources
"`
def call_llm(question: str, context: str):
url = f"{LLAMA_SERVER}/v1/chat/completions"
"`
user_msg = (
"להלן קטעי קוד/מסמכים רלוונטיים מהפרויקט (RAG).\n"
"השתמש בהם כדי לענות ולפתור.\n\n"
"=== הקשר (RAG) ===\n"
f"{context}\n\n"
"=== השאלה ===\n"
f"{question}\n"
)
payload = {
"model": MODEL_NAME,
"messages": [
{"role": "system", "content": SYSTEM_POLICY},
{"role": "user", "content": user_msg},
],
"temperature": 0.2,
"top_p": 0.9,
"max_tokens": 900
}
r = requests.post(url, json=payload, timeout=600)
r.raise_for_status()
data = r.json()
return data["choices"][0]["message"]["content"]
"`
@app.post("/ask")
async def ask(req: Request, body: AskBody):
t0 = time.time()
ip = req.client.host if req.client else None
ua = req.headers.get("user-agent")
"`
context, sources = retrieve(body.question)
answer = call_llm(body.question, context)
latency_ms = int((time.time() – t0) * 1000)
try:
con = db_conn()
with con.cursor() as cur:
cur.execute(
"""
INSERT INTO ai_queries
(client_ip, user_agent, model_name, question, answer, retrieved_sources, latency_ms)
VALUES (%s,%s,%s,%s,%s,%s,%s)
""",
(ip, ua, MODEL_NAME, body.question, answer, json.dumps(sources, ensure_ascii=False), latency_ms)
)
except Exception:
pass
return {"answer": answer, "sources": sources, "latency_ms": latency_ms}
"`
PY
הרצה:
cd /root/ai_coder
uvicorn rag_api:app –host 0.0.0.0 –port 8000
14) הרצה 24/7 עם screen (מומלץ לפרודקשן)
14.1 שרת המודל (llama-server) ב-screen
mkdir -p /root/ai_coder/logs
screen -S llm -dm bash -lc '
cd /root/llama.cpp &&
./build/bin/llama-server -m /root/models/gguf/deepseek-coder-6.7b-instruct.Q4_K_M.gguf –host 0.0.0.0 –port 8001 -c 4096 2>&1 | tee -a /root/ai_coder/logs/llm.log
'
14.2 שרת ה-RAG API ב-screen
screen -S rag -dm bash -lc '
cd /root/ai_coder &&
export $(grep -v "^#" .env | xargs) &&
uvicorn rag_api:app –host 0.0.0.0 –port 8000 2>&1 | tee -a /root/ai_coder/logs/rag.log
'
14.3 צפייה בלוגים
tail -f /root/ai_coder/logs/llm.log
tail -f /root/ai_coder/logs/rag.log
15) פתיחת פורטים ב-UFW (אם צריך גישה מבחוץ)
- פורט 8001 = שרת המודל
- פורט 8000 = ה-API שלך (RAG)
ufw allow 8000/tcp
ufw allow 8001/tcp
ufw status
16) בדיקות curl + אימות שמירה ב-MySQL
16.1 בדיקת API
curl -X POST [http://127.0.0.1:8000/ask](http://127.0.0.1:8000/ask)
-H "Content-Type: application/json"
-d '{"question":"חפש בקוד איפה מתבצעת התחברות ל-MySQL (PDO). תן שדרוג אבטחה מלא (utf8mb4, exceptions, prepared statements) עם קוד מלא."}'
16.2 אימות שנשמר ב-DB
SELECT id, created_at, model_name, LEFT(question, 80) AS q, latency_ms
FROM ai_queries
ORDER BY id DESC
LIMIT 10;
17) העלאה ל-GitHub כריפו חדש (פרויקט ai_coder)
GitHub מיועד לקוד. את המודל (GGUF) לא מעלים לרוב לגיט (גדול). לכן משתמשים ב-.gitignore.
17.1 .gitignore
cd /root/ai_coder
cat > .gitignore <<'EOF'
.env
rag_data/
logs/
**pycache**/
*.pyc
*.log
# מודלים כבדים
*.gguf
*.safetensors
*.bin
EOF
17.2 init + commit + push
תחליף USERNAME ו-REPO לפרטים שלך:
cd /root/ai_coder
git init
git add .
git commit -m "CPU-only Coding RAG Assistant (PHP/Python/JS/SQL)"
git branch -M main
git remote add origin [https://github.com/USERNAME/REPO.git](https://github.com/USERNAME/REPO.git)
git push -u origin main
הערה: GitHub דורש Token (PAT) במקום סיסמה ב-HTTPS.
18) טיפים לשדרוג “איכות קוד” (מאוד מומלץ)
- TOP_K: אם הוא “מפזר” – נסה 4. אם הוא מפספס הקשר – נסה 8.
- Chunking: בריפו עם קבצים גדולים, 110/25 טוב. אם יש הרבה קבצים קצרים, אפשר 80/15.
- Temperature נמוך: 0.2 בקוד זה טוב (מקטין הזיות).
- תדאג שהאינדוקס כולל גם: README, קבצי SQL, קונפיגים, סקריפטים, ועוד.
- אם יש לך WordPress/PHP: שים לב ש-vendor/ לא באינדקס (בכוונה) כדי לא להעמיס.
סיכום
בנית מערכת AI פרודקשן על CPU בלבד:
- מודל קוד רץ ב-llama.cpp (GGUF)
- RAG מאנדקס את הקוד האמיתי שלך מתוך
/home/raviti/htdocs/raviti.net/code/ - API מקומי שמחזיר תשובות “עם מקור”
- שמירת כל שאלה/תשובה ל-MySQL 8.0
- הרצה 24/7 ב-screen
19) האכלת המערכת בעמודי HTML (wget) לתיקייה קבועה + שמירה ל-MySQL + אינדוקס RAG לקוד
נתיב יעד חובה אצלך: /home/raviti/htdocs/raviti.net/code/html/
19.1 יצירת תיקיית HTML
mkdir -p /home/raviti/htdocs/raviti.net/code/html/
ls -lah /home/raviti/htdocs/raviti.net/code/html/
19.2 הורדת HTML מ-Kali Tools (עמוד רשימת הכלים)
זה דף אחד מרכזי. נוריד אותו כ-HTML (ועוד קצת קבצים מינימליים אם יש צורך), בקצב עדין.
cd /home/raviti/htdocs/raviti.net/code/html/
mkdir -p kali_tools_all
cd kali_tools_all
wget
–page-requisites –convert-links –adjust-extension
–no-parent –level=1
–wait=1 –random-wait
–user-agent="pablo-rotem-coder-bot/1.0 (+RAG indexing; contact: [email protected]
)"
"https://www.kali.org/tools/all-tools/
"
בדיקה מה ירד:
find . -maxdepth 3 -type f | head -n 50
מקור הדף:
19.3 הורדת מדריך Hostinger (Kali Linux tutorial) בצורה ממוקדת
כאן עדיף להגביל עומק ולהוריד רק את המסלול של המדריך (לא כל האתר).
cd /home/raviti/htdocs/raviti.net/code/html/
mkdir -p hostinger_kali_tutorial
cd hostinger_kali_tutorial
wget
–mirror –convert-links –adjust-extension
–no-parent –level=2
–reject ".jpg,.jpeg,.png,.gif,.webp,.svg,.css,.woff,.woff2,.ttf,*.ico"
–wait=2 –random-wait
–user-agent="pablo-rotem-coder-bot/1.0 (+RAG indexing; contact: [email protected]
)"
"https://www.hostinger.com/ca/tutorials/kali-linux-tutorial
"
טיפ: אם זה עדיין יורד יותר מדי — תוריד ל---level=1.
כללי robots/שליטה בסריקה:
19.4 הורדת pablo-guides.com (שלך) – בצורה מלאה יותר
זה האתר שלך, אז אתה יכול להיות יותר גמיש. עדיין מומלץ קצב עדין כדי לא להפיל את השרת.
cd /home/raviti/htdocs/raviti.net/code/html/
mkdir -p pablo_guides
cd pablo_guides
wget
–mirror –convert-links –adjust-extension –page-requisites
–no-parent –level=4
–reject ".mp4,.zip,.rar,.7z,*.iso"
–wait=1 –random-wait
–user-agent="pablo-rotem-coder-bot/1.0"
"https://pablo-guides.com/
"
19.5 GitHub repos — לא wget. עושים git clone ומכניסים ל-RAG כקוד
ל-GitHub עדיף להוריד קוד דרך git (ולא HTML של האתר). זה גם נקי יותר וגם פרקטי יותר לקידוד. בנוסף, לגיטהאב יש מגבלות/כללי סריקה וקצבים. :contentReference[oaicite:4]{index=4}
מבנה מומלץ: ניצור תיקייה מקומית לריפואים שנשמרים “ללימוד” ואז נצביע אליה באינדוקס.
mkdir -p /home/raviti/htdocs/raviti.net/code/html/github_repos
דוגמה להורדת ריפו (רק היסטוריה מינימלית):
cd /home/raviti/htdocs/raviti.net/code/html/github_repos
דוגמה – החלף לכתובת הריפו שאתה רוצה:
git clone –depth 1 https://github.com/OWNER/REPO.git
איך מכניסים את זה ל-RAG? יש לך כבר ingest_repo.py שמאונדקס נתיב אחד. נרחיב אותו כדי לאנדקס גם את תיקיית GitHub הזו (וגם את הקוד הראשי שלך) — בסעיף 19.8.
19.6 MySQL: טבלאות לאחסון HTML ודאטה שחולץ
אם לא יצרת עדיין — צור עכשיו:
CREATE TABLE IF NOT EXISTS ai_html_pages (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
url VARCHAR(2048) NULL,
local_path VARCHAR(1024) NOT NULL,
title VARCHAR(512) NULL,
content_hash CHAR(40) NOT NULL,
html LONGTEXT NOT NULL,
text MEDIUMTEXT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_hash (content_hash),
INDEX idx_created_at (created_at),
INDEX idx_title (title(190))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS ai_html_chunks (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
page_id BIGINT UNSIGNED NOT NULL,
chunk_index INT NOT NULL,
chunk_text MEDIUMTEXT NOT NULL,
PRIMARY KEY (id),
UNIQUE KEY uniq_page_chunk (page_id, chunk_index),
INDEX idx_page (page_id),
CONSTRAINT fk_ai_html_chunks_page
FOREIGN KEY (page_id) REFERENCES ai_html_pages(id)
ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
19.7 התקנת ספריות חילוץ HTML (BeautifulSoup)
pip3 install -U beautifulsoup4 lxml
19.8 סקריפט PRO: אינג’סט HTML + דגש על קטעי קוד + שמירה ל-MySQL + אינדוקס ל-Chroma
שיפור משמעותי: אנחנו גם ננסה לחלץ URL מתוך תגיות canonical/og:url אם קיימות, כדי לשמור “מקור” אמיתי ולא רק local_path.
cat > /root/ai_coder/ingest_html_pages.py <<'PY' ========================================================= HTML -> MySQL + Chroma RAG (ממוקד קוד) – CPU
Author: pablo guides
=========================================================
import os
import re
import json
import hashlib
from pathlib import Path
import pymysql
import chromadb
from bs4 import BeautifulSoup
from sentence_transformers import SentenceTransformer
def load_env(path="/root/ai_coder/.env"):
if not os.path.exists(path):
return
with open(path, "r", encoding="utf-8") as f:
for line in f:
line = line.strip()
if not line or line.startswith("#") or "=" not in line:
continue
k, v = line.split("=", 1)
os.environ.setdefault(k.strip(), v.strip())
load_env()
DB_HOST = os.getenv("DB_HOST", "127.0.0.1")
DB_NAME = os.getenv("DB_NAME", "")
DB_USER = os.getenv("DB_USER", "")
DB_PASS = os.getenv("DB_PASS", "")
CHROMA_DIR = os.getenv("CHROMA_DIR", "/root/ai_coder/rag_data")
EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5")
COLLECTION = os.getenv("COLLECTION", "codebase")
HTML_DIR = os.getenv("HTML_DIR", "/home/raviti/htdocs/raviti.net/code/html/")
CHUNK_CHARS = int(os.getenv("HTML_CHUNK_CHARS", "2200"))
OVERLAP_CHARS = int(os.getenv("HTML_OVERLAP_CHARS", "250"))
RE_WS = re.compile(r"[ \t\r\f\v]+")
RE_NL = re.compile(r"\n{3,}")
def db_conn():
return pymysql.connect(
host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME,
charset="utf8mb4", autocommit=True,
cursorclass=pymysql.cursors.DictCursor
)
def sha1_text(s: str) -> str:
h = hashlib.sha1()
h.update(s.encode("utf-8", "ignore"))
return h.hexdigest()
def normalize(s: str) -> str:
s = s.replace("\u00a0", " ")
s = s.replace("\r\n", "\n").replace("\r", "\n")
s = RE_WS.sub(" ", s)
s = RE_NL.sub("\n\n", s)
return s.strip()
def extract_url(soup: BeautifulSoup):
# canonical
link = soup.find("link", rel=lambda x: x and "canonical" in x)
if link and link.get("href"):
return link.get("href")[:2048]
# og:url
og = soup.find("meta", property="og:url")
if og and og.get("content"):
return og.get("content")[:2048]
return None
def extract_from_html(html: str):
soup = BeautifulSoup(html, "lxml")
url = extract_url(soup)
title = None
if soup.title and soup.title.get_text(strip=True):
title = soup.title.get_text(strip=True)[:500]
# remove noise
for tag in soup(["script", "style", "noscript"]):
tag.decompose()
# code blocks
code_blocks = []
for node in soup.find_all(["pre", "code"]):
txt = node.get_text("\n", strip=True)
txt = normalize(txt)
if len(txt) >= 40:
code_blocks.append(txt)
text = soup.get_text("\n", strip=True)
text = normalize(text)
code_section = ""
if code_blocks:
code_section = "\n\n".join([f""`snippet\n{cb}\n"`" for cb in code_blocks])
combined = ""
if code_section:
combined += "=== CODE SNIPPETS ===\n" + code_section + "\n\n"
if text:
combined += "=== PAGE TEXT ===\n" + text
combined = combined.strip()
return url, title, text, combined
def chunk_text(s: str, chunk_chars=2200, overlap_chars=250):
s = s.strip()
if not s:
return []
chunks = []
i = 0
n = len(s)
while i < n: j = min(n, i + chunk_chars) chunk = s[i:j].strip() if len(chunk) >= 120:
chunks.append(chunk)
if j == n:
break
i = max(0, j – overlap_chars)
return chunks
def main():
html_root = Path(HTML_DIR)
if not html_root.exists():
raise SystemExit(f"HTML_DIR לא קיים: {HTML_DIR}")
embedder = SentenceTransformer(EMBED_MODEL)
chroma = chromadb.PersistentClient(path=CHROMA_DIR)
col = chroma.get_or_create_collection(name=COLLECTION)
con = db_conn()
files = list(html_root.rglob("*.html"))
print("HTML files found:", len(files))
added_pages = 0
added_chunks = 0
for p in files:
try:
raw = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
continue
h = sha1_text(raw)
url, title, text, combined = extract_from_html(raw)
if not combined or len(combined) < 200:
continue
with con.cursor() as cur:
cur.execute("SELECT id FROM ai_html_pages WHERE content_hash=%s", (h,))
row = cur.fetchone()
if row:
page_id = row["id"]
else:
cur.execute(
"""
INSERT INTO ai_html_pages (url, local_path, title, content_hash, html, text)
VALUES (%s,%s,%s,%s,%s,%s)
""",
(url, str(p), title, h, raw, text)
)
page_id = cur.lastrowid
added_pages += 1
chunks = chunk_text(combined, CHUNK_CHARS, OVERLAP_CHARS)
if not chunks:
continue
for idx, chunk in enumerate(chunks):
with con.cursor() as cur:
cur.execute(
"SELECT id FROM ai_html_chunks WHERE page_id=%s AND chunk_index=%s",
(page_id, idx)
)
if not cur.fetchone():
cur.execute(
"INSERT INTO ai_html_chunks (page_id, chunk_index, chunk_text) VALUES (%s,%s,%s)",
(page_id, idx, chunk)
)
doc_id = hashlib.sha1(f"html:{page_id}:{idx}:{h}".encode()).hexdigest()
meta = {
"source_type": "html",
"page_id": page_id,
"chunk_index": idx,
"path": str(p),
"title": title or "",
"url": url or ""
}
emb = embedder.encode(chunk, normalize_embeddings=True).tolist()
col.add(ids=[doc_id], documents=[chunk], metadatas=[meta], embeddings=[emb])
added_chunks += 1
print("✅ DONE")
print("Pages added:", added_pages)
print("Chunks added:", added_chunks)
if name == "main":
main()
PY
19.9 עדכון .env עבור HTML_DIR
אם כבר קיים – אפשר להשאיר, אחרת הוסף:
grep -q '^HTML_DIR=' /root/ai_coder/.env || cat >> /root/ai_coder/.env <<'EOF'
HTML_DIR=/home/raviti/htdocs/raviti.net/code/html/
HTML_CHUNK_CHARS=2200
HTML_OVERLAP_CHARS=250
EOF
19.10 הרצת אינג’סט HTML אחרי wget
cd /root/ai_coder
export $(grep -v '^#' .env | xargs)
python3 ingest_html_pages.py
20) GitHub repos: איך “להאכיל” את ה-AI בקוד אמיתי מריפואים רבים
עקרון: כל ריפו שאתה עושה לו clone לתיקייה קבועה, נכנס לאינדוקס קוד (ingest_repo.py) בדיוק כמו הקוד שלך.
20.1 יצירת “ספריית ריפואים” קבועה
mkdir -p /home/raviti/htdocs/raviti.net/code/html/github_repos
20.2 קלונינג רשימת ריפואים מקובץ טקסט
צור קובץ: /root/ai_coder/repos.txt עם כתובת בכל שורה.
cat > /root/ai_coder/repos.txt <<'EOF'
https://github.com/OWNER1/REPO1.git
https://github.com/OWNER2/REPO2.git
EOF
סקריפט שמקלון מה שחסר בלבד:
cat > /root/ai_coder/clone_repos.sh <<'SH' #!/usr/bin/env bash ========================================================= clone many repos safely (depth 1) Author: pablo guides ========================================================= set -euo pipefail DEST="/home/raviti/htdocs/raviti.net/code/html/github_repos" LIST="/root/ai_coder/repos.txt" mkdir -p "$DEST" while IFS= read -r url; do [[ -z "${url// }" ]] && continue name="$(basename "$url" .git)" target="$DEST/$name" if [[ -d "$target/.git" || -d "$target" ]]; then echo "SKIP (exists): $name" continue fi echo "CLONE: $url -> $target"
git clone –depth 1 "$url" "$target"
done < "$LIST"
SH
chmod +x /root/ai_coder/clone_repos.sh
הרצה:
/root/ai_coder/clone_repos.sh
21) אינדוקס גם של הקוד שלך וגם של ריפואי GitHub (2 נתיבים)
כדי לאנדקס כמה מקורות קוד, נשנה את ingest_repo.py לעבוד עם רשימה של נתיבים (מופרדים בפסיק).
21.1 עדכון .env: להגדיר CODE_SOURCES
הוסף בסוף .env:
grep -q '^CODE_SOURCES=' /root/ai_coder/.env || cat >> /root/ai_coder/.env <<'EOF'
מקורות קוד לאינדוקס (פסיקים)
CODE_SOURCES=/home/raviti/htdocs/raviti.net/code/,/home/raviti/htdocs/raviti.net/code/html/github_repos
EOF
21.2 גרסת ingest_repo.py שתומכת במספר נתיבים
cat > /root/ai_coder/ingest_repo.py <<'PY' ========================================================= אינדוקס קוד לרג (Chroma) – multiple sources Author: pablo guides ========================================================= import os import hashlib from pathlib import Path from sentence_transformers import SentenceTransformer import chromadb CHROMA_DIR = os.getenv("CHROMA_DIR", "/root/ai_coder/rag_data") EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5") COLLECTION = os.getenv("COLLECTION", "codebase") נתיב יחיד ישן (תאימות לאחור) REPO_DIR = os.getenv("REPO_DIR", "/home/raviti/htdocs/raviti.net/code/") חדש: כמה מקורות קוד CODE_SOURCES = os.getenv("CODE_SOURCES", REPO_DIR) CHUNK_LINES = int(os.getenv("CHUNK_LINES", "110")) OVERLAP_LINES = int(os.getenv("OVERLAP_LINES", "25")) MAX_FILE_BYTES = int(os.getenv("MAX_FILE_BYTES", "900000")) ALLOWED_EXT = { ".php",".phtml",".inc", ".py", ".js",".mjs",".cjs", ".sql", ".md",".txt",".json",".yml",".yaml",".toml",".ini",".conf",".sh" } SKIP_DIR_NAMES = { ".git",".svn", "node_modules","vendor", "dist","build",".next", "coverage","logs","tmp","cache", ".idea",".vscode" } def is_binary(path: Path) -> bool:
try:
with path.open("rb") as f:
b = f.read(2048)
return b"\x00" in b
except Exception:
return True
def should_skip(path: Path) -> bool:
for part in path.parts:
if part in SKIP_DIR_NAMES:
return True
try:
if path.stat().st_size > MAX_FILE_BYTES:
return True
except Exception:
return True
if is_binary(path):
return True
if path.suffix.lower() in ALLOWED_EXT:
return False
if path.name.lower() in {".env.example", "env.example"}:
return False
return True
def file_hash(p: Path) -> str:
h = hashlib.sha1()
h.update(str(p).encode("utf-8", "ignore"))
h.update(str(p.stat().st_mtime_ns).encode())
return h.hexdigest()
def chunk_lines(lines, chunk=110, overlap=25):
i = 0
n = len(lines)
while i < n:
j = min(n, i + chunk)
yield i, j, "".join(lines[i:j])
if j == n:
break
i = max(0, j – overlap)
def main():
sources = [s.strip() for s in CODE_SOURCES.split(",") if s.strip()]
print("CODE_SOURCES:", sources)
print("CHROMA_DIR:", CHROMA_DIR)
print("EMBED_MODEL:", EMBED_MODEL)
print("COLLECTION:", COLLECTION)
model = SentenceTransformer(EMBED_MODEL)
client = chromadb.PersistentClient(path=CHROMA_DIR)
col = client.get_or_create_collection(name=COLLECTION)
scanned = 0
skipped = 0
added = 0
for src in sources:
root = Path(src)
if not root.exists():
print("SKIP missing source:", src)
continue
for p in root.rglob("*"):
if not p.is_file():
continue
scanned += 1
if should_skip(p):
skipped += 1
continue
try:
text = p.read_text(encoding="utf-8", errors="ignore")
except Exception:
skipped += 1
continue
lines = text.splitlines(keepends=True)
fh = file_hash(p)
for a, b, chunk in chunk_lines(lines, CHUNK_LINES, OVERLAP_LINES):
chunk = chunk.strip()
if len(chunk) < 80:
continue
doc_id = hashlib.sha1(f"{fh}:{a}:{b}".encode()).hexdigest()
meta = {"path": str(p), "start_line": a + 1, "end_line": b, "source_type": "code"}
emb = model.encode(chunk, normalize_embeddings=True).tolist()
col.add(ids=[doc_id], documents=[chunk], metadatas=[meta], embeddings=[emb])
added += 1
print("✅ אינדוקס הסתיים")
print("קבצים נסרקו:", scanned, "דולגו:", skipped, "קטעים נוספו:", added)
if name == "main":
main()
PY
21.3 הרצת אינדוקס קוד מחדש
cd /root/ai_coder
export $(grep -v '^#' .env | xargs)
python3 ingest_repo.py
22) “שם AI משלך”: pablo-rotem-coder + (context tokens) + פרסום ל-Hugging Face
המודל שאתה מריץ ב-CPU הוא עדיין GGUF של מודל בסיס. אתה יכול:
- לפרסם ריפו “Model” ב-Hugging Face עם שם שלך, Model Card, הוראות ריצה, וקבצי הסקריפטים (RAG).
- אם הרשיון מאפשר ויש לך את קובץ ה-GGUF — אפשר להעלות גם אותו (זה גדול → Git-LFS). :contentReference[oaicite:5]{index=5}
22.1 יצירת תיקיית פרסום נקייה + README
mkdir -p /root/ai_coder/hf_publish
cp -f /root/ai_coder/rag_api.py /root/ai_coder/hf_publish/
cp -f /root/ai_coder/ingest_repo.py /root/ai_coder/hf_publish/
cp -f /root/ai_coder/ingest_html_pages.py /root/ai_coder/hf_publish/
cat > /root/ai_coder/hf_publish/README.md <<'EOF'
tags:
rag
llama.cpp
gguf
coding
php
mysql
javascript
python
pablo-rotem-coder-4096
מערכת AI לקידוד על CPU בלבד, עם RAG:
llama.cpp (llama-server) endpoint תואם OpenAI
ChromaDB (persistent)
sentence-transformers embeddings (BGE small)
FastAPI /ask
שמירת שאלות/תשובות ב-MySQL 8.0
אינג'סט קוד מהפרויקט + ריפואים מגיטהאב
אינג'סט HTML (wget) וחילוץ code snippets מתוך
/
Base model
המערכת רצה עם GGUF של מודל בסיס (למשל DeepSeek-Coder 6.7B Instruct Q4_K_M).
השיפור מגיע מ-RAG, לא מאימון weights.
Context length
4096 tokens (llama-server -c 4096)
Folders
HTML pages: /home/raviti/htdocs/raviti.net/code/html/
Chroma: /root/ai_coder/rag_data
Security
אל תעלה קובץ .env (סיסמאות DB).
EOF
22.2 התחברות ל-Hugging Face + יצירת ריפו + העלאה עם Git-LFS
pip3 install -U huggingface-hub
huggingface-cli login
apt -y install git git-lfs
git lfs install
צור ריפו מודל
huggingface-cli repo create pablo-rotem-coder-4096 –type model
קלון הריפו (החלף HF_USERNAME)
cd /root/ai_coder
git clone https://huggingface.co/HF_USERNAME/pablo-rotem-coder-4096
hf_model_repo
העתק תוכן
cp -f /root/ai_coder/hf_publish/* /root/ai_coder/hf_model_repo/
cd /root/ai_coder/hf_model_repo
אם אתה מעלה GGUF גדול – עקוב עם LFS:
git lfs track "*.gguf"
git add .
git commit -m "Initial release: pablo-rotem-coder-4096 (CPU RAG coding assistant)"
git push
תיעוד רשמי HF (CLI/Repos/Upload):
23) זרימת עבודה מומלצת (אוטומציה שבועית/יומית)
כל פעם שאתה מוסיף מקורות חדשים:
- wget (Kali/Hostinger/האתר שלך) לתיקיית
.../code/html/ - git clone לריפואים חדשים לתיקיית
.../code/html/github_repos/ - הרץ אינג’סט HTML
- הרץ אינדוקס קוד
1) wget (דוגמאות – רוץ רק מה שאתה צריך)
cd /home/raviti/htdocs/raviti.net/code/html/kali_tools_all
wget –page-requisites –convert-links –adjust-extension –no-parent –level=1 –wait=1 –random-wait "https://www.kali.org/tools/all-tools/
"
cd /home/raviti/htdocs/raviti.net/code/html/hostinger_kali_tutorial
wget –mirror –convert-links –adjust-extension –no-parent –level=2 –reject ".jpg,.jpeg,.png,.gif,.webp,.svg,.css,.woff,.woff2,.ttf,*.ico" –wait=2 –random-wait "https://www.hostinger.com/ca/tutorials/kali-linux-tutorial
"
2) clone ריפואים
/root/ai_coder/clone_repos.sh
3) ingest HTML
cd /root/ai_coder
export $(grep -v '^#' .env | xargs)
python3 ingest_html_pages.py
4) ingest code
python3 ingest_repo.py
24) למה זה ישפר קידוד בצורה דרמטית
- Kali tools: המערכת תוכל להסביר כלים/שימושים/דגלים (ככל שיש קטעים בדף/קישורים שנמשכו).
- Hostinger tutorial: תוכל לקבל תשובות עם צעדים/פקודות שמבוססות על המדריך.
- pablo-guides.com: ה-AI יכיר את המדריכים שלך ויישר קו עם הסטנדרט שלך.
- GitHub repos: המערכת תכיר קוד אמיתי ותוכל להמליץ פתרונות “בסגנון קוד” של פרויקטים שאתה עוקב אחריהם.
25) בדיקה אחת שמוכיחה שהכל עובד
curl -X POST http://127.0.0.1:8000/ask
-H "Content-Type: application/json"
-d '{"question":"תסביר בקצרה איך מורידים דוקומנטציה עם wget בצורה עדינה (קצב/עומק) ותן פקודת wget מומלצת ל-Kali tools all-tools ול-Hostinger tutorial."}'
