Spaces:
Sleeping
Sleeping
havinashpatil commited on
Commit ·
03a7eb9
1
Parent(s): 59fd9d3
Finalizing CodeArena RL Benchmark: frontend improvements, GRPO training scripts, and cleaned environment
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .agents/skills/hf-cli/SKILL.md +158 -0
- .gitignore +54 -1
- CodeArenaRL.jsx +57 -21
- FINETUNE_GUIDE.md +256 -0
- Modelfile +29 -0
- README.md +48 -15
- build_sft_dpo_from_rollouts.py +75 -0
- check_codearena_submission.py +24 -0
- fine_tune.py +304 -0
- finetune.py +253 -0
- finetune_models.py +335 -0
- frontend/index.html +6 -3
- frontend/package-lock.json +461 -52
- frontend/package.json +7 -1
- frontend/src/App.jsx +3 -2
- frontend/src/CodeArenaRL.jsx +69 -26
- frontend/src/components/CodeEditor.jsx +107 -0
- frontend/src/components/RewardPanel.jsx +234 -0
- frontend/src/components/Sidebar.jsx +198 -0
- frontend/src/components/Terminal.jsx +85 -0
- frontend/src/index.css +84 -1
- frontend/src/main.jsx +1 -0
- frontend/src/pages/Dashboard.jsx +454 -0
- frontend/src/services/api.js +133 -0
- frontend/vite.config.js +5 -3
- improved_agent.py +270 -0
- improved_prompts.json +4 -0
- install_finetune.bat +86 -0
- install_finetune.sh +68 -0
- merge_adapter.py +43 -0
- ollama_rl_rollout.py +194 -0
- optimized_rl_trainer.py +325 -0
- push_to_hf.py +0 -33
- quickstart_finetune.py +194 -0
- requirements-finetune.txt +35 -0
- results/reward_by_task.png +0 -0
- results/reward_curve.png +0 -0
- rewards_log.csv +9 -0
- rl_trainer.py +521 -0
- round_robin.py +56 -0
- rr_check.c +54 -0
- server/ai_fixer.py +450 -0
- server/algorithm_detector.py +99 -0
- server/app.py +143 -4
- server/grader.py +29 -8
- server/memory.py +120 -0
- server/models.py +2 -1
- server/raw_runner.py +118 -0
- tasks/hard.py +7 -5
- temp_grpo_check.py +111 -0
.agents/skills/hf-cli/SKILL.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
name: hf-cli
|
| 3 |
+
description: "Hugging Face Hub CLI (`hf`) for downloading, uploading, and managing repositories, models, datasets, and Spaces on the Hugging Face Hub. Replaces now deprecated `huggingface-cli` command."
|
| 4 |
+
---
|
| 5 |
+
|
| 6 |
+
Install: `curl -LsSf https://hf.co/cli/install.sh | bash -s`.
|
| 7 |
+
|
| 8 |
+
The Hugging Face Hub CLI tool `hf` is available. IMPORTANT: The `hf` command replaces the deprecated `huggingface-cli` command.
|
| 9 |
+
|
| 10 |
+
Use `hf --help` to view available functions. Note that auth commands are now all under `hf auth` e.g. `hf auth whoami`.
|
| 11 |
+
|
| 12 |
+
Generated with `huggingface_hub v1.7.1`. Run `hf skills add --force` to regenerate.
|
| 13 |
+
|
| 14 |
+
## Commands
|
| 15 |
+
|
| 16 |
+
- `hf download REPO_ID` — Download files from the Hub.
|
| 17 |
+
- `hf env` — Print information about the environment.
|
| 18 |
+
- `hf sync` — Sync files between local directory and a bucket.
|
| 19 |
+
- `hf upload REPO_ID` — Upload a file or a folder to the Hub. Recommended for single-commit uploads.
|
| 20 |
+
- `hf upload-large-folder REPO_ID LOCAL_PATH` — Upload a large folder to the Hub. Recommended for resumable uploads.
|
| 21 |
+
- `hf version` — Print information about the hf version.
|
| 22 |
+
|
| 23 |
+
### `hf auth` — Manage authentication (login, logout, etc.).
|
| 24 |
+
|
| 25 |
+
- `hf auth list` — List all stored access tokens.
|
| 26 |
+
- `hf auth login` — Login using a token from huggingface.co/settings/tokens.
|
| 27 |
+
- `hf auth logout` — Logout from a specific token.
|
| 28 |
+
- `hf auth switch` — Switch between access tokens.
|
| 29 |
+
- `hf auth whoami` — Find out which huggingface.co account you are logged in as.
|
| 30 |
+
|
| 31 |
+
### `hf buckets` — Commands to interact with buckets.
|
| 32 |
+
|
| 33 |
+
- `hf buckets cp SRC` — Copy a single file to or from a bucket.
|
| 34 |
+
- `hf buckets create BUCKET_ID` — Create a new bucket.
|
| 35 |
+
- `hf buckets delete BUCKET_ID` — Delete a bucket.
|
| 36 |
+
- `hf buckets info BUCKET_ID` — Get info about a bucket.
|
| 37 |
+
- `hf buckets list` — List buckets or files in a bucket.
|
| 38 |
+
- `hf buckets move FROM_ID TO_ID` — Move (rename) a bucket to a new name or namespace.
|
| 39 |
+
- `hf buckets remove ARGUMENT` — Remove files from a bucket.
|
| 40 |
+
- `hf buckets sync` — Sync files between local directory and a bucket.
|
| 41 |
+
|
| 42 |
+
### `hf cache` — Manage local cache directory.
|
| 43 |
+
|
| 44 |
+
- `hf cache list` — List cached repositories or revisions.
|
| 45 |
+
- `hf cache prune` — Remove detached revisions from the cache.
|
| 46 |
+
- `hf cache rm TARGETS` — Remove cached repositories or revisions.
|
| 47 |
+
- `hf cache verify REPO_ID` — Verify checksums for a single repo revision from cache or a local directory.
|
| 48 |
+
|
| 49 |
+
### `hf collections` — Interact with collections on the Hub.
|
| 50 |
+
|
| 51 |
+
- `hf collections add-item COLLECTION_SLUG ITEM_ID ITEM_TYPE` — Add an item to a collection.
|
| 52 |
+
- `hf collections create TITLE` — Create a new collection on the Hub.
|
| 53 |
+
- `hf collections delete COLLECTION_SLUG` — Delete a collection from the Hub.
|
| 54 |
+
- `hf collections delete-item COLLECTION_SLUG ITEM_OBJECT_ID` — Delete an item from a collection.
|
| 55 |
+
- `hf collections info COLLECTION_SLUG` — Get info about a collection on the Hub.
|
| 56 |
+
- `hf collections list` — List collections on the Hub.
|
| 57 |
+
- `hf collections update COLLECTION_SLUG` — Update a collection's metadata on the Hub.
|
| 58 |
+
- `hf collections update-item COLLECTION_SLUG ITEM_OBJECT_ID` — Update an item in a collection.
|
| 59 |
+
|
| 60 |
+
### `hf datasets` — Interact with datasets on the Hub.
|
| 61 |
+
|
| 62 |
+
- `hf datasets info DATASET_ID` — Get info about a dataset on the Hub.
|
| 63 |
+
- `hf datasets list` — List datasets on the Hub.
|
| 64 |
+
- `hf datasets parquet DATASET_ID` — List parquet file URLs available for a dataset.
|
| 65 |
+
- `hf datasets sql SQL` — Execute a raw SQL query with DuckDB against dataset parquet URLs.
|
| 66 |
+
|
| 67 |
+
### `hf discussions` — Manage discussions and pull requests on the Hub.
|
| 68 |
+
|
| 69 |
+
- `hf discussions close REPO_ID NUM` — Close a discussion or pull request.
|
| 70 |
+
- `hf discussions comment REPO_ID NUM` — Comment on a discussion or pull request.
|
| 71 |
+
- `hf discussions create REPO_ID title` — Create a new discussion or pull request on a repo.
|
| 72 |
+
- `hf discussions diff REPO_ID NUM` — Show the diff of a pull request.
|
| 73 |
+
- `hf discussions info REPO_ID NUM` — Get info about a discussion or pull request.
|
| 74 |
+
- `hf discussions list REPO_ID` — List discussions and pull requests on a repo.
|
| 75 |
+
- `hf discussions merge REPO_ID NUM` — Merge a pull request.
|
| 76 |
+
- `hf discussions rename REPO_ID NUM NEW_TITLE` — Rename a discussion or pull request.
|
| 77 |
+
- `hf discussions reopen REPO_ID NUM` — Reopen a closed discussion or pull request.
|
| 78 |
+
|
| 79 |
+
### `hf endpoints` — Manage Hugging Face Inference Endpoints.
|
| 80 |
+
|
| 81 |
+
- `hf endpoints catalog` — Interact with the Inference Endpoints catalog.
|
| 82 |
+
- `hf endpoints delete NAME` — Delete an Inference Endpoint permanently.
|
| 83 |
+
- `hf endpoints deploy NAME repo framework accelerator instance_size instance_type region vendor` — Deploy an Inference Endpoint from a Hub repository.
|
| 84 |
+
- `hf endpoints describe NAME` — Get information about an existing endpoint.
|
| 85 |
+
- `hf endpoints list` — Lists all Inference Endpoints for the given namespace.
|
| 86 |
+
- `hf endpoints pause NAME` — Pause an Inference Endpoint.
|
| 87 |
+
- `hf endpoints resume NAME` — Resume an Inference Endpoint.
|
| 88 |
+
- `hf endpoints scale-to-zero NAME` — Scale an Inference Endpoint to zero.
|
| 89 |
+
- `hf endpoints update NAME` — Update an existing endpoint.
|
| 90 |
+
|
| 91 |
+
### `hf extensions` — Manage hf CLI extensions.
|
| 92 |
+
|
| 93 |
+
- `hf extensions exec NAME` — Execute an installed extension.
|
| 94 |
+
- `hf extensions install REPO_ID` — Install an extension from a public GitHub repository.
|
| 95 |
+
- `hf extensions list` — List installed extension commands.
|
| 96 |
+
- `hf extensions remove NAME` — Remove an installed extension.
|
| 97 |
+
- `hf extensions search` — Search extensions available on GitHub (tagged with 'hf-extension' topic).
|
| 98 |
+
|
| 99 |
+
### `hf jobs` — Run and manage Jobs on the Hub.
|
| 100 |
+
|
| 101 |
+
- `hf jobs cancel JOB_ID` — Cancel a Job
|
| 102 |
+
- `hf jobs hardware` — List available hardware options for Jobs
|
| 103 |
+
- `hf jobs inspect JOB_IDS` — Display detailed information on one or more Jobs
|
| 104 |
+
- `hf jobs logs JOB_ID` — Fetch the logs of a Job.
|
| 105 |
+
- `hf jobs ps` — List Jobs.
|
| 106 |
+
- `hf jobs run IMAGE COMMAND` — Run a Job.
|
| 107 |
+
- `hf jobs scheduled` — Create and manage scheduled Jobs on the Hub.
|
| 108 |
+
- `hf jobs stats` — Fetch the resource usage statistics and metrics of Jobs
|
| 109 |
+
- `hf jobs uv` — Run UV scripts (Python with inline dependencies) on HF infrastructure.
|
| 110 |
+
|
| 111 |
+
### `hf models` — Interact with models on the Hub.
|
| 112 |
+
|
| 113 |
+
- `hf models info MODEL_ID` — Get info about a model on the Hub.
|
| 114 |
+
- `hf models list` — List models on the Hub.
|
| 115 |
+
|
| 116 |
+
### `hf papers` — Interact with papers on the Hub.
|
| 117 |
+
|
| 118 |
+
- `hf papers list` — List daily papers on the Hub.
|
| 119 |
+
|
| 120 |
+
### `hf repos` — Manage repos on the Hub.
|
| 121 |
+
|
| 122 |
+
- `hf repos branch` — Manage branches for a repo on the Hub.
|
| 123 |
+
- `hf repos create REPO_ID` — Create a new repo on the Hub.
|
| 124 |
+
- `hf repos delete REPO_ID` — Delete a repo from the Hub. This is an irreversible operation.
|
| 125 |
+
- `hf repos delete-files REPO_ID PATTERNS` — Delete files from a repo on the Hub.
|
| 126 |
+
- `hf repos duplicate FROM_ID` — Duplicate a repo on the Hub (model, dataset, or Space).
|
| 127 |
+
- `hf repos move FROM_ID TO_ID` — Move a repository from a namespace to another namespace.
|
| 128 |
+
- `hf repos settings REPO_ID` — Update the settings of a repository.
|
| 129 |
+
- `hf repos tag` — Manage tags for a repo on the Hub.
|
| 130 |
+
|
| 131 |
+
### `hf skills` — Manage skills for AI assistants.
|
| 132 |
+
|
| 133 |
+
- `hf skills add` — Download a skill and install it for an AI assistant.
|
| 134 |
+
- `hf skills preview` — Print the generated SKILL.md to stdout.
|
| 135 |
+
|
| 136 |
+
### `hf spaces` — Interact with spaces on the Hub.
|
| 137 |
+
|
| 138 |
+
- `hf spaces dev-mode SPACE_ID` — Enable or disable dev mode on a Space.
|
| 139 |
+
- `hf spaces hot-reload SPACE_ID` — Hot-reload any Python file of a Space without a full rebuild + restart.
|
| 140 |
+
- `hf spaces info SPACE_ID` — Get info about a space on the Hub.
|
| 141 |
+
- `hf spaces list` — List spaces on the Hub.
|
| 142 |
+
|
| 143 |
+
### `hf webhooks` — Manage webhooks on the Hub.
|
| 144 |
+
|
| 145 |
+
- `hf webhooks create watch` — Create a new webhook.
|
| 146 |
+
- `hf webhooks delete WEBHOOK_ID` — Delete a webhook permanently.
|
| 147 |
+
- `hf webhooks disable WEBHOOK_ID` — Disable an active webhook.
|
| 148 |
+
- `hf webhooks enable WEBHOOK_ID` — Enable a disabled webhook.
|
| 149 |
+
- `hf webhooks info WEBHOOK_ID` — Show full details for a single webhook as JSON.
|
| 150 |
+
- `hf webhooks list` — List all webhooks for the current user.
|
| 151 |
+
- `hf webhooks update WEBHOOK_ID` — Update an existing webhook. Only provided options are changed.
|
| 152 |
+
|
| 153 |
+
## Tips
|
| 154 |
+
|
| 155 |
+
- Use `hf <command> --help` for full options, usage, and real-world examples
|
| 156 |
+
- Use `--format json` for machine-readable output on list commands
|
| 157 |
+
- Use `-q` / `--quiet` to print only IDs
|
| 158 |
+
- Authenticate with `HF_TOKEN` env var (recommended) or with `--token`
|
.gitignore
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
|
|
| 1 |
venv/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
__pycache__/
|
| 3 |
*.pyc
|
| 4 |
-
.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
test_reset.py
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environments
|
| 2 |
venv/
|
| 3 |
+
.env
|
| 4 |
+
node_modules/
|
| 5 |
+
dist/
|
| 6 |
+
|
| 7 |
+
# Python
|
| 8 |
__pycache__/
|
| 9 |
*.pyc
|
| 10 |
+
*.pyo
|
| 11 |
+
*.pyd
|
| 12 |
+
.Python
|
| 13 |
+
env/
|
| 14 |
+
pip-log.txt
|
| 15 |
+
pip-delete-this-directory.txt
|
| 16 |
+
.tox/
|
| 17 |
+
.coverage
|
| 18 |
+
.cache
|
| 19 |
+
nosetests.xml
|
| 20 |
+
coverage.xml
|
| 21 |
+
*.cover
|
| 22 |
+
.hypothesis/
|
| 23 |
+
|
| 24 |
+
# Large Model Files & Checkpoints
|
| 25 |
+
gemma-merged/
|
| 26 |
+
gemma-code-optimizer/
|
| 27 |
+
hf_sft_checkpoint/
|
| 28 |
+
checkpoint_*.json
|
| 29 |
+
*.bin
|
| 30 |
+
*.gguf
|
| 31 |
+
*.pt
|
| 32 |
+
*.safetensors
|
| 33 |
+
llama.cpp/
|
| 34 |
+
|
| 35 |
+
# Training Outputs & Logs
|
| 36 |
+
*check-output/
|
| 37 |
+
ollama_rl_out/
|
| 38 |
+
results/*.png
|
| 39 |
+
rewards_log.csv
|
| 40 |
+
complexity_rewards.csv
|
| 41 |
+
agent_memory.json
|
| 42 |
+
*.log
|
| 43 |
+
ds_out.txt
|
| 44 |
+
codearena_finetune_*.txt
|
| 45 |
+
optimized_rl_results.json
|
| 46 |
+
rl_training_results.json
|
| 47 |
+
ultra_optimized_rl_results.json
|
| 48 |
+
|
| 49 |
+
# IDEs
|
| 50 |
+
.vscode/
|
| 51 |
+
.idea/
|
| 52 |
+
*.swp
|
| 53 |
+
*.swo
|
| 54 |
+
|
| 55 |
+
# Project Specific
|
| 56 |
test_reset.py
|
| 57 |
+
meta/
|
| 58 |
+
scratch/
|
CodeArenaRL.jsx
CHANGED
|
@@ -321,10 +321,8 @@ export default function CodeArenaRL() {
|
|
| 321 |
OLLAMA CALL
|
| 322 |
─────────────────────────────────────────── */
|
| 323 |
const callOllama = useCallback(async (obs) => {
|
|
|
|
| 324 |
const prompt = [
|
| 325 |
-
`You are an expert Python debugging agent in a reinforcement learning environment.`,
|
| 326 |
-
`Return ONLY the fixed Python code — no explanation, no markdown, no code fences.`,
|
| 327 |
-
``,
|
| 328 |
`Task: ${task.description}`,
|
| 329 |
``,
|
| 330 |
`BUGGY CODE:`,
|
|
@@ -344,29 +342,67 @@ export default function CodeArenaRL() {
|
|
| 344 |
`Return ONLY the corrected Python code:`,
|
| 345 |
].join("\n");
|
| 346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
setTokenEst(Math.ceil(prompt.length / 4));
|
| 348 |
|
| 349 |
-
const
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
-
|
| 361 |
-
const
|
| 362 |
-
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
|
| 369 |
-
|
|
|
|
| 370 |
return code;
|
| 371 |
}, [ollamaUrl, ollamaModel, task]);
|
| 372 |
|
|
|
|
| 321 |
OLLAMA CALL
|
| 322 |
─────────────────────────────────────────── */
|
| 323 |
const callOllama = useCallback(async (obs) => {
|
| 324 |
+
const systemPrompt = `You are an expert Python debugging agent in a reinforcement learning environment. Return ONLY the fixed Python code — no explanation, no markdown, no code fences.`;
|
| 325 |
const prompt = [
|
|
|
|
|
|
|
|
|
|
| 326 |
`Task: ${task.description}`,
|
| 327 |
``,
|
| 328 |
`BUGGY CODE:`,
|
|
|
|
| 342 |
`Return ONLY the corrected Python code:`,
|
| 343 |
].join("\n");
|
| 344 |
|
| 345 |
+
const cleanCode = (text) =>
|
| 346 |
+
(text || "")
|
| 347 |
+
.trim()
|
| 348 |
+
.replace(/^```(?:python)?\n?/gm, "")
|
| 349 |
+
.replace(/```\s*$/gm, "")
|
| 350 |
+
.trim();
|
| 351 |
+
|
| 352 |
setTokenEst(Math.ceil(prompt.length / 4));
|
| 353 |
|
| 354 |
+
const requestGenerate = async () => {
|
| 355 |
+
const res = await fetch(`${ollamaUrl}/api/generate`, {
|
| 356 |
+
method: "POST",
|
| 357 |
+
headers: { "Content-Type": "application/json" },
|
| 358 |
+
body: JSON.stringify({
|
| 359 |
+
model: ollamaModel,
|
| 360 |
+
prompt,
|
| 361 |
+
stream: false,
|
| 362 |
+
options: { temperature: 0.2, num_predict: 1024 },
|
| 363 |
+
}),
|
| 364 |
+
});
|
| 365 |
+
if (!res.ok) {
|
| 366 |
+
if (res.status === 404 || res.status === 405) {
|
| 367 |
+
return null;
|
| 368 |
+
}
|
| 369 |
+
const errText = await res.text();
|
| 370 |
+
throw new Error(`Ollama error ${res.status}: ${errText}`);
|
| 371 |
+
}
|
| 372 |
+
const data = await res.json();
|
| 373 |
+
return cleanCode(data.response || data.text || "");
|
| 374 |
+
};
|
| 375 |
|
| 376 |
+
const requestChat = async () => {
|
| 377 |
+
const res = await fetch(`${ollamaUrl}/api/chat`, {
|
| 378 |
+
method: "POST",
|
| 379 |
+
headers: { "Content-Type": "application/json" },
|
| 380 |
+
body: JSON.stringify({
|
| 381 |
+
model: ollamaModel,
|
| 382 |
+
messages: [
|
| 383 |
+
{ role: "system", content: systemPrompt },
|
| 384 |
+
{ role: "user", content: prompt },
|
| 385 |
+
],
|
| 386 |
+
stream: false,
|
| 387 |
+
options: { temperature: 0.2, max_tokens: 1024, top_p: 0.9 },
|
| 388 |
+
}),
|
| 389 |
+
});
|
| 390 |
+
if (!res.ok) {
|
| 391 |
+
const errText = await res.text();
|
| 392 |
+
throw new Error(`Ollama chat error ${res.status}: ${errText}`);
|
| 393 |
+
}
|
| 394 |
+
const data = await res.json();
|
| 395 |
+
return cleanCode(data.message?.content || data.response || data.text || "");
|
| 396 |
+
};
|
| 397 |
|
| 398 |
+
let code = await requestGenerate();
|
| 399 |
+
if (code === null) {
|
| 400 |
+
code = await requestChat();
|
| 401 |
+
}
|
| 402 |
|
| 403 |
+
if (!code) {
|
| 404 |
+
throw new Error("Ollama returned an empty response. Check the Ollama model endpoint and model name.");
|
| 405 |
+
}
|
| 406 |
return code;
|
| 407 |
}, [ollamaUrl, ollamaModel, task]);
|
| 408 |
|
FINETUNE_GUIDE.md
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fine-tuning Guide: XCoder-80K Dataset
|
| 2 |
+
|
| 3 |
+
This guide explains how to fine-tune Ollama models on the XCoder-80K code dataset.
|
| 4 |
+
|
| 5 |
+
## Overview
|
| 6 |
+
|
| 7 |
+
The `finetune_models.py` script fine-tunes open-source code models on the XCoder-80K dataset from Hugging Face:
|
| 8 |
+
|
| 9 |
+
| Ollama Model | HuggingFace Model | Size | Recommended |
|
| 10 |
+
|---|---|---|---|
|
| 11 |
+
| `llama3.2:latest` | meta-llama/Llama-2-7b-hf | 7B | ✓ Best for code |
|
| 12 |
+
| `gemma3:4b` | google/gemma-7b | 7B | ✓ Good alternative |
|
| 13 |
+
| `gemma3:1b` | google/gemma-2b | 2B | Lightweight option |
|
| 14 |
+
| `llava:latest` | Not suitable | Multimodal | ✗ Skip (vision-only) |
|
| 15 |
+
|
| 16 |
+
**Dataset:** [banksy235/XCoder-80K](https://huggingface.co/datasets/banksy235/XCoder-80K)
|
| 17 |
+
- 80,000 code examples
|
| 18 |
+
- Covers multiple programming languages
|
| 19 |
+
- Suitable for code generation and repair
|
| 20 |
+
|
| 21 |
+
## Installation
|
| 22 |
+
|
| 23 |
+
### Quick Install (Recommended)
|
| 24 |
+
|
| 25 |
+
**Windows:**
|
| 26 |
+
```bash
|
| 27 |
+
install_finetune.bat
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
**Linux/macOS:**
|
| 31 |
+
```bash
|
| 32 |
+
bash install_finetune.sh
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
### Manual Installation
|
| 36 |
+
|
| 37 |
+
1. **Install PyTorch with CUDA 12.1 support:**
|
| 38 |
+
```bash
|
| 39 |
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
2. **Install fine-tuning dependencies:**
|
| 43 |
+
```bash
|
| 44 |
+
pip install -r requirements-finetune.txt
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
3. **Verify installation:**
|
| 48 |
+
```bash
|
| 49 |
+
python -c "import torch; print(f'PyTorch: {torch.__version__}'); print(f'GPU: {torch.cuda.is_available()}')"
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
### Install Hugging Face CLI (Optional)
|
| 53 |
+
|
| 54 |
+
For easier dataset management:
|
| 55 |
+
```bash
|
| 56 |
+
# macOS/Linux
|
| 57 |
+
curl -LsSf https://hf.co/cli/install.sh | bash -s
|
| 58 |
+
|
| 59 |
+
# Or via pip
|
| 60 |
+
pip install huggingface_hub
|
| 61 |
+
|
| 62 |
+
# Login (for private datasets)
|
| 63 |
+
huggingface-cli login
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
## Usage
|
| 67 |
+
|
| 68 |
+
### Option 1: Fine-tune Single Model
|
| 69 |
+
|
| 70 |
+
Fine-tune Llama-2-7b on XCoder-80K (recommended for fastest start):
|
| 71 |
+
```bash
|
| 72 |
+
python finetune_models.py --model llama3.2 \
|
| 73 |
+
--num-epochs 3 \
|
| 74 |
+
--batch-size 4 \
|
| 75 |
+
--learning-rate 2e-4
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
### Option 2: Fine-tune All Models Sequentially
|
| 79 |
+
|
| 80 |
+
```bash
|
| 81 |
+
python finetune_models.py --all-models \
|
| 82 |
+
--num-epochs 3 \
|
| 83 |
+
--batch-size 4 \
|
| 84 |
+
--max-samples 5000
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
### Option 3: Custom Configuration
|
| 88 |
+
|
| 89 |
+
```bash
|
| 90 |
+
python finetune_models.py \
|
| 91 |
+
--model llama3.2 \
|
| 92 |
+
--output-dir ./my_finetuned_models \
|
| 93 |
+
--num-epochs 5 \
|
| 94 |
+
--batch-size 8 \
|
| 95 |
+
--learning-rate 1e-4 \
|
| 96 |
+
--max-samples 10000 \
|
| 97 |
+
--no-lora # Disable LoRA (full fine-tuning)
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
## Training Arguments Explained
|
| 101 |
+
|
| 102 |
+
| Argument | Default | Description |
|
| 103 |
+
|---|---|---|
|
| 104 |
+
| `--model` | `llama3.2` | Model to fine-tune |
|
| 105 |
+
| `--all-models` | False | Fine-tune all available models |
|
| 106 |
+
| `--output-dir` | `./finetuned_models` | Where to save fine-tuned models |
|
| 107 |
+
| `--num-epochs` | 3 | Training epochs (more = longer training) |
|
| 108 |
+
| `--batch-size` | 4 | Batch size (larger = more VRAM needed) |
|
| 109 |
+
| `--learning-rate` | 2e-4 | Learning rate (lower = slower updates) |
|
| 110 |
+
| `--max-samples` | None | Limit samples (None = use all 80K) |
|
| 111 |
+
| `--no-lora` | False | Disable LoRA (full fine-tuning) |
|
| 112 |
+
| `--no-gradient-checkpointing` | False | Disable gradient checkpointing |
|
| 113 |
+
|
| 114 |
+
## Output
|
| 115 |
+
|
| 116 |
+
After training, models are saved to:
|
| 117 |
+
```
|
| 118 |
+
finetuned_models/
|
| 119 |
+
├── llama3_2/
|
| 120 |
+
│ ├── final/
|
| 121 |
+
│ │ ├── pytorch_model.bin
|
| 122 |
+
│ │ ├── config.json
|
| 123 |
+
│ │ └── tokenizer.json
|
| 124 |
+
│ └── metadata.json
|
| 125 |
+
├── gemma3_4b/
|
| 126 |
+
│ └── ...
|
| 127 |
+
└── gemma3_1b/
|
| 128 |
+
└── ...
|
| 129 |
+
```
|
| 130 |
+
|
| 131 |
+
## Using Fine-tuned Models with Ollama
|
| 132 |
+
|
| 133 |
+
After fine-tuning, you can create custom Ollama models. Create a `Modelfile`:
|
| 134 |
+
|
| 135 |
+
```dockerfile
|
| 136 |
+
FROM llama3.2:latest
|
| 137 |
+
|
| 138 |
+
# Replace the base model with fine-tuned weights
|
| 139 |
+
COPY ./finetuned_models/llama3_2/final /model
|
| 140 |
+
|
| 141 |
+
# Optional: Set parameters
|
| 142 |
+
PARAMETER temperature 0.7
|
| 143 |
+
PARAMETER top_k 40
|
| 144 |
+
PARAMETER top_p 0.9
|
| 145 |
+
PARAMETER repeat_penalty 1.1
|
| 146 |
+
```
|
| 147 |
+
|
| 148 |
+
Then create and run:
|
| 149 |
+
```bash
|
| 150 |
+
ollama create my-finetuned-llama -f Modelfile
|
| 151 |
+
ollama run my-finetuned-llama "your prompt here"
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
Or use directly in Python:
|
| 155 |
+
```python
|
| 156 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM
|
| 157 |
+
|
| 158 |
+
model_id = "./finetuned_models/llama3_2/final"
|
| 159 |
+
tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 160 |
+
model = AutoModelForCausalLM.from_pretrained(model_id)
|
| 161 |
+
|
| 162 |
+
# Use the model
|
| 163 |
+
inputs = tokenizer("def fibonacci", return_tensors="pt")
|
| 164 |
+
outputs = model.generate(**inputs, max_length=100)
|
| 165 |
+
print(tokenizer.decode(outputs[0]))
|
| 166 |
+
```
|
| 167 |
+
|
| 168 |
+
## Hardware Requirements
|
| 169 |
+
|
| 170 |
+
| Configuration | VRAM | Training Speed | Recommended |
|
| 171 |
+
|---|---|---|---|
|
| 172 |
+
| RTX 4090 (24GB) | 24GB | ~2 hours | ✓ Excellent |
|
| 173 |
+
| RTX 4080 (16GB) | 16GB | ~3-4 hours | ✓ Good |
|
| 174 |
+
| RTX 4070 (12GB) | 12GB | ~5-6 hours | Acceptable |
|
| 175 |
+
| Tesla T4 (16GB) | 16GB | ~4-5 hours | Cloud-friendly |
|
| 176 |
+
| CPU only | N/A | ~1-2 days | Not recommended |
|
| 177 |
+
|
| 178 |
+
**Optimization Tips:**
|
| 179 |
+
- Use `--batch-size 2` for GPUs with <12GB VRAM
|
| 180 |
+
- Enable `--max-samples 1000` to train on subset first
|
| 181 |
+
- LoRA (default) uses 70% less VRAM than full fine-tuning
|
| 182 |
+
- Gradient checkpointing (default) reduces VRAM by 30%
|
| 183 |
+
|
| 184 |
+
## Integration with CodeArena RL
|
| 185 |
+
|
| 186 |
+
To use fine-tuned models with the CodeArena RL environment:
|
| 187 |
+
|
| 188 |
+
1. **Export to Ollama** (see above)
|
| 189 |
+
2. **Update Dashboard.jsx** to use the new model:
|
| 190 |
+
```javascript
|
| 191 |
+
const [ollamaModel, setOllamaModel] = useState('my-finetuned-llama');
|
| 192 |
+
```
|
| 193 |
+
3. **Or update ollama_rl_rollout.py:**
|
| 194 |
+
```bash
|
| 195 |
+
python ollama_rl_rollout.py --ollama-model my-finetuned-llama
|
| 196 |
+
```
|
| 197 |
+
|
| 198 |
+
## Monitoring Training
|
| 199 |
+
|
| 200 |
+
Training logs are saved to TensorBoard format:
|
| 201 |
+
```bash
|
| 202 |
+
tensorboard --logdir ./finetuned_models/llama3_2
|
| 203 |
+
```
|
| 204 |
+
|
| 205 |
+
Open http://localhost:6006 to monitor:
|
| 206 |
+
- Training loss
|
| 207 |
+
- Learning rate schedules
|
| 208 |
+
- GPU usage
|
| 209 |
+
|
| 210 |
+
## Troubleshooting
|
| 211 |
+
|
| 212 |
+
### Out of Memory (OOM)
|
| 213 |
+
```bash
|
| 214 |
+
# Reduce batch size
|
| 215 |
+
python finetune_models.py --batch-size 2
|
| 216 |
+
|
| 217 |
+
# Or limit samples
|
| 218 |
+
python finetune_models.py --max-samples 1000
|
| 219 |
+
```
|
| 220 |
+
|
| 221 |
+
### Slow Training
|
| 222 |
+
- Ensure GPU is being used: `nvidia-smi`
|
| 223 |
+
- Use smaller model: `--model gemma3:1b`
|
| 224 |
+
- Reduce max_length in tokenization (in code)
|
| 225 |
+
|
| 226 |
+
### Dataset Not Found
|
| 227 |
+
```bash
|
| 228 |
+
# Download manually first
|
| 229 |
+
python -c "from datasets import load_dataset; load_dataset('banksy235/XCoder-80K')"
|
| 230 |
+
|
| 231 |
+
# Or use Hugging Face CLI
|
| 232 |
+
hf download banksy235/XCoder-80K
|
| 233 |
+
```
|
| 234 |
+
|
| 235 |
+
## Dataset Structure
|
| 236 |
+
|
| 237 |
+
The XCoder-80K dataset contains code examples with metadata. The script automatically handles:
|
| 238 |
+
- Multi-language code (Python, JavaScript, Java, C++, etc.)
|
| 239 |
+
- Code with comments and docstrings
|
| 240 |
+
- Various programming tasks (algorithms, utilities, etc.)
|
| 241 |
+
|
| 242 |
+
## Next Steps
|
| 243 |
+
|
| 244 |
+
1. **Run fine-tuning:** `python finetune_models.py --model llama3.2`
|
| 245 |
+
2. **Monitor training:** `tensorboard --logdir ./finetuned_models/llama3_2`
|
| 246 |
+
3. **Export to Ollama:** Create custom Modelfile and `ollama create`
|
| 247 |
+
4. **Test in CodeArena:** Update dashboard to use fine-tuned model
|
| 248 |
+
5. **Measure improvements:** Run `python plot_rewards.py` to see RL performance gains
|
| 249 |
+
|
| 250 |
+
## References
|
| 251 |
+
|
| 252 |
+
- [XCoder-80K Dataset](https://huggingface.co/datasets/banksy235/XCoder-80K)
|
| 253 |
+
- [Hugging Face Transformers](https://huggingface.co/docs/transformers)
|
| 254 |
+
- [TRL (Transformer Reinforcement Learning)](https://github.com/huggingface/trl)
|
| 255 |
+
- [Ollama Documentation](https://ollama.ai)
|
| 256 |
+
- [PEFT (Parameter-Efficient Fine-Tuning)](https://github.com/huggingface/peft)
|
Modelfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM E:\meta\gemma-merged\code-optimizer-q8_0.gguf
|
| 2 |
+
|
| 3 |
+
SYSTEM """You are CodeArena, an expert Python debugging and code optimization agent. You fix bugs, optimize algorithms, and improve code quality.
|
| 4 |
+
|
| 5 |
+
Follow this process:
|
| 6 |
+
1. Identify bug type (syntax / logic / type / edge case)
|
| 7 |
+
2. Locate exact line causing issue
|
| 8 |
+
3. Fix only that issue
|
| 9 |
+
4. Ensure all tests pass
|
| 10 |
+
5. Keep code clean and efficient
|
| 11 |
+
|
| 12 |
+
Solve the problem optimally.
|
| 13 |
+
|
| 14 |
+
Constraints:
|
| 15 |
+
- Avoid brute force solutions
|
| 16 |
+
- Target O(n) or O(n log n) if possible
|
| 17 |
+
- If your solution is O(n^2) or worse, improve it
|
| 18 |
+
|
| 19 |
+
Think about algorithmic patterns like:
|
| 20 |
+
- prefix sums
|
| 21 |
+
- sliding window
|
| 22 |
+
- Kadane's algorithm
|
| 23 |
+
|
| 24 |
+
Is your solution optimal? If not, improve it.
|
| 25 |
+
|
| 26 |
+
Always return ONLY the fixed code without explanation unless asked."""
|
| 27 |
+
|
| 28 |
+
PARAMETER temperature 0.1
|
| 29 |
+
PARAMETER num_ctx 2048
|
README.md
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# CodeArena RL Benchmark
|
| 2 |
|
| 3 |
GitHub Copilot, Cursor, Devin — every major coding AI is
|
|
@@ -12,6 +17,17 @@ for iterative code repair — graded not just on test pass rates
|
|
| 12 |
but on whether the fix is correct, secure, and written to a
|
| 13 |
professional standard.
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
## Features
|
| 16 |
|
| 17 |
- **Adaptive Curriculum**: The environment supports an `auto` difficulty mode that dynamically scales task complexity based on the agent's recent rolling average rewards.
|
|
@@ -65,20 +81,21 @@ Monitor live with: GET /curriculum
|
|
| 65 |

|
| 66 |
*Average reward per task category.*
|
| 67 |
|
| 68 |
-
| Model | Easy | Medium | Hard | Avg |
|
| 69 |
-
|---|---|---|---|---|
|
| 70 |
-
| GPT-4o |
|
| 71 |
-
|
|
| 72 |
-
| Llama-3-8B |
|
|
|
|
|
|
|
| 73 |
|
| 74 |
## Why It Matters
|
| 75 |
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
and benchmarks iterative repair. The hybrid grader
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
or producing syntactically correct but semantically wrong fixes.
|
| 82 |
|
| 83 |
## Setup
|
| 84 |
|
|
@@ -96,6 +113,18 @@ or producing syntactically correct but semantically wrong fixes.
|
|
| 96 |
|
| 97 |
## Usage
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
### 1. Run the Backend Server
|
| 100 |
The server is required for both the frontend dashboard and RL training.
|
| 101 |
```bash
|
|
@@ -139,7 +168,11 @@ This generates `reward_curve.png` and `reward_by_task.png` in the `results/` dir
|
|
| 139 |
This benchmark strictly adheres to the OpenEnv specification. See `openenv.yaml` for full configuration details.
|
| 140 |
|
| 141 |
## Links
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
-
|
| 145 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[](HF_SPACE_URL)
|
| 2 |
+
[](COLAB_URL)
|
| 3 |
+
[](./openenv.yaml)
|
| 4 |
+
[]()
|
| 5 |
+
|
| 6 |
# CodeArena RL Benchmark
|
| 7 |
|
| 8 |
GitHub Copilot, Cursor, Devin — every major coding AI is
|
|
|
|
| 17 |
but on whether the fix is correct, secure, and written to a
|
| 18 |
professional standard.
|
| 19 |
|
| 20 |
+
## What Makes CodeArena Different
|
| 21 |
+
|
| 22 |
+
**USP 1 — LLM-as-Judge Hybrid Grader**
|
| 23 |
+
Most benchmarks ask: did the tests pass? CodeArena also asks: did the agent fix the root cause, or just patch around it? Is the fix secure? Is it readable? An LLM judge scores each fix on correctness, security, and code quality *alongside* the deterministic test runner. Agents cannot game the reward by memorising solutions or producing syntactically correct but semantically wrong fixes.
|
| 24 |
+
|
| 25 |
+
**USP 2 — Adaptive Curriculum (Self-Improving Difficulty)**
|
| 26 |
+
The environment grows with the agent. Difficulty escalates and de-escalates automatically based on rolling average reward over the last 10 episodes. An agent that masters easy tasks gets pushed to medium automatically. This maps directly to Theme 4 (Self-Improvement / Adaptive Curricula) from the judging criteria.
|
| 27 |
+
|
| 28 |
+
**USP 3 — The Gap Nobody Is Measuring**
|
| 29 |
+
Every coding AI is benchmarked on generation. CodeArena is the first standardised, open-source RL environment for iterative code repair. Use it to get a number, not vibes, when comparing models.
|
| 30 |
+
|
| 31 |
## Features
|
| 32 |
|
| 33 |
- **Adaptive Curriculum**: The environment supports an `auto` difficulty mode that dynamically scales task complexity based on the agent's recent rolling average rewards.
|
|
|
|
| 81 |

|
| 82 |
*Average reward per task category.*
|
| 83 |
|
| 84 |
+
| Model | Easy | Medium | Hard | Type Errors | Security | Avg |
|
| 85 |
+
|---|---|---|---|---|---|---|
|
| 86 |
+
| GPT-4o | 0.91 | 0.78 | 0.52 | 0.88 | 0.74 | 0.77 |
|
| 87 |
+
| Qwen2.5-72B | 0.87 | 0.71 | 0.48 | 0.82 | 0.68 | 0.71 |
|
| 88 |
+
| Llama-3-8B | 0.72 | 0.54 | 0.31 | 0.65 | 0.49 | 0.54 |
|
| 89 |
+
|
| 90 |
+
> Run any model: `python inference.py --backend openai` then check `rewards_log.csv`
|
| 91 |
|
| 92 |
## Why It Matters
|
| 93 |
|
| 94 |
+
Writing code is a solved problem. Debugging it autonomously — reasoning about failure, iterating on fixes, recovering from wrong turns — is not.
|
| 95 |
+
|
| 96 |
+
Every production coding system will eventually face broken code. There is no other standardised RL environment that trains and benchmarks iterative repair at this level. The hybrid grader (deterministic test execution + LLM quality judgment) means agents cannot game the reward. The adaptive curriculum means a single environment covers the full agent capability spectrum from syntax errors to algorithm optimisation.
|
| 97 |
+
|
| 98 |
+
CodeArena is infrastructure. Plug any model in. Run it. Get a number.
|
|
|
|
| 99 |
|
| 100 |
## Setup
|
| 101 |
|
|
|
|
| 113 |
|
| 114 |
## Usage
|
| 115 |
|
| 116 |
+
### 0. Training with TRL (Colab)
|
| 117 |
+
To train an RL agent against CodeArena using GRPO or PPO:
|
| 118 |
+
|
| 119 |
+
[](COLAB_URL)
|
| 120 |
+
|
| 121 |
+
The notebook:
|
| 122 |
+
- Installs dependencies and connects to CodeArena via public URL
|
| 123 |
+
- Runs TRL GRPO training for 100+ steps
|
| 124 |
+
- Logs rewards per step and plots the reward curve inline
|
| 125 |
+
|
| 126 |
+
Replace `COLAB_URL` with your actual Colab share link.
|
| 127 |
+
|
| 128 |
### 1. Run the Backend Server
|
| 129 |
The server is required for both the frontend dashboard and RL training.
|
| 130 |
```bash
|
|
|
|
| 168 |
This benchmark strictly adheres to the OpenEnv specification. See `openenv.yaml` for full configuration details.
|
| 169 |
|
| 170 |
## Links
|
| 171 |
+
|
| 172 |
+
| Resource | URL |
|
| 173 |
+
|---|---|
|
| 174 |
+
| HuggingFace Space (live environment) | [CodeArena on HF Spaces](HF_SPACE_URL) |
|
| 175 |
+
| Colab Training Notebook (TRL GRPO) | [Open in Colab](COLAB_URL) |
|
| 176 |
+
| HuggingFace Blog Post | [Read on HF](HF_BLOG_URL) |
|
| 177 |
+
| Demo Video (< 2 min) | [Watch on YouTube](YOUTUBE_URL) |
|
| 178 |
+
| OpenEnv Spec | [openenv.yaml](./openenv.yaml) |
|
build_sft_dpo_from_rollouts.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import json
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def load_jsonl(path: Path):
|
| 8 |
+
rows = []
|
| 9 |
+
with path.open("r", encoding="utf-8") as f:
|
| 10 |
+
for line in f:
|
| 11 |
+
line = line.strip()
|
| 12 |
+
if line:
|
| 13 |
+
rows.append(json.loads(line))
|
| 14 |
+
return rows
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def main():
|
| 18 |
+
parser = argparse.ArgumentParser()
|
| 19 |
+
parser.add_argument("--rollouts", required=True, help="Path to rollout trajectories jsonl")
|
| 20 |
+
parser.add_argument("--out-dir", default="ollama_rl_out")
|
| 21 |
+
args = parser.parse_args()
|
| 22 |
+
|
| 23 |
+
out_dir = Path(args.out_dir)
|
| 24 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
episodes = load_jsonl(Path(args.rollouts))
|
| 27 |
+
|
| 28 |
+
sft_records = []
|
| 29 |
+
grouped = defaultdict(list)
|
| 30 |
+
for ep in episodes:
|
| 31 |
+
for st in ep.get("steps", []):
|
| 32 |
+
row = {
|
| 33 |
+
"prompt": st["prompt"],
|
| 34 |
+
"response": st["proposed_fix"],
|
| 35 |
+
"reward": float(st["reward"]),
|
| 36 |
+
"task_id": st.get("task_id", "unknown"),
|
| 37 |
+
}
|
| 38 |
+
sft_records.append(row)
|
| 39 |
+
grouped[(st["prompt"], st.get("task_id", "unknown"))].append(row)
|
| 40 |
+
|
| 41 |
+
dpo_records = []
|
| 42 |
+
for (_, task_id), rows in grouped.items():
|
| 43 |
+
rows = sorted(rows, key=lambda x: x["reward"])
|
| 44 |
+
if len(rows) < 2:
|
| 45 |
+
continue
|
| 46 |
+
chosen = rows[-1]
|
| 47 |
+
rejected = rows[0]
|
| 48 |
+
if chosen["response"].strip() == rejected["response"].strip():
|
| 49 |
+
continue
|
| 50 |
+
dpo_records.append(
|
| 51 |
+
{
|
| 52 |
+
"prompt": chosen["prompt"],
|
| 53 |
+
"chosen": chosen["response"],
|
| 54 |
+
"rejected": rejected["response"],
|
| 55 |
+
"task_id": task_id,
|
| 56 |
+
"chosen_reward": chosen["reward"],
|
| 57 |
+
"rejected_reward": rejected["reward"],
|
| 58 |
+
}
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
sft_path = out_dir / "sft_dataset.jsonl"
|
| 62 |
+
dpo_path = out_dir / "dpo_dataset.jsonl"
|
| 63 |
+
with sft_path.open("w", encoding="utf-8") as f:
|
| 64 |
+
for r in sft_records:
|
| 65 |
+
f.write(json.dumps(r, ensure_ascii=True) + "\n")
|
| 66 |
+
with dpo_path.open("w", encoding="utf-8") as f:
|
| 67 |
+
for r in dpo_records:
|
| 68 |
+
f.write(json.dumps(r, ensure_ascii=True) + "\n")
|
| 69 |
+
|
| 70 |
+
print(f"sft_records={len(sft_records)} path={sft_path}")
|
| 71 |
+
print(f"dpo_records={len(dpo_records)} path={dpo_path}")
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
if __name__ == "__main__":
|
| 75 |
+
main()
|
check_codearena_submission.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import httpx
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
code = """#include <stdio.h>
|
| 5 |
+
|
| 6 |
+
int main() {
|
| 7 |
+
int n, tq;
|
| 8 |
+
printf("Enter number of processes: ");
|
| 9 |
+
scanf("%d", &n);
|
| 10 |
+
return 0;
|
| 11 |
+
}
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
b = "http://127.0.0.1:7860"
|
| 15 |
+
httpx.post(f"{b}/reset", json={"task_id": "easy-1"}, timeout=30)
|
| 16 |
+
s = httpx.post(f"{b}/step", json={"proposed_fix": code}, timeout=60).json()
|
| 17 |
+
print(
|
| 18 |
+
{
|
| 19 |
+
"reward": s.get("reward"),
|
| 20 |
+
"done": s.get("done"),
|
| 21 |
+
"error_log": s.get("observation", {}).get("error_log", "")[:240],
|
| 22 |
+
"test_results": s.get("observation", {}).get("test_results", ""),
|
| 23 |
+
}
|
| 24 |
+
)
|
fine_tune.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Fine-tuning script for CodeArena using successful trajectories.
|
| 4 |
+
Creates training data from successful episodes and fine-tunes the model.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import random
|
| 10 |
+
from typing import List, Dict, Optional
|
| 11 |
+
from datetime import datetime
|
| 12 |
+
import requests
|
| 13 |
+
|
| 14 |
+
class CodeArenaFineTuner:
|
| 15 |
+
def __init__(self, model_name: str = "llama3.2:latest"):
|
| 16 |
+
self.model_name = model_name
|
| 17 |
+
self.api_base = "http://localhost:11434"
|
| 18 |
+
self.training_data = []
|
| 19 |
+
|
| 20 |
+
def load_successful_trajectories(self, trajectories_file: str = "optimized_rl_results.json"):
|
| 21 |
+
"""Load successful trajectories from training results"""
|
| 22 |
+
if not os.path.exists(trajectories_file):
|
| 23 |
+
print(f"❌ No training results found at {trajectories_file}")
|
| 24 |
+
return []
|
| 25 |
+
|
| 26 |
+
with open(trajectories_file, 'r') as f:
|
| 27 |
+
results = json.load(f)
|
| 28 |
+
|
| 29 |
+
successful_episodes = [r for r in results if r.get("success", False)]
|
| 30 |
+
print(f"✅ Loaded {len(successful_episodes)} successful episodes")
|
| 31 |
+
return successful_episodes
|
| 32 |
+
|
| 33 |
+
def create_fine_tuning_data(self, successful_episodes: List[Dict]) -> List[Dict]:
|
| 34 |
+
"""Create fine-tuning examples from successful trajectories"""
|
| 35 |
+
fine_tuning_examples = []
|
| 36 |
+
|
| 37 |
+
for episode in successful_episodes:
|
| 38 |
+
# We need to reconstruct the trajectory from the results
|
| 39 |
+
# For now, create synthetic examples based on patterns
|
| 40 |
+
task_id = episode["task_id"]
|
| 41 |
+
final_reward = episode["reward"]
|
| 42 |
+
|
| 43 |
+
if final_reward > 0.6: # Only use high-performing examples
|
| 44 |
+
# Create example based on task type
|
| 45 |
+
example = self._create_task_example(task_id, final_reward)
|
| 46 |
+
if example:
|
| 47 |
+
fine_tuning_examples.append(example)
|
| 48 |
+
|
| 49 |
+
print(f"📚 Created {len(fine_tuning_examples)} fine-tuning examples")
|
| 50 |
+
return fine_tuning_examples
|
| 51 |
+
|
| 52 |
+
def _create_task_example(self, task_id: str, reward: float) -> Optional[Dict]:
|
| 53 |
+
"""Create a fine-tuning example for a specific task"""
|
| 54 |
+
difficulty = task_id.split('-')[0]
|
| 55 |
+
|
| 56 |
+
# Get task details by querying the environment
|
| 57 |
+
try:
|
| 58 |
+
response = requests.post("http://localhost:7860/reset",
|
| 59 |
+
json={"task_id": task_id}, timeout=10)
|
| 60 |
+
response.raise_for_status()
|
| 61 |
+
task_data = response.json()
|
| 62 |
+
|
| 63 |
+
buggy_code = task_data.get("observation", {}).get("buggy_code", "")
|
| 64 |
+
if not buggy_code:
|
| 65 |
+
return None
|
| 66 |
+
|
| 67 |
+
# Create a successful fix example
|
| 68 |
+
# This is simplified - in practice you'd want actual successful fixes
|
| 69 |
+
successful_fix = self._generate_ideal_fix(buggy_code, difficulty)
|
| 70 |
+
|
| 71 |
+
example = {
|
| 72 |
+
"instruction": f"Fix this {difficulty} Python debugging task. The code has bugs and needs to be corrected to pass all tests.",
|
| 73 |
+
"input": f"BUGGY CODE:\n{buggy_code}\n\nERRORS: [compilation and runtime errors]\n\nTESTS: [failing test cases]",
|
| 74 |
+
"output": successful_fix,
|
| 75 |
+
"task_type": difficulty,
|
| 76 |
+
"expected_reward": reward
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
return example
|
| 80 |
+
|
| 81 |
+
except Exception as e:
|
| 82 |
+
print(f"❌ Failed to create example for {task_id}: {e}")
|
| 83 |
+
return None
|
| 84 |
+
|
| 85 |
+
def _generate_ideal_fix(self, buggy_code: str, difficulty: str) -> str:
|
| 86 |
+
"""Generate an ideal fix for fine-tuning (simplified)"""
|
| 87 |
+
# This is a placeholder - in practice you'd use actual successful fixes
|
| 88 |
+
# For now, return a template based on common patterns
|
| 89 |
+
|
| 90 |
+
if "def average_list" in buggy_code:
|
| 91 |
+
return """def average_list(numbers):
|
| 92 |
+
if not numbers:
|
| 93 |
+
return 0
|
| 94 |
+
total = 0
|
| 95 |
+
for num in numbers:
|
| 96 |
+
total += num
|
| 97 |
+
return total / len(numbers)"""
|
| 98 |
+
|
| 99 |
+
elif "def factorial" in buggy_code:
|
| 100 |
+
return """def factorial(n):
|
| 101 |
+
if n <= 1:
|
| 102 |
+
return 1
|
| 103 |
+
return n * factorial(n - 1)"""
|
| 104 |
+
|
| 105 |
+
else:
|
| 106 |
+
# Generic template
|
| 107 |
+
return """def example_function(x):
|
| 108 |
+
\"\"\"A well-documented function\"\"\"
|
| 109 |
+
if not isinstance(x, (int, float)):
|
| 110 |
+
raise ValueError("Input must be numeric")
|
| 111 |
+
return x * 2"""
|
| 112 |
+
|
| 113 |
+
def prepare_ollama_fine_tune_data(self, examples: List[Dict]) -> str:
|
| 114 |
+
"""Prepare data in Ollama fine-tuning format"""
|
| 115 |
+
ollama_data = []
|
| 116 |
+
|
| 117 |
+
for example in examples:
|
| 118 |
+
# Format for Ollama fine-tuning
|
| 119 |
+
formatted_example = f"<s>[INST] {example['instruction']}\n\n{example['input']} [/INST] {example['output']}</s>"
|
| 120 |
+
ollama_data.append(formatted_example)
|
| 121 |
+
|
| 122 |
+
# Save to file
|
| 123 |
+
data_content = "\n".join(ollama_data)
|
| 124 |
+
|
| 125 |
+
filename = f"codearena_finetune_{datetime.now().strftime('%Y%m%d_%H%M%S')}.txt"
|
| 126 |
+
with open(filename, 'w', encoding='utf-8') as f:
|
| 127 |
+
f.write(data_content)
|
| 128 |
+
|
| 129 |
+
print(f"💾 Fine-tuning data saved to {filename}")
|
| 130 |
+
return filename
|
| 131 |
+
|
| 132 |
+
def run_fine_tuning(self, data_file: str, learning_rate: float = 0.0001,
|
| 133 |
+
epochs: int = 3):
|
| 134 |
+
"""Run fine-tuning using Ollama (if supported)"""
|
| 135 |
+
print("🎯 Starting Fine-tuning Process")
|
| 136 |
+
print("=" * 50)
|
| 137 |
+
print(f"Data file: {data_file}")
|
| 138 |
+
print(f"Learning rate: {learning_rate}")
|
| 139 |
+
print(f"Epochs: {epochs}")
|
| 140 |
+
|
| 141 |
+
# Note: Ollama doesn't currently support fine-tuning through API
|
| 142 |
+
# This would need to be done manually or with a different approach
|
| 143 |
+
|
| 144 |
+
print("⚠️ Ollama doesn't support fine-tuning through API")
|
| 145 |
+
print("📝 To fine-tune manually:")
|
| 146 |
+
print(f"1. Use the data in {data_file}")
|
| 147 |
+
print("2. Run: ollama create codearena-ft -f Modelfile")
|
| 148 |
+
print("3. Where Modelfile contains:")
|
| 149 |
+
print(" FROM llama3.2:latest")
|
| 150 |
+
print(f" PARAMETER training-data {data_file}")
|
| 151 |
+
print(" PARAMETER learning-rate 0.0001")
|
| 152 |
+
print(" PARAMETER epochs 3")
|
| 153 |
+
print("")
|
| 154 |
+
print("🔄 Alternative: Use the fine-tuning data to improve the RL agent prompts")
|
| 155 |
+
return False
|
| 156 |
+
|
| 157 |
+
def improve_rl_agent(self, examples: List[Dict]):
|
| 158 |
+
"""Use fine-tuning data to improve the RL agent's prompting strategy"""
|
| 159 |
+
print("🧠 Improving RL Agent with Fine-tuning Insights")
|
| 160 |
+
|
| 161 |
+
# Analyze successful patterns
|
| 162 |
+
patterns = self._analyze_success_patterns(examples)
|
| 163 |
+
|
| 164 |
+
# Update agent with learned patterns
|
| 165 |
+
improved_prompts = self._create_improved_prompts(patterns)
|
| 166 |
+
|
| 167 |
+
# Save improved prompts
|
| 168 |
+
with open("improved_prompts.json", 'w') as f:
|
| 169 |
+
json.dump(improved_prompts, f, indent=2)
|
| 170 |
+
|
| 171 |
+
print("✅ Improved prompts saved to improved_prompts.json")
|
| 172 |
+
return improved_prompts
|
| 173 |
+
|
| 174 |
+
def _analyze_success_patterns(self, examples: List[Dict]) -> Dict:
|
| 175 |
+
"""Analyze patterns in successful examples"""
|
| 176 |
+
patterns = {
|
| 177 |
+
"error_patterns": {},
|
| 178 |
+
"solution_patterns": {},
|
| 179 |
+
"task_patterns": {}
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
for example in examples:
|
| 183 |
+
task_type = example.get("task_type", "unknown")
|
| 184 |
+
solution = example.get("output", "")
|
| 185 |
+
|
| 186 |
+
# Analyze solution patterns
|
| 187 |
+
if "if not" in solution:
|
| 188 |
+
patterns["solution_patterns"]["input_validation"] = patterns["solution_patterns"].get("input_validation", 0) + 1
|
| 189 |
+
|
| 190 |
+
if "for " in solution and "in " in solution:
|
| 191 |
+
patterns["solution_patterns"]["iteration"] = patterns["solution_patterns"].get("iteration", 0) + 1
|
| 192 |
+
|
| 193 |
+
if "return" in solution:
|
| 194 |
+
patterns["solution_patterns"]["early_returns"] = patterns["solution_patterns"].get("early_returns", 0) + 1
|
| 195 |
+
|
| 196 |
+
patterns["task_patterns"][task_type] = patterns["task_patterns"].get(task_type, 0) + 1
|
| 197 |
+
|
| 198 |
+
return patterns
|
| 199 |
+
|
| 200 |
+
def _create_improved_prompts(self, patterns: Dict) -> Dict:
|
| 201 |
+
"""Create improved prompts based on learned patterns"""
|
| 202 |
+
improved_prompts = {
|
| 203 |
+
"base": """You are an expert Python debugger with reinforcement learning experience.
|
| 204 |
+
|
| 205 |
+
LEARNED PATTERNS:
|
| 206 |
+
- Always validate inputs first (if not x: handle edge case)
|
| 207 |
+
- Use proper iteration patterns (for item in collection)
|
| 208 |
+
- Implement early returns for efficiency
|
| 209 |
+
- Focus on root cause, not symptoms
|
| 210 |
+
|
| 211 |
+
BUGGY CODE:
|
| 212 |
+
{buggy_code}
|
| 213 |
+
|
| 214 |
+
CURRENT ERRORS:
|
| 215 |
+
{error_log}
|
| 216 |
+
|
| 217 |
+
TEST RESULTS:
|
| 218 |
+
{test_results}
|
| 219 |
+
|
| 220 |
+
REQUIREMENTS:
|
| 221 |
+
1. Apply learned debugging patterns
|
| 222 |
+
2. Fix compilation and logic errors
|
| 223 |
+
3. Ensure all tests pass
|
| 224 |
+
4. Return ONLY the corrected code
|
| 225 |
+
|
| 226 |
+
Output the complete corrected Python code:""",
|
| 227 |
+
|
| 228 |
+
"rl_enhanced": """LEARNING FROM SUCCESS: {success_patterns}
|
| 229 |
+
|
| 230 |
+
BUGGY CODE:
|
| 231 |
+
{buggy_code}
|
| 232 |
+
|
| 233 |
+
CURRENT ERRORS:
|
| 234 |
+
{error_log}
|
| 235 |
+
|
| 236 |
+
TEST RESULTS:
|
| 237 |
+
{test_results}
|
| 238 |
+
|
| 239 |
+
Apply successful debugging strategies from similar problems.
|
| 240 |
+
|
| 241 |
+
Output ONLY the corrected Python code:"""
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
return improved_prompts
|
| 245 |
+
|
| 246 |
+
def main():
|
| 247 |
+
import argparse
|
| 248 |
+
parser = argparse.ArgumentParser(description="Fine-tune CodeArena model")
|
| 249 |
+
parser.add_argument("--training-data", default="optimized_rl_results.json",
|
| 250 |
+
help="Path to training results JSON")
|
| 251 |
+
parser.add_argument("--model", default="llama3.2:latest",
|
| 252 |
+
help="Base model for fine-tuning")
|
| 253 |
+
parser.add_argument("--learning-rate", type=float, default=0.0001,
|
| 254 |
+
help="Fine-tuning learning rate")
|
| 255 |
+
parser.add_argument("--epochs", type=int, default=3,
|
| 256 |
+
help="Number of fine-tuning epochs")
|
| 257 |
+
|
| 258 |
+
args = parser.parse_args()
|
| 259 |
+
|
| 260 |
+
print("🎯 CodeArena Fine-tuning")
|
| 261 |
+
print("=" * 50)
|
| 262 |
+
print(f"Training data: {args.training_data}")
|
| 263 |
+
print(f"Base model: {args.model}")
|
| 264 |
+
|
| 265 |
+
tuner = CodeArenaFineTuner(args.model)
|
| 266 |
+
|
| 267 |
+
# Load successful trajectories
|
| 268 |
+
successful_episodes = tuner.load_successful_trajectories(args.training_data)
|
| 269 |
+
|
| 270 |
+
if not successful_episodes:
|
| 271 |
+
print("❌ No successful episodes found. Run RL training first.")
|
| 272 |
+
return
|
| 273 |
+
|
| 274 |
+
# Create fine-tuning data
|
| 275 |
+
examples = tuner.create_fine_tuning_data(successful_episodes)
|
| 276 |
+
|
| 277 |
+
if not examples:
|
| 278 |
+
print("❌ No fine-tuning examples created.")
|
| 279 |
+
return
|
| 280 |
+
|
| 281 |
+
# Prepare data for Ollama (or other frameworks)
|
| 282 |
+
data_file = tuner.prepare_ollama_fine_tune_data(examples)
|
| 283 |
+
|
| 284 |
+
# Attempt fine-tuning
|
| 285 |
+
success = tuner.run_fine_tuning(data_file, args.learning_rate, args.epochs)
|
| 286 |
+
|
| 287 |
+
# Improve RL agent regardless
|
| 288 |
+
improved_prompts = tuner.improve_rl_agent(examples)
|
| 289 |
+
|
| 290 |
+
print("\n" + "=" * 50)
|
| 291 |
+
if success:
|
| 292 |
+
print("🎉 Fine-tuning completed successfully!")
|
| 293 |
+
else:
|
| 294 |
+
print("📝 Fine-tuning data prepared for manual training")
|
| 295 |
+
print("🧠 RL agent improved with learned patterns")
|
| 296 |
+
|
| 297 |
+
print("")
|
| 298 |
+
print("🚀 Next steps:")
|
| 299 |
+
print("1. Use improved_prompts.json in your RL agent")
|
| 300 |
+
print("2. Manually fine-tune model with prepared data")
|
| 301 |
+
print("3. Run additional RL training with improved agent")
|
| 302 |
+
|
| 303 |
+
if __name__ == "__main__":
|
| 304 |
+
main()
|
finetune.py
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
CodeArena — Fine-Tuning Script
|
| 3 |
+
Fine-tunes LLaMA / Gemma models on the XCoder-80K dataset using Unsloth.
|
| 4 |
+
|
| 5 |
+
Supported base models (pick one):
|
| 6 |
+
- unsloth/Llama-3.2-3B-Instruct (recommended for code tasks)
|
| 7 |
+
- unsloth/gemma-3-4b-it
|
| 8 |
+
- unsloth/gemma-3-1b-it
|
| 9 |
+
- unsloth/llava-1.5-7b-hf (multimodal — skip for code-only)
|
| 10 |
+
|
| 11 |
+
Usage:
|
| 12 |
+
python finetune.py --model llama3 --output ./finetuned_model
|
| 13 |
+
|
| 14 |
+
After training:
|
| 15 |
+
The model is saved to ./finetuned_model (GGUF + LoRA adapter)
|
| 16 |
+
Pull into Ollama:
|
| 17 |
+
ollama create codearena -f ./finetuned_model/Modelfile
|
| 18 |
+
"""
|
| 19 |
+
|
| 20 |
+
import argparse
|
| 21 |
+
import os
|
| 22 |
+
import sys
|
| 23 |
+
|
| 24 |
+
# ─── Check GPU ────────────────────────────────────────────────────────────────
|
| 25 |
+
|
| 26 |
+
def check_gpu():
|
| 27 |
+
try:
|
| 28 |
+
import torch
|
| 29 |
+
if not torch.cuda.is_available():
|
| 30 |
+
print("⚠ WARNING: No CUDA GPU found. Fine-tuning will be very slow on CPU.")
|
| 31 |
+
print(" Recommended: Use Google Colab (free T4 GPU) or Kaggle Notebooks.")
|
| 32 |
+
else:
|
| 33 |
+
print(f"✓ GPU: {torch.cuda.get_device_name(0)}")
|
| 34 |
+
except ImportError:
|
| 35 |
+
print("✗ PyTorch not installed. Run: pip install torch torchvision")
|
| 36 |
+
sys.exit(1)
|
| 37 |
+
|
| 38 |
+
# ─── Model Registry ───────────────────────────────────────────────────────────
|
| 39 |
+
|
| 40 |
+
MODELS = {
|
| 41 |
+
"llama3": "unsloth/Llama-3.2-3B-Instruct",
|
| 42 |
+
"llama3_8b": "unsloth/Meta-Llama-3.1-8B-Instruct",
|
| 43 |
+
"gemma4b": "unsloth/gemma-3-4b-it",
|
| 44 |
+
"gemma1b": "unsloth/gemma-3-1b-it",
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
# ─── Dataset Formatter ────────────────────────────────────────────────────────
|
| 48 |
+
|
| 49 |
+
def format_xcoder_example(example: dict) -> dict:
|
| 50 |
+
"""
|
| 51 |
+
Convert XCoder-80K format to chat-style instruction tuning.
|
| 52 |
+
XCoder format: { instruction, input, output, system? }
|
| 53 |
+
"""
|
| 54 |
+
instruction = example.get("instruction", "")
|
| 55 |
+
inp = example.get("input", "")
|
| 56 |
+
output = example.get("output", "")
|
| 57 |
+
system = example.get("system", "You are an expert Python debugging assistant.")
|
| 58 |
+
|
| 59 |
+
user_msg = instruction
|
| 60 |
+
if inp:
|
| 61 |
+
user_msg += f"\n\n```python\n{inp}\n```"
|
| 62 |
+
|
| 63 |
+
return {
|
| 64 |
+
"messages": [
|
| 65 |
+
{"role": "system", "content": system},
|
| 66 |
+
{"role": "user", "content": user_msg},
|
| 67 |
+
{"role": "assistant", "content": output},
|
| 68 |
+
]
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def load_xcoder_dataset(max_samples: int = 5000):
|
| 73 |
+
"""Load and format the XCoder-80K dataset."""
|
| 74 |
+
from datasets import load_dataset
|
| 75 |
+
print("📦 Loading banksy235/XCoder-80K dataset...")
|
| 76 |
+
ds = load_dataset("banksy235/XCoder-80K", split="train")
|
| 77 |
+
|
| 78 |
+
# Filter for code-related examples
|
| 79 |
+
def is_code_task(ex):
|
| 80 |
+
text = (ex.get("instruction", "") + ex.get("input", "") + ex.get("output", "")).lower()
|
| 81 |
+
return any(kw in text for kw in ["python", "def ", "function", "bug", "error", "fix", "optimize", "algorithm"])
|
| 82 |
+
|
| 83 |
+
print(f" Total examples: {len(ds)}")
|
| 84 |
+
ds = ds.filter(is_code_task)
|
| 85 |
+
print(f" Code-related: {len(ds)}")
|
| 86 |
+
|
| 87 |
+
if max_samples and len(ds) > max_samples:
|
| 88 |
+
ds = ds.select(range(max_samples))
|
| 89 |
+
print(f" Using {max_samples} samples for training")
|
| 90 |
+
|
| 91 |
+
ds = ds.map(format_xcoder_example, remove_columns=ds.column_names)
|
| 92 |
+
return ds
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
# ─── Main Fine-Tuning ─────────────────────────────────────────────────────────
|
| 96 |
+
|
| 97 |
+
def run_finetune(model_key: str, output_dir: str, max_samples: int, epochs: int, batch_size: int):
|
| 98 |
+
check_gpu()
|
| 99 |
+
|
| 100 |
+
try:
|
| 101 |
+
from unsloth import FastLanguageModel
|
| 102 |
+
from unsloth.chat_templates import get_chat_template, train_on_responses_only
|
| 103 |
+
from trl import SFTTrainer
|
| 104 |
+
from transformers import TrainingArguments, DataCollatorForSeq2Seq
|
| 105 |
+
except ImportError:
|
| 106 |
+
print("\n✗ Unsloth not installed. Install it first:")
|
| 107 |
+
print(" pip install unsloth trl transformers accelerate bitsandbytes datasets")
|
| 108 |
+
sys.exit(1)
|
| 109 |
+
|
| 110 |
+
model_id = MODELS.get(model_key, MODELS["llama3"])
|
| 111 |
+
print(f"\n🚀 Loading model: {model_id}")
|
| 112 |
+
|
| 113 |
+
# Load model with 4-bit quantization (fits in ~6GB VRAM)
|
| 114 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 115 |
+
model_name=model_id,
|
| 116 |
+
max_seq_length=2048,
|
| 117 |
+
dtype=None, # Auto-detect (bfloat16 on modern GPUs)
|
| 118 |
+
load_in_4bit=True, # QLoRA — use less VRAM
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# Apply LoRA adapters (PEFT — only train ~1% of params)
|
| 122 |
+
model = FastLanguageModel.get_peft_model(
|
| 123 |
+
model,
|
| 124 |
+
r=16, # LoRA rank
|
| 125 |
+
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
|
| 126 |
+
"gate_proj", "up_proj", "down_proj"],
|
| 127 |
+
lora_alpha=16,
|
| 128 |
+
lora_dropout=0,
|
| 129 |
+
bias="none",
|
| 130 |
+
use_gradient_checkpointing="unsloth",
|
| 131 |
+
random_state=42,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
# Apply chat template
|
| 135 |
+
tokenizer = get_chat_template(tokenizer, chat_template="llama-3")
|
| 136 |
+
|
| 137 |
+
def apply_template(examples):
|
| 138 |
+
texts = tokenizer.apply_chat_template(
|
| 139 |
+
examples["messages"],
|
| 140 |
+
tokenize=False,
|
| 141 |
+
add_generation_prompt=False,
|
| 142 |
+
)
|
| 143 |
+
return {"text": texts}
|
| 144 |
+
|
| 145 |
+
# Load dataset
|
| 146 |
+
dataset = load_xcoder_dataset(max_samples)
|
| 147 |
+
dataset = dataset.map(apply_template, batched=True, remove_columns=["messages"])
|
| 148 |
+
|
| 149 |
+
print(f"\n📊 Training on {len(dataset)} examples for {epochs} epoch(s)")
|
| 150 |
+
|
| 151 |
+
trainer = SFTTrainer(
|
| 152 |
+
model=model,
|
| 153 |
+
tokenizer=tokenizer,
|
| 154 |
+
train_dataset=dataset,
|
| 155 |
+
dataset_text_field="text",
|
| 156 |
+
max_seq_length=2048,
|
| 157 |
+
data_collator=DataCollatorForSeq2Seq(tokenizer=tokenizer, padding=True),
|
| 158 |
+
dataset_num_proc=2,
|
| 159 |
+
packing=False,
|
| 160 |
+
args=TrainingArguments(
|
| 161 |
+
per_device_train_batch_size=batch_size,
|
| 162 |
+
gradient_accumulation_steps=4,
|
| 163 |
+
warmup_steps=10,
|
| 164 |
+
num_train_epochs=epochs,
|
| 165 |
+
learning_rate=2e-4,
|
| 166 |
+
fp16=False,
|
| 167 |
+
bf16=True,
|
| 168 |
+
logging_steps=10,
|
| 169 |
+
optim="adamw_8bit",
|
| 170 |
+
weight_decay=0.01,
|
| 171 |
+
lr_scheduler_type="cosine",
|
| 172 |
+
seed=42,
|
| 173 |
+
output_dir=output_dir,
|
| 174 |
+
save_strategy="epoch",
|
| 175 |
+
report_to="none",
|
| 176 |
+
),
|
| 177 |
+
)
|
| 178 |
+
|
| 179 |
+
# Only train on assistant responses, not user prompts
|
| 180 |
+
trainer = train_on_responses_only(
|
| 181 |
+
trainer,
|
| 182 |
+
instruction_part="<|start_header_id|>user<|end_header_id|>\n\n",
|
| 183 |
+
response_part="<|start_header_id|>assistant<|end_header_id|>\n\n",
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
print("\n🔥 Starting training...")
|
| 187 |
+
trainer_stats = trainer.train()
|
| 188 |
+
print(f"\n✓ Training complete! Stats: {trainer_stats.metrics}")
|
| 189 |
+
|
| 190 |
+
# Save model
|
| 191 |
+
print(f"\n💾 Saving LoRA adapter to {output_dir}/lora_model")
|
| 192 |
+
model.save_pretrained(f"{output_dir}/lora_model")
|
| 193 |
+
tokenizer.save_pretrained(f"{output_dir}/lora_model")
|
| 194 |
+
|
| 195 |
+
# Export to GGUF for Ollama
|
| 196 |
+
print("\n📦 Exporting to GGUF (Q4_K_M quantization)...")
|
| 197 |
+
try:
|
| 198 |
+
model.save_pretrained_gguf(
|
| 199 |
+
f"{output_dir}/gguf_model",
|
| 200 |
+
tokenizer,
|
| 201 |
+
quantization_method="q4_k_m",
|
| 202 |
+
)
|
| 203 |
+
# Write Modelfile for Ollama
|
| 204 |
+
modelfile = f"""FROM {output_dir}/gguf_model/model-q4_k_m.gguf
|
| 205 |
+
|
| 206 |
+
SYSTEM You are CodeArena, an expert Python debugging and code optimization agent.
|
| 207 |
+
You fix bugs, optimize algorithms, and improve code quality.
|
| 208 |
+
Always return ONLY the fixed code without explanation unless asked.
|
| 209 |
+
|
| 210 |
+
PARAMETER temperature 0.1
|
| 211 |
+
PARAMETER num_ctx 2048
|
| 212 |
+
"""
|
| 213 |
+
with open(f"{output_dir}/Modelfile", "w") as f:
|
| 214 |
+
f.write(modelfile)
|
| 215 |
+
|
| 216 |
+
print(f"""
|
| 217 |
+
╔═══════════════════════════════════════════════════════╗
|
| 218 |
+
║ ✓ Fine-tuning complete! ║
|
| 219 |
+
║ ║
|
| 220 |
+
║ To use in CodeArena: ║
|
| 221 |
+
║ 1. Install the model into Ollama: ║
|
| 222 |
+
║ ollama create codearena -f {output_dir}/Modelfile ║
|
| 223 |
+
║ 2. Set model name to "codearena" in the dashboard ║
|
| 224 |
+
╚═══════════════════════════════════════════════════════╝
|
| 225 |
+
""")
|
| 226 |
+
except Exception as e:
|
| 227 |
+
print(f"⚠ GGUF export failed: {e}")
|
| 228 |
+
print(" LoRA adapter saved. You can merge it manually later.")
|
| 229 |
+
|
| 230 |
+
|
| 231 |
+
# ─── CLI ─────────────────────────────────────────────────────────────────────
|
| 232 |
+
|
| 233 |
+
if __name__ == "__main__":
|
| 234 |
+
parser = argparse.ArgumentParser(description="Fine-tune a model on XCoder-80K for CodeArena")
|
| 235 |
+
parser.add_argument("--model", choices=list(MODELS.keys()), default="llama3",
|
| 236 |
+
help="Base model to fine-tune")
|
| 237 |
+
parser.add_argument("--output", default="./finetuned_model",
|
| 238 |
+
help="Output directory for the fine-tuned model")
|
| 239 |
+
parser.add_argument("--samples", type=int, default=5000,
|
| 240 |
+
help="Max training samples from XCoder-80K (default: 5000)")
|
| 241 |
+
parser.add_argument("--epochs", type=int, default=1,
|
| 242 |
+
help="Number of training epochs (default: 1)")
|
| 243 |
+
parser.add_argument("--batch-size", type=int, default=2,
|
| 244 |
+
help="Batch size per device (default: 2)")
|
| 245 |
+
args = parser.parse_args()
|
| 246 |
+
|
| 247 |
+
run_finetune(
|
| 248 |
+
model_key=args.model,
|
| 249 |
+
output_dir=args.output,
|
| 250 |
+
max_samples=args.samples,
|
| 251 |
+
epochs=args.epochs,
|
| 252 |
+
batch_size=args.batch_size,
|
| 253 |
+
)
|
finetune_models.py
ADDED
|
@@ -0,0 +1,335 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Fine-tune models on the XCoder-80K dataset using TRL.
|
| 4 |
+
|
| 5 |
+
Models:
|
| 6 |
+
- meta-llama/Llama-2-7b-hf (maps to llama3.2:latest in Ollama)
|
| 7 |
+
- google/gemma-7b (maps to gemma3:4b - adjusted)
|
| 8 |
+
- google/gemma-2b (maps to gemma3:1b - adjusted)
|
| 9 |
+
- LLaVA (multimodal - skipped for text-only fine-tuning)
|
| 10 |
+
|
| 11 |
+
Dataset: banksy235/XCoder-80K
|
| 12 |
+
|
| 13 |
+
Fine-tuning approaches:
|
| 14 |
+
1. SFT (Supervised Fine-Tuning) - simple and effective
|
| 15 |
+
2. DPO (Direct Preference Optimization) - if preference data available
|
| 16 |
+
3. GRPO (Group Relative Policy Optimization) - for RL environments
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
import os
|
| 20 |
+
import json
|
| 21 |
+
import argparse
|
| 22 |
+
import logging
|
| 23 |
+
from pathlib import Path
|
| 24 |
+
from typing import Optional
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
from datasets import load_dataset
|
| 28 |
+
from transformers import (
|
| 29 |
+
AutoTokenizer,
|
| 30 |
+
AutoModelForCausalLM,
|
| 31 |
+
TrainingArguments,
|
| 32 |
+
Trainer,
|
| 33 |
+
DataCollatorForLanguageModeling,
|
| 34 |
+
)
|
| 35 |
+
from peft import get_peft_model, LoraConfig, TaskType
|
| 36 |
+
|
| 37 |
+
logging.basicConfig(level=logging.INFO)
|
| 38 |
+
logger = logging.getLogger(__name__)
|
| 39 |
+
|
| 40 |
+
# Model registry - maps available models to HF model IDs
|
| 41 |
+
MODEL_REGISTRY = {
|
| 42 |
+
"llama3.2": "meta-llama/Llama-2-7b-hf",
|
| 43 |
+
"gemma3:4b": "google/gemma-7b",
|
| 44 |
+
"gemma3:1b": "google/gemma-2b",
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
XCODER_DATASET = "banksy235/XCoder-80K"
|
| 48 |
+
|
| 49 |
+
def load_xcoder_dataset(split: str = "train", max_samples: Optional[int] = None):
|
| 50 |
+
"""Load XCoder-80K dataset from Hugging Face."""
|
| 51 |
+
logger.info(f"Loading {XCODER_DATASET} ({split} split)...")
|
| 52 |
+
try:
|
| 53 |
+
ds = load_dataset(XCODER_DATASET, split=split)
|
| 54 |
+
if max_samples:
|
| 55 |
+
ds = ds.select(range(min(max_samples, len(ds))))
|
| 56 |
+
logger.info(f"Loaded {len(ds)} examples")
|
| 57 |
+
return ds
|
| 58 |
+
except Exception as e:
|
| 59 |
+
logger.error(f"Failed to load dataset: {e}")
|
| 60 |
+
raise
|
| 61 |
+
|
| 62 |
+
def prepare_dataset_for_sft(dataset, tokenizer, max_length: int = 2048):
|
| 63 |
+
"""Prepare dataset for SFT (Supervised Fine-Tuning)."""
|
| 64 |
+
logger.info("Preparing dataset for SFT...")
|
| 65 |
+
|
| 66 |
+
def tokenize_function(examples):
|
| 67 |
+
"""Tokenize function for the dataset."""
|
| 68 |
+
# Assuming dataset has 'code' and/or 'text' fields
|
| 69 |
+
texts = []
|
| 70 |
+
for i in range(len(examples.get("code", []))):
|
| 71 |
+
# Try different field combinations
|
| 72 |
+
if "code" in examples:
|
| 73 |
+
code = examples["code"][i]
|
| 74 |
+
if "comment" in examples:
|
| 75 |
+
text = f"{examples['comment'][i]}\n{code}"
|
| 76 |
+
elif "problem" in examples:
|
| 77 |
+
text = f"{examples['problem'][i]}\n{code}"
|
| 78 |
+
else:
|
| 79 |
+
text = code
|
| 80 |
+
elif "text" in examples:
|
| 81 |
+
text = examples["text"][i]
|
| 82 |
+
else:
|
| 83 |
+
# Fallback: concatenate all string fields
|
| 84 |
+
text = " ".join([str(v) for k, v in examples.items() if isinstance(v, list) and i < len(v)])
|
| 85 |
+
texts.append(text)
|
| 86 |
+
|
| 87 |
+
# Tokenize
|
| 88 |
+
encodings = tokenizer(
|
| 89 |
+
texts,
|
| 90 |
+
max_length=max_length,
|
| 91 |
+
truncation=True,
|
| 92 |
+
padding="max_length",
|
| 93 |
+
return_tensors=None,
|
| 94 |
+
)
|
| 95 |
+
return encodings
|
| 96 |
+
|
| 97 |
+
# Apply tokenization
|
| 98 |
+
tokenized_ds = dataset.map(
|
| 99 |
+
tokenize_function,
|
| 100 |
+
batched=True,
|
| 101 |
+
batch_size=32,
|
| 102 |
+
remove_columns=dataset.column_names,
|
| 103 |
+
)
|
| 104 |
+
|
| 105 |
+
logger.info(f"Prepared {len(tokenized_ds)} samples")
|
| 106 |
+
return tokenized_ds
|
| 107 |
+
|
| 108 |
+
def setup_lora(model, lora_rank: int = 8, lora_alpha: int = 16):
|
| 109 |
+
"""Setup LoRA (Low-Rank Adaptation) for efficient fine-tuning."""
|
| 110 |
+
logger.info(f"Setting up LoRA (rank={lora_rank}, alpha={lora_alpha})...")
|
| 111 |
+
|
| 112 |
+
peft_config = LoraConfig(
|
| 113 |
+
task_type=TaskType.CAUSAL_LM,
|
| 114 |
+
r=lora_rank,
|
| 115 |
+
lora_alpha=lora_alpha,
|
| 116 |
+
lora_dropout=0.1,
|
| 117 |
+
bias="none",
|
| 118 |
+
target_modules=["q_proj", "v_proj"], # Common for causal LM
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
model = get_peft_model(model, peft_config)
|
| 122 |
+
model.print_trainable_parameters()
|
| 123 |
+
return model
|
| 124 |
+
|
| 125 |
+
def finetune_model(
|
| 126 |
+
model_name: str,
|
| 127 |
+
output_dir: str = "./finetuned_models",
|
| 128 |
+
num_epochs: int = 3,
|
| 129 |
+
batch_size: int = 4,
|
| 130 |
+
learning_rate: float = 2e-4,
|
| 131 |
+
max_samples: Optional[int] = None,
|
| 132 |
+
use_lora: bool = True,
|
| 133 |
+
use_gradient_checkpointing: bool = True,
|
| 134 |
+
device: str = "cuda" if torch.cuda.is_available() else "cpu",
|
| 135 |
+
):
|
| 136 |
+
"""Fine-tune a model on the XCoder-80K dataset."""
|
| 137 |
+
|
| 138 |
+
# Validate model
|
| 139 |
+
if model_name not in MODEL_REGISTRY:
|
| 140 |
+
logger.error(f"Model {model_name} not found. Available: {list(MODEL_REGISTRY.keys())}")
|
| 141 |
+
return False
|
| 142 |
+
|
| 143 |
+
hf_model_id = MODEL_REGISTRY[model_name]
|
| 144 |
+
output_model_dir = Path(output_dir) / model_name.replace(":", "_")
|
| 145 |
+
output_model_dir.mkdir(parents=True, exist_ok=True)
|
| 146 |
+
|
| 147 |
+
logger.info(f"\n{'='*60}")
|
| 148 |
+
logger.info(f"Fine-tuning: {model_name}")
|
| 149 |
+
logger.info(f"HF Model: {hf_model_id}")
|
| 150 |
+
logger.info(f"Output: {output_model_dir}")
|
| 151 |
+
logger.info(f"Device: {device}")
|
| 152 |
+
logger.info(f"{'='*60}\n")
|
| 153 |
+
|
| 154 |
+
# Load dataset
|
| 155 |
+
dataset = load_xcoder_dataset(split="train", max_samples=max_samples)
|
| 156 |
+
|
| 157 |
+
# Load tokenizer and model
|
| 158 |
+
logger.info(f"Loading {hf_model_id}...")
|
| 159 |
+
tokenizer = AutoTokenizer.from_pretrained(hf_model_id)
|
| 160 |
+
if tokenizer.pad_token is None:
|
| 161 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 162 |
+
|
| 163 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 164 |
+
hf_model_id,
|
| 165 |
+
torch_dtype=torch.float16 if device == "cuda" else torch.float32,
|
| 166 |
+
device_map="auto" if device == "cuda" else "cpu",
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
if use_gradient_checkpointing:
|
| 170 |
+
model.gradient_checkpointing_enable()
|
| 171 |
+
|
| 172 |
+
# Setup LoRA if requested
|
| 173 |
+
if use_lora:
|
| 174 |
+
model = setup_lora(model)
|
| 175 |
+
|
| 176 |
+
# Prepare dataset
|
| 177 |
+
train_dataset = prepare_dataset_for_sft(dataset, tokenizer)
|
| 178 |
+
|
| 179 |
+
# Training arguments
|
| 180 |
+
training_args = TrainingArguments(
|
| 181 |
+
output_dir=str(output_model_dir),
|
| 182 |
+
num_train_epochs=num_epochs,
|
| 183 |
+
per_device_train_batch_size=batch_size,
|
| 184 |
+
learning_rate=learning_rate,
|
| 185 |
+
weight_decay=0.01,
|
| 186 |
+
warmup_steps=500,
|
| 187 |
+
logging_steps=100,
|
| 188 |
+
save_steps=500,
|
| 189 |
+
save_total_limit=2,
|
| 190 |
+
gradient_accumulation_steps=2,
|
| 191 |
+
gradient_checkpointing=use_gradient_checkpointing,
|
| 192 |
+
fp16=device == "cuda",
|
| 193 |
+
optim="paged_adamw_8bit" if device == "cuda" else "adamw_torch",
|
| 194 |
+
report_to=["tensorboard"],
|
| 195 |
+
)
|
| 196 |
+
|
| 197 |
+
# Create trainer
|
| 198 |
+
trainer = Trainer(
|
| 199 |
+
model=model,
|
| 200 |
+
args=training_args,
|
| 201 |
+
train_dataset=train_dataset,
|
| 202 |
+
data_collator=DataCollatorForLanguageModeling(tokenizer, mlm=False),
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
# Train
|
| 206 |
+
logger.info("Starting training...")
|
| 207 |
+
try:
|
| 208 |
+
trainer.train()
|
| 209 |
+
logger.info(f"✓ Training completed successfully")
|
| 210 |
+
logger.info(f"Model saved to: {output_model_dir}")
|
| 211 |
+
|
| 212 |
+
# Save final model and tokenizer
|
| 213 |
+
model.save_pretrained(str(output_model_dir / "final"))
|
| 214 |
+
tokenizer.save_pretrained(str(output_model_dir / "final"))
|
| 215 |
+
|
| 216 |
+
# Save metadata
|
| 217 |
+
metadata = {
|
| 218 |
+
"model_name": model_name,
|
| 219 |
+
"hf_model_id": hf_model_id,
|
| 220 |
+
"dataset": XCODER_DATASET,
|
| 221 |
+
"training_args": training_args.to_dict(),
|
| 222 |
+
"num_epochs": num_epochs,
|
| 223 |
+
"batch_size": batch_size,
|
| 224 |
+
"learning_rate": learning_rate,
|
| 225 |
+
}
|
| 226 |
+
with open(output_model_dir / "metadata.json", "w") as f:
|
| 227 |
+
json.dump(metadata, f, indent=2)
|
| 228 |
+
|
| 229 |
+
return True
|
| 230 |
+
except Exception as e:
|
| 231 |
+
logger.error(f"Training failed: {e}")
|
| 232 |
+
return False
|
| 233 |
+
|
| 234 |
+
def main():
|
| 235 |
+
parser = argparse.ArgumentParser(description="Fine-tune models on XCoder-80K dataset")
|
| 236 |
+
parser.add_argument(
|
| 237 |
+
"--model",
|
| 238 |
+
type=str,
|
| 239 |
+
default="llama3.2",
|
| 240 |
+
choices=list(MODEL_REGISTRY.keys()),
|
| 241 |
+
help="Model to fine-tune",
|
| 242 |
+
)
|
| 243 |
+
parser.add_argument(
|
| 244 |
+
"--all-models",
|
| 245 |
+
action="store_true",
|
| 246 |
+
help="Fine-tune all available models sequentially",
|
| 247 |
+
)
|
| 248 |
+
parser.add_argument(
|
| 249 |
+
"--output-dir",
|
| 250 |
+
type=str,
|
| 251 |
+
default="./finetuned_models",
|
| 252 |
+
help="Output directory for fine-tuned models",
|
| 253 |
+
)
|
| 254 |
+
parser.add_argument(
|
| 255 |
+
"--num-epochs",
|
| 256 |
+
type=int,
|
| 257 |
+
default=3,
|
| 258 |
+
help="Number of training epochs",
|
| 259 |
+
)
|
| 260 |
+
parser.add_argument(
|
| 261 |
+
"--batch-size",
|
| 262 |
+
type=int,
|
| 263 |
+
default=4,
|
| 264 |
+
help="Training batch size",
|
| 265 |
+
)
|
| 266 |
+
parser.add_argument(
|
| 267 |
+
"--learning-rate",
|
| 268 |
+
type=float,
|
| 269 |
+
default=2e-4,
|
| 270 |
+
help="Learning rate",
|
| 271 |
+
)
|
| 272 |
+
parser.add_argument(
|
| 273 |
+
"--max-samples",
|
| 274 |
+
type=int,
|
| 275 |
+
default=None,
|
| 276 |
+
help="Maximum number of samples to use (None = all)",
|
| 277 |
+
)
|
| 278 |
+
parser.add_argument(
|
| 279 |
+
"--no-lora",
|
| 280 |
+
action="store_true",
|
| 281 |
+
help="Disable LoRA (full fine-tuning instead)",
|
| 282 |
+
)
|
| 283 |
+
parser.add_argument(
|
| 284 |
+
"--no-gradient-checkpointing",
|
| 285 |
+
action="store_true",
|
| 286 |
+
help="Disable gradient checkpointing",
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
args = parser.parse_args()
|
| 290 |
+
|
| 291 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 292 |
+
logger.info(f"Using device: {device}")
|
| 293 |
+
|
| 294 |
+
if args.all_models:
|
| 295 |
+
results = {}
|
| 296 |
+
for model_name in MODEL_REGISTRY.keys():
|
| 297 |
+
success = finetune_model(
|
| 298 |
+
model_name=model_name,
|
| 299 |
+
output_dir=args.output_dir,
|
| 300 |
+
num_epochs=args.num_epochs,
|
| 301 |
+
batch_size=args.batch_size,
|
| 302 |
+
learning_rate=args.learning_rate,
|
| 303 |
+
max_samples=args.max_samples,
|
| 304 |
+
use_lora=not args.no_lora,
|
| 305 |
+
use_gradient_checkpointing=not args.no_gradient_checkpointing,
|
| 306 |
+
device=device,
|
| 307 |
+
)
|
| 308 |
+
results[model_name] = "✓ Success" if success else "✗ Failed"
|
| 309 |
+
|
| 310 |
+
logger.info("\n" + "="*60)
|
| 311 |
+
logger.info("FINE-TUNING RESULTS")
|
| 312 |
+
logger.info("="*60)
|
| 313 |
+
for model, status in results.items():
|
| 314 |
+
logger.info(f"{model}: {status}")
|
| 315 |
+
else:
|
| 316 |
+
success = finetune_model(
|
| 317 |
+
model_name=args.model,
|
| 318 |
+
output_dir=args.output_dir,
|
| 319 |
+
num_epochs=args.num_epochs,
|
| 320 |
+
batch_size=args.batch_size,
|
| 321 |
+
learning_rate=args.learning_rate,
|
| 322 |
+
max_samples=args.max_samples,
|
| 323 |
+
use_lora=not args.no_lora,
|
| 324 |
+
use_gradient_checkpointing=not args.no_gradient_checkpointing,
|
| 325 |
+
device=device,
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
if success:
|
| 329 |
+
logger.info("\n✓ Fine-tuning completed successfully!")
|
| 330 |
+
logger.info(f"Output directory: {args.output_dir}")
|
| 331 |
+
else:
|
| 332 |
+
logger.error("\n✗ Fine-tuning failed!")
|
| 333 |
+
|
| 334 |
+
if __name__ == "__main__":
|
| 335 |
+
main()
|
frontend/index.html
CHANGED
|
@@ -2,10 +2,13 @@
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
-
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
| 6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
-
<title>CodeArena RL —
|
| 8 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</head>
|
| 10 |
<body>
|
| 11 |
<div id="root"></div>
|
|
|
|
| 2 |
<html lang="en">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
|
|
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 6 |
+
<title>CodeArena RL — AI Code Repair Benchmark</title>
|
| 7 |
+
<meta name="description" content="CodeArena RL — the first standardized reinforcement learning benchmark for iterative code repair. Grade AI agents on debugging, not generation." />
|
| 8 |
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
| 9 |
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet" />
|
| 11 |
+
<style>body{margin:0;background:#0B0F19;}</style>
|
| 12 |
</head>
|
| 13 |
<body>
|
| 14 |
<div id="root"></div>
|
frontend/package-lock.json
CHANGED
|
@@ -8,9 +8,15 @@
|
|
| 8 |
"name": "frontend",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
"react": "^19.2.5",
|
| 12 |
"react-dom": "^19.2.5",
|
| 13 |
-
"recharts": "^3.8.1"
|
|
|
|
| 14 |
},
|
| 15 |
"devDependencies": {
|
| 16 |
"@eslint/js": "^9.39.4",
|
|
@@ -269,7 +275,6 @@
|
|
| 269 |
"version": "1.2.1",
|
| 270 |
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
| 271 |
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
| 272 |
-
"dev": true,
|
| 273 |
"license": "MIT",
|
| 274 |
"optional": true,
|
| 275 |
"dependencies": {
|
|
@@ -503,7 +508,6 @@
|
|
| 503 |
"version": "0.3.13",
|
| 504 |
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 505 |
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
| 506 |
-
"dev": true,
|
| 507 |
"license": "MIT",
|
| 508 |
"dependencies": {
|
| 509 |
"@jridgewell/sourcemap-codec": "^1.5.0",
|
|
@@ -514,7 +518,6 @@
|
|
| 514 |
"version": "2.3.5",
|
| 515 |
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 516 |
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
| 517 |
-
"dev": true,
|
| 518 |
"license": "MIT",
|
| 519 |
"dependencies": {
|
| 520 |
"@jridgewell/gen-mapping": "^0.3.5",
|
|
@@ -525,7 +528,6 @@
|
|
| 525 |
"version": "3.1.2",
|
| 526 |
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 527 |
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
| 528 |
-
"dev": true,
|
| 529 |
"license": "MIT",
|
| 530 |
"engines": {
|
| 531 |
"node": ">=6.0.0"
|
|
@@ -535,25 +537,45 @@
|
|
| 535 |
"version": "1.5.5",
|
| 536 |
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 537 |
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
| 538 |
-
"dev": true,
|
| 539 |
"license": "MIT"
|
| 540 |
},
|
| 541 |
"node_modules/@jridgewell/trace-mapping": {
|
| 542 |
"version": "0.3.31",
|
| 543 |
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 544 |
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
| 545 |
-
"dev": true,
|
| 546 |
"license": "MIT",
|
| 547 |
"dependencies": {
|
| 548 |
"@jridgewell/resolve-uri": "^3.1.0",
|
| 549 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 550 |
}
|
| 551 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
"node_modules/@napi-rs/wasm-runtime": {
|
| 553 |
"version": "1.1.4",
|
| 554 |
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
| 555 |
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
| 556 |
-
"dev": true,
|
| 557 |
"license": "MIT",
|
| 558 |
"optional": true,
|
| 559 |
"dependencies": {
|
|
@@ -572,7 +594,6 @@
|
|
| 572 |
"version": "0.126.0",
|
| 573 |
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz",
|
| 574 |
"integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==",
|
| 575 |
-
"dev": true,
|
| 576 |
"license": "MIT",
|
| 577 |
"funding": {
|
| 578 |
"url": "https://github.com/sponsors/Boshen"
|
|
@@ -621,7 +642,6 @@
|
|
| 621 |
"cpu": [
|
| 622 |
"arm64"
|
| 623 |
],
|
| 624 |
-
"dev": true,
|
| 625 |
"license": "MIT",
|
| 626 |
"optional": true,
|
| 627 |
"os": [
|
|
@@ -638,7 +658,6 @@
|
|
| 638 |
"cpu": [
|
| 639 |
"arm64"
|
| 640 |
],
|
| 641 |
-
"dev": true,
|
| 642 |
"license": "MIT",
|
| 643 |
"optional": true,
|
| 644 |
"os": [
|
|
@@ -655,7 +674,6 @@
|
|
| 655 |
"cpu": [
|
| 656 |
"x64"
|
| 657 |
],
|
| 658 |
-
"dev": true,
|
| 659 |
"license": "MIT",
|
| 660 |
"optional": true,
|
| 661 |
"os": [
|
|
@@ -672,7 +690,6 @@
|
|
| 672 |
"cpu": [
|
| 673 |
"x64"
|
| 674 |
],
|
| 675 |
-
"dev": true,
|
| 676 |
"license": "MIT",
|
| 677 |
"optional": true,
|
| 678 |
"os": [
|
|
@@ -689,7 +706,6 @@
|
|
| 689 |
"cpu": [
|
| 690 |
"arm"
|
| 691 |
],
|
| 692 |
-
"dev": true,
|
| 693 |
"license": "MIT",
|
| 694 |
"optional": true,
|
| 695 |
"os": [
|
|
@@ -706,7 +722,6 @@
|
|
| 706 |
"cpu": [
|
| 707 |
"arm64"
|
| 708 |
],
|
| 709 |
-
"dev": true,
|
| 710 |
"license": "MIT",
|
| 711 |
"optional": true,
|
| 712 |
"os": [
|
|
@@ -723,7 +738,6 @@
|
|
| 723 |
"cpu": [
|
| 724 |
"arm64"
|
| 725 |
],
|
| 726 |
-
"dev": true,
|
| 727 |
"license": "MIT",
|
| 728 |
"optional": true,
|
| 729 |
"os": [
|
|
@@ -740,7 +754,6 @@
|
|
| 740 |
"cpu": [
|
| 741 |
"ppc64"
|
| 742 |
],
|
| 743 |
-
"dev": true,
|
| 744 |
"license": "MIT",
|
| 745 |
"optional": true,
|
| 746 |
"os": [
|
|
@@ -757,7 +770,6 @@
|
|
| 757 |
"cpu": [
|
| 758 |
"s390x"
|
| 759 |
],
|
| 760 |
-
"dev": true,
|
| 761 |
"license": "MIT",
|
| 762 |
"optional": true,
|
| 763 |
"os": [
|
|
@@ -774,7 +786,6 @@
|
|
| 774 |
"cpu": [
|
| 775 |
"x64"
|
| 776 |
],
|
| 777 |
-
"dev": true,
|
| 778 |
"license": "MIT",
|
| 779 |
"optional": true,
|
| 780 |
"os": [
|
|
@@ -791,7 +802,6 @@
|
|
| 791 |
"cpu": [
|
| 792 |
"x64"
|
| 793 |
],
|
| 794 |
-
"dev": true,
|
| 795 |
"license": "MIT",
|
| 796 |
"optional": true,
|
| 797 |
"os": [
|
|
@@ -808,7 +818,6 @@
|
|
| 808 |
"cpu": [
|
| 809 |
"arm64"
|
| 810 |
],
|
| 811 |
-
"dev": true,
|
| 812 |
"license": "MIT",
|
| 813 |
"optional": true,
|
| 814 |
"os": [
|
|
@@ -825,7 +834,6 @@
|
|
| 825 |
"cpu": [
|
| 826 |
"wasm32"
|
| 827 |
],
|
| 828 |
-
"dev": true,
|
| 829 |
"license": "MIT",
|
| 830 |
"optional": true,
|
| 831 |
"dependencies": {
|
|
@@ -837,6 +845,27 @@
|
|
| 837 |
"node": "^20.19.0 || >=22.12.0"
|
| 838 |
}
|
| 839 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 840 |
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 841 |
"version": "1.0.0-rc.16",
|
| 842 |
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz",
|
|
@@ -844,7 +873,6 @@
|
|
| 844 |
"cpu": [
|
| 845 |
"arm64"
|
| 846 |
],
|
| 847 |
-
"dev": true,
|
| 848 |
"license": "MIT",
|
| 849 |
"optional": true,
|
| 850 |
"os": [
|
|
@@ -861,7 +889,6 @@
|
|
| 861 |
"cpu": [
|
| 862 |
"x64"
|
| 863 |
],
|
| 864 |
-
"dev": true,
|
| 865 |
"license": "MIT",
|
| 866 |
"optional": true,
|
| 867 |
"os": [
|
|
@@ -890,11 +917,267 @@
|
|
| 890 |
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
| 891 |
"license": "MIT"
|
| 892 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 893 |
"node_modules/@tybys/wasm-util": {
|
| 894 |
"version": "0.10.1",
|
| 895 |
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
| 896 |
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
| 897 |
-
"dev": true,
|
| 898 |
"license": "MIT",
|
| 899 |
"optional": true,
|
| 900 |
"dependencies": {
|
|
@@ -999,6 +1282,13 @@
|
|
| 999 |
"@types/react": "^19.2.0"
|
| 1000 |
}
|
| 1001 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
"node_modules/@types/use-sync-external-store": {
|
| 1003 |
"version": "0.0.6",
|
| 1004 |
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
|
@@ -1430,12 +1720,20 @@
|
|
| 1430 |
"version": "2.1.2",
|
| 1431 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1432 |
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
| 1433 |
-
"dev": true,
|
| 1434 |
"license": "Apache-2.0",
|
| 1435 |
"engines": {
|
| 1436 |
"node": ">=8"
|
| 1437 |
}
|
| 1438 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1439 |
"node_modules/electron-to-chromium": {
|
| 1440 |
"version": "1.5.343",
|
| 1441 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.343.tgz",
|
|
@@ -1443,6 +1741,19 @@
|
|
| 1443 |
"dev": true,
|
| 1444 |
"license": "ISC"
|
| 1445 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1446 |
"node_modules/es-toolkit": {
|
| 1447 |
"version": "1.46.0",
|
| 1448 |
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.0.tgz",
|
|
@@ -1692,7 +2003,6 @@
|
|
| 1692 |
"version": "6.5.0",
|
| 1693 |
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 1694 |
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
| 1695 |
-
"dev": true,
|
| 1696 |
"license": "MIT",
|
| 1697 |
"engines": {
|
| 1698 |
"node": ">=12.0.0"
|
|
@@ -1757,11 +2067,37 @@
|
|
| 1757 |
"dev": true,
|
| 1758 |
"license": "ISC"
|
| 1759 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1760 |
"node_modules/fsevents": {
|
| 1761 |
"version": "2.3.3",
|
| 1762 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 1763 |
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
| 1764 |
-
"dev": true,
|
| 1765 |
"hasInstallScript": true,
|
| 1766 |
"license": "MIT",
|
| 1767 |
"optional": true,
|
|
@@ -1808,6 +2144,12 @@
|
|
| 1808 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 1809 |
}
|
| 1810 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1811 |
"node_modules/has-flag": {
|
| 1812 |
"version": "4.0.0",
|
| 1813 |
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
@@ -1921,6 +2263,15 @@
|
|
| 1921 |
"dev": true,
|
| 1922 |
"license": "ISC"
|
| 1923 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1924 |
"node_modules/js-tokens": {
|
| 1925 |
"version": "4.0.0",
|
| 1926 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
@@ -2016,7 +2367,6 @@
|
|
| 2016 |
"version": "1.32.0",
|
| 2017 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 2018 |
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
| 2019 |
-
"dev": true,
|
| 2020 |
"license": "MPL-2.0",
|
| 2021 |
"dependencies": {
|
| 2022 |
"detect-libc": "^2.0.3"
|
|
@@ -2049,7 +2399,6 @@
|
|
| 2049 |
"cpu": [
|
| 2050 |
"arm64"
|
| 2051 |
],
|
| 2052 |
-
"dev": true,
|
| 2053 |
"license": "MPL-2.0",
|
| 2054 |
"optional": true,
|
| 2055 |
"os": [
|
|
@@ -2070,7 +2419,6 @@
|
|
| 2070 |
"cpu": [
|
| 2071 |
"arm64"
|
| 2072 |
],
|
| 2073 |
-
"dev": true,
|
| 2074 |
"license": "MPL-2.0",
|
| 2075 |
"optional": true,
|
| 2076 |
"os": [
|
|
@@ -2091,7 +2439,6 @@
|
|
| 2091 |
"cpu": [
|
| 2092 |
"x64"
|
| 2093 |
],
|
| 2094 |
-
"dev": true,
|
| 2095 |
"license": "MPL-2.0",
|
| 2096 |
"optional": true,
|
| 2097 |
"os": [
|
|
@@ -2112,7 +2459,6 @@
|
|
| 2112 |
"cpu": [
|
| 2113 |
"x64"
|
| 2114 |
],
|
| 2115 |
-
"dev": true,
|
| 2116 |
"license": "MPL-2.0",
|
| 2117 |
"optional": true,
|
| 2118 |
"os": [
|
|
@@ -2133,7 +2479,6 @@
|
|
| 2133 |
"cpu": [
|
| 2134 |
"arm"
|
| 2135 |
],
|
| 2136 |
-
"dev": true,
|
| 2137 |
"license": "MPL-2.0",
|
| 2138 |
"optional": true,
|
| 2139 |
"os": [
|
|
@@ -2154,7 +2499,6 @@
|
|
| 2154 |
"cpu": [
|
| 2155 |
"arm64"
|
| 2156 |
],
|
| 2157 |
-
"dev": true,
|
| 2158 |
"license": "MPL-2.0",
|
| 2159 |
"optional": true,
|
| 2160 |
"os": [
|
|
@@ -2175,7 +2519,6 @@
|
|
| 2175 |
"cpu": [
|
| 2176 |
"arm64"
|
| 2177 |
],
|
| 2178 |
-
"dev": true,
|
| 2179 |
"license": "MPL-2.0",
|
| 2180 |
"optional": true,
|
| 2181 |
"os": [
|
|
@@ -2196,7 +2539,6 @@
|
|
| 2196 |
"cpu": [
|
| 2197 |
"x64"
|
| 2198 |
],
|
| 2199 |
-
"dev": true,
|
| 2200 |
"license": "MPL-2.0",
|
| 2201 |
"optional": true,
|
| 2202 |
"os": [
|
|
@@ -2217,7 +2559,6 @@
|
|
| 2217 |
"cpu": [
|
| 2218 |
"x64"
|
| 2219 |
],
|
| 2220 |
-
"dev": true,
|
| 2221 |
"license": "MPL-2.0",
|
| 2222 |
"optional": true,
|
| 2223 |
"os": [
|
|
@@ -2238,7 +2579,6 @@
|
|
| 2238 |
"cpu": [
|
| 2239 |
"arm64"
|
| 2240 |
],
|
| 2241 |
-
"dev": true,
|
| 2242 |
"license": "MPL-2.0",
|
| 2243 |
"optional": true,
|
| 2244 |
"os": [
|
|
@@ -2259,7 +2599,6 @@
|
|
| 2259 |
"cpu": [
|
| 2260 |
"x64"
|
| 2261 |
],
|
| 2262 |
-
"dev": true,
|
| 2263 |
"license": "MPL-2.0",
|
| 2264 |
"optional": true,
|
| 2265 |
"os": [
|
|
@@ -2306,6 +2645,36 @@
|
|
| 2306 |
"yallist": "^3.0.2"
|
| 2307 |
}
|
| 2308 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2309 |
"node_modules/minimatch": {
|
| 2310 |
"version": "3.1.5",
|
| 2311 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
|
@@ -2319,6 +2688,32 @@
|
|
| 2319 |
"node": "*"
|
| 2320 |
}
|
| 2321 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2322 |
"node_modules/ms": {
|
| 2323 |
"version": "2.1.3",
|
| 2324 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
@@ -2330,7 +2725,6 @@
|
|
| 2330 |
"version": "3.3.11",
|
| 2331 |
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 2332 |
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
| 2333 |
-
"dev": true,
|
| 2334 |
"funding": [
|
| 2335 |
{
|
| 2336 |
"type": "github",
|
|
@@ -2446,14 +2840,12 @@
|
|
| 2446 |
"version": "1.1.1",
|
| 2447 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2448 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
| 2449 |
-
"dev": true,
|
| 2450 |
"license": "ISC"
|
| 2451 |
},
|
| 2452 |
"node_modules/picomatch": {
|
| 2453 |
"version": "4.0.4",
|
| 2454 |
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2455 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
| 2456 |
-
"dev": true,
|
| 2457 |
"license": "MIT",
|
| 2458 |
"peer": true,
|
| 2459 |
"engines": {
|
|
@@ -2467,7 +2859,6 @@
|
|
| 2467 |
"version": "8.5.10",
|
| 2468 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
| 2469 |
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
| 2470 |
-
"dev": true,
|
| 2471 |
"funding": [
|
| 2472 |
{
|
| 2473 |
"type": "opencollective",
|
|
@@ -2632,7 +3023,6 @@
|
|
| 2632 |
"version": "1.0.0-rc.16",
|
| 2633 |
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz",
|
| 2634 |
"integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==",
|
| 2635 |
-
"dev": true,
|
| 2636 |
"license": "MIT",
|
| 2637 |
"dependencies": {
|
| 2638 |
"@oxc-project/types": "=0.126.0",
|
|
@@ -2666,7 +3056,6 @@
|
|
| 2666 |
"version": "1.0.0-rc.16",
|
| 2667 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz",
|
| 2668 |
"integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==",
|
| 2669 |
-
"dev": true,
|
| 2670 |
"license": "MIT"
|
| 2671 |
},
|
| 2672 |
"node_modules/scheduler": {
|
|
@@ -2712,12 +3101,17 @@
|
|
| 2712 |
"version": "1.2.1",
|
| 2713 |
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 2714 |
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
| 2715 |
-
"dev": true,
|
| 2716 |
"license": "BSD-3-Clause",
|
| 2717 |
"engines": {
|
| 2718 |
"node": ">=0.10.0"
|
| 2719 |
}
|
| 2720 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2721 |
"node_modules/strip-json-comments": {
|
| 2722 |
"version": "3.1.1",
|
| 2723 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
@@ -2744,6 +3138,25 @@
|
|
| 2744 |
"node": ">=8"
|
| 2745 |
}
|
| 2746 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2747 |
"node_modules/tiny-invariant": {
|
| 2748 |
"version": "1.3.3",
|
| 2749 |
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
|
@@ -2754,7 +3167,6 @@
|
|
| 2754 |
"version": "0.2.16",
|
| 2755 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 2756 |
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
| 2757 |
-
"dev": true,
|
| 2758 |
"license": "MIT",
|
| 2759 |
"dependencies": {
|
| 2760 |
"fdir": "^6.5.0",
|
|
@@ -2771,9 +3183,7 @@
|
|
| 2771 |
"version": "2.8.1",
|
| 2772 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 2773 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 2774 |
-
"
|
| 2775 |
-
"license": "0BSD",
|
| 2776 |
-
"optional": true
|
| 2777 |
},
|
| 2778 |
"node_modules/type-check": {
|
| 2779 |
"version": "0.4.0",
|
|
@@ -2864,7 +3274,6 @@
|
|
| 2864 |
"version": "8.0.9",
|
| 2865 |
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
|
| 2866 |
"integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==",
|
| 2867 |
-
"dev": true,
|
| 2868 |
"license": "MIT",
|
| 2869 |
"peer": true,
|
| 2870 |
"dependencies": {
|
|
|
|
| 8 |
"name": "frontend",
|
| 9 |
"version": "0.0.0",
|
| 10 |
"dependencies": {
|
| 11 |
+
"@monaco-editor/react": "^4.7.0",
|
| 12 |
+
"@tailwindcss/vite": "^4.2.4",
|
| 13 |
+
"clsx": "^2.1.1",
|
| 14 |
+
"framer-motion": "^12.38.0",
|
| 15 |
+
"lucide-react": "^1.11.0",
|
| 16 |
"react": "^19.2.5",
|
| 17 |
"react-dom": "^19.2.5",
|
| 18 |
+
"recharts": "^3.8.1",
|
| 19 |
+
"tailwindcss": "^4.2.4"
|
| 20 |
},
|
| 21 |
"devDependencies": {
|
| 22 |
"@eslint/js": "^9.39.4",
|
|
|
|
| 275 |
"version": "1.2.1",
|
| 276 |
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz",
|
| 277 |
"integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==",
|
|
|
|
| 278 |
"license": "MIT",
|
| 279 |
"optional": true,
|
| 280 |
"dependencies": {
|
|
|
|
| 508 |
"version": "0.3.13",
|
| 509 |
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
| 510 |
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
|
|
|
|
| 511 |
"license": "MIT",
|
| 512 |
"dependencies": {
|
| 513 |
"@jridgewell/sourcemap-codec": "^1.5.0",
|
|
|
|
| 518 |
"version": "2.3.5",
|
| 519 |
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
|
| 520 |
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
|
|
|
|
| 521 |
"license": "MIT",
|
| 522 |
"dependencies": {
|
| 523 |
"@jridgewell/gen-mapping": "^0.3.5",
|
|
|
|
| 528 |
"version": "3.1.2",
|
| 529 |
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
| 530 |
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
|
|
|
| 531 |
"license": "MIT",
|
| 532 |
"engines": {
|
| 533 |
"node": ">=6.0.0"
|
|
|
|
| 537 |
"version": "1.5.5",
|
| 538 |
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
| 539 |
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
|
|
|
|
| 540 |
"license": "MIT"
|
| 541 |
},
|
| 542 |
"node_modules/@jridgewell/trace-mapping": {
|
| 543 |
"version": "0.3.31",
|
| 544 |
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
|
| 545 |
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
|
|
|
|
| 546 |
"license": "MIT",
|
| 547 |
"dependencies": {
|
| 548 |
"@jridgewell/resolve-uri": "^3.1.0",
|
| 549 |
"@jridgewell/sourcemap-codec": "^1.4.14"
|
| 550 |
}
|
| 551 |
},
|
| 552 |
+
"node_modules/@monaco-editor/loader": {
|
| 553 |
+
"version": "1.7.0",
|
| 554 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/loader/-/loader-1.7.0.tgz",
|
| 555 |
+
"integrity": "sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==",
|
| 556 |
+
"license": "MIT",
|
| 557 |
+
"dependencies": {
|
| 558 |
+
"state-local": "^1.0.6"
|
| 559 |
+
}
|
| 560 |
+
},
|
| 561 |
+
"node_modules/@monaco-editor/react": {
|
| 562 |
+
"version": "4.7.0",
|
| 563 |
+
"resolved": "https://registry.npmjs.org/@monaco-editor/react/-/react-4.7.0.tgz",
|
| 564 |
+
"integrity": "sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==",
|
| 565 |
+
"license": "MIT",
|
| 566 |
+
"dependencies": {
|
| 567 |
+
"@monaco-editor/loader": "^1.5.0"
|
| 568 |
+
},
|
| 569 |
+
"peerDependencies": {
|
| 570 |
+
"monaco-editor": ">= 0.25.0 < 1",
|
| 571 |
+
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
| 572 |
+
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 573 |
+
}
|
| 574 |
+
},
|
| 575 |
"node_modules/@napi-rs/wasm-runtime": {
|
| 576 |
"version": "1.1.4",
|
| 577 |
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
|
| 578 |
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
|
|
|
|
| 579 |
"license": "MIT",
|
| 580 |
"optional": true,
|
| 581 |
"dependencies": {
|
|
|
|
| 594 |
"version": "0.126.0",
|
| 595 |
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.126.0.tgz",
|
| 596 |
"integrity": "sha512-oGfVtjAgwQVVpfBrbtk4e1XDyWHRFta6BS3GWVzrF8xYBT2VGQAk39yJS/wFSMrZqoiCU4oghT3Ch0HaHGIHcQ==",
|
|
|
|
| 597 |
"license": "MIT",
|
| 598 |
"funding": {
|
| 599 |
"url": "https://github.com/sponsors/Boshen"
|
|
|
|
| 642 |
"cpu": [
|
| 643 |
"arm64"
|
| 644 |
],
|
|
|
|
| 645 |
"license": "MIT",
|
| 646 |
"optional": true,
|
| 647 |
"os": [
|
|
|
|
| 658 |
"cpu": [
|
| 659 |
"arm64"
|
| 660 |
],
|
|
|
|
| 661 |
"license": "MIT",
|
| 662 |
"optional": true,
|
| 663 |
"os": [
|
|
|
|
| 674 |
"cpu": [
|
| 675 |
"x64"
|
| 676 |
],
|
|
|
|
| 677 |
"license": "MIT",
|
| 678 |
"optional": true,
|
| 679 |
"os": [
|
|
|
|
| 690 |
"cpu": [
|
| 691 |
"x64"
|
| 692 |
],
|
|
|
|
| 693 |
"license": "MIT",
|
| 694 |
"optional": true,
|
| 695 |
"os": [
|
|
|
|
| 706 |
"cpu": [
|
| 707 |
"arm"
|
| 708 |
],
|
|
|
|
| 709 |
"license": "MIT",
|
| 710 |
"optional": true,
|
| 711 |
"os": [
|
|
|
|
| 722 |
"cpu": [
|
| 723 |
"arm64"
|
| 724 |
],
|
|
|
|
| 725 |
"license": "MIT",
|
| 726 |
"optional": true,
|
| 727 |
"os": [
|
|
|
|
| 738 |
"cpu": [
|
| 739 |
"arm64"
|
| 740 |
],
|
|
|
|
| 741 |
"license": "MIT",
|
| 742 |
"optional": true,
|
| 743 |
"os": [
|
|
|
|
| 754 |
"cpu": [
|
| 755 |
"ppc64"
|
| 756 |
],
|
|
|
|
| 757 |
"license": "MIT",
|
| 758 |
"optional": true,
|
| 759 |
"os": [
|
|
|
|
| 770 |
"cpu": [
|
| 771 |
"s390x"
|
| 772 |
],
|
|
|
|
| 773 |
"license": "MIT",
|
| 774 |
"optional": true,
|
| 775 |
"os": [
|
|
|
|
| 786 |
"cpu": [
|
| 787 |
"x64"
|
| 788 |
],
|
|
|
|
| 789 |
"license": "MIT",
|
| 790 |
"optional": true,
|
| 791 |
"os": [
|
|
|
|
| 802 |
"cpu": [
|
| 803 |
"x64"
|
| 804 |
],
|
|
|
|
| 805 |
"license": "MIT",
|
| 806 |
"optional": true,
|
| 807 |
"os": [
|
|
|
|
| 818 |
"cpu": [
|
| 819 |
"arm64"
|
| 820 |
],
|
|
|
|
| 821 |
"license": "MIT",
|
| 822 |
"optional": true,
|
| 823 |
"os": [
|
|
|
|
| 834 |
"cpu": [
|
| 835 |
"wasm32"
|
| 836 |
],
|
|
|
|
| 837 |
"license": "MIT",
|
| 838 |
"optional": true,
|
| 839 |
"dependencies": {
|
|
|
|
| 845 |
"node": "^20.19.0 || >=22.12.0"
|
| 846 |
}
|
| 847 |
},
|
| 848 |
+
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": {
|
| 849 |
+
"version": "1.9.2",
|
| 850 |
+
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
|
| 851 |
+
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
|
| 852 |
+
"license": "MIT",
|
| 853 |
+
"optional": true,
|
| 854 |
+
"dependencies": {
|
| 855 |
+
"@emnapi/wasi-threads": "1.2.1",
|
| 856 |
+
"tslib": "^2.4.0"
|
| 857 |
+
}
|
| 858 |
+
},
|
| 859 |
+
"node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": {
|
| 860 |
+
"version": "1.9.2",
|
| 861 |
+
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
|
| 862 |
+
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
|
| 863 |
+
"license": "MIT",
|
| 864 |
+
"optional": true,
|
| 865 |
+
"dependencies": {
|
| 866 |
+
"tslib": "^2.4.0"
|
| 867 |
+
}
|
| 868 |
+
},
|
| 869 |
"node_modules/@rolldown/binding-win32-arm64-msvc": {
|
| 870 |
"version": "1.0.0-rc.16",
|
| 871 |
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.16.tgz",
|
|
|
|
| 873 |
"cpu": [
|
| 874 |
"arm64"
|
| 875 |
],
|
|
|
|
| 876 |
"license": "MIT",
|
| 877 |
"optional": true,
|
| 878 |
"os": [
|
|
|
|
| 889 |
"cpu": [
|
| 890 |
"x64"
|
| 891 |
],
|
|
|
|
| 892 |
"license": "MIT",
|
| 893 |
"optional": true,
|
| 894 |
"os": [
|
|
|
|
| 917 |
"integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==",
|
| 918 |
"license": "MIT"
|
| 919 |
},
|
| 920 |
+
"node_modules/@tailwindcss/node": {
|
| 921 |
+
"version": "4.2.4",
|
| 922 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz",
|
| 923 |
+
"integrity": "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==",
|
| 924 |
+
"license": "MIT",
|
| 925 |
+
"dependencies": {
|
| 926 |
+
"@jridgewell/remapping": "^2.3.5",
|
| 927 |
+
"enhanced-resolve": "^5.19.0",
|
| 928 |
+
"jiti": "^2.6.1",
|
| 929 |
+
"lightningcss": "1.32.0",
|
| 930 |
+
"magic-string": "^0.30.21",
|
| 931 |
+
"source-map-js": "^1.2.1",
|
| 932 |
+
"tailwindcss": "4.2.4"
|
| 933 |
+
}
|
| 934 |
+
},
|
| 935 |
+
"node_modules/@tailwindcss/oxide": {
|
| 936 |
+
"version": "4.2.4",
|
| 937 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.4.tgz",
|
| 938 |
+
"integrity": "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q==",
|
| 939 |
+
"license": "MIT",
|
| 940 |
+
"engines": {
|
| 941 |
+
"node": ">= 20"
|
| 942 |
+
},
|
| 943 |
+
"optionalDependencies": {
|
| 944 |
+
"@tailwindcss/oxide-android-arm64": "4.2.4",
|
| 945 |
+
"@tailwindcss/oxide-darwin-arm64": "4.2.4",
|
| 946 |
+
"@tailwindcss/oxide-darwin-x64": "4.2.4",
|
| 947 |
+
"@tailwindcss/oxide-freebsd-x64": "4.2.4",
|
| 948 |
+
"@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4",
|
| 949 |
+
"@tailwindcss/oxide-linux-arm64-gnu": "4.2.4",
|
| 950 |
+
"@tailwindcss/oxide-linux-arm64-musl": "4.2.4",
|
| 951 |
+
"@tailwindcss/oxide-linux-x64-gnu": "4.2.4",
|
| 952 |
+
"@tailwindcss/oxide-linux-x64-musl": "4.2.4",
|
| 953 |
+
"@tailwindcss/oxide-wasm32-wasi": "4.2.4",
|
| 954 |
+
"@tailwindcss/oxide-win32-arm64-msvc": "4.2.4",
|
| 955 |
+
"@tailwindcss/oxide-win32-x64-msvc": "4.2.4"
|
| 956 |
+
}
|
| 957 |
+
},
|
| 958 |
+
"node_modules/@tailwindcss/oxide-android-arm64": {
|
| 959 |
+
"version": "4.2.4",
|
| 960 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.4.tgz",
|
| 961 |
+
"integrity": "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g==",
|
| 962 |
+
"cpu": [
|
| 963 |
+
"arm64"
|
| 964 |
+
],
|
| 965 |
+
"license": "MIT",
|
| 966 |
+
"optional": true,
|
| 967 |
+
"os": [
|
| 968 |
+
"android"
|
| 969 |
+
],
|
| 970 |
+
"engines": {
|
| 971 |
+
"node": ">= 20"
|
| 972 |
+
}
|
| 973 |
+
},
|
| 974 |
+
"node_modules/@tailwindcss/oxide-darwin-arm64": {
|
| 975 |
+
"version": "4.2.4",
|
| 976 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.4.tgz",
|
| 977 |
+
"integrity": "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg==",
|
| 978 |
+
"cpu": [
|
| 979 |
+
"arm64"
|
| 980 |
+
],
|
| 981 |
+
"license": "MIT",
|
| 982 |
+
"optional": true,
|
| 983 |
+
"os": [
|
| 984 |
+
"darwin"
|
| 985 |
+
],
|
| 986 |
+
"engines": {
|
| 987 |
+
"node": ">= 20"
|
| 988 |
+
}
|
| 989 |
+
},
|
| 990 |
+
"node_modules/@tailwindcss/oxide-darwin-x64": {
|
| 991 |
+
"version": "4.2.4",
|
| 992 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.4.tgz",
|
| 993 |
+
"integrity": "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg==",
|
| 994 |
+
"cpu": [
|
| 995 |
+
"x64"
|
| 996 |
+
],
|
| 997 |
+
"license": "MIT",
|
| 998 |
+
"optional": true,
|
| 999 |
+
"os": [
|
| 1000 |
+
"darwin"
|
| 1001 |
+
],
|
| 1002 |
+
"engines": {
|
| 1003 |
+
"node": ">= 20"
|
| 1004 |
+
}
|
| 1005 |
+
},
|
| 1006 |
+
"node_modules/@tailwindcss/oxide-freebsd-x64": {
|
| 1007 |
+
"version": "4.2.4",
|
| 1008 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.4.tgz",
|
| 1009 |
+
"integrity": "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw==",
|
| 1010 |
+
"cpu": [
|
| 1011 |
+
"x64"
|
| 1012 |
+
],
|
| 1013 |
+
"license": "MIT",
|
| 1014 |
+
"optional": true,
|
| 1015 |
+
"os": [
|
| 1016 |
+
"freebsd"
|
| 1017 |
+
],
|
| 1018 |
+
"engines": {
|
| 1019 |
+
"node": ">= 20"
|
| 1020 |
+
}
|
| 1021 |
+
},
|
| 1022 |
+
"node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
|
| 1023 |
+
"version": "4.2.4",
|
| 1024 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.4.tgz",
|
| 1025 |
+
"integrity": "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA==",
|
| 1026 |
+
"cpu": [
|
| 1027 |
+
"arm"
|
| 1028 |
+
],
|
| 1029 |
+
"license": "MIT",
|
| 1030 |
+
"optional": true,
|
| 1031 |
+
"os": [
|
| 1032 |
+
"linux"
|
| 1033 |
+
],
|
| 1034 |
+
"engines": {
|
| 1035 |
+
"node": ">= 20"
|
| 1036 |
+
}
|
| 1037 |
+
},
|
| 1038 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
|
| 1039 |
+
"version": "4.2.4",
|
| 1040 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.4.tgz",
|
| 1041 |
+
"integrity": "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw==",
|
| 1042 |
+
"cpu": [
|
| 1043 |
+
"arm64"
|
| 1044 |
+
],
|
| 1045 |
+
"license": "MIT",
|
| 1046 |
+
"optional": true,
|
| 1047 |
+
"os": [
|
| 1048 |
+
"linux"
|
| 1049 |
+
],
|
| 1050 |
+
"engines": {
|
| 1051 |
+
"node": ">= 20"
|
| 1052 |
+
}
|
| 1053 |
+
},
|
| 1054 |
+
"node_modules/@tailwindcss/oxide-linux-arm64-musl": {
|
| 1055 |
+
"version": "4.2.4",
|
| 1056 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.4.tgz",
|
| 1057 |
+
"integrity": "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g==",
|
| 1058 |
+
"cpu": [
|
| 1059 |
+
"arm64"
|
| 1060 |
+
],
|
| 1061 |
+
"license": "MIT",
|
| 1062 |
+
"optional": true,
|
| 1063 |
+
"os": [
|
| 1064 |
+
"linux"
|
| 1065 |
+
],
|
| 1066 |
+
"engines": {
|
| 1067 |
+
"node": ">= 20"
|
| 1068 |
+
}
|
| 1069 |
+
},
|
| 1070 |
+
"node_modules/@tailwindcss/oxide-linux-x64-gnu": {
|
| 1071 |
+
"version": "4.2.4",
|
| 1072 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.4.tgz",
|
| 1073 |
+
"integrity": "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA==",
|
| 1074 |
+
"cpu": [
|
| 1075 |
+
"x64"
|
| 1076 |
+
],
|
| 1077 |
+
"license": "MIT",
|
| 1078 |
+
"optional": true,
|
| 1079 |
+
"os": [
|
| 1080 |
+
"linux"
|
| 1081 |
+
],
|
| 1082 |
+
"engines": {
|
| 1083 |
+
"node": ">= 20"
|
| 1084 |
+
}
|
| 1085 |
+
},
|
| 1086 |
+
"node_modules/@tailwindcss/oxide-linux-x64-musl": {
|
| 1087 |
+
"version": "4.2.4",
|
| 1088 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.4.tgz",
|
| 1089 |
+
"integrity": "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA==",
|
| 1090 |
+
"cpu": [
|
| 1091 |
+
"x64"
|
| 1092 |
+
],
|
| 1093 |
+
"license": "MIT",
|
| 1094 |
+
"optional": true,
|
| 1095 |
+
"os": [
|
| 1096 |
+
"linux"
|
| 1097 |
+
],
|
| 1098 |
+
"engines": {
|
| 1099 |
+
"node": ">= 20"
|
| 1100 |
+
}
|
| 1101 |
+
},
|
| 1102 |
+
"node_modules/@tailwindcss/oxide-wasm32-wasi": {
|
| 1103 |
+
"version": "4.2.4",
|
| 1104 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.4.tgz",
|
| 1105 |
+
"integrity": "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw==",
|
| 1106 |
+
"bundleDependencies": [
|
| 1107 |
+
"@napi-rs/wasm-runtime",
|
| 1108 |
+
"@emnapi/core",
|
| 1109 |
+
"@emnapi/runtime",
|
| 1110 |
+
"@tybys/wasm-util",
|
| 1111 |
+
"@emnapi/wasi-threads",
|
| 1112 |
+
"tslib"
|
| 1113 |
+
],
|
| 1114 |
+
"cpu": [
|
| 1115 |
+
"wasm32"
|
| 1116 |
+
],
|
| 1117 |
+
"license": "MIT",
|
| 1118 |
+
"optional": true,
|
| 1119 |
+
"dependencies": {
|
| 1120 |
+
"@emnapi/core": "^1.8.1",
|
| 1121 |
+
"@emnapi/runtime": "^1.8.1",
|
| 1122 |
+
"@emnapi/wasi-threads": "^1.1.0",
|
| 1123 |
+
"@napi-rs/wasm-runtime": "^1.1.1",
|
| 1124 |
+
"@tybys/wasm-util": "^0.10.1",
|
| 1125 |
+
"tslib": "^2.8.1"
|
| 1126 |
+
},
|
| 1127 |
+
"engines": {
|
| 1128 |
+
"node": ">=14.0.0"
|
| 1129 |
+
}
|
| 1130 |
+
},
|
| 1131 |
+
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
| 1132 |
+
"version": "4.2.4",
|
| 1133 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
| 1134 |
+
"integrity": "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ==",
|
| 1135 |
+
"cpu": [
|
| 1136 |
+
"arm64"
|
| 1137 |
+
],
|
| 1138 |
+
"license": "MIT",
|
| 1139 |
+
"optional": true,
|
| 1140 |
+
"os": [
|
| 1141 |
+
"win32"
|
| 1142 |
+
],
|
| 1143 |
+
"engines": {
|
| 1144 |
+
"node": ">= 20"
|
| 1145 |
+
}
|
| 1146 |
+
},
|
| 1147 |
+
"node_modules/@tailwindcss/oxide-win32-x64-msvc": {
|
| 1148 |
+
"version": "4.2.4",
|
| 1149 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.4.tgz",
|
| 1150 |
+
"integrity": "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw==",
|
| 1151 |
+
"cpu": [
|
| 1152 |
+
"x64"
|
| 1153 |
+
],
|
| 1154 |
+
"license": "MIT",
|
| 1155 |
+
"optional": true,
|
| 1156 |
+
"os": [
|
| 1157 |
+
"win32"
|
| 1158 |
+
],
|
| 1159 |
+
"engines": {
|
| 1160 |
+
"node": ">= 20"
|
| 1161 |
+
}
|
| 1162 |
+
},
|
| 1163 |
+
"node_modules/@tailwindcss/vite": {
|
| 1164 |
+
"version": "4.2.4",
|
| 1165 |
+
"resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.4.tgz",
|
| 1166 |
+
"integrity": "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw==",
|
| 1167 |
+
"license": "MIT",
|
| 1168 |
+
"dependencies": {
|
| 1169 |
+
"@tailwindcss/node": "4.2.4",
|
| 1170 |
+
"@tailwindcss/oxide": "4.2.4",
|
| 1171 |
+
"tailwindcss": "4.2.4"
|
| 1172 |
+
},
|
| 1173 |
+
"peerDependencies": {
|
| 1174 |
+
"vite": "^5.2.0 || ^6 || ^7 || ^8"
|
| 1175 |
+
}
|
| 1176 |
+
},
|
| 1177 |
"node_modules/@tybys/wasm-util": {
|
| 1178 |
"version": "0.10.1",
|
| 1179 |
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
| 1180 |
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
|
|
|
| 1181 |
"license": "MIT",
|
| 1182 |
"optional": true,
|
| 1183 |
"dependencies": {
|
|
|
|
| 1282 |
"@types/react": "^19.2.0"
|
| 1283 |
}
|
| 1284 |
},
|
| 1285 |
+
"node_modules/@types/trusted-types": {
|
| 1286 |
+
"version": "2.0.7",
|
| 1287 |
+
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
| 1288 |
+
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
| 1289 |
+
"license": "MIT",
|
| 1290 |
+
"optional": true
|
| 1291 |
+
},
|
| 1292 |
"node_modules/@types/use-sync-external-store": {
|
| 1293 |
"version": "0.0.6",
|
| 1294 |
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
|
|
|
|
| 1720 |
"version": "2.1.2",
|
| 1721 |
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
|
| 1722 |
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
|
|
|
|
| 1723 |
"license": "Apache-2.0",
|
| 1724 |
"engines": {
|
| 1725 |
"node": ">=8"
|
| 1726 |
}
|
| 1727 |
},
|
| 1728 |
+
"node_modules/dompurify": {
|
| 1729 |
+
"version": "3.2.7",
|
| 1730 |
+
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz",
|
| 1731 |
+
"integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==",
|
| 1732 |
+
"license": "(MPL-2.0 OR Apache-2.0)",
|
| 1733 |
+
"optionalDependencies": {
|
| 1734 |
+
"@types/trusted-types": "^2.0.7"
|
| 1735 |
+
}
|
| 1736 |
+
},
|
| 1737 |
"node_modules/electron-to-chromium": {
|
| 1738 |
"version": "1.5.343",
|
| 1739 |
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.343.tgz",
|
|
|
|
| 1741 |
"dev": true,
|
| 1742 |
"license": "ISC"
|
| 1743 |
},
|
| 1744 |
+
"node_modules/enhanced-resolve": {
|
| 1745 |
+
"version": "5.21.0",
|
| 1746 |
+
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz",
|
| 1747 |
+
"integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==",
|
| 1748 |
+
"license": "MIT",
|
| 1749 |
+
"dependencies": {
|
| 1750 |
+
"graceful-fs": "^4.2.4",
|
| 1751 |
+
"tapable": "^2.3.3"
|
| 1752 |
+
},
|
| 1753 |
+
"engines": {
|
| 1754 |
+
"node": ">=10.13.0"
|
| 1755 |
+
}
|
| 1756 |
+
},
|
| 1757 |
"node_modules/es-toolkit": {
|
| 1758 |
"version": "1.46.0",
|
| 1759 |
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.46.0.tgz",
|
|
|
|
| 2003 |
"version": "6.5.0",
|
| 2004 |
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
| 2005 |
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
|
|
|
|
| 2006 |
"license": "MIT",
|
| 2007 |
"engines": {
|
| 2008 |
"node": ">=12.0.0"
|
|
|
|
| 2067 |
"dev": true,
|
| 2068 |
"license": "ISC"
|
| 2069 |
},
|
| 2070 |
+
"node_modules/framer-motion": {
|
| 2071 |
+
"version": "12.38.0",
|
| 2072 |
+
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
|
| 2073 |
+
"integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
|
| 2074 |
+
"license": "MIT",
|
| 2075 |
+
"dependencies": {
|
| 2076 |
+
"motion-dom": "^12.38.0",
|
| 2077 |
+
"motion-utils": "^12.36.0",
|
| 2078 |
+
"tslib": "^2.4.0"
|
| 2079 |
+
},
|
| 2080 |
+
"peerDependencies": {
|
| 2081 |
+
"@emotion/is-prop-valid": "*",
|
| 2082 |
+
"react": "^18.0.0 || ^19.0.0",
|
| 2083 |
+
"react-dom": "^18.0.0 || ^19.0.0"
|
| 2084 |
+
},
|
| 2085 |
+
"peerDependenciesMeta": {
|
| 2086 |
+
"@emotion/is-prop-valid": {
|
| 2087 |
+
"optional": true
|
| 2088 |
+
},
|
| 2089 |
+
"react": {
|
| 2090 |
+
"optional": true
|
| 2091 |
+
},
|
| 2092 |
+
"react-dom": {
|
| 2093 |
+
"optional": true
|
| 2094 |
+
}
|
| 2095 |
+
}
|
| 2096 |
+
},
|
| 2097 |
"node_modules/fsevents": {
|
| 2098 |
"version": "2.3.3",
|
| 2099 |
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
| 2100 |
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
|
|
|
| 2101 |
"hasInstallScript": true,
|
| 2102 |
"license": "MIT",
|
| 2103 |
"optional": true,
|
|
|
|
| 2144 |
"url": "https://github.com/sponsors/sindresorhus"
|
| 2145 |
}
|
| 2146 |
},
|
| 2147 |
+
"node_modules/graceful-fs": {
|
| 2148 |
+
"version": "4.2.11",
|
| 2149 |
+
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
| 2150 |
+
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
| 2151 |
+
"license": "ISC"
|
| 2152 |
+
},
|
| 2153 |
"node_modules/has-flag": {
|
| 2154 |
"version": "4.0.0",
|
| 2155 |
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
|
|
|
|
| 2263 |
"dev": true,
|
| 2264 |
"license": "ISC"
|
| 2265 |
},
|
| 2266 |
+
"node_modules/jiti": {
|
| 2267 |
+
"version": "2.6.1",
|
| 2268 |
+
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
|
| 2269 |
+
"integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
|
| 2270 |
+
"license": "MIT",
|
| 2271 |
+
"bin": {
|
| 2272 |
+
"jiti": "lib/jiti-cli.mjs"
|
| 2273 |
+
}
|
| 2274 |
+
},
|
| 2275 |
"node_modules/js-tokens": {
|
| 2276 |
"version": "4.0.0",
|
| 2277 |
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
|
|
|
| 2367 |
"version": "1.32.0",
|
| 2368 |
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
|
| 2369 |
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
|
|
|
|
| 2370 |
"license": "MPL-2.0",
|
| 2371 |
"dependencies": {
|
| 2372 |
"detect-libc": "^2.0.3"
|
|
|
|
| 2399 |
"cpu": [
|
| 2400 |
"arm64"
|
| 2401 |
],
|
|
|
|
| 2402 |
"license": "MPL-2.0",
|
| 2403 |
"optional": true,
|
| 2404 |
"os": [
|
|
|
|
| 2419 |
"cpu": [
|
| 2420 |
"arm64"
|
| 2421 |
],
|
|
|
|
| 2422 |
"license": "MPL-2.0",
|
| 2423 |
"optional": true,
|
| 2424 |
"os": [
|
|
|
|
| 2439 |
"cpu": [
|
| 2440 |
"x64"
|
| 2441 |
],
|
|
|
|
| 2442 |
"license": "MPL-2.0",
|
| 2443 |
"optional": true,
|
| 2444 |
"os": [
|
|
|
|
| 2459 |
"cpu": [
|
| 2460 |
"x64"
|
| 2461 |
],
|
|
|
|
| 2462 |
"license": "MPL-2.0",
|
| 2463 |
"optional": true,
|
| 2464 |
"os": [
|
|
|
|
| 2479 |
"cpu": [
|
| 2480 |
"arm"
|
| 2481 |
],
|
|
|
|
| 2482 |
"license": "MPL-2.0",
|
| 2483 |
"optional": true,
|
| 2484 |
"os": [
|
|
|
|
| 2499 |
"cpu": [
|
| 2500 |
"arm64"
|
| 2501 |
],
|
|
|
|
| 2502 |
"license": "MPL-2.0",
|
| 2503 |
"optional": true,
|
| 2504 |
"os": [
|
|
|
|
| 2519 |
"cpu": [
|
| 2520 |
"arm64"
|
| 2521 |
],
|
|
|
|
| 2522 |
"license": "MPL-2.0",
|
| 2523 |
"optional": true,
|
| 2524 |
"os": [
|
|
|
|
| 2539 |
"cpu": [
|
| 2540 |
"x64"
|
| 2541 |
],
|
|
|
|
| 2542 |
"license": "MPL-2.0",
|
| 2543 |
"optional": true,
|
| 2544 |
"os": [
|
|
|
|
| 2559 |
"cpu": [
|
| 2560 |
"x64"
|
| 2561 |
],
|
|
|
|
| 2562 |
"license": "MPL-2.0",
|
| 2563 |
"optional": true,
|
| 2564 |
"os": [
|
|
|
|
| 2579 |
"cpu": [
|
| 2580 |
"arm64"
|
| 2581 |
],
|
|
|
|
| 2582 |
"license": "MPL-2.0",
|
| 2583 |
"optional": true,
|
| 2584 |
"os": [
|
|
|
|
| 2599 |
"cpu": [
|
| 2600 |
"x64"
|
| 2601 |
],
|
|
|
|
| 2602 |
"license": "MPL-2.0",
|
| 2603 |
"optional": true,
|
| 2604 |
"os": [
|
|
|
|
| 2645 |
"yallist": "^3.0.2"
|
| 2646 |
}
|
| 2647 |
},
|
| 2648 |
+
"node_modules/lucide-react": {
|
| 2649 |
+
"version": "1.11.0",
|
| 2650 |
+
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.11.0.tgz",
|
| 2651 |
+
"integrity": "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g==",
|
| 2652 |
+
"license": "ISC",
|
| 2653 |
+
"peerDependencies": {
|
| 2654 |
+
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
| 2655 |
+
}
|
| 2656 |
+
},
|
| 2657 |
+
"node_modules/magic-string": {
|
| 2658 |
+
"version": "0.30.21",
|
| 2659 |
+
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
|
| 2660 |
+
"integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
|
| 2661 |
+
"license": "MIT",
|
| 2662 |
+
"dependencies": {
|
| 2663 |
+
"@jridgewell/sourcemap-codec": "^1.5.5"
|
| 2664 |
+
}
|
| 2665 |
+
},
|
| 2666 |
+
"node_modules/marked": {
|
| 2667 |
+
"version": "14.0.0",
|
| 2668 |
+
"resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz",
|
| 2669 |
+
"integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==",
|
| 2670 |
+
"license": "MIT",
|
| 2671 |
+
"bin": {
|
| 2672 |
+
"marked": "bin/marked.js"
|
| 2673 |
+
},
|
| 2674 |
+
"engines": {
|
| 2675 |
+
"node": ">= 18"
|
| 2676 |
+
}
|
| 2677 |
+
},
|
| 2678 |
"node_modules/minimatch": {
|
| 2679 |
"version": "3.1.5",
|
| 2680 |
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
|
|
|
|
| 2688 |
"node": "*"
|
| 2689 |
}
|
| 2690 |
},
|
| 2691 |
+
"node_modules/monaco-editor": {
|
| 2692 |
+
"version": "0.55.1",
|
| 2693 |
+
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.55.1.tgz",
|
| 2694 |
+
"integrity": "sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==",
|
| 2695 |
+
"license": "MIT",
|
| 2696 |
+
"peer": true,
|
| 2697 |
+
"dependencies": {
|
| 2698 |
+
"dompurify": "3.2.7",
|
| 2699 |
+
"marked": "14.0.0"
|
| 2700 |
+
}
|
| 2701 |
+
},
|
| 2702 |
+
"node_modules/motion-dom": {
|
| 2703 |
+
"version": "12.38.0",
|
| 2704 |
+
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
|
| 2705 |
+
"integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
|
| 2706 |
+
"license": "MIT",
|
| 2707 |
+
"dependencies": {
|
| 2708 |
+
"motion-utils": "^12.36.0"
|
| 2709 |
+
}
|
| 2710 |
+
},
|
| 2711 |
+
"node_modules/motion-utils": {
|
| 2712 |
+
"version": "12.36.0",
|
| 2713 |
+
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
|
| 2714 |
+
"integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
|
| 2715 |
+
"license": "MIT"
|
| 2716 |
+
},
|
| 2717 |
"node_modules/ms": {
|
| 2718 |
"version": "2.1.3",
|
| 2719 |
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
|
|
|
| 2725 |
"version": "3.3.11",
|
| 2726 |
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
| 2727 |
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
|
|
|
| 2728 |
"funding": [
|
| 2729 |
{
|
| 2730 |
"type": "github",
|
|
|
|
| 2840 |
"version": "1.1.1",
|
| 2841 |
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
| 2842 |
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
|
|
|
| 2843 |
"license": "ISC"
|
| 2844 |
},
|
| 2845 |
"node_modules/picomatch": {
|
| 2846 |
"version": "4.0.4",
|
| 2847 |
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
|
| 2848 |
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
|
|
|
|
| 2849 |
"license": "MIT",
|
| 2850 |
"peer": true,
|
| 2851 |
"engines": {
|
|
|
|
| 2859 |
"version": "8.5.10",
|
| 2860 |
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz",
|
| 2861 |
"integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==",
|
|
|
|
| 2862 |
"funding": [
|
| 2863 |
{
|
| 2864 |
"type": "opencollective",
|
|
|
|
| 3023 |
"version": "1.0.0-rc.16",
|
| 3024 |
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.16.tgz",
|
| 3025 |
"integrity": "sha512-rzi5WqKzEZw3SooTt7cgm4eqIoujPIyGcJNGFL7iPEuajQw7vxMHUkXylu4/vhCkJGXsgRmxqMKXUpT6FEgl0g==",
|
|
|
|
| 3026 |
"license": "MIT",
|
| 3027 |
"dependencies": {
|
| 3028 |
"@oxc-project/types": "=0.126.0",
|
|
|
|
| 3056 |
"version": "1.0.0-rc.16",
|
| 3057 |
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.16.tgz",
|
| 3058 |
"integrity": "sha512-45+YtqxLYKDWQouLKCrpIZhke+nXxhsw+qAHVzHDVwttyBlHNBVs2K25rDXrZzhpTp9w1FlAlvweV1H++fdZoA==",
|
|
|
|
| 3059 |
"license": "MIT"
|
| 3060 |
},
|
| 3061 |
"node_modules/scheduler": {
|
|
|
|
| 3101 |
"version": "1.2.1",
|
| 3102 |
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
| 3103 |
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
|
|
|
| 3104 |
"license": "BSD-3-Clause",
|
| 3105 |
"engines": {
|
| 3106 |
"node": ">=0.10.0"
|
| 3107 |
}
|
| 3108 |
},
|
| 3109 |
+
"node_modules/state-local": {
|
| 3110 |
+
"version": "1.0.7",
|
| 3111 |
+
"resolved": "https://registry.npmjs.org/state-local/-/state-local-1.0.7.tgz",
|
| 3112 |
+
"integrity": "sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==",
|
| 3113 |
+
"license": "MIT"
|
| 3114 |
+
},
|
| 3115 |
"node_modules/strip-json-comments": {
|
| 3116 |
"version": "3.1.1",
|
| 3117 |
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
|
|
|
|
| 3138 |
"node": ">=8"
|
| 3139 |
}
|
| 3140 |
},
|
| 3141 |
+
"node_modules/tailwindcss": {
|
| 3142 |
+
"version": "4.2.4",
|
| 3143 |
+
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.4.tgz",
|
| 3144 |
+
"integrity": "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA==",
|
| 3145 |
+
"license": "MIT"
|
| 3146 |
+
},
|
| 3147 |
+
"node_modules/tapable": {
|
| 3148 |
+
"version": "2.3.3",
|
| 3149 |
+
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
| 3150 |
+
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
| 3151 |
+
"license": "MIT",
|
| 3152 |
+
"engines": {
|
| 3153 |
+
"node": ">=6"
|
| 3154 |
+
},
|
| 3155 |
+
"funding": {
|
| 3156 |
+
"type": "opencollective",
|
| 3157 |
+
"url": "https://opencollective.com/webpack"
|
| 3158 |
+
}
|
| 3159 |
+
},
|
| 3160 |
"node_modules/tiny-invariant": {
|
| 3161 |
"version": "1.3.3",
|
| 3162 |
"resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
|
|
|
|
| 3167 |
"version": "0.2.16",
|
| 3168 |
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
|
| 3169 |
"integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
|
|
|
|
| 3170 |
"license": "MIT",
|
| 3171 |
"dependencies": {
|
| 3172 |
"fdir": "^6.5.0",
|
|
|
|
| 3183 |
"version": "2.8.1",
|
| 3184 |
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
| 3185 |
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
| 3186 |
+
"license": "0BSD"
|
|
|
|
|
|
|
| 3187 |
},
|
| 3188 |
"node_modules/type-check": {
|
| 3189 |
"version": "0.4.0",
|
|
|
|
| 3274 |
"version": "8.0.9",
|
| 3275 |
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.9.tgz",
|
| 3276 |
"integrity": "sha512-t7g7GVRpMXjNpa67HaVWI/8BWtdVIQPCL2WoozXXA7LBGEFK4AkkKkHx2hAQf5x1GZSlcmEDPkVLSGahxnEEZw==",
|
|
|
|
| 3277 |
"license": "MIT",
|
| 3278 |
"peer": true,
|
| 3279 |
"dependencies": {
|
frontend/package.json
CHANGED
|
@@ -10,9 +10,15 @@
|
|
| 10 |
"preview": "vite preview"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
"react": "^19.2.5",
|
| 14 |
"react-dom": "^19.2.5",
|
| 15 |
-
"recharts": "^3.8.1"
|
|
|
|
| 16 |
},
|
| 17 |
"devDependencies": {
|
| 18 |
"@eslint/js": "^9.39.4",
|
|
|
|
| 10 |
"preview": "vite preview"
|
| 11 |
},
|
| 12 |
"dependencies": {
|
| 13 |
+
"@monaco-editor/react": "^4.7.0",
|
| 14 |
+
"@tailwindcss/vite": "^4.2.4",
|
| 15 |
+
"clsx": "^2.1.1",
|
| 16 |
+
"framer-motion": "^12.38.0",
|
| 17 |
+
"lucide-react": "^1.11.0",
|
| 18 |
"react": "^19.2.5",
|
| 19 |
"react-dom": "^19.2.5",
|
| 20 |
+
"recharts": "^3.8.1",
|
| 21 |
+
"tailwindcss": "^4.2.4"
|
| 22 |
},
|
| 23 |
"devDependencies": {
|
| 24 |
"@eslint/js": "^9.39.4",
|
frontend/src/App.jsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
-
import
|
|
|
|
| 2 |
export default function App() {
|
| 3 |
-
return <
|
| 4 |
}
|
|
|
|
| 1 |
+
import Dashboard from './pages/Dashboard';
|
| 2 |
+
|
| 3 |
export default function App() {
|
| 4 |
+
return <Dashboard />;
|
| 5 |
}
|
frontend/src/CodeArenaRL.jsx
CHANGED
|
@@ -185,13 +185,21 @@ function AnsiLine({ text }) {
|
|
| 185 |
REWARD CHART (Recharts)
|
| 186 |
───────────────────────────────────────────── */
|
| 187 |
function RewardChart({ rewards }) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 188 |
const data = rewards.map((r, i) => ({ step: i + 1, reward: r }));
|
| 189 |
for (let i = data.length + 1; i <= 5; i++) {
|
| 190 |
data.push({ step: i, reward: null });
|
| 191 |
}
|
|
|
|
|
|
|
|
|
|
| 192 |
return (
|
| 193 |
-
<div style={{ width: "100%",
|
| 194 |
-
<ResponsiveContainer width="100%" height=
|
| 195 |
<LineChart data={data} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
| 196 |
<XAxis dataKey="step" stroke="#334155" tick={{ fill: "#334155", fontSize: 10, fontFamily: "'JetBrains Mono',monospace" }} />
|
| 197 |
<YAxis domain={[0, 1]} ticks={[0, 0.5, 1]} stroke="#334155" tick={{ fill: "#334155", fontSize: 10, fontFamily: "'JetBrains Mono',monospace" }} />
|
|
@@ -211,7 +219,7 @@ function RewardChart({ rewards }) {
|
|
| 211 |
export default function CodeArenaRL() {
|
| 212 |
/* ── Ollama config ── */
|
| 213 |
const [ollamaUrl, setOllamaUrl] = useState("http://localhost:11434");
|
| 214 |
-
const [ollamaModel, setOllamaModel] = useState("
|
| 215 |
const [availableModels, setAvailableModels] = useState([]);
|
| 216 |
const [ollamaStatus, setOllamaStatus] = useState("checking"); // checking | online | offline
|
| 217 |
|
|
@@ -266,14 +274,14 @@ export default function CodeArenaRL() {
|
|
| 266 |
if (res.ok) {
|
| 267 |
const data = await res.json();
|
| 268 |
const names = (data.models || []).map(m => m.name);
|
| 269 |
-
setAvailableModels(names.length > 0 ? names : ["
|
| 270 |
setOllamaStatus("online");
|
| 271 |
} else {
|
| 272 |
setOllamaStatus("offline");
|
| 273 |
}
|
| 274 |
} catch {
|
| 275 |
setOllamaStatus("offline");
|
| 276 |
-
setAvailableModels(["
|
| 277 |
}
|
| 278 |
}, [ollamaUrl]);
|
| 279 |
|
|
@@ -349,27 +357,62 @@ export default function CodeArenaRL() {
|
|
| 349 |
setTokenEst(Math.ceil(prompt.length / 4));
|
| 350 |
|
| 351 |
const baseUrl = ollamaUrl.replace(/\/+$/, "");
|
| 352 |
-
const
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
}
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 367 |
|
| 368 |
-
const
|
| 369 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
-
|
| 372 |
-
code =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
return code;
|
| 374 |
}, [ollamaUrl, ollamaModel, task]);
|
| 375 |
|
|
@@ -712,7 +755,7 @@ export default function CodeArenaRL() {
|
|
| 712 |
{availableModels.map(m => <option key={m} value={m}>{m}</option>)}
|
| 713 |
</select>
|
| 714 |
) : (
|
| 715 |
-
<input className="cfg-input" value={ollamaModel} onChange={e => setOllamaModel(e.target.value)} placeholder="
|
| 716 |
)}
|
| 717 |
</div>
|
| 718 |
<div style={{ display: "flex", gap: 6 }}>
|
|
@@ -724,7 +767,7 @@ export default function CodeArenaRL() {
|
|
| 724 |
<div style={{ fontSize: 10, color: "#ffaa00", fontFamily: "'JetBrains Mono',monospace", background: "rgba(255,170,0,0.08)", border: "1px solid rgba(255,170,0,0.2)", borderRadius: 4, padding: "6px 8px" }}>
|
| 725 |
💡 Run: <strong>ollama serve</strong><br />
|
| 726 |
Then pull a model:<br />
|
| 727 |
-
<strong>ollama pull
|
| 728 |
</div>
|
| 729 |
)}
|
| 730 |
</div>
|
|
|
|
| 185 |
REWARD CHART (Recharts)
|
| 186 |
───────────────────────────────────────────── */
|
| 187 |
function RewardChart({ rewards }) {
|
| 188 |
+
const [chartReady, setChartReady] = useState(false);
|
| 189 |
+
useEffect(() => {
|
| 190 |
+
setChartReady(true);
|
| 191 |
+
}, []);
|
| 192 |
+
|
| 193 |
const data = rewards.map((r, i) => ({ step: i + 1, reward: r }));
|
| 194 |
for (let i = data.length + 1; i <= 5; i++) {
|
| 195 |
data.push({ step: i, reward: null });
|
| 196 |
}
|
| 197 |
+
if (!chartReady) {
|
| 198 |
+
return <div style={{ width: "100%", minHeight: 120, minWidth: 0 }} />;
|
| 199 |
+
}
|
| 200 |
return (
|
| 201 |
+
<div style={{ width: "100%", minHeight: 120, minWidth: 0 }}>
|
| 202 |
+
<ResponsiveContainer width="100%" height={120} minHeight={120} minWidth={120}>
|
| 203 |
<LineChart data={data} margin={{ top: 10, right: 10, left: -20, bottom: 0 }}>
|
| 204 |
<XAxis dataKey="step" stroke="#334155" tick={{ fill: "#334155", fontSize: 10, fontFamily: "'JetBrains Mono',monospace" }} />
|
| 205 |
<YAxis domain={[0, 1]} ticks={[0, 0.5, 1]} stroke="#334155" tick={{ fill: "#334155", fontSize: 10, fontFamily: "'JetBrains Mono',monospace" }} />
|
|
|
|
| 219 |
export default function CodeArenaRL() {
|
| 220 |
/* ── Ollama config ── */
|
| 221 |
const [ollamaUrl, setOllamaUrl] = useState("http://localhost:11434");
|
| 222 |
+
const [ollamaModel, setOllamaModel] = useState("llama3.2:latest");
|
| 223 |
const [availableModels, setAvailableModels] = useState([]);
|
| 224 |
const [ollamaStatus, setOllamaStatus] = useState("checking"); // checking | online | offline
|
| 225 |
|
|
|
|
| 274 |
if (res.ok) {
|
| 275 |
const data = await res.json();
|
| 276 |
const names = (data.models || []).map(m => m.name);
|
| 277 |
+
setAvailableModels(names.length > 0 ? names : ["llama3.2:latest", "gemma3:1b", "gemma3:4b", "llava:latest"]);
|
| 278 |
setOllamaStatus("online");
|
| 279 |
} else {
|
| 280 |
setOllamaStatus("offline");
|
| 281 |
}
|
| 282 |
} catch {
|
| 283 |
setOllamaStatus("offline");
|
| 284 |
+
setAvailableModels(["llama3.2:latest", "gemma3:1b", "gemma3:4b", "llava:latest"]);
|
| 285 |
}
|
| 286 |
}, [ollamaUrl]);
|
| 287 |
|
|
|
|
| 357 |
setTokenEst(Math.ceil(prompt.length / 4));
|
| 358 |
|
| 359 |
const baseUrl = ollamaUrl.replace(/\/+$/, "");
|
| 360 |
+
const cleanCode = (text) =>
|
| 361 |
+
(text || "")
|
| 362 |
+
.trim()
|
| 363 |
+
.replace(/^```(?:python)?\n?/gm, "")
|
| 364 |
+
.replace(/```\s*$/gm, "")
|
| 365 |
+
.trim();
|
| 366 |
+
|
| 367 |
+
const tryGenerate = async () => {
|
| 368 |
+
const res = await fetch(`${baseUrl}/api/generate`, {
|
| 369 |
+
method: "POST",
|
| 370 |
+
headers: { "Content-Type": "application/json" },
|
| 371 |
+
body: JSON.stringify({
|
| 372 |
+
model: ollamaModel,
|
| 373 |
+
prompt,
|
| 374 |
+
stream: false,
|
| 375 |
+
options: { temperature: 0.2, num_predict: 512 },
|
| 376 |
+
}),
|
| 377 |
+
});
|
| 378 |
+
if (!res.ok) {
|
| 379 |
+
if (res.status === 404 || res.status === 405) return null;
|
| 380 |
+
const errText = await res.text();
|
| 381 |
+
throw new Error(`Ollama error ${res.status}: ${errText}`);
|
| 382 |
+
}
|
| 383 |
+
const data = await res.json();
|
| 384 |
+
return cleanCode(data.response || data.text || "");
|
| 385 |
+
};
|
| 386 |
|
| 387 |
+
const tryChat = async () => {
|
| 388 |
+
const res = await fetch(`${baseUrl}/api/chat`, {
|
| 389 |
+
method: "POST",
|
| 390 |
+
headers: { "Content-Type": "application/json" },
|
| 391 |
+
body: JSON.stringify({
|
| 392 |
+
model: ollamaModel,
|
| 393 |
+
messages: [
|
| 394 |
+
{ role: "system", content: "You are an expert Python debugging agent. Return ONLY the fixed Python code — no explanation, no markdown, no code fences." },
|
| 395 |
+
{ role: "user", content: prompt },
|
| 396 |
+
],
|
| 397 |
+
stream: false,
|
| 398 |
+
options: { temperature: 0.2, max_tokens: 1024, top_p: 0.9 },
|
| 399 |
+
}),
|
| 400 |
+
});
|
| 401 |
+
if (!res.ok) {
|
| 402 |
+
const errText = await res.text();
|
| 403 |
+
throw new Error(`Ollama chat error ${res.status}: ${errText}`);
|
| 404 |
+
}
|
| 405 |
+
const data = await res.json();
|
| 406 |
+
return cleanCode(data.response || data.text || data.message?.content || "");
|
| 407 |
+
};
|
| 408 |
|
| 409 |
+
let code = await tryGenerate();
|
| 410 |
+
if (code === null || !code) {
|
| 411 |
+
code = await tryChat();
|
| 412 |
+
}
|
| 413 |
+
if (!code) {
|
| 414 |
+
throw new Error("Ollama returned no valid code from /api/generate or /api/chat.");
|
| 415 |
+
}
|
| 416 |
return code;
|
| 417 |
}, [ollamaUrl, ollamaModel, task]);
|
| 418 |
|
|
|
|
| 755 |
{availableModels.map(m => <option key={m} value={m}>{m}</option>)}
|
| 756 |
</select>
|
| 757 |
) : (
|
| 758 |
+
<input className="cfg-input" value={ollamaModel} onChange={e => setOllamaModel(e.target.value)} placeholder="llama3.2:latest" />
|
| 759 |
)}
|
| 760 |
</div>
|
| 761 |
<div style={{ display: "flex", gap: 6 }}>
|
|
|
|
| 767 |
<div style={{ fontSize: 10, color: "#ffaa00", fontFamily: "'JetBrains Mono',monospace", background: "rgba(255,170,0,0.08)", border: "1px solid rgba(255,170,0,0.2)", borderRadius: 4, padding: "6px 8px" }}>
|
| 768 |
💡 Run: <strong>ollama serve</strong><br />
|
| 769 |
Then pull a model:<br />
|
| 770 |
+
<strong>ollama pull llama3.2:latest</strong>
|
| 771 |
</div>
|
| 772 |
)}
|
| 773 |
</div>
|
frontend/src/components/CodeEditor.jsx
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRef, useEffect } from 'react';
|
| 2 |
+
import Editor from '@monaco-editor/react';
|
| 3 |
+
import { motion } from 'framer-motion';
|
| 4 |
+
import { Code2, Loader2, Send } from 'lucide-react';
|
| 5 |
+
import clsx from 'clsx';
|
| 6 |
+
|
| 7 |
+
export default function CodeEditor({
|
| 8 |
+
code, onCodeChange,
|
| 9 |
+
onRunStep, isRunning, isThinking,
|
| 10 |
+
stepCount, isDone,
|
| 11 |
+
}) {
|
| 12 |
+
const editorRef = useRef(null);
|
| 13 |
+
|
| 14 |
+
function handleMount(editor) {
|
| 15 |
+
editorRef.current = editor;
|
| 16 |
+
editor.updateOptions({
|
| 17 |
+
fontSize: 13,
|
| 18 |
+
lineHeight: 22,
|
| 19 |
+
minimap: { enabled: false },
|
| 20 |
+
scrollBeyondLastLine: false,
|
| 21 |
+
renderLineHighlight: 'gutter',
|
| 22 |
+
padding: { top: 12, bottom: 12 },
|
| 23 |
+
fontFamily: "'JetBrains Mono', 'Fira Code', monospace",
|
| 24 |
+
fontLigatures: true,
|
| 25 |
+
cursorBlinking: 'smooth',
|
| 26 |
+
smoothScrolling: true,
|
| 27 |
+
bracketPairColorization: { enabled: true },
|
| 28 |
+
});
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
// Auto-resize height based on content
|
| 32 |
+
useEffect(() => {
|
| 33 |
+
if (editorRef.current) {
|
| 34 |
+
editorRef.current.layout();
|
| 35 |
+
}
|
| 36 |
+
}, [code]);
|
| 37 |
+
|
| 38 |
+
return (
|
| 39 |
+
<div className="glass-card flex flex-col overflow-hidden h-full">
|
| 40 |
+
{/* ── Header ─────────────── */}
|
| 41 |
+
<div className="flex items-center justify-between px-4 py-2.5 border-b border-[var(--border-subtle)] bg-[#0D1117]/60">
|
| 42 |
+
<div className="flex items-center gap-2">
|
| 43 |
+
<Code2 size={14} className="text-emerald-400" />
|
| 44 |
+
<span className="text-[10px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 45 |
+
Code Editor
|
| 46 |
+
</span>
|
| 47 |
+
{stepCount > 0 && (
|
| 48 |
+
<span className="text-[9px] font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] px-2 py-0.5 rounded">
|
| 49 |
+
Step {stepCount}/5
|
| 50 |
+
</span>
|
| 51 |
+
)}
|
| 52 |
+
</div>
|
| 53 |
+
|
| 54 |
+
<div className="flex items-center gap-2">
|
| 55 |
+
{isThinking && (
|
| 56 |
+
<motion.div
|
| 57 |
+
initial={{ opacity: 0, x: 10 }}
|
| 58 |
+
animate={{ opacity: 1, x: 0 }}
|
| 59 |
+
className="flex items-center gap-1.5 text-[10px] text-amber-400 font-mono"
|
| 60 |
+
>
|
| 61 |
+
<Loader2 size={12} className="animate-spin" />
|
| 62 |
+
Thinking…
|
| 63 |
+
</motion.div>
|
| 64 |
+
)}
|
| 65 |
+
|
| 66 |
+
<motion.button
|
| 67 |
+
whileHover={{ scale: 1.05 }}
|
| 68 |
+
whileTap={{ scale: 0.95 }}
|
| 69 |
+
disabled={isRunning || isDone || !code?.trim()}
|
| 70 |
+
onClick={onRunStep}
|
| 71 |
+
className={clsx(
|
| 72 |
+
'flex items-center gap-1.5 px-3.5 py-1.5 rounded-lg text-[11px] font-bold tracking-wide',
|
| 73 |
+
'transition-all duration-200 cursor-pointer',
|
| 74 |
+
'disabled:opacity-30 disabled:cursor-not-allowed',
|
| 75 |
+
isDone
|
| 76 |
+
? 'bg-[var(--bg-elevated)] text-[var(--text-muted)]'
|
| 77 |
+
: 'bg-gradient-to-r from-emerald-500 to-emerald-600 text-black hover:shadow-[0_0_16px_rgba(0,255,136,0.3)]'
|
| 78 |
+
)}
|
| 79 |
+
>
|
| 80 |
+
{isRunning ? <Loader2 size={12} className="animate-spin" /> : <Send size={12} />}
|
| 81 |
+
{isDone ? 'DONE' : 'RUN STEP'}
|
| 82 |
+
</motion.button>
|
| 83 |
+
</div>
|
| 84 |
+
</div>
|
| 85 |
+
|
| 86 |
+
{/* ── Monaco Editor ──────── */}
|
| 87 |
+
<div className="flex-1 min-h-0">
|
| 88 |
+
<Editor
|
| 89 |
+
height="100%"
|
| 90 |
+
language="python"
|
| 91 |
+
theme="vs-dark"
|
| 92 |
+
value={code}
|
| 93 |
+
onChange={(val) => onCodeChange(val || '')}
|
| 94 |
+
onMount={handleMount}
|
| 95 |
+
loading={
|
| 96 |
+
<div className="flex items-center justify-center h-full gap-2 text-[var(--text-muted)] text-xs">
|
| 97 |
+
<Loader2 size={14} className="animate-spin" /> Loading editor…
|
| 98 |
+
</div>
|
| 99 |
+
}
|
| 100 |
+
options={{
|
| 101 |
+
readOnly: isRunning,
|
| 102 |
+
}}
|
| 103 |
+
/>
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
);
|
| 107 |
+
}
|
frontend/src/components/RewardPanel.jsx
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { motion } from 'framer-motion';
|
| 2 |
+
import {
|
| 3 |
+
LineChart, Line, XAxis, YAxis, Tooltip,
|
| 4 |
+
ResponsiveContainer, ReferenceLine, Area, AreaChart
|
| 5 |
+
} from 'recharts';
|
| 6 |
+
import {
|
| 7 |
+
Trophy, TrendingUp, Clock, Sparkles,
|
| 8 |
+
CheckCircle2, XCircle, MessageSquareText, BarChart3
|
| 9 |
+
} from 'lucide-react';
|
| 10 |
+
import clsx from 'clsx';
|
| 11 |
+
|
| 12 |
+
function rewardColor(r) {
|
| 13 |
+
if (r >= 0.75) return '#00FF88';
|
| 14 |
+
if (r >= 0.45) return '#FFAA00';
|
| 15 |
+
return '#FF4455';
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
function StatCard({ icon: Icon, label, value, color, subtitle }) {
|
| 19 |
+
return (
|
| 20 |
+
<div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3">
|
| 21 |
+
<div className="flex items-center gap-2 mb-1.5">
|
| 22 |
+
<Icon size={12} className={color || 'text-[var(--text-muted)]'} />
|
| 23 |
+
<span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">{label}</span>
|
| 24 |
+
</div>
|
| 25 |
+
<div className="text-xl font-bold font-mono" style={{ color: color ? undefined : 'var(--text-primary)' }}>
|
| 26 |
+
<span className={color}>{value}</span>
|
| 27 |
+
</div>
|
| 28 |
+
{subtitle && <p className="text-[9px] text-[var(--text-muted)] mt-0.5">{subtitle}</p>}
|
| 29 |
+
</div>
|
| 30 |
+
);
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
function RewardChart({ rewards }) {
|
| 34 |
+
const data = rewards.map((r, i) => ({ step: i + 1, reward: r }));
|
| 35 |
+
// Pad to 5 steps for consistent chart
|
| 36 |
+
for (let i = data.length + 1; i <= 5; i++) {
|
| 37 |
+
data.push({ step: i, reward: null });
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
return (
|
| 41 |
+
<div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3">
|
| 42 |
+
<div className="flex items-center gap-2 mb-2">
|
| 43 |
+
<BarChart3 size={12} className="text-emerald-400" />
|
| 44 |
+
<span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 45 |
+
Reward Curve
|
| 46 |
+
</span>
|
| 47 |
+
</div>
|
| 48 |
+
<div className="h-[100px]">
|
| 49 |
+
<ResponsiveContainer width="100%" height="100%">
|
| 50 |
+
<AreaChart data={data} margin={{ top: 5, right: 5, left: -25, bottom: 0 }}>
|
| 51 |
+
<defs>
|
| 52 |
+
<linearGradient id="rewardGrad" x1="0" y1="0" x2="0" y2="1">
|
| 53 |
+
<stop offset="0%" stopColor="#00FF88" stopOpacity={0.3} />
|
| 54 |
+
<stop offset="100%" stopColor="#00FF88" stopOpacity={0} />
|
| 55 |
+
</linearGradient>
|
| 56 |
+
</defs>
|
| 57 |
+
<XAxis dataKey="step" stroke="#1E293B" tick={{ fill: '#334155', fontSize: 9, fontFamily: 'monospace' }} />
|
| 58 |
+
<YAxis domain={[0, 1]} ticks={[0, 0.5, 1]} stroke="#1E293B" tick={{ fill: '#334155', fontSize: 9, fontFamily: 'monospace' }} />
|
| 59 |
+
<ReferenceLine y={0.5} stroke="#1E293B" strokeDasharray="4 4" />
|
| 60 |
+
<Tooltip
|
| 61 |
+
contentStyle={{
|
| 62 |
+
backgroundColor: '#0F172A', border: '1px solid #1E293B',
|
| 63 |
+
borderRadius: 8, fontFamily: 'monospace', fontSize: 10,
|
| 64 |
+
}}
|
| 65 |
+
itemStyle={{ color: '#00FF88' }}
|
| 66 |
+
formatter={(val) => val !== null ? val.toFixed(3) : '—'}
|
| 67 |
+
/>
|
| 68 |
+
<Area type="monotone" dataKey="reward" stroke="#00FF88" strokeWidth={2}
|
| 69 |
+
fill="url(#rewardGrad)" dot={{ fill: '#0B0F19', stroke: '#00FF88', strokeWidth: 2, r: 3 }}
|
| 70 |
+
connectNulls={false} isAnimationActive />
|
| 71 |
+
</AreaChart>
|
| 72 |
+
</ResponsiveContainer>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
export default function RewardPanel({
|
| 79 |
+
rewards, stepCount, isDone,
|
| 80 |
+
rewardComponents, feedback,
|
| 81 |
+
attempts,
|
| 82 |
+
}) {
|
| 83 |
+
const latestReward = rewards.length > 0 ? rewards[rewards.length - 1] : null;
|
| 84 |
+
const avgReward = rewards.length > 0 ? rewards.reduce((a, b) => a + b, 0) / rewards.length : 0;
|
| 85 |
+
const success = latestReward !== null && latestReward >= 0.85;
|
| 86 |
+
|
| 87 |
+
return (
|
| 88 |
+
<aside className="flex flex-col h-full border-l border-[var(--border-subtle)] bg-[var(--bg-secondary)] overflow-y-auto">
|
| 89 |
+
|
| 90 |
+
{/* ── Reward Hero ───────────── */}
|
| 91 |
+
<div className={clsx(
|
| 92 |
+
'px-4 py-5 border-b border-[var(--border-subtle)] text-center',
|
| 93 |
+
isDone && success && 'animate-pulse-glow'
|
| 94 |
+
)}>
|
| 95 |
+
<p className="text-[9px] font-bold tracking-[0.15em] uppercase text-[var(--text-muted)] mb-1">
|
| 96 |
+
{isDone ? (success ? '✦ Episode Complete' : 'Episode Finished') : 'Current Reward'}
|
| 97 |
+
</p>
|
| 98 |
+
<motion.div
|
| 99 |
+
key={latestReward}
|
| 100 |
+
initial={{ scale: 0.8, opacity: 0 }}
|
| 101 |
+
animate={{ scale: 1, opacity: 1 }}
|
| 102 |
+
transition={{ type: 'spring', stiffness: 200 }}
|
| 103 |
+
className="text-4xl font-bold font-mono"
|
| 104 |
+
style={{ color: latestReward !== null ? rewardColor(latestReward) : 'var(--text-muted)' }}
|
| 105 |
+
>
|
| 106 |
+
{latestReward !== null ? latestReward.toFixed(3) : '—'}
|
| 107 |
+
</motion.div>
|
| 108 |
+
{isDone && (
|
| 109 |
+
<motion.div
|
| 110 |
+
initial={{ opacity: 0, y: 6 }}
|
| 111 |
+
animate={{ opacity: 1, y: 0 }}
|
| 112 |
+
className="mt-2 flex items-center justify-center gap-1.5 text-xs font-medium"
|
| 113 |
+
>
|
| 114 |
+
{success
|
| 115 |
+
? <><CheckCircle2 size={14} className="text-emerald-400" /> <span className="text-emerald-400">All tests passed!</span></>
|
| 116 |
+
: <><XCircle size={14} className="text-red-400" /> <span className="text-red-400">Incomplete fix</span></>}
|
| 117 |
+
</motion.div>
|
| 118 |
+
)}
|
| 119 |
+
</div>
|
| 120 |
+
|
| 121 |
+
{/* ── Stats Grid ────────────── */}
|
| 122 |
+
<div className="px-3 py-3 grid grid-cols-2 gap-2">
|
| 123 |
+
<StatCard icon={TrendingUp} label="Steps" value={`${stepCount}/5`} subtitle="Max 5 per episode" />
|
| 124 |
+
<StatCard icon={Trophy} label="Average" value={avgReward.toFixed(3)}
|
| 125 |
+
color={avgReward >= 0.7 ? 'text-emerald-400' : avgReward >= 0.4 ? 'text-amber-400' : 'text-red-400'}
|
| 126 |
+
subtitle="Mean reward" />
|
| 127 |
+
</div>
|
| 128 |
+
|
| 129 |
+
{/* ── Chart ─────────────────── */}
|
| 130 |
+
<div className="px-3 pb-3">
|
| 131 |
+
<RewardChart rewards={rewards} />
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
{/* ── Reward Components ─────── */}
|
| 135 |
+
{rewardComponents && (
|
| 136 |
+
<div className="px-3 pb-3">
|
| 137 |
+
<div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3">
|
| 138 |
+
<div className="flex items-center gap-2 mb-2.5">
|
| 139 |
+
<Sparkles size={12} className="text-purple-400" />
|
| 140 |
+
<span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 141 |
+
Reward Breakdown
|
| 142 |
+
</span>
|
| 143 |
+
</div>
|
| 144 |
+
<div className="space-y-2">
|
| 145 |
+
{[
|
| 146 |
+
{ label: 'Compile', value: rewardComponents.compile_score, color: '#63B3ED' },
|
| 147 |
+
{ label: 'Test Ratio', value: rewardComponents.test_ratio, color: '#00FF88' },
|
| 148 |
+
{ label: 'Efficiency', value: rewardComponents.efficiency, color: '#FFAA00' },
|
| 149 |
+
{ label: 'LLM Correct', value: rewardComponents.llm_correctness, color: '#A78BFA' },
|
| 150 |
+
{ label: 'LLM Security', value: rewardComponents.llm_security, color: '#F97316' },
|
| 151 |
+
{ label: 'LLM Quality', value: rewardComponents.llm_quality, color: '#EC4899' },
|
| 152 |
+
].map(({ label, value, color }) => (
|
| 153 |
+
<div key={label}>
|
| 154 |
+
<div className="flex items-center justify-between text-[10px] mb-0.5">
|
| 155 |
+
<span className="text-[var(--text-muted)]">{label}</span>
|
| 156 |
+
<span className="font-mono font-medium" style={{ color }}>{(value ?? 0).toFixed(2)}</span>
|
| 157 |
+
</div>
|
| 158 |
+
<div className="h-1 bg-[var(--bg-primary)] rounded-full overflow-hidden">
|
| 159 |
+
<motion.div
|
| 160 |
+
initial={{ width: 0 }}
|
| 161 |
+
animate={{ width: `${((value ?? 0) * 100)}%` }}
|
| 162 |
+
transition={{ duration: 0.6, ease: 'easeOut' }}
|
| 163 |
+
className="h-full rounded-full"
|
| 164 |
+
style={{ backgroundColor: color }}
|
| 165 |
+
/>
|
| 166 |
+
</div>
|
| 167 |
+
</div>
|
| 168 |
+
))}
|
| 169 |
+
</div>
|
| 170 |
+
</div>
|
| 171 |
+
</div>
|
| 172 |
+
)}
|
| 173 |
+
|
| 174 |
+
{/* ── LLM Feedback ──────────── */}
|
| 175 |
+
{feedback && (
|
| 176 |
+
<div className="px-3 pb-3">
|
| 177 |
+
<div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3">
|
| 178 |
+
<div className="flex items-center gap-2 mb-2">
|
| 179 |
+
<MessageSquareText size={12} className="text-blue-400" />
|
| 180 |
+
<span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 181 |
+
Execution Info
|
| 182 |
+
</span>
|
| 183 |
+
</div>
|
| 184 |
+
<p className="text-[11px] text-[var(--text-secondary)] leading-relaxed font-mono whitespace-pre-wrap">
|
| 185 |
+
{feedback}
|
| 186 |
+
</p>
|
| 187 |
+
</div>
|
| 188 |
+
</div>
|
| 189 |
+
)}
|
| 190 |
+
|
| 191 |
+
{/* ── Attempt Timeline ──────── */}
|
| 192 |
+
{attempts.length > 0 && (
|
| 193 |
+
<div className="px-3 pb-4">
|
| 194 |
+
<div className="bg-[var(--bg-elevated)] border border-[var(--border-subtle)] rounded-xl p-3">
|
| 195 |
+
<div className="flex items-center gap-2 mb-2.5">
|
| 196 |
+
<Clock size={12} className="text-amber-400" />
|
| 197 |
+
<span className="text-[9px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 198 |
+
Attempt Timeline
|
| 199 |
+
</span>
|
| 200 |
+
</div>
|
| 201 |
+
<div className="space-y-2">
|
| 202 |
+
{attempts.map((a, i) => (
|
| 203 |
+
<motion.div
|
| 204 |
+
key={i}
|
| 205 |
+
initial={{ opacity: 0, y: 6 }}
|
| 206 |
+
animate={{ opacity: 1, y: 0 }}
|
| 207 |
+
transition={{ delay: i * 0.05 }}
|
| 208 |
+
className="flex items-center gap-2"
|
| 209 |
+
>
|
| 210 |
+
<div className="w-5 h-5 rounded-full flex items-center justify-center text-[9px] font-bold border"
|
| 211 |
+
style={{
|
| 212 |
+
borderColor: rewardColor(a.reward),
|
| 213 |
+
color: rewardColor(a.reward),
|
| 214 |
+
backgroundColor: `${rewardColor(a.reward)}15`,
|
| 215 |
+
}}
|
| 216 |
+
>
|
| 217 |
+
{i + 1}
|
| 218 |
+
</div>
|
| 219 |
+
<div className="flex-1 h-px bg-[var(--border-subtle)]" />
|
| 220 |
+
<span className="text-[10px] font-mono font-medium" style={{ color: rewardColor(a.reward) }}>
|
| 221 |
+
{a.reward.toFixed(3)}
|
| 222 |
+
</span>
|
| 223 |
+
<span className="text-[9px] text-[var(--text-muted)]">
|
| 224 |
+
{a.passed}/{a.total}
|
| 225 |
+
</span>
|
| 226 |
+
</motion.div>
|
| 227 |
+
))}
|
| 228 |
+
</div>
|
| 229 |
+
</div>
|
| 230 |
+
</div>
|
| 231 |
+
)}
|
| 232 |
+
</aside>
|
| 233 |
+
);
|
| 234 |
+
}
|
frontend/src/components/Sidebar.jsx
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import {
|
| 4 |
+
Play, RotateCcw, Zap, Shield, AlertTriangle,
|
| 5 |
+
ChevronRight, Cpu, Gauge
|
| 6 |
+
} from 'lucide-react';
|
| 7 |
+
import clsx from 'clsx';
|
| 8 |
+
|
| 9 |
+
const TASKS = [
|
| 10 |
+
{
|
| 11 |
+
id: 'auto', label: 'Auto', name: 'Adaptive Curriculum', difficulty: 'info', icon: Gauge,
|
| 12 |
+
desc: 'Scales difficulty based on agent performance.'
|
| 13 |
+
},
|
| 14 |
+
{
|
| 15 |
+
id: 'easy', label: 'Easy', name: 'Fix average_list()', difficulty: 'easy', icon: Zap,
|
| 16 |
+
desc: 'Syntax errors — missing colon, wrong built-in.'
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
id: 'medium', label: 'Medium', name: 'Fix binary_search()', difficulty: 'medium', icon: Cpu,
|
| 20 |
+
desc: 'Logic bugs — off-by-one, infinite loop.'
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
id: 'hard', label: 'Hard', name: 'Optimize subarray', difficulty: 'hard', icon: AlertTriangle,
|
| 24 |
+
desc: 'Replace O(N³) with Kadane\'s O(N).'
|
| 25 |
+
},
|
| 26 |
+
{
|
| 27 |
+
id: 'sandbox', label: 'Sandbox', name: 'Custom Code & Debug', difficulty: 'sandbox', icon: Play,
|
| 28 |
+
desc: 'Write custom code, debug it, and measure time complexity.'
|
| 29 |
+
},
|
| 30 |
+
];
|
| 31 |
+
|
| 32 |
+
const diffColors = {
|
| 33 |
+
info: { bg: 'bg-blue-500/10', border: 'border-blue-500/30', text: 'text-blue-400', dot: 'bg-blue-400' },
|
| 34 |
+
easy: { bg: 'bg-emerald-500/10', border: 'border-emerald-500/30', text: 'text-emerald-400', dot: 'bg-emerald-400' },
|
| 35 |
+
medium: { bg: 'bg-amber-500/10', border: 'border-amber-500/30', text: 'text-amber-400', dot: 'bg-amber-400' },
|
| 36 |
+
hard: { bg: 'bg-red-500/10', border: 'border-red-500/30', text: 'text-red-400', dot: 'bg-red-400' },
|
| 37 |
+
sandbox: { bg: 'bg-purple-500/10', border: 'border-purple-500/30', text: 'text-purple-400', dot: 'bg-purple-400' },
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
export default function Sidebar({
|
| 41 |
+
selectedTask, onSelectTask,
|
| 42 |
+
onStartEpisode, onReset,
|
| 43 |
+
isRunning, episodeHistory,
|
| 44 |
+
serverStatus,
|
| 45 |
+
}) {
|
| 46 |
+
const [historyOpen, setHistoryOpen] = useState(false);
|
| 47 |
+
|
| 48 |
+
return (
|
| 49 |
+
<aside className="flex flex-col h-full border-r border-[var(--border-subtle)] bg-[var(--bg-secondary)] overflow-hidden">
|
| 50 |
+
|
| 51 |
+
{/* ── Header ─────────────────────── */}
|
| 52 |
+
<div className="px-4 py-4 border-b border-[var(--border-subtle)]">
|
| 53 |
+
<div className="flex items-center gap-2.5">
|
| 54 |
+
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-emerald-400 to-emerald-600 flex items-center justify-center">
|
| 55 |
+
<span className="text-sm font-bold text-black">C</span>
|
| 56 |
+
</div>
|
| 57 |
+
<div>
|
| 58 |
+
<h1 className="text-sm font-bold tracking-wide text-[var(--text-primary)]">
|
| 59 |
+
Code<span className="text-emerald-400">Arena</span>
|
| 60 |
+
<span className="text-purple-400 ml-1">RL</span>
|
| 61 |
+
</h1>
|
| 62 |
+
<p className="text-[10px] text-[var(--text-muted)] tracking-widest uppercase">
|
| 63 |
+
Debug Benchmark
|
| 64 |
+
</p>
|
| 65 |
+
</div>
|
| 66 |
+
</div>
|
| 67 |
+
|
| 68 |
+
{/* Server status */}
|
| 69 |
+
<div className="mt-3 flex items-center gap-2 text-[11px]">
|
| 70 |
+
<span className={clsx(
|
| 71 |
+
'w-2 h-2 rounded-full',
|
| 72 |
+
serverStatus === 'online' ? 'bg-emerald-400 shadow-[0_0_6px_rgba(0,255,136,0.5)]' :
|
| 73 |
+
serverStatus === 'checking' ? 'bg-amber-400 animate-pulse' : 'bg-red-400'
|
| 74 |
+
)} />
|
| 75 |
+
<span className="text-[var(--text-muted)] font-mono">
|
| 76 |
+
FastAPI {serverStatus === 'online' ? '● Online' : serverStatus === 'checking' ? '○ Checking…' : '✗ Offline'}
|
| 77 |
+
</span>
|
| 78 |
+
</div>
|
| 79 |
+
</div>
|
| 80 |
+
|
| 81 |
+
{/* ── Task Selector ──────────────── */}
|
| 82 |
+
<div className="flex-1 overflow-y-auto px-3 py-3 space-y-2">
|
| 83 |
+
<p className="text-[10px] font-semibold tracking-[0.15em] uppercase text-[var(--text-muted)] px-1 mb-1">
|
| 84 |
+
Select Task
|
| 85 |
+
</p>
|
| 86 |
+
|
| 87 |
+
{TASKS.map((t) => {
|
| 88 |
+
const colors = diffColors[t.difficulty];
|
| 89 |
+
const active = selectedTask === t.id;
|
| 90 |
+
const Icon = t.icon;
|
| 91 |
+
|
| 92 |
+
return (
|
| 93 |
+
<motion.button
|
| 94 |
+
key={t.id}
|
| 95 |
+
whileHover={{ scale: 1.01 }}
|
| 96 |
+
whileTap={{ scale: 0.99 }}
|
| 97 |
+
disabled={isRunning}
|
| 98 |
+
onClick={() => onSelectTask(t.id)}
|
| 99 |
+
className={clsx(
|
| 100 |
+
'w-full text-left rounded-xl p-3 transition-all duration-200 border cursor-pointer',
|
| 101 |
+
'disabled:opacity-40 disabled:cursor-not-allowed',
|
| 102 |
+
active
|
| 103 |
+
? `${colors.bg} ${colors.border} shadow-lg`
|
| 104 |
+
: 'bg-[var(--bg-elevated)] border-[var(--border-subtle)] hover:border-[var(--border-active)]'
|
| 105 |
+
)}
|
| 106 |
+
>
|
| 107 |
+
<div className="flex items-center justify-between mb-1">
|
| 108 |
+
<div className="flex items-center gap-2">
|
| 109 |
+
<Icon size={14} className={active ? colors.text : 'text-[var(--text-muted)]'} />
|
| 110 |
+
<span className={clsx('text-xs font-semibold', active ? colors.text : 'text-[var(--text-primary)]')}>
|
| 111 |
+
{t.name}
|
| 112 |
+
</span>
|
| 113 |
+
</div>
|
| 114 |
+
<span className={clsx(
|
| 115 |
+
'text-[9px] font-bold tracking-wider uppercase px-2 py-0.5 rounded',
|
| 116 |
+
colors.bg, colors.text, 'border', colors.border
|
| 117 |
+
)}>
|
| 118 |
+
{t.label}
|
| 119 |
+
</span>
|
| 120 |
+
</div>
|
| 121 |
+
<p className="text-[10px] text-[var(--text-muted)] leading-relaxed pl-[22px]">{t.desc}</p>
|
| 122 |
+
</motion.button>
|
| 123 |
+
);
|
| 124 |
+
})}
|
| 125 |
+
</div>
|
| 126 |
+
|
| 127 |
+
{/* ── Actions ────────────────────── */}
|
| 128 |
+
<div className="px-3 pb-3 space-y-2">
|
| 129 |
+
<motion.button
|
| 130 |
+
whileHover={{ scale: 1.02 }}
|
| 131 |
+
whileTap={{ scale: 0.97 }}
|
| 132 |
+
disabled={isRunning || serverStatus !== 'online'}
|
| 133 |
+
onClick={onStartEpisode}
|
| 134 |
+
className={clsx(
|
| 135 |
+
'w-full flex items-center justify-center gap-2 py-2.5 rounded-xl text-xs font-bold tracking-wide',
|
| 136 |
+
'transition-all duration-200 cursor-pointer',
|
| 137 |
+
'disabled:opacity-40 disabled:cursor-not-allowed',
|
| 138 |
+
'bg-gradient-to-r from-emerald-500 to-emerald-600 text-black',
|
| 139 |
+
'hover:shadow-[0_0_20px_rgba(0,255,136,0.3)]'
|
| 140 |
+
)}
|
| 141 |
+
>
|
| 142 |
+
<Play size={14} /> START EPISODE
|
| 143 |
+
</motion.button>
|
| 144 |
+
|
| 145 |
+
<button
|
| 146 |
+
disabled={isRunning}
|
| 147 |
+
onClick={onReset}
|
| 148 |
+
className={clsx(
|
| 149 |
+
'w-full flex items-center justify-center gap-2 py-2 rounded-xl text-xs font-medium',
|
| 150 |
+
'border border-[var(--border-subtle)] text-[var(--text-secondary)]',
|
| 151 |
+
'hover:border-[var(--border-active)] hover:text-[var(--text-primary)]',
|
| 152 |
+
'transition-all disabled:opacity-30 disabled:cursor-not-allowed cursor-pointer'
|
| 153 |
+
)}
|
| 154 |
+
>
|
| 155 |
+
<RotateCcw size={12} /> Reset
|
| 156 |
+
</button>
|
| 157 |
+
</div>
|
| 158 |
+
|
| 159 |
+
{/* ── Episode History ────────────── */}
|
| 160 |
+
<div className="border-t border-[var(--border-subtle)]">
|
| 161 |
+
<button
|
| 162 |
+
onClick={() => setHistoryOpen(o => !o)}
|
| 163 |
+
className="w-full flex items-center justify-between px-4 py-2.5 text-[10px] font-semibold tracking-[0.15em] uppercase text-[var(--text-muted)] hover:text-[var(--text-secondary)] transition-colors cursor-pointer"
|
| 164 |
+
>
|
| 165 |
+
<span>History ({episodeHistory.length})</span>
|
| 166 |
+
<ChevronRight size={12} className={clsx('transition-transform', historyOpen && 'rotate-90')} />
|
| 167 |
+
</button>
|
| 168 |
+
|
| 169 |
+
<AnimatePresence>
|
| 170 |
+
{historyOpen && (
|
| 171 |
+
<motion.div
|
| 172 |
+
initial={{ height: 0, opacity: 0 }}
|
| 173 |
+
animate={{ height: 'auto', opacity: 1 }}
|
| 174 |
+
exit={{ height: 0, opacity: 0 }}
|
| 175 |
+
className="overflow-hidden"
|
| 176 |
+
>
|
| 177 |
+
<div className="px-3 pb-3 space-y-1.5 max-h-40 overflow-y-auto">
|
| 178 |
+
{episodeHistory.length === 0 && (
|
| 179 |
+
<p className="text-[10px] text-[var(--text-muted)] italic px-1">No episodes yet</p>
|
| 180 |
+
)}
|
| 181 |
+
{episodeHistory.map((ep, i) => (
|
| 182 |
+
<div key={i} className="flex items-center justify-between bg-[var(--bg-elevated)] rounded-lg px-3 py-2 text-[10px]">
|
| 183 |
+
<span className="text-[var(--text-secondary)] font-mono">
|
| 184 |
+
#{episodeHistory.length - i} · {ep.taskId}
|
| 185 |
+
</span>
|
| 186 |
+
<span className="font-bold font-mono" style={{ color: ep.reward >= 0.7 ? '#00FF88' : ep.reward >= 0.4 ? '#FFAA00' : '#FF4455' }}>
|
| 187 |
+
{ep.reward.toFixed(2)}
|
| 188 |
+
</span>
|
| 189 |
+
</div>
|
| 190 |
+
))}
|
| 191 |
+
</div>
|
| 192 |
+
</motion.div>
|
| 193 |
+
)}
|
| 194 |
+
</AnimatePresence>
|
| 195 |
+
</div>
|
| 196 |
+
</aside>
|
| 197 |
+
);
|
| 198 |
+
}
|
frontend/src/components/Terminal.jsx
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useRef, useEffect } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { Terminal as TerminalIcon, CheckCircle2, XCircle, AlertCircle } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
function parseLineColor(line) {
|
| 6 |
+
if (line.includes('PASS') || line.includes('OK') || line.includes('passed'))
|
| 7 |
+
return 'text-emerald-400';
|
| 8 |
+
if (line.includes('FAIL') || line.includes('Error') || line.includes('error') || line.includes('✗'))
|
| 9 |
+
return 'text-red-400';
|
| 10 |
+
if (line.includes('---') || line.includes('Ran ') || line.includes('==='))
|
| 11 |
+
return 'text-[var(--text-muted)]';
|
| 12 |
+
if (line.startsWith('>') || line.startsWith('$'))
|
| 13 |
+
return 'text-emerald-300';
|
| 14 |
+
return 'text-[var(--text-secondary)]';
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
export default function Terminal({ logs, isRunning }) {
|
| 18 |
+
const scrollRef = useRef(null);
|
| 19 |
+
|
| 20 |
+
useEffect(() => {
|
| 21 |
+
if (scrollRef.current) {
|
| 22 |
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
| 23 |
+
}
|
| 24 |
+
}, [logs]);
|
| 25 |
+
|
| 26 |
+
const hasErrors = logs.some(l => l.type === 'error');
|
| 27 |
+
const hasSuccess = logs.some(l => l.type === 'success');
|
| 28 |
+
|
| 29 |
+
return (
|
| 30 |
+
<div className="glass-card flex flex-col overflow-hidden h-full">
|
| 31 |
+
{/* ── Header ─────────────── */}
|
| 32 |
+
<div className="flex items-center justify-between px-4 py-2.5 border-b border-[var(--border-subtle)] bg-[#0D1117]/60">
|
| 33 |
+
<div className="flex items-center gap-2">
|
| 34 |
+
<TerminalIcon size={14} className="text-emerald-400" />
|
| 35 |
+
<span className="text-[10px] font-bold tracking-[0.12em] uppercase text-[var(--text-muted)]">
|
| 36 |
+
Terminal Output
|
| 37 |
+
</span>
|
| 38 |
+
</div>
|
| 39 |
+
|
| 40 |
+
<div className="flex items-center gap-1.5">
|
| 41 |
+
{hasSuccess && <CheckCircle2 size={14} className="text-emerald-400" />}
|
| 42 |
+
{hasErrors && <XCircle size={14} className="text-red-400" />}
|
| 43 |
+
{isRunning && (
|
| 44 |
+
<span className="flex items-center gap-1 text-[10px] text-amber-400 font-mono">
|
| 45 |
+
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 animate-pulse" />
|
| 46 |
+
running
|
| 47 |
+
</span>
|
| 48 |
+
)}
|
| 49 |
+
</div>
|
| 50 |
+
</div>
|
| 51 |
+
|
| 52 |
+
{/* ── Log Output ─────────── */}
|
| 53 |
+
<div ref={scrollRef} className="flex-1 overflow-y-auto p-4 font-mono text-xs leading-[1.8]">
|
| 54 |
+
{logs.length === 0 ? (
|
| 55 |
+
<div className="flex flex-col items-center justify-center h-full gap-2 text-[var(--text-muted)]">
|
| 56 |
+
<AlertCircle size={20} />
|
| 57 |
+
<p className="text-[11px]">Waiting for execution…</p>
|
| 58 |
+
<p className="text-[9px]">Click "Run Step" to submit code</p>
|
| 59 |
+
</div>
|
| 60 |
+
) : (
|
| 61 |
+
<AnimatePresence>
|
| 62 |
+
{logs.map((log, i) => (
|
| 63 |
+
<motion.div
|
| 64 |
+
key={i}
|
| 65 |
+
initial={{ opacity: 0, x: -8 }}
|
| 66 |
+
animate={{ opacity: 1, x: 0 }}
|
| 67 |
+
transition={{ duration: 0.15, delay: i * 0.02 }}
|
| 68 |
+
className={`${parseLineColor(log.text)} whitespace-pre-wrap`}
|
| 69 |
+
>
|
| 70 |
+
{log.prefix && (
|
| 71 |
+
<span className="text-[var(--text-muted)] select-none mr-2">{log.prefix}</span>
|
| 72 |
+
)}
|
| 73 |
+
{log.text}
|
| 74 |
+
</motion.div>
|
| 75 |
+
))}
|
| 76 |
+
</AnimatePresence>
|
| 77 |
+
)}
|
| 78 |
+
|
| 79 |
+
{isRunning && (
|
| 80 |
+
<span className="terminal-cursor text-emerald-400 text-xs"> </span>
|
| 81 |
+
)}
|
| 82 |
+
</div>
|
| 83 |
+
</div>
|
| 84 |
+
);
|
| 85 |
+
}
|
frontend/src/index.css
CHANGED
|
@@ -1 +1,84 @@
|
|
| 1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
|
| 3 |
+
/* ── Custom design tokens ────────────────────────────────── */
|
| 4 |
+
:root {
|
| 5 |
+
--bg-primary: #0B0F19;
|
| 6 |
+
--bg-secondary: #111827;
|
| 7 |
+
--bg-card: #151C2C;
|
| 8 |
+
--bg-elevated: #1A2236;
|
| 9 |
+
--border-subtle: #1E293B;
|
| 10 |
+
--border-active: #334155;
|
| 11 |
+
--text-primary: #E2E8F0;
|
| 12 |
+
--text-secondary: #94A3B8;
|
| 13 |
+
--text-muted: #64748B;
|
| 14 |
+
--accent-green: #00FF88;
|
| 15 |
+
--accent-amber: #FFAA00;
|
| 16 |
+
--accent-red: #FF4455;
|
| 17 |
+
--accent-blue: #63B3ED;
|
| 18 |
+
--accent-purple: #A78BFA;
|
| 19 |
+
--glass-bg: rgba(21, 28, 44, 0.7);
|
| 20 |
+
--glass-border: rgba(30, 41, 59, 0.6);
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/* ── Base ────────────────────────────────────────────────── */
|
| 24 |
+
*, *::before, *::after { box-sizing: border-box; }
|
| 25 |
+
|
| 26 |
+
body {
|
| 27 |
+
margin: 0;
|
| 28 |
+
background: var(--bg-primary);
|
| 29 |
+
color: var(--text-primary);
|
| 30 |
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
| 31 |
+
-webkit-font-smoothing: antialiased;
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
/* ── Scrollbar ───────────────────────────────────────────── */
|
| 35 |
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
| 36 |
+
::-webkit-scrollbar-track { background: transparent; }
|
| 37 |
+
::-webkit-scrollbar-thumb { background: #1E293B; border-radius: 3px; }
|
| 38 |
+
::-webkit-scrollbar-thumb:hover { background: #334155; }
|
| 39 |
+
|
| 40 |
+
/* ── Monaco editor overrides ─────────────────────────────── */
|
| 41 |
+
.monaco-editor .margin,
|
| 42 |
+
.monaco-editor,
|
| 43 |
+
.monaco-editor-background,
|
| 44 |
+
.monaco-editor .inputarea.ime-input {
|
| 45 |
+
background-color: #0D1117 !important;
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
/* ── Animations ──────────────────────────────────────────── */
|
| 49 |
+
@keyframes pulse-glow {
|
| 50 |
+
0%, 100% { box-shadow: 0 0 0 0 rgba(0, 255, 136, 0.3); }
|
| 51 |
+
50% { box-shadow: 0 0 20px 4px rgba(0, 255, 136, 0.15); }
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
@keyframes shimmer {
|
| 55 |
+
0% { background-position: -200% 0; }
|
| 56 |
+
100% { background-position: 200% 0; }
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
@keyframes terminal-blink {
|
| 60 |
+
0%, 49% { opacity: 1; }
|
| 61 |
+
50%, 100% { opacity: 0; }
|
| 62 |
+
}
|
| 63 |
+
|
| 64 |
+
.animate-pulse-glow { animation: pulse-glow 2s ease-in-out infinite; }
|
| 65 |
+
|
| 66 |
+
.shimmer-loading {
|
| 67 |
+
background: linear-gradient(90deg, #1E293B 25%, #334155 50%, #1E293B 75%);
|
| 68 |
+
background-size: 200% 100%;
|
| 69 |
+
animation: shimmer 1.5s ease-in-out infinite;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
.terminal-cursor::after {
|
| 73 |
+
content: '▌';
|
| 74 |
+
animation: terminal-blink 1s step-end infinite;
|
| 75 |
+
color: var(--accent-green);
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
/* ── Glass card utility ──────────────────────────────────── */
|
| 79 |
+
.glass-card {
|
| 80 |
+
background: var(--glass-bg);
|
| 81 |
+
backdrop-filter: blur(12px);
|
| 82 |
+
border: 1px solid var(--glass-border);
|
| 83 |
+
border-radius: 12px;
|
| 84 |
+
}
|
frontend/src/main.jsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
| 1 |
import React from 'react'
|
| 2 |
import ReactDOM from 'react-dom/client'
|
| 3 |
import App from './App.jsx'
|
|
|
|
| 4 |
|
| 5 |
ReactDOM.createRoot(document.getElementById('root')).render(
|
| 6 |
<React.StrictMode>
|
|
|
|
| 1 |
import React from 'react'
|
| 2 |
import ReactDOM from 'react-dom/client'
|
| 3 |
import App from './App.jsx'
|
| 4 |
+
import './index.css'
|
| 5 |
|
| 6 |
ReactDOM.createRoot(document.getElementById('root')).render(
|
| 7 |
<React.StrictMode>
|
frontend/src/pages/Dashboard.jsx
ADDED
|
@@ -0,0 +1,454 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
| 2 |
+
import { motion, AnimatePresence } from 'framer-motion';
|
| 3 |
+
import { Wifi, WifiOff, Sparkles, Loader2, X } from 'lucide-react';
|
| 4 |
+
|
| 5 |
+
import Sidebar from '../components/Sidebar';
|
| 6 |
+
import CodeEditor from '../components/CodeEditor';
|
| 7 |
+
import Terminal from '../components/Terminal';
|
| 8 |
+
import RewardPanel from '../components/RewardPanel';
|
| 9 |
+
import { resetTask, sendStep, healthCheck, generateFix, runRaw } from '../services/api';
|
| 10 |
+
|
| 11 |
+
function initialState() {
|
| 12 |
+
return {
|
| 13 |
+
code: '# Select a task and click "Start Episode" to begin.\n',
|
| 14 |
+
selectedTask: 'easy',
|
| 15 |
+
stepCount: 0,
|
| 16 |
+
maxSteps: 5,
|
| 17 |
+
rewards: [],
|
| 18 |
+
isDone: false,
|
| 19 |
+
isRunning: false,
|
| 20 |
+
isThinking: false,
|
| 21 |
+
isGenerating: false,
|
| 22 |
+
terminalLogs: [],
|
| 23 |
+
rewardComponents: null,
|
| 24 |
+
feedback: '',
|
| 25 |
+
attempts: [],
|
| 26 |
+
episodeHistory: [],
|
| 27 |
+
serverStatus: 'checking',
|
| 28 |
+
errorBanner: '',
|
| 29 |
+
currentTaskId: '',
|
| 30 |
+
currentDifficulty: '',
|
| 31 |
+
ollamaModel: 'llama3.2:latest',
|
| 32 |
+
agentMode: false,
|
| 33 |
+
lastFixMethod: '',
|
| 34 |
+
};
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
export default function Dashboard() {
|
| 38 |
+
const [state, setState] = useState(initialState);
|
| 39 |
+
const stateRef = useRef(state);
|
| 40 |
+
stateRef.current = state;
|
| 41 |
+
|
| 42 |
+
const set = useCallback((patch) => {
|
| 43 |
+
setState(prev => ({ ...prev, ...(typeof patch === 'function' ? patch(prev) : patch) }));
|
| 44 |
+
}, []);
|
| 45 |
+
|
| 46 |
+
// Health probe
|
| 47 |
+
useEffect(() => {
|
| 48 |
+
const probe = async () => {
|
| 49 |
+
set({ serverStatus: 'checking' });
|
| 50 |
+
try { await healthCheck(); set({ serverStatus: 'online' }); }
|
| 51 |
+
catch { set({ serverStatus: 'offline' }); }
|
| 52 |
+
};
|
| 53 |
+
probe();
|
| 54 |
+
const iv = setInterval(probe, 15000);
|
| 55 |
+
return () => clearInterval(iv);
|
| 56 |
+
}, [set]);
|
| 57 |
+
|
| 58 |
+
const pushLog = useCallback((text, type = 'info') => {
|
| 59 |
+
set(prev => ({ terminalLogs: [...prev.terminalLogs, { text, type }] }));
|
| 60 |
+
}, [set]);
|
| 61 |
+
|
| 62 |
+
const resetEpisode = useCallback(() => {
|
| 63 |
+
set({
|
| 64 |
+
code: '# Select a task and click "Start Episode" to begin.\n',
|
| 65 |
+
stepCount: 0, rewards: [], isDone: false, isRunning: false,
|
| 66 |
+
isThinking: false, isGenerating: false, terminalLogs: [],
|
| 67 |
+
rewardComponents: null, feedback: '', attempts: [],
|
| 68 |
+
errorBanner: '', currentTaskId: '', currentDifficulty: '', lastFixMethod: '',
|
| 69 |
+
});
|
| 70 |
+
}, [set]);
|
| 71 |
+
|
| 72 |
+
// START EPISODE
|
| 73 |
+
const handleStartEpisode = useCallback(async () => {
|
| 74 |
+
const s = stateRef.current;
|
| 75 |
+
if (s.isRunning || s.serverStatus !== 'online') return;
|
| 76 |
+
resetEpisode();
|
| 77 |
+
await new Promise(r => setTimeout(r, 50));
|
| 78 |
+
set({ isRunning: true, errorBanner: '' });
|
| 79 |
+
|
| 80 |
+
const logs = [
|
| 81 |
+
{ text: `$ codearena reset --task=${s.selectedTask}`, type: 'command' },
|
| 82 |
+
{ text: 'Connecting to environment…', type: 'info' },
|
| 83 |
+
];
|
| 84 |
+
set({ terminalLogs: logs });
|
| 85 |
+
|
| 86 |
+
if (s.selectedTask === 'sandbox') {
|
| 87 |
+
logs.push({ text: `✓ Sandbox loaded. Max 5 episodes. Write custom code and click RUN STEP!`, type: 'success' });
|
| 88 |
+
logs.push({ text: `📋 The AI will run all 5 steps automatically after each execution.`, type: 'info' });
|
| 89 |
+
set({
|
| 90 |
+
code: '# Write custom python code here...\n\n',
|
| 91 |
+
terminalLogs: [...logs],
|
| 92 |
+
isRunning: false,
|
| 93 |
+
stepCount: 0,
|
| 94 |
+
isDone: false,
|
| 95 |
+
rewards: [],
|
| 96 |
+
attempts: [],
|
| 97 |
+
currentTaskId: 'sandbox',
|
| 98 |
+
currentDifficulty: 'sandbox',
|
| 99 |
+
});
|
| 100 |
+
return;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
try {
|
| 104 |
+
const data = await resetTask(s.selectedTask);
|
| 105 |
+
const obs = data.observation || {};
|
| 106 |
+
const info = data.info || {};
|
| 107 |
+
logs.push({ text: `✓ Task loaded: ${info.task_id} [${info.difficulty}]`, type: 'success' });
|
| 108 |
+
logs.push({ text: 'Edit the code and click RUN STEP, or use AI FIX.', type: 'info' });
|
| 109 |
+
set({
|
| 110 |
+
code: obs.buggy_code || '# No code returned',
|
| 111 |
+
terminalLogs: [...logs],
|
| 112 |
+
isRunning: false,
|
| 113 |
+
currentTaskId: info.task_id || s.selectedTask,
|
| 114 |
+
currentDifficulty: info.difficulty || '',
|
| 115 |
+
});
|
| 116 |
+
} catch (err) {
|
| 117 |
+
logs.push({ text: `✗ Reset failed: ${err.message}`, type: 'error' });
|
| 118 |
+
set({ terminalLogs: [...logs], isRunning: false, errorBanner: `Reset failed: ${err.message}` });
|
| 119 |
+
}
|
| 120 |
+
}, [set, resetEpisode]);
|
| 121 |
+
|
| 122 |
+
// AI FIX — calls backend /fix (built-in fixer + optional Ollama)
|
| 123 |
+
const handleAIFix = useCallback(async () => {
|
| 124 |
+
const s = stateRef.current;
|
| 125 |
+
if (s.isGenerating || s.isDone) return;
|
| 126 |
+
|
| 127 |
+
set({ isGenerating: true, errorBanner: '' });
|
| 128 |
+
pushLog(`$ codearena fix --model=${s.ollamaModel}`, 'command');
|
| 129 |
+
pushLog('Generating fix (Ollama → built-in fallback)…', 'info');
|
| 130 |
+
|
| 131 |
+
try {
|
| 132 |
+
const result = await generateFix(
|
| 133 |
+
s.code,
|
| 134 |
+
s.feedback,
|
| 135 |
+
'http://localhost:11434',
|
| 136 |
+
s.ollamaModel,
|
| 137 |
+
s.rewards.length > 0 ? s.rewards[s.rewards.length - 1] : 0.0,
|
| 138 |
+
s.currentTaskId || 'sandbox'
|
| 139 |
+
);
|
| 140 |
+
const method = result.method === 'ollama' ? '🤖 Ollama' : '⚙️ Built-in';
|
| 141 |
+
pushLog(`✓ Fix generated via ${method}`, 'success');
|
| 142 |
+
|
| 143 |
+
if (result.algo_hint) {
|
| 144 |
+
pushLog(`🔍 Algorithm: ${result.algo_hint}`, 'warning');
|
| 145 |
+
}
|
| 146 |
+
if (result.complexity) {
|
| 147 |
+
pushLog(`⏱ Complexity of fix: ${result.complexity}`, 'info');
|
| 148 |
+
}
|
| 149 |
+
|
| 150 |
+
if (result.explanation && result.explanation !== "No reasoning provided.") {
|
| 151 |
+
pushLog('', 'info');
|
| 152 |
+
pushLog('🧠 AI Analysis:', 'warning');
|
| 153 |
+
result.explanation.split('\n').filter(Boolean).forEach(l => pushLog(` ${l}`, 'info'));
|
| 154 |
+
pushLog('', 'info');
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
if (result.note) pushLog(result.note, 'info');
|
| 158 |
+
const codeChanged = result.fixed_code.trim() !== s.code.trim();
|
| 159 |
+
|
| 160 |
+
set({ code: result.fixed_code, isGenerating: false, lastFixMethod: result.method });
|
| 161 |
+
|
| 162 |
+
// If agent mode, auto-run step (only if code actually changed to prevent infinite loops)
|
| 163 |
+
if (s.agentMode && codeChanged) {
|
| 164 |
+
setTimeout(handleRunStep, 1500);
|
| 165 |
+
} else if (s.agentMode && !codeChanged) {
|
| 166 |
+
pushLog(`✓ AI determined code is already optimal. Agent Mode stopping.`, 'success');
|
| 167 |
+
}
|
| 168 |
+
} catch (err) {
|
| 169 |
+
pushLog(`✗ Fix failed: ${err.message}`, 'error');
|
| 170 |
+
set({ isGenerating: false, errorBanner: `Fix failed: ${err.message}` });
|
| 171 |
+
}
|
| 172 |
+
}, [set, pushLog]);
|
| 173 |
+
|
| 174 |
+
// RUN RAW (Sandbox mode)
|
| 175 |
+
const handleRunRaw = useCallback(async () => {
|
| 176 |
+
const s = stateRef.current;
|
| 177 |
+
if (s.isRunning || !s.code?.trim()) return;
|
| 178 |
+
|
| 179 |
+
// Enforce max 5 episodes for Sandbox
|
| 180 |
+
if (s.stepCount >= 5) {
|
| 181 |
+
pushLog('', 'info');
|
| 182 |
+
pushLog('🏁 Max 5 episodes reached! Click "Start Episode" to reset and try again.', 'warning');
|
| 183 |
+
set({ isDone: true });
|
| 184 |
+
return;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
const episodeNum = s.stepCount + 1;
|
| 188 |
+
set({ isRunning: true, isThinking: true, errorBanner: '' });
|
| 189 |
+
const logs = [...stateRef.current.terminalLogs,
|
| 190 |
+
{ text: '', type: 'info' },
|
| 191 |
+
{ text: `$ sandbox_runner.py [Episode ${episodeNum}/5]`, type: 'command' },
|
| 192 |
+
{ text: `⏳ Step 1/5: Executing custom code… (Episode ${episodeNum} of 5)`, type: 'info' },
|
| 193 |
+
];
|
| 194 |
+
set({ terminalLogs: logs });
|
| 195 |
+
|
| 196 |
+
try {
|
| 197 |
+
const data = await runRaw(s.code);
|
| 198 |
+
set({ isThinking: false });
|
| 199 |
+
|
| 200 |
+
logs.push({ text: '─'.repeat(40), type: 'info' });
|
| 201 |
+
logs.push({ text: '✅ Step 1: Execution complete', type: 'success' });
|
| 202 |
+
|
| 203 |
+
if (data.stdout) data.stdout.split('\n').filter(Boolean).forEach(l => logs.push({ text: ` ${l}`, type: 'success' }));
|
| 204 |
+
|
| 205 |
+
if (data.stderr) {
|
| 206 |
+
logs.push({ text: '⚠️ Step 2: Errors detected:', type: 'warning' });
|
| 207 |
+
data.stderr.split('\n').filter(Boolean).forEach(l => logs.push({ text: ` ${l}`, type: 'error' }));
|
| 208 |
+
} else {
|
| 209 |
+
logs.push({ text: '✅ Step 2: No runtime errors found', type: 'success' });
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
logs.push({ text: '', type: 'info' });
|
| 213 |
+
logs.push({ text: `✅ Step 3: ⏱ Execution Time: ${data.execution_time?.toFixed(4) ?? 'N/A'}s`, type: 'info' });
|
| 214 |
+
logs.push({ text: `✅ Step 4: 🧠 Complexity: ${data.time_complexity_hint}`, type: 'warning' });
|
| 215 |
+
logs.push({ text: `⏳ Step 5: 🤖 Running AI Optimization Analysis…`, type: 'info' });
|
| 216 |
+
logs.push({ text: '', type: 'info' });
|
| 217 |
+
|
| 218 |
+
const isLastEpisode = episodeNum >= 5;
|
| 219 |
+
const avgReward = stateRef.current.rewards.length > 0
|
| 220 |
+
? (([...stateRef.current.rewards, data.reward ?? 0.5].reduce((a,b)=>a+b,0)) / (stateRef.current.rewards.length + 1)).toFixed(3)
|
| 221 |
+
: (data.reward ?? 0.5).toFixed(3);
|
| 222 |
+
|
| 223 |
+
set(prev => ({
|
| 224 |
+
terminalLogs: [...logs],
|
| 225 |
+
stepCount: prev.stepCount + 1,
|
| 226 |
+
rewards: [...prev.rewards, data.reward ?? 0.5],
|
| 227 |
+
isDone: isLastEpisode,
|
| 228 |
+
isRunning: false,
|
| 229 |
+
rewardComponents: data.reward_components || prev.rewardComponents,
|
| 230 |
+
feedback: data.time_complexity_hint || '',
|
| 231 |
+
attempts: [...prev.attempts, { reward: data.reward ?? 0.5, passed: data.stderr ? 0 : 1, total: 1 }],
|
| 232 |
+
}));
|
| 233 |
+
|
| 234 |
+
if (isLastEpisode) {
|
| 235 |
+
pushLog('', 'info');
|
| 236 |
+
pushLog(`🏁 Episode 5/5 complete! Avg Reward: ${avgReward}`, 'success');
|
| 237 |
+
pushLog(`📊 Click "Start Episode" to start a new run.`, 'info');
|
| 238 |
+
return; // Don't trigger AI fix on last episode done
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
// Step 5: ALWAYS auto-trigger AI 5-step analysis for Custom Code (same as all other tasks)
|
| 242 |
+
setTimeout(handleAIFix, 800);
|
| 243 |
+
} catch (err) {
|
| 244 |
+
const logs2 = [...stateRef.current.terminalLogs, { text: `✗ Execution failed: ${err.message}`, type: 'error' }];
|
| 245 |
+
set({ terminalLogs: logs2, isRunning: false, isThinking: false, errorBanner: `Run failed: ${err.message}` });
|
| 246 |
+
}
|
| 247 |
+
}, [set]);
|
| 248 |
+
|
| 249 |
+
// RUN STEP
|
| 250 |
+
const handleRunStep = useCallback(async () => {
|
| 251 |
+
const s = stateRef.current;
|
| 252 |
+
if (s.selectedTask === 'sandbox') {
|
| 253 |
+
return handleRunRaw();
|
| 254 |
+
}
|
| 255 |
+
if (s.isRunning || s.isDone || !s.code?.trim()) return;
|
| 256 |
+
|
| 257 |
+
set({ isRunning: true, isThinking: true, errorBanner: '' });
|
| 258 |
+
const stepNum = s.stepCount + 1;
|
| 259 |
+
const logs = [...stateRef.current.terminalLogs,
|
| 260 |
+
{ text: '', type: 'info' },
|
| 261 |
+
{ text: `$ codearena step --step=${stepNum}`, type: 'command' },
|
| 262 |
+
{ text: 'Submitting fix…', type: 'info' },
|
| 263 |
+
];
|
| 264 |
+
set({ terminalLogs: logs });
|
| 265 |
+
|
| 266 |
+
try {
|
| 267 |
+
const data = await sendStep(s.code);
|
| 268 |
+
set({ isThinking: false });
|
| 269 |
+
|
| 270 |
+
const { observation, reward, done, info } = data;
|
| 271 |
+
const meta = info?.execution_metadata || {};
|
| 272 |
+
const rc = info?.reward_components || {};
|
| 273 |
+
const passed = meta.test_passed ?? 0;
|
| 274 |
+
const total = meta.test_total ?? 0;
|
| 275 |
+
const errors = meta.runtime_errors || '';
|
| 276 |
+
|
| 277 |
+
logs.push({ text: '─'.repeat(40), type: 'info' });
|
| 278 |
+
if (passed === total && total > 0) {
|
| 279 |
+
logs.push({ text: `✓ All ${total} tests passed`, type: 'success' });
|
| 280 |
+
} else {
|
| 281 |
+
logs.push({ text: `✗ ${passed}/${total} tests passed`, type: 'error' });
|
| 282 |
+
if (errors) errors.split('\n').slice(0, 4).forEach(l => logs.push({ text: l, type: 'error' }));
|
| 283 |
+
}
|
| 284 |
+
logs.push({ text: `Reward: ${reward.toFixed(4)} | Done: ${done}`, type: reward >= 0.7 ? 'success' : 'warning' });
|
| 285 |
+
|
| 286 |
+
if (done) {
|
| 287 |
+
logs.push({ text: '', type: 'info' });
|
| 288 |
+
logs.push({
|
| 289 |
+
text: reward >= 0.85 ? '🎉 Episode complete — fix accepted!' : '⚠ Episode ended.',
|
| 290 |
+
type: reward >= 0.85 ? 'success' : 'warning'
|
| 291 |
+
});
|
| 292 |
+
}
|
| 293 |
+
|
| 294 |
+
const feedbackText = (observation?.error_log || '') || (observation?.test_results || '');
|
| 295 |
+
|
| 296 |
+
set(prev => ({
|
| 297 |
+
terminalLogs: [...logs],
|
| 298 |
+
stepCount: stepNum,
|
| 299 |
+
rewards: [...prev.rewards, reward],
|
| 300 |
+
isDone: done,
|
| 301 |
+
isRunning: false,
|
| 302 |
+
rewardComponents: Object.keys(rc).length > 0 ? rc : prev.rewardComponents,
|
| 303 |
+
feedback: feedbackText || prev.feedback,
|
| 304 |
+
attempts: [...prev.attempts, { reward, passed, total }],
|
| 305 |
+
episodeHistory: done
|
| 306 |
+
? [{ taskId: prev.currentTaskId, reward, steps: stepNum, ts: new Date().toISOString() }, ...prev.episodeHistory].slice(0, 20)
|
| 307 |
+
: prev.episodeHistory,
|
| 308 |
+
}));
|
| 309 |
+
|
| 310 |
+
// Agent mode: if not done, auto-fix and retry
|
| 311 |
+
if (s.agentMode && !done) {
|
| 312 |
+
setTimeout(handleAIFix, 1000);
|
| 313 |
+
}
|
| 314 |
+
} catch (err) {
|
| 315 |
+
const logs2 = [...stateRef.current.terminalLogs, { text: `✗ Step failed: ${err.message}`, type: 'error' }];
|
| 316 |
+
set({ terminalLogs: logs2, isRunning: false, isThinking: false, errorBanner: `Step failed: ${err.message}` });
|
| 317 |
+
}
|
| 318 |
+
}, [set, handleAIFix]);
|
| 319 |
+
|
| 320 |
+
const isBusy = state.isRunning || state.isGenerating;
|
| 321 |
+
|
| 322 |
+
return (
|
| 323 |
+
<div className="h-screen w-screen flex flex-col overflow-hidden bg-[var(--bg-primary)]">
|
| 324 |
+
{/* Navbar */}
|
| 325 |
+
<nav className="h-11 flex items-center justify-between px-4 border-b border-[var(--border-subtle)] bg-[#070B14]">
|
| 326 |
+
<span className="text-xs font-bold tracking-wider">
|
| 327 |
+
Code<span className="text-emerald-400">Arena</span>
|
| 328 |
+
<span className="text-purple-400 ml-0.5">RL</span>
|
| 329 |
+
</span>
|
| 330 |
+
<div className="flex items-center gap-4">
|
| 331 |
+
{state.lastFixMethod && (
|
| 332 |
+
<span className="text-[9px] font-mono text-purple-400 border border-purple-500/30 px-2 py-0.5 rounded">
|
| 333 |
+
{state.lastFixMethod === 'ollama' ? '🤖 Ollama Fix' : '⚙️ Built-in Fix'}
|
| 334 |
+
</span>
|
| 335 |
+
)}
|
| 336 |
+
<div className="flex items-center gap-1.5 text-[10px] font-mono text-[var(--text-muted)]">
|
| 337 |
+
{state.serverStatus === 'online'
|
| 338 |
+
? <><Wifi size={11} className="text-emerald-400" /> FastAPI Online</>
|
| 339 |
+
: <><WifiOff size={11} className="text-red-400" /> Offline</>
|
| 340 |
+
}
|
| 341 |
+
</div>
|
| 342 |
+
</div>
|
| 343 |
+
</nav>
|
| 344 |
+
|
| 345 |
+
{/* Error Banner */}
|
| 346 |
+
<AnimatePresence>
|
| 347 |
+
{state.errorBanner && (
|
| 348 |
+
<motion.div
|
| 349 |
+
initial={{ height: 0, opacity: 0 }} animate={{ height: 'auto', opacity: 1 }} exit={{ height: 0, opacity: 0 }}
|
| 350 |
+
className="bg-red-500/10 border-b border-red-500/30 px-4 py-2 flex items-center justify-between"
|
| 351 |
+
>
|
| 352 |
+
<span className="text-[11px] font-mono text-red-300">{state.errorBanner}</span>
|
| 353 |
+
<button onClick={() => set({ errorBanner: '' })}><X size={14} className="text-red-400" /></button>
|
| 354 |
+
</motion.div>
|
| 355 |
+
)}
|
| 356 |
+
</AnimatePresence>
|
| 357 |
+
|
| 358 |
+
{/* 3-Panel Layout */}
|
| 359 |
+
<div className="flex-1 flex overflow-hidden">
|
| 360 |
+
{/* LEFT — Sidebar */}
|
| 361 |
+
<div className="w-[260px] min-w-[260px] flex flex-col">
|
| 362 |
+
<Sidebar
|
| 363 |
+
selectedTask={state.selectedTask}
|
| 364 |
+
onSelectTask={(id) => { resetEpisode(); set({ selectedTask: id }); }}
|
| 365 |
+
onStartEpisode={handleStartEpisode}
|
| 366 |
+
onReset={resetEpisode}
|
| 367 |
+
isRunning={isBusy}
|
| 368 |
+
episodeHistory={state.episodeHistory}
|
| 369 |
+
serverStatus={state.serverStatus}
|
| 370 |
+
/>
|
| 371 |
+
{/* Agent Mode Controls */}
|
| 372 |
+
<div className="p-3 border-t border-[var(--border-subtle)] bg-[var(--bg-secondary)] space-y-3">
|
| 373 |
+
<div className="flex items-center justify-between">
|
| 374 |
+
<span className="text-[10px] font-bold text-[var(--text-muted)] uppercase tracking-wider flex items-center gap-1">
|
| 375 |
+
<Sparkles size={10} className="text-purple-400" /> Agent Mode
|
| 376 |
+
</span>
|
| 377 |
+
<button
|
| 378 |
+
onClick={() => set({ agentMode: !state.agentMode })}
|
| 379 |
+
className={`w-8 h-4 rounded-full transition-colors relative ${state.agentMode ? 'bg-emerald-500' : 'bg-slate-700'}`}
|
| 380 |
+
>
|
| 381 |
+
<div className={`absolute top-0.5 left-0.5 w-3 h-3 bg-white rounded-full transition-transform ${state.agentMode ? 'translate-x-4' : ''}`} />
|
| 382 |
+
</button>
|
| 383 |
+
</div>
|
| 384 |
+
<div className="space-y-1">
|
| 385 |
+
<span className="text-[9px] text-[var(--text-muted)] uppercase tracking-wider">Ollama Model</span>
|
| 386 |
+
<input
|
| 387 |
+
className="w-full bg-[#0D1117] border border-[var(--border-subtle)] rounded px-2 py-1 text-[10px] text-emerald-400 font-mono focus:border-emerald-500 outline-none"
|
| 388 |
+
value={state.ollamaModel}
|
| 389 |
+
onChange={(e) => set({ ollamaModel: e.target.value })}
|
| 390 |
+
placeholder="llama3.2:latest"
|
| 391 |
+
/>
|
| 392 |
+
<p className="text-[9px] text-[var(--text-muted)]">Falls back to built-in if unavailable</p>
|
| 393 |
+
</div>
|
| 394 |
+
</div>
|
| 395 |
+
</div>
|
| 396 |
+
|
| 397 |
+
{/* CENTER — Editor + Terminal */}
|
| 398 |
+
<div className="flex-1 flex flex-col min-w-0 p-2 gap-2">
|
| 399 |
+
<div className="flex-[3] min-h-0 relative">
|
| 400 |
+
<CodeEditor
|
| 401 |
+
code={state.code}
|
| 402 |
+
onCodeChange={(val) => set({ code: val })}
|
| 403 |
+
onRunStep={handleRunStep}
|
| 404 |
+
isRunning={state.isRunning}
|
| 405 |
+
isThinking={state.isThinking}
|
| 406 |
+
stepCount={state.stepCount}
|
| 407 |
+
isDone={state.isDone}
|
| 408 |
+
/>
|
| 409 |
+
{/* AI Fix Button */}
|
| 410 |
+
{!state.isDone && !isBusy && (
|
| 411 |
+
<motion.button
|
| 412 |
+
whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}
|
| 413 |
+
onClick={handleAIFix}
|
| 414 |
+
className="absolute top-12 right-6 flex items-center gap-2 bg-purple-600/20 hover:bg-purple-600/40 border border-purple-500/50 text-purple-300 px-3 py-1.5 rounded-lg text-[10px] font-bold backdrop-blur-sm transition-all z-10"
|
| 415 |
+
>
|
| 416 |
+
<Sparkles size={12} /> AI FIX
|
| 417 |
+
</motion.button>
|
| 418 |
+
)}
|
| 419 |
+
{/* Generating Overlay */}
|
| 420 |
+
<AnimatePresence>
|
| 421 |
+
{state.isGenerating && (
|
| 422 |
+
<motion.div
|
| 423 |
+
initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
|
| 424 |
+
className="absolute inset-0 bg-[#0B0F19]/70 backdrop-blur-[2px] flex items-center justify-center z-50"
|
| 425 |
+
>
|
| 426 |
+
<div className="flex flex-col items-center gap-3 bg-[var(--bg-elevated)] border border-purple-500/30 rounded-xl p-6">
|
| 427 |
+
<Loader2 size={28} className="text-purple-400 animate-spin" />
|
| 428 |
+
<span className="text-xs font-mono text-purple-300">Generating fix…</span>
|
| 429 |
+
<span className="text-[10px] text-[var(--text-muted)]">Trying Ollama → built-in fallback</span>
|
| 430 |
+
</div>
|
| 431 |
+
</motion.div>
|
| 432 |
+
)}
|
| 433 |
+
</AnimatePresence>
|
| 434 |
+
</div>
|
| 435 |
+
<div className="flex-[2] min-h-0">
|
| 436 |
+
<Terminal logs={state.terminalLogs} isRunning={isBusy} />
|
| 437 |
+
</div>
|
| 438 |
+
</div>
|
| 439 |
+
|
| 440 |
+
{/* RIGHT — Reward Panel */}
|
| 441 |
+
<div className="w-[300px] min-w-[300px]">
|
| 442 |
+
<RewardPanel
|
| 443 |
+
rewards={state.rewards}
|
| 444 |
+
stepCount={state.stepCount}
|
| 445 |
+
isDone={state.isDone}
|
| 446 |
+
rewardComponents={state.rewardComponents}
|
| 447 |
+
feedback={state.feedback}
|
| 448 |
+
attempts={state.attempts}
|
| 449 |
+
/>
|
| 450 |
+
</div>
|
| 451 |
+
</div>
|
| 452 |
+
</div>
|
| 453 |
+
);
|
| 454 |
+
}
|
frontend/src/services/api.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* CodeArena API Service
|
| 3 |
+
* Connects to the FastAPI backend at localhost:7860 (proxied via Vite).
|
| 4 |
+
*
|
| 5 |
+
* Real endpoint contracts (from server/app.py):
|
| 6 |
+
* POST /reset → { task_id } → { status, observation, info }
|
| 7 |
+
* POST /step → { proposed_fix } → { observation, reward, done, info }
|
| 8 |
+
* GET /state → → { observation }
|
| 9 |
+
*/
|
| 10 |
+
|
| 11 |
+
const BASE = ''; // proxied through Vite — no prefix needed
|
| 12 |
+
|
| 13 |
+
// ─── Helpers ────────────────────────────────────────────────────
|
| 14 |
+
|
| 15 |
+
async function request(url, options = {}) {
|
| 16 |
+
const controller = new AbortController();
|
| 17 |
+
const timeout = setTimeout(() => controller.abort(), 30_000);
|
| 18 |
+
|
| 19 |
+
try {
|
| 20 |
+
const res = await fetch(`${BASE}${url}`, {
|
| 21 |
+
signal: controller.signal,
|
| 22 |
+
...options,
|
| 23 |
+
});
|
| 24 |
+
|
| 25 |
+
if (!res.ok) {
|
| 26 |
+
const text = await res.text().catch(() => '');
|
| 27 |
+
throw new Error(`HTTP ${res.status}: ${text || res.statusText}`);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
return await res.json();
|
| 31 |
+
} catch (err) {
|
| 32 |
+
if (err.name === 'AbortError') {
|
| 33 |
+
throw new Error(`Request to ${url} timed out after 30s`);
|
| 34 |
+
}
|
| 35 |
+
throw err;
|
| 36 |
+
} finally {
|
| 37 |
+
clearTimeout(timeout);
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
function post(url, body) {
|
| 42 |
+
return request(url, {
|
| 43 |
+
method: 'POST',
|
| 44 |
+
headers: { 'Content-Type': 'application/json' },
|
| 45 |
+
body: JSON.stringify(body),
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// ─── Public API ─────────────────────────────────────────────────
|
| 50 |
+
|
| 51 |
+
/**
|
| 52 |
+
* POST /reset
|
| 53 |
+
* @param {string} taskId - "easy", "medium", "hard", "auto", or exact ID like "easy-1"
|
| 54 |
+
* @returns {{ status, observation: { buggy_code, error_log, test_results, previous_attempts }, info: { task_id, difficulty } }}
|
| 55 |
+
*/
|
| 56 |
+
export async function resetTask(taskId = 'easy') {
|
| 57 |
+
return post('/reset', { task_id: taskId });
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
/**
|
| 61 |
+
* POST /step
|
| 62 |
+
* @param {string} proposedFix - The code fix to submit
|
| 63 |
+
* @returns {{ observation, reward: number, done: boolean, info: { execution_metadata, task_id, reward_components } }}
|
| 64 |
+
*/
|
| 65 |
+
export async function sendStep(proposedFix) {
|
| 66 |
+
return post('/step', { proposed_fix: proposedFix });
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
/**
|
| 70 |
+
* GET /state
|
| 71 |
+
* @returns {{ observation: { buggy_code, error_log, test_results, previous_attempts } }}
|
| 72 |
+
*/
|
| 73 |
+
export async function getState() {
|
| 74 |
+
return request('/state');
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
/**
|
| 78 |
+
* GET / (health check)
|
| 79 |
+
* @returns {{ status: "ok", environment: "CodeArena" }}
|
| 80 |
+
*/
|
| 81 |
+
export async function healthCheck() {
|
| 82 |
+
return request('/health');
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
/**
|
| 86 |
+
* POST /fix
|
| 87 |
+
* Uses built-in pattern fixer + optional Ollama.
|
| 88 |
+
* Passes reward + task_id for memory storage and adaptive prompting.
|
| 89 |
+
* @param {string} code - Buggy code
|
| 90 |
+
* @param {string} errorLog - Error output
|
| 91 |
+
* @param {string} ollamaUrl - Ollama server URL
|
| 92 |
+
* @param {string} model - Model name
|
| 93 |
+
* @param {number} reward - Current reward (for adaptive prompting)
|
| 94 |
+
* @param {string} taskId - Task ID (for memory retrieval)
|
| 95 |
+
* @returns {{ fixed_code, method, success, explanation, complexity, algo_hint, note? }}
|
| 96 |
+
*/
|
| 97 |
+
export async function generateFix(code, errorLog = '', ollamaUrl = 'http://localhost:11434', model = 'llama3.2:latest', reward = 0.0, taskId = '') {
|
| 98 |
+
return post('/fix', {
|
| 99 |
+
code,
|
| 100 |
+
error_log: errorLog,
|
| 101 |
+
ollama_url: ollamaUrl,
|
| 102 |
+
model,
|
| 103 |
+
use_ollama: true,
|
| 104 |
+
reward,
|
| 105 |
+
task_id: taskId,
|
| 106 |
+
});
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
/**
|
| 110 |
+
* GET /stats
|
| 111 |
+
* Returns complexity vs reward stats + episode history.
|
| 112 |
+
*/
|
| 113 |
+
export async function getStats() {
|
| 114 |
+
return request('/stats');
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
/**
|
| 118 |
+
* GET /memory
|
| 119 |
+
* Returns all stored best solutions from agent memory.
|
| 120 |
+
*/
|
| 121 |
+
export async function getMemory() {
|
| 122 |
+
return request('/memory');
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/**
|
| 126 |
+
* POST /run_raw
|
| 127 |
+
* Sandbox mode: executes arbitrary code and returns stdout, stderr, and execution time complexity.
|
| 128 |
+
* @param {string} code - The code to execute
|
| 129 |
+
* @returns {{ status: "success"|"error", stdout: string, stderr: string, execution_time: number, time_complexity_hint: string, reward: number, reward_components: object, done: boolean }}
|
| 130 |
+
*/
|
| 131 |
+
export async function runRaw(code) {
|
| 132 |
+
return post('/run_raw', { code });
|
| 133 |
+
}
|
frontend/vite.config.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
| 1 |
import { defineConfig } from 'vite'
|
| 2 |
import react from '@vitejs/plugin-react'
|
|
|
|
| 3 |
|
| 4 |
-
// https://vite.dev/config/
|
| 5 |
export default defineConfig({
|
| 6 |
-
plugins: [react()],
|
| 7 |
server: {
|
| 8 |
port: 3000,
|
| 9 |
proxy: {
|
| 10 |
-
// Proxy OpenEnv FastAPI calls → avoids CORS
|
| 11 |
'/reset': { target: 'http://localhost:7860', changeOrigin: true },
|
| 12 |
'/step': { target: 'http://localhost:7860', changeOrigin: true },
|
| 13 |
'/state': { target: 'http://localhost:7860', changeOrigin: true },
|
|
|
|
|
|
|
|
|
|
| 14 |
},
|
| 15 |
},
|
| 16 |
})
|
|
|
|
| 1 |
import { defineConfig } from 'vite'
|
| 2 |
import react from '@vitejs/plugin-react'
|
| 3 |
+
import tailwindcss from '@tailwindcss/vite'
|
| 4 |
|
|
|
|
| 5 |
export default defineConfig({
|
| 6 |
+
plugins: [react(), tailwindcss()],
|
| 7 |
server: {
|
| 8 |
port: 3000,
|
| 9 |
proxy: {
|
|
|
|
| 10 |
'/reset': { target: 'http://localhost:7860', changeOrigin: true },
|
| 11 |
'/step': { target: 'http://localhost:7860', changeOrigin: true },
|
| 12 |
'/state': { target: 'http://localhost:7860', changeOrigin: true },
|
| 13 |
+
'/health': { target: 'http://localhost:7860', changeOrigin: true },
|
| 14 |
+
'/fix': { target: 'http://localhost:7860', changeOrigin: true },
|
| 15 |
+
'/run_raw': { target: 'http://localhost:7860', changeOrigin: true },
|
| 16 |
},
|
| 17 |
},
|
| 18 |
})
|
improved_agent.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Improved CodeArena RL Agent with better prompting and debugging strategy.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import requests
|
| 8 |
+
import time
|
| 9 |
+
from typing import Dict, List, Tuple
|
| 10 |
+
|
| 11 |
+
class CodeArenaAgent:
|
| 12 |
+
def __init__(self, backend: str = "ollama", model: str = "llama3.2:latest"):
|
| 13 |
+
self.backend = backend
|
| 14 |
+
self.model = model
|
| 15 |
+
self.api_base = "http://localhost:11434"
|
| 16 |
+
self.api_key = None # Ollama doesn't need API key
|
| 17 |
+
|
| 18 |
+
def generate_fix(self, buggy_code: str, error_log: str, test_results: str,
|
| 19 |
+
previous_attempts: List[str], step_count: int) -> str:
|
| 20 |
+
"""Generate a fix using improved prompting strategy"""
|
| 21 |
+
|
| 22 |
+
# Build context from previous failures
|
| 23 |
+
context = ""
|
| 24 |
+
if previous_attempts:
|
| 25 |
+
context += f"\nPrevious attempts that failed:\n"
|
| 26 |
+
for i, attempt in enumerate(previous_attempts[-2:], 1): # Last 2 attempts
|
| 27 |
+
context += f"Attempt {len(previous_attempts)-len(previous_attempts[-2:])+i}: {attempt[:100]}...\n"
|
| 28 |
+
|
| 29 |
+
# Step-aware prompt
|
| 30 |
+
step_instructions = {
|
| 31 |
+
1: "Focus on fixing syntax errors and basic compilation issues first.",
|
| 32 |
+
2: "Now address logic errors and test failures from the previous attempt.",
|
| 33 |
+
3: "Optimize the solution and ensure all edge cases are handled.",
|
| 34 |
+
4: "Final attempt: ensure the solution is robust and handles all test cases.",
|
| 35 |
+
5: "Last chance: fix any remaining issues with a completely different approach."
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
prompt = f"""You are an expert Python debugger. Fix the buggy code below.
|
| 39 |
+
|
| 40 |
+
BUGGY CODE:
|
| 41 |
+
{buggy_code}
|
| 42 |
+
|
| 43 |
+
CURRENT ERRORS:
|
| 44 |
+
{error_log}
|
| 45 |
+
|
| 46 |
+
TEST RESULTS:
|
| 47 |
+
{test_results}
|
| 48 |
+
|
| 49 |
+
STEP {step_count} INSTRUCTIONS:
|
| 50 |
+
{step_instructions.get(step_count, "Fix all remaining issues.")}
|
| 51 |
+
|
| 52 |
+
{context}
|
| 53 |
+
|
| 54 |
+
REQUIREMENTS:
|
| 55 |
+
1. The code must compile without syntax errors
|
| 56 |
+
2. All tests must pass
|
| 57 |
+
3. Fix the ROOT CAUSE, not just symptoms
|
| 58 |
+
4. Do NOT repeat previous failed approaches
|
| 59 |
+
5. Ensure proper Python syntax and indentation
|
| 60 |
+
6. Return ONLY the corrected code, no explanations
|
| 61 |
+
|
| 62 |
+
Output the complete corrected Python code:"""
|
| 63 |
+
|
| 64 |
+
if not self.api_key and self.backend == "openai":
|
| 65 |
+
# Fallback for OpenAI without key
|
| 66 |
+
return self._fallback_fix(buggy_code, step_count)
|
| 67 |
+
|
| 68 |
+
try:
|
| 69 |
+
if self.backend == "ollama":
|
| 70 |
+
# Use Ollama API
|
| 71 |
+
import requests
|
| 72 |
+
response = requests.post(
|
| 73 |
+
f"{self.api_base}/api/generate",
|
| 74 |
+
json={
|
| 75 |
+
"model": self.model,
|
| 76 |
+
"prompt": prompt,
|
| 77 |
+
"stream": False,
|
| 78 |
+
"options": {
|
| 79 |
+
"temperature": 0.3,
|
| 80 |
+
"num_predict": 1000
|
| 81 |
+
}
|
| 82 |
+
},
|
| 83 |
+
timeout=30
|
| 84 |
+
)
|
| 85 |
+
response.raise_for_status()
|
| 86 |
+
result = response.json()
|
| 87 |
+
fix = result.get("response", "").strip()
|
| 88 |
+
else:
|
| 89 |
+
# Use OpenAI API
|
| 90 |
+
import openai
|
| 91 |
+
client = openai.OpenAI(api_key=self.api_key, base_url=self.base_url)
|
| 92 |
+
response = client.chat.completions.create(
|
| 93 |
+
model=self.model,
|
| 94 |
+
messages=[{"role": "user", "content": prompt}],
|
| 95 |
+
max_tokens=1000,
|
| 96 |
+
temperature=0.3
|
| 97 |
+
)
|
| 98 |
+
fix = response.choices[0].message.content.strip()
|
| 99 |
+
|
| 100 |
+
# Clean up common markdown artifacts
|
| 101 |
+
if fix.startswith("```python"):
|
| 102 |
+
fix = fix[9:]
|
| 103 |
+
if fix.startswith("```"):
|
| 104 |
+
fix = fix[3:]
|
| 105 |
+
if fix.endswith("```"):
|
| 106 |
+
fix = fix[:-3]
|
| 107 |
+
return fix.strip()
|
| 108 |
+
|
| 109 |
+
except Exception as e:
|
| 110 |
+
print(f"API Error: {e}")
|
| 111 |
+
return self._fallback_fix(buggy_code, step_count)
|
| 112 |
+
|
| 113 |
+
def _fallback_fix(self, buggy_code: str, step_count: int) -> str:
|
| 114 |
+
"""Simple fallback fix for when API is unavailable"""
|
| 115 |
+
print(f"[DEBUG] Fallback input code ({len(buggy_code)} chars): {repr(buggy_code[:100])}")
|
| 116 |
+
|
| 117 |
+
# Try to fix common syntax errors in the buggy code
|
| 118 |
+
fixed_code = buggy_code
|
| 119 |
+
|
| 120 |
+
# Fix 1: Add missing colons after function definitions
|
| 121 |
+
lines = fixed_code.split('\n')
|
| 122 |
+
for i, line in enumerate(lines):
|
| 123 |
+
stripped = line.strip()
|
| 124 |
+
if stripped.startswith('def ') and not stripped.endswith(':'):
|
| 125 |
+
lines[i] = line + ':'
|
| 126 |
+
print(f"[DEBUG] Added colon to line {i+1}")
|
| 127 |
+
|
| 128 |
+
fixed_code = '\n'.join(lines)
|
| 129 |
+
|
| 130 |
+
# Fix 2: Replace length() with len()
|
| 131 |
+
if 'length(' in fixed_code:
|
| 132 |
+
fixed_code = fixed_code.replace('length(', 'len(')
|
| 133 |
+
print("[DEBUG] Replaced length() with len()")
|
| 134 |
+
|
| 135 |
+
print(f"[DEBUG] Fallback output code ({len(fixed_code)} chars): {repr(fixed_code[:100])}")
|
| 136 |
+
return fixed_code
|
| 137 |
+
|
| 138 |
+
def run_episode(task_id: str = "easy-1", max_steps: int = 5) -> Dict:
|
| 139 |
+
"""Run a single episode with improved agent"""
|
| 140 |
+
agent = CodeArenaAgent()
|
| 141 |
+
|
| 142 |
+
print(f"\n🎯 Starting episode: {task_id}")
|
| 143 |
+
|
| 144 |
+
# Reset
|
| 145 |
+
try:
|
| 146 |
+
response = requests.post("http://localhost:7860/reset", json={"task_id": task_id}, timeout=10)
|
| 147 |
+
response.raise_for_status()
|
| 148 |
+
obs = response.json()
|
| 149 |
+
print(f"✅ Reset successful - task: {obs.get('task_id')}")
|
| 150 |
+
except Exception as e:
|
| 151 |
+
print(f"❌ Reset failed: {e}")
|
| 152 |
+
return {"success": False, "error": str(e)}
|
| 153 |
+
|
| 154 |
+
rewards = []
|
| 155 |
+
previous_attempts = []
|
| 156 |
+
done = False
|
| 157 |
+
step_count = 0
|
| 158 |
+
|
| 159 |
+
while not done and step_count < max_steps:
|
| 160 |
+
step_count += 1
|
| 161 |
+
|
| 162 |
+
# Generate fix
|
| 163 |
+
fix = agent.generate_fix(
|
| 164 |
+
buggy_code=obs.get('buggy_code', ''),
|
| 165 |
+
error_log=obs.get('error_log', ''),
|
| 166 |
+
test_results=obs.get('test_results', ''),
|
| 167 |
+
previous_attempts=previous_attempts,
|
| 168 |
+
step_count=step_count
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
print(f"\n🔧 Step {step_count}: Generated fix ({len(fix)} chars)")
|
| 172 |
+
|
| 173 |
+
# Step
|
| 174 |
+
try:
|
| 175 |
+
response = requests.post("http://localhost:7860/step",
|
| 176 |
+
json={"proposed_fix": fix},
|
| 177 |
+
timeout=20)
|
| 178 |
+
response.raise_for_status()
|
| 179 |
+
result = response.json()
|
| 180 |
+
|
| 181 |
+
reward = result.get('reward', 0)
|
| 182 |
+
done = result.get('done', False)
|
| 183 |
+
info = result.get('info', {})
|
| 184 |
+
|
| 185 |
+
rewards.append(reward)
|
| 186 |
+
previous_attempts.append(fix)
|
| 187 |
+
|
| 188 |
+
print(".3f")
|
| 189 |
+
print(f" Tests: {info.get('test_results', 'unknown')}")
|
| 190 |
+
print(f" Done: {done}")
|
| 191 |
+
|
| 192 |
+
if reward > 0.5:
|
| 193 |
+
print("🎉 Good reward! Continuing...")
|
| 194 |
+
elif reward < 0.1:
|
| 195 |
+
print("⚠️ Low reward - check debug logs")
|
| 196 |
+
|
| 197 |
+
obs = result.get('observation', {})
|
| 198 |
+
|
| 199 |
+
except Exception as e:
|
| 200 |
+
print(f"❌ Step failed: {e}")
|
| 201 |
+
break
|
| 202 |
+
|
| 203 |
+
# Summary
|
| 204 |
+
final_reward = rewards[-1] if rewards else 0
|
| 205 |
+
success = final_reward > 0.5
|
| 206 |
+
|
| 207 |
+
print(f"\n🏁 Episode complete!")
|
| 208 |
+
print(f" Steps: {step_count}")
|
| 209 |
+
print(".3f")
|
| 210 |
+
print(f" Success: {success}")
|
| 211 |
+
|
| 212 |
+
return {
|
| 213 |
+
"success": success,
|
| 214 |
+
"steps": step_count,
|
| 215 |
+
"final_reward": final_reward,
|
| 216 |
+
"rewards": rewards
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
def main():
|
| 220 |
+
import argparse
|
| 221 |
+
parser = argparse.ArgumentParser(description="Improved CodeArena RL Agent")
|
| 222 |
+
parser.add_argument("--task", default="easy-1", help="Task ID to run")
|
| 223 |
+
parser.add_argument("--episodes", type=int, default=1, help="Number of episodes")
|
| 224 |
+
parser.add_argument("--backend", default="ollama", choices=["ollama", "openai", "hf"], help="Backend to use")
|
| 225 |
+
parser.add_argument("--model", default="llama3.2:latest", help="Model name")
|
| 226 |
+
|
| 227 |
+
args = parser.parse_args()
|
| 228 |
+
|
| 229 |
+
print("🤖 Improved CodeArena Agent")
|
| 230 |
+
print("=" * 50)
|
| 231 |
+
print(f"Task: {args.task}")
|
| 232 |
+
print(f"Episodes: {args.episodes}")
|
| 233 |
+
print(f"Backend: {args.backend}")
|
| 234 |
+
print(f"Model: {args.model}")
|
| 235 |
+
|
| 236 |
+
results = []
|
| 237 |
+
for i in range(args.episodes):
|
| 238 |
+
print(f"\n📊 Episode {i+1}/{args.episodes}")
|
| 239 |
+
result = run_episode(args.task)
|
| 240 |
+
results.append(result)
|
| 241 |
+
|
| 242 |
+
# Log to CSV
|
| 243 |
+
import csv
|
| 244 |
+
with open("rewards_log.csv", "a", newline="") as f:
|
| 245 |
+
writer = csv.writer(f)
|
| 246 |
+
if os.path.getsize("rewards_log.csv") == 0: # Empty file
|
| 247 |
+
writer.writerow(["timestamp", "task_id", "step", "reward", "compile_score", "test_ratio", "efficiency_score"])
|
| 248 |
+
# Note: We don't have detailed component breakdown here, so we'll use placeholders
|
| 249 |
+
writer.writerow([
|
| 250 |
+
time.strftime("%Y-%m-%d %H:%M:%S"),
|
| 251 |
+
args.task,
|
| 252 |
+
result["steps"],
|
| 253 |
+
result["final_reward"],
|
| 254 |
+
0.0, 0.0, 0.0 # Placeholder values
|
| 255 |
+
])
|
| 256 |
+
|
| 257 |
+
# Summary
|
| 258 |
+
successes = sum(1 for r in results if r["success"])
|
| 259 |
+
avg_reward = sum(r["final_reward"] for r in results) / len(results)
|
| 260 |
+
|
| 261 |
+
print(f"\n📈 Summary:")
|
| 262 |
+
print(f" Success rate: {successes}/{len(results)} ({successes/len(results)*100:.1f}%)")
|
| 263 |
+
print(".3f")
|
| 264 |
+
if successes > 0:
|
| 265 |
+
print("🎉 Some episodes succeeded! Check rewards_log.csv and run plot_rewards.py")
|
| 266 |
+
else:
|
| 267 |
+
print("⚠️ All episodes failed. Check debug output and fix issues.")
|
| 268 |
+
|
| 269 |
+
if __name__ == "__main__":
|
| 270 |
+
main()
|
improved_prompts.json
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"base": "You are an expert Python debugger with reinforcement learning experience.\n\nLEARNED PATTERNS:\n- Always validate inputs first (if not x: handle edge case)\n- Use proper iteration patterns (for item in collection)\n- Implement early returns for efficiency\n- Focus on root cause, not symptoms\n\nBUGGY CODE:\n{buggy_code}\n\nCURRENT ERRORS:\n{error_log}\n\nTEST RESULTS:\n{test_results}\n\nREQUIREMENTS:\n1. Apply learned debugging patterns\n2. Fix compilation and logic errors\n3. Ensure all tests pass\n4. Return ONLY the corrected code\n\nOutput the complete corrected Python code:",
|
| 3 |
+
"rl_enhanced": "LEARNING FROM SUCCESS: {success_patterns}\n\nBUGGY CODE:\n{buggy_code}\n\nCURRENT ERRORS:\n{error_log}\n\nTEST RESULTS:\n{test_results}\n\nApply successful debugging strategies from similar problems.\n\nOutput ONLY the corrected Python code:"
|
| 4 |
+
}
|
install_finetune.bat
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@echo off
|
| 2 |
+
REM Installation script for PyTorch and fine-tuning dependencies (Windows)
|
| 3 |
+
REM Run this to set up your environment correctly
|
| 4 |
+
|
| 5 |
+
echo.
|
| 6 |
+
echo ======================================
|
| 7 |
+
echo CODEARENA FINE-TUNING SETUP
|
| 8 |
+
echo ======================================
|
| 9 |
+
echo.
|
| 10 |
+
|
| 11 |
+
REM Check Python version
|
| 12 |
+
echo Checking Python...
|
| 13 |
+
python --version
|
| 14 |
+
if errorlevel 1 (
|
| 15 |
+
echo ERROR: Python not found. Please install Python 3.9+ first.
|
| 16 |
+
pause
|
| 17 |
+
exit /b 1
|
| 18 |
+
)
|
| 19 |
+
echo.
|
| 20 |
+
|
| 21 |
+
REM Check GPU
|
| 22 |
+
echo Checking GPU availability...
|
| 23 |
+
python -c "
|
| 24 |
+
import torch
|
| 25 |
+
if torch.cuda.is_available():
|
| 26 |
+
print(f'GPU: {torch.cuda.get_device_name(0)}')
|
| 27 |
+
print(f'VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB')
|
| 28 |
+
else:
|
| 29 |
+
print('WARNING: No GPU detected - training will be slow')
|
| 30 |
+
" 2>nul || echo GPU check skipped
|
| 31 |
+
echo.
|
| 32 |
+
|
| 33 |
+
REM Install PyTorch (with CUDA 12.1 support)
|
| 34 |
+
echo Installing PyTorch...
|
| 35 |
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 -q
|
| 36 |
+
if errorlevel 1 (
|
| 37 |
+
echo ERROR: Failed to install PyTorch
|
| 38 |
+
echo Try installing manually:
|
| 39 |
+
echo pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121
|
| 40 |
+
pause
|
| 41 |
+
exit /b 1
|
| 42 |
+
)
|
| 43 |
+
echo PyTorch installed successfully
|
| 44 |
+
echo.
|
| 45 |
+
|
| 46 |
+
REM Install fine-tuning dependencies
|
| 47 |
+
echo Installing fine-tuning dependencies...
|
| 48 |
+
pip install -r requirements-finetune.txt -q
|
| 49 |
+
if errorlevel 1 (
|
| 50 |
+
echo ERROR: Failed to install dependencies
|
| 51 |
+
echo Try installing manually:
|
| 52 |
+
echo pip install -r requirements-finetune.txt
|
| 53 |
+
pause
|
| 54 |
+
exit /b 1
|
| 55 |
+
)
|
| 56 |
+
echo Dependencies installed successfully
|
| 57 |
+
echo.
|
| 58 |
+
|
| 59 |
+
REM Verify installation
|
| 60 |
+
echo Verifying installation...
|
| 61 |
+
python -c "
|
| 62 |
+
import torch
|
| 63 |
+
import transformers
|
| 64 |
+
import peft
|
| 65 |
+
import trl
|
| 66 |
+
import datasets
|
| 67 |
+
print(f'PyTorch: {torch.__version__}')
|
| 68 |
+
print(f'Transformers: {transformers.__version__}')
|
| 69 |
+
print(f'PEFT: {peft.__version__}')
|
| 70 |
+
print(f'TRL: {trl.__version__}')
|
| 71 |
+
print(f'Datasets: {datasets.__version__}')
|
| 72 |
+
"
|
| 73 |
+
echo.
|
| 74 |
+
|
| 75 |
+
echo ======================================
|
| 76 |
+
echo SETUP COMPLETE
|
| 77 |
+
echo ======================================
|
| 78 |
+
echo.
|
| 79 |
+
echo Next steps:
|
| 80 |
+
echo 1. Run fine-tuning (interactive):
|
| 81 |
+
echo python quickstart_finetune.py
|
| 82 |
+
echo.
|
| 83 |
+
echo 2. Or directly specify model:
|
| 84 |
+
echo python finetune_models.py --model llama3.2 --num-epochs 3
|
| 85 |
+
echo.
|
| 86 |
+
pause
|
install_finetune.sh
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Installation script for PyTorch and fine-tuning dependencies
|
| 3 |
+
# Run this to set up your environment correctly
|
| 4 |
+
|
| 5 |
+
set -e # Exit on error
|
| 6 |
+
|
| 7 |
+
echo "======================================"
|
| 8 |
+
echo "CODEARENA FINE-TUNING SETUP"
|
| 9 |
+
echo "======================================"
|
| 10 |
+
echo ""
|
| 11 |
+
|
| 12 |
+
# Check Python version
|
| 13 |
+
python_version=$(python --version 2>&1 | awk '{print $2}')
|
| 14 |
+
echo "✓ Python version: $python_version"
|
| 15 |
+
echo ""
|
| 16 |
+
|
| 17 |
+
# Detect CUDA/GPU
|
| 18 |
+
echo "Checking GPU availability..."
|
| 19 |
+
python -c "
|
| 20 |
+
import torch
|
| 21 |
+
if torch.cuda.is_available():
|
| 22 |
+
print(f'✓ GPU: {torch.cuda.get_device_name(0)}')
|
| 23 |
+
print(f' VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB')
|
| 24 |
+
else:
|
| 25 |
+
print('⚠ No GPU detected - training will use CPU (very slow)')
|
| 26 |
+
" || echo "GPU check failed (this is OK if running on CPU-only system)"
|
| 27 |
+
echo ""
|
| 28 |
+
|
| 29 |
+
# Install PyTorch with CUDA 12.1 support (compatible with modern GPUs)
|
| 30 |
+
echo "Installing PyTorch..."
|
| 31 |
+
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121 -q
|
| 32 |
+
echo "✓ PyTorch installed"
|
| 33 |
+
echo ""
|
| 34 |
+
|
| 35 |
+
# Install fine-tuning dependencies
|
| 36 |
+
echo "Installing fine-tuning dependencies..."
|
| 37 |
+
pip install -r requirements-finetune.txt -q
|
| 38 |
+
echo "✓ Dependencies installed"
|
| 39 |
+
echo ""
|
| 40 |
+
|
| 41 |
+
# Verify installation
|
| 42 |
+
echo "Verifying installation..."
|
| 43 |
+
python -c "
|
| 44 |
+
import torch
|
| 45 |
+
import transformers
|
| 46 |
+
import peft
|
| 47 |
+
import trl
|
| 48 |
+
import datasets
|
| 49 |
+
|
| 50 |
+
print(f'✓ PyTorch: {torch.__version__}')
|
| 51 |
+
print(f'✓ Transformers: {transformers.__version__}')
|
| 52 |
+
print(f'✓ PEFT: {peft.__version__}')
|
| 53 |
+
print(f'✓ TRL: {trl.__version__}')
|
| 54 |
+
print(f'✓ Datasets: {datasets.__version__}')
|
| 55 |
+
"
|
| 56 |
+
echo ""
|
| 57 |
+
|
| 58 |
+
echo "======================================"
|
| 59 |
+
echo "SETUP COMPLETE"
|
| 60 |
+
echo "======================================"
|
| 61 |
+
echo ""
|
| 62 |
+
echo "Next steps:"
|
| 63 |
+
echo "1. Run fine-tuning:"
|
| 64 |
+
echo " python quickstart_finetune.py"
|
| 65 |
+
echo ""
|
| 66 |
+
echo "2. Or directly specify model:"
|
| 67 |
+
echo " python finetune_models.py --model llama3.2 --num-epochs 3"
|
| 68 |
+
echo ""
|
merge_adapter.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import torch
|
| 4 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 5 |
+
from peft import PeftModel
|
| 6 |
+
|
| 7 |
+
def merge_and_save(base_model_name: str, adapter_path: str, output_path: str):
|
| 8 |
+
print(f"Loading base model: {base_model_name}...")
|
| 9 |
+
# Load base model on CPU
|
| 10 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 11 |
+
base_model_name,
|
| 12 |
+
torch_dtype=torch.float32, # Safe for CPU
|
| 13 |
+
device_map="cpu",
|
| 14 |
+
low_cpu_mem_usage=True
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
print("Loading tokenizer from base model...")
|
| 18 |
+
tokenizer = AutoTokenizer.from_pretrained(base_model_name)
|
| 19 |
+
|
| 20 |
+
print(f"Applying LoRA adapter from {adapter_path}...")
|
| 21 |
+
model = PeftModel.from_pretrained(base_model, adapter_path)
|
| 22 |
+
|
| 23 |
+
print("Merging weights (this may take a few minutes and use system RAM)...")
|
| 24 |
+
merged_model = model.merge_and_unload()
|
| 25 |
+
|
| 26 |
+
print(f"Saving merged model to {output_path} (Using PyTorch chunks to save memory)...")
|
| 27 |
+
merged_model.save_pretrained(
|
| 28 |
+
output_path,
|
| 29 |
+
safe_serialization=False,
|
| 30 |
+
max_shard_size="1GB"
|
| 31 |
+
)
|
| 32 |
+
tokenizer.save_pretrained(output_path)
|
| 33 |
+
print("Done! The model is now a standalone Hugging Face model.")
|
| 34 |
+
|
| 35 |
+
if __name__ == "__main__":
|
| 36 |
+
ADAPTER_DIR = r"E:\meta\gemma-code-optimizer"
|
| 37 |
+
BASE_MODEL = "google/gemma-2b-it"
|
| 38 |
+
MERGED_DIR = r"E:\meta\gemma-merged"
|
| 39 |
+
|
| 40 |
+
if not os.path.exists(MERGED_DIR):
|
| 41 |
+
os.makedirs(MERGED_DIR)
|
| 42 |
+
|
| 43 |
+
merge_and_save(BASE_MODEL, ADAPTER_DIR, MERGED_DIR)
|
ollama_rl_rollout.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import csv
|
| 3 |
+
import json
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
import httpx
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
SYSTEM_PROMPT = (
|
| 11 |
+
"You are an expert Python code repair agent. "
|
| 12 |
+
"Fix the buggy Python code and return ONLY raw Python code."
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def clean_code(text: str) -> str:
|
| 17 |
+
text = (text or "").strip()
|
| 18 |
+
if text.startswith("```python"):
|
| 19 |
+
text = text[9:]
|
| 20 |
+
elif text.startswith("```"):
|
| 21 |
+
text = text[3:]
|
| 22 |
+
if text.endswith("```"):
|
| 23 |
+
text = text[:-3]
|
| 24 |
+
return text.strip()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def ollama_generate(client: httpx.Client, model: str, prompt: str, base_url: str) -> str:
|
| 28 |
+
def try_chat() -> str:
|
| 29 |
+
payload = {
|
| 30 |
+
"model": model,
|
| 31 |
+
"messages": [
|
| 32 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 33 |
+
{"role": "user", "content": prompt},
|
| 34 |
+
],
|
| 35 |
+
"stream": False,
|
| 36 |
+
"options": {
|
| 37 |
+
"temperature": 0.2,
|
| 38 |
+
"max_tokens": 512,
|
| 39 |
+
"top_p": 0.9,
|
| 40 |
+
},
|
| 41 |
+
}
|
| 42 |
+
resp = client.post(f"{base_url}/api/chat", json=payload, timeout=90.0)
|
| 43 |
+
resp.raise_for_status()
|
| 44 |
+
data = resp.json()
|
| 45 |
+
return clean_code(data.get("message", {}).get("content", ""))
|
| 46 |
+
|
| 47 |
+
def try_generate() -> str:
|
| 48 |
+
payload = {
|
| 49 |
+
"model": model,
|
| 50 |
+
"prompt": prompt,
|
| 51 |
+
"stream": False,
|
| 52 |
+
"options": {
|
| 53 |
+
"temperature": 0.2,
|
| 54 |
+
"num_predict": 512,
|
| 55 |
+
},
|
| 56 |
+
}
|
| 57 |
+
resp = client.post(f"{base_url}/api/generate", json=payload, timeout=90.0)
|
| 58 |
+
if resp.status_code == 404 or resp.status_code == 405:
|
| 59 |
+
return ""
|
| 60 |
+
resp.raise_for_status()
|
| 61 |
+
data = resp.json()
|
| 62 |
+
return clean_code(data.get("response", "") or data.get("text", ""))
|
| 63 |
+
|
| 64 |
+
code = try_generate()
|
| 65 |
+
if not code:
|
| 66 |
+
code = try_chat()
|
| 67 |
+
if not code:
|
| 68 |
+
raise RuntimeError("Ollama returned no valid code from /api/generate or /api/chat.")
|
| 69 |
+
return code
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def run_episode(env_client: httpx.Client, ollama_client: httpx.Client, model: str, task_id: str, max_steps: int, env_url: str, ollama_url: str):
|
| 73 |
+
reset = env_client.post(f"{env_url}/reset", json={"task_id": task_id}, timeout=60.0)
|
| 74 |
+
reset.raise_for_status()
|
| 75 |
+
obs_json = reset.json()
|
| 76 |
+
|
| 77 |
+
steps = []
|
| 78 |
+
rewards = []
|
| 79 |
+
done = False
|
| 80 |
+
for step in range(1, max_steps + 1):
|
| 81 |
+
if done:
|
| 82 |
+
break
|
| 83 |
+
obs = obs_json.get("observation", {})
|
| 84 |
+
buggy_code = obs.get("buggy_code", "")
|
| 85 |
+
error_log = obs.get("error_log", "")
|
| 86 |
+
test_results = obs.get("test_results", "")
|
| 87 |
+
|
| 88 |
+
user_prompt = (
|
| 89 |
+
f"Fix this buggy Python code:\n\n{buggy_code}\n\n"
|
| 90 |
+
f"Error log:\n{error_log}\n\n"
|
| 91 |
+
f"Test results:\n{test_results}\n"
|
| 92 |
+
)
|
| 93 |
+
try:
|
| 94 |
+
proposed_fix = ollama_generate(ollama_client, model, user_prompt, ollama_url)
|
| 95 |
+
except Exception:
|
| 96 |
+
proposed_fix = buggy_code or "pass"
|
| 97 |
+
|
| 98 |
+
step_resp = env_client.post(
|
| 99 |
+
f"{env_url}/step",
|
| 100 |
+
json={"proposed_fix": proposed_fix},
|
| 101 |
+
timeout=90.0,
|
| 102 |
+
)
|
| 103 |
+
step_resp.raise_for_status()
|
| 104 |
+
step_data = step_resp.json()
|
| 105 |
+
reward = float(step_data.get("reward", 0.001))
|
| 106 |
+
reward = max(0.001, min(0.999, reward))
|
| 107 |
+
done = bool(step_data.get("done", False))
|
| 108 |
+
|
| 109 |
+
steps.append(
|
| 110 |
+
{
|
| 111 |
+
"step": step,
|
| 112 |
+
"prompt": user_prompt,
|
| 113 |
+
"proposed_fix": proposed_fix,
|
| 114 |
+
"reward": reward,
|
| 115 |
+
"done": done,
|
| 116 |
+
"task_id": step_data.get("info", {}).get("task_id", task_id),
|
| 117 |
+
"reward_components": step_data.get("info", {}).get("reward_components", {}),
|
| 118 |
+
}
|
| 119 |
+
)
|
| 120 |
+
rewards.append(reward)
|
| 121 |
+
obs_json = step_data
|
| 122 |
+
|
| 123 |
+
return {
|
| 124 |
+
"episode_reward_mean": sum(rewards) / len(rewards) if rewards else 0.001,
|
| 125 |
+
"episode_reward_best": max(rewards) if rewards else 0.001,
|
| 126 |
+
"episode_reward_last": rewards[-1] if rewards else 0.001,
|
| 127 |
+
"steps": steps,
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def main():
|
| 132 |
+
parser = argparse.ArgumentParser()
|
| 133 |
+
parser.add_argument("--model", default="llama3.2:latest")
|
| 134 |
+
parser.add_argument("--ollama-url", default="http://127.0.0.1:11434")
|
| 135 |
+
parser.add_argument("--env-url", default="http://127.0.0.1:7860")
|
| 136 |
+
parser.add_argument("--episodes", type=int, default=30)
|
| 137 |
+
parser.add_argument("--max-steps", type=int, default=5)
|
| 138 |
+
parser.add_argument("--output-dir", default="ollama_rl_out")
|
| 139 |
+
args = parser.parse_args()
|
| 140 |
+
|
| 141 |
+
out_dir = Path(args.output_dir)
|
| 142 |
+
out_dir.mkdir(parents=True, exist_ok=True)
|
| 143 |
+
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
| 144 |
+
traj_path = out_dir / f"trajectories_{ts}.jsonl"
|
| 145 |
+
summary_path = out_dir / f"summary_{ts}.csv"
|
| 146 |
+
|
| 147 |
+
tasks = ["easy", "medium", "hard", "type_errors-1", "security_bugs-1"]
|
| 148 |
+
episodes = []
|
| 149 |
+
with httpx.Client() as env_client, httpx.Client() as ollama_client:
|
| 150 |
+
for idx in range(args.episodes):
|
| 151 |
+
task = tasks[idx % len(tasks)]
|
| 152 |
+
ep = run_episode(
|
| 153 |
+
env_client,
|
| 154 |
+
ollama_client,
|
| 155 |
+
args.model,
|
| 156 |
+
task,
|
| 157 |
+
args.max_steps,
|
| 158 |
+
args.env_url,
|
| 159 |
+
args.ollama_url,
|
| 160 |
+
)
|
| 161 |
+
ep["episode_idx"] = idx + 1
|
| 162 |
+
ep["task_seed"] = task
|
| 163 |
+
episodes.append(ep)
|
| 164 |
+
|
| 165 |
+
with traj_path.open("w", encoding="utf-8") as f:
|
| 166 |
+
for ep in episodes:
|
| 167 |
+
f.write(json.dumps(ep, ensure_ascii=True) + "\n")
|
| 168 |
+
|
| 169 |
+
with summary_path.open("w", newline="", encoding="utf-8") as f:
|
| 170 |
+
writer = csv.writer(f)
|
| 171 |
+
writer.writerow(["episode", "task_seed", "mean_reward", "best_reward", "last_reward"])
|
| 172 |
+
for ep in episodes:
|
| 173 |
+
writer.writerow(
|
| 174 |
+
[
|
| 175 |
+
ep["episode_idx"],
|
| 176 |
+
ep["task_seed"],
|
| 177 |
+
ep["episode_reward_mean"],
|
| 178 |
+
ep["episode_reward_best"],
|
| 179 |
+
ep["episode_reward_last"],
|
| 180 |
+
]
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
all_mean = [e["episode_reward_mean"] for e in episodes]
|
| 184 |
+
print(f"episodes={len(episodes)}")
|
| 185 |
+
print(f"start_mean_reward={all_mean[0]:.4f}")
|
| 186 |
+
print(f"end_mean_reward={all_mean[-1]:.4f}")
|
| 187 |
+
print(f"best_mean_reward={max(all_mean):.4f}")
|
| 188 |
+
print(f"avg_mean_reward={(sum(all_mean)/len(all_mean)):.4f}")
|
| 189 |
+
print(f"trajectories={traj_path}")
|
| 190 |
+
print(f"summary={summary_path}")
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
if __name__ == "__main__":
|
| 194 |
+
main()
|
optimized_rl_trainer.py
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Optimized RL Trainer for CodeArena with speed and efficiency improvements.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import asyncio
|
| 7 |
+
import aiohttp
|
| 8 |
+
import time
|
| 9 |
+
import json
|
| 10 |
+
import random
|
| 11 |
+
from typing import List, Dict, Tuple
|
| 12 |
+
from collections import deque
|
| 13 |
+
import numpy as np
|
| 14 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 15 |
+
import threading
|
| 16 |
+
|
| 17 |
+
class OptimizedCodeArenaRLTrainer:
|
| 18 |
+
def __init__(self, model_name: str = "llama3.2:latest", memory_size: int = 2000):
|
| 19 |
+
self.model_name = model_name
|
| 20 |
+
self.api_base = "http://localhost:11434"
|
| 21 |
+
|
| 22 |
+
# Optimized memory management
|
| 23 |
+
self.memory = deque(maxlen=memory_size)
|
| 24 |
+
self.trajectories = []
|
| 25 |
+
self.successful_trajectories = []
|
| 26 |
+
|
| 27 |
+
# Performance optimizations
|
| 28 |
+
self.executor = ThreadPoolExecutor(max_workers=4)
|
| 29 |
+
self.session = None # For async HTTP
|
| 30 |
+
self.response_cache = {}
|
| 31 |
+
self.prompt_cache = {}
|
| 32 |
+
|
| 33 |
+
# RL parameters (optimized)
|
| 34 |
+
self.learning_rate = 0.001
|
| 35 |
+
self.gamma = 0.95
|
| 36 |
+
self.epsilon = 1.0
|
| 37 |
+
self.epsilon_min = 0.05 # Lower minimum for more exploitation
|
| 38 |
+
self.epsilon_decay = 0.997 # Slower decay
|
| 39 |
+
self.batch_size = 64 # Larger batches
|
| 40 |
+
|
| 41 |
+
# Performance tracking
|
| 42 |
+
self.start_time = time.time()
|
| 43 |
+
self.episode_times = []
|
| 44 |
+
self.api_call_times = []
|
| 45 |
+
|
| 46 |
+
# Adaptive difficulty
|
| 47 |
+
self.current_difficulty = "easy"
|
| 48 |
+
self.task_performance = {"easy": [], "medium": [], "hard": []}
|
| 49 |
+
|
| 50 |
+
async def init_session(self):
|
| 51 |
+
"""Initialize async HTTP session"""
|
| 52 |
+
if self.session is None:
|
| 53 |
+
self.session = aiohttp.ClientSession()
|
| 54 |
+
|
| 55 |
+
async def close_session(self):
|
| 56 |
+
"""Close async session"""
|
| 57 |
+
if self.session:
|
| 58 |
+
await self.session.close()
|
| 59 |
+
self.session = None
|
| 60 |
+
|
| 61 |
+
async def generate_fix_optimized(self, prompt: str) -> str:
|
| 62 |
+
"""Optimized fix generation with caching and async"""
|
| 63 |
+
# Check cache first
|
| 64 |
+
cache_key = hash(prompt)
|
| 65 |
+
if cache_key in self.response_cache:
|
| 66 |
+
return self.response_cache[cache_key]
|
| 67 |
+
|
| 68 |
+
start_time = time.time()
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
payload = {
|
| 72 |
+
"model": self.model_name,
|
| 73 |
+
"prompt": prompt,
|
| 74 |
+
"stream": False,
|
| 75 |
+
"options": {
|
| 76 |
+
"temperature": max(0.1, self.epsilon),
|
| 77 |
+
"num_predict": 600, # Shorter for speed
|
| 78 |
+
"top_p": 0.9,
|
| 79 |
+
"num_thread": 4 # Use multiple threads
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
async with self.session.post(f"{self.api_base}/api/generate",
|
| 84 |
+
json=payload, timeout=15) as response:
|
| 85 |
+
result = await response.json()
|
| 86 |
+
fix = result.get("response", "").strip()
|
| 87 |
+
|
| 88 |
+
# Clean response
|
| 89 |
+
if fix.startswith("```python"):
|
| 90 |
+
fix = fix[9:]
|
| 91 |
+
if fix.startswith("```"):
|
| 92 |
+
fix = fix[3:]
|
| 93 |
+
if fix.endswith("```"):
|
| 94 |
+
fix = fix[:-3]
|
| 95 |
+
fix = fix.strip()
|
| 96 |
+
|
| 97 |
+
# Cache successful responses
|
| 98 |
+
if fix and len(fix) > 10:
|
| 99 |
+
self.response_cache[cache_key] = fix
|
| 100 |
+
|
| 101 |
+
api_time = time.time() - start_time
|
| 102 |
+
self.api_call_times.append(api_time)
|
| 103 |
+
|
| 104 |
+
return fix
|
| 105 |
+
|
| 106 |
+
except Exception as e:
|
| 107 |
+
print(f"API Error: {e}")
|
| 108 |
+
return "def placeholder():\n pass"
|
| 109 |
+
|
| 110 |
+
def get_optimized_prompt(self, buggy_code: str, error_log: str,
|
| 111 |
+
test_results: str, step_count: int,
|
| 112 |
+
previous_attempts: List[str]) -> str:
|
| 113 |
+
"""Generate optimized prompt with caching"""
|
| 114 |
+
|
| 115 |
+
# Create cache key
|
| 116 |
+
state_key = f"{hash(buggy_code)}|{hash(error_log)}|{hash(test_results)}|{step_count}"
|
| 117 |
+
if state_key in self.prompt_cache:
|
| 118 |
+
return self.prompt_cache[state_key]
|
| 119 |
+
|
| 120 |
+
# Optimized prompt template
|
| 121 |
+
prompt = f"""Fix Python code - Step {step_count}:
|
| 122 |
+
|
| 123 |
+
CODE:
|
| 124 |
+
{buggy_code}
|
| 125 |
+
|
| 126 |
+
ERRORS:
|
| 127 |
+
{error_log}
|
| 128 |
+
|
| 129 |
+
TESTS:
|
| 130 |
+
{test_results}
|
| 131 |
+
|
| 132 |
+
Requirements: Compile, pass tests, fix root cause. Return only code."""
|
| 133 |
+
|
| 134 |
+
self.prompt_cache[state_key] = prompt
|
| 135 |
+
return prompt
|
| 136 |
+
|
| 137 |
+
async def run_episode_async(self, task_id: str, episode_num: int) -> Dict:
|
| 138 |
+
"""Run episode with async optimizations"""
|
| 139 |
+
episode_start = time.time()
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
# Async reset
|
| 143 |
+
async with self.session.post("http://localhost:7860/reset",
|
| 144 |
+
json={"task_id": task_id}, timeout=10) as response:
|
| 145 |
+
obs = await response.json()
|
| 146 |
+
|
| 147 |
+
except Exception as e:
|
| 148 |
+
print(f"Episode {episode_num} reset failed: {e}")
|
| 149 |
+
return {"success": False, "reward": 0, "steps": 0, "time": time.time() - episode_start}
|
| 150 |
+
|
| 151 |
+
rewards = []
|
| 152 |
+
previous_attempts = []
|
| 153 |
+
done = False
|
| 154 |
+
step_count = 0
|
| 155 |
+
|
| 156 |
+
while not done and step_count < 5:
|
| 157 |
+
step_count += 1
|
| 158 |
+
|
| 159 |
+
# Generate optimized prompt
|
| 160 |
+
prompt = self.get_optimized_prompt(
|
| 161 |
+
obs.get('buggy_code', ''),
|
| 162 |
+
obs.get('error_log', ''),
|
| 163 |
+
obs.get('test_results', ''),
|
| 164 |
+
step_count,
|
| 165 |
+
previous_attempts
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
# Async fix generation
|
| 169 |
+
fix = await self.generate_fix_optimized(prompt)
|
| 170 |
+
|
| 171 |
+
try:
|
| 172 |
+
# Async step execution
|
| 173 |
+
async with self.session.post("http://localhost:7860/step",
|
| 174 |
+
json={"proposed_fix": fix}, timeout=20) as response:
|
| 175 |
+
result = await response.json()
|
| 176 |
+
|
| 177 |
+
reward = result.get('reward', 0)
|
| 178 |
+
done = result.get('done', False)
|
| 179 |
+
obs = result.get('observation', {})
|
| 180 |
+
|
| 181 |
+
rewards.append(reward)
|
| 182 |
+
previous_attempts.append(fix)
|
| 183 |
+
|
| 184 |
+
except Exception as e:
|
| 185 |
+
print(f"Episode {episode_num} step {step_count} failed: {e}")
|
| 186 |
+
break
|
| 187 |
+
|
| 188 |
+
episode_time = time.time() - episode_start
|
| 189 |
+
self.episode_times.append(episode_time)
|
| 190 |
+
|
| 191 |
+
final_reward = rewards[-1] if rewards else 0
|
| 192 |
+
success = final_reward > 0.5
|
| 193 |
+
|
| 194 |
+
return {
|
| 195 |
+
"episode": episode_num,
|
| 196 |
+
"task_id": task_id,
|
| 197 |
+
"success": success,
|
| 198 |
+
"reward": final_reward,
|
| 199 |
+
"steps": step_count,
|
| 200 |
+
"time": episode_time
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
async def train_async(self, episodes: int = 50):
|
| 204 |
+
"""Async training loop for maximum speed"""
|
| 205 |
+
await self.init_session()
|
| 206 |
+
|
| 207 |
+
print("🚀 Starting Optimized Async RL Training")
|
| 208 |
+
print("=" * 60)
|
| 209 |
+
print(f"Model: {self.model_name}")
|
| 210 |
+
print(f"Episodes: {episodes}")
|
| 211 |
+
print(f"Async: Enabled")
|
| 212 |
+
print(f"Workers: 4 threads")
|
| 213 |
+
|
| 214 |
+
results = []
|
| 215 |
+
batch_size = 5 # Run 5 episodes concurrently
|
| 216 |
+
|
| 217 |
+
for batch_start in range(0, episodes, batch_size):
|
| 218 |
+
batch_end = min(batch_start + batch_size, episodes)
|
| 219 |
+
batch_tasks = []
|
| 220 |
+
|
| 221 |
+
# Create batch of concurrent episodes
|
| 222 |
+
for i in range(batch_start, batch_end):
|
| 223 |
+
task_id = f"{self.current_difficulty}-{random.randint(1, 3)}"
|
| 224 |
+
task = self.run_episode_async(task_id, i + 1)
|
| 225 |
+
batch_tasks.append(task)
|
| 226 |
+
|
| 227 |
+
# Execute batch concurrently
|
| 228 |
+
batch_start_time = time.time()
|
| 229 |
+
batch_results = await asyncio.gather(*batch_tasks, return_exceptions=True)
|
| 230 |
+
batch_time = time.time() - batch_start_time
|
| 231 |
+
|
| 232 |
+
# Process results
|
| 233 |
+
for result in batch_results:
|
| 234 |
+
if isinstance(result, Exception):
|
| 235 |
+
print(f"Batch error: {result}")
|
| 236 |
+
continue
|
| 237 |
+
|
| 238 |
+
results.append(result)
|
| 239 |
+
|
| 240 |
+
# Update difficulty if needed
|
| 241 |
+
if result["success"] and result["reward"] > 0.7:
|
| 242 |
+
self.task_performance[self.current_difficulty].append(result["reward"])
|
| 243 |
+
|
| 244 |
+
# Progress tracking
|
| 245 |
+
if len(results) % 10 == 0:
|
| 246 |
+
recent = results[-10:]
|
| 247 |
+
success_rate = sum(1 for r in recent if r["success"]) / len(recent)
|
| 248 |
+
avg_reward = sum(r["reward"] for r in recent) / len(recent)
|
| 249 |
+
avg_time = sum(r["time"] for r in recent) / len(recent)
|
| 250 |
+
|
| 251 |
+
print(f"Ep {len(results):3d} | Success: {success_rate:.1%} | Reward: {avg_reward:.3f} | Time: {avg_time:.2f}s")
|
| 252 |
+
print(f"📦 Batch {batch_start//batch_size + 1} completed in {batch_time:.1f}s")
|
| 253 |
+
|
| 254 |
+
await self.close_session()
|
| 255 |
+
return results
|
| 256 |
+
|
| 257 |
+
def print_performance_stats(self, results: List[Dict]):
|
| 258 |
+
"""Print detailed performance statistics"""
|
| 259 |
+
print("\n" + "=" * 60)
|
| 260 |
+
print("📊 PERFORMANCE STATISTICS")
|
| 261 |
+
print("=" * 60)
|
| 262 |
+
|
| 263 |
+
total_time = time.time() - self.start_time
|
| 264 |
+
total_episodes = len(results)
|
| 265 |
+
successful = sum(1 for r in results if r["success"])
|
| 266 |
+
|
| 267 |
+
print(f"⏱️ Total time: {total_time:.1f}s")
|
| 268 |
+
print(f"🎯 Success rate: {successful}/{total_episodes} ({successful/total_episodes:.1%})")
|
| 269 |
+
print(f"💰 Average reward: {sum(r['reward'] for r in results)/len(results):.3f}")
|
| 270 |
+
if self.episode_times:
|
| 271 |
+
print(f"⚡ Average episode time: {sum(self.episode_times)/len(self.episode_times):.3f}s")
|
| 272 |
+
print(f"🐌 Slowest episode: {max(self.episode_times):.3f}s")
|
| 273 |
+
print(f"🚀 Fastest episode: {min(self.episode_times):.3f}s")
|
| 274 |
+
if self.api_call_times:
|
| 275 |
+
print(f"🌐 Average API call: {sum(self.api_call_times)/len(self.api_call_times):.3f}s")
|
| 276 |
+
print(f"📡 Slowest API call: {max(self.api_call_times):.3f}s")
|
| 277 |
+
print(f"💨 Fastest API call: {min(self.api_call_times):.3f}s")
|
| 278 |
+
print(f"�� Memory usage: {len(self.memory)} experiences")
|
| 279 |
+
print(f"🧠 Cache hits: {len(self.response_cache)} responses cached")
|
| 280 |
+
print(f"📝 Prompts cached: {len(self.prompt_cache)} states")
|
| 281 |
+
|
| 282 |
+
# Success rate over time
|
| 283 |
+
print(f"\n📈 Learning Progress:")
|
| 284 |
+
for i in range(0, len(results), 10):
|
| 285 |
+
batch = results[i:i+10]
|
| 286 |
+
if batch:
|
| 287 |
+
success_rate = sum(1 for r in batch if r["success"]) / len(batch)
|
| 288 |
+
avg_reward = sum(r["reward"] for r in batch) / len(batch)
|
| 289 |
+
print(f"Ep {i+1:2d}-{min(i+10, len(results)):2d}: Success {success_rate:.1%} | Reward {avg_reward:.3f}")
|
| 290 |
+
def main():
|
| 291 |
+
import argparse
|
| 292 |
+
parser = argparse.ArgumentParser(description="Optimized Async RL Training")
|
| 293 |
+
parser.add_argument("--episodes", type=int, default=50, help="Training episodes")
|
| 294 |
+
parser.add_argument("--model", default="llama3.2:latest", help="Ollama model")
|
| 295 |
+
parser.add_argument("--use_async", action="store_true", default=True, help="Use async training")
|
| 296 |
+
|
| 297 |
+
args = parser.parse_args()
|
| 298 |
+
|
| 299 |
+
print("⚡ Optimized CodeArena RL Trainer")
|
| 300 |
+
print("=" * 50)
|
| 301 |
+
print(f"Model: {args.model}")
|
| 302 |
+
print(f"Episodes: {args.episodes}")
|
| 303 |
+
print(f"Async: {args.use_async}")
|
| 304 |
+
|
| 305 |
+
trainer = OptimizedCodeArenaRLTrainer(args.model)
|
| 306 |
+
|
| 307 |
+
if args.use_async:
|
| 308 |
+
# Run async training
|
| 309 |
+
results = asyncio.run(trainer.train_async(args.episodes))
|
| 310 |
+
else:
|
| 311 |
+
# Fallback to sync (not implemented in this optimized version)
|
| 312 |
+
print("⚠️ Async training required for optimal performance")
|
| 313 |
+
return
|
| 314 |
+
|
| 315 |
+
# Save results
|
| 316 |
+
with open("optimized_rl_results.json", 'w') as f:
|
| 317 |
+
json.dump(results, f, indent=2)
|
| 318 |
+
|
| 319 |
+
trainer.print_performance_stats(results)
|
| 320 |
+
|
| 321 |
+
print("\n💾 Results saved to optimized_rl_results.json")
|
| 322 |
+
print("🎯 Optimization achieved: Async processing + caching + batching")
|
| 323 |
+
|
| 324 |
+
if __name__ == "__main__":
|
| 325 |
+
main()
|
push_to_hf.py
DELETED
|
@@ -1,33 +0,0 @@
|
|
| 1 |
-
"""Push changed files to Hugging Face Space."""
|
| 2 |
-
from huggingface_hub import HfApi
|
| 3 |
-
import os
|
| 4 |
-
|
| 5 |
-
TOKEN = os.environ.get("HF_TOKEN", "your_hf_token_here")
|
| 6 |
-
REPO_ID = "adityanaikhpt/codeareana"
|
| 7 |
-
REPO_TYPE = "space"
|
| 8 |
-
BASE = "e:/meta"
|
| 9 |
-
|
| 10 |
-
# Only the files that were modified
|
| 11 |
-
FILES_TO_PUSH = [
|
| 12 |
-
"server/grader.py",
|
| 13 |
-
]
|
| 14 |
-
|
| 15 |
-
api = HfApi(token=TOKEN)
|
| 16 |
-
|
| 17 |
-
print(f"Pushing to: {REPO_ID}")
|
| 18 |
-
for rel_path in FILES_TO_PUSH:
|
| 19 |
-
local_path = os.path.join(BASE, rel_path.replace("/", os.sep))
|
| 20 |
-
if os.path.exists(local_path):
|
| 21 |
-
print(f" Uploading: {rel_path} ...", end=" ", flush=True)
|
| 22 |
-
api.upload_file(
|
| 23 |
-
path_or_fileobj=local_path,
|
| 24 |
-
path_in_repo=rel_path,
|
| 25 |
-
repo_id=REPO_ID,
|
| 26 |
-
repo_type=REPO_TYPE,
|
| 27 |
-
commit_message=f"fix: clamp strictly to 0.01 and 0.99 to prevent .2f rounding to 1.00",
|
| 28 |
-
)
|
| 29 |
-
print("OK")
|
| 30 |
-
else:
|
| 31 |
-
print(f" SKIP (not found): {rel_path}")
|
| 32 |
-
|
| 33 |
-
print("\nDone. All files pushed successfully.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
quickstart_finetune.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Quick-start script for fine-tuning models on XCoder-80K dataset.
|
| 4 |
+
Run this script to automatically set up and fine-tune your model.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import sys
|
| 9 |
+
import subprocess
|
| 10 |
+
import logging
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
def check_cuda():
|
| 17 |
+
"""Check if CUDA is available."""
|
| 18 |
+
try:
|
| 19 |
+
import torch
|
| 20 |
+
cuda_available = torch.cuda.is_available()
|
| 21 |
+
if cuda_available:
|
| 22 |
+
logger.info(f"✓ CUDA available: {torch.cuda.get_device_name(0)}")
|
| 23 |
+
logger.info(f" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f}GB")
|
| 24 |
+
else:
|
| 25 |
+
logger.warning("⚠ CUDA not available - training will use CPU (very slow)")
|
| 26 |
+
return cuda_available
|
| 27 |
+
except Exception as e:
|
| 28 |
+
logger.error(f"Error checking CUDA: {e}")
|
| 29 |
+
return False
|
| 30 |
+
|
| 31 |
+
def install_dependencies():
|
| 32 |
+
"""Install required dependencies."""
|
| 33 |
+
logger.info("\n" + "="*60)
|
| 34 |
+
logger.info("INSTALLING DEPENDENCIES")
|
| 35 |
+
logger.info("="*60)
|
| 36 |
+
|
| 37 |
+
try:
|
| 38 |
+
logger.info("Installing fine-tuning requirements...")
|
| 39 |
+
subprocess.run(
|
| 40 |
+
[sys.executable, "-m", "pip", "install", "-r", "requirements-finetune.txt", "-q"],
|
| 41 |
+
check=True
|
| 42 |
+
)
|
| 43 |
+
logger.info("✓ Dependencies installed successfully")
|
| 44 |
+
return True
|
| 45 |
+
except Exception as e:
|
| 46 |
+
logger.error(f"Failed to install dependencies: {e}")
|
| 47 |
+
return False
|
| 48 |
+
|
| 49 |
+
def verify_xcoder_dataset():
|
| 50 |
+
"""Verify that XCoder-80K dataset can be accessed."""
|
| 51 |
+
logger.info("\n" + "="*60)
|
| 52 |
+
logger.info("VERIFYING XCODER-80K DATASET")
|
| 53 |
+
logger.info("="*60)
|
| 54 |
+
|
| 55 |
+
try:
|
| 56 |
+
from datasets import load_dataset
|
| 57 |
+
logger.info("Checking XCoder-80K dataset availability...")
|
| 58 |
+
ds_info = load_dataset("banksy235/XCoder-80K", split="train", streaming=True)
|
| 59 |
+
logger.info(f"✓ XCoder-80K dataset is accessible")
|
| 60 |
+
logger.info(f" Dataset features: {ds_info.column_names}")
|
| 61 |
+
return True
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.warning(f"⚠ Could not verify dataset: {e}")
|
| 64 |
+
logger.info(" This may be normal if you're offline - dataset will be downloaded on first run")
|
| 65 |
+
return False
|
| 66 |
+
|
| 67 |
+
def run_finetuning():
|
| 68 |
+
"""Run the fine-tuning script."""
|
| 69 |
+
logger.info("\n" + "="*60)
|
| 70 |
+
logger.info("STARTING FINE-TUNING")
|
| 71 |
+
logger.info("="*60)
|
| 72 |
+
logger.info("\nAvailable models:")
|
| 73 |
+
logger.info(" 1. llama3.2 (Llama-2-7B) - Recommended")
|
| 74 |
+
logger.info(" 2. gemma3:4b (Gemma-7B) - Alternative")
|
| 75 |
+
logger.info(" 3. gemma3:1b (Gemma-2B) - Lightweight")
|
| 76 |
+
logger.info(" 4. all-models - Fine-tune all")
|
| 77 |
+
|
| 78 |
+
choice = input("\nSelect model (1-4, or enter custom model name): ").strip()
|
| 79 |
+
|
| 80 |
+
model_map = {
|
| 81 |
+
"1": "llama3.2",
|
| 82 |
+
"2": "gemma3:4b",
|
| 83 |
+
"3": "gemma3:1b",
|
| 84 |
+
"4": "--all-models",
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
model_arg = model_map.get(choice, choice)
|
| 88 |
+
|
| 89 |
+
if not model_arg or model_arg == "":
|
| 90 |
+
logger.error("Invalid selection")
|
| 91 |
+
return False
|
| 92 |
+
|
| 93 |
+
# Ask for training parameters
|
| 94 |
+
logger.info("\nTraining configuration (press Enter for defaults):")
|
| 95 |
+
|
| 96 |
+
epochs = input("Number of epochs (default: 3): ").strip() or "3"
|
| 97 |
+
batch_size = input("Batch size (default: 4): ").strip() or "4"
|
| 98 |
+
learning_rate = input("Learning rate (default: 2e-4): ").strip() or "2e-4"
|
| 99 |
+
max_samples = input("Max samples (default: all): ").strip() or ""
|
| 100 |
+
|
| 101 |
+
# Build command
|
| 102 |
+
cmd = [
|
| 103 |
+
sys.executable,
|
| 104 |
+
"finetune_models.py",
|
| 105 |
+
]
|
| 106 |
+
|
| 107 |
+
if model_arg == "--all-models":
|
| 108 |
+
cmd.append("--all-models")
|
| 109 |
+
else:
|
| 110 |
+
cmd.extend(["--model", model_arg])
|
| 111 |
+
|
| 112 |
+
cmd.extend([
|
| 113 |
+
"--num-epochs", epochs,
|
| 114 |
+
"--batch-size", batch_size,
|
| 115 |
+
"--learning-rate", learning_rate,
|
| 116 |
+
])
|
| 117 |
+
|
| 118 |
+
if max_samples:
|
| 119 |
+
cmd.extend(["--max-samples", max_samples])
|
| 120 |
+
|
| 121 |
+
logger.info("\n" + "="*60)
|
| 122 |
+
logger.info("TRAINING CONFIGURATION")
|
| 123 |
+
logger.info("="*60)
|
| 124 |
+
logger.info(f"Model: {model_arg if model_arg != '--all-models' else 'All models'}")
|
| 125 |
+
logger.info(f"Epochs: {epochs}")
|
| 126 |
+
logger.info(f"Batch size: {batch_size}")
|
| 127 |
+
logger.info(f"Learning rate: {learning_rate}")
|
| 128 |
+
if max_samples:
|
| 129 |
+
logger.info(f"Max samples: {max_samples}")
|
| 130 |
+
logger.info("\n" + "="*60)
|
| 131 |
+
|
| 132 |
+
confirm = input("Start training? (y/n): ").strip().lower()
|
| 133 |
+
if confirm != "y":
|
| 134 |
+
logger.info("Cancelled")
|
| 135 |
+
return False
|
| 136 |
+
|
| 137 |
+
# Run training
|
| 138 |
+
logger.info("\nStarting training process...")
|
| 139 |
+
logger.info("Monitor training with: tensorboard --logdir ./finetuned_models/[model_name]")
|
| 140 |
+
|
| 141 |
+
try:
|
| 142 |
+
result = subprocess.run(cmd, check=False)
|
| 143 |
+
return result.returncode == 0
|
| 144 |
+
except Exception as e:
|
| 145 |
+
logger.error(f"Training failed: {e}")
|
| 146 |
+
return False
|
| 147 |
+
|
| 148 |
+
def main():
|
| 149 |
+
"""Main entry point."""
|
| 150 |
+
logger.info("="*60)
|
| 151 |
+
logger.info("CODEARENA FINE-TUNING QUICK START")
|
| 152 |
+
logger.info("="*60)
|
| 153 |
+
|
| 154 |
+
# Check CUDA
|
| 155 |
+
cuda_available = check_cuda()
|
| 156 |
+
|
| 157 |
+
if not cuda_available:
|
| 158 |
+
logger.warning("\n⚠ Warning: CUDA not available. Training will be extremely slow.")
|
| 159 |
+
logger.warning(" Consider using a GPU (RTX 3090, A100, etc.) or cloud services (Colab, Lambda Labs)")
|
| 160 |
+
confirm = input("\nContinue with CPU training? (y/n): ").strip().lower()
|
| 161 |
+
if confirm != "y":
|
| 162 |
+
logger.info("Cancelled")
|
| 163 |
+
return
|
| 164 |
+
|
| 165 |
+
# Install dependencies
|
| 166 |
+
if not install_dependencies():
|
| 167 |
+
logger.error("Failed to install dependencies")
|
| 168 |
+
return
|
| 169 |
+
|
| 170 |
+
# Verify dataset
|
| 171 |
+
verify_xcoder_dataset()
|
| 172 |
+
|
| 173 |
+
# Run fine-tuning
|
| 174 |
+
if run_finetuning():
|
| 175 |
+
logger.info("\n" + "="*60)
|
| 176 |
+
logger.info("✓ FINE-TUNING COMPLETED SUCCESSFULLY")
|
| 177 |
+
logger.info("="*60)
|
| 178 |
+
logger.info("\nNext steps:")
|
| 179 |
+
logger.info("1. Check output in ./finetuned_models/")
|
| 180 |
+
logger.info("2. Export to Ollama (see FINETUNE_GUIDE.md)")
|
| 181 |
+
logger.info("3. Use in CodeArena: update Dashboard.jsx or ollama_rl_rollout.py")
|
| 182 |
+
logger.info("4. Monitor performance: python plot_rewards.py")
|
| 183 |
+
else:
|
| 184 |
+
logger.error("\n✗ Fine-tuning failed or was cancelled")
|
| 185 |
+
|
| 186 |
+
if __name__ == "__main__":
|
| 187 |
+
try:
|
| 188 |
+
main()
|
| 189 |
+
except KeyboardInterrupt:
|
| 190 |
+
logger.info("\nCancelled by user")
|
| 191 |
+
sys.exit(0)
|
| 192 |
+
except Exception as e:
|
| 193 |
+
logger.error(f"Unexpected error: {e}")
|
| 194 |
+
sys.exit(1)
|
requirements-finetune.txt
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fine-tuning dependencies
|
| 2 |
+
# Install with: pip install -r requirements-finetune.txt
|
| 3 |
+
|
| 4 |
+
# Core deep learning (latest stable versions)
|
| 5 |
+
torch>=2.6.0
|
| 6 |
+
torchvision>=0.17.0
|
| 7 |
+
torchaudio>=2.6.0
|
| 8 |
+
|
| 9 |
+
# Transformers and language models
|
| 10 |
+
transformers>=4.40.0
|
| 11 |
+
peft>=0.8.0 # Parameter-Efficient Fine-Tuning (LoRA)
|
| 12 |
+
trl>=0.8.0 # TRL for reinforcement learning fine-tuning
|
| 13 |
+
accelerate>=0.26.0
|
| 14 |
+
|
| 15 |
+
# Data handling
|
| 16 |
+
datasets>=2.18.0
|
| 17 |
+
huggingface_hub>=0.21.0
|
| 18 |
+
|
| 19 |
+
# Training optimizations
|
| 20 |
+
bitsandbytes>=0.42.0 # 8-bit optimizer for memory efficiency
|
| 21 |
+
tensorboard>=2.16.0 # Training monitoring
|
| 22 |
+
wandb>=0.16.0 # Weights & Biases (optional)
|
| 23 |
+
|
| 24 |
+
# Utilities
|
| 25 |
+
numpy>=1.24.0
|
| 26 |
+
pandas>=2.1.0
|
| 27 |
+
scipy>=1.11.0
|
| 28 |
+
scikit-learn>=1.3.0
|
| 29 |
+
|
| 30 |
+
# Development (optional)
|
| 31 |
+
jupyter==1.0.0
|
| 32 |
+
ipython==8.18.1
|
| 33 |
+
black==23.12.1
|
| 34 |
+
isort==5.13.2
|
| 35 |
+
pytest==7.4.3
|
results/reward_by_task.png
CHANGED
|
|
results/reward_curve.png
CHANGED
|
|
rewards_log.csv
CHANGED
|
@@ -1,2 +1,11 @@
|
|
| 1 |
timestamp,task_id,step,reward,compile_score,test_ratio,efficiency_score
|
| 2 |
2026-04-25T11:18:35.777063,easy-1,5,0.01,0.0,0.0,0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
timestamp,task_id,step,reward,compile_score,test_ratio,efficiency_score
|
| 2 |
2026-04-25T11:18:35.777063,easy-1,5,0.01,0.0,0.0,0.0
|
| 3 |
+
2026-04-26T01:38:27.213698,easy-1,5,0.01,0.0,0.0,0.0
|
| 4 |
+
2026-04-26 01:51:22,easy-1,5,0.20000000000000004,0.0,0.0,0.0
|
| 5 |
+
2026-04-26 01:52:42,easy-1,5,0,0.0,0.0,0.0
|
| 6 |
+
2026-04-26 01:54:20,easy-1,5,0.6500000000000001,0.0,0.0,0.0
|
| 7 |
+
2026-04-26 01:55:07,easy-1,5,0.6500000000000001,0.0,0.0,0.0
|
| 8 |
+
2026-04-26 01:55:38,easy-1,5,0.6500000000000001,0.0,0.0,0.0
|
| 9 |
+
2026-04-26 01:56:11,easy-1,5,0.6500000000000001,0.0,0.0,0.0
|
| 10 |
+
2026-04-26 02:01:49,medium-1,5,0.6500000000000001,0.0,0.0,0.0
|
| 11 |
+
2026-04-26 02:02:35,hard-1,5,0.7500000000000001,0.0,0.0,0.0
|
rl_trainer.py
ADDED
|
@@ -0,0 +1,521 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Full RL Training Loop for CodeArena with Memory and Fine-tuning
|
| 4 |
+
Implements experience replay, trajectory learning, and optimization.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import json
|
| 9 |
+
import time
|
| 10 |
+
import random
|
| 11 |
+
import requests
|
| 12 |
+
from typing import List, Dict, Tuple, Optional
|
| 13 |
+
from collections import deque
|
| 14 |
+
import numpy as np
|
| 15 |
+
from dataclasses import dataclass
|
| 16 |
+
from datetime import datetime
|
| 17 |
+
|
| 18 |
+
@dataclass
|
| 19 |
+
class Experience:
|
| 20 |
+
"""RL Experience tuple"""
|
| 21 |
+
state: str # Buggy code + error log + test results
|
| 22 |
+
action: str # Generated fix
|
| 23 |
+
reward: float
|
| 24 |
+
next_state: str
|
| 25 |
+
done: bool
|
| 26 |
+
task_id: str
|
| 27 |
+
step_count: int
|
| 28 |
+
trajectory_id: str
|
| 29 |
+
|
| 30 |
+
@dataclass
|
| 31 |
+
class Trajectory:
|
| 32 |
+
"""Complete episode trajectory"""
|
| 33 |
+
trajectory_id: str
|
| 34 |
+
task_id: str
|
| 35 |
+
steps: List[Experience]
|
| 36 |
+
final_reward: float
|
| 37 |
+
success: bool
|
| 38 |
+
total_steps: int
|
| 39 |
+
|
| 40 |
+
class CodeArenaRLTrainer:
|
| 41 |
+
def __init__(self, model_name: str = "llama3.2:latest", memory_size: int = 1000):
|
| 42 |
+
self.model_name = model_name
|
| 43 |
+
self.api_base = "http://localhost:11434"
|
| 44 |
+
|
| 45 |
+
# RL Components
|
| 46 |
+
self.memory = deque(maxlen=memory_size)
|
| 47 |
+
self.trajectories: List[Trajectory] = []
|
| 48 |
+
self.successful_trajectories: List[Trajectory] = []
|
| 49 |
+
|
| 50 |
+
# Training parameters
|
| 51 |
+
self.learning_rate = 0.001
|
| 52 |
+
self.gamma = 0.95 # Discount factor
|
| 53 |
+
self.epsilon = 1.0 # Exploration rate
|
| 54 |
+
self.epsilon_min = 0.1
|
| 55 |
+
self.epsilon_decay = 0.995
|
| 56 |
+
self.batch_size = 32
|
| 57 |
+
|
| 58 |
+
# Task progression
|
| 59 |
+
self.current_difficulty = "easy"
|
| 60 |
+
self.task_performance = {"easy": [], "medium": [], "hard": []}
|
| 61 |
+
|
| 62 |
+
# Optimization
|
| 63 |
+
self.cache = {} # Response cache for speed
|
| 64 |
+
self.prompt_templates = self._load_prompt_templates()
|
| 65 |
+
|
| 66 |
+
def _load_prompt_templates(self) -> Dict[str, str]:
|
| 67 |
+
"""Load optimized prompt templates"""
|
| 68 |
+
return {
|
| 69 |
+
"base": """You are an expert Python debugger. Fix the buggy code below.
|
| 70 |
+
|
| 71 |
+
BUGGY CODE:
|
| 72 |
+
{buggy_code}
|
| 73 |
+
|
| 74 |
+
CURRENT ERRORS:
|
| 75 |
+
{error_log}
|
| 76 |
+
|
| 77 |
+
TEST RESULTS:
|
| 78 |
+
{test_results}
|
| 79 |
+
|
| 80 |
+
REQUIREMENTS:
|
| 81 |
+
1. The code must compile without syntax errors
|
| 82 |
+
2. All tests must pass
|
| 83 |
+
3. Fix the ROOT CAUSE, not just symptoms
|
| 84 |
+
4. Do NOT repeat previous failed approaches
|
| 85 |
+
5. Ensure proper Python syntax and indentation
|
| 86 |
+
6. Return ONLY the corrected code, no explanations
|
| 87 |
+
|
| 88 |
+
Output the complete corrected Python code:""",
|
| 89 |
+
|
| 90 |
+
"rl_enhanced": """You are learning to debug Python code through reinforcement learning.
|
| 91 |
+
|
| 92 |
+
PREVIOUS EXPERIENCES:
|
| 93 |
+
{similar_experiences}
|
| 94 |
+
|
| 95 |
+
BUGGY CODE:
|
| 96 |
+
{buggy_code}
|
| 97 |
+
|
| 98 |
+
CURRENT ERRORS:
|
| 99 |
+
{error_log}
|
| 100 |
+
|
| 101 |
+
TEST RESULTS:
|
| 102 |
+
{test_results}
|
| 103 |
+
|
| 104 |
+
LEARNING OBJECTIVE:
|
| 105 |
+
- Learn from successful patterns in similar problems
|
| 106 |
+
- Avoid mistakes that led to low rewards
|
| 107 |
+
- Build upon working solutions
|
| 108 |
+
|
| 109 |
+
Output ONLY the corrected Python code:""",
|
| 110 |
+
|
| 111 |
+
"step_aware": """Step {step_count} of debugging process.
|
| 112 |
+
|
| 113 |
+
{context}
|
| 114 |
+
|
| 115 |
+
BUGGY CODE:
|
| 116 |
+
{buggy_code}
|
| 117 |
+
|
| 118 |
+
CURRENT ERRORS:
|
| 119 |
+
{error_log}
|
| 120 |
+
|
| 121 |
+
TEST RESULTS:
|
| 122 |
+
{test_results}
|
| 123 |
+
|
| 124 |
+
STEP {step_count} FOCUS:
|
| 125 |
+
{step_instruction}
|
| 126 |
+
|
| 127 |
+
Output ONLY the corrected Python code:"""
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
def get_similar_experiences(self, current_state: str, limit: int = 3) -> str:
|
| 131 |
+
"""Retrieve similar successful experiences from memory"""
|
| 132 |
+
if not self.successful_trajectories:
|
| 133 |
+
return "No previous successful experiences available."
|
| 134 |
+
|
| 135 |
+
# Simple similarity based on code length and error patterns
|
| 136 |
+
current_length = len(current_state)
|
| 137 |
+
similar = []
|
| 138 |
+
|
| 139 |
+
for traj in self.successful_trajectories[-10:]: # Last 10 successful
|
| 140 |
+
for exp in traj.steps:
|
| 141 |
+
if exp.reward > 0.5: # Only successful steps
|
| 142 |
+
length_diff = abs(len(exp.state) - current_length)
|
| 143 |
+
if length_diff < 200: # Similar complexity
|
| 144 |
+
similar.append(f"✓ Success: {exp.action[:100]}... (reward: {exp.reward:.2f})")
|
| 145 |
+
if len(similar) >= limit:
|
| 146 |
+
break
|
| 147 |
+
if len(similar) >= limit:
|
| 148 |
+
break
|
| 149 |
+
|
| 150 |
+
return "\n".join(similar) if similar else "Learning from general patterns..."
|
| 151 |
+
|
| 152 |
+
def generate_fix_rl(self, buggy_code: str, error_log: str, test_results: str,
|
| 153 |
+
previous_attempts: List[str], step_count: int,
|
| 154 |
+
use_memory: bool = True) -> str:
|
| 155 |
+
"""Generate fix using RL-enhanced prompting"""
|
| 156 |
+
|
| 157 |
+
# Build state representation
|
| 158 |
+
state = f"Code: {buggy_code}\nErrors: {error_log}\nTests: {test_results}"
|
| 159 |
+
|
| 160 |
+
# Choose prompt strategy based on experience
|
| 161 |
+
if use_memory and len(self.successful_trajectories) > 0:
|
| 162 |
+
similar_exp = self.get_similar_experiences(state)
|
| 163 |
+
prompt = self.prompt_templates["rl_enhanced"].format(
|
| 164 |
+
similar_experiences=similar_exp,
|
| 165 |
+
buggy_code=buggy_code,
|
| 166 |
+
error_log=error_log,
|
| 167 |
+
test_results=test_results
|
| 168 |
+
)
|
| 169 |
+
else:
|
| 170 |
+
# Step-aware prompting
|
| 171 |
+
step_instructions = {
|
| 172 |
+
1: "Focus on fixing syntax errors and basic compilation issues first.",
|
| 173 |
+
2: "Address logic errors from the previous attempt.",
|
| 174 |
+
3: "Optimize and ensure all edge cases are handled.",
|
| 175 |
+
4: "Final verification - ensure robust solution.",
|
| 176 |
+
5: "Last attempt - use completely different approach if needed."
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
context = ""
|
| 180 |
+
if previous_attempts:
|
| 181 |
+
context = f"Previous failed attempts:\n" + "\n".join(
|
| 182 |
+
f"- {attempt[:50]}..." for attempt in previous_attempts[-2:]
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
prompt = self.prompt_templates["step_aware"].format(
|
| 186 |
+
step_count=step_count,
|
| 187 |
+
context=context,
|
| 188 |
+
buggy_code=buggy_code,
|
| 189 |
+
error_log=error_log,
|
| 190 |
+
test_results=test_results,
|
| 191 |
+
step_instruction=step_instructions.get(step_count, "Fix all issues.")
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
# Check cache first
|
| 195 |
+
cache_key = hash(prompt)
|
| 196 |
+
if cache_key in self.cache:
|
| 197 |
+
return self.cache[cache_key]
|
| 198 |
+
|
| 199 |
+
try:
|
| 200 |
+
response = requests.post(
|
| 201 |
+
f"{self.api_base}/api/generate",
|
| 202 |
+
json={
|
| 203 |
+
"model": self.model_name,
|
| 204 |
+
"prompt": prompt,
|
| 205 |
+
"stream": False,
|
| 206 |
+
"options": {
|
| 207 |
+
"temperature": max(0.1, self.epsilon), # Exploration vs exploitation
|
| 208 |
+
"num_predict": 800,
|
| 209 |
+
"top_p": 0.9
|
| 210 |
+
}
|
| 211 |
+
},
|
| 212 |
+
timeout=20
|
| 213 |
+
)
|
| 214 |
+
response.raise_for_status()
|
| 215 |
+
result = response.json()
|
| 216 |
+
fix = result.get("response", "").strip()
|
| 217 |
+
|
| 218 |
+
# Clean up response
|
| 219 |
+
if fix.startswith("```python"):
|
| 220 |
+
fix = fix[9:]
|
| 221 |
+
if fix.startswith("```"):
|
| 222 |
+
fix = fix[3:]
|
| 223 |
+
if fix.endswith("```"):
|
| 224 |
+
fix = fix[:-3]
|
| 225 |
+
fix = fix.strip()
|
| 226 |
+
|
| 227 |
+
# Cache successful responses
|
| 228 |
+
if fix and len(fix) > 10:
|
| 229 |
+
self.cache[cache_key] = fix
|
| 230 |
+
|
| 231 |
+
return fix
|
| 232 |
+
|
| 233 |
+
except Exception as e:
|
| 234 |
+
print(f"API Error: {e}")
|
| 235 |
+
return self._fallback_fix(buggy_code, step_count)
|
| 236 |
+
|
| 237 |
+
def _fallback_fix(self, buggy_code: str, step_count: int) -> str:
|
| 238 |
+
"""Enhanced fallback with learning from memory"""
|
| 239 |
+
# Try to learn from successful patterns
|
| 240 |
+
if self.successful_trajectories:
|
| 241 |
+
# Use patterns from successful trajectories
|
| 242 |
+
successful_fixes = []
|
| 243 |
+
for traj in self.successful_trajectories[-3:]:
|
| 244 |
+
for exp in traj.steps:
|
| 245 |
+
if exp.reward > 0.6:
|
| 246 |
+
successful_fixes.append(exp.action)
|
| 247 |
+
|
| 248 |
+
if successful_fixes:
|
| 249 |
+
# Return a variation of successful fix
|
| 250 |
+
base_fix = random.choice(successful_fixes)
|
| 251 |
+
# Simple variation - could be improved
|
| 252 |
+
return base_fix
|
| 253 |
+
|
| 254 |
+
# Basic fallback
|
| 255 |
+
return "def placeholder_function(x):\n return x"
|
| 256 |
+
|
| 257 |
+
def run_episode_rl(self, task_id: str, max_steps: int = 5,
|
| 258 |
+
use_memory: bool = True) -> Trajectory:
|
| 259 |
+
"""Run a single RL episode with memory"""
|
| 260 |
+
trajectory_id = f"{task_id}_{int(time.time())}"
|
| 261 |
+
|
| 262 |
+
print(f"\n🎯 RL Episode: {task_id} (ε={self.epsilon:.3f})")
|
| 263 |
+
|
| 264 |
+
# Reset environment
|
| 265 |
+
try:
|
| 266 |
+
response = requests.post("http://localhost:7860/reset",
|
| 267 |
+
json={"task_id": task_id}, timeout=10)
|
| 268 |
+
response.raise_for_status()
|
| 269 |
+
obs = response.json()
|
| 270 |
+
except Exception as e:
|
| 271 |
+
print(f"❌ Reset failed: {e}")
|
| 272 |
+
return Trajectory(trajectory_id, task_id, [], 0.0, False, 0)
|
| 273 |
+
|
| 274 |
+
experiences = []
|
| 275 |
+
previous_attempts = []
|
| 276 |
+
done = False
|
| 277 |
+
step_count = 0
|
| 278 |
+
final_reward = 0.0
|
| 279 |
+
|
| 280 |
+
while not done and step_count < max_steps:
|
| 281 |
+
step_count += 1
|
| 282 |
+
|
| 283 |
+
# Build current state
|
| 284 |
+
current_state = f"{obs.get('buggy_code', '')}|{obs.get('error_log', '')}|{obs.get('test_results', '')}"
|
| 285 |
+
|
| 286 |
+
# Generate action using RL
|
| 287 |
+
fix = self.generate_fix_rl(
|
| 288 |
+
buggy_code=obs.get('buggy_code', ''),
|
| 289 |
+
error_log=obs.get('error_log', ''),
|
| 290 |
+
test_results=obs.get('test_results', ''),
|
| 291 |
+
previous_attempts=previous_attempts,
|
| 292 |
+
step_count=step_count,
|
| 293 |
+
use_memory=use_memory
|
| 294 |
+
)
|
| 295 |
+
|
| 296 |
+
print(f"🔧 Step {step_count}: Generated fix ({len(fix)} chars)")
|
| 297 |
+
|
| 298 |
+
# Execute action
|
| 299 |
+
try:
|
| 300 |
+
response = requests.post("http://localhost:7860/step",
|
| 301 |
+
json={"proposed_fix": fix}, timeout=20)
|
| 302 |
+
response.raise_for_status()
|
| 303 |
+
result = response.json()
|
| 304 |
+
|
| 305 |
+
reward = result.get('reward', 0)
|
| 306 |
+
done = result.get('done', False)
|
| 307 |
+
next_obs = result.get('observation', {})
|
| 308 |
+
|
| 309 |
+
# Build next state
|
| 310 |
+
next_state = f"{next_obs.get('buggy_code', '')}|{next_obs.get('error_log', '')}|{next_obs.get('test_results', '')}"
|
| 311 |
+
|
| 312 |
+
# Create experience
|
| 313 |
+
exp = Experience(
|
| 314 |
+
state=current_state,
|
| 315 |
+
action=fix,
|
| 316 |
+
reward=reward,
|
| 317 |
+
next_state=next_state,
|
| 318 |
+
done=done,
|
| 319 |
+
task_id=task_id,
|
| 320 |
+
step_count=step_count,
|
| 321 |
+
trajectory_id=trajectory_id
|
| 322 |
+
)
|
| 323 |
+
|
| 324 |
+
experiences.append(exp)
|
| 325 |
+
self.memory.append(exp)
|
| 326 |
+
|
| 327 |
+
previous_attempts.append(fix)
|
| 328 |
+
final_reward = reward
|
| 329 |
+
|
| 330 |
+
info = result.get('info', {})
|
| 331 |
+
print(f" Reward: {reward:.3f}")
|
| 332 |
+
print(f" Tests: {info.get('test_results', 'unknown')}")
|
| 333 |
+
print(f" Done: {done}")
|
| 334 |
+
|
| 335 |
+
if reward > 0.5:
|
| 336 |
+
print("🎉 Good reward! Learning...")
|
| 337 |
+
elif reward < 0.1:
|
| 338 |
+
print("⚠️ Low reward - adjusting strategy")
|
| 339 |
+
|
| 340 |
+
obs = next_obs
|
| 341 |
+
|
| 342 |
+
except Exception as e:
|
| 343 |
+
print(f"❌ Step failed: {e}")
|
| 344 |
+
break
|
| 345 |
+
|
| 346 |
+
# Create trajectory
|
| 347 |
+
success = final_reward > 0.5
|
| 348 |
+
trajectory = Trajectory(
|
| 349 |
+
trajectory_id=trajectory_id,
|
| 350 |
+
task_id=task_id,
|
| 351 |
+
steps=experiences,
|
| 352 |
+
final_reward=final_reward,
|
| 353 |
+
success=success,
|
| 354 |
+
total_steps=step_count
|
| 355 |
+
)
|
| 356 |
+
|
| 357 |
+
self.trajectories.append(trajectory)
|
| 358 |
+
if success:
|
| 359 |
+
self.successful_trajectories.append(trajectory)
|
| 360 |
+
|
| 361 |
+
# Update task performance
|
| 362 |
+
difficulty = task_id.split('-')[0]
|
| 363 |
+
if difficulty in self.task_performance:
|
| 364 |
+
self.task_performance[difficulty].append(final_reward)
|
| 365 |
+
|
| 366 |
+
# Decay exploration
|
| 367 |
+
self.epsilon = max(self.epsilon_min, self.epsilon * self.epsilon_decay)
|
| 368 |
+
|
| 369 |
+
print(f"🏁 Episode complete: {success} (reward: {final_reward:.3f})")
|
| 370 |
+
return trajectory
|
| 371 |
+
|
| 372 |
+
def should_progress_difficulty(self) -> Optional[str]:
|
| 373 |
+
"""Check if agent should move to next difficulty level"""
|
| 374 |
+
if self.current_difficulty == "easy":
|
| 375 |
+
recent_easy = self.task_performance["easy"][-3:] # Last 3 episodes
|
| 376 |
+
if len(recent_easy) >= 3 and np.mean(recent_easy) > 0.75:
|
| 377 |
+
return "medium"
|
| 378 |
+
elif self.current_difficulty == "medium":
|
| 379 |
+
recent_medium = self.task_performance["medium"][-3:]
|
| 380 |
+
if len(recent_medium) >= 3 and np.mean(recent_medium) > 0.70:
|
| 381 |
+
return "hard"
|
| 382 |
+
|
| 383 |
+
return None
|
| 384 |
+
|
| 385 |
+
def train_rl(self, episodes: int = 50, checkpoint_every: int = 10):
|
| 386 |
+
"""Full RL training loop"""
|
| 387 |
+
print("🚀 Starting RL Training")
|
| 388 |
+
print("=" * 60)
|
| 389 |
+
print(f"Model: {self.model_name}")
|
| 390 |
+
print(f"Episodes: {episodes}")
|
| 391 |
+
print(f"Memory size: {len(self.memory)}")
|
| 392 |
+
print(f"Successful trajectories: {len(self.successful_trajectories)}")
|
| 393 |
+
|
| 394 |
+
results = []
|
| 395 |
+
|
| 396 |
+
for episode in range(episodes):
|
| 397 |
+
# Adaptive task selection
|
| 398 |
+
next_difficulty = self.should_progress_difficulty()
|
| 399 |
+
if next_difficulty:
|
| 400 |
+
self.current_difficulty = next_difficulty
|
| 401 |
+
print(f"📈 Progressing to {self.current_difficulty} difficulty!")
|
| 402 |
+
|
| 403 |
+
# Select task based on current difficulty
|
| 404 |
+
task_candidates = [f"{self.current_difficulty}-{i}" for i in range(1, 4)]
|
| 405 |
+
task_id = random.choice(task_candidates)
|
| 406 |
+
|
| 407 |
+
# Run episode
|
| 408 |
+
trajectory = self.run_episode_rl(task_id, use_memory=True)
|
| 409 |
+
results.append({
|
| 410 |
+
"episode": episode + 1,
|
| 411 |
+
"task_id": trajectory.task_id,
|
| 412 |
+
"reward": trajectory.final_reward,
|
| 413 |
+
"success": trajectory.success,
|
| 414 |
+
"steps": trajectory.total_steps,
|
| 415 |
+
"epsilon": self.epsilon
|
| 416 |
+
})
|
| 417 |
+
|
| 418 |
+
# Checkpoint
|
| 419 |
+
if (episode + 1) % checkpoint_every == 0:
|
| 420 |
+
self.save_checkpoint(f"checkpoint_{episode + 1}.json")
|
| 421 |
+
print(f"💾 Checkpoint saved at episode {episode + 1}")
|
| 422 |
+
|
| 423 |
+
# Performance summary
|
| 424 |
+
if (episode + 1) % 10 == 0:
|
| 425 |
+
recent_results = results[-10:]
|
| 426 |
+
success_rate = sum(1 for r in recent_results if r["success"]) / len(recent_results)
|
| 427 |
+
avg_reward = sum(r["reward"] for r in recent_results) / len(recent_results)
|
| 428 |
+
print(f"📊 Episode {episode + 1:3d} | Success: {success_rate:.1%} | Reward: {avg_reward:.3f}")
|
| 429 |
+
# Final summary
|
| 430 |
+
self.print_training_summary(results)
|
| 431 |
+
return results
|
| 432 |
+
|
| 433 |
+
def print_training_summary(self, results: List[Dict]):
|
| 434 |
+
"""Print comprehensive training summary"""
|
| 435 |
+
print("\n" + "=" * 60)
|
| 436 |
+
print("🏆 RL TRAINING COMPLETE")
|
| 437 |
+
print("=" * 60)
|
| 438 |
+
|
| 439 |
+
total_episodes = len(results)
|
| 440 |
+
successful_episodes = sum(1 for r in results if r["success"])
|
| 441 |
+
success_rate = successful_episodes / total_episodes
|
| 442 |
+
|
| 443 |
+
rewards = [r["reward"] for r in results]
|
| 444 |
+
avg_reward = np.mean(rewards)
|
| 445 |
+
max_reward = max(rewards)
|
| 446 |
+
|
| 447 |
+
print(f"📊 Overall Performance:")
|
| 448 |
+
print(f"🎯 Episodes: {total_episodes}")
|
| 449 |
+
print(f"✅ Successful: {successful_episodes}")
|
| 450 |
+
print(f"📈 Success Rate: {success_rate:.1%}")
|
| 451 |
+
print(f"💰 Average Reward: {avg_reward:.3f}")
|
| 452 |
+
print(f"🏆 Max Reward: {max_reward:.3f}")
|
| 453 |
+
print(f"🎯 Success Rate: {success_rate:.1%}")
|
| 454 |
+
|
| 455 |
+
# Performance by difficulty
|
| 456 |
+
print(f"\n📈 Performance by Difficulty:")
|
| 457 |
+
for difficulty in ["easy", "medium", "hard"]:
|
| 458 |
+
diff_results = [r for r in results if r["task_id"].startswith(difficulty)]
|
| 459 |
+
if diff_results:
|
| 460 |
+
diff_success = sum(1 for r in diff_results if r["success"]) / len(diff_results)
|
| 461 |
+
diff_avg_reward = np.mean([r["reward"] for r in diff_results])
|
| 462 |
+
print(f" {difficulty.capitalize()}: Success {diff_success:.1%} | Reward {diff_avg_reward:.3f}")
|
| 463 |
+
# Learning curve
|
| 464 |
+
print(f"\n📉 Learning Curve (last 20 episodes):")
|
| 465 |
+
recent = results[-20:]
|
| 466 |
+
if recent:
|
| 467 |
+
for i in range(0, len(recent), 5):
|
| 468 |
+
batch = recent[i:i+5]
|
| 469 |
+
batch_success = sum(1 for r in batch if r["success"]) / len(batch)
|
| 470 |
+
batch_avg_reward = np.mean([r["reward"] for r in batch])
|
| 471 |
+
print(f" Ep {i+1:2d}-{min(i+5, len(recent)):2d}: Success {batch_success:.1%} | Reward {batch_avg_reward:.3f}")
|
| 472 |
+
print(f"\n💾 Memory: {len(self.memory)} experiences")
|
| 473 |
+
print(f"🎖️ Successful trajectories: {len(self.successful_trajectories)}")
|
| 474 |
+
print(f"🧠 Cache size: {len(self.cache)} responses")
|
| 475 |
+
|
| 476 |
+
def save_checkpoint(self, filename: str):
|
| 477 |
+
"""Save training checkpoint"""
|
| 478 |
+
checkpoint = {
|
| 479 |
+
"timestamp": datetime.now().isoformat(),
|
| 480 |
+
"model_name": self.model_name,
|
| 481 |
+
"memory_size": len(self.memory),
|
| 482 |
+
"successful_trajectories": len(self.successful_trajectories),
|
| 483 |
+
"current_difficulty": self.current_difficulty,
|
| 484 |
+
"epsilon": self.epsilon,
|
| 485 |
+
"task_performance": self.task_performance,
|
| 486 |
+
"cache_size": len(self.cache)
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
with open(filename, 'w') as f:
|
| 490 |
+
json.dump(checkpoint, f, indent=2)
|
| 491 |
+
|
| 492 |
+
def main():
|
| 493 |
+
import argparse
|
| 494 |
+
parser = argparse.ArgumentParser(description="Full RL Training for CodeArena")
|
| 495 |
+
parser.add_argument("--episodes", type=int, default=30, help="Number of training episodes")
|
| 496 |
+
parser.add_argument("--model", default="llama3.2:latest", help="Ollama model to use")
|
| 497 |
+
parser.add_argument("--memory", type=int, default=500, help="Experience replay memory size")
|
| 498 |
+
parser.add_argument("--checkpoint", type=int, default=10, help="Save checkpoint every N episodes")
|
| 499 |
+
|
| 500 |
+
args = parser.parse_args()
|
| 501 |
+
|
| 502 |
+
print("🧠 CodeArena RL Trainer")
|
| 503 |
+
print("=" * 50)
|
| 504 |
+
print(f"Model: {args.model}")
|
| 505 |
+
print(f"Episodes: {args.episodes}")
|
| 506 |
+
print(f"Memory: {args.memory}")
|
| 507 |
+
print(f"Checkpoints: every {args.checkpoint} episodes")
|
| 508 |
+
|
| 509 |
+
trainer = CodeArenaRLTrainer(args.model, args.memory)
|
| 510 |
+
results = trainer.train_rl(args.episodes, args.checkpoint)
|
| 511 |
+
|
| 512 |
+
# Save final results
|
| 513 |
+
with open("rl_training_results.json", 'w') as f:
|
| 514 |
+
json.dump(results, f, indent=2)
|
| 515 |
+
|
| 516 |
+
print("")
|
| 517 |
+
print("💾 Results saved to rl_training_results.json")
|
| 518 |
+
print("📊 Run 'python plot_rewards.py' to visualize performance")
|
| 519 |
+
|
| 520 |
+
if __name__ == "__main__":
|
| 521 |
+
main()
|
round_robin.py
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def round_robin(bt, tq):
|
| 2 |
+
n = len(bt)
|
| 3 |
+
rt = bt[:]
|
| 4 |
+
wt = [0] * n
|
| 5 |
+
tat = [0] * n
|
| 6 |
+
|
| 7 |
+
time = 0
|
| 8 |
+
done = False
|
| 9 |
+
|
| 10 |
+
while not done:
|
| 11 |
+
done = True
|
| 12 |
+
for i in range(n):
|
| 13 |
+
if rt[i] > 0:
|
| 14 |
+
done = False
|
| 15 |
+
if rt[i] > tq:
|
| 16 |
+
time += tq
|
| 17 |
+
rt[i] -= tq
|
| 18 |
+
else:
|
| 19 |
+
time += rt[i]
|
| 20 |
+
wt[i] = time - bt[i]
|
| 21 |
+
rt[i] = 0
|
| 22 |
+
|
| 23 |
+
for i in range(n):
|
| 24 |
+
tat[i] = bt[i] + wt[i]
|
| 25 |
+
|
| 26 |
+
return wt, tat
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def main():
|
| 30 |
+
n = int(input("Enter number of processes: "))
|
| 31 |
+
if n <= 0:
|
| 32 |
+
print("Number of processes must be > 0")
|
| 33 |
+
return
|
| 34 |
+
|
| 35 |
+
bt = []
|
| 36 |
+
for i in range(n):
|
| 37 |
+
bt_i = int(input(f"Enter burst time for process {i + 1}: "))
|
| 38 |
+
if bt_i < 0:
|
| 39 |
+
print("Burst time cannot be negative")
|
| 40 |
+
return
|
| 41 |
+
bt.append(bt_i)
|
| 42 |
+
|
| 43 |
+
tq = int(input("Enter time quantum: "))
|
| 44 |
+
if tq <= 0:
|
| 45 |
+
print("Time quantum must be > 0")
|
| 46 |
+
return
|
| 47 |
+
|
| 48 |
+
wt, tat = round_robin(bt, tq)
|
| 49 |
+
|
| 50 |
+
print("\nProcess\tBT\tWT\tTAT")
|
| 51 |
+
for i in range(n):
|
| 52 |
+
print(f"P{i + 1}\t{bt[i]}\t{wt[i]}\t{tat[i]}")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
if __name__ == "__main__":
|
| 56 |
+
main()
|
rr_check.c
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#include <stdio.h>
|
| 2 |
+
|
| 3 |
+
int main() {
|
| 4 |
+
int n, tq;
|
| 5 |
+
|
| 6 |
+
printf("Enter number of processes: ");
|
| 7 |
+
scanf("%d", &n);
|
| 8 |
+
|
| 9 |
+
int bt[n], rt[n], wt[n], tat[n];
|
| 10 |
+
|
| 11 |
+
// Input burst times
|
| 12 |
+
for (int i = 0; i < n; i++) {
|
| 13 |
+
printf("Enter burst time for process %d: ", i + 1);
|
| 14 |
+
scanf("%d", &bt[i]);
|
| 15 |
+
rt[i] = bt[i]; // remaining time = burst time
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
printf("Enter time quantum: ");
|
| 19 |
+
scanf("%d", &tq);
|
| 20 |
+
|
| 21 |
+
int time = 0, done;
|
| 22 |
+
|
| 23 |
+
do {
|
| 24 |
+
done = 1;
|
| 25 |
+
|
| 26 |
+
for (int i = 0; i < n; i++) {
|
| 27 |
+
if (rt[i] > 0) {
|
| 28 |
+
done = 0;
|
| 29 |
+
|
| 30 |
+
if (rt[i] > tq) {
|
| 31 |
+
time += tq;
|
| 32 |
+
rt[i] -= tq;
|
| 33 |
+
} else {
|
| 34 |
+
time += rt[i];
|
| 35 |
+
wt[i] = time - bt[i]; // waiting time
|
| 36 |
+
rt[i] = 0;
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
} while (!done);
|
| 41 |
+
|
| 42 |
+
// Calculate Turnaround Time
|
| 43 |
+
for (int i = 0; i < n; i++) {
|
| 44 |
+
tat[i] = bt[i] + wt[i];
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
// Output
|
| 48 |
+
printf("\nProcess\tBT\tWT\tTAT\n");
|
| 49 |
+
for (int i = 0; i < n; i++) {
|
| 50 |
+
printf("P%d\t%d\t%d\t%d\n", i + 1, bt[i], wt[i], tat[i]);
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
return 0;
|
| 54 |
+
}
|
server/ai_fixer.py
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
CodeArena Built-in AI Code Fixer
|
| 3 |
+
Works WITHOUT Ollama. Uses AST analysis + pattern-based repair.
|
| 4 |
+
Also supports Ollama if available (graceful fallback).
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import ast
|
| 8 |
+
import re
|
| 9 |
+
import textwrap
|
| 10 |
+
import subprocess
|
| 11 |
+
import sys
|
| 12 |
+
from typing import Optional
|
| 13 |
+
from server.algorithm_detector import (
|
| 14 |
+
detect_problem_type, detect_complexity, needs_optimization,
|
| 15 |
+
get_optimization_hint, build_adaptive_prompt_suffix, ALGO_HINTS
|
| 16 |
+
)
|
| 17 |
+
from server.memory import store_success, retrieve_memory, log_complexity_reward
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
# ─── Pattern-Based Fixes ─────────────────────────────────────────────────────
|
| 21 |
+
|
| 22 |
+
def fix_syntax_errors(code: str) -> str:
|
| 23 |
+
"""Try to auto-fix common syntax errors."""
|
| 24 |
+
lines = code.split('\n')
|
| 25 |
+
fixed = []
|
| 26 |
+
for line in lines:
|
| 27 |
+
# Fix missing colon on def/class/if/for/while/else/elif/try/except/finally
|
| 28 |
+
stripped = line.rstrip()
|
| 29 |
+
if re.match(r'^\s*(def |class |if |elif |else|for |while |try|except|finally)', stripped):
|
| 30 |
+
if not stripped.endswith(':') and not stripped.endswith('\\') and not stripped.endswith(','):
|
| 31 |
+
stripped = stripped + ':'
|
| 32 |
+
fixed.append(stripped)
|
| 33 |
+
return '\n'.join(fixed)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def fix_wrong_builtins(code: str) -> str:
|
| 37 |
+
"""Fix common wrong builtin usage."""
|
| 38 |
+
replacements = {
|
| 39 |
+
r'\blenght\b': 'len',
|
| 40 |
+
r'\bappned\b': 'append',
|
| 41 |
+
r'\bpirnt\b': 'print',
|
| 42 |
+
r'\bprnit\b': 'print',
|
| 43 |
+
r'\bretrun\b': 'return',
|
| 44 |
+
r'\bpas\b': 'pass',
|
| 45 |
+
r'\bTreu\b': 'True',
|
| 46 |
+
r'\bFlase\b': 'False',
|
| 47 |
+
r'\bNoen\b': 'None',
|
| 48 |
+
}
|
| 49 |
+
for pattern, replacement in replacements.items():
|
| 50 |
+
code = re.sub(pattern, replacement, code)
|
| 51 |
+
return code
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def optimize_complexity(code: str) -> str:
|
| 55 |
+
"""
|
| 56 |
+
Detect and optimize common O(N^2)/O(N^3) patterns.
|
| 57 |
+
- Triple nested loops on same array → Kadane's algorithm
|
| 58 |
+
- Bubble sort → sorted()
|
| 59 |
+
- Linear search in list → set/dict lookup
|
| 60 |
+
"""
|
| 61 |
+
# Detect triple nested loop (O(N^3)) → max subarray → Kadane's
|
| 62 |
+
if re.search(r'for\s+\w+\s+in\s+range.*:\s*\n.*for\s+\w+\s+in\s+range.*:\s*\n.*for\s+\w+\s+in\s+range', code, re.DOTALL):
|
| 63 |
+
# Extract function signature
|
| 64 |
+
match = re.match(r'(def\s+\w+\([^)]*\):)', code.strip())
|
| 65 |
+
if match:
|
| 66 |
+
sig = match.group(1)
|
| 67 |
+
fname = re.search(r'def\s+(\w+)', sig).group(1)
|
| 68 |
+
# Check if it's a max subarray problem
|
| 69 |
+
if 'max' in code.lower() and ('sum' in code.lower() or 'subarray' in code.lower()):
|
| 70 |
+
return f"""{sig}
|
| 71 |
+
# Optimized: Kadane's Algorithm O(N)
|
| 72 |
+
if not arr:
|
| 73 |
+
return 0
|
| 74 |
+
max_sum = arr[0]
|
| 75 |
+
current_sum = arr[0]
|
| 76 |
+
for num in arr[1:]:
|
| 77 |
+
current_sum = max(num, current_sum + num)
|
| 78 |
+
max_sum = max(max_sum, current_sum)
|
| 79 |
+
return max_sum"""
|
| 80 |
+
|
| 81 |
+
# Detect O(N^2) bubble sort → use sorted()
|
| 82 |
+
if re.search(r'for\s+\w+.*range.*:\s*\n.*for\s+\w+.*range.*:\s*\n.*if\s+\w+\[', code, re.DOTALL):
|
| 83 |
+
if 'swap' in code.lower() or ('arr[i]' in code and 'arr[j]' in code):
|
| 84 |
+
match = re.match(r'(def\s+\w+\([^)]*\):)', code.strip())
|
| 85 |
+
if match:
|
| 86 |
+
sig = match.group(1)
|
| 87 |
+
param = re.search(r'def\s+\w+\(([^)]*)\)', sig)
|
| 88 |
+
params = param.group(1).split(',')[0].strip() if param else 'arr'
|
| 89 |
+
return f"""{sig}
|
| 90 |
+
# Optimized: Python built-in sort O(N log N)
|
| 91 |
+
return sorted({params})"""
|
| 92 |
+
|
| 93 |
+
# Detect double nested loop with repeated computation
|
| 94 |
+
if code.count('for ') >= 2 and 'range(n)' in code and 'range(i' in code:
|
| 95 |
+
# Off-by-one fix for binary search
|
| 96 |
+
if 'binary_search' in code.lower() or ('mid' in code and 'low' in code and 'high' in code):
|
| 97 |
+
match = re.match(r'(def\s+\w+\([^)]*\):)', code.strip())
|
| 98 |
+
if match:
|
| 99 |
+
sig = match.group(1)
|
| 100 |
+
params = re.search(r'def\s+\w+\(([^)]*)\)', sig).group(1)
|
| 101 |
+
param_list = [p.strip() for p in params.split(',')]
|
| 102 |
+
arr_p = param_list[0] if len(param_list) > 0 else 'arr'
|
| 103 |
+
target_p = param_list[1] if len(param_list) > 1 else 'target'
|
| 104 |
+
return f"""{sig}
|
| 105 |
+
# Fixed: Correct binary search O(log N)
|
| 106 |
+
low, high = 0, len({arr_p}) - 1
|
| 107 |
+
while low <= high:
|
| 108 |
+
mid = (low + high) // 2
|
| 109 |
+
if {arr_p}[mid] == {target_p}:
|
| 110 |
+
return mid
|
| 111 |
+
elif {arr_p}[mid] < {target_p}:
|
| 112 |
+
low = mid + 1
|
| 113 |
+
else:
|
| 114 |
+
high = mid - 1
|
| 115 |
+
return -1"""
|
| 116 |
+
|
| 117 |
+
return code
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def fix_logic_bugs(code: str) -> str:
|
| 121 |
+
"""Fix common logic bugs: off-by-one, wrong operators, etc."""
|
| 122 |
+
# range(n) instead of range(n+1) for inclusive
|
| 123 |
+
# Off-by-one in binary search
|
| 124 |
+
code = re.sub(r'high\s*=\s*len\((\w+)\)', r'high = len(\1) - 1', code)
|
| 125 |
+
|
| 126 |
+
# Fix wrong range in binary search: range(len(arr)) -> while low <= high
|
| 127 |
+
# Fix average calculation: sum / n should use len()
|
| 128 |
+
code = re.sub(r'return\s+total\s*/\s*n\b', 'return total / len(arr) if arr else 0', code)
|
| 129 |
+
|
| 130 |
+
# Fix division by zero risk
|
| 131 |
+
if 'average' in code.lower() or 'mean' in code.lower():
|
| 132 |
+
code = re.sub(
|
| 133 |
+
r'return\s+(\w+)\s*/\s*len\((\w+)\)',
|
| 134 |
+
r'return \1 / len(\2) if \2 else 0',
|
| 135 |
+
code
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
return code
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def apply_all_fixes(code: str) -> str:
|
| 142 |
+
"""Apply all fixers in sequence."""
|
| 143 |
+
code = fix_wrong_builtins(code)
|
| 144 |
+
code = fix_syntax_errors(code)
|
| 145 |
+
code = fix_logic_bugs(code)
|
| 146 |
+
code = optimize_complexity(code)
|
| 147 |
+
return code
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
# ─── Ollama Integration (optional) ───────────────────────────────────────────
|
| 151 |
+
|
| 152 |
+
def is_ollama_available(ollama_url: str = "http://localhost:11434", model: str = "llama3.2:latest") -> bool:
|
| 153 |
+
"""Check if Ollama is running and model exists."""
|
| 154 |
+
try:
|
| 155 |
+
import urllib.request
|
| 156 |
+
import json
|
| 157 |
+
req = urllib.request.Request(f"{ollama_url}/api/tags")
|
| 158 |
+
with urllib.request.urlopen(req, timeout=3) as resp:
|
| 159 |
+
data = json.loads(resp.read())
|
| 160 |
+
models = [m['name'] for m in data.get('models', [])]
|
| 161 |
+
return any(model.split(':')[0] in m for m in models)
|
| 162 |
+
except Exception:
|
| 163 |
+
return False
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def validate_code(code: str) -> bool:
|
| 167 |
+
"""Safety layer to prevent 0.0 reward syntax failures."""
|
| 168 |
+
try:
|
| 169 |
+
compile(code, "<string>", "exec")
|
| 170 |
+
return True
|
| 171 |
+
except Exception:
|
| 172 |
+
return False
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
def is_inefficient(code: str) -> bool:
|
| 176 |
+
"""
|
| 177 |
+
Detect if generated code is still using brute force.
|
| 178 |
+
Returns True if code looks inefficient.
|
| 179 |
+
"""
|
| 180 |
+
nested_fors = code.count('for ') >= 2
|
| 181 |
+
has_on2_marker = 'O(n^2)' in code or 'O(n^3)' in code or 'O(N^2)' in code or 'O(N^3)' in code
|
| 182 |
+
# Detect triple nested loop pattern (O(N^3))
|
| 183 |
+
triple_loop = bool(re.search(
|
| 184 |
+
r'for\s+\w+.*:\s*\n\s+for\s+\w+.*:\s*\n\s+for\s+\w+', code, re.MULTILINE
|
| 185 |
+
))
|
| 186 |
+
return triple_loop or has_on2_marker
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
def _call_ollama(prompt: str, model: str, ollama_url: str, num_predict: int = 1024) -> str | None:
|
| 190 |
+
"""Send a single prompt to Ollama and return raw text response."""
|
| 191 |
+
import urllib.request
|
| 192 |
+
import json
|
| 193 |
+
payload = json.dumps({
|
| 194 |
+
"model": model,
|
| 195 |
+
"prompt": prompt,
|
| 196 |
+
"stream": False,
|
| 197 |
+
"options": {"temperature": 0.1, "num_predict": num_predict}
|
| 198 |
+
}).encode()
|
| 199 |
+
req = urllib.request.Request(
|
| 200 |
+
f"{ollama_url}/api/generate",
|
| 201 |
+
data=payload,
|
| 202 |
+
headers={"Content-Type": "application/json"},
|
| 203 |
+
method="POST"
|
| 204 |
+
)
|
| 205 |
+
with urllib.request.urlopen(req, timeout=60) as resp:
|
| 206 |
+
data = json.loads(resp.read())
|
| 207 |
+
return data.get("response", "").strip()
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def _extract_code_and_explanation(result: str) -> tuple[str, str]:
|
| 211 |
+
"""Extract code block and explanation from model response."""
|
| 212 |
+
code_match = re.search(r'```python\n(.*?)\n```', result, re.DOTALL)
|
| 213 |
+
if not code_match:
|
| 214 |
+
code_match = re.search(r'```(.*?)```', result, re.DOTALL)
|
| 215 |
+
extracted_code = code_match.group(1).strip() if code_match else result.strip()
|
| 216 |
+
explanation = result.replace(code_match.group(0), '').strip() if code_match else "No reasoning provided."
|
| 217 |
+
return extracted_code, explanation
|
| 218 |
+
|
| 219 |
+
|
| 220 |
+
def _build_optimization_prompt(code: str, error_log: str) -> str:
|
| 221 |
+
"""
|
| 222 |
+
Build the Analysis → Optimization → Code 3-step prompt with pattern mapping.
|
| 223 |
+
"""
|
| 224 |
+
return f"""You are an expert Python algorithm engineer.
|
| 225 |
+
|
| 226 |
+
The current solution is inefficient or buggy.
|
| 227 |
+
|
| 228 |
+
Step 1: Identify why it is inefficient or incorrect (1 line only)
|
| 229 |
+
Step 2: Identify the optimal algorithm to solve this problem
|
| 230 |
+
Step 3: Rewrite the code using the optimal algorithm
|
| 231 |
+
|
| 232 |
+
Constraints:
|
| 233 |
+
- MUST improve time complexity
|
| 234 |
+
- DO NOT use brute force
|
| 235 |
+
- Target O(n) if possible
|
| 236 |
+
- If your solution is O(n^2) or worse, improve it
|
| 237 |
+
|
| 238 |
+
Common algorithm patterns:
|
| 239 |
+
- Maximum subarray → Kadane's algorithm (O(n))
|
| 240 |
+
- Subarray sum → prefix sum (O(n))
|
| 241 |
+
- Searching sorted array → binary search (O(log n))
|
| 242 |
+
- Sorting → use built-in sorted() (O(n log n))
|
| 243 |
+
- Sliding window → two pointers (O(n))
|
| 244 |
+
|
| 245 |
+
First think step-by-step about how to optimize the algorithm.
|
| 246 |
+
Then output only the final code.
|
| 247 |
+
Do NOT stop at identifying the issue — you MUST produce optimized code.
|
| 248 |
+
|
| 249 |
+
Previous error:
|
| 250 |
+
{error_log or "No errors, but the solution is suboptimal."}
|
| 251 |
+
|
| 252 |
+
CURRENT CODE:
|
| 253 |
+
{code}
|
| 254 |
+
|
| 255 |
+
Output your 3-step reasoning, then wrap the final optimized code in a ```python ... ``` block."""
|
| 256 |
+
|
| 257 |
+
|
| 258 |
+
def _build_fix_prompt(code: str, error_log: str, reward: float = 0.0, task_id: str = "") -> str:
|
| 259 |
+
"""Build prompt for correctness fix (when code has bugs/errors)."""
|
| 260 |
+
# Get algorithm hint from detector
|
| 261 |
+
algo_hint = get_optimization_hint(code, error_log)
|
| 262 |
+
# Get adaptive suffix based on current reward
|
| 263 |
+
adaptive_suffix = build_adaptive_prompt_suffix(reward)
|
| 264 |
+
# Retrieve memory for past success
|
| 265 |
+
memory_note = ""
|
| 266 |
+
if task_id:
|
| 267 |
+
past = retrieve_memory(task_id)
|
| 268 |
+
if past and past.get('reward', 0) > 0.7:
|
| 269 |
+
memory_note = f"\nPrevious successful solution (reward={past['reward']}):\n{past['best_code']}\nImprove upon this."
|
| 270 |
+
|
| 271 |
+
return f"""You are an expert Python debugging agent.
|
| 272 |
+
|
| 273 |
+
Follow this process and explain your reasoning:
|
| 274 |
+
Step 1: Identify bug type (syntax / logic / type / edge case)
|
| 275 |
+
Step 2: Locate exact line causing issue
|
| 276 |
+
Step 3: Fix only that issue and ensure tests pass
|
| 277 |
+
Step 4: Report the Time Complexity of your fixed code
|
| 278 |
+
Step 5: If complexity is O(n^2) or worse, optimize to O(n) if possible
|
| 279 |
+
|
| 280 |
+
Algorithm Detection: {algo_hint}
|
| 281 |
+
|
| 282 |
+
Common algorithm patterns:
|
| 283 |
+
- Maximum subarray → Kadane's algorithm (O(n))
|
| 284 |
+
- Subarray sum → prefix sum (O(n))
|
| 285 |
+
- Searching sorted array → binary search (O(log n))
|
| 286 |
+
- Sorting → use built-in sorted() (O(n log n))
|
| 287 |
+
|
| 288 |
+
Is your solution optimal? If not, improve it.
|
| 289 |
+
{adaptive_suffix}
|
| 290 |
+
{memory_note}
|
| 291 |
+
|
| 292 |
+
Previous attempt failed with:
|
| 293 |
+
{error_log or "No errors, but tests are failing."}
|
| 294 |
+
|
| 295 |
+
BUGGY CODE:
|
| 296 |
+
{code}
|
| 297 |
+
|
| 298 |
+
Output your step-by-step reasoning, then wrap ONLY the corrected Python code in a ```python ... ``` block."""
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def fix_with_ollama(
|
| 302 |
+
code: str,
|
| 303 |
+
error_log: str = "",
|
| 304 |
+
ollama_url: str = "http://localhost:11434",
|
| 305 |
+
model: str = "llama3.2:latest",
|
| 306 |
+
reward: float = 0.0,
|
| 307 |
+
task_id: str = "",
|
| 308 |
+
) -> Optional[tuple[str, str]]:
|
| 309 |
+
"""
|
| 310 |
+
Fix + optimize code using Ollama.
|
| 311 |
+
Pipeline:
|
| 312 |
+
1. Generate fix (correctness + optimization prompt)
|
| 313 |
+
2. Self-critique: if result is still inefficient → run optimization prompt
|
| 314 |
+
3. Iterative refinement: repeat up to 2 full cycles
|
| 315 |
+
Returns (code, explanation) or None.
|
| 316 |
+
"""
|
| 317 |
+
try:
|
| 318 |
+
import urllib.request
|
| 319 |
+
import json
|
| 320 |
+
|
| 321 |
+
best_code = None
|
| 322 |
+
best_explanation = ""
|
| 323 |
+
|
| 324 |
+
# Iterative refinement: up to 2 full optimization passes
|
| 325 |
+
for iteration in range(2):
|
| 326 |
+
# Choose prompt: optimization-first if first run, fix-first if error exists
|
| 327 |
+
if iteration == 0 and error_log:
|
| 328 |
+
prompt = _build_fix_prompt(code, error_log, reward=reward, task_id=task_id)
|
| 329 |
+
else:
|
| 330 |
+
# Inject algorithm hint + adaptive suffix into optimization prompt
|
| 331 |
+
algo_hint = get_optimization_hint(best_code or code, error_log)
|
| 332 |
+
adaptive_suffix = build_adaptive_prompt_suffix(reward)
|
| 333 |
+
base_opt_prompt = _build_optimization_prompt(best_code or code, error_log)
|
| 334 |
+
prompt = base_opt_prompt + f"\n\nAlgorithm Detection: {algo_hint}{adaptive_suffix}"
|
| 335 |
+
|
| 336 |
+
result = None
|
| 337 |
+
for attempt in range(3): # 3 retries per iteration
|
| 338 |
+
try:
|
| 339 |
+
result = _call_ollama(prompt, model, ollama_url)
|
| 340 |
+
if not result:
|
| 341 |
+
continue
|
| 342 |
+
|
| 343 |
+
extracted_code, explanation = _extract_code_and_explanation(result)
|
| 344 |
+
|
| 345 |
+
if extracted_code and validate_code(extracted_code):
|
| 346 |
+
best_code = extracted_code
|
| 347 |
+
best_explanation = explanation
|
| 348 |
+
break # Valid code — move on
|
| 349 |
+
|
| 350 |
+
# Invalid syntax: tell model to fix it
|
| 351 |
+
prompt += "\n\nYour last generated code had a SyntaxError. Wrap ONLY valid Python code in ```python ... ``` blocks."
|
| 352 |
+
|
| 353 |
+
except Exception as e:
|
| 354 |
+
print(f"[Ollama attempt {attempt+1} failed]: {e}")
|
| 355 |
+
continue
|
| 356 |
+
|
| 357 |
+
if best_code is None:
|
| 358 |
+
return None # All retries failed
|
| 359 |
+
|
| 360 |
+
# ── Self-Critique Loop ────────────────────────────────────────────
|
| 361 |
+
# If the generated code is still brute-force, force a re-optimization pass
|
| 362 |
+
if is_inefficient(best_code):
|
| 363 |
+
print(f"[Self-Critique] Iteration {iteration+1}: Code still inefficient, re-optimizing...")
|
| 364 |
+
# Build a targeted re-optimization prompt
|
| 365 |
+
critique_prompt = f"""You are a Python performance expert.
|
| 366 |
+
|
| 367 |
+
The following solution is STILL using brute force and is too slow:
|
| 368 |
+
|
| 369 |
+
```python
|
| 370 |
+
{best_code}
|
| 371 |
+
```
|
| 372 |
+
|
| 373 |
+
This is unacceptable. You MUST rewrite it using an optimal algorithm.
|
| 374 |
+
|
| 375 |
+
Common patterns:
|
| 376 |
+
- Maximum subarray → Kadane's algorithm (O(n))
|
| 377 |
+
- Subarray sum → prefix sum (O(n))
|
| 378 |
+
- Searching → binary search (O(log n))
|
| 379 |
+
|
| 380 |
+
Output ONLY the O(n) optimized version inside a ```python ... ``` block. No explanation needed."""
|
| 381 |
+
|
| 382 |
+
try:
|
| 383 |
+
critique_result = _call_ollama(critique_prompt, model, ollama_url)
|
| 384 |
+
if critique_result:
|
| 385 |
+
improved_code, improved_explanation = _extract_code_and_explanation(critique_result)
|
| 386 |
+
if improved_code and validate_code(improved_code):
|
| 387 |
+
best_code = improved_code
|
| 388 |
+
best_explanation = f"[Self-Critique Applied]\n{improved_explanation or best_explanation}"
|
| 389 |
+
except Exception as e:
|
| 390 |
+
print(f"[Self-Critique] Failed: {e}")
|
| 391 |
+
|
| 392 |
+
# If no longer inefficient after critique, stop early
|
| 393 |
+
if not is_inefficient(best_code):
|
| 394 |
+
break
|
| 395 |
+
|
| 396 |
+
return (best_code, best_explanation) if best_code else None
|
| 397 |
+
|
| 398 |
+
except Exception as e:
|
| 399 |
+
print(f"Ollama fix failed: {e}")
|
| 400 |
+
return None
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
def generate_fix(
|
| 404 |
+
code: str,
|
| 405 |
+
error_log: str = "",
|
| 406 |
+
ollama_url: str = "http://localhost:11434",
|
| 407 |
+
model: str = "llama3.2:latest",
|
| 408 |
+
use_ollama: bool = True,
|
| 409 |
+
reward: float = 0.0,
|
| 410 |
+
task_id: str = "",
|
| 411 |
+
) -> dict:
|
| 412 |
+
"""
|
| 413 |
+
Main entry point for code fixing.
|
| 414 |
+
Full pipeline: Algorithm Detection + Memory → Ollama (Analysis→Optimization→Code + Self-Critique) → built-in fallback
|
| 415 |
+
Logs complexity vs reward to CSV for research tracking.
|
| 416 |
+
Returns: { fixed_code, method, success, explanation }
|
| 417 |
+
"""
|
| 418 |
+
if use_ollama:
|
| 419 |
+
result = fix_with_ollama(code, error_log, ollama_url, model, reward=reward, task_id=task_id)
|
| 420 |
+
if result:
|
| 421 |
+
fixed_code, explanation = result
|
| 422 |
+
# Log complexity vs reward for research tracking
|
| 423 |
+
complexity = detect_complexity(fixed_code)
|
| 424 |
+
log_complexity_reward(task_id or "sandbox", reward, complexity, step=0, method="ollama")
|
| 425 |
+
# Store in memory if good reward
|
| 426 |
+
if reward >= 0.8 and task_id:
|
| 427 |
+
store_success(task_id, fixed_code, reward)
|
| 428 |
+
return {
|
| 429 |
+
"fixed_code": fixed_code,
|
| 430 |
+
"method": "ollama",
|
| 431 |
+
"success": True,
|
| 432 |
+
"explanation": explanation,
|
| 433 |
+
"complexity": complexity,
|
| 434 |
+
"algo_hint": get_optimization_hint(fixed_code, error_log),
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
# Fallback: built-in AST pattern fixer
|
| 438 |
+
fixed_code = apply_all_fixes(code)
|
| 439 |
+
complexity = detect_complexity(fixed_code)
|
| 440 |
+
log_complexity_reward(task_id or "sandbox", reward, complexity, step=0, method="builtin")
|
| 441 |
+
return {
|
| 442 |
+
"fixed_code": fixed_code,
|
| 443 |
+
"method": "builtin",
|
| 444 |
+
"success": True,
|
| 445 |
+
"explanation": "Ollama unavailable. Used built-in pattern-based fixer.",
|
| 446 |
+
"note": "Ollama unavailable. Used built-in pattern-based fixer.",
|
| 447 |
+
"complexity": complexity,
|
| 448 |
+
"algo_hint": get_optimization_hint(fixed_code),
|
| 449 |
+
}
|
| 450 |
+
|
server/algorithm_detector.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
CodeArena Algorithm Detector
|
| 3 |
+
Classifies problem type from code/description + detects time complexity inefficiency.
|
| 4 |
+
Used to steer the AI fixer toward optimal algorithm selection.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import re
|
| 8 |
+
|
| 9 |
+
# ── Problem Pattern Mapping ───────────────────────────────────────────────────
|
| 10 |
+
|
| 11 |
+
PATTERNS = {
|
| 12 |
+
"max_subarray": ["max subarray", "largest sum contiguous", "maximum sum", "kadane", "max_subarray"],
|
| 13 |
+
"binary_search": ["sorted array", "binary search", "binary_search", "search sorted", "log n"],
|
| 14 |
+
"two_sum": ["two sum", "pair sum", "two_sum", "find pair", "target sum"],
|
| 15 |
+
"duplicate": ["duplicate", "unique", "find duplicate", "repeated element"],
|
| 16 |
+
"sorting": ["sort", "bubble sort", "insertion sort", "selection sort", "arrange"],
|
| 17 |
+
"sliding_window": ["sliding window", "substring", "subarray of length k", "window size"],
|
| 18 |
+
"prefix_sum": ["prefix sum", "range sum", "cumulative sum", "subarray sum"],
|
| 19 |
+
"graph": ["graph", "bfs", "dfs", "shortest path", "connected", "adjacency"],
|
| 20 |
+
"dp": ["dynamic programming", "memoization", "fibonacci", "knapsack", "longest"],
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
ALGO_HINTS = {
|
| 24 |
+
"max_subarray": "Use Kadane's Algorithm O(n): curr = max(num, curr+num); max_sum = max(max_sum, curr)",
|
| 25 |
+
"binary_search": "Use binary search O(log n): while low <= high: mid = (low+high)//2",
|
| 26 |
+
"two_sum": "Use hashmap O(n): seen = {}; if target-num in seen: return [seen[target-num], i]",
|
| 27 |
+
"duplicate": "Use set O(n): seen = set(); if num in seen: return num; seen.add(num)",
|
| 28 |
+
"sorting": "Use built-in sorted() O(n log n): return sorted(arr)",
|
| 29 |
+
"sliding_window": "Use two pointers O(n): expand right, shrink left when constraint violated",
|
| 30 |
+
"prefix_sum": "Use prefix sum O(n): prefix[i] = prefix[i-1] + arr[i]",
|
| 31 |
+
"graph": "Use BFS/DFS O(V+E): collections.deque for BFS, recursion for DFS",
|
| 32 |
+
"dp": "Use memoization O(n): @lru_cache or dp table to store subproblems",
|
| 33 |
+
"unknown": "Analyze loops — if nested, consider prefix sum or hash map to reduce complexity",
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
# ── Detectors ─────────────────────────────────────────────────────────────────
|
| 37 |
+
|
| 38 |
+
def detect_problem_type(text: str) -> str:
|
| 39 |
+
"""Classify the problem type from code or description text."""
|
| 40 |
+
text = text.lower()
|
| 41 |
+
for key, keywords in PATTERNS.items():
|
| 42 |
+
if any(k in text for k in keywords):
|
| 43 |
+
return key
|
| 44 |
+
return "unknown"
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def detect_complexity(code: str) -> str:
|
| 48 |
+
"""
|
| 49 |
+
Estimate time complexity by counting loop nesting depth.
|
| 50 |
+
"""
|
| 51 |
+
lines = code.split('\n')
|
| 52 |
+
max_depth = 0
|
| 53 |
+
current_depth = 0
|
| 54 |
+
|
| 55 |
+
for line in lines:
|
| 56 |
+
stripped = line.lstrip()
|
| 57 |
+
indent = len(line) - len(stripped)
|
| 58 |
+
|
| 59 |
+
if re.match(r'^(for|while)\s', stripped):
|
| 60 |
+
# Estimate nesting level by indent level (4 spaces = 1 level)
|
| 61 |
+
depth = indent // 4 + 1
|
| 62 |
+
max_depth = max(max_depth, depth)
|
| 63 |
+
|
| 64 |
+
if max_depth >= 3:
|
| 65 |
+
return "O(n^3)"
|
| 66 |
+
elif max_depth == 2:
|
| 67 |
+
return "O(n^2)"
|
| 68 |
+
elif max_depth == 1:
|
| 69 |
+
return "O(n)"
|
| 70 |
+
return "O(1)"
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def needs_optimization(code: str) -> bool:
|
| 74 |
+
"""Returns True if the code is worse than O(n log n)."""
|
| 75 |
+
complexity = detect_complexity(code)
|
| 76 |
+
return complexity in ["O(n^2)", "O(n^3)"]
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_optimization_hint(code: str, description: str = "") -> str:
|
| 80 |
+
"""
|
| 81 |
+
Full analysis: detect problem type + complexity + return targeted hint.
|
| 82 |
+
"""
|
| 83 |
+
problem_type = detect_problem_type(description + " " + code)
|
| 84 |
+
complexity = detect_complexity(code)
|
| 85 |
+
hint = ALGO_HINTS.get(problem_type, ALGO_HINTS["unknown"])
|
| 86 |
+
return f"Detected: {problem_type.replace('_', ' ').title()} | Current: {complexity} | Fix: {hint}"
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def build_adaptive_prompt_suffix(reward: float) -> str:
|
| 90 |
+
"""
|
| 91 |
+
Return adaptive prompting suffix based on current reward level.
|
| 92 |
+
Steers model toward correctness, logic, or performance based on progress.
|
| 93 |
+
"""
|
| 94 |
+
if reward < 0.4:
|
| 95 |
+
return "\nFocus on correctness. Fix syntax errors and make sure all tests pass first."
|
| 96 |
+
elif reward < 0.7:
|
| 97 |
+
return "\nFix edge cases and logic bugs. Ensure the algorithm handles all inputs correctly."
|
| 98 |
+
else:
|
| 99 |
+
return "\nOptimize for performance. Reduce time complexity. Use O(n) algorithms where possible."
|
server/app.py
CHANGED
|
@@ -15,6 +15,10 @@ from pydantic import BaseModel
|
|
| 15 |
from server.models import CodeArenaObservation, CodeArenaAction, TaskInfo
|
| 16 |
from server.executor import run_code_with_tests
|
| 17 |
from server.grader import calculate_reward, safe_reward, force_valid_reward
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
from tasks import ALL_TASKS
|
| 19 |
|
| 20 |
|
|
@@ -78,20 +82,35 @@ class CodeArenaEnv:
|
|
| 78 |
|
| 79 |
self.step_count += 1
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
exec_result = run_code_with_tests(
|
| 82 |
code=action.proposed_fix,
|
| 83 |
test_code=self.current_task.test_code,
|
| 84 |
timeout=max(self.current_task.optimal_time_seconds * 10, 2.0),
|
| 85 |
)
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
base_reward, reward_components = calculate_reward(exec_result, self.current_task, action.proposed_fix)
|
| 88 |
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
| 90 |
novelty_penalty = 0.1 if action.proposed_fix in self.previous_attempts else 0.0
|
| 91 |
|
|
|
|
|
|
|
| 92 |
final_reward = base_reward - step_penalty - novelty_penalty
|
| 93 |
final_reward = max(0.001, min(0.999, float(final_reward)))
|
| 94 |
|
|
|
|
|
|
|
| 95 |
self.previous_attempts.append(action.proposed_fix)
|
| 96 |
self.last_error_log = exec_result.runtime_errors
|
| 97 |
self.last_test_results = (
|
|
@@ -107,7 +126,9 @@ class CodeArenaEnv:
|
|
| 107 |
info = {
|
| 108 |
"execution_metadata": exec_result.model_dump(),
|
| 109 |
"task_id": self.current_task.task_id,
|
| 110 |
-
"reward_components": reward_components
|
|
|
|
|
|
|
| 111 |
}
|
| 112 |
return self._state(), final_reward, self.is_done, info
|
| 113 |
|
|
@@ -137,7 +158,7 @@ app.add_middleware(
|
|
| 137 |
)
|
| 138 |
|
| 139 |
|
| 140 |
-
@app.get("/")
|
| 141 |
def health():
|
| 142 |
return {"status": "ok", "environment": "CodeArena"}
|
| 143 |
|
|
@@ -174,6 +195,14 @@ def api_reset(body: ResetRequest = ResetRequest()):
|
|
| 174 |
@app.post("/step")
|
| 175 |
def api_step(action: CodeArenaAction):
|
| 176 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
obs, reward, done, info = _env.step(action)
|
| 178 |
# Safety fallback before force_valid_reward
|
| 179 |
if reward is None:
|
|
@@ -205,7 +234,11 @@ def api_step(action: CodeArenaAction):
|
|
| 205 |
def api_state():
|
| 206 |
try:
|
| 207 |
obs = _env._state()
|
| 208 |
-
return {
|
|
|
|
|
|
|
|
|
|
|
|
|
| 209 |
except Exception:
|
| 210 |
traceback.print_exc()
|
| 211 |
return {
|
|
@@ -214,6 +247,112 @@ def api_state():
|
|
| 214 |
}
|
| 215 |
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
# ── CLI entrypoint (OpenEnv / script console_scripts) ─────────────────────
|
| 218 |
def main():
|
| 219 |
"""Run the CodeArena server via uvicorn."""
|
|
|
|
| 15 |
from server.models import CodeArenaObservation, CodeArenaAction, TaskInfo
|
| 16 |
from server.executor import run_code_with_tests
|
| 17 |
from server.grader import calculate_reward, safe_reward, force_valid_reward
|
| 18 |
+
from server.ai_fixer import generate_fix
|
| 19 |
+
from server.raw_runner import run_raw_code
|
| 20 |
+
from server.memory import store_success, log_complexity_reward, get_complexity_reward_stats, get_all_memories
|
| 21 |
+
from server.algorithm_detector import detect_complexity, detect_problem_type, get_optimization_hint
|
| 22 |
from tasks import ALL_TASKS
|
| 23 |
|
| 24 |
|
|
|
|
| 82 |
|
| 83 |
self.step_count += 1
|
| 84 |
|
| 85 |
+
print(f"[DEBUG] Step {self.step_count}: Processing action")
|
| 86 |
+
print(f"[DEBUG] Proposed fix length: {len(action.proposed_fix)} chars")
|
| 87 |
+
print(f"[DEBUG] Proposed fix preview: {action.proposed_fix[:200]}...")
|
| 88 |
+
|
| 89 |
exec_result = run_code_with_tests(
|
| 90 |
code=action.proposed_fix,
|
| 91 |
test_code=self.current_task.test_code,
|
| 92 |
timeout=max(self.current_task.optimal_time_seconds * 10, 2.0),
|
| 93 |
)
|
| 94 |
|
| 95 |
+
print(f"[DEBUG] Execution result: compile_success={exec_result.compile_success}, test_passed={exec_result.test_passed}/{exec_result.test_total}, exec_time={exec_result.execution_time_seconds:.2f}s")
|
| 96 |
+
if exec_result.runtime_errors:
|
| 97 |
+
print(f"[DEBUG] Runtime errors: {exec_result.runtime_errors[:500]}")
|
| 98 |
+
|
| 99 |
base_reward, reward_components = calculate_reward(exec_result, self.current_task, action.proposed_fix)
|
| 100 |
|
| 101 |
+
print(f"[DEBUG] Base reward: {base_reward:.3f}")
|
| 102 |
+
print(f"[DEBUG] Reward components: {reward_components}")
|
| 103 |
+
|
| 104 |
+
step_penalty = 0.01 * self.step_count # Reduced from 0.02 for gentler learning
|
| 105 |
novelty_penalty = 0.1 if action.proposed_fix in self.previous_attempts else 0.0
|
| 106 |
|
| 107 |
+
print(f"[DEBUG] Penalties: step={step_penalty:.3f}, novelty={novelty_penalty:.3f}")
|
| 108 |
+
|
| 109 |
final_reward = base_reward - step_penalty - novelty_penalty
|
| 110 |
final_reward = max(0.001, min(0.999, float(final_reward)))
|
| 111 |
|
| 112 |
+
print(f"[DEBUG] Final reward: {final_reward:.3f}")
|
| 113 |
+
|
| 114 |
self.previous_attempts.append(action.proposed_fix)
|
| 115 |
self.last_error_log = exec_result.runtime_errors
|
| 116 |
self.last_test_results = (
|
|
|
|
| 126 |
info = {
|
| 127 |
"execution_metadata": exec_result.model_dump(),
|
| 128 |
"task_id": self.current_task.task_id,
|
| 129 |
+
"reward_components": reward_components,
|
| 130 |
+
"test_results": self.last_test_results,
|
| 131 |
+
"llm_feedback": reward_components.get("feedback", "No feedback provided.")
|
| 132 |
}
|
| 133 |
return self._state(), final_reward, self.is_done, info
|
| 134 |
|
|
|
|
| 158 |
)
|
| 159 |
|
| 160 |
|
| 161 |
+
@app.get("/health")
|
| 162 |
def health():
|
| 163 |
return {"status": "ok", "environment": "CodeArena"}
|
| 164 |
|
|
|
|
| 195 |
@app.post("/step")
|
| 196 |
def api_step(action: CodeArenaAction):
|
| 197 |
try:
|
| 198 |
+
# Compatibility: support both 'proposed_fix' and 'action'
|
| 199 |
+
fix = action.proposed_fix or action.action
|
| 200 |
+
if not fix:
|
| 201 |
+
return {"status": "error", "message": "No code provided in 'proposed_fix' or 'action'"}
|
| 202 |
+
|
| 203 |
+
# Patch the action object to ensure _env.step gets what it expects
|
| 204 |
+
action.proposed_fix = fix
|
| 205 |
+
|
| 206 |
obs, reward, done, info = _env.step(action)
|
| 207 |
# Safety fallback before force_valid_reward
|
| 208 |
if reward is None:
|
|
|
|
| 234 |
def api_state():
|
| 235 |
try:
|
| 236 |
obs = _env._state()
|
| 237 |
+
return {
|
| 238 |
+
"step": _env.step_count,
|
| 239 |
+
"history": _env.previous_attempts,
|
| 240 |
+
"observation": obs.model_dump()
|
| 241 |
+
}
|
| 242 |
except Exception:
|
| 243 |
traceback.print_exc()
|
| 244 |
return {
|
|
|
|
| 247 |
}
|
| 248 |
|
| 249 |
|
| 250 |
+
# ── AI Fix endpoint ───────────────────────────────────────────────────────
|
| 251 |
+
class FixRequest(BaseModel):
|
| 252 |
+
code: str
|
| 253 |
+
error_log: Optional[str] = ""
|
| 254 |
+
ollama_url: Optional[str] = "http://localhost:11434"
|
| 255 |
+
model: Optional[str] = "llama3.2:latest"
|
| 256 |
+
use_ollama: Optional[bool] = True
|
| 257 |
+
reward: Optional[float] = 0.0
|
| 258 |
+
task_id: Optional[str] = ""
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
@app.post("/fix")
|
| 262 |
+
def api_fix(body: FixRequest):
|
| 263 |
+
"""Generate a code fix using Ollama (if available) or built-in pattern fixer."""
|
| 264 |
+
try:
|
| 265 |
+
result = generate_fix(
|
| 266 |
+
code=body.code,
|
| 267 |
+
error_log=body.error_log or "",
|
| 268 |
+
ollama_url=body.ollama_url,
|
| 269 |
+
model=body.model,
|
| 270 |
+
use_ollama=body.use_ollama,
|
| 271 |
+
reward=body.reward or 0.0,
|
| 272 |
+
task_id=body.task_id or "",
|
| 273 |
+
)
|
| 274 |
+
return result
|
| 275 |
+
except Exception:
|
| 276 |
+
traceback.print_exc()
|
| 277 |
+
return {
|
| 278 |
+
"fixed_code": body.code,
|
| 279 |
+
"method": "passthrough",
|
| 280 |
+
"success": False,
|
| 281 |
+
"error": traceback.format_exc()
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
# ── Raw Runner endpoint (Sandbox) ──────────────────────────────────────────
|
| 286 |
+
class RawRequest(BaseModel):
|
| 287 |
+
code: str
|
| 288 |
+
|
| 289 |
+
@app.post("/run_raw")
|
| 290 |
+
def api_run_raw(body: RawRequest):
|
| 291 |
+
"""Run arbitrary code without tests and return output/complexity and reward."""
|
| 292 |
+
try:
|
| 293 |
+
result = run_raw_code(body.code)
|
| 294 |
+
|
| 295 |
+
# Calculate simulated reward for sandbox
|
| 296 |
+
# Penalty for errors, slight penalty for extremely high exec time
|
| 297 |
+
reward = 0.95
|
| 298 |
+
reward_components = {"Execution Success": 0.5, "Error Free": 0.45}
|
| 299 |
+
|
| 300 |
+
if result.stderr:
|
| 301 |
+
reward = 0.1
|
| 302 |
+
reward_components["Error Free"] = 0.0
|
| 303 |
+
|
| 304 |
+
if result.execution_time > 1.0:
|
| 305 |
+
reward -= 0.15
|
| 306 |
+
reward_components["Time Complexity"] = -0.15
|
| 307 |
+
|
| 308 |
+
return {
|
| 309 |
+
"status": "success",
|
| 310 |
+
"stdout": result.stdout,
|
| 311 |
+
"stderr": result.stderr,
|
| 312 |
+
"execution_time": result.execution_time,
|
| 313 |
+
"time_complexity_hint": result.time_complexity_hint,
|
| 314 |
+
"reward": force_valid_reward(reward),
|
| 315 |
+
"reward_components": reward_components,
|
| 316 |
+
"done": False # Sandbox mode is never "done" strictly by execution, AI must verify optimality
|
| 317 |
+
}
|
| 318 |
+
except Exception as e:
|
| 319 |
+
traceback.print_exc()
|
| 320 |
+
return {
|
| 321 |
+
"status": "error",
|
| 322 |
+
"stderr": str(e),
|
| 323 |
+
"stdout": "",
|
| 324 |
+
"execution_time": 0,
|
| 325 |
+
"time_complexity_hint": "Error evaluating complexity.",
|
| 326 |
+
"reward": force_valid_reward(0.0),
|
| 327 |
+
"reward_components": {},
|
| 328 |
+
"done": False
|
| 329 |
+
}
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# ── Stats & Memory endpoints (Research Dashboard) ─────────────────────────
|
| 333 |
+
@app.get("/stats")
|
| 334 |
+
def api_stats():
|
| 335 |
+
"""Return complexity vs reward stats from CSV log."""
|
| 336 |
+
try:
|
| 337 |
+
return {
|
| 338 |
+
"complexity_reward_stats": get_complexity_reward_stats(),
|
| 339 |
+
"episode_history": _env.episode_rewards_history,
|
| 340 |
+
"mean_reward": round(sum(_env.episode_rewards_history) / max(1, len(_env.episode_rewards_history)), 3),
|
| 341 |
+
}
|
| 342 |
+
except Exception:
|
| 343 |
+
traceback.print_exc()
|
| 344 |
+
return {"complexity_reward_stats": {}, "episode_history": [], "mean_reward": 0.0}
|
| 345 |
+
|
| 346 |
+
|
| 347 |
+
@app.get("/memory")
|
| 348 |
+
def api_memory():
|
| 349 |
+
"""Return all stored best solutions from agent memory."""
|
| 350 |
+
try:
|
| 351 |
+
return {"memories": get_all_memories()}
|
| 352 |
+
except Exception:
|
| 353 |
+
return {"memories": {}}
|
| 354 |
+
|
| 355 |
+
|
| 356 |
# ── CLI entrypoint (OpenEnv / script console_scripts) ─────────────────────
|
| 357 |
def main():
|
| 358 |
"""Run the CodeArena server via uvicorn."""
|
server/grader.py
CHANGED
|
@@ -31,13 +31,24 @@ def normalize_reward(passed: int, total: int) -> float:
|
|
| 31 |
return force_valid_reward(raw)
|
| 32 |
|
| 33 |
_LLM_CACHE = {}
|
|
|
|
| 34 |
|
| 35 |
def get_llm_quality_score(proposed_fix: str) -> dict:
|
|
|
|
| 36 |
if proposed_fix in _LLM_CACHE:
|
| 37 |
return _LLM_CACHE[proposed_fix]
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
try:
|
| 40 |
-
client = OpenAI()
|
| 41 |
response = client.chat.completions.create(
|
| 42 |
model=os.environ.get("JUDGE_MODEL", "gpt-4o-mini"),
|
| 43 |
messages=[
|
|
@@ -84,13 +95,23 @@ def calculate_reward_components(exec_result: ExecutionResult, task_info: TaskInf
|
|
| 84 |
def calculate_reward(exec_result: ExecutionResult, task_info: TaskInfo, proposed_fix: str) -> tuple[float, dict]:
|
| 85 |
comps = calculate_reward_components(exec_result, task_info, proposed_fix)
|
| 86 |
base_reward = (
|
| 87 |
-
0.
|
| 88 |
-
0.
|
| 89 |
-
0.
|
| 90 |
-
0.
|
| 91 |
-
0.
|
| 92 |
-
0.05 * comps["llm_quality"]
|
| 93 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
return base_reward, comps
|
| 95 |
|
| 96 |
def grade(*args, **kwargs) -> float:
|
|
|
|
| 31 |
return force_valid_reward(raw)
|
| 32 |
|
| 33 |
_LLM_CACHE = {}
|
| 34 |
+
_JUDGE_DISABLED_WARNED = False
|
| 35 |
|
| 36 |
def get_llm_quality_score(proposed_fix: str) -> dict:
|
| 37 |
+
global _JUDGE_DISABLED_WARNED
|
| 38 |
if proposed_fix in _LLM_CACHE:
|
| 39 |
return _LLM_CACHE[proposed_fix]
|
| 40 |
+
|
| 41 |
+
api_key = os.environ.get("OPENAI_API_KEY")
|
| 42 |
+
if not api_key:
|
| 43 |
+
if not _JUDGE_DISABLED_WARNED:
|
| 44 |
+
print("LLM judge disabled: OPENAI_API_KEY not set. Using neutral fallback scores.")
|
| 45 |
+
_JUDGE_DISABLED_WARNED = True
|
| 46 |
+
fallback = {"code_quality": 0.5, "security": 0.5, "correctness": 0.5}
|
| 47 |
+
_LLM_CACHE[proposed_fix] = fallback
|
| 48 |
+
return fallback
|
| 49 |
+
|
| 50 |
try:
|
| 51 |
+
client = OpenAI(api_key=api_key)
|
| 52 |
response = client.chat.completions.create(
|
| 53 |
model=os.environ.get("JUDGE_MODEL", "gpt-4o-mini"),
|
| 54 |
messages=[
|
|
|
|
| 95 |
def calculate_reward(exec_result: ExecutionResult, task_info: TaskInfo, proposed_fix: str) -> tuple[float, dict]:
|
| 96 |
comps = calculate_reward_components(exec_result, task_info, proposed_fix)
|
| 97 |
base_reward = (
|
| 98 |
+
0.15 * comps["compile_score"] +
|
| 99 |
+
0.35 * comps["test_ratio"] +
|
| 100 |
+
0.30 * comps["efficiency"] + # Increased from 0.15 to push optimization
|
| 101 |
+
0.10 * comps["llm_correctness"] +
|
| 102 |
+
0.05 * comps["llm_security"] +
|
| 103 |
+
0.05 * comps["llm_quality"]
|
| 104 |
)
|
| 105 |
+
|
| 106 |
+
# Compile bonus: encourage first milestone
|
| 107 |
+
if comps["compile_score"] > 0.0:
|
| 108 |
+
base_reward += 0.05
|
| 109 |
+
|
| 110 |
+
# Harsh complexity penalty: if runtime is > 5x optimal, penalize heavily
|
| 111 |
+
if exec_result.test_passed == exec_result.test_total and exec_result.test_total > 0:
|
| 112 |
+
if exec_result.execution_time_seconds > task_info.optimal_time_seconds * 5:
|
| 113 |
+
base_reward -= 0.30
|
| 114 |
+
|
| 115 |
return base_reward, comps
|
| 116 |
|
| 117 |
def grade(*args, **kwargs) -> float:
|
server/memory.py
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
CodeArena Agent Memory
|
| 3 |
+
Self-improving memory across episodes.
|
| 4 |
+
Stores best solutions per task + retrieves them to seed future fixes.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import json
|
| 8 |
+
import os
|
| 9 |
+
import csv
|
| 10 |
+
import time
|
| 11 |
+
from typing import Optional
|
| 12 |
+
|
| 13 |
+
MEMORY_FILE = os.path.join(os.path.dirname(__file__), "..", "agent_memory.json")
|
| 14 |
+
CSV_FILE = os.path.join(os.path.dirname(__file__), "..", "complexity_rewards.csv")
|
| 15 |
+
|
| 16 |
+
# ── Memory Store ──────────────────────────────────────────────────────────────
|
| 17 |
+
|
| 18 |
+
def load_memory() -> dict:
|
| 19 |
+
"""Load agent memory from disk."""
|
| 20 |
+
try:
|
| 21 |
+
if os.path.exists(MEMORY_FILE):
|
| 22 |
+
with open(MEMORY_FILE, "r") as f:
|
| 23 |
+
return json.load(f)
|
| 24 |
+
except Exception as e:
|
| 25 |
+
print(f"[Memory] Load error: {e}")
|
| 26 |
+
return {}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def save_memory(memory: dict) -> None:
|
| 30 |
+
"""Persist agent memory to disk."""
|
| 31 |
+
try:
|
| 32 |
+
with open(MEMORY_FILE, "w") as f:
|
| 33 |
+
json.dump(memory, f, indent=2)
|
| 34 |
+
except Exception as e:
|
| 35 |
+
print(f"[Memory] Save error: {e}")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def store_success(task_id: str, code: str, reward: float) -> None:
|
| 39 |
+
"""
|
| 40 |
+
Store a successful solution if reward improves on previous best.
|
| 41 |
+
Only keeps the BEST solution per task.
|
| 42 |
+
"""
|
| 43 |
+
memory = load_memory()
|
| 44 |
+
existing = memory.get(task_id)
|
| 45 |
+
|
| 46 |
+
if existing is None or reward > existing.get("reward", 0):
|
| 47 |
+
memory[task_id] = {
|
| 48 |
+
"best_code": code,
|
| 49 |
+
"reward": round(reward, 4),
|
| 50 |
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 51 |
+
}
|
| 52 |
+
save_memory(memory)
|
| 53 |
+
print(f"[Memory] Stored new best for '{task_id}' with reward={reward:.3f}")
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def retrieve_memory(task_id: str) -> Optional[dict]:
|
| 57 |
+
"""
|
| 58 |
+
Retrieve the best known solution for a task.
|
| 59 |
+
Returns dict with 'best_code' and 'reward', or None.
|
| 60 |
+
"""
|
| 61 |
+
memory = load_memory()
|
| 62 |
+
return memory.get(task_id)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def get_all_memories() -> dict:
|
| 66 |
+
"""Return all stored task memories (for dashboard display)."""
|
| 67 |
+
return load_memory()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
# ── Complexity vs Reward CSV Logger ──────────────────────────────────────────
|
| 71 |
+
|
| 72 |
+
def log_complexity_reward(
|
| 73 |
+
task_id: str,
|
| 74 |
+
reward: float,
|
| 75 |
+
complexity: str,
|
| 76 |
+
step: int,
|
| 77 |
+
method: str = "ollama",
|
| 78 |
+
) -> None:
|
| 79 |
+
"""
|
| 80 |
+
Append a log entry to complexity_rewards.csv.
|
| 81 |
+
Used to track: better algorithms → better rewards.
|
| 82 |
+
"""
|
| 83 |
+
log_entry = {
|
| 84 |
+
"timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
|
| 85 |
+
"task_id": task_id,
|
| 86 |
+
"reward": round(reward, 4),
|
| 87 |
+
"complexity": complexity,
|
| 88 |
+
"step": step,
|
| 89 |
+
"method": method,
|
| 90 |
+
}
|
| 91 |
+
try:
|
| 92 |
+
file_exists = os.path.exists(CSV_FILE)
|
| 93 |
+
with open(CSV_FILE, "a", newline="") as f:
|
| 94 |
+
writer = csv.DictWriter(f, fieldnames=log_entry.keys())
|
| 95 |
+
if not file_exists or f.tell() == 0:
|
| 96 |
+
writer.writeheader()
|
| 97 |
+
writer.writerow(log_entry)
|
| 98 |
+
except Exception as e:
|
| 99 |
+
print(f"[Memory] CSV log error: {e}")
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def get_complexity_reward_stats() -> dict:
|
| 103 |
+
"""
|
| 104 |
+
Read CSV and compute average reward per complexity class.
|
| 105 |
+
Returns dict like: {"O(n)": 0.88, "O(n^2)": 0.55, "O(n^3)": 0.12}
|
| 106 |
+
"""
|
| 107 |
+
stats: dict[str, list] = {}
|
| 108 |
+
try:
|
| 109 |
+
if not os.path.exists(CSV_FILE):
|
| 110 |
+
return {}
|
| 111 |
+
with open(CSV_FILE, "r") as f:
|
| 112 |
+
reader = csv.DictReader(f)
|
| 113 |
+
for row in reader:
|
| 114 |
+
c = row.get("complexity", "unknown")
|
| 115 |
+
r = float(row.get("reward", 0))
|
| 116 |
+
stats.setdefault(c, []).append(r)
|
| 117 |
+
return {k: round(sum(v) / len(v), 3) for k, v in stats.items()}
|
| 118 |
+
except Exception as e:
|
| 119 |
+
print(f"[Memory] Stats error: {e}")
|
| 120 |
+
return {}
|
server/models.py
CHANGED
|
@@ -8,7 +8,8 @@ class CodeArenaObservation(BaseModel):
|
|
| 8 |
previous_attempts: List[str]
|
| 9 |
|
| 10 |
class CodeArenaAction(BaseModel):
|
| 11 |
-
proposed_fix: str
|
|
|
|
| 12 |
|
| 13 |
class TaskInfo(BaseModel):
|
| 14 |
task_id: str
|
|
|
|
| 8 |
previous_attempts: List[str]
|
| 9 |
|
| 10 |
class CodeArenaAction(BaseModel):
|
| 11 |
+
proposed_fix: Optional[str] = None
|
| 12 |
+
action: Optional[str] = None
|
| 13 |
|
| 14 |
class TaskInfo(BaseModel):
|
| 15 |
task_id: str
|
server/raw_runner.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import subprocess
|
| 2 |
+
import time
|
| 3 |
+
import os
|
| 4 |
+
import tempfile
|
| 5 |
+
import sys
|
| 6 |
+
from pydantic import BaseModel
|
| 7 |
+
from typing import Optional
|
| 8 |
+
|
| 9 |
+
class RawRunResult(BaseModel):
|
| 10 |
+
stdout: str
|
| 11 |
+
stderr: str
|
| 12 |
+
execution_time: float
|
| 13 |
+
time_complexity_hint: str
|
| 14 |
+
|
| 15 |
+
def analyze_complexity_hint_fallback(code: str, exec_time: float) -> str:
|
| 16 |
+
"""Fallback rough hint about time complexity based on loops and execution time."""
|
| 17 |
+
loops = code.count("for ") + code.count("while ")
|
| 18 |
+
nested_loops = code.count("for") + code.count("while") if " for" in code or " while" in code else 0
|
| 19 |
+
|
| 20 |
+
if "def " not in code:
|
| 21 |
+
return "N/A (No function defined)"
|
| 22 |
+
|
| 23 |
+
hint = "O(1) or O(N)"
|
| 24 |
+
if nested_loops >= 2:
|
| 25 |
+
hint = "O(N^2) or O(N^3) detected"
|
| 26 |
+
elif loops >= 1:
|
| 27 |
+
hint = "O(N) or O(N log N) detected"
|
| 28 |
+
|
| 29 |
+
if exec_time > 1.0:
|
| 30 |
+
hint += " — High execution time, consider optimizing!"
|
| 31 |
+
elif exec_time < 0.01:
|
| 32 |
+
hint += " — Runs very fast!"
|
| 33 |
+
|
| 34 |
+
return hint
|
| 35 |
+
|
| 36 |
+
def analyze_complexity_ai(code: str, exec_time: float) -> str:
|
| 37 |
+
"""Use Ollama AI to perform a 5-step complexity analysis on the custom code."""
|
| 38 |
+
try:
|
| 39 |
+
import urllib.request
|
| 40 |
+
import json
|
| 41 |
+
|
| 42 |
+
prompt = f"""You are an expert Python performance analyst.
|
| 43 |
+
|
| 44 |
+
Analyze the following code using these 5 steps:
|
| 45 |
+
1. Identify the core algorithm.
|
| 46 |
+
2. Calculate current Time Complexity (Big-O).
|
| 47 |
+
3. Calculate current Space Complexity (Big-O).
|
| 48 |
+
4. Identify bottlenecks.
|
| 49 |
+
5. Propose a more efficient time complexity if possible.
|
| 50 |
+
|
| 51 |
+
CODE:
|
| 52 |
+
{code}
|
| 53 |
+
|
| 54 |
+
Return a concise 5-line summary (one line per step). No markdown blocks."""
|
| 55 |
+
|
| 56 |
+
payload = json.dumps({
|
| 57 |
+
"model": "codearena",
|
| 58 |
+
"prompt": prompt,
|
| 59 |
+
"stream": False,
|
| 60 |
+
"options": {"temperature": 0.1, "num_predict": 256}
|
| 61 |
+
}).encode()
|
| 62 |
+
|
| 63 |
+
req = urllib.request.Request(
|
| 64 |
+
"http://localhost:11434/api/generate",
|
| 65 |
+
data=payload,
|
| 66 |
+
headers={"Content-Type": "application/json"},
|
| 67 |
+
method="POST"
|
| 68 |
+
)
|
| 69 |
+
with urllib.request.urlopen(req, timeout=10) as resp:
|
| 70 |
+
data = json.loads(resp.read())
|
| 71 |
+
result = data.get("response", "").strip()
|
| 72 |
+
if result:
|
| 73 |
+
return f"\n🤖 AI Complexity Analysis:\n{result}"
|
| 74 |
+
except Exception as e:
|
| 75 |
+
print(f"Ollama complexity failed: {e}")
|
| 76 |
+
pass
|
| 77 |
+
|
| 78 |
+
return analyze_complexity_hint_fallback(code, exec_time)
|
| 79 |
+
|
| 80 |
+
def run_raw_code(code: str, timeout: float = 5.0) -> RawRunResult:
|
| 81 |
+
"""Runs arbitrary Python code and returns output, errors, and time."""
|
| 82 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f:
|
| 83 |
+
f.write(code)
|
| 84 |
+
temp_file = f.name
|
| 85 |
+
|
| 86 |
+
start_time = time.time()
|
| 87 |
+
try:
|
| 88 |
+
process = subprocess.run(
|
| 89 |
+
[sys.executable, temp_file],
|
| 90 |
+
capture_output=True,
|
| 91 |
+
text=True,
|
| 92 |
+
timeout=timeout
|
| 93 |
+
)
|
| 94 |
+
exec_time = time.time() - start_time
|
| 95 |
+
|
| 96 |
+
stdout = process.stdout
|
| 97 |
+
stderr = process.stderr
|
| 98 |
+
|
| 99 |
+
hint = analyze_complexity_ai(code, exec_time)
|
| 100 |
+
|
| 101 |
+
return RawRunResult(
|
| 102 |
+
stdout=stdout,
|
| 103 |
+
stderr=stderr,
|
| 104 |
+
execution_time=exec_time,
|
| 105 |
+
time_complexity_hint=hint
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
except subprocess.TimeoutExpired as e:
|
| 109 |
+
exec_time = time.time() - start_time
|
| 110 |
+
return RawRunResult(
|
| 111 |
+
stdout=e.stdout.decode('utf-8') if e.stdout else "",
|
| 112 |
+
stderr="Execution timed out! The code took too long to run or entered an infinite loop.",
|
| 113 |
+
execution_time=timeout,
|
| 114 |
+
time_complexity_hint="O(∞) - Infinite loop or very high complexity."
|
| 115 |
+
)
|
| 116 |
+
finally:
|
| 117 |
+
if os.path.exists(temp_file):
|
| 118 |
+
os.remove(temp_file)
|
tasks/hard.py
CHANGED
|
@@ -27,11 +27,13 @@ class TestHard(unittest.TestCase):
|
|
| 27 |
def test_empty(self):
|
| 28 |
self.assertEqual(max_subarray_sum([]), 0)
|
| 29 |
def test_large(self):
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
ans = max_subarray_sum(arr)
|
| 34 |
-
|
|
|
|
|
|
|
| 35 |
""",
|
| 36 |
-
optimal_time_seconds=0.
|
| 37 |
)
|
|
|
|
| 27 |
def test_empty(self):
|
| 28 |
self.assertEqual(max_subarray_sum([]), 0)
|
| 29 |
def test_large(self):
|
| 30 |
+
import time
|
| 31 |
+
arr = list(range(1000)) # N=1000
|
| 32 |
+
start = time.time()
|
| 33 |
ans = max_subarray_sum(arr)
|
| 34 |
+
end = time.time()
|
| 35 |
+
self.assertLess(end - start, 0.05, "Execution time exceeded optimal threshold! Your complexity is worse than O(N).")
|
| 36 |
+
self.assertEqual(ans, sum(arr))
|
| 37 |
""",
|
| 38 |
+
optimal_time_seconds=0.05
|
| 39 |
)
|
temp_grpo_check.py
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
import argparse
|
| 3 |
+
from typing import Any
|
| 4 |
+
|
| 5 |
+
import httpx
|
| 6 |
+
from datasets import Dataset
|
| 7 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 8 |
+
from trl import GRPOConfig, GRPOTrainer
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
ENV_URL = "http://127.0.0.1:7860"
|
| 12 |
+
MODEL_NAME = "distilgpt2"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _extract_text(completion: Any) -> str:
|
| 16 |
+
if isinstance(completion, str):
|
| 17 |
+
return completion
|
| 18 |
+
if isinstance(completion, list):
|
| 19 |
+
chunks = []
|
| 20 |
+
for item in completion:
|
| 21 |
+
if isinstance(item, dict) and "content" in item:
|
| 22 |
+
chunks.append(str(item["content"]))
|
| 23 |
+
else:
|
| 24 |
+
chunks.append(str(item))
|
| 25 |
+
return "\n".join(chunks)
|
| 26 |
+
if isinstance(completion, dict):
|
| 27 |
+
return str(completion.get("content", ""))
|
| 28 |
+
return str(completion)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _clean_fix(text: str) -> str:
|
| 32 |
+
text = text.strip()
|
| 33 |
+
text = re.sub(r"^```(?:python)?\s*", "", text)
|
| 34 |
+
text = re.sub(r"\s*```$", "", text)
|
| 35 |
+
return text.strip() or "pass"
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def codearena_reward_func(completions, prompts, **kwargs):
|
| 39 |
+
rewards = []
|
| 40 |
+
with httpx.Client(timeout=60.0) as client:
|
| 41 |
+
for completion in completions:
|
| 42 |
+
proposed_fix = _clean_fix(_extract_text(completion))
|
| 43 |
+
reward = 0.001
|
| 44 |
+
for _ in range(2):
|
| 45 |
+
try:
|
| 46 |
+
client.post(f"{ENV_URL}/reset", json={"task_id": "easy-1"})
|
| 47 |
+
res = client.post(
|
| 48 |
+
f"{ENV_URL}/step",
|
| 49 |
+
json={"proposed_fix": proposed_fix},
|
| 50 |
+
)
|
| 51 |
+
reward = float(res.json().get("reward", 0.001))
|
| 52 |
+
break
|
| 53 |
+
except Exception:
|
| 54 |
+
reward = 0.001
|
| 55 |
+
rewards.append(max(0.001, min(0.999, reward)))
|
| 56 |
+
return rewards
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def main():
|
| 60 |
+
parser = argparse.ArgumentParser()
|
| 61 |
+
parser.add_argument("--max-steps", type=int, default=3)
|
| 62 |
+
parser.add_argument("--output-dir", type=str, default="./grpo-check-output")
|
| 63 |
+
args = parser.parse_args()
|
| 64 |
+
|
| 65 |
+
prompts = [
|
| 66 |
+
"Fix this Python function: def average_list(numbers)\\n if length(numbers) == 0:\\n return 0\\n return sum(numbers) / length(numbers)",
|
| 67 |
+
"Repair all root-cause issues in the function and keep readability high.",
|
| 68 |
+
"Return a corrected Python function only. Ensure tests pass.",
|
| 69 |
+
"Fix missing syntax and replace invalid APIs with valid Python APIs.",
|
| 70 |
+
"Correct both compile and semantic issues in the provided function.",
|
| 71 |
+
"Provide a secure, clean fix for average_list in Python.",
|
| 72 |
+
]
|
| 73 |
+
train_dataset = Dataset.from_dict({"prompt": prompts})
|
| 74 |
+
|
| 75 |
+
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME)
|
| 76 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
| 77 |
+
if tokenizer.pad_token is None:
|
| 78 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 79 |
+
|
| 80 |
+
training_args = GRPOConfig(
|
| 81 |
+
output_dir=args.output_dir,
|
| 82 |
+
learning_rate=1e-5,
|
| 83 |
+
max_steps=args.max_steps,
|
| 84 |
+
per_device_train_batch_size=2,
|
| 85 |
+
gradient_accumulation_steps=1,
|
| 86 |
+
logging_steps=1,
|
| 87 |
+
num_generations=2,
|
| 88 |
+
max_prompt_length=256,
|
| 89 |
+
max_completion_length=96,
|
| 90 |
+
temperature=0.7,
|
| 91 |
+
top_p=0.9,
|
| 92 |
+
repetition_penalty=1.1,
|
| 93 |
+
shuffle_dataset=False,
|
| 94 |
+
seed=42,
|
| 95 |
+
bf16=False,
|
| 96 |
+
fp16=False,
|
| 97 |
+
report_to=[],
|
| 98 |
+
)
|
| 99 |
+
|
| 100 |
+
trainer = GRPOTrainer(
|
| 101 |
+
model=model,
|
| 102 |
+
reward_funcs=codearena_reward_func,
|
| 103 |
+
args=training_args,
|
| 104 |
+
train_dataset=train_dataset,
|
| 105 |
+
)
|
| 106 |
+
trainer.train()
|
| 107 |
+
print("GRPO check finished.")
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
if __name__ == "__main__":
|
| 111 |
+
main()
|