Spaces:
Running
Running
Upload folder using huggingface_hub
Browse files- .env +11 -0
- .env.example +11 -0
- Dockerfile +12 -0
- Makefile +22 -0
- README.md +192 -12
- README_hf_space.md +14 -0
- __pycache__/app.cpython-312.pyc.2197825028720 +0 -0
- __pycache__/upload_to_hf.cpython-312.pyc.2197825028720 +0 -0
- api/__init__.py +1 -0
- api/__pycache__/__init__.cpython-312.pyc.2197825028720 +0 -0
- api/__pycache__/main.cpython-312.pyc.2197825028720 +0 -0
- api/main.py +44 -0
- api/security.py +33 -0
- app.py +45 -0
- client_example.py +29 -0
- data/sample_snippets.json +11 -0
- docker-compose.yml +11 -0
- instruction.md +187 -0
- requirements.txt +16 -0
- run_api.bat +6 -0
- run_space.bat +6 -0
- run_tasks.bat +2 -0
- smoke_test.py +112 -0
- specification_file.md +159 -0
- src/__init__.py +6 -0
- src/__pycache__/__init__.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/config.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/generator.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/hallucination.cpython-312.pyc.2197822599920 +0 -0
- src/__pycache__/lora_prepare.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/model_loader.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/pipeline.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/prompts.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/rag.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/relevancy.cpython-312.pyc.2197825028720 +0 -0
- src/__pycache__/schemas.cpython-312.pyc.2197825028720 +0 -0
- src/config.py +28 -0
- src/generator.py +86 -0
- src/hallucination.py +58 -0
- src/lora_prepare.py +18 -0
- src/model_loader.py +64 -0
- src/pipeline.py +58 -0
- src/prompts.py +19 -0
- src/rag.py +57 -0
- src/relevancy.py +17 -0
- src/schemas.py +18 -0
- tasks.py +111 -0
- upload_to_hf.py +40 -0
.env
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL_NAME=Qwen/Qwen2.5-Coder-1.5B-Instruct
|
| 2 |
+
FALLBACK_MODEL_NAME=Qwen/Qwen2.5-Coder-0.5B-Instruct
|
| 3 |
+
FINAL_FALLBACK_MODEL_NAME=sshleifer/tiny-gpt2
|
| 4 |
+
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
| 5 |
+
MAX_NEW_TOKENS=256
|
| 6 |
+
TEMPERATURE=0.2
|
| 7 |
+
TOP_P=0.95
|
| 8 |
+
USE_RAG=true
|
| 9 |
+
FORCE_MOCK_MODE=false
|
| 10 |
+
API_KEY=gk_zxcvbnmlkjhgfdsaq1w2e3r4t5y6u7i8o9p0
|
| 11 |
+
RATE_LIMIT_PER_MINUTE=30
|
.env.example
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL_NAME=Qwen/Qwen2.5-Coder-1.5B-Instruct
|
| 2 |
+
FALLBACK_MODEL_NAME=Qwen/Qwen2.5-Coder-0.5B-Instruct
|
| 3 |
+
FINAL_FALLBACK_MODEL_NAME=sshleifer/tiny-gpt2
|
| 4 |
+
EMBEDDING_MODEL=sentence-transformers/all-MiniLM-L6-v2
|
| 5 |
+
MAX_NEW_TOKENS=256
|
| 6 |
+
TEMPERATURE=0.2
|
| 7 |
+
TOP_P=0.95
|
| 8 |
+
USE_RAG=true
|
| 9 |
+
FORCE_MOCK_MODE=false
|
| 10 |
+
API_KEY=change-me
|
| 11 |
+
RATE_LIMIT_PER_MINUTE=30
|
Dockerfile
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
COPY requirements.txt /app/requirements.txt
|
| 6 |
+
RUN pip install --no-cache-dir -r /app/requirements.txt
|
| 7 |
+
|
| 8 |
+
COPY . /app
|
| 9 |
+
|
| 10 |
+
EXPOSE 8000
|
| 11 |
+
|
| 12 |
+
CMD ["uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
Makefile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.PHONY: install run space smoke compile docker-up docker-down
|
| 2 |
+
|
| 3 |
+
install:
|
| 4 |
+
python tasks.py install
|
| 5 |
+
|
| 6 |
+
run:
|
| 7 |
+
python tasks.py run
|
| 8 |
+
|
| 9 |
+
space:
|
| 10 |
+
python tasks.py space
|
| 11 |
+
|
| 12 |
+
smoke:
|
| 13 |
+
python tasks.py smoke
|
| 14 |
+
|
| 15 |
+
compile:
|
| 16 |
+
python tasks.py compile
|
| 17 |
+
|
| 18 |
+
docker-up:
|
| 19 |
+
python tasks.py docker-up
|
| 20 |
+
|
| 21 |
+
docker-down:
|
| 22 |
+
python tasks.py docker-down
|
README.md
CHANGED
|
@@ -1,12 +1,192 @@
|
|
| 1 |
-
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
-
|
| 11 |
-
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Advanced Coding LLM (Production-Ready Starter)
|
| 2 |
+
|
| 3 |
+
This project provides a deployable coding assistant API built on a free Hugging Face coding model.
|
| 4 |
+
|
| 5 |
+
## Model Strategy
|
| 6 |
+
|
| 7 |
+
- Primary model: `Qwen/Qwen2.5-Coder-1.5B-Instruct` (free/open on Hugging Face).
|
| 8 |
+
- Fallback model: `Qwen/Qwen2.5-Coder-0.5B-Instruct` if primary load fails.
|
| 9 |
+
- Final emergency fallback: `sshleifer/tiny-gpt2` (for guaranteed startup).
|
| 10 |
+
- No heavy training required.
|
| 11 |
+
- LoRA-ready architecture included in `src/lora_prepare.py`.
|
| 12 |
+
|
| 13 |
+
## Features
|
| 14 |
+
|
| 15 |
+
- Code generation
|
| 16 |
+
- Debugging / buggy code fixing
|
| 17 |
+
- Code explanation
|
| 18 |
+
- Instruction following
|
| 19 |
+
- Confidence estimation (from token probabilities)
|
| 20 |
+
- Important token extraction (low-confidence tokens)
|
| 21 |
+
- Relevancy score (embedding cosine similarity)
|
| 22 |
+
- Hallucination checks:
|
| 23 |
+
- Syntax validation
|
| 24 |
+
- Runtime smoke test
|
| 25 |
+
- Optional RAG with FAISS from `data/sample_snippets.json`
|
| 26 |
+
|
| 27 |
+
## Project Structure
|
| 28 |
+
|
| 29 |
+
```text
|
| 30 |
+
coding-llm/
|
| 31 |
+
│── data/
|
| 32 |
+
│── src/
|
| 33 |
+
│── api/
|
| 34 |
+
│── requirements.txt
|
| 35 |
+
│── README.md
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
## API Output Format
|
| 39 |
+
|
| 40 |
+
`POST /generate` returns:
|
| 41 |
+
|
| 42 |
+
```json
|
| 43 |
+
{
|
| 44 |
+
"code": "...",
|
| 45 |
+
"explanation": "...",
|
| 46 |
+
"confidence": 0.0,
|
| 47 |
+
"important_tokens": ["..."],
|
| 48 |
+
"relevancy_score": 0.0,
|
| 49 |
+
"hallucination": false,
|
| 50 |
+
"latency_ms": 0
|
| 51 |
+
}
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
If hallucination is detected, the reason is appended inside `explanation`.
|
| 55 |
+
|
| 56 |
+
## Local Run
|
| 57 |
+
|
| 58 |
+
1. Create environment and install:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
pip install -r requirements.txt
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
Optional: create `.env` from `.env.example` and set values:
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
copy .env.example .env
|
| 68 |
+
```
|
| 69 |
+
|
| 70 |
+
2. Run API:
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
uvicorn api.main:app --host 0.0.0.0 --port 8000
|
| 74 |
+
```
|
| 75 |
+
|
| 76 |
+
3. Test request:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
curl -X POST "http://127.0.0.1:8000/generate" ^
|
| 80 |
+
-H "Content-Type: application/json" ^
|
| 81 |
+
-d "{\"instruction\":\"Fix this Python function\",\"input\":\"def add(a,b) return a+b\"}"
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
4. Optional client:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
python client_example.py
|
| 88 |
+
```
|
| 89 |
+
|
| 90 |
+
## Hugging Face Deployment (Space)
|
| 91 |
+
|
| 92 |
+
This repo includes:
|
| 93 |
+
|
| 94 |
+
- `app.py` (Gradio app for HF Space)
|
| 95 |
+
- `upload_to_hf.py` (upload helper script)
|
| 96 |
+
- `README_hf_space.md` (Space metadata template)
|
| 97 |
+
|
| 98 |
+
Steps:
|
| 99 |
+
|
| 100 |
+
1. Create a HF access token with write permission.
|
| 101 |
+
2. Run:
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
python upload_to_hf.py --repo-id <your-username/coding-llm-space> --token <HF_TOKEN>
|
| 105 |
+
```
|
| 106 |
+
|
| 107 |
+
3. Your Space launches with public UI and can be called by Hugging Face API key.
|
| 108 |
+
|
| 109 |
+
## Security and Ops
|
| 110 |
+
|
| 111 |
+
- API key auth enabled when `API_KEY` is set.
|
| 112 |
+
- In-memory per-IP rate limiting via `RATE_LIMIT_PER_MINUTE`.
|
| 113 |
+
- Dockerized API included (`Dockerfile`).
|
| 114 |
+
- Model is loaded lazily on first `/generate` request (faster boot, fewer startup failures).
|
| 115 |
+
- Set `FORCE_MOCK_MODE=true` to run instantly without downloading models.
|
| 116 |
+
- Windows quick-start scripts:
|
| 117 |
+
- `run_api.bat`
|
| 118 |
+
- `run_space.bat`
|
| 119 |
+
|
| 120 |
+
## Docker Compose
|
| 121 |
+
|
| 122 |
+
```bash
|
| 123 |
+
copy .env.example .env
|
| 124 |
+
docker compose up --build
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
API is available at `http://127.0.0.1:8000`.
|
| 128 |
+
|
| 129 |
+
## Automated Smoke Test
|
| 130 |
+
|
| 131 |
+
Run this after API starts:
|
| 132 |
+
|
| 133 |
+
```bash
|
| 134 |
+
python smoke_test.py
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
This validates:
|
| 138 |
+
- `GET /health`
|
| 139 |
+
- `POST /generate`
|
| 140 |
+
- required JSON output keys
|
| 141 |
+
|
| 142 |
+
## One-command Task Runner
|
| 143 |
+
|
| 144 |
+
Cross-platform:
|
| 145 |
+
|
| 146 |
+
```bash
|
| 147 |
+
python tasks.py install
|
| 148 |
+
python tasks.py run
|
| 149 |
+
python tasks.py smoke
|
| 150 |
+
python tasks.py serve-smoke
|
| 151 |
+
python tasks.py docker-up
|
| 152 |
+
python tasks.py docker-down
|
| 153 |
+
python tasks.py hf-upload --repo-id <user/space-name> --token <HF_TOKEN>
|
| 154 |
+
```
|
| 155 |
+
|
| 156 |
+
Windows shortcut:
|
| 157 |
+
|
| 158 |
+
```bat
|
| 159 |
+
run_tasks.bat install
|
| 160 |
+
run_tasks.bat run
|
| 161 |
+
run_tasks.bat smoke
|
| 162 |
+
run_tasks.bat serve-smoke
|
| 163 |
+
```
|
| 164 |
+
|
| 165 |
+
Makefile (Linux/macOS/WSL):
|
| 166 |
+
|
| 167 |
+
```bash
|
| 168 |
+
make install
|
| 169 |
+
make run
|
| 170 |
+
make smoke
|
| 171 |
+
make docker-up
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
## FastAPI Endpoint
|
| 175 |
+
|
| 176 |
+
### `POST /generate`
|
| 177 |
+
|
| 178 |
+
Input JSON:
|
| 179 |
+
|
| 180 |
+
```json
|
| 181 |
+
{
|
| 182 |
+
"instruction": "Explain this code and improve it",
|
| 183 |
+
"input": "def f(x): return x*x"
|
| 184 |
+
}
|
| 185 |
+
```
|
| 186 |
+
|
| 187 |
+
## Notes for Production
|
| 188 |
+
|
| 189 |
+
- Keep `max_new_tokens` modest for low latency.
|
| 190 |
+
- Add request auth/rate limiting before exposing public endpoint.
|
| 191 |
+
- For stronger quality, add curated retrieval corpus in `data/`.
|
| 192 |
+
- For robust hallucination checks, extend tests per language/framework.
|
README_hf_space.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Advanced Coding LLM
|
| 3 |
+
emoji: 🤖
|
| 4 |
+
colorFrom: indigo
|
| 5 |
+
colorTo: blue
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 4.44.0
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
# Hugging Face Space Metadata
|
| 13 |
+
|
| 14 |
+
This file can be copied to your Space `README.md` to configure Space UI metadata.
|
__pycache__/app.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.34 kB). View file
|
|
|
__pycache__/upload_to_hf.cpython-312.pyc.2197825028720
ADDED
|
Binary file (2.01 kB). View file
|
|
|
api/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""API package."""
|
api/__pycache__/__init__.cpython-312.pyc.2197825028720
ADDED
|
Binary file (150 Bytes). View file
|
|
|
api/__pycache__/main.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.21 kB). View file
|
|
|
api/main.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FastAPI entrypoint for coding model."""
|
| 2 |
+
|
| 3 |
+
from fastapi import Depends, FastAPI, Request
|
| 4 |
+
|
| 5 |
+
from api.security import check_rate_limit, verify_api_key
|
| 6 |
+
from src.pipeline import CodingLLMPipeline
|
| 7 |
+
from src.schemas import GenerateRequest, GenerateResponse
|
| 8 |
+
|
| 9 |
+
app = FastAPI(title="Advanced Coding LLM API", version="1.0.0")
|
| 10 |
+
pipeline = CodingLLMPipeline()
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@app.get("/health")
|
| 14 |
+
def health():
|
| 15 |
+
model_name = pipeline.model_bundle.active_model_name if pipeline.model_bundle else "not_loaded_yet"
|
| 16 |
+
return {"status": "ok", "model": model_name}
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@app.post("/generate", response_model=GenerateResponse)
|
| 20 |
+
def generate(
|
| 21 |
+
payload: GenerateRequest,
|
| 22 |
+
request: Request,
|
| 23 |
+
_: None = Depends(verify_api_key),
|
| 24 |
+
):
|
| 25 |
+
client_host = request.client.host if request.client else "unknown"
|
| 26 |
+
check_rate_limit(client_host)
|
| 27 |
+
try:
|
| 28 |
+
result = pipeline.run(payload.instruction, payload.input)
|
| 29 |
+
return GenerateResponse(**result)
|
| 30 |
+
except Exception as exc:
|
| 31 |
+
# Keep response shape consistent with GenerateResponse model.
|
| 32 |
+
return GenerateResponse(
|
| 33 |
+
code="",
|
| 34 |
+
explanation=(
|
| 35 |
+
"Generation failed.\n\n"
|
| 36 |
+
f"Error: {exc}\n"
|
| 37 |
+
"Hint: Check model name/access and internet connection for first model download."
|
| 38 |
+
),
|
| 39 |
+
confidence=0.0,
|
| 40 |
+
important_tokens=[],
|
| 41 |
+
relevancy_score=0.0,
|
| 42 |
+
hallucination=True,
|
| 43 |
+
latency_ms=0,
|
| 44 |
+
)
|
api/security.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Simple API key and in-memory rate limiting."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import time
|
| 6 |
+
from collections import defaultdict, deque
|
| 7 |
+
|
| 8 |
+
from fastapi import Header, HTTPException
|
| 9 |
+
|
| 10 |
+
from src.config import settings
|
| 11 |
+
|
| 12 |
+
_request_windows: dict[str, deque[float]] = defaultdict(deque)
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def verify_api_key(x_api_key: str = Header(default="")):
|
| 16 |
+
"""Enforce API key when configured."""
|
| 17 |
+
if settings.api_key and x_api_key != settings.api_key:
|
| 18 |
+
raise HTTPException(status_code=401, detail="Invalid API key")
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def check_rate_limit(client_id: str) -> None:
|
| 22 |
+
"""Allow at most N requests per minute for each client id."""
|
| 23 |
+
now = time.time()
|
| 24 |
+
window_start = now - 60
|
| 25 |
+
q = _request_windows[client_id]
|
| 26 |
+
|
| 27 |
+
while q and q[0] < window_start:
|
| 28 |
+
q.popleft()
|
| 29 |
+
|
| 30 |
+
if len(q) >= settings.rate_limit_per_minute:
|
| 31 |
+
raise HTTPException(status_code=429, detail="Rate limit exceeded")
|
| 32 |
+
|
| 33 |
+
q.append(now)
|
app.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Gradio app for Hugging Face Spaces deployment."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
|
| 7 |
+
import gradio as gr
|
| 8 |
+
|
| 9 |
+
from src.pipeline import CodingLLMPipeline
|
| 10 |
+
|
| 11 |
+
pipeline = CodingLLMPipeline()
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def generate(instruction: str, user_input: str):
|
| 15 |
+
try:
|
| 16 |
+
result = pipeline.run(instruction, user_input)
|
| 17 |
+
return json.dumps(result, indent=2)
|
| 18 |
+
except Exception as exc:
|
| 19 |
+
return json.dumps(
|
| 20 |
+
{
|
| 21 |
+
"code": "",
|
| 22 |
+
"explanation": f"Generation failed: {exc}",
|
| 23 |
+
"confidence": 0.0,
|
| 24 |
+
"important_tokens": [],
|
| 25 |
+
"relevancy_score": 0.0,
|
| 26 |
+
"hallucination": True,
|
| 27 |
+
"latency_ms": 0,
|
| 28 |
+
},
|
| 29 |
+
indent=2,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
demo = gr.Interface(
|
| 34 |
+
fn=generate,
|
| 35 |
+
inputs=[
|
| 36 |
+
gr.Textbox(label="Instruction", placeholder="Explain, generate or debug code..."),
|
| 37 |
+
gr.Textbox(label="Input", lines=12, placeholder="Paste code or context"),
|
| 38 |
+
],
|
| 39 |
+
outputs=gr.Code(label="JSON Output", language="json"),
|
| 40 |
+
title="Advanced Coding LLM",
|
| 41 |
+
description="Fast coding assistant with confidence, relevancy, and hallucination checks.",
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
if __name__ == "__main__":
|
| 45 |
+
demo.launch()
|
client_example.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Small client example to call POST /generate."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
import requests
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
BASE_URL = os.getenv("CODING_LLM_URL", "http://127.0.0.1:8000")
|
| 14 |
+
API_KEY = os.getenv("API_KEY", "")
|
| 15 |
+
|
| 16 |
+
payload = {
|
| 17 |
+
"instruction": "Fix this function and explain",
|
| 18 |
+
"input": "def add(a,b) return a+b",
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
headers = {"Content-Type": "application/json"}
|
| 22 |
+
if API_KEY:
|
| 23 |
+
headers["x-api-key"] = API_KEY
|
| 24 |
+
|
| 25 |
+
resp = requests.post(f"{BASE_URL}/generate", headers=headers, json=payload, timeout=60)
|
| 26 |
+
if resp.status_code == 401:
|
| 27 |
+
raise PermissionError("Unauthorized (401). Ensure API_KEY matches server configuration.")
|
| 28 |
+
resp.raise_for_status()
|
| 29 |
+
print(json.dumps(resp.json(), indent=2))
|
data/sample_snippets.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"snippet": "def binary_search(arr, target):\n lo, hi = 0, len(arr) - 1\n while lo <= hi:\n mid = (lo + hi) // 2\n if arr[mid] == target:\n return mid\n if arr[mid] < target:\n lo = mid + 1\n else:\n hi = mid - 1\n return -1"
|
| 4 |
+
},
|
| 5 |
+
{
|
| 6 |
+
"snippet": "def is_palindrome(s):\n clean = ''.join(ch.lower() for ch in s if ch.isalnum())\n return clean == clean[::-1]"
|
| 7 |
+
},
|
| 8 |
+
{
|
| 9 |
+
"snippet": "def safe_div(a, b):\n if b == 0:\n raise ValueError('Division by zero')\n return a / b"
|
| 10 |
+
}
|
| 11 |
+
]
|
docker-compose.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: "3.9"
|
| 2 |
+
|
| 3 |
+
services:
|
| 4 |
+
coding-llm-api:
|
| 5 |
+
build: .
|
| 6 |
+
container_name: coding-llm-api
|
| 7 |
+
env_file:
|
| 8 |
+
- .env
|
| 9 |
+
ports:
|
| 10 |
+
- "8000:8000"
|
| 11 |
+
restart: unless-stopped
|
instruction.md
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Advanced Coding LLM - Complete Instructions
|
| 2 |
+
|
| 3 |
+
This document provides full setup, run, validation, optimization, and deployment steps for the `coding-llm` project.
|
| 4 |
+
|
| 5 |
+
## 1) Prerequisites
|
| 6 |
+
|
| 7 |
+
- Python 3.10+ (recommended 3.11/3.12)
|
| 8 |
+
- Git
|
| 9 |
+
- Internet access for first model download
|
| 10 |
+
- Optional: Docker Desktop
|
| 11 |
+
- Optional: Hugging Face account and access token
|
| 12 |
+
|
| 13 |
+
## 2) Project Setup
|
| 14 |
+
|
| 15 |
+
From project root:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
cd "C:\Users\GIRISH\OneDrive\Desktop\AI model_14_04_26\coding-llm"
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
Create environment file:
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
copy .env.example .env
|
| 25 |
+
```
|
| 26 |
+
|
| 27 |
+
Install dependencies:
|
| 28 |
+
|
| 29 |
+
```bash
|
| 30 |
+
python tasks.py install
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
## 3) Configure `.env`
|
| 34 |
+
|
| 35 |
+
Open `.env` and set values:
|
| 36 |
+
|
| 37 |
+
- `MODEL_NAME=Qwen/Qwen2.5-Coder-1.5B-Instruct`
|
| 38 |
+
- `FALLBACK_MODEL_NAME=Qwen/Qwen2.5-Coder-0.5B-Instruct`
|
| 39 |
+
- `FINAL_FALLBACK_MODEL_NAME=sshleifer/tiny-gpt2` (optional emergency fallback)
|
| 40 |
+
- `FORCE_MOCK_MODE=false` (true for instant test mode)
|
| 41 |
+
- `API_KEY=<your_secret_key>`
|
| 42 |
+
- `RATE_LIMIT_PER_MINUTE=30`
|
| 43 |
+
- `USE_RAG=true`
|
| 44 |
+
|
| 45 |
+
## 4) Run API Locally
|
| 46 |
+
|
| 47 |
+
```bash
|
| 48 |
+
python tasks.py run
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
Server runs at:
|
| 52 |
+
|
| 53 |
+
- `http://127.0.0.1:8000`
|
| 54 |
+
|
| 55 |
+
Health endpoint:
|
| 56 |
+
|
| 57 |
+
- `GET http://127.0.0.1:8000/health`
|
| 58 |
+
|
| 59 |
+
## 5) Run Smoke Tests
|
| 60 |
+
|
| 61 |
+
### Full smoke test
|
| 62 |
+
|
| 63 |
+
```bash
|
| 64 |
+
python smoke_test.py
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Health-only smoke test
|
| 68 |
+
|
| 69 |
+
```bash
|
| 70 |
+
set SMOKE_SKIP_GENERATE=true
|
| 71 |
+
python smoke_test.py
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
### Combined run-and-test command
|
| 75 |
+
|
| 76 |
+
```bash
|
| 77 |
+
python tasks.py serve-smoke
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
This starts server, executes smoke test, and shuts server down automatically.
|
| 81 |
+
|
| 82 |
+
## 6) If Generation Is Slow on First Run
|
| 83 |
+
|
| 84 |
+
First `/generate` may take long due to model download/warmup.
|
| 85 |
+
|
| 86 |
+
Options:
|
| 87 |
+
|
| 88 |
+
- Increase timeout:
|
| 89 |
+
- `set SMOKE_TIMEOUT=900`
|
| 90 |
+
- Use mock mode for quick validation:
|
| 91 |
+
- set `FORCE_MOCK_MODE=true`
|
| 92 |
+
- Run full mode after model cache is ready.
|
| 93 |
+
|
| 94 |
+
## 7) API Usage
|
| 95 |
+
|
| 96 |
+
### Endpoint
|
| 97 |
+
|
| 98 |
+
- `POST /generate`
|
| 99 |
+
|
| 100 |
+
### Input JSON
|
| 101 |
+
|
| 102 |
+
```json
|
| 103 |
+
{
|
| 104 |
+
"instruction": "Fix this code",
|
| 105 |
+
"input": "def add(a,b) return a+b"
|
| 106 |
+
}
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### Required Header (if API key enabled)
|
| 110 |
+
|
| 111 |
+
- `x-api-key: <API_KEY>`
|
| 112 |
+
|
| 113 |
+
### Output JSON
|
| 114 |
+
|
| 115 |
+
```json
|
| 116 |
+
{
|
| 117 |
+
"code": "...",
|
| 118 |
+
"explanation": "...",
|
| 119 |
+
"confidence": 0.0,
|
| 120 |
+
"important_tokens": ["..."],
|
| 121 |
+
"relevancy_score": 0.0,
|
| 122 |
+
"hallucination": false,
|
| 123 |
+
"latency_ms": 0
|
| 124 |
+
}
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
## 8) Docker Deployment
|
| 128 |
+
|
| 129 |
+
```bash
|
| 130 |
+
copy .env.example .env
|
| 131 |
+
docker compose up --build -d
|
| 132 |
+
```
|
| 133 |
+
|
| 134 |
+
Validate:
|
| 135 |
+
|
| 136 |
+
```bash
|
| 137 |
+
python smoke_test.py
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
Stop:
|
| 141 |
+
|
| 142 |
+
```bash
|
| 143 |
+
docker compose down
|
| 144 |
+
```
|
| 145 |
+
|
| 146 |
+
## 9) Hugging Face Space Deployment
|
| 147 |
+
|
| 148 |
+
Create HF token (write permission), then:
|
| 149 |
+
|
| 150 |
+
```bash
|
| 151 |
+
python tasks.py hf-upload --repo-id <username/coding-llm-space> --token <HF_TOKEN>
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
After upload, configure Space variables/secrets:
|
| 155 |
+
|
| 156 |
+
- `MODEL_NAME`
|
| 157 |
+
- `FALLBACK_MODEL_NAME`
|
| 158 |
+
- `FORCE_MOCK_MODE`
|
| 159 |
+
- `API_KEY` (if needed in your architecture)
|
| 160 |
+
|
| 161 |
+
## 10) Production Hardening Checklist
|
| 162 |
+
|
| 163 |
+
- Keep `API_KEY` enabled
|
| 164 |
+
- Keep rate limiting enabled (`RATE_LIMIT_PER_MINUTE`)
|
| 165 |
+
- Put API behind HTTPS reverse proxy
|
| 166 |
+
- Add logging and monitoring
|
| 167 |
+
- Pin model versions if strict reproducibility required
|
| 168 |
+
- Use `FORCE_MOCK_MODE=false` in production
|
| 169 |
+
|
| 170 |
+
## 11) Common Troubleshooting
|
| 171 |
+
|
| 172 |
+
- `WinError 10061`:
|
| 173 |
+
- API server is not running. Start with `python tasks.py run`.
|
| 174 |
+
- `401 Unauthorized`:
|
| 175 |
+
- `x-api-key` does not match server `API_KEY`.
|
| 176 |
+
- Health works but generate times out:
|
| 177 |
+
- model is still downloading/warming up.
|
| 178 |
+
- Low-quality gibberish output:
|
| 179 |
+
- likely fallback model path used; verify `.env` model names.
|
| 180 |
+
|
| 181 |
+
## 12) Recommended Daily Commands
|
| 182 |
+
|
| 183 |
+
- Install/update: `python tasks.py install`
|
| 184 |
+
- Run API: `python tasks.py run`
|
| 185 |
+
- Smoke: `python tasks.py smoke`
|
| 186 |
+
- Run+smoke: `python tasks.py serve-smoke`
|
| 187 |
+
- Docker up/down: `python tasks.py docker-up` / `python tasks.py docker-down`
|
requirements.txt
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.115.0
|
| 2 |
+
uvicorn>=0.30.0
|
| 3 |
+
transformers>=4.44.0
|
| 4 |
+
torch>=2.3.0
|
| 5 |
+
accelerate>=0.33.0
|
| 6 |
+
sentence-transformers>=3.0.0
|
| 7 |
+
faiss-cpu>=1.8.0
|
| 8 |
+
numpy>=1.26.0
|
| 9 |
+
scikit-learn>=1.5.0
|
| 10 |
+
pydantic>=2.8.0
|
| 11 |
+
huggingface-hub>=0.24.0
|
| 12 |
+
gradio>=4.40.0
|
| 13 |
+
datasets>=2.20.0
|
| 14 |
+
peft>=0.12.0
|
| 15 |
+
requests>=2.32.0
|
| 16 |
+
python-dotenv>=1.0.1
|
run_api.bat
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
if exist .env (
|
| 4 |
+
for /f "usebackq delims=" %%a in (".env") do set "%%a"
|
| 5 |
+
)
|
| 6 |
+
uvicorn api.main:app --host 0.0.0.0 --port 8000
|
run_space.bat
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
setlocal
|
| 3 |
+
if exist .env (
|
| 4 |
+
for /f "usebackq delims=" %%a in (".env") do set "%%a"
|
| 5 |
+
)
|
| 6 |
+
python app.py
|
run_tasks.bat
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
python tasks.py %*
|
smoke_test.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Automated smoke test for /health and /generate endpoints."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
|
| 10 |
+
import requests
|
| 11 |
+
from dotenv import load_dotenv
|
| 12 |
+
|
| 13 |
+
load_dotenv()
|
| 14 |
+
|
| 15 |
+
BASE_URL = os.getenv("CODING_LLM_URL", "http://127.0.0.1:8000")
|
| 16 |
+
API_KEY = os.getenv("API_KEY", "")
|
| 17 |
+
TIMEOUT = int(os.getenv("SMOKE_TIMEOUT", "300"))
|
| 18 |
+
SKIP_GENERATE = os.getenv("SMOKE_SKIP_GENERATE", "false").lower() == "true"
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _headers() -> dict[str, str]:
|
| 22 |
+
headers = {"Content-Type": "application/json"}
|
| 23 |
+
if API_KEY:
|
| 24 |
+
headers["x-api-key"] = API_KEY
|
| 25 |
+
return headers
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def wait_for_health() -> dict:
|
| 29 |
+
candidate_urls = [BASE_URL]
|
| 30 |
+
if "127.0.0.1" in BASE_URL:
|
| 31 |
+
candidate_urls.append(BASE_URL.replace("127.0.0.1", "localhost"))
|
| 32 |
+
elif "localhost" in BASE_URL:
|
| 33 |
+
candidate_urls.append(BASE_URL.replace("localhost", "127.0.0.1"))
|
| 34 |
+
|
| 35 |
+
deadline = time.time() + TIMEOUT
|
| 36 |
+
last_errors: list[str] = []
|
| 37 |
+
while time.time() < deadline:
|
| 38 |
+
for url in candidate_urls:
|
| 39 |
+
try:
|
| 40 |
+
resp = requests.get(f"{url}/health", timeout=10)
|
| 41 |
+
if resp.status_code == 200:
|
| 42 |
+
return resp.json()
|
| 43 |
+
last_errors.append(f"{url}/health -> HTTP {resp.status_code}")
|
| 44 |
+
except requests.RequestException as exc:
|
| 45 |
+
last_errors.append(f"{url}/health -> {exc}")
|
| 46 |
+
time.sleep(2)
|
| 47 |
+
preview = "; ".join(last_errors[-5:]) if last_errors else "No response details captured."
|
| 48 |
+
raise TimeoutError(f"Health check timeout. API did not become ready. Recent errors: {preview}")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_generate() -> dict:
|
| 52 |
+
payload = {
|
| 53 |
+
"instruction": "Fix this function and explain briefly",
|
| 54 |
+
"input": "def add(a,b) return a+b",
|
| 55 |
+
}
|
| 56 |
+
resp = requests.post(
|
| 57 |
+
f"{BASE_URL}/generate",
|
| 58 |
+
headers=_headers(),
|
| 59 |
+
json=payload,
|
| 60 |
+
timeout=TIMEOUT,
|
| 61 |
+
)
|
| 62 |
+
if resp.status_code == 401:
|
| 63 |
+
raise PermissionError(
|
| 64 |
+
"Unauthorized (401). Set API_KEY in .env or environment before running smoke_test.py."
|
| 65 |
+
)
|
| 66 |
+
resp.raise_for_status()
|
| 67 |
+
body = resp.json()
|
| 68 |
+
|
| 69 |
+
required_keys = [
|
| 70 |
+
"code",
|
| 71 |
+
"explanation",
|
| 72 |
+
"confidence",
|
| 73 |
+
"important_tokens",
|
| 74 |
+
"relevancy_score",
|
| 75 |
+
"hallucination",
|
| 76 |
+
"latency_ms",
|
| 77 |
+
]
|
| 78 |
+
missing = [k for k in required_keys if k not in body]
|
| 79 |
+
if missing:
|
| 80 |
+
raise ValueError(f"Missing keys in /generate response: {missing}")
|
| 81 |
+
return body
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def main():
|
| 85 |
+
print(f"[smoke] waiting for {BASE_URL}/health ...")
|
| 86 |
+
health = wait_for_health()
|
| 87 |
+
print("[smoke] health ok:", json.dumps(health))
|
| 88 |
+
|
| 89 |
+
if SKIP_GENERATE:
|
| 90 |
+
print("[smoke] skipping /generate (SMOKE_SKIP_GENERATE=true)")
|
| 91 |
+
print("[smoke] SUCCESS")
|
| 92 |
+
return
|
| 93 |
+
|
| 94 |
+
print("[smoke] running /generate ... (first run may download model)")
|
| 95 |
+
result = test_generate()
|
| 96 |
+
print("[smoke] /generate ok")
|
| 97 |
+
print(json.dumps(result, indent=2)[:2000])
|
| 98 |
+
print("[smoke] SUCCESS")
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
if __name__ == "__main__":
|
| 102 |
+
try:
|
| 103 |
+
main()
|
| 104 |
+
except Exception as exc:
|
| 105 |
+
msg = str(exc)
|
| 106 |
+
if "Read timed out" in msg:
|
| 107 |
+
msg = (
|
| 108 |
+
f"{msg}\nHint: model warmup is still running. "
|
| 109 |
+
"Wait longer, increase SMOKE_TIMEOUT, or restart API with FORCE_MOCK_MODE=true for instant checks."
|
| 110 |
+
)
|
| 111 |
+
print(f"[smoke] FAILED: {msg}")
|
| 112 |
+
sys.exit(1)
|
specification_file.md
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Advanced Coding LLM - Technical Specification
|
| 2 |
+
|
| 3 |
+
## 1) Objective
|
| 4 |
+
|
| 5 |
+
Build a production-ready coding assistant API deployable locally and on Hugging Face, supporting:
|
| 6 |
+
|
| 7 |
+
- Code generation
|
| 8 |
+
- Debugging/fixing buggy code
|
| 9 |
+
- Code explanation
|
| 10 |
+
- Instruction following
|
| 11 |
+
- Explainability signals
|
| 12 |
+
- Relevancy scoring
|
| 13 |
+
- Hallucination checks
|
| 14 |
+
- Optional RAG
|
| 15 |
+
|
| 16 |
+
## 2) Core Functional Requirements
|
| 17 |
+
|
| 18 |
+
### 2.1 Model
|
| 19 |
+
|
| 20 |
+
- Primary model: `Qwen/Qwen2.5-Coder-1.5B-Instruct`
|
| 21 |
+
- Fallback model: `Qwen/Qwen2.5-Coder-0.5B-Instruct`
|
| 22 |
+
- Emergency fallback mode supported (mock path available)
|
| 23 |
+
- Architecture compatible with future LoRA integration (`src/lora_prepare.py`)
|
| 24 |
+
|
| 25 |
+
### 2.2 API
|
| 26 |
+
|
| 27 |
+
- Framework: FastAPI
|
| 28 |
+
- Endpoint: `POST /generate`
|
| 29 |
+
- Health: `GET /health`
|
| 30 |
+
- Input schema:
|
| 31 |
+
- `instruction: str`
|
| 32 |
+
- `input: str`
|
| 33 |
+
- Output schema:
|
| 34 |
+
- `code: str`
|
| 35 |
+
- `explanation: str`
|
| 36 |
+
- `confidence: float`
|
| 37 |
+
- `important_tokens: list[str]`
|
| 38 |
+
- `relevancy_score: float`
|
| 39 |
+
- `hallucination: bool`
|
| 40 |
+
- `latency_ms: int`
|
| 41 |
+
|
| 42 |
+
### 2.3 Explainability
|
| 43 |
+
|
| 44 |
+
- Confidence from token probabilities over generated tokens
|
| 45 |
+
- Important tokens extracted from low-probability tokens
|
| 46 |
+
|
| 47 |
+
### 2.4 Relevancy
|
| 48 |
+
|
| 49 |
+
- Query-to-output semantic score using TF-IDF + cosine similarity
|
| 50 |
+
|
| 51 |
+
### 2.5 Hallucination Checks
|
| 52 |
+
|
| 53 |
+
- Python syntax validation (`ast.parse`)
|
| 54 |
+
- Runtime smoke execution for Python-like outputs
|
| 55 |
+
- Skip runtime execution for non-Python-like outputs
|
| 56 |
+
|
| 57 |
+
### 2.6 RAG
|
| 58 |
+
|
| 59 |
+
- Basic retrieval from local snippets dataset
|
| 60 |
+
- FAISS index over normalized TF-IDF vectors
|
| 61 |
+
- Inject top-k snippets into prompt context
|
| 62 |
+
|
| 63 |
+
## 3) Non-Functional Requirements
|
| 64 |
+
|
| 65 |
+
- Runnable on local workstation
|
| 66 |
+
- Supports no-training initial deployment
|
| 67 |
+
- Lazy-load model to reduce startup failures
|
| 68 |
+
- Graceful fallback response when model unavailable
|
| 69 |
+
- Windows-compatible developer workflow
|
| 70 |
+
|
| 71 |
+
## 4) Security Requirements
|
| 72 |
+
|
| 73 |
+
- API key auth via `x-api-key` (if configured)
|
| 74 |
+
- Per-IP in-memory rate limiting
|
| 75 |
+
- No secrets committed to repository (`.env` ignored)
|
| 76 |
+
|
| 77 |
+
## 5) Performance Requirements
|
| 78 |
+
|
| 79 |
+
- Lazy model initialization
|
| 80 |
+
- Runtime checks bounded by timeout
|
| 81 |
+
- Optional mock mode (`FORCE_MOCK_MODE=true`) for fast operational checks
|
| 82 |
+
|
| 83 |
+
## 6) Deployment Requirements
|
| 84 |
+
|
| 85 |
+
### Local
|
| 86 |
+
|
| 87 |
+
- `python tasks.py install`
|
| 88 |
+
- `python tasks.py run`
|
| 89 |
+
|
| 90 |
+
### Docker
|
| 91 |
+
|
| 92 |
+
- `docker compose up --build -d`
|
| 93 |
+
|
| 94 |
+
### Hugging Face Space
|
| 95 |
+
|
| 96 |
+
- `python tasks.py hf-upload --repo-id <id> --token <token>`
|
| 97 |
+
- Gradio entrypoint in `app.py`
|
| 98 |
+
|
| 99 |
+
## 7) Project Structure
|
| 100 |
+
|
| 101 |
+
```text
|
| 102 |
+
coding-llm/
|
| 103 |
+
│── data/
|
| 104 |
+
│── src/
|
| 105 |
+
│── api/
|
| 106 |
+
│── requirements.txt
|
| 107 |
+
│── README.md
|
| 108 |
+
│── instruction.md
|
| 109 |
+
│── specification_file.md
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
## 8) Module Responsibilities
|
| 113 |
+
|
| 114 |
+
- `api/main.py`: API routes and response wiring
|
| 115 |
+
- `api/security.py`: API key + rate limiting
|
| 116 |
+
- `src/config.py`: environment-driven settings
|
| 117 |
+
- `src/model_loader.py`: model/fallback loading
|
| 118 |
+
- `src/generator.py`: generation + confidence extraction
|
| 119 |
+
- `src/pipeline.py`: orchestration layer
|
| 120 |
+
- `src/rag.py`: snippet retrieval
|
| 121 |
+
- `src/relevancy.py`: relevancy score computation
|
| 122 |
+
- `src/hallucination.py`: syntax/runtime checks
|
| 123 |
+
- `src/lora_prepare.py`: LoRA adapter hook
|
| 124 |
+
- `app.py`: Gradio UI for HF Spaces
|
| 125 |
+
- `upload_to_hf.py`: HF deployment uploader
|
| 126 |
+
- `tasks.py`: command runner
|
| 127 |
+
- `smoke_test.py`: runtime integration validation
|
| 128 |
+
|
| 129 |
+
## 9) Operational Modes
|
| 130 |
+
|
| 131 |
+
- **Real Model Mode**
|
| 132 |
+
- `FORCE_MOCK_MODE=false`
|
| 133 |
+
- Uses HF model loading and generation
|
| 134 |
+
- **Mock Mode**
|
| 135 |
+
- `FORCE_MOCK_MODE=true`
|
| 136 |
+
- Returns deterministic fallback output for reliability testing
|
| 137 |
+
|
| 138 |
+
## 10) Validation and QA
|
| 139 |
+
|
| 140 |
+
- Static compile check with `python -m compileall`
|
| 141 |
+
- Lint diagnostics via editor/tooling
|
| 142 |
+
- Smoke checks:
|
| 143 |
+
- health endpoint reachable
|
| 144 |
+
- generate endpoint returns full schema
|
| 145 |
+
|
| 146 |
+
## 11) Known Constraints
|
| 147 |
+
|
| 148 |
+
- First generation may be slow due to model download/warmup
|
| 149 |
+
- Quality depends on available model and decoding configuration
|
| 150 |
+
- In-memory rate limiter is single-process only
|
| 151 |
+
|
| 152 |
+
## 12) Future Enhancements
|
| 153 |
+
|
| 154 |
+
- Redis-backed distributed rate limiting
|
| 155 |
+
- Better language-aware hallucination tests
|
| 156 |
+
- Prompt templates per task type
|
| 157 |
+
- Streaming token responses
|
| 158 |
+
- Persistent vector store (Chroma/FAISS on-disk)
|
| 159 |
+
- CI/CD workflow for automated deploy/test
|
src/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Source package for coding-llm."""
|
| 2 |
+
|
| 3 |
+
import os
|
| 4 |
+
|
| 5 |
+
# Avoid TensorFlow/Keras import path conflicts in mixed Python environments.
|
| 6 |
+
os.environ.setdefault("TRANSFORMERS_NO_TF", "1")
|
src/__pycache__/__init__.cpython-312.pyc.2197825028720
ADDED
|
Binary file (168 Bytes). View file
|
|
|
src/__pycache__/config.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.02 kB). View file
|
|
|
src/__pycache__/generator.cpython-312.pyc.2197825028720
ADDED
|
Binary file (3.11 kB). View file
|
|
|
src/__pycache__/hallucination.cpython-312.pyc.2197822599920
ADDED
|
Binary file (2.66 kB). View file
|
|
|
src/__pycache__/lora_prepare.cpython-312.pyc.2197825028720
ADDED
|
Binary file (708 Bytes). View file
|
|
|
src/__pycache__/model_loader.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.69 kB). View file
|
|
|
src/__pycache__/pipeline.cpython-312.pyc.2197825028720
ADDED
|
Binary file (2.81 kB). View file
|
|
|
src/__pycache__/prompts.cpython-312.pyc.2197825028720
ADDED
|
Binary file (878 Bytes). View file
|
|
|
src/__pycache__/rag.cpython-312.pyc.2197825028720
ADDED
|
Binary file (3.2 kB). View file
|
|
|
src/__pycache__/relevancy.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.41 kB). View file
|
|
|
src/__pycache__/schemas.cpython-312.pyc.2197825028720
ADDED
|
Binary file (1.04 kB). View file
|
|
|
src/config.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Project-level configuration values."""
|
| 2 |
+
|
| 3 |
+
from dataclasses import dataclass
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
load_dotenv()
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
@dataclass(frozen=True)
|
| 12 |
+
class Settings:
|
| 13 |
+
"""Central settings object for model and runtime controls."""
|
| 14 |
+
|
| 15 |
+
model_name: str = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-Coder-1.5B-Instruct")
|
| 16 |
+
fallback_model_name: str = os.getenv("FALLBACK_MODEL_NAME", "Qwen/Qwen2.5-Coder-0.5B-Instruct")
|
| 17 |
+
final_fallback_model_name: str = os.getenv("FINAL_FALLBACK_MODEL_NAME", "sshleifer/tiny-gpt2")
|
| 18 |
+
embedding_model: str = os.getenv("EMBEDDING_MODEL", "sentence-transformers/all-MiniLM-L6-v2")
|
| 19 |
+
max_new_tokens: int = int(os.getenv("MAX_NEW_TOKENS", "256"))
|
| 20 |
+
temperature: float = float(os.getenv("TEMPERATURE", "0.2"))
|
| 21 |
+
top_p: float = float(os.getenv("TOP_P", "0.95"))
|
| 22 |
+
use_rag: bool = os.getenv("USE_RAG", "true").lower() == "true"
|
| 23 |
+
force_mock_mode: bool = os.getenv("FORCE_MOCK_MODE", "false").lower() == "true"
|
| 24 |
+
api_key: str = os.getenv("API_KEY", "")
|
| 25 |
+
rate_limit_per_minute: int = int(os.getenv("RATE_LIMIT_PER_MINUTE", "30"))
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
settings = Settings()
|
src/generator.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Text generation and output parsing."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import List, Tuple
|
| 7 |
+
|
| 8 |
+
from torch.nn.functional import softmax
|
| 9 |
+
|
| 10 |
+
from src.config import settings
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass
|
| 14 |
+
class GenerationResult:
|
| 15 |
+
code: str
|
| 16 |
+
explanation: str
|
| 17 |
+
confidence: float
|
| 18 |
+
important_tokens: List[str]
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _split_code_and_explanation(text: str) -> Tuple[str, str]:
|
| 22 |
+
marker = "Explanation:"
|
| 23 |
+
if marker in text:
|
| 24 |
+
code, explanation = text.split(marker, 1)
|
| 25 |
+
return code.strip(), explanation.strip()
|
| 26 |
+
return text.strip(), "Model did not provide explicit explanation."
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def generate_response(model_bundle, prompt: str) -> GenerationResult:
|
| 30 |
+
"""Generate model response with token-level confidence signals."""
|
| 31 |
+
if getattr(model_bundle, "is_mock", False):
|
| 32 |
+
# Keep API runnable even when model download/loading is unavailable.
|
| 33 |
+
fallback_code = (
|
| 34 |
+
"def solve_task(input_data):\n"
|
| 35 |
+
" \"\"\"Fallback implementation when model is unavailable.\"\"\"\n"
|
| 36 |
+
" return input_data\n"
|
| 37 |
+
)
|
| 38 |
+
fallback_explanation = (
|
| 39 |
+
"Running in mock fallback mode because no pretrained model could be loaded. "
|
| 40 |
+
"Set MODEL_NAME/FALLBACK_MODEL_NAME and ensure network/model access."
|
| 41 |
+
)
|
| 42 |
+
load_error = getattr(model_bundle, "load_error", "")
|
| 43 |
+
if load_error:
|
| 44 |
+
fallback_explanation = f"{fallback_explanation}\n\nLoader error: {load_error}"
|
| 45 |
+
return GenerationResult(
|
| 46 |
+
code=fallback_code,
|
| 47 |
+
explanation=fallback_explanation,
|
| 48 |
+
confidence=0.15,
|
| 49 |
+
important_tokens=["<mock-fallback>"],
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
tokenizer = model_bundle.tokenizer
|
| 53 |
+
model = model_bundle.model
|
| 54 |
+
|
| 55 |
+
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
|
| 56 |
+
outputs = model.generate(
|
| 57 |
+
**inputs,
|
| 58 |
+
max_new_tokens=settings.max_new_tokens,
|
| 59 |
+
temperature=settings.temperature,
|
| 60 |
+
top_p=settings.top_p,
|
| 61 |
+
do_sample=True,
|
| 62 |
+
return_dict_in_generate=True,
|
| 63 |
+
output_scores=True,
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
generated_ids = outputs.sequences[0][inputs["input_ids"].shape[1] :]
|
| 67 |
+
generated_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
|
| 68 |
+
|
| 69 |
+
token_probs = []
|
| 70 |
+
important = []
|
| 71 |
+
for step_scores, token_id in zip(outputs.scores, generated_ids):
|
| 72 |
+
probs = softmax(step_scores[0], dim=-1)
|
| 73 |
+
p = probs[token_id].item()
|
| 74 |
+
token_probs.append(p)
|
| 75 |
+
if p < 0.30:
|
| 76 |
+
important.append(tokenizer.decode([token_id]))
|
| 77 |
+
|
| 78 |
+
confidence = float(sum(token_probs) / max(len(token_probs), 1))
|
| 79 |
+
code, explanation = _split_code_and_explanation(generated_text)
|
| 80 |
+
|
| 81 |
+
return GenerationResult(
|
| 82 |
+
code=code,
|
| 83 |
+
explanation=explanation,
|
| 84 |
+
confidence=confidence,
|
| 85 |
+
important_tokens=important[:20],
|
| 86 |
+
)
|
src/hallucination.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Simple hallucination checks for generated code."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import ast
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
import tempfile
|
| 9 |
+
from dataclasses import dataclass
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
@dataclass
|
| 13 |
+
class HallucinationCheckResult:
|
| 14 |
+
hallucination: bool
|
| 15 |
+
reason: str
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _python_syntax_check(code: str) -> HallucinationCheckResult:
|
| 19 |
+
try:
|
| 20 |
+
ast.parse(code)
|
| 21 |
+
return HallucinationCheckResult(False, "Syntax is valid.")
|
| 22 |
+
except SyntaxError as exc:
|
| 23 |
+
return HallucinationCheckResult(True, f"Syntax error: {exc}")
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def _python_runtime_smoke_test(code: str) -> HallucinationCheckResult:
|
| 27 |
+
try:
|
| 28 |
+
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False, encoding="utf-8") as tmp:
|
| 29 |
+
tmp.write(code)
|
| 30 |
+
tmp_path = tmp.name
|
| 31 |
+
proc = subprocess.run(
|
| 32 |
+
[sys.executable, tmp_path],
|
| 33 |
+
capture_output=True,
|
| 34 |
+
text=True,
|
| 35 |
+
timeout=4,
|
| 36 |
+
check=False,
|
| 37 |
+
)
|
| 38 |
+
if proc.returncode != 0:
|
| 39 |
+
stderr = proc.stderr.strip()[:300]
|
| 40 |
+
return HallucinationCheckResult(True, f"Runtime test failed: {stderr}")
|
| 41 |
+
return HallucinationCheckResult(False, "Runtime smoke test passed.")
|
| 42 |
+
except subprocess.TimeoutExpired:
|
| 43 |
+
return HallucinationCheckResult(True, "Runtime test timed out.")
|
| 44 |
+
except Exception as exc: # pragma: no cover
|
| 45 |
+
return HallucinationCheckResult(True, f"Runtime test error: {exc}")
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def hallucination_check(code: str) -> HallucinationCheckResult:
|
| 49 |
+
"""Run syntax and runtime checks and consolidate status."""
|
| 50 |
+
# Skip expensive/irrelevant execution for obviously non-Python snippets.
|
| 51 |
+
python_hints = ("def ", "class ", "import ", "from ", "print(", "if __name__")
|
| 52 |
+
if not any(hint in code for hint in python_hints):
|
| 53 |
+
return HallucinationCheckResult(False, "Skipped runtime check for non-Python-like output.")
|
| 54 |
+
|
| 55 |
+
syntax_result = _python_syntax_check(code)
|
| 56 |
+
if syntax_result.hallucination:
|
| 57 |
+
return syntax_result
|
| 58 |
+
return _python_runtime_smoke_test(code)
|
src/lora_prepare.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Starter utilities for future LoRA fine-tuning."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from peft import LoraConfig, get_peft_model
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def attach_lora(model):
|
| 9 |
+
"""Attach a default LoRA adapter to a loaded model."""
|
| 10 |
+
config = LoraConfig(
|
| 11 |
+
r=16,
|
| 12 |
+
lora_alpha=32,
|
| 13 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
|
| 14 |
+
lora_dropout=0.05,
|
| 15 |
+
bias="none",
|
| 16 |
+
task_type="CAUSAL_LM",
|
| 17 |
+
)
|
| 18 |
+
return get_peft_model(model, config)
|
src/model_loader.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Model loading utilities."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from dataclasses import dataclass
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 9 |
+
|
| 10 |
+
from src.config import settings
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass
|
| 14 |
+
class ModelBundle:
|
| 15 |
+
"""Holds tokenizer and model together."""
|
| 16 |
+
|
| 17 |
+
tokenizer: Any
|
| 18 |
+
model: Any
|
| 19 |
+
active_model_name: str
|
| 20 |
+
is_mock: bool = False
|
| 21 |
+
load_error: str = ""
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def load_model_bundle() -> ModelBundle:
|
| 25 |
+
"""Load Qwen2.5-Coder first, then fallback if needed."""
|
| 26 |
+
if settings.force_mock_mode:
|
| 27 |
+
return ModelBundle(
|
| 28 |
+
tokenizer=None,
|
| 29 |
+
model=None,
|
| 30 |
+
active_model_name="mock-rule-based",
|
| 31 |
+
is_mock=True,
|
| 32 |
+
load_error="FORCE_MOCK_MODE=true",
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
candidate_models = [
|
| 36 |
+
settings.model_name,
|
| 37 |
+
settings.fallback_model_name,
|
| 38 |
+
settings.final_fallback_model_name,
|
| 39 |
+
]
|
| 40 |
+
last_error = None
|
| 41 |
+
|
| 42 |
+
for model_name in candidate_models:
|
| 43 |
+
try:
|
| 44 |
+
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
| 45 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 46 |
+
model_name,
|
| 47 |
+
trust_remote_code=True,
|
| 48 |
+
device_map="auto",
|
| 49 |
+
)
|
| 50 |
+
return ModelBundle(
|
| 51 |
+
tokenizer=tokenizer,
|
| 52 |
+
model=model,
|
| 53 |
+
active_model_name=model_name,
|
| 54 |
+
)
|
| 55 |
+
except Exception as exc: # pragma: no cover
|
| 56 |
+
last_error = exc
|
| 57 |
+
|
| 58 |
+
return ModelBundle(
|
| 59 |
+
tokenizer=None,
|
| 60 |
+
model=None,
|
| 61 |
+
active_model_name="mock-rule-based",
|
| 62 |
+
is_mock=True,
|
| 63 |
+
load_error=str(last_error),
|
| 64 |
+
)
|
src/pipeline.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""End-to-end orchestration for generation and validation."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import time
|
| 6 |
+
|
| 7 |
+
from src.config import settings
|
| 8 |
+
from src.generator import generate_response
|
| 9 |
+
from src.hallucination import hallucination_check
|
| 10 |
+
from src.model_loader import load_model_bundle
|
| 11 |
+
from src.prompts import SYSTEM_PROMPT, build_user_prompt
|
| 12 |
+
from src.rag import CodeRAG
|
| 13 |
+
from src.relevancy import RelevancyScorer
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class CodingLLMPipeline:
|
| 17 |
+
"""Coordinates model, RAG, explainability, and quality checks."""
|
| 18 |
+
|
| 19 |
+
def __init__(self):
|
| 20 |
+
self.model_bundle = None
|
| 21 |
+
self.relevancy = RelevancyScorer()
|
| 22 |
+
self.rag = CodeRAG() if settings.use_rag else None
|
| 23 |
+
|
| 24 |
+
def _ensure_model_loaded(self):
|
| 25 |
+
if self.model_bundle is None:
|
| 26 |
+
self.model_bundle = load_model_bundle()
|
| 27 |
+
|
| 28 |
+
def run(self, instruction: str, user_input: str) -> dict:
|
| 29 |
+
started = time.perf_counter()
|
| 30 |
+
self._ensure_model_loaded()
|
| 31 |
+
|
| 32 |
+
query_text = f"{instruction}\n{user_input}".strip()
|
| 33 |
+
retrieved_context = self.rag.retrieve(query_text) if self.rag else ""
|
| 34 |
+
|
| 35 |
+
prompt = f"{SYSTEM_PROMPT}\n\n{build_user_prompt(instruction, user_input, retrieved_context)}"
|
| 36 |
+
generation = generate_response(self.model_bundle, prompt)
|
| 37 |
+
hallucination_result = hallucination_check(generation.code)
|
| 38 |
+
relevancy_score = self.relevancy.score(query_text, generation.code)
|
| 39 |
+
explanation = generation.explanation
|
| 40 |
+
if hallucination_result.hallucination:
|
| 41 |
+
explanation = f"{generation.explanation}\n\nHallucination check reason: {hallucination_result.reason}"
|
| 42 |
+
|
| 43 |
+
latency_ms = int((time.perf_counter() - started) * 1000)
|
| 44 |
+
return {
|
| 45 |
+
"code": generation.code,
|
| 46 |
+
"explanation": explanation,
|
| 47 |
+
"confidence": round(generation.confidence, 4),
|
| 48 |
+
"important_tokens": generation.important_tokens,
|
| 49 |
+
"relevancy_score": round(relevancy_score, 4),
|
| 50 |
+
"hallucination": hallucination_result.hallucination,
|
| 51 |
+
"latency_ms": latency_ms,
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
@property
|
| 55 |
+
def active_model_name(self) -> str:
|
| 56 |
+
"""Current model name, loading lazily if needed."""
|
| 57 |
+
self._ensure_model_loaded()
|
| 58 |
+
return self.model_bundle.active_model_name
|
src/prompts.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prompt templates for coding assistant tasks."""
|
| 2 |
+
|
| 3 |
+
SYSTEM_PROMPT = """You are an advanced coding model.
|
| 4 |
+
Follow instructions exactly and produce high-quality code.
|
| 5 |
+
Always include:
|
| 6 |
+
1) Final code
|
| 7 |
+
2) A short explanation of what and why
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def build_user_prompt(instruction: str, user_input: str, retrieved_context: str = "") -> str:
|
| 12 |
+
"""Build a single prompt string with optional RAG context."""
|
| 13 |
+
context_block = f"\nRelevant snippets:\n{retrieved_context}\n" if retrieved_context else ""
|
| 14 |
+
return (
|
| 15 |
+
f"Instruction:\n{instruction}\n"
|
| 16 |
+
f"{context_block}"
|
| 17 |
+
f"\nInput:\n{user_input}\n"
|
| 18 |
+
"\nRespond with code first, then explanation."
|
| 19 |
+
)
|
src/rag.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Basic FAISS RAG retriever for code snippets."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
from typing import List
|
| 8 |
+
|
| 9 |
+
import faiss
|
| 10 |
+
import numpy as np
|
| 11 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class CodeRAG:
|
| 15 |
+
"""Loads snippets from data and serves top-k retrieval."""
|
| 16 |
+
|
| 17 |
+
def __init__(self, data_path: str = "data/sample_snippets.json"):
|
| 18 |
+
project_root = Path(__file__).resolve().parents[1]
|
| 19 |
+
self.data_path = project_root / data_path
|
| 20 |
+
self.snippets = self._load_data()
|
| 21 |
+
self.vectorizer = TfidfVectorizer(ngram_range=(1, 2), min_df=1)
|
| 22 |
+
self.snippet_vectors = self._build_vectors(self.snippets)
|
| 23 |
+
self.index = self._build_index(self.snippet_vectors)
|
| 24 |
+
|
| 25 |
+
def _load_data(self) -> List[str]:
|
| 26 |
+
if not self.data_path.exists():
|
| 27 |
+
return []
|
| 28 |
+
payload = json.loads(self.data_path.read_text(encoding="utf-8"))
|
| 29 |
+
return [item["snippet"] for item in payload]
|
| 30 |
+
|
| 31 |
+
def _build_vectors(self, snippets: List[str]) -> np.ndarray | None:
|
| 32 |
+
if not snippets:
|
| 33 |
+
return None
|
| 34 |
+
matrix = self.vectorizer.fit_transform(snippets)
|
| 35 |
+
vectors = matrix.toarray().astype(np.float32)
|
| 36 |
+
# Normalize for cosine-like similarity with IndexFlatIP.
|
| 37 |
+
norms = np.linalg.norm(vectors, axis=1, keepdims=True)
|
| 38 |
+
norms[norms == 0.0] = 1.0
|
| 39 |
+
return vectors / norms
|
| 40 |
+
|
| 41 |
+
def _build_index(self, vectors: np.ndarray | None):
|
| 42 |
+
if vectors is None:
|
| 43 |
+
return None
|
| 44 |
+
index = faiss.IndexFlatIP(vectors.shape[1])
|
| 45 |
+
index.add(vectors)
|
| 46 |
+
return index
|
| 47 |
+
|
| 48 |
+
def retrieve(self, query: str, top_k: int = 2) -> str:
|
| 49 |
+
if not self.snippets or self.index is None or self.snippet_vectors is None:
|
| 50 |
+
return ""
|
| 51 |
+
query_vec = self.vectorizer.transform([query]).toarray().astype(np.float32)
|
| 52 |
+
qnorm = np.linalg.norm(query_vec, axis=1, keepdims=True)
|
| 53 |
+
qnorm[qnorm == 0.0] = 1.0
|
| 54 |
+
query_vec = query_vec / qnorm
|
| 55 |
+
_, idx = self.index.search(query_vec, min(top_k, len(self.snippets)))
|
| 56 |
+
selected = [self.snippets[i] for i in idx[0] if i >= 0]
|
| 57 |
+
return "\n\n".join(selected)
|
src/relevancy.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Lightweight relevancy scoring without heavy embedding backends."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
from sklearn.feature_extraction.text import TfidfVectorizer
|
| 6 |
+
from sklearn.metrics.pairwise import cosine_similarity
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class RelevancyScorer:
|
| 10 |
+
"""Computes semantic relevancy between request and generated code."""
|
| 11 |
+
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self.vectorizer = TfidfVectorizer(ngram_range=(1, 2), min_df=1)
|
| 14 |
+
|
| 15 |
+
def score(self, query_text: str, generated_text: str) -> float:
|
| 16 |
+
matrix = self.vectorizer.fit_transform([query_text, generated_text])
|
| 17 |
+
return float(cosine_similarity(matrix[0], matrix[1])[0][0])
|
src/schemas.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""API schema definitions."""
|
| 2 |
+
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class GenerateRequest(BaseModel):
|
| 7 |
+
instruction: str = Field(..., min_length=1)
|
| 8 |
+
input: str = Field("", description="Source code or problem context.")
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class GenerateResponse(BaseModel):
|
| 12 |
+
code: str
|
| 13 |
+
explanation: str
|
| 14 |
+
confidence: float
|
| 15 |
+
important_tokens: list[str]
|
| 16 |
+
relevancy_score: float
|
| 17 |
+
hallucination: bool
|
| 18 |
+
latency_ms: int
|
tasks.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Simple task runner for common project workflows."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
import os
|
| 7 |
+
import subprocess
|
| 8 |
+
import sys
|
| 9 |
+
import time
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def run_cmd(cmd: list[str]) -> int:
|
| 13 |
+
print(">", " ".join(cmd))
|
| 14 |
+
return subprocess.call(cmd)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def task_install() -> int:
|
| 18 |
+
return run_cmd([sys.executable, "-m", "pip", "install", "-r", "requirements.txt"])
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def task_run() -> int:
|
| 22 |
+
return run_cmd([sys.executable, "-m", "uvicorn", "api.main:app", "--host", "0.0.0.0", "--port", "8000"])
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def task_space() -> int:
|
| 26 |
+
return run_cmd([sys.executable, "app.py"])
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def task_smoke() -> int:
|
| 30 |
+
return run_cmd([sys.executable, "smoke_test.py"])
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def task_compile() -> int:
|
| 34 |
+
return run_cmd([sys.executable, "-m", "compileall", "."])
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def task_docker_up() -> int:
|
| 38 |
+
return run_cmd(["docker", "compose", "up", "--build"])
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
def task_docker_down() -> int:
|
| 42 |
+
return run_cmd(["docker", "compose", "down"])
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def task_hf_upload(repo_id: str, token: str, private: bool) -> int:
|
| 46 |
+
cmd = [sys.executable, "upload_to_hf.py", "--repo-id", repo_id, "--token", token]
|
| 47 |
+
if private:
|
| 48 |
+
cmd.append("--private")
|
| 49 |
+
return run_cmd(cmd)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def task_serve_smoke() -> int:
|
| 53 |
+
"""Start API, run smoke test, then stop API."""
|
| 54 |
+
env = os.environ.copy()
|
| 55 |
+
server_cmd = [sys.executable, "-m", "uvicorn", "api.main:app", "--host", "127.0.0.1", "--port", "8000"]
|
| 56 |
+
print(">", " ".join(server_cmd))
|
| 57 |
+
server = subprocess.Popen(server_cmd, env=env)
|
| 58 |
+
try:
|
| 59 |
+
time.sleep(3)
|
| 60 |
+
return run_cmd([sys.executable, "smoke_test.py"])
|
| 61 |
+
finally:
|
| 62 |
+
server.terminate()
|
| 63 |
+
try:
|
| 64 |
+
server.wait(timeout=10)
|
| 65 |
+
except subprocess.TimeoutExpired:
|
| 66 |
+
server.kill()
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def main():
|
| 70 |
+
parser = argparse.ArgumentParser(description="Project task runner")
|
| 71 |
+
sub = parser.add_subparsers(dest="task", required=True)
|
| 72 |
+
|
| 73 |
+
sub.add_parser("install")
|
| 74 |
+
sub.add_parser("run")
|
| 75 |
+
sub.add_parser("space")
|
| 76 |
+
sub.add_parser("smoke")
|
| 77 |
+
sub.add_parser("compile")
|
| 78 |
+
sub.add_parser("docker-up")
|
| 79 |
+
sub.add_parser("docker-down")
|
| 80 |
+
sub.add_parser("serve-smoke")
|
| 81 |
+
hf = sub.add_parser("hf-upload")
|
| 82 |
+
hf.add_argument("--repo-id", required=True)
|
| 83 |
+
hf.add_argument("--token", required=True)
|
| 84 |
+
hf.add_argument("--private", action="store_true")
|
| 85 |
+
|
| 86 |
+
args = parser.parse_args()
|
| 87 |
+
|
| 88 |
+
if args.task == "install":
|
| 89 |
+
code = task_install()
|
| 90 |
+
elif args.task == "run":
|
| 91 |
+
code = task_run()
|
| 92 |
+
elif args.task == "space":
|
| 93 |
+
code = task_space()
|
| 94 |
+
elif args.task == "smoke":
|
| 95 |
+
code = task_smoke()
|
| 96 |
+
elif args.task == "compile":
|
| 97 |
+
code = task_compile()
|
| 98 |
+
elif args.task == "docker-up":
|
| 99 |
+
code = task_docker_up()
|
| 100 |
+
elif args.task == "docker-down":
|
| 101 |
+
code = task_docker_down()
|
| 102 |
+
elif args.task == "serve-smoke":
|
| 103 |
+
code = task_serve_smoke()
|
| 104 |
+
else:
|
| 105 |
+
code = task_hf_upload(args.repo_id, args.token, args.private)
|
| 106 |
+
|
| 107 |
+
raise SystemExit(code)
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
if __name__ == "__main__":
|
| 111 |
+
main()
|
upload_to_hf.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Upload this project as a Hugging Face Space."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import argparse
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
from huggingface_hub import HfApi, create_repo, upload_folder
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
def main():
|
| 12 |
+
parser = argparse.ArgumentParser(description="Upload coding-llm project to Hugging Face Space.")
|
| 13 |
+
parser.add_argument("--repo-id", required=True, help="Example: username/coding-llm-space")
|
| 14 |
+
parser.add_argument("--token", required=True, help="Hugging Face access token")
|
| 15 |
+
parser.add_argument("--private", action="store_true", help="Create private repo/space")
|
| 16 |
+
args = parser.parse_args()
|
| 17 |
+
|
| 18 |
+
root = Path(__file__).resolve().parent
|
| 19 |
+
api = HfApi(token=args.token)
|
| 20 |
+
create_repo(
|
| 21 |
+
repo_id=args.repo_id,
|
| 22 |
+
token=args.token,
|
| 23 |
+
repo_type="space",
|
| 24 |
+
private=args.private,
|
| 25 |
+
exist_ok=True,
|
| 26 |
+
space_sdk="gradio",
|
| 27 |
+
)
|
| 28 |
+
upload_folder(
|
| 29 |
+
folder_path=str(root),
|
| 30 |
+
repo_id=args.repo_id,
|
| 31 |
+
repo_type="space",
|
| 32 |
+
token=args.token,
|
| 33 |
+
ignore_patterns=[".git*", "__pycache__", "*.pyc"],
|
| 34 |
+
)
|
| 35 |
+
info = api.repo_info(repo_id=args.repo_id, repo_type="space")
|
| 36 |
+
print(f"Uploaded successfully: {info.id}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
if __name__ == "__main__":
|
| 40 |
+
main()
|