Uncategorized 22/01/2026 26 דק׳ קריאה

מדריך מלא: הקמת AI לקוד על שרת CPU בלבד עם RAG + שמירת שאלות/תשובות ב-MySQL (PHP 8.3 / Python / JS / SQL)

pablo guides · 0 תגובות

מדריך מלא: הקמת AI לקוד על שרת CPU בלבד עם RAG + שמירת שאלות/תשובות ב-MySQL (PHP 8.3 / Python / JS / SQL)

מטרה: לבנות “עוזר מתכנת” שמבין את הקוד שלך באמת, עונה עם הקשר מהפרויקט (RAG), מציע תיקונים מלאים, ושומר כל שאלה/תשובה במסד נתונים.

הגבלות CPU בלבד: בלי GPU לא מאמנים מודלים ענקיים בפועל. במקום זה משתמשים ב-RAG כדי “להאכיל” את העוזר בקוד אמיתי מתוך הפרויקט. זה נותן תוצאות פרקטיות ומקצועיות במיוחד לקוד.


תוכן עניינים

  1. דרישות והכנות
  2. מבנה תיקיות מומלץ
  3. התקנת llama.cpp
  4. הורדת מודל GGUF לקוד
  5. הרצת שרת מודל (llama-server)
  6. התקנת רכיבי RAG (Chroma + Embeddings) בלי venv
  7. יצירת טבלת MySQL ללוגים
  8. קובץ הגדרות .env (DB user/pass/name)
  9. הרשאות קריאה לקוד שלך (ACL)
  10. סקריפט אינדוקס: ingest_repo.py
  11. אינדוקס בפועל + בדיקת Retrieval
  12. שרת RAG API: rag_api.py
  13. הרצה 24/7 עם screen + לוגים
  14. פתיחת פורטים (UFW)
  15. בדיקות curl + אימות שמירה ב-MySQL
  16. העלאת הפרויקט ל-GitHub כריפו חדש
  17. טיפים לשדרוג איכות לקוד

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

בדיקה שהקובץ קיים:


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) זרימת עבודה מומלצת (אוטומציה שבועית/יומית)

כל פעם שאתה מוסיף מקורות חדשים:

  1. wget (Kali/Hostinger/האתר שלך) לתיקיית .../code/html/
  2. git clone לריפואים חדשים לתיקיית .../code/html/github_repos/
  3. הרץ אינג’סט HTML
  4. הרץ אינדוקס קוד

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."}'