Upload 54 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +1 -0
- README.md +442 -6
- configs/config.yaml +105 -0
- data/indices/ast_index.pkl +3 -0
- data/indices/semantic_index.faiss +3 -0
- data/indices/semantic_index.metadata.pkl +3 -0
- models/checkpoints/lora_finetuned/README.md +207 -0
- models/checkpoints/lora_finetuned/adapter_config.json +50 -0
- models/checkpoints/lora_finetuned/adapter_model.safetensors +3 -0
- models/checkpoints/lora_finetuned/added_tokens.json +24 -0
- models/checkpoints/lora_finetuned/merges.txt +0 -0
- models/checkpoints/lora_finetuned/special_tokens_map.json +31 -0
- models/checkpoints/lora_finetuned/tokenizer.json +0 -0
- models/checkpoints/lora_finetuned/tokenizer_config.json +207 -0
- models/checkpoints/lora_finetuned/vocab.json +0 -0
- requirements.txt +10 -0
- src/__pycache__/config.cpython-312.pyc +0 -0
- src/config.py +163 -0
- src/data/data_loader.py +95 -0
- src/evaluation/__init__.py +11 -0
- src/evaluation/comparator.py +74 -0
- src/evaluation/metrics.py +258 -0
- src/evaluation/rag_evaluation.py +132 -0
- src/evaluation/visualization.py +76 -0
- src/generators/__init__.py +11 -0
- src/generators/__pycache__/__init__.cpython-312.pyc +0 -0
- src/generators/__pycache__/commit_generator.cpython-312.pyc +0 -0
- src/generators/__pycache__/doc_generator.cpython-312.pyc +0 -0
- src/generators/__pycache__/program_generator.cpython-312.pyc +0 -0
- src/generators/__pycache__/text_to_mongo_generator.cpython-312.pyc +0 -0
- src/generators/commit_generator.py +26 -0
- src/generators/doc_generator.py +47 -0
- src/generators/llm_baseline.py +85 -0
- src/generators/program_generator.py +70 -0
- src/generators/text_to_mongo_generator.py +125 -0
- src/models/__pycache__/load_model.cpython-312.pyc +0 -0
- src/models/load_model.py +263 -0
- src/plots/baseline_vs_finetuned_latency.png +0 -0
- src/plots/baseline_vs_finetuned_text_to_mongo.png +0 -0
- src/plots/text_to_mongo_baseline_metrics.png +0 -0
- src/plots/text_to_mongo_finetuned_metrics.png +0 -0
- src/rag/__pycache__/rag_pipeline.cpython-312.pyc +0 -0
- src/rag/rag_pipeline.py +166 -0
- src/vectorDB/__init__.py +13 -0
- src/vectorDB/__pycache__/__init__.cpython-312.pyc +0 -0
- src/vectorDB/__pycache__/ast_index.cpython-312.pyc +0 -0
- src/vectorDB/__pycache__/embedder.cpython-312.pyc +0 -0
- src/vectorDB/__pycache__/hybrid_retriever.cpython-312.pyc +0 -0
- src/vectorDB/__pycache__/index_manager.cpython-312.pyc +0 -0
- src/vectorDB/__pycache__/semantic_index.cpython-312.pyc +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
data/indices/semantic_index.faiss filter=lfs diff=lfs merge=lfs -text
|
README.md
CHANGED
|
@@ -1,10 +1,446 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
-
colorTo:
|
| 6 |
-
sdk:
|
|
|
|
|
|
|
| 7 |
pinned: false
|
| 8 |
---
|
| 9 |
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Text to MongoDB Generator
|
| 3 |
+
emoji: 🚀
|
| 4 |
+
colorFrom: blue
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.9.1
|
| 8 |
+
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# CodeGen: Hybrid RAG Pipeline
|
| 13 |
+
|
| 14 |
+
[](https://www.python.org/)
|
| 15 |
+
[](https://pytorch.org/)
|
| 16 |
+
[](https://opensource.org/licenses/MIT)
|
| 17 |
+
|
| 18 |
+
A capstone project (eMasters CS: AI & ML, Group 2) that fine-tunes a small open-source code language model — **Qwen2.5-Coder-0.5B-Instruct** — with **LoRA**, builds a **hybrid semantic + AST/structural retrieval (RAG)** layer on top of it, and evaluates the result against the un-adapted base model and an independently invoked LLM baseline, across text-to-SQL, documentation-generation, program-synthesis, and commit-message-generation tasks.
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## Table of Contents
|
| 23 |
+
|
| 24 |
+
- [Executive Summary](#-executive-summary)
|
| 25 |
+
- [Key Capabilities](#-key-capabilities)
|
| 26 |
+
- [System Architecture](#-system-architecture)
|
| 27 |
+
- [Pipeline Stages](#-pipeline-stages)
|
| 28 |
+
- [Benchmark Results](#-benchmark-results)
|
| 29 |
+
- [Dataset](#-dataset)
|
| 30 |
+
- [Installation](#-installation)
|
| 31 |
+
- [Quick Start](#-quick-start)
|
| 32 |
+
- [Repository Layout](#-repository-layout)
|
| 33 |
+
- [Configuration](#-configuration)
|
| 34 |
+
- [Requirements](#-requirements)
|
| 35 |
+
- [License](#-license)
|
| 36 |
+
|
| 37 |
+
---
|
| 38 |
+
|
| 39 |
+
## Executive Summary
|
| 40 |
+
|
| 41 |
+
CodeGen combines **Retrieval-Augmented Generation (RAG)** and **parameter-efficient LoRA fine-tuning** on top of a compact instruction-tuned code model to support three developer-facing capabilities: natural-language-to-SQL translation, automatic source-code documentation generation, and program/commit-message generation. The primary evaluation dataset is the **Spider** cross-domain text-to-SQL benchmark (1,034 dev-split examples), supplemented by small hand-curated example sets for documentation, program-synthesis, and commit-message generation.
|
| 42 |
+
|
| 43 |
+
Retrieval embeddings are produced by the small code LM itself (no second embedding model needs to be downloaded), and are combined with an AST/structural fingerprint index so retrieval can be reranked on structure, not just vector similarity.
|
| 44 |
+
|
| 45 |
+
---
|
| 46 |
+
|
| 47 |
+
## Key Capabilities
|
| 48 |
+
|
| 49 |
+
| Capability | Description |
|
| 50 |
+
|---|---|
|
| 51 |
+
| **Multi-Task Generation** | Program synthesis, docstring generation, Text-to-SQL translation, commit-message synthesis |
|
| 52 |
+
| **Code-LM Self-Embeddings** | Retrieval embeddings mean-pooled from the base model's own hidden states — no separate embedding model required (an external `BAAI/bge-small-en-v1.5` path is also available and config-switchable) |
|
| 53 |
+
| **Hybrid Retrieval** | FAISS (`IndexFlatIP`) dense semantic search fused with an AST/structural fingerprint reranker (α = 0.7 dense / 0.3 structural) |
|
| 54 |
+
| **LoRA Fine-Tuning** | PEFT LoRA adapter (r=16, α=32) on the attention projections, with a custom training loop (gradient checkpointing, prompt-token label masking, OOM-safe stepping) |
|
| 55 |
+
| **Small-LM vs. LLM Harness** | Pluggable large-LLM baseline generator supporting local, OpenAI-compatible, and Gemini backends |
|
| 56 |
+
| **Comprehensive Evaluation** | BLEU, ROUGE-1/L, token-F1, CodeBERTScore, SQL Exact Match, SQL Execution Accuracy, RAG gain-over-baseline, top-K sweep |
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## System Architecture
|
| 61 |
+
|
| 62 |
+
```
|
| 63 |
+
┌─────────────────────────────────────────────────────────────────┐
|
| 64 |
+
│ 1. Configuration & Data │
|
| 65 |
+
│ • Load config.yaml (paths, model, LoRA, retrieval, eval) │
|
| 66 |
+
│ • Convert & normalize Spider (dev.json + tables.json + SQLite) │
|
| 67 |
+
│ • Build retrieval corpus (1,034 question/SQL pairs) │
|
| 68 |
+
└──────────────────────────┬──────────────────────────────────────┘
|
| 69 |
+
│
|
| 70 |
+
┌──────────────────────────▼──────────────────────────────────────┐
|
| 71 |
+
│ 2. Model Loading & LoRA Fine-Tuning │
|
| 72 |
+
│ • Load Qwen2.5-Coder-0.5B-Instruct (tokenizer + base weights) │
|
| 73 |
+
│ • Attach LoRA adapter, train on illustrative task examples │
|
| 74 |
+
│ • Save / reload adapter checkpoint │
|
| 75 |
+
└──────────────────────────┬──────────────────────────────────────┘
|
| 76 |
+
│
|
| 77 |
+
┌──────────────────────────▼──────────────────────────────────────┐
|
| 78 |
+
│ 3. Generation Tasks │
|
| 79 |
+
│ • Text-to-SQL • Documentation • Program Synthesis │
|
| 80 |
+
│ • Commit Messages, evaluated for base model vs. LoRA model │
|
| 81 |
+
└──────────────────────────┬──────────────────────────────────────┘
|
| 82 |
+
│
|
| 83 |
+
┌──────────────────────────▼──────────────────────────────────────┐
|
| 84 |
+
│ 4. Hybrid Indexing & RAG │
|
| 85 |
+
│ • CodeLMEmbedder → FAISS semantic index (IndexFlatIP) │
|
| 86 |
+
│ • ASTIndexer → structural fingerprints (Python AST / SQL regex)│
|
| 87 |
+
│ • HybridRetriever → dense + structural blended top-K search │
|
| 88 |
+
│ • RAGPipeline → context injection + generation │
|
| 89 |
+
└──────────────────────────┬──────────────────────────────────────┘
|
| 90 |
+
│
|
| 91 |
+
┌──────────────────────────▼──────────────────────────────────────┐
|
| 92 |
+
│ 5. Evaluation & Comparison │
|
| 93 |
+
│ • Base vs. LoRA vs. RAG vs. LLM-baseline (ModelComparator) │
|
| 94 |
+
│ • RAG gain-over-baseline & top-K sweep (RAGEvaluator) │
|
| 95 |
+
│ • Charts (matplotlib) + JSON metrics persisted to output/ │
|
| 96 |
+
└─────────────────────────────────────────────────────────────────┘
|
| 97 |
+
```
|
| 98 |
+
|
| 99 |
+
---
|
| 100 |
+
|
| 101 |
+
## Pipeline Stages
|
| 102 |
+
|
| 103 |
+
The pipeline is run through a set of Jupyter notebooks rather than a single CLI entry point:
|
| 104 |
+
|
| 105 |
+
| Notebook | Stage |
|
| 106 |
+
|---|---|
|
| 107 |
+
| `baseline_model.ipynb` | Loads the **un-adapted base model**, runs text-to-SQL and documentation generation, saves metrics to `outputs/generated/baseline_model_results.json` |
|
| 108 |
+
| `finetuned_model.ipynb` | Loads/trains the **LoRA adapter**, re-runs the same two tasks, saves metrics to `outputs/generated/finetuned_model_results.json` |
|
| 109 |
+
| `spider_eval.ipynb` | Spider-specific SQL execution evaluation |
|
| 110 |
+
| `eval.ipynb` | RAG-gain measurement, top-K sweep, small-LM-vs-LLM comparison |
|
| 111 |
+
| `main.ipynb` | Complete End-to-End pipeline |
|
| 112 |
+
|
| 113 |
+
Supporting production code lives in `src/` (see [Repository Layout](#-repository-layout)) and is imported by the notebooks — it is not currently exposed as a standalone CLI script.
|
| 114 |
+
|
| 115 |
+
### Task-Specific Generators (`src/generators/`)
|
| 116 |
+
|
| 117 |
+
| Task | Module | Benchmark used | Primary Metrics |
|
| 118 |
+
|---|---|---|---|
|
| 119 |
+
| **Text-to-SQL** | `text_to_sql.py`, `sql_generator.py` | Spider (100-sample subset of the 1,034-question dev split) | Exact Match, Execution Accuracy, BLEU, CodeBERTScore |
|
| 120 |
+
| **Documentation Generation** | `doc_gen.py` | 3 hand-authored illustrative examples | BLEU, ROUGE, CodeBERTScore, F1 |
|
| 121 |
+
| **Program Synthesis** | `program_generator.py` | 3 hand-authored illustrative examples | Qualitative + shared metric suite |
|
| 122 |
+
| **Commit Message Generation** | `commit_gen.py` | 3 hand-authored illustrative examples | Qualitative + shared metric suite |
|
| 123 |
+
| **LLM Baseline** | `llm_baseline.py` | Same prompts as program synthesis | Comparative scoring against the small LM |
|
| 124 |
+
|
| 125 |
+
### Hybrid Indexing & Retrieval (`src/embeddings/`)
|
| 126 |
+
|
| 127 |
+
| Component | Technology | Purpose |
|
| 128 |
+
|---|---|---|
|
| 129 |
+
| `code_lm_embedder.py` | Mean-pooled hidden states of the base model itself | Retrieval embeddings without a second model download |
|
| 130 |
+
| `embedding.py` | `BAAI/bge-small-en-v1.5` (external, config-switchable) | Alternative embedding backend |
|
| 131 |
+
| `indexing.py` | FAISS `IndexFlatIP` | Dense semantic vector store (cosine similarity via normalized inner product) |
|
| 132 |
+
| `ast_indexing.py` | Python `ast` module (Python code) / regex-based structural extractor (SQL) | Structural fingerprints — function/class names, or referenced tables/aggregates/clauses |
|
| 133 |
+
| `hybrid_retriever.py` | Blended reranker | Over-fetches `k × 4` from FAISS, reranks with `α * dense + (1-α) * structural` |
|
| 134 |
+
|
| 135 |
+
### Evaluation & Persistence (`src/evaluation/`)
|
| 136 |
+
|
| 137 |
+
| Component | Function |
|
| 138 |
+
|---|---|
|
| 139 |
+
| `metrics.py` | BLEU, ROUGE, token-F1, CodeBERTScore, execution accuracy primitives |
|
| 140 |
+
| `evaluation.py` | Task-level evaluation engine |
|
| 141 |
+
| `sql_eval.py` | SQL normalization and Spider-style execution comparison |
|
| 142 |
+
| `comparator.py` | Base vs. LoRA vs. RAG side-by-side comparison |
|
| 143 |
+
| `rag_evaluation.py` | RAG gain-over-baseline measurement and top-K sweep |
|
| 144 |
+
| `visualization.py` | Matplotlib bar-chart generation, saved to `output/plots/` |
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## Benchmark Results
|
| 149 |
+
|
| 150 |
+
Numbers below are taken directly from `outputs/generated/baseline_model_results.json` and `finetuned_model_results.json` (100-sample Spider text-to-SQL subset, 3-sample documentation-generation set).
|
| 151 |
+
|
| 152 |
+
### Baseline (un-adapted) vs. LoRA Fine-Tuned
|
| 153 |
+
|
| 154 |
+
| Task | Metric | Base Model | LoRA Fine-Tuned |
|
| 155 |
+
|---|---|:---:|:---:|
|
| 156 |
+
| **Text-to-SQL** | BLEU | 0.2329 | **0.2467** |
|
| 157 |
+
| | CodeBERTScore | 0.9740 | **0.9759** |
|
| 158 |
+
| | F1 | 0.4642 | **0.4879** |
|
| 159 |
+
| | ROUGE-1 | 0.4378 | **0.4602** |
|
| 160 |
+
| | ROUGE-L | 0.4274 | **0.4503** |
|
| 161 |
+
| | Exact Match | 7% | **8%** |
|
| 162 |
+
| | Execution Accuracy | 11% | **14%** |
|
| 163 |
+
| **Documentation Generation** | BLEU | 0.0241 | **0.0528** |
|
| 164 |
+
| | CodeBERTScore | 0.9250 | **0.9447** |
|
| 165 |
+
| | F1 | 0.1181 | **0.2022** |
|
| 166 |
+
| | ROUGE-1 | 0.0831 | **0.1615** |
|
| 167 |
+
| | ROUGE-L | 0.0719 | **0.1494** |
|
| 168 |
+
|
| 169 |
+
LoRA fine-tuning (trained on only 9 illustrative prompt/completion examples — 2.16M trainable params, 0.44% of the model) improves every metric on both tasks. Gains are proportionally larger on documentation generation.
|
| 170 |
+
|
| 171 |
+
### RAG, Top-K Sweep, and LLM Baseline (5-question SQL slice)
|
| 172 |
+
|
| 173 |
+
- On the small 5-question evaluation slice used for this stage, the **RAG-augmented pipeline underperformed the no-RAG LoRA baseline** on every metric — a genuine, reproducible negative result, not a placeholder. See [Known Limitations](#-known-limitations--future-work) for why.
|
| 174 |
+
- A **top-K sweep** over K ∈ {1, 2, 3, 5, 8} shows the gain is least negative around **K = 2–3** and degrades further at K = 8.
|
| 175 |
+
- The **LLM-baseline comparison** fell back to the same local Qwen2.5-Coder-0.5B model in this run because no external API credential was resolved at runtime — see the security note below and Known Limitations.
|
| 176 |
+
- For reference, the rule-based Spider baseline scores **12.28% exact match / 33.66% execution accuracy** over the full 1,034-question dev set.
|
| 177 |
+
|
| 178 |
+
Plots for all of the above are saved under `output/plots/` (`scores_baseline_text_to_sql_vs_doc_generation.png`, `scores_finetuned_text_to_sql_vs_doc_generation.png`, `scores_model_comparison_checkpoint2.png`, `scores_model_comparison_full.png`, `topk_gain_sweep.png`).
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
## Dataset
|
| 182 |
+
|
| 183 |
+
This project uses the **[Spider](https://yale-lily.github.io/spider)** dataset — a large-scale, cross-domain text-to-SQL benchmark — as its primary evaluation dataset.
|
| 184 |
+
|
| 185 |
+
> **Note:** The Spider dataset is **not included in this repository**. GitHub's file/repo upload limits make it impractical to commit the raw dataset (question/SQL pairs, table schemas, and per-database SQLite files), so it must be downloaded separately before running the pipeline.
|
| 186 |
+
|
| 187 |
+
### Download
|
| 188 |
+
|
| 189 |
+
1. Download the dataset from Google Drive: **[Spider dataset (Google Drive)](https://drive.google.com/file/d/1403EGqzIDoHMdQF4c9Bkyl7dZLZ5Wt6J/view)**
|
| 190 |
+
- Alternatively, see the [official Spider dataset page](https://yale-lily.github.io/spider) for the source release and licensing terms.
|
| 191 |
+
2. Unzip the download and place its contents under `data/raw/Spider/` at the repository root, so that `dev.json`, `tables.json`, and the per-database SQLite files sit alongside each other, e.g.:
|
| 192 |
+
|
| 193 |
+
```
|
| 194 |
+
data/raw/Spider/
|
| 195 |
+
├── dev.json
|
| 196 |
+
├── tables.json
|
| 197 |
+
└── database/
|
| 198 |
+
├── concert_singer/
|
| 199 |
+
│ └── concert_singer.sqlite
|
| 200 |
+
└── ...
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
3. Run `src/scripts/convert_spider_to_jsonl.py` to convert the raw files into the normalized `data/processed/spider_eval.jsonl` and retrieval corpus used by the notebooks (see [Quick Start](#-quick-start)).
|
| 204 |
+
|
| 205 |
+
`data/raw/`, `data/processed/`, and `data/indices/` are excluded via `.gitignore` for the same reason — they're generated/downloaded locally rather than versioned.
|
| 206 |
+
|
| 207 |
+
---
|
| 208 |
+
|
| 209 |
+
## Installation
|
| 210 |
+
|
| 211 |
+
### Prerequisites
|
| 212 |
+
|
| 213 |
+
```bash
|
| 214 |
+
Python 3.10 or higher
|
| 215 |
+
A CUDA-capable GPU is optional — the code auto-detects CUDA > MPS (Apple Silicon) > CPU
|
| 216 |
+
Recommended: 16 GB RAM, 8 GB+ VRAM if using GPU
|
| 217 |
+
```
|
| 218 |
+
|
| 219 |
+
### Step 1: Clone Repository
|
| 220 |
+
|
| 221 |
+
```bash
|
| 222 |
+
git clone https://github.com/Rithusravya/Emasters_Group-2_CapstoneProject.git
|
| 223 |
+
cd Emasters_Group-2_CapstoneProject
|
| 224 |
+
```
|
| 225 |
+
|
| 226 |
+
### Step 2: Create Virtual Environment
|
| 227 |
+
|
| 228 |
+
```bash
|
| 229 |
+
python -m venv venv
|
| 230 |
+
source venv/bin/activate # macOS/Linux
|
| 231 |
+
# OR
|
| 232 |
+
venv\Scripts\activate # Windows
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
### Step 3: Install Dependencies
|
| 236 |
+
|
| 237 |
+
```bash
|
| 238 |
+
pip install --upgrade pip
|
| 239 |
+
pip install -r requirements.txt
|
| 240 |
+
```
|
| 241 |
+
|
| 242 |
+
### Step 4: Verify Installation
|
| 243 |
+
|
| 244 |
+
```bash
|
| 245 |
+
python -c "import torch; print(f'CUDA Available: {torch.cuda.is_available()}')"
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
---
|
| 249 |
+
|
| 250 |
+
## Quick Start
|
| 251 |
+
|
| 252 |
+
This project is driven through notebooks, not a CLI. Typical order:
|
| 253 |
+
|
| 254 |
+
1. **Download & prepare data** — download the Spider dataset as described in [Dataset](#-dataset) and place it under `data/raw/Spider/`. Then run `src/scripts/convert_spider_to_jsonl.py` to convert the raw Spider files into `data/processed/spider_eval.jsonl` and a retrieval corpus; `src/scripts/prepare_processed_data.py` builds the MongoDB-style variant.
|
| 255 |
+
2. **Baseline evaluation** — open `baseline_model.ipynb` and run all cells to evaluate the un-adapted base model on text-to-SQL and documentation generation.
|
| 256 |
+
3. **LoRA fine-tuning + evaluation** — open `finetuned_model.ipynb` to train (or reload) the LoRA adapter at `models/checkpoints/lora_finetuned/` and re-run the same evaluations.
|
| 257 |
+
4. **RAG / top-K / LLM comparison** — open `eval.ipynb` to build the FAISS + AST indices, measure RAG gain, sweep top-K, and compare against the LLM baseline.
|
| 258 |
+
5. **Spider execution evaluation** — open `spider_eval.ipynb` for SQL-execution-based scoring against the SQLite databases.
|
| 259 |
+
|
| 260 |
+
Programmatic usage of the core pipeline, e.g. for text-to-SQL generation:
|
| 261 |
+
|
| 262 |
+
```python
|
| 263 |
+
from src.config import load_config
|
| 264 |
+
from src.models.load_model import ModelLoader
|
| 265 |
+
from src.generators.program_generator import GenerationPipeline
|
| 266 |
+
from src.generators.text_to_sql import TextToSQLGenerator
|
| 267 |
+
|
| 268 |
+
config = load_config() # reads configs/config.yaml
|
| 269 |
+
loader = ModelLoader(config)
|
| 270 |
+
models, tokenizer = loader.load_models(lora_path=config.lora.output_dir)
|
| 271 |
+
|
| 272 |
+
pipeline = GenerationPipeline(models["lora"] or models["base"], tokenizer, config.generation)
|
| 273 |
+
sql_gen = TextToSQLGenerator(pipeline)
|
| 274 |
+
|
| 275 |
+
result = sql_gen.generate_queries(
|
| 276 |
+
question="How many singers are there?",
|
| 277 |
+
schema="CREATE TABLE singer (singer_id INT, name TEXT, age INT)",
|
| 278 |
+
)
|
| 279 |
+
print(result["sql_query"])
|
| 280 |
+
```
|
| 281 |
+
|
| 282 |
+
---
|
| 283 |
+
|
| 284 |
+
## Repository Layout
|
| 285 |
+
|
| 286 |
+
```
|
| 287 |
+
Emasters_Group-2_CapstoneProject/
|
| 288 |
+
├── configs/
|
| 289 |
+
│ └── config.yaml # Project, model, LoRA, retrieval, eval & output paths
|
| 290 |
+
├── data/
|
| 291 |
+
│ ├── raw/ # Raw Spider files (dev.json, tables.json, SQLite DBs)
|
| 292 |
+
│ ├── processed/ # Normalized JSONL (spider_eval, retrieval_corpus, etc.)
|
| 293 |
+
│ └── indices/ # Saved FAISS index, AST store, FAISS metadata
|
| 294 |
+
├── models/
|
| 295 |
+
│ └── checkpoints/lora_finetuned/ # Saved LoRA adapter + tokenizer files
|
| 296 |
+
├── src/
|
| 297 |
+
│ ├── config.py # YAML → dataclass config loader
|
| 298 |
+
│ ├── data/
|
| 299 |
+
│ │ └── data_loader.py # Generic JSON/JSONL dataset loader (Spider, etc.)
|
| 300 |
+
│ ├── models/
|
| 301 |
+
│ │ └── load_model.py # Base model + LoRA loading, training loop, base-vs-LoRA generation
|
| 302 |
+
│ ├── generators/
|
| 303 |
+
│ │ ├── program_generator.py # Core GenerationPipeline + prompt builder
|
| 304 |
+
│ │ ├── text_to_sql.py # Text-to-SQL generator
|
| 305 |
+
│ │ ├── sql_generator.py # SQL prompt/parsing helpers
|
| 306 |
+
│ │ ├── doc_gen.py # Documentation generator
|
| 307 |
+
│ │ ├── commit_gen.py # Commit-message generator
|
| 308 |
+
│ │ └── llm_baseline.py # Pluggable local/OpenAI/Gemini LLM baseline
|
| 309 |
+
│ ├── embeddings/
|
| 310 |
+
│ │ ├── code_lm_embedder.py # Embeddings from the small code LM's own hidden states
|
| 311 |
+
│ │ ├── embedding.py # External embedding-model backend (BGE)
|
| 312 |
+
│ │ ├── indexing.py # FAISS semantic index manager
|
| 313 |
+
│ │ ├── ast_indexing.py # AST / SQL structural fingerprint indexer
|
| 314 |
+
│ │ └── hybrid_retriever.py # Dense + structural blended retrieval
|
| 315 |
+
│ ├── rag/
|
| 316 |
+
│ │ └── rag_pipeline.py # Context retrieval, prompt building, RAG generation
|
| 317 |
+
│ ├── evaluation/
|
| 318 |
+
│ │ ├── metrics.py # BLEU, ROUGE, F1, CodeBERTScore, execution accuracy
|
| 319 |
+
│ │ ├── evaluation.py # Task-level evaluation engine
|
| 320 |
+
│ │ ├── sql_eval.py # Spider SQL normalization & execution comparison
|
| 321 |
+
│ │ ├── comparator.py # Base vs. LoRA vs. RAG comparison
|
| 322 |
+
│ │ ├── rag_evaluation.py # RAG gain-over-baseline + top-K sweep
|
| 323 |
+
│ │ └── visualization.py # Chart generation
|
| 324 |
+
│ └── scripts/
|
| 325 |
+
│ ├── convert_spider_to_jsonl.py # Raw Spider → normalized JSONL
|
| 326 |
+
│ └── prepare_processed_data.py # Build derived/MongoDB-style datasets
|
| 327 |
+
├── outputs/generated/ # Per-task generated samples & timestamped run results
|
| 328 |
+
├── output/plots/ # Saved comparison charts
|
| 329 |
+
├── backup/ # Earlier snapshot of evaluation/rag/embeddings modules
|
| 330 |
+
├── baseline_model.ipynb # Base model evaluation
|
| 331 |
+
├── finetuned_model.ipynb # LoRA fine-tuning + evaluation
|
| 332 |
+
├── spider_eval.ipynb # Spider execution-based SQL evaluation
|
| 333 |
+
├── eval.ipynb # RAG gain / top-K sweep / LLM comparison
|
| 334 |
+
├── main.ipynb # Colab entry point
|
| 335 |
+
├── requirements.txt
|
| 336 |
+
└── README.md
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
---
|
| 340 |
+
|
| 341 |
+
## Configuration (`configs/config.yaml`)
|
| 342 |
+
|
| 343 |
+
Key fields actually read by `src/config.py`:
|
| 344 |
+
|
| 345 |
+
```yaml
|
| 346 |
+
model_name: "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 347 |
+
lora_adapter_path: "models/checkpoints/lora_finetuned"
|
| 348 |
+
embedding_model: "BAAI/bge-small-en-v1.5" # used only when embedding.source == "external"
|
| 349 |
+
|
| 350 |
+
embedding:
|
| 351 |
+
source: "code_lm" # "code_lm" (default, self-embeddings) or "external"
|
| 352 |
+
|
| 353 |
+
retrieval:
|
| 354 |
+
hybrid: true
|
| 355 |
+
structural_weight_alpha: 0.7
|
| 356 |
+
structural_language: "sql"
|
| 357 |
+
topk_sweep: [1, 2, 3, 5, 8]
|
| 358 |
+
|
| 359 |
+
llm_baseline:
|
| 360 |
+
backend: "local" # "local" or an external provider ("openai" / "gemini")
|
| 361 |
+
local_model: "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 362 |
+
api_model: "gpt-5.4-mini"
|
| 363 |
+
max_new_tokens: 256
|
| 364 |
+
temperature: 0.2
|
| 365 |
+
|
| 366 |
+
lora:
|
| 367 |
+
r: 16
|
| 368 |
+
alpha: 32
|
| 369 |
+
dropout: 0.05
|
| 370 |
+
target_modules: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 371 |
+
batch_size: 4
|
| 372 |
+
learning_rate: 0.0003
|
| 373 |
+
epochs: 5
|
| 374 |
+
output_dir: "models/checkpoints/lora_finetuned"
|
| 375 |
+
|
| 376 |
+
indexing:
|
| 377 |
+
top_k: 3
|
| 378 |
+
vector_dim: 1024 # must match the embedding model's output dimension
|
| 379 |
+
|
| 380 |
+
outputs:
|
| 381 |
+
logs_dir: "output/logs"
|
| 382 |
+
metrics_path: "output/metrics/evaluation_metrics.json"
|
| 383 |
+
plots_dir: "output/plots"
|
| 384 |
+
```
|
| 385 |
+
|
| 386 |
+
---
|
| 387 |
+
|
| 388 |
+
## Requirements
|
| 389 |
+
|
| 390 |
+
| Package | Version | Purpose |
|
| 391 |
+
|---|---|---|
|
| 392 |
+
| `torch` | 2.5.1 | Core deep learning framework |
|
| 393 |
+
| `transformers` | 4.46.3 | Pretrained model + tokenizer loading (Qwen2.5-Coder) |
|
| 394 |
+
| `peft` | 0.13.2 | LoRA fine-tuning |
|
| 395 |
+
| `datasets` | 3.1.0 | Dataset utilities |
|
| 396 |
+
| `faiss-cpu` | 1.9.0 | Dense vector similarity search |
|
| 397 |
+
| `tree-sitter` / `tree-sitter-python` | 0.23.2 / 0.23.6 | (Available for AST-based parsing) |
|
| 398 |
+
| `codebleu` | 0.7.0 | CodeBLEU metric |
|
| 399 |
+
| `codebert-score` | 0.3.13 | CodeBERTScore semantic similarity metric |
|
| 400 |
+
| `evaluate` | 0.4.3 | Hugging Face evaluation metrics |
|
| 401 |
+
| `rouge-score` | 0.1.2 | ROUGE metrics |
|
| 402 |
+
| `nltk` | 3.9.1 | Text preprocessing / BLEU support |
|
| 403 |
+
| `PyYAML` | 6.0.2 | Config parsing |
|
| 404 |
+
| `matplotlib` / `seaborn` | 3.10.0 / 0.13.2 | Plotting |
|
| 405 |
+
| `numpy` | 2.1.3 | Numerical computing |
|
| 406 |
+
|
| 407 |
+
See `requirements.txt` for the full pinned list.
|
| 408 |
+
|
| 409 |
+
---
|
| 410 |
+
|
| 411 |
+
## Contributing
|
| 412 |
+
|
| 413 |
+
1. **Fork** the repository
|
| 414 |
+
2. **Create** a feature branch (`git checkout -b feature/amazing-feature`)
|
| 415 |
+
3. **Commit** your changes (`git commit -m 'Add amazing feature'`)
|
| 416 |
+
4. **Push** to the branch (`git push origin feature/amazing-feature`)
|
| 417 |
+
5. **Open** a Pull Request
|
| 418 |
+
|
| 419 |
+
---
|
| 420 |
+
|
| 421 |
+
## License
|
| 422 |
+
|
| 423 |
+
This project is licensed under the **MIT License** — see the `LICENSE` file for details.
|
| 424 |
+
|
| 425 |
+
---
|
| 426 |
+
|
| 427 |
+
## Citation
|
| 428 |
+
|
| 429 |
+
```bibtex
|
| 430 |
+
@software{codegen_capstone_2026,
|
| 431 |
+
title = {CodeGen: Small Code-LM Fine-Tuning and Hybrid RAG Pipeline},
|
| 432 |
+
author = {Emasters Group 2},
|
| 433 |
+
year = {2026},
|
| 434 |
+
url = {https://github.com/Rithusravya/Emasters_Group-2_CapstoneProject}
|
| 435 |
+
}
|
| 436 |
+
```
|
| 437 |
+
|
| 438 |
+
---
|
| 439 |
+
|
| 440 |
+
<div align="center">
|
| 441 |
+
|
| 442 |
+
**Built by IIIT-H eMasters Group 2**
|
| 443 |
+
|
| 444 |
+
[⬆ Back to Top](#-codegen-small-code-lm-fine-tuning--hybrid-rag-pipeline)
|
| 445 |
+
|
| 446 |
+
</div>
|
configs/config.yaml
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# ============================================================
|
| 2 |
+
# General & System Configurations
|
| 3 |
+
# ============================================================
|
| 4 |
+
project_name: "Emasters_Group-2_CapstoneProject"
|
| 5 |
+
seed: 42
|
| 6 |
+
device: "auto" # Options: 'auto', 'cuda', 'mps', 'cpu'
|
| 7 |
+
dtype: "float16" # Options: 'float16', 'bfloat16', 'float32'
|
| 8 |
+
|
| 9 |
+
# ============================================================
|
| 10 |
+
# Model & Tokenizer Configs
|
| 11 |
+
# ============================================================
|
| 12 |
+
model_name: "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 13 |
+
lora_adapter_path: "models/checkpoints/lora_finetuned"
|
| 14 |
+
|
| 15 |
+
# ============================================================
|
| 16 |
+
# Embedding Model Configs
|
| 17 |
+
# ============================================================
|
| 18 |
+
embedding_model: "BAAI/bge-small-en-v1.5" # used only when embedding.source == "external"
|
| 19 |
+
embedding:
|
| 20 |
+
source: "external" # Options: 'code_lm' or 'external'
|
| 21 |
+
external_model: "BAAI/bge-small-en-v1.5"
|
| 22 |
+
max_length: 512
|
| 23 |
+
batch_size: 32
|
| 24 |
+
|
| 25 |
+
# ============================================================
|
| 26 |
+
# Retrieval Settings (semantic + AST/structural hybrid search)
|
| 27 |
+
# ============================================================
|
| 28 |
+
retrieval:
|
| 29 |
+
hybrid: true
|
| 30 |
+
structural_weight_alpha: 0.7
|
| 31 |
+
structural_language: "mongodb"
|
| 32 |
+
topk_sweep: [1, 2, 3, 5, 8]
|
| 33 |
+
|
| 34 |
+
# ============================================================
|
| 35 |
+
# Large-LLM baseline (for "small code LM vs. LLM" comparison)
|
| 36 |
+
# ============================================================
|
| 37 |
+
llm_baseline:
|
| 38 |
+
backend: "api" # Options: 'local' or 'api'
|
| 39 |
+
local_model: "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 40 |
+
api_model: "gemini-3.6-flash"
|
| 41 |
+
api_key_env: "GEMINI_API_KEY"
|
| 42 |
+
max_new_tokens: 256
|
| 43 |
+
temperature: 0.2
|
| 44 |
+
|
| 45 |
+
# ============================================================
|
| 46 |
+
# Dataset Paths
|
| 47 |
+
# ============================================================
|
| 48 |
+
data_paths:
|
| 49 |
+
raw_dir: "data/raw"
|
| 50 |
+
spider: "spider_real_execution_mongo.json"
|
| 51 |
+
|
| 52 |
+
# ============================================================
|
| 53 |
+
# FAISS Vector & AST Index Paths
|
| 54 |
+
# ============================================================
|
| 55 |
+
indices_paths:
|
| 56 |
+
faiss_index: "data/indices/faiss_semantic.index"
|
| 57 |
+
ast_store: "data/indices/ast_store.pkl"
|
| 58 |
+
|
| 59 |
+
# ============================================================
|
| 60 |
+
# Text Generation Parameters
|
| 61 |
+
# ============================================================
|
| 62 |
+
generation:
|
| 63 |
+
max_length: 512
|
| 64 |
+
temperature: 0.2
|
| 65 |
+
top_k: 50
|
| 66 |
+
top_p: 0.95
|
| 67 |
+
num_return_sequences: 1
|
| 68 |
+
|
| 69 |
+
# ============================================================
|
| 70 |
+
# Evaluation Settings
|
| 71 |
+
# ============================================================
|
| 72 |
+
evaluation:
|
| 73 |
+
device: "cpu" # CodeBERTScore device
|
| 74 |
+
bertscore_model: "microsoft/codebert-base"
|
| 75 |
+
bertscore_local_path: "models/codebert-base"
|
| 76 |
+
timeout_seconds: 3
|
| 77 |
+
|
| 78 |
+
# ============================================================
|
| 79 |
+
# LoRA Training Settings
|
| 80 |
+
# ============================================================
|
| 81 |
+
lora:
|
| 82 |
+
r: 16
|
| 83 |
+
alpha: 32
|
| 84 |
+
dropout: 0.05
|
| 85 |
+
batch_size: 1
|
| 86 |
+
learning_rate: 0.0002
|
| 87 |
+
epochs: 1
|
| 88 |
+
output_dir: "models/checkpoints/lora_finetuned"
|
| 89 |
+
trainable_fraction: 1.0 # Fraction of LoRA parameters to train (0.0 to 1.0)
|
| 90 |
+
|
| 91 |
+
# ============================================================
|
| 92 |
+
# Indexing Settings
|
| 93 |
+
# ============================================================
|
| 94 |
+
indexing:
|
| 95 |
+
top_k: 3
|
| 96 |
+
vector_dim: 1024 # Must match embedding model's output dimension
|
| 97 |
+
|
| 98 |
+
# ============================================================
|
| 99 |
+
# Outputs & Evaluation Paths
|
| 100 |
+
# ============================================================
|
| 101 |
+
outputs:
|
| 102 |
+
logs_dir: "output/logs"
|
| 103 |
+
metrics_path: "output/metrics/evaluation_metrics.json"
|
| 104 |
+
plots_dir: "output/plots"
|
| 105 |
+
generated_dir: "output/generated"
|
data/indices/ast_index.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:c5f4bae7dc5a9a4ef572e9f0f64123668a95382b038132bddcd11473f779db90
|
| 3 |
+
size 361374
|
data/indices/semantic_index.faiss
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:03db2b2dd28bcb7e5165ce11e60a976728796f48a7a13b3eaf259519c507ef16
|
| 3 |
+
size 1711149
|
data/indices/semantic_index.metadata.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:bed7ff41bd109624a645eb1e91cf612c3d4811f4eb62ba7bd787c1078659f0a9
|
| 3 |
+
size 329781
|
models/checkpoints/lora_finetuned/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
base_model: Qwen/Qwen2.5-Coder-0.5B-Instruct
|
| 3 |
+
library_name: peft
|
| 4 |
+
pipeline_tag: text-generation
|
| 5 |
+
tags:
|
| 6 |
+
- base_model:adapter:Qwen/Qwen2.5-Coder-0.5B-Instruct
|
| 7 |
+
- lora
|
| 8 |
+
- transformers
|
| 9 |
+
---
|
| 10 |
+
|
| 11 |
+
# Model Card for Model ID
|
| 12 |
+
|
| 13 |
+
<!-- Provide a quick summary of what the model is/does. -->
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
## Model Details
|
| 18 |
+
|
| 19 |
+
### Model Description
|
| 20 |
+
|
| 21 |
+
<!-- Provide a longer summary of what this model is. -->
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
- **Developed by:** [More Information Needed]
|
| 26 |
+
- **Funded by [optional]:** [More Information Needed]
|
| 27 |
+
- **Shared by [optional]:** [More Information Needed]
|
| 28 |
+
- **Model type:** [More Information Needed]
|
| 29 |
+
- **Language(s) (NLP):** [More Information Needed]
|
| 30 |
+
- **License:** [More Information Needed]
|
| 31 |
+
- **Finetuned from model [optional]:** [More Information Needed]
|
| 32 |
+
|
| 33 |
+
### Model Sources [optional]
|
| 34 |
+
|
| 35 |
+
<!-- Provide the basic links for the model. -->
|
| 36 |
+
|
| 37 |
+
- **Repository:** [More Information Needed]
|
| 38 |
+
- **Paper [optional]:** [More Information Needed]
|
| 39 |
+
- **Demo [optional]:** [More Information Needed]
|
| 40 |
+
|
| 41 |
+
## Uses
|
| 42 |
+
|
| 43 |
+
<!-- Address questions around how the model is intended to be used, including the foreseeable users of the model and those affected by the model. -->
|
| 44 |
+
|
| 45 |
+
### Direct Use
|
| 46 |
+
|
| 47 |
+
<!-- This section is for the model use without fine-tuning or plugging into a larger ecosystem/app. -->
|
| 48 |
+
|
| 49 |
+
[More Information Needed]
|
| 50 |
+
|
| 51 |
+
### Downstream Use [optional]
|
| 52 |
+
|
| 53 |
+
<!-- This section is for the model use when fine-tuned for a task, or when plugged into a larger ecosystem/app -->
|
| 54 |
+
|
| 55 |
+
[More Information Needed]
|
| 56 |
+
|
| 57 |
+
### Out-of-Scope Use
|
| 58 |
+
|
| 59 |
+
<!-- This section addresses misuse, malicious use, and uses that the model will not work well for. -->
|
| 60 |
+
|
| 61 |
+
[More Information Needed]
|
| 62 |
+
|
| 63 |
+
## Bias, Risks, and Limitations
|
| 64 |
+
|
| 65 |
+
<!-- This section is meant to convey both technical and sociotechnical limitations. -->
|
| 66 |
+
|
| 67 |
+
[More Information Needed]
|
| 68 |
+
|
| 69 |
+
### Recommendations
|
| 70 |
+
|
| 71 |
+
<!-- This section is meant to convey recommendations with respect to the bias, risk, and technical limitations. -->
|
| 72 |
+
|
| 73 |
+
Users (both direct and downstream) should be made aware of the risks, biases and limitations of the model. More information needed for further recommendations.
|
| 74 |
+
|
| 75 |
+
## How to Get Started with the Model
|
| 76 |
+
|
| 77 |
+
Use the code below to get started with the model.
|
| 78 |
+
|
| 79 |
+
[More Information Needed]
|
| 80 |
+
|
| 81 |
+
## Training Details
|
| 82 |
+
|
| 83 |
+
### Training Data
|
| 84 |
+
|
| 85 |
+
<!-- This should link to a Dataset Card, perhaps with a short stub of information on what the training data is all about as well as documentation related to data pre-processing or additional filtering. -->
|
| 86 |
+
|
| 87 |
+
[More Information Needed]
|
| 88 |
+
|
| 89 |
+
### Training Procedure
|
| 90 |
+
|
| 91 |
+
<!-- This relates heavily to the Technical Specifications. Content here should link to that section when it is relevant to the training procedure. -->
|
| 92 |
+
|
| 93 |
+
#### Preprocessing [optional]
|
| 94 |
+
|
| 95 |
+
[More Information Needed]
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
#### Training Hyperparameters
|
| 99 |
+
|
| 100 |
+
- **Training regime:** [More Information Needed] <!--fp32, fp16 mixed precision, bf16 mixed precision, bf16 non-mixed precision, fp16 non-mixed precision, fp8 mixed precision -->
|
| 101 |
+
|
| 102 |
+
#### Speeds, Sizes, Times [optional]
|
| 103 |
+
|
| 104 |
+
<!-- This section provides information about throughput, start/end time, checkpoint size if relevant, etc. -->
|
| 105 |
+
|
| 106 |
+
[More Information Needed]
|
| 107 |
+
|
| 108 |
+
## Evaluation
|
| 109 |
+
|
| 110 |
+
<!-- This section describes the evaluation protocols and provides the results. -->
|
| 111 |
+
|
| 112 |
+
### Testing Data, Factors & Metrics
|
| 113 |
+
|
| 114 |
+
#### Testing Data
|
| 115 |
+
|
| 116 |
+
<!-- This should link to a Dataset Card if possible. -->
|
| 117 |
+
|
| 118 |
+
[More Information Needed]
|
| 119 |
+
|
| 120 |
+
#### Factors
|
| 121 |
+
|
| 122 |
+
<!-- These are the things the evaluation is disaggregating by, e.g., subpopulations or domains. -->
|
| 123 |
+
|
| 124 |
+
[More Information Needed]
|
| 125 |
+
|
| 126 |
+
#### Metrics
|
| 127 |
+
|
| 128 |
+
<!-- These are the evaluation metrics being used, ideally with a description of why. -->
|
| 129 |
+
|
| 130 |
+
[More Information Needed]
|
| 131 |
+
|
| 132 |
+
### Results
|
| 133 |
+
|
| 134 |
+
[More Information Needed]
|
| 135 |
+
|
| 136 |
+
#### Summary
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
## Model Examination [optional]
|
| 141 |
+
|
| 142 |
+
<!-- Relevant interpretability work for the model goes here -->
|
| 143 |
+
|
| 144 |
+
[More Information Needed]
|
| 145 |
+
|
| 146 |
+
## Environmental Impact
|
| 147 |
+
|
| 148 |
+
<!-- Total emissions (in grams of CO2eq) and additional considerations, such as electricity usage, go here. Edit the suggested text below accordingly -->
|
| 149 |
+
|
| 150 |
+
Carbon emissions can be estimated using the [Machine Learning Impact calculator](https://mlco2.github.io/impact#compute) presented in [Lacoste et al. (2019)](https://arxiv.org/abs/1910.09700).
|
| 151 |
+
|
| 152 |
+
- **Hardware Type:** [More Information Needed]
|
| 153 |
+
- **Hours used:** [More Information Needed]
|
| 154 |
+
- **Cloud Provider:** [More Information Needed]
|
| 155 |
+
- **Compute Region:** [More Information Needed]
|
| 156 |
+
- **Carbon Emitted:** [More Information Needed]
|
| 157 |
+
|
| 158 |
+
## Technical Specifications [optional]
|
| 159 |
+
|
| 160 |
+
### Model Architecture and Objective
|
| 161 |
+
|
| 162 |
+
[More Information Needed]
|
| 163 |
+
|
| 164 |
+
### Compute Infrastructure
|
| 165 |
+
|
| 166 |
+
[More Information Needed]
|
| 167 |
+
|
| 168 |
+
#### Hardware
|
| 169 |
+
|
| 170 |
+
[More Information Needed]
|
| 171 |
+
|
| 172 |
+
#### Software
|
| 173 |
+
|
| 174 |
+
[More Information Needed]
|
| 175 |
+
|
| 176 |
+
## Citation [optional]
|
| 177 |
+
|
| 178 |
+
<!-- If there is a paper or blog post introducing the model, the APA and Bibtex information for that should go in this section. -->
|
| 179 |
+
|
| 180 |
+
**BibTeX:**
|
| 181 |
+
|
| 182 |
+
[More Information Needed]
|
| 183 |
+
|
| 184 |
+
**APA:**
|
| 185 |
+
|
| 186 |
+
[More Information Needed]
|
| 187 |
+
|
| 188 |
+
## Glossary [optional]
|
| 189 |
+
|
| 190 |
+
<!-- If relevant, include terms and calculations in this section that can help readers understand the model or model card. -->
|
| 191 |
+
|
| 192 |
+
[More Information Needed]
|
| 193 |
+
|
| 194 |
+
## More Information [optional]
|
| 195 |
+
|
| 196 |
+
[More Information Needed]
|
| 197 |
+
|
| 198 |
+
## Model Card Authors [optional]
|
| 199 |
+
|
| 200 |
+
[More Information Needed]
|
| 201 |
+
|
| 202 |
+
## Model Card Contact
|
| 203 |
+
|
| 204 |
+
[More Information Needed]
|
| 205 |
+
### Framework versions
|
| 206 |
+
|
| 207 |
+
- PEFT 0.20.0
|
models/checkpoints/lora_finetuned/adapter_config.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"alora_invocation_tokens": null,
|
| 3 |
+
"alpha_pattern": {},
|
| 4 |
+
"arrow_config": null,
|
| 5 |
+
"auto_mapping": null,
|
| 6 |
+
"base_model_name_or_path": "Qwen/Qwen2.5-Coder-0.5B-Instruct",
|
| 7 |
+
"bias": "none",
|
| 8 |
+
"corda_config": null,
|
| 9 |
+
"ensure_weight_tying": false,
|
| 10 |
+
"eva_config": null,
|
| 11 |
+
"exclude_modules": null,
|
| 12 |
+
"fan_in_fan_out": false,
|
| 13 |
+
"inference_mode": true,
|
| 14 |
+
"init_lora_weights": true,
|
| 15 |
+
"layer_replication": null,
|
| 16 |
+
"layers_pattern": null,
|
| 17 |
+
"layers_to_transform": null,
|
| 18 |
+
"loftq_config": {},
|
| 19 |
+
"lora_alpha": 32,
|
| 20 |
+
"lora_bias": false,
|
| 21 |
+
"lora_dropout": 0.05,
|
| 22 |
+
"lora_ga_config": null,
|
| 23 |
+
"megatron_config": null,
|
| 24 |
+
"megatron_core": "megatron.core",
|
| 25 |
+
"modules_to_save": null,
|
| 26 |
+
"monteclora_config": null,
|
| 27 |
+
"peft_type": "LORA",
|
| 28 |
+
"peft_version": "0.20.0",
|
| 29 |
+
"qalora_group_size": 16,
|
| 30 |
+
"r": 16,
|
| 31 |
+
"rank_pattern": {},
|
| 32 |
+
"revision": null,
|
| 33 |
+
"target_modules": [
|
| 34 |
+
"gate_proj",
|
| 35 |
+
"q_proj",
|
| 36 |
+
"up_proj",
|
| 37 |
+
"k_proj",
|
| 38 |
+
"down_proj",
|
| 39 |
+
"v_proj",
|
| 40 |
+
"o_proj"
|
| 41 |
+
],
|
| 42 |
+
"target_parameters": null,
|
| 43 |
+
"task_type": "CAUSAL_LM",
|
| 44 |
+
"trainable_token_indices": null,
|
| 45 |
+
"use_bdlora": null,
|
| 46 |
+
"use_dora": false,
|
| 47 |
+
"use_qalora": false,
|
| 48 |
+
"use_rslora": false,
|
| 49 |
+
"velora_config": null
|
| 50 |
+
}
|
models/checkpoints/lora_finetuned/adapter_model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a72b6b7afbe198b677f5df314bcfe5d5452d142d433fe9b6bc1215431fe297ae
|
| 3 |
+
size 35237104
|
models/checkpoints/lora_finetuned/added_tokens.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"</tool_call>": 151658,
|
| 3 |
+
"<tool_call>": 151657,
|
| 4 |
+
"<|box_end|>": 151649,
|
| 5 |
+
"<|box_start|>": 151648,
|
| 6 |
+
"<|endoftext|>": 151643,
|
| 7 |
+
"<|file_sep|>": 151664,
|
| 8 |
+
"<|fim_middle|>": 151660,
|
| 9 |
+
"<|fim_pad|>": 151662,
|
| 10 |
+
"<|fim_prefix|>": 151659,
|
| 11 |
+
"<|fim_suffix|>": 151661,
|
| 12 |
+
"<|im_end|>": 151645,
|
| 13 |
+
"<|im_start|>": 151644,
|
| 14 |
+
"<|image_pad|>": 151655,
|
| 15 |
+
"<|object_ref_end|>": 151647,
|
| 16 |
+
"<|object_ref_start|>": 151646,
|
| 17 |
+
"<|quad_end|>": 151651,
|
| 18 |
+
"<|quad_start|>": 151650,
|
| 19 |
+
"<|repo_name|>": 151663,
|
| 20 |
+
"<|video_pad|>": 151656,
|
| 21 |
+
"<|vision_end|>": 151653,
|
| 22 |
+
"<|vision_pad|>": 151654,
|
| 23 |
+
"<|vision_start|>": 151652
|
| 24 |
+
}
|
models/checkpoints/lora_finetuned/merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
models/checkpoints/lora_finetuned/special_tokens_map.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"additional_special_tokens": [
|
| 3 |
+
"<|im_start|>",
|
| 4 |
+
"<|im_end|>",
|
| 5 |
+
"<|object_ref_start|>",
|
| 6 |
+
"<|object_ref_end|>",
|
| 7 |
+
"<|box_start|>",
|
| 8 |
+
"<|box_end|>",
|
| 9 |
+
"<|quad_start|>",
|
| 10 |
+
"<|quad_end|>",
|
| 11 |
+
"<|vision_start|>",
|
| 12 |
+
"<|vision_end|>",
|
| 13 |
+
"<|vision_pad|>",
|
| 14 |
+
"<|image_pad|>",
|
| 15 |
+
"<|video_pad|>"
|
| 16 |
+
],
|
| 17 |
+
"eos_token": {
|
| 18 |
+
"content": "<|im_end|>",
|
| 19 |
+
"lstrip": false,
|
| 20 |
+
"normalized": false,
|
| 21 |
+
"rstrip": false,
|
| 22 |
+
"single_word": false
|
| 23 |
+
},
|
| 24 |
+
"pad_token": {
|
| 25 |
+
"content": "<|endoftext|>",
|
| 26 |
+
"lstrip": false,
|
| 27 |
+
"normalized": false,
|
| 28 |
+
"rstrip": false,
|
| 29 |
+
"single_word": false
|
| 30 |
+
}
|
| 31 |
+
}
|
models/checkpoints/lora_finetuned/tokenizer.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
models/checkpoints/lora_finetuned/tokenizer_config.json
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"151643": {
|
| 6 |
+
"content": "<|endoftext|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151644": {
|
| 14 |
+
"content": "<|im_start|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151645": {
|
| 22 |
+
"content": "<|im_end|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151646": {
|
| 30 |
+
"content": "<|object_ref_start|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151647": {
|
| 38 |
+
"content": "<|object_ref_end|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151648": {
|
| 46 |
+
"content": "<|box_start|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151649": {
|
| 54 |
+
"content": "<|box_end|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151650": {
|
| 62 |
+
"content": "<|quad_start|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151651": {
|
| 70 |
+
"content": "<|quad_end|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151652": {
|
| 78 |
+
"content": "<|vision_start|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151653": {
|
| 86 |
+
"content": "<|vision_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151654": {
|
| 94 |
+
"content": "<|vision_pad|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151655": {
|
| 102 |
+
"content": "<|image_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151656": {
|
| 110 |
+
"content": "<|video_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151657": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"151658": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151659": {
|
| 134 |
+
"content": "<|fim_prefix|>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151660": {
|
| 142 |
+
"content": "<|fim_middle|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151661": {
|
| 150 |
+
"content": "<|fim_suffix|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151662": {
|
| 158 |
+
"content": "<|fim_pad|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151663": {
|
| 166 |
+
"content": "<|repo_name|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151664": {
|
| 174 |
+
"content": "<|file_sep|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
}
|
| 181 |
+
},
|
| 182 |
+
"additional_special_tokens": [
|
| 183 |
+
"<|im_start|>",
|
| 184 |
+
"<|im_end|>",
|
| 185 |
+
"<|object_ref_start|>",
|
| 186 |
+
"<|object_ref_end|>",
|
| 187 |
+
"<|box_start|>",
|
| 188 |
+
"<|box_end|>",
|
| 189 |
+
"<|quad_start|>",
|
| 190 |
+
"<|quad_end|>",
|
| 191 |
+
"<|vision_start|>",
|
| 192 |
+
"<|vision_end|>",
|
| 193 |
+
"<|vision_pad|>",
|
| 194 |
+
"<|image_pad|>",
|
| 195 |
+
"<|video_pad|>"
|
| 196 |
+
],
|
| 197 |
+
"bos_token": null,
|
| 198 |
+
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n",
|
| 199 |
+
"clean_up_tokenization_spaces": false,
|
| 200 |
+
"eos_token": "<|im_end|>",
|
| 201 |
+
"errors": "replace",
|
| 202 |
+
"model_max_length": 32768,
|
| 203 |
+
"pad_token": "<|endoftext|>",
|
| 204 |
+
"split_special_tokens": false,
|
| 205 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 206 |
+
"unk_token": null
|
| 207 |
+
}
|
models/checkpoints/lora_finetuned/vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
requirements.txt
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spaces
|
| 2 |
+
gradio==5.9.1
|
| 3 |
+
torch==2.5.1
|
| 4 |
+
transformers==4.46.3
|
| 5 |
+
peft==0.20.0
|
| 6 |
+
faiss-cpu==1.9.0.post1
|
| 7 |
+
numpy==2.1.3
|
| 8 |
+
PyYAML==6.0.2
|
| 9 |
+
huggingface-hub==0.26.2
|
| 10 |
+
tqdm==4.67.1
|
src/__pycache__/config.cpython-312.pyc
ADDED
|
Binary file (10.7 kB). View file
|
|
|
src/config.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from dataclasses import dataclass, field, fields
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from typing import List, Optional, Any, Type, TypeVar
|
| 5 |
+
import yaml
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
T = TypeVar("T")
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def _build(dataclass_type: Type[T], data: dict) -> T:
|
| 13 |
+
valid_keys = {f.name for f in fields(dataclass_type)}
|
| 14 |
+
filtered = {k: v for k, v in data.items() if k in valid_keys}
|
| 15 |
+
return dataclass_type(**filtered)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class PathConfig:
|
| 20 |
+
data_dir: Path = Path("data/raw")
|
| 21 |
+
checkpoints_dir: Path = Path("models/checkpoints")
|
| 22 |
+
lora_save_path: Path = Path("models/checkpoints/lora_finetuned")
|
| 23 |
+
faiss_index_path: Path = Path("data/indices/faiss_semantic.index")
|
| 24 |
+
ast_store_path: Path = Path("data/indices/ast_store.pkl")
|
| 25 |
+
|
| 26 |
+
@dataclass
|
| 27 |
+
class ModelConfig:
|
| 28 |
+
name_or_path: str = "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 29 |
+
embedding_name: str = "BAAI/bge-small-en-v1.5"
|
| 30 |
+
|
| 31 |
+
@dataclass
|
| 32 |
+
class LoRAConfig:
|
| 33 |
+
r: int = 16
|
| 34 |
+
alpha: int = 32
|
| 35 |
+
target_modules: List[str] = field(
|
| 36 |
+
default_factory=lambda: ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 37 |
+
)
|
| 38 |
+
dropout: float = 0.05
|
| 39 |
+
batch_size: int = 1
|
| 40 |
+
learning_rate: float = 2e-4
|
| 41 |
+
epochs: int = 1
|
| 42 |
+
output_dir: str = "models/checkpoints/lora_finetuned"
|
| 43 |
+
trainable_fraction: float = 1.0
|
| 44 |
+
|
| 45 |
+
@dataclass
|
| 46 |
+
class EmbeddingConfig:
|
| 47 |
+
source: str = "external" # 'code_lm' or 'external'
|
| 48 |
+
external_model: str = "BAAI/bge-small-en-v1.5"
|
| 49 |
+
max_length: int = 512
|
| 50 |
+
batch_size: int = 32
|
| 51 |
+
|
| 52 |
+
@dataclass
|
| 53 |
+
class RetrievalConfig:
|
| 54 |
+
hybrid: bool = True
|
| 55 |
+
structural_language: str = "sql"
|
| 56 |
+
structural_weight_alpha: float = 0.7
|
| 57 |
+
topk_sweep: List[int] = field(default_factory=lambda: [1, 2, 3, 5, 8])
|
| 58 |
+
|
| 59 |
+
@dataclass
|
| 60 |
+
class LLMBaselineConfig:
|
| 61 |
+
backend: str = "api" # 'local' or 'api'
|
| 62 |
+
local_model: str = "Qwen/Qwen2.5-Coder-0.5B-Instruct"
|
| 63 |
+
api_model: str = "gemini-3.6-flash"
|
| 64 |
+
api_key_env: str = "GEMINI_API_KEY"
|
| 65 |
+
max_new_tokens: int = 256
|
| 66 |
+
temperature: float = 0.2
|
| 67 |
+
|
| 68 |
+
@dataclass
|
| 69 |
+
class GenerationConfig:
|
| 70 |
+
max_length: int = 512
|
| 71 |
+
temperature: float = 0.2
|
| 72 |
+
top_k: int = 50
|
| 73 |
+
top_p: float = 0.95
|
| 74 |
+
num_return_sequences: int = 1
|
| 75 |
+
|
| 76 |
+
@dataclass
|
| 77 |
+
class EvaluationConfig:
|
| 78 |
+
device: str = "cpu"
|
| 79 |
+
bertscore_model: str = "microsoft/codebert-base"
|
| 80 |
+
bertscore_local_path: str = "models/codebert-base"
|
| 81 |
+
timeout_seconds: int = 3
|
| 82 |
+
|
| 83 |
+
def resolve_bertscore_model(self, base_dir: Optional[Path] = None) -> str:
|
| 84 |
+
candidates = []
|
| 85 |
+
local = Path(self.bertscore_local_path)
|
| 86 |
+
if base_dir is not None:
|
| 87 |
+
candidates.append(Path(base_dir) / self.bertscore_local_path)
|
| 88 |
+
candidates.append(local)
|
| 89 |
+
# Fallback: resolve relative to this config.py's own directory (the
|
| 90 |
+
# project root), since that's where `models/codebert-base` is
|
| 91 |
+
# expected to live per the project layout.
|
| 92 |
+
candidates.append(Path(__file__).resolve().parent / self.bertscore_local_path)
|
| 93 |
+
|
| 94 |
+
for candidate in candidates:
|
| 95 |
+
if candidate.is_dir() and (candidate / "config.json").exists():
|
| 96 |
+
return str(candidate.resolve())
|
| 97 |
+
|
| 98 |
+
logger.info(
|
| 99 |
+
f"Local CodeBERT checkpoint not found at '{self.bertscore_local_path}' "
|
| 100 |
+
f"(tried: {[str(c) for c in candidates]}); falling back to "
|
| 101 |
+
f"downloading '{self.bertscore_model}' from the Hugging Face Hub."
|
| 102 |
+
)
|
| 103 |
+
return self.bertscore_model
|
| 104 |
+
|
| 105 |
+
@dataclass
|
| 106 |
+
class OutputConfig:
|
| 107 |
+
logs_dir: str = "output/logs"
|
| 108 |
+
metrics_path: str = "output/metrics/evaluation_metrics.json"
|
| 109 |
+
plots_dir: str = "output/plots"
|
| 110 |
+
generated_dir: str = "outputs/generated"
|
| 111 |
+
|
| 112 |
+
@dataclass
|
| 113 |
+
class IndexingConfig:
|
| 114 |
+
top_k: int = 3
|
| 115 |
+
vector_dim: int = 1024
|
| 116 |
+
|
| 117 |
+
class PipelineConfig:
|
| 118 |
+
def __init__(self, yaml_path: Optional[str] = None):
|
| 119 |
+
if yaml_path is None:
|
| 120 |
+
base_path = Path(__file__).resolve().parent
|
| 121 |
+
yaml_path = base_path / "configs" / "config.yaml"
|
| 122 |
+
|
| 123 |
+
self.yaml_path = Path(yaml_path)
|
| 124 |
+
self._load_yaml()
|
| 125 |
+
|
| 126 |
+
def _load_yaml(self):
|
| 127 |
+
if self.yaml_path.exists():
|
| 128 |
+
with open(self.yaml_path, "r", encoding="utf-8") as f:
|
| 129 |
+
data = yaml.safe_load(f) or {}
|
| 130 |
+
else:
|
| 131 |
+
logger.warning(f"Config not found at {self.yaml_path}. Using defaults.")
|
| 132 |
+
data = {}
|
| 133 |
+
|
| 134 |
+
path_data = data.get('data_paths', {})
|
| 135 |
+
idx_data = data.get('indices_paths', {})
|
| 136 |
+
self.paths = PathConfig(
|
| 137 |
+
data_dir=Path(path_data.get('raw_dir', "data/raw")),
|
| 138 |
+
faiss_index_path=Path(idx_data.get('faiss_index', "data/indices/faiss_semantic.index")),
|
| 139 |
+
ast_store_path=Path(idx_data.get('ast_store', "data/indices/ast_store.pkl"))
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
self.model = ModelConfig(
|
| 143 |
+
name_or_path=data.get('model_name', "Qwen/Qwen2.5-Coder-0.5B-Instruct"),
|
| 144 |
+
embedding_name=data.get('embedding_model', "BAAI/bge-small-en-v1.5")
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
self.lora = _build(LoRAConfig, data.get('lora', {}))
|
| 148 |
+
self.embedding = _build(EmbeddingConfig, data.get('embedding', {}))
|
| 149 |
+
self.retrieval = _build(RetrievalConfig, data.get('retrieval', {}))
|
| 150 |
+
self.llm_baseline = _build(LLMBaselineConfig, data.get('llm_baseline', {}))
|
| 151 |
+
self.generation = _build(GenerationConfig, data.get('generation', {}))
|
| 152 |
+
self.evaluation = _build(EvaluationConfig, data.get('evaluation', {}))
|
| 153 |
+
self.outputs = _build(OutputConfig, data.get('outputs', {}))
|
| 154 |
+
self.indexing = _build(IndexingConfig, data.get('indexing', {}))
|
| 155 |
+
|
| 156 |
+
# Top-level attributes for backward compatibility
|
| 157 |
+
self.model_name = self.model.name_or_path
|
| 158 |
+
self.embedding_model = self.model.embedding_name
|
| 159 |
+
self.data_paths = self.paths
|
| 160 |
+
self.indices_paths = self.paths
|
| 161 |
+
|
| 162 |
+
def load_config(yaml_path: Optional[str] = None) -> PipelineConfig:
|
| 163 |
+
return PipelineConfig(yaml_path=yaml_path)
|
src/data/data_loader.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
import logging
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
class DatasetLoader:
|
| 10 |
+
def __init__(self, target_filename="qwen_spider_mongodb_conversion.json"):
|
| 11 |
+
self.target_filename = target_filename
|
| 12 |
+
self.data = None
|
| 13 |
+
self.file_path = None
|
| 14 |
+
|
| 15 |
+
def scan_and_find_file(self, search_dirs=None):
|
| 16 |
+
"""Scans directories recursively to find the target JSON file."""
|
| 17 |
+
if search_dirs is None:
|
| 18 |
+
search_dirs = [
|
| 19 |
+
Path.cwd(),
|
| 20 |
+
Path.cwd().parent,
|
| 21 |
+
Path.cwd() / "data",
|
| 22 |
+
Path.cwd() / "data" / "raw",
|
| 23 |
+
Path.cwd() / "data" / "raw" / "Spider",
|
| 24 |
+
Path.cwd() / "outputs",
|
| 25 |
+
]
|
| 26 |
+
|
| 27 |
+
search_dirs = list({d.resolve() for d in search_dirs if d.exists()})
|
| 28 |
+
|
| 29 |
+
for directory in search_dirs:
|
| 30 |
+
for path in directory.rglob(self.target_filename):
|
| 31 |
+
if path.is_file():
|
| 32 |
+
logger.info(f"✅ Found {self.target_filename} at: {path}")
|
| 33 |
+
return path
|
| 34 |
+
|
| 35 |
+
logger.warning(f"File not found in predefined paths. Scanning entire {Path.cwd()}...")
|
| 36 |
+
for path in Path.cwd().rglob(self.target_filename):
|
| 37 |
+
if path.is_file():
|
| 38 |
+
logger.info(f"✅ Found {self.target_filename} at: {path}")
|
| 39 |
+
return path
|
| 40 |
+
|
| 41 |
+
raise FileNotFoundError(f"❌ Could not find {self.target_filename} in any scanned directories.")
|
| 42 |
+
|
| 43 |
+
def load_data(self, file_path=None):
|
| 44 |
+
"""Loads the JSON data and parses it into a list of dictionaries."""
|
| 45 |
+
if file_path is None:
|
| 46 |
+
file_path = self.scan_and_find_file()
|
| 47 |
+
else:
|
| 48 |
+
file_path = Path(file_path)
|
| 49 |
+
if not file_path.exists():
|
| 50 |
+
raise FileNotFoundError(f"❌ Provided path does not exist: {file_path}")
|
| 51 |
+
|
| 52 |
+
self.file_path = file_path
|
| 53 |
+
logger.info(f"📂 Loading data from {file_path}...")
|
| 54 |
+
|
| 55 |
+
with open(file_path, "r", encoding="utf-8") as f:
|
| 56 |
+
raw_data = json.load(f)
|
| 57 |
+
|
| 58 |
+
if "detailed_results" in raw_data:
|
| 59 |
+
self.data = raw_data["detailed_results"]
|
| 60 |
+
elif isinstance(raw_data, list):
|
| 61 |
+
self.data = raw_data
|
| 62 |
+
else:
|
| 63 |
+
self.data = [raw_data]
|
| 64 |
+
|
| 65 |
+
# Normalize: flatten generated_mongodb_query if it's a list
|
| 66 |
+
for item in self.data:
|
| 67 |
+
if "generated_mongodb_query" in item and isinstance(item["generated_mongodb_query"], list) and len(item["generated_mongodb_query"]) > 0:
|
| 68 |
+
item["generated_mongodb_query"] = item["generated_mongodb_query"][0]
|
| 69 |
+
|
| 70 |
+
logger.info(f"✅ Successfully loaded {len(self.data)} records.")
|
| 71 |
+
return self.data
|
| 72 |
+
|
| 73 |
+
def get_training_data(self):
|
| 74 |
+
"""Returns data formatted for LoRA training (Question -> MongoDB Query)."""
|
| 75 |
+
if self.data is None:
|
| 76 |
+
self.load_data()
|
| 77 |
+
|
| 78 |
+
training_data = []
|
| 79 |
+
for item in self.data:
|
| 80 |
+
question = item.get("question", "")
|
| 81 |
+
mongodb_query = item.get("generated_mongodb_query", "")
|
| 82 |
+
|
| 83 |
+
# Flatten if still a list (safety net)
|
| 84 |
+
if isinstance(mongodb_query, list) and len(mongodb_query) > 0:
|
| 85 |
+
mongodb_query = mongodb_query[0]
|
| 86 |
+
|
| 87 |
+
if question and mongodb_query:
|
| 88 |
+
training_data.append({
|
| 89 |
+
"question": question,
|
| 90 |
+
"generated_mongodb_query": mongodb_query,
|
| 91 |
+
"database_id": item.get("database_id", "")
|
| 92 |
+
})
|
| 93 |
+
|
| 94 |
+
logger.info(f"✅ Prepared {len(training_data)} training examples.")
|
| 95 |
+
return training_data
|
src/evaluation/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from evaluation.metrics import EvaluationMetrics
|
| 2 |
+
from evaluation.comparator import ModelComparator
|
| 3 |
+
from evaluation.rag_evaluation import RAGEvaluator
|
| 4 |
+
from evaluation.visualization import ResultVisualizer
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"EvaluationMetrics",
|
| 8 |
+
"ModelComparator",
|
| 9 |
+
"RAGEvaluator",
|
| 10 |
+
"ResultVisualizer"
|
| 11 |
+
]
|
src/evaluation/comparator.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from typing import List, Dict, Any, Optional
|
| 4 |
+
from evaluation.metrics import EvaluationMetrics
|
| 5 |
+
|
| 6 |
+
logger = logging.getLogger(__name__)
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def _default_bertscore_model() -> str:
|
| 10 |
+
local_dir = Path("models/codebert-base")
|
| 11 |
+
if local_dir.is_dir() and (local_dir / "config.json").exists():
|
| 12 |
+
return str(local_dir.resolve())
|
| 13 |
+
return "microsoft/codebert-base"
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class ModelComparator:
|
| 17 |
+
"""
|
| 18 |
+
Compares generation outputs across 4 categories:
|
| 19 |
+
1. Base_Model (Small Code LM)
|
| 20 |
+
2. LoRA_Model (Fine-tuned)
|
| 21 |
+
3. RAG_Pipeline (Retrieval Augmented)
|
| 22 |
+
4. LLM_Baseline (OpenAI/Gemini API)
|
| 23 |
+
"""
|
| 24 |
+
def __init__(self, device: str = "cpu", bertscore_model: Optional[str] = None):
|
| 25 |
+
self.device = device
|
| 26 |
+
self.bertscore_model = bertscore_model or _default_bertscore_model()
|
| 27 |
+
logger.info(f"ModelComparator using BERTScore/CodeBERTScore backbone: {self.bertscore_model}")
|
| 28 |
+
self.metrics = EvaluationMetrics()
|
| 29 |
+
|
| 30 |
+
def compare(
|
| 31 |
+
self,
|
| 32 |
+
references: List[str],
|
| 33 |
+
base_preds: List[str],
|
| 34 |
+
lora_preds: List[str],
|
| 35 |
+
rag_preds: Optional[List[str]] = None,
|
| 36 |
+
llm_preds: Optional[List[str]] = None,
|
| 37 |
+
test_cases: Optional[List[str]] = None,
|
| 38 |
+
task_type: str = "sql" # "sql" or "doc"
|
| 39 |
+
) -> Dict[str, Dict[str, float]]:
|
| 40 |
+
|
| 41 |
+
results = {}
|
| 42 |
+
categories = {
|
| 43 |
+
"Base_Model": base_preds,
|
| 44 |
+
"LoRA_Model": lora_preds
|
| 45 |
+
}
|
| 46 |
+
if rag_preds: categories["RAG_Pipeline"] = rag_preds
|
| 47 |
+
if llm_preds: categories["LLM_Baseline"] = llm_preds
|
| 48 |
+
|
| 49 |
+
for name, preds in categories.items():
|
| 50 |
+
logger.info(f"Calculating metrics for {name}...")
|
| 51 |
+
cat_metrics = {}
|
| 52 |
+
|
| 53 |
+
# Text-based metrics (BLEU, ROUGE, BERTScore)
|
| 54 |
+
cat_metrics["BLEU"] = self.metrics.compute_bleu(references, preds)
|
| 55 |
+
rouge_scores = self.metrics.compute_rouge(references, preds)
|
| 56 |
+
cat_metrics.update(rouge_scores)
|
| 57 |
+
cat_metrics["BERTScore"] = self.metrics.compute_bertscore(
|
| 58 |
+
references, preds, device=self.device, model_type=self.bertscore_model
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
# Exact & Response Accuracy
|
| 62 |
+
# exact_matches = [self.metrics.compute_exact_match(r, p) for r, p in zip(references, preds)]
|
| 63 |
+
# cat_metrics["Exact_Match_Accuracy"] = round(sum(exact_matches) / len(exact_matches), 4) if exact_matches else 0.0
|
| 64 |
+
|
| 65 |
+
resp_acc = [self.metrics.compute_response_accuracy(r, p) for r, p in zip(references, preds)]
|
| 66 |
+
cat_metrics["Response_Accuracy"] = round(sum(resp_acc) / len(resp_acc), 4) if resp_acc else 0.0
|
| 67 |
+
|
| 68 |
+
# SQL Execution Accuracy (if task is SQL and test cases/DBs are provided)
|
| 69 |
+
if task_type == "sql" and test_cases:
|
| 70 |
+
pass
|
| 71 |
+
|
| 72 |
+
results[name] = cat_metrics
|
| 73 |
+
|
| 74 |
+
return results
|
src/evaluation/metrics.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import torch
|
| 3 |
+
import logging
|
| 4 |
+
from typing import List, Dict, Optional
|
| 5 |
+
from nltk.translate.bleu_score import corpus_bleu, SmoothingFunction
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
_BERTSCORE_NUM_LAYERS_OVERRIDES = {
|
| 10 |
+
"models/codebert-base": 10,
|
| 11 |
+
"models/codebert-base-mlm": 10
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _resolve_bertscore_num_layers(model_type: str) -> Optional[int]:
|
| 16 |
+
try:
|
| 17 |
+
from bert_score.utils import model2layers
|
| 18 |
+
except Exception:
|
| 19 |
+
model2layers = {}
|
| 20 |
+
|
| 21 |
+
if model_type in model2layers:
|
| 22 |
+
return model2layers[model_type]
|
| 23 |
+
|
| 24 |
+
short_name = model_type.split("/")[-1]
|
| 25 |
+
if short_name in model2layers:
|
| 26 |
+
return model2layers[short_name]
|
| 27 |
+
|
| 28 |
+
if model_type in _BERTSCORE_NUM_LAYERS_OVERRIDES:
|
| 29 |
+
return _BERTSCORE_NUM_LAYERS_OVERRIDES[model_type]
|
| 30 |
+
|
| 31 |
+
name_lower = model_type.replace("\\", "/").lower()
|
| 32 |
+
for key in sorted(_BERTSCORE_NUM_LAYERS_OVERRIDES, key=len, reverse=True):
|
| 33 |
+
key_short = key.split("/")[-1].lower()
|
| 34 |
+
if key_short in name_lower:
|
| 35 |
+
return _BERTSCORE_NUM_LAYERS_OVERRIDES[key]
|
| 36 |
+
|
| 37 |
+
return None
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class EvaluationMetrics:
|
| 41 |
+
|
| 42 |
+
@staticmethod
|
| 43 |
+
def _tokenize_sql(sql: str) -> List[str]:
|
| 44 |
+
"""Basic SQL tokenization for BLEU/ROUGE."""
|
| 45 |
+
if not sql: return []
|
| 46 |
+
sql = sql.lower().strip()
|
| 47 |
+
sql = re.sub(r'[^a-z0-9\s_\*\(\)\,\.\'\"\=\>\<\!\+\-\%]', ' ', sql)
|
| 48 |
+
return sql.split()
|
| 49 |
+
|
| 50 |
+
@staticmethod
|
| 51 |
+
def compute_bleu(references: List[str], predictions: List[str]) -> float:
|
| 52 |
+
try:
|
| 53 |
+
refs = [[EvaluationMetrics._tokenize_sql(r)] for r in references]
|
| 54 |
+
preds = [EvaluationMetrics._tokenize_sql(p) for p in predictions]
|
| 55 |
+
smoother = SmoothingFunction().method1
|
| 56 |
+
score = corpus_bleu(refs, preds, smoothing_function=smoother)
|
| 57 |
+
return round(score, 4)
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"BLEU calculation failed: {e}")
|
| 60 |
+
return 0.0
|
| 61 |
+
|
| 62 |
+
@staticmethod
|
| 63 |
+
def compute_rouge(references: List[str], predictions: List[str]) -> Dict[str, float]:
|
| 64 |
+
try:
|
| 65 |
+
from rouge_score import rouge_scorer
|
| 66 |
+
scorer = rouge_scorer.RougeScorer(['rouge1', 'rougeL'], use_stemmer=True)
|
| 67 |
+
rouge1_scores, rougeL_scores = [], []
|
| 68 |
+
|
| 69 |
+
for ref, pred in zip(references, predictions):
|
| 70 |
+
scores = scorer.score(ref, pred)
|
| 71 |
+
rouge1_scores.append(scores['rouge1'].fmeasure)
|
| 72 |
+
rougeL_scores.append(scores['rougeL'].fmeasure)
|
| 73 |
+
|
| 74 |
+
return {
|
| 75 |
+
"ROUGE-1": round(sum(rouge1_scores) / len(rouge1_scores), 4) if rouge1_scores else 0.0,
|
| 76 |
+
"ROUGE-L": round(sum(rougeL_scores) / len(rougeL_scores), 4) if rougeL_scores else 0.0
|
| 77 |
+
}
|
| 78 |
+
except Exception as e:
|
| 79 |
+
logger.error(f"ROUGE calculation failed: {e}")
|
| 80 |
+
return {"ROUGE-1": 0.0, "ROUGE-L": 0.0}
|
| 81 |
+
|
| 82 |
+
@staticmethod
|
| 83 |
+
def compute_bertscore(references: List[str], predictions: List[str], device: str = "cpu", model_type: str = "models/codebert-base") -> float:
|
| 84 |
+
"""CodeBERTScore: BERTScore F1 computed with a code-aware encoder (CodeBERT by default).
|
| 85 |
+
|
| 86 |
+
NOTE: `model_type` values that aren't in bert_score's built-in
|
| 87 |
+
`model2layers` table (which is the case for both "microsoft/codebert-base"
|
| 88 |
+
and "FacebookAI/roberta-base") need an explicit `num_layers`, or the
|
| 89 |
+
underlying `bert_score.score()` call raises a KeyError. See
|
| 90 |
+
`_resolve_bertscore_num_layers` above -- this is what was previously
|
| 91 |
+
making CodeBERTScore silently return 0.0 for every model category.
|
| 92 |
+
"""
|
| 93 |
+
try:
|
| 94 |
+
from bert_score import score
|
| 95 |
+
valid_pairs = [(r, p) for r, p in zip(references, predictions) if r.strip() and p.strip()]
|
| 96 |
+
if not valid_pairs:
|
| 97 |
+
return 0.0
|
| 98 |
+
refs, preds = zip(*valid_pairs)
|
| 99 |
+
|
| 100 |
+
num_layers = _resolve_bertscore_num_layers(model_type)
|
| 101 |
+
score_kwargs = {"num_layers": num_layers} if num_layers is not None else {}
|
| 102 |
+
|
| 103 |
+
with torch.no_grad():
|
| 104 |
+
P, R, F1 = score(
|
| 105 |
+
list(preds), list(refs),
|
| 106 |
+
model_type=model_type,
|
| 107 |
+
verbose=False,
|
| 108 |
+
device=device,
|
| 109 |
+
**score_kwargs,
|
| 110 |
+
)
|
| 111 |
+
return round(F1.mean().item(), 4)
|
| 112 |
+
except Exception as e:
|
| 113 |
+
logger.error(f"BERTScore ({model_type}) calculation failed: {e}")
|
| 114 |
+
return 0.0
|
| 115 |
+
|
| 116 |
+
@staticmethod
|
| 117 |
+
def _find_top_level_groups(s: str) -> List[tuple]:
|
| 118 |
+
"""Locates every top-level `{...}`/`[...]` span in `s`, skipping over
|
| 119 |
+
brace/bracket characters that appear inside double-quoted string
|
| 120 |
+
literals (so e.g. a `$regex: "a{2,3}"` value doesn't confuse the
|
| 121 |
+
bracket matching)."""
|
| 122 |
+
groups = []
|
| 123 |
+
depth = 0
|
| 124 |
+
start = None
|
| 125 |
+
in_string = False
|
| 126 |
+
i, n = 0, len(s)
|
| 127 |
+
while i < n:
|
| 128 |
+
ch = s[i]
|
| 129 |
+
if in_string:
|
| 130 |
+
if ch == '\\':
|
| 131 |
+
i += 2
|
| 132 |
+
continue
|
| 133 |
+
if ch == '"':
|
| 134 |
+
in_string = False
|
| 135 |
+
i += 1
|
| 136 |
+
continue
|
| 137 |
+
if ch == '"':
|
| 138 |
+
in_string = True
|
| 139 |
+
i += 1
|
| 140 |
+
continue
|
| 141 |
+
if ch in '{[':
|
| 142 |
+
if depth == 0:
|
| 143 |
+
start = i
|
| 144 |
+
depth += 1
|
| 145 |
+
elif ch in '}]':
|
| 146 |
+
depth -= 1
|
| 147 |
+
if depth == 0 and start is not None:
|
| 148 |
+
groups.append((start, i + 1))
|
| 149 |
+
start = None
|
| 150 |
+
i += 1
|
| 151 |
+
return groups
|
| 152 |
+
|
| 153 |
+
@staticmethod
|
| 154 |
+
def _split_top_level(inner: str, sep: str) -> List[str]:
|
| 155 |
+
"""Splits `inner` on `sep`, but only at bracket-depth 0 and never
|
| 156 |
+
inside a double-quoted string, so nested `{...}`/`[...]`/`(...)`
|
| 157 |
+
and comma-containing string values aren't split incorrectly."""
|
| 158 |
+
parts, depth, current = [], 0, []
|
| 159 |
+
in_string = False
|
| 160 |
+
i = 0
|
| 161 |
+
while i < len(inner):
|
| 162 |
+
ch = inner[i]
|
| 163 |
+
if in_string:
|
| 164 |
+
current.append(ch)
|
| 165 |
+
if ch == '\\' and i + 1 < len(inner):
|
| 166 |
+
current.append(inner[i + 1])
|
| 167 |
+
i += 1
|
| 168 |
+
elif ch == '"':
|
| 169 |
+
in_string = False
|
| 170 |
+
i += 1
|
| 171 |
+
continue
|
| 172 |
+
if ch == '"':
|
| 173 |
+
in_string = True
|
| 174 |
+
current.append(ch)
|
| 175 |
+
i += 1
|
| 176 |
+
continue
|
| 177 |
+
if ch in '{[(':
|
| 178 |
+
depth += 1
|
| 179 |
+
elif ch in '}])':
|
| 180 |
+
depth -= 1
|
| 181 |
+
if ch == sep and depth == 0:
|
| 182 |
+
parts.append(''.join(current))
|
| 183 |
+
current = []
|
| 184 |
+
else:
|
| 185 |
+
current.append(ch)
|
| 186 |
+
i += 1
|
| 187 |
+
parts.append(''.join(current))
|
| 188 |
+
return [p for p in parts if p.strip() != '']
|
| 189 |
+
|
| 190 |
+
@classmethod
|
| 191 |
+
def _canon_group(cls, s: str) -> str:
|
| 192 |
+
s = s.strip()
|
| 193 |
+
if s.startswith('{') and s.endswith('}'):
|
| 194 |
+
inner = s[1:-1]
|
| 195 |
+
items = cls._split_top_level(inner, ',')
|
| 196 |
+
kvs = []
|
| 197 |
+
for item in items:
|
| 198 |
+
if ':' not in item:
|
| 199 |
+
return s # malformed for our purposes -- bail out safely
|
| 200 |
+
k, v = item.split(':', 1)
|
| 201 |
+
kvs.append((k.strip(), cls._canon_group(v.strip())))
|
| 202 |
+
kvs.sort(key=lambda kv: kv[0])
|
| 203 |
+
return '{' + ','.join(f'{k}:{v}' for k, v in kvs) + '}'
|
| 204 |
+
elif s.startswith('[') and s.endswith(']'):
|
| 205 |
+
inner = s[1:-1]
|
| 206 |
+
items = cls._split_top_level(inner, ',')
|
| 207 |
+
return '[' + ','.join(cls._canon_group(i.strip()) for i in items) + ']'
|
| 208 |
+
return s
|
| 209 |
+
|
| 210 |
+
@classmethod
|
| 211 |
+
def _canonicalize_mql(cls, s: str) -> str:
|
| 212 |
+
try:
|
| 213 |
+
if s.count('{') != s.count('}') or s.count('[') != s.count(']'):
|
| 214 |
+
return s
|
| 215 |
+
groups = cls._find_top_level_groups(s)
|
| 216 |
+
if not groups:
|
| 217 |
+
return s
|
| 218 |
+
out, last = [], 0
|
| 219 |
+
for gstart, gend in groups:
|
| 220 |
+
out.append(s[last:gstart])
|
| 221 |
+
out.append(cls._canon_group(s[gstart:gend]))
|
| 222 |
+
last = gend
|
| 223 |
+
out.append(s[last:])
|
| 224 |
+
return ''.join(out)
|
| 225 |
+
except Exception:
|
| 226 |
+
return s
|
| 227 |
+
|
| 228 |
+
# @classmethod
|
| 229 |
+
# def compute_exact_match(cls, gold: str, pred: str) -> float:
|
| 230 |
+
# if not gold or not pred:
|
| 231 |
+
# return 0.0
|
| 232 |
+
|
| 233 |
+
# def normalize(s):
|
| 234 |
+
# s = s.strip().rstrip(';')
|
| 235 |
+
# s = s.replace("'", '"') # Normalize single quotes to double quotes
|
| 236 |
+
# s = cls._canonicalize_mql(s) # Order-independent object-key comparison
|
| 237 |
+
# s = re.sub(r'\s+', '', s) # Remove all remaining whitespace
|
| 238 |
+
# return s.lower()
|
| 239 |
+
|
| 240 |
+
# return 1.0 if normalize(gold) == normalize(pred) else 0.0
|
| 241 |
+
|
| 242 |
+
@staticmethod
|
| 243 |
+
def _tokenize_mql(query: str) -> List[str]:
|
| 244 |
+
"""Tokenizer optimized for MongoDB Query Language (MQL) brackets and punctuation."""
|
| 245 |
+
if not query: return []
|
| 246 |
+
# Add spaces around brackets, commas, colons, and dots
|
| 247 |
+
query = re.sub(r'([(){}[\],:.])', r' \1 ', query)
|
| 248 |
+
return [tok.lower() for tok in query.split() if tok.strip()]
|
| 249 |
+
|
| 250 |
+
@staticmethod
|
| 251 |
+
def compute_response_accuracy(gold: str, pred: str) -> float:
|
| 252 |
+
"""Semantic response accuracy (recall-based token overlap)."""
|
| 253 |
+
if not gold or not pred: return 0.0
|
| 254 |
+
g_tokens = set(EvaluationMetrics._tokenize_mql(gold))
|
| 255 |
+
p_tokens = set(EvaluationMetrics._tokenize_mql(pred))
|
| 256 |
+
if not g_tokens: return 0.0
|
| 257 |
+
overlap = len(g_tokens.intersection(p_tokens))
|
| 258 |
+
return round(overlap / len(g_tokens), 4)
|
src/evaluation/rag_evaluation.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
from typing import List, Dict, Any
|
| 3 |
+
import matplotlib.pyplot as plt
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from evaluation.metrics import EvaluationMetrics
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
class RAGEvaluator:
|
| 10 |
+
"""Evaluates RAG pipeline improvements and optimizes Top-K retrieval."""
|
| 11 |
+
|
| 12 |
+
def __init__(self, comparator):
|
| 13 |
+
self.comparator = comparator
|
| 14 |
+
self.metrics = EvaluationMetrics()
|
| 15 |
+
logger.info(f"RAGEvaluator using BERTScore/CodeBERTScore backbone: {getattr(comparator, 'bertscore_model', 'models/codebert-base')}")
|
| 16 |
+
|
| 17 |
+
def measure_rag_improvement(self, rag_pipeline, references, queries, lora_preds, top_k=3):
|
| 18 |
+
logger.info(f"Measuring RAG improvement over baseline LoRA (top_k={top_k})...")
|
| 19 |
+
rag_preds = []
|
| 20 |
+
for q in queries:
|
| 21 |
+
out, _ = rag_pipeline.generate_with_rag(q, top_k=top_k)
|
| 22 |
+
rag_preds.append(out)
|
| 23 |
+
|
| 24 |
+
bertscore_model = getattr(self.comparator, "bertscore_model", "models/codebert-base")
|
| 25 |
+
|
| 26 |
+
rag_metrics = {
|
| 27 |
+
"BLEU": self.metrics.compute_bleu(references, rag_preds),
|
| 28 |
+
"BERTScore": self.metrics.compute_bertscore(references, rag_preds, device=self.comparator.device, model_type=bertscore_model)
|
| 29 |
+
# "Exact_Match_Accuracy": round(sum([self.metrics.compute_exact_match(r, p) for r, p in zip(references, rag_preds)]) / len(references), 4)
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
baseline_metrics = {
|
| 33 |
+
"BLEU": self.metrics.compute_bleu(references, lora_preds),
|
| 34 |
+
"BERTScore": self.metrics.compute_bertscore(references, lora_preds, device=self.comparator.device, model_type=bertscore_model)
|
| 35 |
+
# "Exact_Match_Accuracy": round(sum([self.metrics.compute_exact_match(r, p) for r, p in zip(references, lora_preds)]) / len(references), 4)
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
gain = {k: round(rag_metrics[k] - baseline_metrics[k], 4) for k in rag_metrics}
|
| 39 |
+
|
| 40 |
+
return {
|
| 41 |
+
"metrics": {"RAG_Pipeline": rag_metrics, "Baseline_LoRA": baseline_metrics},
|
| 42 |
+
"gain_over_baseline": gain,
|
| 43 |
+
"baseline_used_for_gain": "Baseline_LoRA"
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
def sweep_top_k(self, rag_pipeline, references, queries, baseline_preds, k_values=[1, 2, 3, 5, 8], verbose=True):
|
| 47 |
+
sweep_results = {}
|
| 48 |
+
|
| 49 |
+
# Pre-compute baseline metrics once to save time
|
| 50 |
+
baseline_bleu = self.metrics.compute_bleu(references, baseline_preds)
|
| 51 |
+
baseline_rouge = self.metrics.compute_rouge(references, baseline_preds).get("ROUGE-1", 0.0)
|
| 52 |
+
|
| 53 |
+
for k in k_values:
|
| 54 |
+
logger.info(f"Sweeping top_k={k}...")
|
| 55 |
+
preds = []
|
| 56 |
+
for q in queries:
|
| 57 |
+
out, _ = rag_pipeline.generate_with_rag(q, top_k=k)
|
| 58 |
+
preds.append(out)
|
| 59 |
+
|
| 60 |
+
# Use BLEU and ROUGE-1 for sweeping
|
| 61 |
+
bleu = self.metrics.compute_bleu(references, preds)
|
| 62 |
+
rouge1 = self.metrics.compute_rouge(references, preds).get("ROUGE-1", 0.0)
|
| 63 |
+
|
| 64 |
+
metrics = {"BLEU": bleu, "ROUGE-1": rouge1}
|
| 65 |
+
gain = {
|
| 66 |
+
"BLEU": round(bleu - baseline_bleu, 4),
|
| 67 |
+
"ROUGE-1": round(rouge1 - baseline_rouge, 4)
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
sweep_results[k] = {"metrics": metrics, "gain_over_baseline": gain, "predictions": preds}
|
| 71 |
+
|
| 72 |
+
if verbose:
|
| 73 |
+
print("\n" + self.format_sweep_table(sweep_results))
|
| 74 |
+
|
| 75 |
+
return sweep_results
|
| 76 |
+
|
| 77 |
+
@staticmethod
|
| 78 |
+
def format_sweep_table(sweep_results: Dict[int, Dict[str, Any]]) -> str:
|
| 79 |
+
ks = list(sweep_results.keys())
|
| 80 |
+
if not ks:
|
| 81 |
+
return "(no sweep results to display)"
|
| 82 |
+
|
| 83 |
+
columns = [
|
| 84 |
+
("Top-K", lambda k, r: str(k)),
|
| 85 |
+
("BLEU", lambda k, r: f"{r['metrics'].get('BLEU', 0.0):.4f}"),
|
| 86 |
+
("ROUGE-1", lambda k, r: f"{r['metrics'].get('ROUGE-1', 0.0):.4f}"),
|
| 87 |
+
("BLEU Gain", lambda k, r: f"{r['gain_over_baseline'].get('BLEU', 0.0):+.4f}"),
|
| 88 |
+
("ROUGE-1 Gain", lambda k, r: f"{r['gain_over_baseline'].get('ROUGE-1', 0.0):+.4f}"),
|
| 89 |
+
]
|
| 90 |
+
|
| 91 |
+
rows = [[render(k, sweep_results[k]) for _, render in columns] for k in ks]
|
| 92 |
+
headers = [name for name, _ in columns]
|
| 93 |
+
|
| 94 |
+
widths = [
|
| 95 |
+
max(len(headers[i]), *(len(row[i]) for row in rows))
|
| 96 |
+
for i in range(len(columns))
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
def fmt_row(cells):
|
| 100 |
+
return "| " + " | ".join(c.center(w) for c, w in zip(cells, widths)) + " |"
|
| 101 |
+
|
| 102 |
+
separator = "+-" + "-+-".join("-" * w for w in widths) + "-+"
|
| 103 |
+
|
| 104 |
+
lines = ["Top-K Retrieval Sweep Results", separator, fmt_row(headers), separator]
|
| 105 |
+
lines += [fmt_row(row) for row in rows]
|
| 106 |
+
lines.append(separator)
|
| 107 |
+
return "\n".join(lines)
|
| 108 |
+
|
| 109 |
+
def best_k(self, sweep_results, metric="BLEU"):
|
| 110 |
+
best_k_val = -999
|
| 111 |
+
best_k = None
|
| 112 |
+
for k, res in sweep_results.items():
|
| 113 |
+
val = res["gain_over_baseline"].get(metric, -999)
|
| 114 |
+
if val > best_k_val:
|
| 115 |
+
best_k_val = val
|
| 116 |
+
best_k = k
|
| 117 |
+
return best_k if best_k is not None else list(sweep_results.keys())[0]
|
| 118 |
+
|
| 119 |
+
def plot_gain_vs_k(self, sweep_results, save_path="output/plots/topk_gain_sweep.png"):
|
| 120 |
+
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
|
| 121 |
+
ks = list(sweep_results.keys())
|
| 122 |
+
bleu_gains = [sweep_results[k]["gain_over_baseline"].get("BLEU", 0) for k in ks]
|
| 123 |
+
|
| 124 |
+
plt.figure(figsize=(8, 5))
|
| 125 |
+
plt.plot(ks, bleu_gains, marker='o', linestyle='-', color='b')
|
| 126 |
+
plt.title("RAG Gain over Baseline by Top-K")
|
| 127 |
+
plt.xlabel("Top-K Retrieved Contexts")
|
| 128 |
+
plt.ylabel("BLEU Score Gain")
|
| 129 |
+
plt.grid(True)
|
| 130 |
+
plt.savefig(save_path)
|
| 131 |
+
plt.close()
|
| 132 |
+
logger.info(f"✅ Saved Top-K sweep plot to {save_path}")
|
src/evaluation/visualization.py
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import seaborn as sns
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
from typing import Dict
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
class ResultVisualizer:
|
| 11 |
+
"""Visualizes evaluation metrics across Base, LoRA, RAG, and LLM categories."""
|
| 12 |
+
|
| 13 |
+
def __init__(self, output_dir: str = "output/plots"):
|
| 14 |
+
self.output_dir = Path(output_dir)
|
| 15 |
+
self.output_dir.mkdir(parents=True, exist_ok=True)
|
| 16 |
+
sns.set_theme(style="whitegrid", palette="muted")
|
| 17 |
+
|
| 18 |
+
def plot_comparison(self, eval_results: Dict[str, Dict[str, float]], save_name: str = "model_comparison.png", bertscore_model_label: str = None):
|
| 19 |
+
# Flatten dictionary for seaborn
|
| 20 |
+
data = []
|
| 21 |
+
for model_name, metrics in eval_results.items():
|
| 22 |
+
for metric_name, value in metrics.items():
|
| 23 |
+
data.append({"Model": model_name, "Metric": metric_name, "Score": value})
|
| 24 |
+
|
| 25 |
+
df = pd.DataFrame(data)
|
| 26 |
+
|
| 27 |
+
if df.empty:
|
| 28 |
+
logger.warning("No data to plot.")
|
| 29 |
+
return
|
| 30 |
+
|
| 31 |
+
plt.figure(figsize=(14, 8))
|
| 32 |
+
sns.barplot(x="Metric", y="Score", hue="Model", data=df)
|
| 33 |
+
|
| 34 |
+
plt.title("Comprehensive Model Comparison: Base vs LoRA vs RAG vs LLM", fontsize=16, fontweight='bold')
|
| 35 |
+
if bertscore_model_label:
|
| 36 |
+
plt.suptitle(f"BERTScore backbone: {bertscore_model_label}", fontsize=9, y=0.93, color="gray")
|
| 37 |
+
plt.ylabel("Score", fontsize=12)
|
| 38 |
+
plt.xlabel("Evaluation Metric", fontsize=12)
|
| 39 |
+
plt.xticks(rotation=45, ha='right')
|
| 40 |
+
plt.legend(title="Model Category", bbox_to_anchor=(1.05, 1), loc='upper left')
|
| 41 |
+
plt.tight_layout()
|
| 42 |
+
|
| 43 |
+
save_path = self.output_dir / save_name
|
| 44 |
+
plt.savefig(save_path, dpi=300)
|
| 45 |
+
plt.close()
|
| 46 |
+
logger.info(f"✅ Saved comparison plot to {save_path}")
|
| 47 |
+
|
| 48 |
+
def plot_radar_chart(self, eval_results: Dict[str, Dict[str, float]], save_name: str = "radar_comparison.png"):
|
| 49 |
+
"""Generates a radar chart for a holistic view of model capabilities."""
|
| 50 |
+
import numpy as np
|
| 51 |
+
|
| 52 |
+
categories = list(next(iter(eval_results.values())).keys())
|
| 53 |
+
N = len(categories)
|
| 54 |
+
if N == 0: return
|
| 55 |
+
|
| 56 |
+
# Compute the angle for each axis
|
| 57 |
+
angles = [n / float(N) * 2 * 3.14159 for n in range(N)]
|
| 58 |
+
angles += angles[:1]
|
| 59 |
+
|
| 60 |
+
fig, ax = plt.subplots(figsize=(8, 8), subplot_kw=dict(polar=True))
|
| 61 |
+
|
| 62 |
+
for model_name, metrics in eval_results.items():
|
| 63 |
+
values = list(metrics.values())
|
| 64 |
+
values += values[:1]
|
| 65 |
+
ax.plot(angles, values, linewidth=2, label=model_name)
|
| 66 |
+
ax.fill(angles, values, alpha=0.15)
|
| 67 |
+
|
| 68 |
+
ax.set_xticks(angles[:-1])
|
| 69 |
+
ax.set_xticklabels(categories, fontsize=10)
|
| 70 |
+
plt.title("Model Capability Radar", size=15, y=1.1, fontweight='bold')
|
| 71 |
+
plt.legend(loc='upper right', bbox_to_anchor=(1.3, 1.1))
|
| 72 |
+
|
| 73 |
+
save_path = self.output_dir / save_name
|
| 74 |
+
plt.savefig(save_path, dpi=300, bbox_inches='tight')
|
| 75 |
+
plt.close()
|
| 76 |
+
logger.info(f"✅ Saved radar chart to {save_path}")
|
src/generators/__init__.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from generators.program_generator import GenerationPipeline
|
| 2 |
+
from generators.doc_generator import DocGenerator
|
| 3 |
+
from generators.commit_generator import CommitMessageGenerator
|
| 4 |
+
from generators.text_to_mongo_generator import TextToMongoGenerator
|
| 5 |
+
|
| 6 |
+
__all__ = [
|
| 7 |
+
"GenerationPipeline",
|
| 8 |
+
"DocGenerator",
|
| 9 |
+
"CommitMessageGenerator",
|
| 10 |
+
"TextToMongoGenerator"
|
| 11 |
+
]
|
src/generators/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (489 Bytes). View file
|
|
|
src/generators/__pycache__/commit_generator.cpython-312.pyc
ADDED
|
Binary file (1.62 kB). View file
|
|
|
src/generators/__pycache__/doc_generator.cpython-312.pyc
ADDED
|
Binary file (2.58 kB). View file
|
|
|
src/generators/__pycache__/program_generator.cpython-312.pyc
ADDED
|
Binary file (3.74 kB). View file
|
|
|
src/generators/__pycache__/text_to_mongo_generator.cpython-312.pyc
ADDED
|
Binary file (6.15 kB). View file
|
|
|
src/generators/commit_generator.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
logger = logging.getLogger(__name__)
|
| 4 |
+
|
| 5 |
+
class CommitMessageGenerator:
|
| 6 |
+
"""
|
| 7 |
+
Generates conventional commit messages based on git diffs.
|
| 8 |
+
"""
|
| 9 |
+
def __init__(self, pipeline):
|
| 10 |
+
self.pipeline = pipeline
|
| 11 |
+
|
| 12 |
+
def _build_prompt(self, diff: str) -> str:
|
| 13 |
+
prompt = (
|
| 14 |
+
"You are an expert software developer. Generate a concise, conventional commit message "
|
| 15 |
+
"for the following git diff. The message should start with a standard type "
|
| 16 |
+
"(feat, fix, docs, style, refactor, test, chore) followed by a colon and a short, "
|
| 17 |
+
"imperative summary of the changes.\n\n"
|
| 18 |
+
f"Diff:\n{diff}\n\n"
|
| 19 |
+
"Commit Message:"
|
| 20 |
+
)
|
| 21 |
+
return prompt
|
| 22 |
+
|
| 23 |
+
def generate_commit_msg(self, diff: str) -> str:
|
| 24 |
+
"""Generates a commit message for the provided git diff."""
|
| 25 |
+
prompt = self._build_prompt(diff)
|
| 26 |
+
return self.pipeline.generate(prompt)
|
src/generators/doc_generator.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
|
| 3 |
+
logger = logging.getLogger(__name__)
|
| 4 |
+
|
| 5 |
+
class DocGenerator:
|
| 6 |
+
"""
|
| 7 |
+
Generates documentation/docstrings for code snippets using 5-shot prompting.
|
| 8 |
+
"""
|
| 9 |
+
def __init__(self, pipeline):
|
| 10 |
+
self.pipeline = pipeline
|
| 11 |
+
|
| 12 |
+
def _build_prompt(self, code_snippet: str) -> str:
|
| 13 |
+
# 5 high-quality examples for few-shot learning
|
| 14 |
+
examples = [
|
| 15 |
+
{
|
| 16 |
+
"code": "def add(a, b):\n return a + b",
|
| 17 |
+
"doc": "Adds two numbers together and returns their sum."
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"code": "import os\ndef list_files(directory):\n return os.listdir(directory)",
|
| 21 |
+
"doc": "Returns a list containing the names of all files and directories in the specified directory path."
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"code": "class Dog:\n def __init__(self, name):\n self.name = name\n def bark(self):\n return 'Woof!'",
|
| 25 |
+
"doc": "A simple Dog class that initializes with a name and provides a method to make the dog bark."
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"code": "def factorial(n):\n if n == 0:\n return 1\n return n * factorial(n-1)",
|
| 29 |
+
"doc": "Calculates the factorial of a given non-negative integer n using a recursive approach."
|
| 30 |
+
},
|
| 31 |
+
{
|
| 32 |
+
"code": "import json\ndef parse_json(file_path):\n with open(file_path, 'r') as f:\n return json.load(f)",
|
| 33 |
+
"doc": "Reads a JSON file from the specified file path and returns the parsed content as a Python dictionary."
|
| 34 |
+
}
|
| 35 |
+
]
|
| 36 |
+
|
| 37 |
+
prompt = "You are an expert programmer. Generate a concise, accurate, and professional docstring/description for the given code.\n\n"
|
| 38 |
+
for ex in examples:
|
| 39 |
+
prompt += f"Code:\n{ex['code']}\nDescription: {ex['doc']}\n\n"
|
| 40 |
+
|
| 41 |
+
prompt += f"Code:\n{code_snippet}\nDescription:"
|
| 42 |
+
return prompt
|
| 43 |
+
|
| 44 |
+
def generate_docstring(self, code_snippet: str) -> str:
|
| 45 |
+
"""Generates a docstring for the provided code snippet."""
|
| 46 |
+
prompt = self._build_prompt(code_snippet)
|
| 47 |
+
return self.pipeline.generate(prompt)
|
src/generators/llm_baseline.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
import time
|
| 4 |
+
from typing import Optional
|
| 5 |
+
from google import genai
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
class LargeLLMGenerator:
|
| 10 |
+
"""
|
| 11 |
+
Wrapper for Google Gemini API to serve as an upper-bound baseline for fast, reliable generation.
|
| 12 |
+
"""
|
| 13 |
+
def __init__(self, config=None, backend: str = "api", api_model: str = "gemini-3.6-flash", local_model_name: str = None):
|
| 14 |
+
self.backend = backend
|
| 15 |
+
self.api_model = api_model
|
| 16 |
+
self.local_model_name = local_model_name
|
| 17 |
+
self.client = None
|
| 18 |
+
self.config = config
|
| 19 |
+
self.api_key_env_name = "GEMINI_API_KEY"
|
| 20 |
+
|
| 21 |
+
# Override with config if provided
|
| 22 |
+
if config and hasattr(config, "llm_baseline"):
|
| 23 |
+
llm_cfg = config.llm_baseline
|
| 24 |
+
self.backend = getattr(llm_cfg, "backend", self.backend)
|
| 25 |
+
self.api_model = getattr(llm_cfg, "api_model", self.api_model)
|
| 26 |
+
self.local_model_name = getattr(llm_cfg, "local_model", self.local_model_name)
|
| 27 |
+
self.api_key_env_name = getattr(llm_cfg, "api_key_env", None) or self.api_key_env_name
|
| 28 |
+
|
| 29 |
+
self._initialize_client()
|
| 30 |
+
|
| 31 |
+
def _initialize_client(self):
|
| 32 |
+
if self.backend == "api":
|
| 33 |
+
self._init_gemini()
|
| 34 |
+
else:
|
| 35 |
+
logger.info(f"Using local/off-the-shelf LLM backend: {self.local_model_name}")
|
| 36 |
+
|
| 37 |
+
def _init_gemini(self):
|
| 38 |
+
try:
|
| 39 |
+
api_key = os.getenv(self.api_key_env_name)
|
| 40 |
+
|
| 41 |
+
if not api_key:
|
| 42 |
+
logger.error(
|
| 43 |
+
f"API key not found. Set the '{self.api_key_env_name}' environment "
|
| 44 |
+
"variable before initializing LargeLLMGenerator."
|
| 45 |
+
)
|
| 46 |
+
return
|
| 47 |
+
|
| 48 |
+
# Initialize the modern google-genai client
|
| 49 |
+
self.client = genai.Client(api_key=api_key)
|
| 50 |
+
logger.info(f"✅ Initialized Gemini client for model: {self.api_model}")
|
| 51 |
+
except ImportError:
|
| 52 |
+
logger.error("google-genai package not installed. Run: pip install google-genai")
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.error(f"Failed to initialize Gemini client: {e}")
|
| 55 |
+
|
| 56 |
+
def generate(self, prompt: str, max_tokens: int = 512, temperature: float = 0.2) -> str:
|
| 57 |
+
if self.backend == "api":
|
| 58 |
+
return self._generate_api(prompt, max_tokens, temperature)
|
| 59 |
+
else:
|
| 60 |
+
logger.warning("Local LLM generation not fully implemented in this baseline wrapper. Returning empty string.")
|
| 61 |
+
return ""
|
| 62 |
+
|
| 63 |
+
def _generate_api(self, prompt: str, max_tokens: int, temperature: float) -> str:
|
| 64 |
+
if not self.client:
|
| 65 |
+
return "[API Client Not Initialized]"
|
| 66 |
+
try:
|
| 67 |
+
start_time = time.time()
|
| 68 |
+
|
| 69 |
+
# Generate content using the google-genai client
|
| 70 |
+
response = self.client.models.generate_content(
|
| 71 |
+
model=self.api_model,
|
| 72 |
+
contents=prompt,
|
| 73 |
+
config={
|
| 74 |
+
"max_output_tokens": max_tokens,
|
| 75 |
+
"temperature": temperature
|
| 76 |
+
}
|
| 77 |
+
)
|
| 78 |
+
text = response.text.strip()
|
| 79 |
+
|
| 80 |
+
latency = round((time.time() - start_time) * 1000, 2)
|
| 81 |
+
logger.info(f"Gemini API ({self.api_model}) generated {len(text)} chars in {latency}ms")
|
| 82 |
+
return text
|
| 83 |
+
except Exception as e:
|
| 84 |
+
logger.error(f"API Generation failed: {e}")
|
| 85 |
+
return f"[API Error: {str(e)}]"
|
src/generators/program_generator.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import torch
|
| 3 |
+
import os
|
| 4 |
+
os.environ.setdefault("PYTORCH_ENABLE_MPS_FALLBACK", "1")
|
| 5 |
+
|
| 6 |
+
from typing import Any
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class GenerationPipeline:
|
| 12 |
+
def __init__(self, model: Any, tokenizer: Any, config: Any = None):
|
| 13 |
+
self.model = model
|
| 14 |
+
self.tokenizer = tokenizer
|
| 15 |
+
self.config = config
|
| 16 |
+
|
| 17 |
+
def _build_generation_kwargs(self) -> dict:
|
| 18 |
+
"""Builds generation kwargs based on config or defaults."""
|
| 19 |
+
# Safely extract token IDs as standard Python ints to prevent PyTorch warnings
|
| 20 |
+
eos_id = self.tokenizer.eos_token_id
|
| 21 |
+
if isinstance(eos_id, torch.Tensor):
|
| 22 |
+
eos_id = eos_id.item()
|
| 23 |
+
|
| 24 |
+
kwargs = {
|
| 25 |
+
"max_new_tokens": 128, # Hard cap: MongoDB queries are short
|
| 26 |
+
"temperature": 0.2,
|
| 27 |
+
"top_p": 0.95,
|
| 28 |
+
"top_k": 50,
|
| 29 |
+
"do_sample": True,
|
| 30 |
+
"pad_token_id": eos_id,
|
| 31 |
+
"eos_token_id": eos_id,
|
| 32 |
+
"repetition_penalty": 1.2,
|
| 33 |
+
"no_repeat_ngram_size": 3, # CRITICAL: Prevents repeating blocks of numbers/text
|
| 34 |
+
}
|
| 35 |
+
if self.config:
|
| 36 |
+
kwargs["max_new_tokens"] = min(getattr(self.config, "max_length", 256), 128)
|
| 37 |
+
kwargs["temperature"] = getattr(self.config, "temperature", 0.2)
|
| 38 |
+
kwargs["top_p"] = getattr(self.config, "top_p", 0.95)
|
| 39 |
+
kwargs["top_k"] = getattr(self.config, "top_k", 50)
|
| 40 |
+
return kwargs
|
| 41 |
+
|
| 42 |
+
def generate(self, prompt: str) -> str:
|
| 43 |
+
"""Generates text from a given prompt."""
|
| 44 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
|
| 45 |
+
with torch.no_grad():
|
| 46 |
+
outputs = self.model.generate(**inputs, **self._build_generation_kwargs())
|
| 47 |
+
|
| 48 |
+
generated_text = self.tokenizer.decode(
|
| 49 |
+
outputs[0][inputs["input_ids"].shape[1]:],
|
| 50 |
+
skip_special_tokens=True
|
| 51 |
+
)
|
| 52 |
+
generated_text = generated_text.strip()
|
| 53 |
+
|
| 54 |
+
generated_text = self._clean_output(generated_text)
|
| 55 |
+
return generated_text
|
| 56 |
+
|
| 57 |
+
@staticmethod
|
| 58 |
+
def _clean_output(text: str) -> str:
|
| 59 |
+
"""Remove hallucinated refusals or trailing junk after the actual answer."""
|
| 60 |
+
# Common patterns that indicate the model went off-track
|
| 61 |
+
stop_patterns = [
|
| 62 |
+
"I'm sorry", "I can't", "I cannot", "I am sorry",
|
| 63 |
+
"I apologize", "As an AI", "As a language model",
|
| 64 |
+
"```", "\n\n", "###", "Human:", "User:", "Assistant:"
|
| 65 |
+
]
|
| 66 |
+
for pattern in stop_patterns:
|
| 67 |
+
idx = text.find(pattern)
|
| 68 |
+
if idx > 0: # Only cut if it's not at the very start
|
| 69 |
+
text = text[:idx]
|
| 70 |
+
return text.strip()
|
src/generators/text_to_mongo_generator.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import time
|
| 3 |
+
import logging
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from typing import List, Dict, Any
|
| 6 |
+
from tqdm import tqdm
|
| 7 |
+
|
| 8 |
+
logger = logging.getLogger(__name__)
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
class TextToMongoGenerator:
|
| 12 |
+
def __init__(self, pipeline, config: Any = None):
|
| 13 |
+
self.pipeline = pipeline
|
| 14 |
+
self.config = config
|
| 15 |
+
|
| 16 |
+
def _build_prompt(self, question: str, schema: str = "") -> str:
|
| 17 |
+
prompt = "### Task: Generate MongoDB Query (MQL)\n"
|
| 18 |
+
if schema:
|
| 19 |
+
prompt += f"### Collection Schema / Structure:\n{schema}\n\n"
|
| 20 |
+
prompt += f"### Question:\n{question}\n\n### MongoDB Query:\n db."
|
| 21 |
+
return prompt
|
| 22 |
+
|
| 23 |
+
def generate_single(self, question: str, schema: str = "") -> Dict[str, Any]:
|
| 24 |
+
"""Generates a single MongoDB query and measures latency."""
|
| 25 |
+
prompt = self._build_prompt(question, schema)
|
| 26 |
+
start_time = time.perf_counter()
|
| 27 |
+
raw_output = self.pipeline.generate(prompt)
|
| 28 |
+
end_time = time.perf_counter()
|
| 29 |
+
latency_ms = (end_time - start_time) * 1000
|
| 30 |
+
|
| 31 |
+
# Clean up hallucinations and repetition loops
|
| 32 |
+
generated_query = self._clean_mongo_output(raw_output)
|
| 33 |
+
|
| 34 |
+
return {
|
| 35 |
+
"generated_query": generated_query,
|
| 36 |
+
"latency_ms": round(latency_ms, 2)
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
@staticmethod
|
| 40 |
+
def clean_mongo_output(raw_output: str) -> str:
|
| 41 |
+
stripped = raw_output.strip()
|
| 42 |
+
if stripped.lower().startswith("db."):
|
| 43 |
+
# Model re-emitted the prefix itself; don't duplicate it.
|
| 44 |
+
restored = stripped
|
| 45 |
+
else:
|
| 46 |
+
restored = "db." + stripped
|
| 47 |
+
|
| 48 |
+
cleaned = (
|
| 49 |
+
restored.replace("```javascript", "")
|
| 50 |
+
.replace("```json", "")
|
| 51 |
+
.replace("```js", "")
|
| 52 |
+
.replace("```", "")
|
| 53 |
+
.strip()
|
| 54 |
+
)
|
| 55 |
+
|
| 56 |
+
# Stop at common hallucination markers
|
| 57 |
+
stop_markers = ["--->", "\n\n", "\nUser:", "\nQuestion:", "```", "###", "Human:"]
|
| 58 |
+
for marker in stop_markers:
|
| 59 |
+
idx = cleaned.find(marker)
|
| 60 |
+
if idx != -1:
|
| 61 |
+
cleaned = cleaned[:idx]
|
| 62 |
+
|
| 63 |
+
# Extract the first valid MongoDB query line
|
| 64 |
+
lines = cleaned.split('\n')
|
| 65 |
+
for line in lines:
|
| 66 |
+
line = line.strip()
|
| 67 |
+
if line.startswith("db."):
|
| 68 |
+
return line.rstrip(';').strip()
|
| 69 |
+
|
| 70 |
+
# Fallback: find "db." anywhere in the text
|
| 71 |
+
if "db." in cleaned:
|
| 72 |
+
idx = cleaned.find("db.")
|
| 73 |
+
end_idx = len(cleaned)
|
| 74 |
+
for marker in stop_markers + ["\n"]:
|
| 75 |
+
pos = cleaned.find(marker, idx)
|
| 76 |
+
if pos != -1:
|
| 77 |
+
end_idx = min(end_idx, pos)
|
| 78 |
+
return cleaned[idx:end_idx].strip().rstrip(';')
|
| 79 |
+
|
| 80 |
+
return cleaned.strip()
|
| 81 |
+
|
| 82 |
+
_clean_mongo_output = clean_mongo_output
|
| 83 |
+
|
| 84 |
+
def generate_batch(self, dataset: List[Dict[str, Any]], output_path: str = None) -> List[Dict[str, Any]]:
|
| 85 |
+
results = []
|
| 86 |
+
logger.info(f"🚀 Starting batch MongoDB query generation for {len(dataset)} examples...")
|
| 87 |
+
|
| 88 |
+
for item in tqdm(dataset, desc="Generating MongoDB Queries", unit="query"):
|
| 89 |
+
query_id = item.get("query_id", f"sample_{len(results)}")
|
| 90 |
+
database_id = item.get("database_id", "unknown")
|
| 91 |
+
question = item.get("question", "")
|
| 92 |
+
schema = item.get("schema", "")
|
| 93 |
+
gold_query = item.get("generated_mongodb_query", item.get("gold_query", ""))
|
| 94 |
+
if isinstance(gold_query, list) and len(gold_query) > 0:
|
| 95 |
+
gold_query = gold_query[0]
|
| 96 |
+
|
| 97 |
+
if not question:
|
| 98 |
+
continue
|
| 99 |
+
|
| 100 |
+
gen_result = self.generate_single(question, schema)
|
| 101 |
+
|
| 102 |
+
result_entry = {
|
| 103 |
+
"query_id": query_id,
|
| 104 |
+
"database_id": database_id,
|
| 105 |
+
"question": question,
|
| 106 |
+
"gold_query": gold_query,
|
| 107 |
+
"generated_query": gen_result["generated_query"],
|
| 108 |
+
"latency_ms": gen_result["latency_ms"],
|
| 109 |
+
}
|
| 110 |
+
results.append(result_entry)
|
| 111 |
+
|
| 112 |
+
if output_path:
|
| 113 |
+
self.save_results(results, output_path)
|
| 114 |
+
|
| 115 |
+
return results
|
| 116 |
+
|
| 117 |
+
def save_results(self, results: List[Dict[str, Any]], output_path: str):
|
| 118 |
+
path = Path(output_path)
|
| 119 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 120 |
+
|
| 121 |
+
output_data = {"results": results}
|
| 122 |
+
with open(path, "w", encoding="utf-8") as f:
|
| 123 |
+
json.dump(output_data, f, indent=4, ensure_ascii=False)
|
| 124 |
+
|
| 125 |
+
logger.info(f"✅ Successfully saved {len(results)} generated queries to {path}")
|
src/models/__pycache__/load_model.cpython-312.pyc
ADDED
|
Binary file (15.7 kB). View file
|
|
|
src/models/load_model.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import logging
|
| 3 |
+
import torch
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
from peft import LoraConfig, get_peft_model, PeftModel
|
| 7 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 8 |
+
from torch.utils.data import DataLoader, Dataset
|
| 9 |
+
|
| 10 |
+
os.environ["PYTORCH_MPS_HIGH_WATERMARK_RATIO"] = "0.0"
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
class SpiderDataset(Dataset):
|
| 15 |
+
"""Formats Spider JSON data into causal LM training examples (Question -> MongoDB Query)."""
|
| 16 |
+
|
| 17 |
+
def __init__(self, data, tokenizer, max_length=512):
|
| 18 |
+
self.data = data
|
| 19 |
+
self.tokenizer = tokenizer
|
| 20 |
+
self.max_length = max_length
|
| 21 |
+
|
| 22 |
+
def __len__(self):
|
| 23 |
+
return len(self.data)
|
| 24 |
+
|
| 25 |
+
def __getitem__(self, idx):
|
| 26 |
+
item = self.data[idx]
|
| 27 |
+
question = item.get("question", "")
|
| 28 |
+
mongodb_query = item.get("generated_mongodb_query", "")
|
| 29 |
+
|
| 30 |
+
# Handle if stored as a list
|
| 31 |
+
if isinstance(mongodb_query, list) and len(mongodb_query) > 0:
|
| 32 |
+
mongodb_query = mongodb_query[0]
|
| 33 |
+
|
| 34 |
+
prompt = f"### Task: Generate MongoDB Query (MQL)\n### Question:\n{question}\n\n### MongoDB Query:\n"
|
| 35 |
+
|
| 36 |
+
prompt_enc = self.tokenizer(prompt, truncation=True, max_length=self.max_length, return_tensors="pt")
|
| 37 |
+
completion_enc = self.tokenizer(mongodb_query, truncation=True, max_length=self.max_length, return_tensors="pt")
|
| 38 |
+
|
| 39 |
+
input_ids = torch.cat([prompt_enc.input_ids[0], completion_enc.input_ids[0]], dim=0)
|
| 40 |
+
attention_mask = torch.cat([prompt_enc.attention_mask[0], completion_enc.attention_mask[0]], dim=0)
|
| 41 |
+
|
| 42 |
+
prompt_len = prompt_enc.input_ids.shape[1]
|
| 43 |
+
labels = torch.cat([torch.full((prompt_len,), -100, dtype=torch.long), completion_enc.input_ids[0]], dim=0)
|
| 44 |
+
|
| 45 |
+
return {
|
| 46 |
+
"input_ids": input_ids,
|
| 47 |
+
"attention_mask": attention_mask,
|
| 48 |
+
"labels": labels
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
class ModelLoader:
|
| 52 |
+
def __init__(self, config, device_override=None):
|
| 53 |
+
self.config = config
|
| 54 |
+
self.device = device_override or self._detect_device()
|
| 55 |
+
if self.device == "cuda":
|
| 56 |
+
self.dtype = torch.float16
|
| 57 |
+
elif self.device == "mps":
|
| 58 |
+
self.dtype = torch.float16
|
| 59 |
+
else:
|
| 60 |
+
self.dtype = torch.float32
|
| 61 |
+
self.tokenizer = None
|
| 62 |
+
self.base_model = None
|
| 63 |
+
self.lora_model = None
|
| 64 |
+
|
| 65 |
+
logger.info(f"🖥️ Initialized ModelLoader | Device: {self.device} | Dtype: {self.dtype}")
|
| 66 |
+
|
| 67 |
+
def _detect_device(self):
|
| 68 |
+
if torch.cuda.is_available():
|
| 69 |
+
return "cuda"
|
| 70 |
+
if hasattr(torch.backends, "mps") and torch.backends.mps.is_available():
|
| 71 |
+
return "mps"
|
| 72 |
+
return "cpu"
|
| 73 |
+
|
| 74 |
+
def load_tokenizer(self):
|
| 75 |
+
model_name = getattr(self.config, "model_name", "Qwen/Qwen2.5-Coder-0.5B-Instruct")
|
| 76 |
+
logger.info(f"🔄 Loading tokenizer: {model_name}")
|
| 77 |
+
self.tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
|
| 78 |
+
if self.tokenizer.pad_token is None:
|
| 79 |
+
self.tokenizer.pad_token = self.tokenizer.eos_token
|
| 80 |
+
return self.tokenizer
|
| 81 |
+
|
| 82 |
+
def load_base_model(self):
|
| 83 |
+
model_name = getattr(self.config, "model_name", "Qwen/Qwen2.5-Coder-0.5B-Instruct")
|
| 84 |
+
logger.info(f"🔄 Loading base model: {model_name}")
|
| 85 |
+
|
| 86 |
+
if self.device == "cuda":
|
| 87 |
+
self.base_model = AutoModelForCausalLM.from_pretrained(
|
| 88 |
+
model_name,
|
| 89 |
+
torch_dtype=self.dtype,
|
| 90 |
+
trust_remote_code=True,
|
| 91 |
+
low_cpu_mem_usage=True,
|
| 92 |
+
device_map="auto"
|
| 93 |
+
)
|
| 94 |
+
else:
|
| 95 |
+
self.base_model = AutoModelForCausalLM.from_pretrained(
|
| 96 |
+
model_name,
|
| 97 |
+
torch_dtype=self.dtype,
|
| 98 |
+
trust_remote_code=True,
|
| 99 |
+
low_cpu_mem_usage=True
|
| 100 |
+
)
|
| 101 |
+
self.base_model.to(self.device)
|
| 102 |
+
|
| 103 |
+
self.base_model.eval()
|
| 104 |
+
return self.base_model
|
| 105 |
+
|
| 106 |
+
def setup_lora(self, trainable_fraction=1.0, r=16, alpha=32, dropout=0.05):
|
| 107 |
+
if self.base_model is None:
|
| 108 |
+
self.load_base_model()
|
| 109 |
+
|
| 110 |
+
target_modules = ["q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj"]
|
| 111 |
+
|
| 112 |
+
peft_config = LoraConfig(
|
| 113 |
+
r=r,
|
| 114 |
+
lora_alpha=alpha,
|
| 115 |
+
target_modules=target_modules,
|
| 116 |
+
lora_dropout=dropout,
|
| 117 |
+
bias="none",
|
| 118 |
+
task_type="CAUSAL_LM"
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
self.lora_model = get_peft_model(self.base_model, peft_config)
|
| 122 |
+
|
| 123 |
+
if trainable_fraction < 1.0:
|
| 124 |
+
self._apply_trainable_fraction(trainable_fraction)
|
| 125 |
+
else:
|
| 126 |
+
logger.info("✅ LoRA configured with 100% trainable parameters.")
|
| 127 |
+
|
| 128 |
+
self.lora_model.print_trainable_parameters()
|
| 129 |
+
return self.lora_model
|
| 130 |
+
|
| 131 |
+
def _apply_trainable_fraction(self, fraction):
|
| 132 |
+
lora_params = [(n, p) for n, p in self.lora_model.named_parameters() if "lora_" in n and p.requires_grad]
|
| 133 |
+
total_lora = len(lora_params)
|
| 134 |
+
num_to_freeze = int(total_lora * (1.0 - fraction))
|
| 135 |
+
|
| 136 |
+
if num_to_freeze > 0:
|
| 137 |
+
for i, (name, param) in enumerate(lora_params):
|
| 138 |
+
if i < num_to_freeze:
|
| 139 |
+
param.requires_grad = False
|
| 140 |
+
|
| 141 |
+
trainable_count = sum(1 for p in self.lora_model.parameters() if p.requires_grad)
|
| 142 |
+
logger.info(f"🔒 Froze {num_to_freeze}/{total_lora} LoRA parameters. Remaining trainable: {trainable_count} ({fraction*100}%).")
|
| 143 |
+
|
| 144 |
+
def autodetect_saved_model(self, output_dir):
|
| 145 |
+
output_dir = Path(output_dir)
|
| 146 |
+
if output_dir.exists() and (output_dir / "adapter_config.json").exists():
|
| 147 |
+
logger.info(f"✅ Found saved LoRA adapter at {output_dir}. Loading...")
|
| 148 |
+
if self.base_model is None:
|
| 149 |
+
self.load_base_model()
|
| 150 |
+
# NOTE (ZeroGPU compatibility): peft's from_pretrained() infers the load
|
| 151 |
+
# device via infer_device(), NOT from the base model's actual .device.
|
| 152 |
+
# On HF Spaces ZeroGPU, `spaces` monkey-patches torch.cuda.is_available()
|
| 153 |
+
# to report True even before a real GPU is attached to the process, so
|
| 154 |
+
# infer_device() always says "cuda" here -- which makes peft try to load
|
| 155 |
+
# the adapter's .safetensors weights with a raw, direct CUDA file load.
|
| 156 |
+
# That bypasses spaces' safe .to("cuda") staging and crashes with
|
| 157 |
+
# "No CUDA GPUs are available". Forcing torch_device="cpu" here loads the
|
| 158 |
+
# adapter safely on CPU; the single .to(self.device) call right after is
|
| 159 |
+
# a normal nn.Module.to() move, which spaces *does* stage/support.
|
| 160 |
+
self.lora_model = PeftModel.from_pretrained(self.base_model, str(output_dir), torch_device="cpu")
|
| 161 |
+
self.lora_model.to(self.device)
|
| 162 |
+
self.lora_model.eval()
|
| 163 |
+
return self.lora_model
|
| 164 |
+
logger.info(f"ℹ️ No saved adapter found at {output_dir}.")
|
| 165 |
+
return None
|
| 166 |
+
|
| 167 |
+
def train_lora(self, train_data, output_dir, epochs=3, batch_size=1, grad_accum=4, lr=2e-4, max_length=512):
|
| 168 |
+
lr = float(lr) # Ensure lr is a float to prevent PyTorch optimizer TypeError
|
| 169 |
+
if self.lora_model is None:
|
| 170 |
+
self.setup_lora()
|
| 171 |
+
|
| 172 |
+
dataset = SpiderDataset(train_data, self.tokenizer, max_length)
|
| 173 |
+
|
| 174 |
+
def collate_fn(batch):
|
| 175 |
+
max_len = max(len(b["input_ids"]) for b in batch)
|
| 176 |
+
input_ids, attention_mask, labels = [], [], []
|
| 177 |
+
pad_id = self.tokenizer.pad_token_id
|
| 178 |
+
|
| 179 |
+
for b in batch:
|
| 180 |
+
pad_len = max_len - len(b["input_ids"])
|
| 181 |
+
input_ids.append(b["input_ids"].tolist() + [pad_id] * pad_len)
|
| 182 |
+
attention_mask.append(b["attention_mask"].tolist() + [0] * pad_len)
|
| 183 |
+
labels.append(b["labels"].tolist() + [-100] * pad_len)
|
| 184 |
+
|
| 185 |
+
return {
|
| 186 |
+
"input_ids": torch.tensor(input_ids, dtype=torch.long).to(self.device),
|
| 187 |
+
"attention_mask": torch.tensor(attention_mask, dtype=torch.long).to(self.device),
|
| 188 |
+
"labels": torch.tensor(labels, dtype=torch.long).to(self.device)
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
loader = DataLoader(dataset, batch_size=batch_size, shuffle=True, collate_fn=collate_fn)
|
| 192 |
+
|
| 193 |
+
optimizer = torch.optim.AdamW(
|
| 194 |
+
filter(lambda p: p.requires_grad, self.lora_model.parameters()),
|
| 195 |
+
lr=lr
|
| 196 |
+
)
|
| 197 |
+
|
| 198 |
+
self.lora_model.train()
|
| 199 |
+
|
| 200 |
+
logger.info(f"🚀 Starting LoRA training | Device: {self.device} | Epochs: {epochs} | Batch: {batch_size} | Grad Accum: {grad_accum} | LR: {lr}")
|
| 201 |
+
|
| 202 |
+
for epoch in range(epochs):
|
| 203 |
+
epoch_loss = 0.0
|
| 204 |
+
progress_bar = tqdm(loader, desc=f"Epoch {epoch+1}/{epochs}", leave=False)
|
| 205 |
+
|
| 206 |
+
optimizer.zero_grad()
|
| 207 |
+
|
| 208 |
+
for step, batch in enumerate(progress_bar):
|
| 209 |
+
outputs = self.lora_model(**batch)
|
| 210 |
+
loss = outputs.loss / grad_accum
|
| 211 |
+
loss.backward()
|
| 212 |
+
epoch_loss += loss.item() * grad_accum
|
| 213 |
+
|
| 214 |
+
if (step + 1) % grad_accum == 0:
|
| 215 |
+
optimizer.step()
|
| 216 |
+
optimizer.zero_grad()
|
| 217 |
+
|
| 218 |
+
progress_bar.set_postfix({"loss": f"{loss.item() * grad_accum:.4f}"})
|
| 219 |
+
|
| 220 |
+
# Clear MPS cache periodically to prevent memory leaks on Mac
|
| 221 |
+
if self.device == "mps" and (step + 1) % 10 == 0:
|
| 222 |
+
torch.mps.empty_cache()
|
| 223 |
+
|
| 224 |
+
optimizer.step()
|
| 225 |
+
optimizer.zero_grad()
|
| 226 |
+
|
| 227 |
+
avg_loss = epoch_loss / len(loader)
|
| 228 |
+
logger.info(f"✅ Epoch {epoch+1} completed | Avg Loss: {avg_loss:.4f}")
|
| 229 |
+
|
| 230 |
+
output_dir = Path(output_dir)
|
| 231 |
+
output_dir.mkdir(parents=True, exist_ok=True)
|
| 232 |
+
self.lora_model.save_pretrained(str(output_dir))
|
| 233 |
+
self.tokenizer.save_pretrained(str(output_dir))
|
| 234 |
+
logger.info(f"💾 LoRA adapter saved to {output_dir}")
|
| 235 |
+
|
| 236 |
+
self.lora_model.eval()
|
| 237 |
+
return self.lora_model
|
| 238 |
+
|
| 239 |
+
def load_models(self, lora_path=None):
|
| 240 |
+
"""Returns a dictionary of models and the tokenizer."""
|
| 241 |
+
if self.tokenizer is None:
|
| 242 |
+
self.load_tokenizer()
|
| 243 |
+
if self.base_model is None:
|
| 244 |
+
self.load_base_model()
|
| 245 |
+
|
| 246 |
+
models = {"base": self.base_model, "lora": None}
|
| 247 |
+
|
| 248 |
+
if self.lora_model is not None:
|
| 249 |
+
logger.info("Reusing already-loaded LoRA model in memory.")
|
| 250 |
+
models["lora"] = self.lora_model
|
| 251 |
+
elif lora_path:
|
| 252 |
+
lora_path = Path(lora_path)
|
| 253 |
+
if lora_path.exists() and (lora_path / "adapter_config.json").exists():
|
| 254 |
+
logger.info(f"Loading LoRA model from {lora_path}")
|
| 255 |
+
self.lora_model = PeftModel.from_pretrained(self.base_model, str(lora_path))
|
| 256 |
+
self.lora_model.to(self.device)
|
| 257 |
+
self.lora_model.eval()
|
| 258 |
+
models["lora"] = self.lora_model
|
| 259 |
+
else:
|
| 260 |
+
logger.warning(f"LoRA path {lora_path} does not exist or is invalid. Returning base model as 'lora'.")
|
| 261 |
+
models["lora"] = self.base_model
|
| 262 |
+
|
| 263 |
+
return models, self.tokenizer
|
src/plots/baseline_vs_finetuned_latency.png
ADDED
|
src/plots/baseline_vs_finetuned_text_to_mongo.png
ADDED
|
src/plots/text_to_mongo_baseline_metrics.png
ADDED
|
src/plots/text_to_mongo_finetuned_metrics.png
ADDED
|
src/rag/__pycache__/rag_pipeline.cpython-312.pyc
ADDED
|
Binary file (9.05 kB). View file
|
|
|
src/rag/rag_pipeline.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import torch
|
| 3 |
+
from typing import Any, List, Tuple, Dict, Optional
|
| 4 |
+
|
| 5 |
+
logger = logging.getLogger(__name__)
|
| 6 |
+
|
| 7 |
+
METADATA_TEXT_FIELDS = ["generated_mongodb_query", "mongodb_query", "code", "sql", "SQL", "question", "text", "schema"]
|
| 8 |
+
|
| 9 |
+
class RAGPipeline:
|
| 10 |
+
def __init__(
|
| 11 |
+
self,
|
| 12 |
+
retriever: Any,
|
| 13 |
+
generator: Optional[Any] = None,
|
| 14 |
+
model: Optional[Any] = None,
|
| 15 |
+
tokenizer: Optional[Any] = None,
|
| 16 |
+
config: Any = None
|
| 17 |
+
):
|
| 18 |
+
"""
|
| 19 |
+
Args:
|
| 20 |
+
retriever: An instance of IndexManager or HybridRetriever that has a `.search(query, k)` method.
|
| 21 |
+
generator: An instance of GenerationPipeline (has `.generate(prompt)`).
|
| 22 |
+
If provided, `model` and `tokenizer` are ignored.
|
| 23 |
+
model: HuggingFace model (used if generator is None).
|
| 24 |
+
tokenizer: HuggingFace tokenizer (used if generator is None).
|
| 25 |
+
config: Generation configuration object.
|
| 26 |
+
"""
|
| 27 |
+
self.retriever = retriever
|
| 28 |
+
self.generator = generator
|
| 29 |
+
self.model = model
|
| 30 |
+
self.tokenizer = tokenizer
|
| 31 |
+
self.config = config
|
| 32 |
+
|
| 33 |
+
if self.generator is None and (self.model is None or self.tokenizer is None):
|
| 34 |
+
raise ValueError("Either `generator` or both `model` and `tokenizer` must be provided.")
|
| 35 |
+
|
| 36 |
+
def retrieve_context(self, query: str, top_k: int = 3) -> List[Dict[str, Any]]:
|
| 37 |
+
"""
|
| 38 |
+
Retrieves relevant context using the hybrid retriever.
|
| 39 |
+
Returns a list of metadata dictionaries.
|
| 40 |
+
"""
|
| 41 |
+
try:
|
| 42 |
+
# The new HybridRetriever/IndexManager returns List[Dict] or List[Tuple[Dict, float]]
|
| 43 |
+
results = self.retriever.search(query, k=top_k)
|
| 44 |
+
|
| 45 |
+
# Handle different return formats gracefully
|
| 46 |
+
if results and isinstance(results[0], tuple):
|
| 47 |
+
return [metadata for metadata, score in results]
|
| 48 |
+
elif results and isinstance(results[0], dict):
|
| 49 |
+
return results
|
| 50 |
+
else:
|
| 51 |
+
return []
|
| 52 |
+
except Exception as e:
|
| 53 |
+
logger.error(f"Error during context retrieval: {e}")
|
| 54 |
+
return []
|
| 55 |
+
|
| 56 |
+
@staticmethod
|
| 57 |
+
def _metadata_to_text(metadata: Any) -> str:
|
| 58 |
+
"""Extracts the most relevant text field from a metadata dictionary."""
|
| 59 |
+
if not isinstance(metadata, dict):
|
| 60 |
+
return str(metadata)
|
| 61 |
+
|
| 62 |
+
for field in METADATA_TEXT_FIELDS:
|
| 63 |
+
if metadata.get(field):
|
| 64 |
+
return str(metadata[field])
|
| 65 |
+
|
| 66 |
+
# Fallback: concatenate all string values
|
| 67 |
+
return " | ".join([f"{k}: {v}" for k, v in metadata.items() if isinstance(v, str)])
|
| 68 |
+
|
| 69 |
+
def build_prompt(self, query: str, context_items: List[Dict[str, Any]]) -> str:
|
| 70 |
+
context_str = ""
|
| 71 |
+
for i, item in enumerate(context_items):
|
| 72 |
+
mongodb_query = (
|
| 73 |
+
item.get('generated_mongodb_query', '')
|
| 74 |
+
or item.get('mongodb_query', '')
|
| 75 |
+
)
|
| 76 |
+
question = item.get('question', '')
|
| 77 |
+
|
| 78 |
+
if isinstance(mongodb_query, list) and len(mongodb_query) > 0:
|
| 79 |
+
mongodb_query = mongodb_query[0]
|
| 80 |
+
|
| 81 |
+
if mongodb_query:
|
| 82 |
+
context_str += f"--- Example {i + 1} ---\n"
|
| 83 |
+
if question:
|
| 84 |
+
context_str += f"Question: {question}\n"
|
| 85 |
+
context_str += f"MongoDB Query: {mongodb_query}\n\n"
|
| 86 |
+
|
| 87 |
+
prompt = (
|
| 88 |
+
"You are an expert MongoDB query generator. Given a question, output ONLY the MongoDB query.\n"
|
| 89 |
+
"Do not include explanations, apologies, or extra text.\n\n"
|
| 90 |
+
f"Context Examples:\n{context_str}\n"
|
| 91 |
+
f"### Task: Generate MongoDB Query (MQL)\n"
|
| 92 |
+
f"### Question:\n{query}\n\n"
|
| 93 |
+
f"### MongoDB Query:\ndb."
|
| 94 |
+
)
|
| 95 |
+
return prompt
|
| 96 |
+
|
| 97 |
+
def _build_generation_kwargs(self) -> dict:
|
| 98 |
+
eos_id = self.tokenizer.eos_token_id if self.tokenizer else None
|
| 99 |
+
if isinstance(eos_id, torch.Tensor):
|
| 100 |
+
eos_id = eos_id.item()
|
| 101 |
+
|
| 102 |
+
kwargs = {
|
| 103 |
+
"max_new_tokens": 128,
|
| 104 |
+
"temperature": 0.2,
|
| 105 |
+
"top_p": 0.95,
|
| 106 |
+
"top_k": 50,
|
| 107 |
+
"do_sample": True,
|
| 108 |
+
"pad_token_id": eos_id,
|
| 109 |
+
"eos_token_id": eos_id,
|
| 110 |
+
"repetition_penalty": 1.2,
|
| 111 |
+
"no_repeat_ngram_size": 3,
|
| 112 |
+
}
|
| 113 |
+
if self.config:
|
| 114 |
+
kwargs["max_new_tokens"] = min(getattr(self.config, "max_length", 256), 128)
|
| 115 |
+
kwargs["temperature"] = getattr(self.config, "temperature", 0.2)
|
| 116 |
+
kwargs["top_p"] = getattr(self.config, "top_p", 0.95)
|
| 117 |
+
kwargs["top_k"] = getattr(self.config, "top_k", 50)
|
| 118 |
+
return kwargs
|
| 119 |
+
|
| 120 |
+
@staticmethod
|
| 121 |
+
def _clean_mongo_output(raw_output: str) -> str:
|
| 122 |
+
stripped = raw_output.strip()
|
| 123 |
+
if stripped.lower().startswith("db."):
|
| 124 |
+
restored = stripped
|
| 125 |
+
else:
|
| 126 |
+
restored = "db." + stripped
|
| 127 |
+
|
| 128 |
+
cleaned = restored.replace("```javascript", "").replace("```json", "").replace("```", "").strip()
|
| 129 |
+
stop_markers = ["--->", "\n\n", "\nUser:", "\nQuestion:", "```", "###", "Human:"]
|
| 130 |
+
for marker in stop_markers:
|
| 131 |
+
idx = cleaned.find(marker)
|
| 132 |
+
if idx != -1:
|
| 133 |
+
cleaned = cleaned[:idx]
|
| 134 |
+
lines = cleaned.split('\n')
|
| 135 |
+
for line in lines:
|
| 136 |
+
if line.strip().startswith("db."):
|
| 137 |
+
return line.strip().rstrip(';')
|
| 138 |
+
if "db." in cleaned:
|
| 139 |
+
idx = cleaned.find("db.")
|
| 140 |
+
return cleaned[idx:].split('\n')[0].strip().rstrip(';')
|
| 141 |
+
return cleaned.strip()
|
| 142 |
+
|
| 143 |
+
# Update generate_with_rag to clean the output before returning:
|
| 144 |
+
def generate_with_rag(self, query: str, top_k: int = 3) -> Tuple[str, List[Dict[str, Any]]]:
|
| 145 |
+
context_metadata = self.retrieve_context(query, top_k=top_k)
|
| 146 |
+
prompt = self.build_prompt(query, context_metadata)
|
| 147 |
+
|
| 148 |
+
if self.generator:
|
| 149 |
+
generated_text = self.generator.generate(prompt)
|
| 150 |
+
else:
|
| 151 |
+
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.model.device)
|
| 152 |
+
gen_kwargs = self._build_generation_kwargs()
|
| 153 |
+
with torch.no_grad():
|
| 154 |
+
outputs = self.model.generate(**inputs, **gen_kwargs)
|
| 155 |
+
generated_text = self.tokenizer.decode(
|
| 156 |
+
outputs[0][inputs["input_ids"].shape[1]:],
|
| 157 |
+
skip_special_tokens=True
|
| 158 |
+
).strip()
|
| 159 |
+
|
| 160 |
+
generated_text = self._clean_mongo_output(generated_text)
|
| 161 |
+
return generated_text, context_metadata
|
| 162 |
+
|
| 163 |
+
def generate_program(self, prompt: str, top_k: int = 3) -> str:
|
| 164 |
+
"""Wrapper for simple program generation using RAG."""
|
| 165 |
+
generated_text, _ = self.generate_with_rag(query=prompt, top_k=top_k)
|
| 166 |
+
return generated_text
|
src/vectorDB/__init__.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from vectorDB.embedder import HFEmbedder
|
| 2 |
+
from vectorDB.semantic_index import SemanticIndex
|
| 3 |
+
from vectorDB.ast_index import ASTIndex
|
| 4 |
+
from vectorDB.hybrid_retriever import HybridRetriever
|
| 5 |
+
from vectorDB.index_manager import IndexManager
|
| 6 |
+
|
| 7 |
+
__all__ = [
|
| 8 |
+
"HFEmbedder",
|
| 9 |
+
"SemanticIndex",
|
| 10 |
+
"ASTIndex",
|
| 11 |
+
"HybridRetriever",
|
| 12 |
+
"IndexManager"
|
| 13 |
+
]
|
src/vectorDB/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (496 Bytes). View file
|
|
|
src/vectorDB/__pycache__/ast_index.cpython-312.pyc
ADDED
|
Binary file (10.3 kB). View file
|
|
|
src/vectorDB/__pycache__/embedder.cpython-312.pyc
ADDED
|
Binary file (3.7 kB). View file
|
|
|
src/vectorDB/__pycache__/hybrid_retriever.cpython-312.pyc
ADDED
|
Binary file (4.58 kB). View file
|
|
|
src/vectorDB/__pycache__/index_manager.cpython-312.pyc
ADDED
|
Binary file (7.58 kB). View file
|
|
|
src/vectorDB/__pycache__/semantic_index.cpython-312.pyc
ADDED
|
Binary file (7.48 kB). View file
|
|
|