How to Serve Needle in the Playground UI for Interactive Testing and Browser-Based Fine-Tuning
Launch the Needle Playground with python -m needle.playground.server --host 0.0.0.0 --port 8080 to get a web UI for interactive model testing and in-browser LoRA fine-tuning.
The Needle Playground is a lightweight, self-contained web interface built into the cactus-compute/needle repository. It lets you load model checkpoints, experiment with tool-calling JSON schemas, run inference, and fine-tune models directly in your browser—no separate frontend build step required. This guide covers how to serve Needle in the Playground UI, the underlying architecture, and how to perform interactive testing and browser-based fine-tuning.
Architecture Overview
The Playground consists of three tightly integrated components defined in [needle/playground/server.py](https://github.com/cactus-compute/needle/blob/main/needle/playground/server.py):
| Component | Responsibility | Source Location |
|---|---|---|
| Engine | Loads Needle models, manages agent state, provides thread-safe inference | Engine class, lines 17-34 |
| HTTP Server | Serves static UI files and JSON API endpoints | ThreadingHTTPServer setup, line 76 |
| Playground UI | Pure HTML/JS frontend for model interaction and fine-tuning | [index.html](https://github.com/cactus-compute/needle/blob/main/needle/playground/index.html), [app.js](https://github.com/cactus-compute/needle/blob/main/needle/playground/app.js) |
Starting the Playground Server
Basic Launch
After installing Needle (pip install . or pip install -e . for development), start the server:
python -m needle.playground.server --host 0.0.0.0 --port 8080
This invokes the main(args) function, which initializes the Engine and starts a ThreadingHTTPServer.
Starting from Python Code
For programmatic control, import and call main directly:
import argparse
from needle.playground.server import main
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", type=int, default=8080)
parser.add_argument("--weights", help="Path to a .cact checkpoint")
args = parser.parse_args()
main(args) # Launches ThreadingHTTPServer with Engine
Once running, open http://localhost:8080 to access the Playground UI.
The Engine: Model Management and Inference
The Engine class is the core server-side component. It lazily loads models and maintains thread-safe access to the Needle agent.
Lazy Model Loading
def load(self):
from .. import Needle
self.agent = Needle(tools="[]", weights=self.weights)
self.tools_json = "[]"
The engine only loads weights on first use, minimizing startup time. When a client uploads a new checkpoint via /load-model, the engine swaps the weight path and calls load() again.
Thread-Safe Completion
The complete method wraps agent inference with a lock, ensuring concurrent requests don't corrupt model state:
def complete(self, query: str, tools_json: str) -> dict:
with self._lock:
# Update tools if changed, then run inference
...
API Endpoints for Interactive Testing
All endpoints are implemented in a single _Handler class that extends BaseHTTPRequestHandler. The _send method provides uniform JSON serialization and error handling.
| Endpoint | Method | Purpose |
|---|---|---|
/model |
GET | Returns current model name for UI display |
/complete |
POST | Runs single-turn inference with tools and query |
/reset |
POST | Clears agent conversation state |
/load-model |
POST | Accepts .cact file upload, reloads engine |
/finetune |
POST | Starts background LoRA fine-tuning job |
/finetune/status |
GET | Polls fine-tuning progress from _FT dict |
/download/<file> |
GET | Serves generated LoRA checkpoint for download |
Calling the Completion API Programmatically
Test your tool schemas without the browser:
import requests, json
url = "http://localhost:8080/complete"
payload = {
"query": "lock the front door",
"tools": [
{
"name": "lock_door",
"description": "Lock a door.",
"parameters": {
"type": "object",
"properties": {"door": {"type": "string"}},
"required": ["door"]
}
}
]
}
resp = requests.post(url, json=payload)
print(json.dumps(resp.json(), indent=2))
Browser-Based Fine-Tuning Pipeline
The Playground's most powerful feature is end-to-end fine-tuning without leaving the browser. When you click "Finetune on these tools", the server spawns a background thread running _finetune_worker.
Fine-Tuning Workflow
- Data Generation — Calls
generate_datasetwith your tools JSON and OpenRouter API key, writing synthetic examples to a temp JSONL file - LoRA Training — Invokes
finetune_localfrom [needle/model/finetune.py](https://github.com/cactus-compute/needle/blob/main/needle/model/finetune.py) to train an adapter (needle_playground_lora.pkl) - Export — Uses
build_mainfrom [needle/model/export.py](https://github.com/cactus-compute/needle/blob/main/needle/model/export.py) to merge LoRA weights into a new checkpoint (needle_tuned.cact) - Hot Reload — The
Engineautomatically loads the tuned model for immediate testing
Progress is tracked in the module-level _FT dictionary and streamed to the UI via /finetune/status polling.
Triggering Fine-Tuning via HTTP
import requests, json
url = "http://localhost:8080/finetune"
payload = {
"tools": json.dumps([
{
"name": "set_lights",
"description": "Adjust lighting brightness",
"parameters": {
"type": "object",
"properties": {
"room": {"type": "string"},
"brightness": {"type": "integer", "minimum": 0, "maximum": 100}
},
"required": ["room", "brightness"]
}
}
]),
"api_key": "sk-or-xxxxxxxxxxxxxxxx", # OpenRouter API key
"samples": 200 # Number of synthetic training examples
}
resp = requests.post(url, json=payload)
print(resp.json()) # Returns job ID for status polling
Typical Interaction Flow
Once the Playground is running, here's the standard workflow for interactive testing:
-
Load or confirm model — The UI defaults to built-in weights, or upload a
.cactcheckpoint via/load-model -
Define tools — Select a preset (Smart Home, Calculator, etc.) or paste custom JSON into the sidebar editor
-
Test queries — Type natural language commands and click Run. The UI POSTs to
/completeand displays parsed function calls or refusal messages -
Fine-tune — Click Finetune on these tools, provide your OpenRouter key, adjust sample count, and start. The modal shows real-time progress: data generation → training → building
.cact -
Download and iterate — When complete, download
needle_tuned.cactor continue testing the hot-reloaded model immediately
Key Files Reference
| File | Purpose |
|---|---|
needle/playground/server.py |
Core server, Engine class, request handlers, _finetune_worker |
needle/playground/index.html |
Static UI skeleton served at root path |
needle/playground/app.js |
Frontend logic: presets, query submission, fine-tune polling |
needle/model/finetune.py |
Synthetic data generation and LoRA training |
needle/model/export.py |
Checkpoint merging and .cact export |
needle/cli.py |
Main CLI entry point used by playground launcher |
Summary
- Serve Needle in the Playground UI with
python -m needle.playground.serverusing standard--hostand--portarguments - The Engine class in
server.pymanages lazy model loading and thread-safe inference - Seven JSON API endpoints provide complete coverage: model info, completion, reset, upload, fine-tuning, status polling, and download
- Browser-based fine-tuning runs a four-stage pipeline (data generation → LoRA training → export → hot reload) triggered by a single POST to
/finetune - All UI assets are static files—no build process required—making deployment trivial
Frequently Asked Questions
What file format does the Playground use for model checkpoints?
Needle uses .cact files—custom checkpoints that bundle base weights with optional LoRA adapters. Upload these via the Load model button or POST to /load-model. The Engine.load() method handles deserialization.
Can I fine-tune without an OpenRouter API key?
No. The _finetune_worker function calls generate_dataset, which uses OpenRouter's API to synthesize tool-calling training examples. The key is passed in the /finetune POST body and never stored server-side.
How do I know when fine-tuning is complete?
Poll the GET /finetune/status endpoint. The server updates the _FT dictionary with stage progress ("generating", "training", "building", "done"). The UI polls this automatically; programmatic callers should poll every 2-5 seconds until status equals "done".
Is the Playground suitable for production deployments?
The Playground uses Python's built-in ThreadingHTTPServer, which is not production-grade. For production, place the Playground behind a reverse proxy (nginx, traefik) or rewrite the server using FastAPI/uvicorn. The Engine class is framework-agnostic and can be adapted to any ASGI/WSGI server.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →