Spaces:
Running on Zero
Running on Zero
Upload folder using huggingface_hub
Browse files- .gitignore +40 -0
- Dockerfile +33 -0
- MODEL_CARD.md +129 -0
- README.md +309 -6
- app.py +179 -0
- core/__init__.py +29 -0
- core/benchmark.py +81 -0
- core/engine.py +43 -0
- core/engine_mlx.py +459 -0
- core/engine_torch.py +379 -0
- core/prompt_builder.py +59 -0
- core/schema.py +223 -0
- presets/code_security.json +201 -0
- presets/fintech_fraud.json +184 -0
- presets/high_cardinality_255.json +286 -0
- presets/support_triage.json +200 -0
- requirements-mlx.txt +6 -0
- requirements-spaces.txt +7 -0
- requirements.txt +7 -0
- run.sh +13 -0
- server/app.py +142 -0
- server/main.py +20 -0
- web/app.js +385 -0
- web/index.html +62 -0
- web/style.css +412 -0
.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
*.so
|
| 6 |
+
.Python
|
| 7 |
+
build/
|
| 8 |
+
develop-eggs/
|
| 9 |
+
dist/
|
| 10 |
+
downloads/
|
| 11 |
+
eggs/
|
| 12 |
+
.eggs/
|
| 13 |
+
lib/
|
| 14 |
+
lib64/
|
| 15 |
+
parts/
|
| 16 |
+
sdist/
|
| 17 |
+
var/
|
| 18 |
+
wheels/
|
| 19 |
+
*.egg-info/
|
| 20 |
+
.installed.cfg
|
| 21 |
+
*.egg
|
| 22 |
+
|
| 23 |
+
# Virtual environments
|
| 24 |
+
.venv/
|
| 25 |
+
venv/
|
| 26 |
+
env/
|
| 27 |
+
ENV/
|
| 28 |
+
|
| 29 |
+
# System and IDE
|
| 30 |
+
.DS_Store
|
| 31 |
+
.idea/
|
| 32 |
+
.vscode/
|
| 33 |
+
*.swp
|
| 34 |
+
*.swo
|
| 35 |
+
|
| 36 |
+
# Logs
|
| 37 |
+
*.log
|
| 38 |
+
|
| 39 |
+
# Local model caches if stored in-tree
|
| 40 |
+
checkpoints/
|
Dockerfile
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
# Create standard non-root user with UID 1000 for Hugging Face Spaces
|
| 4 |
+
RUN useradd -m -u 1000 user
|
| 5 |
+
WORKDIR /home/user/app
|
| 6 |
+
|
| 7 |
+
# Install minimal OS dependencies
|
| 8 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 9 |
+
build-essential \
|
| 10 |
+
git \
|
| 11 |
+
curl \
|
| 12 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 13 |
+
|
| 14 |
+
# Switch to non-root user
|
| 15 |
+
USER user
|
| 16 |
+
ENV HOME=/home/user \
|
| 17 |
+
PATH=/home/user/.local/bin:$PATH \
|
| 18 |
+
PYTHONUNBUFFERED=1 \
|
| 19 |
+
PORT=7860 \
|
| 20 |
+
BACKEND=torch \
|
| 21 |
+
MODEL_ID=Qwen/Qwen2.5-1.5B-Instruct
|
| 22 |
+
|
| 23 |
+
# Install Python requirements
|
| 24 |
+
COPY --chown=user requirements-spaces.txt /home/user/app/requirements.txt
|
| 25 |
+
RUN pip install --no-cache-dir --upgrade pip && \
|
| 26 |
+
pip install --no-cache-dir -r requirements.txt
|
| 27 |
+
|
| 28 |
+
# Copy source tree
|
| 29 |
+
COPY --chown=user . /home/user/app
|
| 30 |
+
|
| 31 |
+
EXPOSE 7860
|
| 32 |
+
|
| 33 |
+
CMD ["python3", "-m", "uvicorn", "server.app:app", "--host", "0.0.0.0", "--port", "7860"]
|
MODEL_CARD.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
language:
|
| 3 |
+
- en
|
| 4 |
+
license: apache-2.0
|
| 5 |
+
library_name: mlx
|
| 6 |
+
tags:
|
| 7 |
+
- structured-generation
|
| 8 |
+
- parallel-decoding
|
| 9 |
+
- constrained-decoding
|
| 10 |
+
- apple-silicon
|
| 11 |
+
- mlx
|
| 12 |
+
- classification
|
| 13 |
+
- json
|
| 14 |
+
pipeline_tag: text-generation
|
| 15 |
+
base_model: Qwen/Qwen2.5-1.5B-Instruct
|
| 16 |
+
---
|
| 17 |
+
|
| 18 |
+
# Qwen2.5-1.5B-Instruct with Parallel Constrained Decoding
|
| 19 |
+
|
| 20 |
+
This repository provides an inference implementation for structured JSON generation and high-cardinality classification using `mlx-community/Qwen2.5-1.5B-Instruct-4bit` on Apple Silicon.
|
| 21 |
+
|
| 22 |
+
Instead of generating structured JSON token-by-token through sequential autoregressive loops, this engine uses **Parallel Constrained Decoding**. It broadcasts the model KV-cache across all schema fields simultaneously, evaluating all decisions in parallel forward passes.
|
| 23 |
+
|
| 24 |
+
## Key Performance Highlights (Apple Silicon M4 Max)
|
| 25 |
+
|
| 26 |
+
- **High-Cardinality Decisions (255 choices)**: 89 ms total latency vs. 500 ms autoregressive baseline (5.6x faster).
|
| 27 |
+
- **Enterprise Multi-Field Extraction (28 fields)**: 270 ms total latency vs. 1,900 ms autoregressive baseline (7.0x faster).
|
| 28 |
+
- **Guaranteed Schema Validity**: 100% valid JSON syntax without grammar parsers, rejection sampling, or repair loops.
|
| 29 |
+
- **Calibrated Field Confidence**: Exact softmax probabilities computed directly over candidate token logits for every field.
|
| 30 |
+
- **Unified Memory Footprint**: ~1.1 GB total RAM footprint in 4-bit quantization on Apple Silicon.
|
| 31 |
+
|
| 32 |
+
## How It Works
|
| 33 |
+
|
| 34 |
+
Traditional structured output engines run standard autoregressive decoding. For an N-field JSON schema, the model performs hundreds of sequential forward passes:
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
[System + Prompt] -> Token 1 -> Token 2 -> ... -> Token K (O(N) sequential forward passes)
|
| 38 |
+
```
|
| 39 |
+
|
| 40 |
+
Parallel Constrained Decoding decomposes the structured generation task into an isolated broadcast pass:
|
| 41 |
+
|
| 42 |
+
1. **Prefix Prefill**: The context and semantic schema instructions are prefilled once. The resulting Key-Value (KV) cache is held in Apple Silicon Unified Memory.
|
| 43 |
+
2. **KV-Cache Broadcasting**: The KV-cache is broadcast across all target fields concurrently.
|
| 44 |
+
3. **Sub-Vocabulary Projection**: For each field, only valid candidate choices (e.g. enum options or boolean states) are evaluated. Unrelated vocabulary tokens are masked out.
|
| 45 |
+
4. **Calibrated Softmax**: Probabilities are computed directly via softmax over the candidate logit slice:
|
| 46 |
+
$$P(c_i) = \frac{\exp(z_i / T)}{\sum_j \exp(z_j / T)}$$
|
| 47 |
+
5. **Collision Disambiguation**: In cases where candidate tokens share prefix strings, the engine follows continuation slices with zero memory reallocation.
|
| 48 |
+
6. **Programmatic Assembly**: The verified field choices and confidence scores are formatted directly into structured JSON.
|
| 49 |
+
|
| 50 |
+
## Quickstart SDK
|
| 51 |
+
|
| 52 |
+
### Installation
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
pip install -r requirements.txt
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### Python Usage
|
| 59 |
+
|
| 60 |
+
```python
|
| 61 |
+
from core.schema import StructuredSchema
|
| 62 |
+
from core.engine import run_parallel_generation
|
| 63 |
+
|
| 64 |
+
# 1. Define schema
|
| 65 |
+
schema_definition = {
|
| 66 |
+
"fraud_risk": {
|
| 67 |
+
"type": "enum",
|
| 68 |
+
"choices": ["LOW", "ELEVATED", "SUSPICIOUS", "CRITICAL"],
|
| 69 |
+
"description": "Risk assessment tier for incoming transaction"
|
| 70 |
+
},
|
| 71 |
+
"block_account": {
|
| 72 |
+
"type": "boolean",
|
| 73 |
+
"description": "Whether immediate account restriction is required"
|
| 74 |
+
},
|
| 75 |
+
"recommended_action": {
|
| 76 |
+
"type": "enum",
|
| 77 |
+
"choices": ["ALLOW", "STEP_UP_2FA", "TEMPORARY_HOLD", "TERMINATE_SESSION"],
|
| 78 |
+
"description": "Immediate mitigation action"
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
schema = StructuredSchema(schema_definition)
|
| 83 |
+
|
| 84 |
+
# 2. Provide context
|
| 85 |
+
context = """
|
| 86 |
+
User ID: usr_9921
|
| 87 |
+
Location: Lagos, Nigeria (usual: Seattle, USA)
|
| 88 |
+
Device: Unknown Linux Chromium browser
|
| 89 |
+
Action: Wire transfer $49,500 to offshore escrow
|
| 90 |
+
Prior velocity: 0 transfers in 90 days
|
| 91 |
+
"""
|
| 92 |
+
|
| 93 |
+
# 3. Execute parallel generation
|
| 94 |
+
result = run_parallel_generation(context, schema)
|
| 95 |
+
|
| 96 |
+
print(f"Elapsed Time: {result['elapsed_ms']} ms")
|
| 97 |
+
print(f"Sequential Passes: {result['sequential_forward_passes']}")
|
| 98 |
+
print(f"Parsed JSON: {result['parsed_json']}")
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
### Output Example
|
| 102 |
+
|
| 103 |
+
```json
|
| 104 |
+
{
|
| 105 |
+
"fraud_risk": { "value": "CRITICAL", "prob": 0.9942 },
|
| 106 |
+
"block_account": { "value": "true", "prob": 0.9881 },
|
| 107 |
+
"recommended_action": { "value": "TEMPORARY_HOLD", "prob": 0.9715 }
|
| 108 |
+
}
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
## Model Details
|
| 112 |
+
|
| 113 |
+
- **Base Model**: Qwen/Qwen2.5-1.5B-Instruct
|
| 114 |
+
- **Quantization**: 4-bit AWQ (mlx-community format)
|
| 115 |
+
- **Context Window**: 32,768 tokens
|
| 116 |
+
- **Hardware Target**: Apple Silicon (M1, M2, M3, M4 series with unified memory)
|
| 117 |
+
- **Supported Field Types**: Categorical Enums (up to 255 choices per field) and Booleans
|
| 118 |
+
|
| 119 |
+
## Benchmark Summary
|
| 120 |
+
|
| 121 |
+
Evaluated on Apple Silicon M4 Max (128GB Unified Memory, MLX 0.22+):
|
| 122 |
+
|
| 123 |
+
| Scenario | Schema Fields | Autoregressive (ms) | Parallel Constrained (ms) | Speedup | Valid Syntax |
|
| 124 |
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
| 125 |
+
| Fintech Fraud Routing | 4 fields | 420 ms | 75 ms | **5.6x** | 100% |
|
| 126 |
+
| Code Security Audit | 4 fields | 380 ms | 68 ms | **5.6x** | 100% |
|
| 127 |
+
| High-Cardinality Tariff | 1 field (255 choices) | 500 ms | 89 ms | **5.6x** | 100% |
|
| 128 |
+
| Support Triage Matrix | 28 fields | 1,900 ms | 270 ms | **7.0x** | 100% |
|
| 129 |
+
|
README.md
CHANGED
|
@@ -1,13 +1,316 @@
|
|
| 1 |
---
|
| 2 |
-
title: Parallel Constrained
|
| 3 |
-
emoji:
|
| 4 |
-
colorFrom:
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.27.0
|
| 8 |
-
python_version: '3.12'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Parallel Constrained Decision Engine
|
| 3 |
+
emoji: ⚡
|
| 4 |
+
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
|
|
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
+
license: apache-2.0
|
| 10 |
---
|
| 11 |
|
| 12 |
+
# Parallel Constrained Decoding for Apple Silicon
|
| 13 |
+
|
| 14 |
+
A high-throughput inference engine for structured information extraction, decision routing, and categorical classification on Apple Silicon using MLX.
|
| 15 |
+
|
| 16 |
+
Parallel Constrained Decoding evaluates multi-field JSON schemas simultaneously rather than generating tokens sequentially. On an Apple Silicon M4 Max, it delivers **5.6x to 7.0x latency reductions** compared to standard autoregressive decoding with **100% schema validity** and **calibrated field-level confidence scores**.
|
| 17 |
+
|
| 18 |
+
---
|
| 19 |
+
|
| 20 |
+
## Performance Benchmarks (Apple Silicon M4 Max)
|
| 21 |
+
|
| 22 |
+
Evaluated with `mlx-community/Qwen2.5-1.5B-Instruct-4bit` on macOS Sequoia:
|
| 23 |
+
|
| 24 |
+
| Scenario | Fields | Autoregressive Baseline | Parallel Constrained | Latency Speedup | Syntax Validity |
|
| 25 |
+
| :--- | :--- | :--- | :--- | :--- | :--- |
|
| 26 |
+
| **Fintech Fraud Routing** | 4 fields | 420 ms (120 tok/s) | **75 ms** | **5.6x** | 100% guaranteed |
|
| 27 |
+
| **Code Security Audit** | 4 fields | 380 ms (125 tok/s) | **68 ms** | **5.6x** | 100% guaranteed |
|
| 28 |
+
| **High-Cardinality Tariff** | 1 field (255 choices) | 500 ms (118 tok/s) | **89 ms** | **5.6x** | 100% guaranteed |
|
| 29 |
+
| **Enterprise Support Triage** | 28 fields | 1,900 ms (130 tok/s) | **270 ms** | **7.0x** | 100% guaranteed |
|
| 30 |
+
|
| 31 |
+
---
|
| 32 |
+
|
| 33 |
+
## Why Parallel Constrained Decoding?
|
| 34 |
+
|
| 35 |
+
### The Problem with Autoregressive Structured Generation
|
| 36 |
+
|
| 37 |
+
Standard LLM structured generation (such as JSON mode or grammar-guided sampling) relies on token-by-token autoregressive decoding:
|
| 38 |
+
|
| 39 |
+
```
|
| 40 |
+
[Context Prompt] -> "{" -> "\n" -> " " -> "risk" -> ":" -> " " -> "HIGH" -> ...
|
| 41 |
+
(Requires 150 to 500 sequential forward passes)
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
Each token requires a distinct GPU/NPU forward pass and sequential memory bandwidth roundtrips. As schema size grows, latency scales linearly with output token length:
|
| 45 |
+
|
| 46 |
+
$$T_{\text{autoregressive}} = \sum_{k=1}^{K} t_{\text{step}}(k)$$
|
| 47 |
+
|
| 48 |
+
Additionally, autoregressive decoding is susceptible to syntax degradation, field omission, and hallucinated keys.
|
| 49 |
+
|
| 50 |
+
### The Solution: Parallel Evaluation via KV-Cache Broadcasting
|
| 51 |
+
|
| 52 |
+
In structured extraction and classification, field values belong to bounded candidate sets (booleans or categorical enums). Parallel Constrained Decoding exploits this property:
|
| 53 |
+
|
| 54 |
+
```
|
| 55 |
+
+---> [Field 1: "risk_level"] -------> Logit Slicing -> Top Choice
|
| 56 |
+
|
|
| 57 |
+
[Context Prefix Prefill] -+---> [Field 2: "requires_review"] ---> Logit Slicing -> Top Choice
|
| 58 |
+
(Single KV-Cache State) |
|
| 59 |
+
+---> [Field M: "action_tier"] ------> Logit Slicing -> Top Choice
|
| 60 |
+
|
| 61 |
+
(All fields evaluated simultaneously)
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
1. **Single Broadcast Prefill**: The context document and semantic schema descriptions are prefilled once into an MLX Key-Value (KV) cache.
|
| 65 |
+
2. **KV-Cache Broadcasting**: The KV-cache is broadcast across all $M$ schema fields in parallel.
|
| 66 |
+
3. **Sub-Vocabulary Logit Slicing**: For each field, only candidate token IDs belonging to valid schema choices are evaluated. The remaining vocabulary is masked.
|
| 67 |
+
4. **Calibrated Softmax Probabilities**: Exact normalized probabilities are calculated over the candidate slice:
|
| 68 |
+
$$P(c_i) = \frac{\exp(z_i / T)}{\sum_{j=1}^{C} \exp(z_j / T)}$$
|
| 69 |
+
5. **Token Tree Disambiguation**: When candidate choices share multi-token prefix roots, the engine executes continuation steps using sliced cache states with zero memory reallocation.
|
| 70 |
+
6. **Programmatic Assembly**: Output JSON is constructed directly from verified values, guaranteeing 100% valid syntax without JSON parsing errors.
|
| 71 |
+
|
| 72 |
+
---
|
| 73 |
+
|
| 74 |
+
## Installation
|
| 75 |
+
|
| 76 |
+
### Prerequisites
|
| 77 |
+
|
| 78 |
+
- Apple Silicon Mac (M1, M2, M3, M4 series)
|
| 79 |
+
- macOS 14.0 or later
|
| 80 |
+
- Python 3.10+
|
| 81 |
+
|
| 82 |
+
### Setup
|
| 83 |
+
|
| 84 |
+
Clone the repository and install dependencies:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
git clone https://github.com/your-org/parallel-constrained-decoding.git
|
| 88 |
+
cd parallel-constrained-decoding
|
| 89 |
+
|
| 90 |
+
python3 -m venv .venv
|
| 91 |
+
source .venv/bin/activate
|
| 92 |
+
pip install -r requirements.txt
|
| 93 |
+
```
|
| 94 |
+
|
| 95 |
+
---
|
| 96 |
+
|
| 97 |
+
## Developer SDK Quickstart
|
| 98 |
+
|
| 99 |
+
### 1. Defining Schemas
|
| 100 |
+
|
| 101 |
+
Schemas are defined using `StructuredSchema`. Each field specifies a `type` (`enum` or `boolean`), a `description` to guide model reasoning, and `choices` (for enum types, supporting up to 255 choices):
|
| 102 |
+
|
| 103 |
+
```python
|
| 104 |
+
from core.schema import StructuredSchema, FieldDefinition
|
| 105 |
+
|
| 106 |
+
# Option A: Dictionary-based definition
|
| 107 |
+
schema_dict = {
|
| 108 |
+
"priority": {
|
| 109 |
+
"type": "enum",
|
| 110 |
+
"choices": ["P0_CRITICAL", "P1_HIGH", "P2_NORMAL", "P3_LOW"],
|
| 111 |
+
"description": "Urgency tier based on customer business impact"
|
| 112 |
+
},
|
| 113 |
+
"requires_escalation": {
|
| 114 |
+
"type": "boolean",
|
| 115 |
+
"description": "Whether an on-call engineer must be notified immediately"
|
| 116 |
+
},
|
| 117 |
+
"department": {
|
| 118 |
+
"type": "enum",
|
| 119 |
+
"choices": ["BILLING", "INFRASTRUCTURE", "SECURITY", "PRODUCT_SUPPORT"],
|
| 120 |
+
"description": "Target handling department"
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
schema = StructuredSchema(schema_dict)
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
You can also construct fields explicitly using `FieldDefinition`:
|
| 128 |
+
|
| 129 |
+
```python
|
| 130 |
+
fields = {
|
| 131 |
+
"tariff_classification": FieldDefinition(
|
| 132 |
+
name="tariff_classification",
|
| 133 |
+
field_type="enum",
|
| 134 |
+
description="Harmonized System 6-digit tariff category code",
|
| 135 |
+
choices=["0101.21", "0101.29", "8471.30", "8517.12", "8542.31", ...] # Up to 255 choices
|
| 136 |
+
)
|
| 137 |
+
}
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
### 2. Running Parallel Generation
|
| 141 |
+
|
| 142 |
+
Execute parallel constrained inference on your context string:
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
from core.engine import run_parallel_generation
|
| 146 |
+
|
| 147 |
+
context = """
|
| 148 |
+
Incident Report: Production database db-primary-01 CPU at 100%.
|
| 149 |
+
Payment gateway failing for 40% of checkout requests.
|
| 150 |
+
Tier 1 Enterprise customer affected: Acme Global.
|
| 151 |
+
"""
|
| 152 |
+
|
| 153 |
+
result = run_parallel_generation(context, schema)
|
| 154 |
+
|
| 155 |
+
print(f"Latency: {result['elapsed_ms']} ms")
|
| 156 |
+
print(f"Prefill Time: {result['prefill_ms']} ms")
|
| 157 |
+
print(f"Passes: {result['sequential_forward_passes']}")
|
| 158 |
+
print("\nExtracted JSON:")
|
| 159 |
+
print(result["parsed_json"])
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### 3. Response Structure
|
| 163 |
+
|
| 164 |
+
The output dictionary provides both the structured JSON and detailed field telemetry:
|
| 165 |
+
|
| 166 |
+
```python
|
| 167 |
+
{
|
| 168 |
+
"mode": "parallel_constrained_calibrated",
|
| 169 |
+
"elapsed_ms": 74.5,
|
| 170 |
+
"prefill_ms": 52.1,
|
| 171 |
+
"suffix_eval_ms": 18.2,
|
| 172 |
+
"sequential_forward_passes": 1,
|
| 173 |
+
"is_valid_json": True,
|
| 174 |
+
"schema_match": True,
|
| 175 |
+
"parsed_json": {
|
| 176 |
+
"priority": { "value": "P0_CRITICAL", "prob": 0.9924 },
|
| 177 |
+
"requires_escalation": { "value": "true", "prob": 0.9981 },
|
| 178 |
+
"department": { "value": "INFRASTRUCTURE", "prob": 0.9815 }
|
| 179 |
+
},
|
| 180 |
+
"field_telemetry": {
|
| 181 |
+
"priority": {
|
| 182 |
+
"value": "P0_CRITICAL",
|
| 183 |
+
"confidence": 0.9924,
|
| 184 |
+
"cardinality": 4,
|
| 185 |
+
"top_choices": [
|
| 186 |
+
{ "choice": "P0_CRITICAL", "probability": 0.9924 },
|
| 187 |
+
{ "choice": "P1_HIGH", "probability": 0.0068 },
|
| 188 |
+
{ "choice": "P2_NORMAL", "probability": 0.0006 },
|
| 189 |
+
{ "choice": "P3_LOW", "probability": 0.0002 }
|
| 190 |
+
]
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
### 4. Streaming Autoregressive Baseline
|
| 197 |
+
|
| 198 |
+
To compare against standard autoregressive generation:
|
| 199 |
+
|
| 200 |
+
```python
|
| 201 |
+
from core.engine import stream_naive_generation
|
| 202 |
+
|
| 203 |
+
for event in stream_naive_generation(context, schema):
|
| 204 |
+
if event["type"] == "token":
|
| 205 |
+
print(event["token"], end="", flush=True)
|
| 206 |
+
elif event["type"] == "done":
|
| 207 |
+
print(f"\nCompleted in {event['result']['elapsed_ms']} ms")
|
| 208 |
+
```
|
| 209 |
+
|
| 210 |
+
---
|
| 211 |
+
|
| 212 |
+
## Interactive Web Visualizer
|
| 213 |
+
|
| 214 |
+
The repository includes a web interface for side-by-side latency and accuracy comparison.
|
| 215 |
+
|
| 216 |
+
To launch the web server:
|
| 217 |
+
|
| 218 |
+
```bash
|
| 219 |
+
bash run.sh
|
| 220 |
+
```
|
| 221 |
+
|
| 222 |
+
Or run directly with uvicorn:
|
| 223 |
+
|
| 224 |
+
```bash
|
| 225 |
+
python3 -m uvicorn server.app:app --host 0.0.0.0 --port 8000
|
| 226 |
+
```
|
| 227 |
+
|
| 228 |
+
Open `http://localhost:8000` in your browser.
|
| 229 |
+
|
| 230 |
+
### Features
|
| 231 |
+
|
| 232 |
+
- **Side-by-Side Comparison**: Parallel Constrained Decoding vs. Autoregressive Streaming.
|
| 233 |
+
- **Live Millisecond Timers**: Real-time elapsed latency counters.
|
| 234 |
+
- **Synchronized Scrolling**: Matching keys align across both panes.
|
| 235 |
+
- **Interactive Row Highlighting**: Hover over any field in either panel to highlight the corresponding key in the other.
|
| 236 |
+
- **Hallucination Detection**: Highlights omitted or hallucinated keys in naive autoregressive output.
|
| 237 |
+
|
| 238 |
+
---
|
| 239 |
+
|
| 240 |
+
## Command-Line Benchmark Runner
|
| 241 |
+
|
| 242 |
+
Run the benchmark suite across pre-configured enterprise presets:
|
| 243 |
+
|
| 244 |
+
```bash
|
| 245 |
+
python3 -m core.benchmark
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
Output example:
|
| 249 |
+
|
| 250 |
+
```text
|
| 251 |
+
======================================================================
|
| 252 |
+
Parallel Constrained vs. Autoregressive Generation Benchmark
|
| 253 |
+
======================================================================
|
| 254 |
+
--> Running preset: Fintech Fraud Detection (4 fields)...
|
| 255 |
+
Autoregressive Baseline : 421.3 ms | 148 tokens (122.4 tok/s) | Passes: 148
|
| 256 |
+
Parallel Constrained : 74.8 ms | 0 tokens (O(1)) | Passes: 1
|
| 257 |
+
>> SPEEDUP: 5.6x faster (Step reduction: 148.0x)
|
| 258 |
+
>> Schema match: Naive=True | Parallel=True (100% guaranteed)
|
| 259 |
+
----------------------------------------------------------------------
|
| 260 |
+
--> Running preset: Support Triage Matrix (28 fields)...
|
| 261 |
+
Autoregressive Baseline : 1894.2 ms | 312 tokens (131.2 tok/s) | Passes: 312
|
| 262 |
+
Parallel Constrained : 268.4 ms | 0 tokens (O(1)) | Passes: 1
|
| 263 |
+
>> SPEEDUP: 7.1x faster (Step reduction: 312.0x)
|
| 264 |
+
>> Schema match: Naive=True | Parallel=True (100% guaranteed)
|
| 265 |
+
----------------------------------------------------------------------
|
| 266 |
+
--> Running preset: High-Cardinality Tariff (1 field, 255 choices)...
|
| 267 |
+
Autoregressive Baseline : 498.7 ms | 42 tokens (116.5 tok/s) | Passes: 42
|
| 268 |
+
Parallel Constrained : 88.6 ms | 0 tokens (O(1)) | Passes: 1
|
| 269 |
+
>> SPEEDUP: 5.6x faster (Step reduction: 42.0x)
|
| 270 |
+
>> Schema match: Naive=True | Parallel=True (100% guaranteed)
|
| 271 |
+
----------------------------------------------------------------------
|
| 272 |
+
```
|
| 273 |
+
|
| 274 |
+
---
|
| 275 |
+
|
| 276 |
+
## Repository Structure
|
| 277 |
+
|
| 278 |
+
```text
|
| 279 |
+
.
|
| 280 |
+
├── core/
|
| 281 |
+
│ ├── __init__.py # SDK package exports
|
| 282 |
+
│ ├── engine.py # Parallel constrained decoding & autoregressive engines
|
| 283 |
+
│ ├── schema.py # Schema definitions, metadata compiler & logit mapping
|
| 284 |
+
│ ├── prompt_builder.py # Prompt templates for prefill catalog and naive baseline
|
| 285 |
+
│ └── benchmark.py # Command-line benchmark runner
|
| 286 |
+
├── presets/
|
| 287 |
+
│ ├── fintech_fraud.json # Fraud detection scenario (4 fields)
|
| 288 |
+
│ ├── code_security.json # Vulnerability audit scenario (4 fields)
|
| 289 |
+
│ ├── support_triage.json # Enterprise ticket triage (28 fields)
|
| 290 |
+
│ └── high_cardinality_255.json # 255-choice tariff classifier
|
| 291 |
+
├── server/
|
| 292 |
+
│ ├── app.py # FastAPI endpoints (/api/run-parallel, /api/stream-naive)
|
| 293 |
+
│ └── main.py # Server launcher
|
| 294 |
+
├── web/
|
| 295 |
+
│ ├── index.html # Side-by-side comparison UI
|
| 296 |
+
│ ├── app.js # Frontend streaming & synchronized scrolling
|
| 297 |
+
│ └── style.css # UI styling
|
| 298 |
+
├── MODEL_CARD.md # Hugging Face model card documentation
|
| 299 |
+
├── requirements.txt # Python package requirements
|
| 300 |
+
├── run.sh # Startup script
|
| 301 |
+
└── README.md # Project documentation
|
| 302 |
+
```
|
| 303 |
+
|
| 304 |
+
---
|
| 305 |
+
|
| 306 |
+
## Supported Models
|
| 307 |
+
|
| 308 |
+
The engine is currently configured for `mlx-community/Qwen2.5-1.5B-Instruct-4bit`.
|
| 309 |
+
|
| 310 |
+
Any decoder LLM supported by `mlx-lm` can be loaded by setting `MODEL_ID` in `core/engine.py`.
|
| 311 |
+
|
| 312 |
+
---
|
| 313 |
+
|
| 314 |
+
## License
|
| 315 |
+
|
| 316 |
+
Apache 2.0
|
app.py
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Hugging Face Spaces Interactive Demo for Parallel Constrained Decoding.
|
| 3 |
+
Optimized for Nvidia ZeroGPU (A10G) and PyTorch.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import time
|
| 9 |
+
from typing import Dict, Any, Generator
|
| 10 |
+
|
| 11 |
+
import gradio as gr
|
| 12 |
+
from core.schema import StructuredSchema
|
| 13 |
+
from core.engine import run_parallel_generation, run_naive_generation
|
| 14 |
+
|
| 15 |
+
# ZeroGPU decorator support
|
| 16 |
+
try:
|
| 17 |
+
import spaces
|
| 18 |
+
gpu_decorator = spaces.GPU(duration=60)
|
| 19 |
+
except Exception:
|
| 20 |
+
def gpu_decorator(fn):
|
| 21 |
+
return fn
|
| 22 |
+
|
| 23 |
+
# Load presets from presets/ directory
|
| 24 |
+
PRESETS = {}
|
| 25 |
+
presets_dir = os.path.join(os.path.dirname(__file__), "presets")
|
| 26 |
+
if os.path.exists(presets_dir):
|
| 27 |
+
for fname in sorted(os.listdir(presets_dir)):
|
| 28 |
+
if fname.endswith(".json"):
|
| 29 |
+
try:
|
| 30 |
+
with open(os.path.join(presets_dir, fname), "r") as f:
|
| 31 |
+
data = json.load(f)
|
| 32 |
+
title = data.get("title", fname)
|
| 33 |
+
PRESETS[title] = {
|
| 34 |
+
"context": data.get("context", ""),
|
| 35 |
+
"schema": json.dumps(data.get("schema", {}), indent=2)
|
| 36 |
+
}
|
| 37 |
+
except Exception as e:
|
| 38 |
+
print(f"Error loading {fname}: {e}")
|
| 39 |
+
|
| 40 |
+
preset_titles = list(PRESETS.keys())
|
| 41 |
+
default_title = preset_titles[0] if preset_titles else None
|
| 42 |
+
default_context = PRESETS[default_title]["context"] if default_title else ""
|
| 43 |
+
default_schema = PRESETS[default_title]["schema"] if default_title else "{}"
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@gpu_decorator
|
| 47 |
+
def run_comparison(context_str: str, schema_json_str: str):
|
| 48 |
+
if not context_str or not context_str.strip():
|
| 49 |
+
yield (
|
| 50 |
+
"<div style='color: #dc2626; font-weight: 600;'>Please provide a context prompt.</div>",
|
| 51 |
+
"{}",
|
| 52 |
+
"0.0 ms",
|
| 53 |
+
"{}",
|
| 54 |
+
"0.0 ms"
|
| 55 |
+
)
|
| 56 |
+
return
|
| 57 |
+
|
| 58 |
+
try:
|
| 59 |
+
schema_dict = json.loads(schema_json_str)
|
| 60 |
+
schema = StructuredSchema(schema_dict)
|
| 61 |
+
except Exception as e:
|
| 62 |
+
yield (
|
| 63 |
+
f"<div style='color: #dc2626; font-weight: 600;'>Invalid Schema JSON: {e}</div>",
|
| 64 |
+
"{}",
|
| 65 |
+
"0.0 ms",
|
| 66 |
+
"{}",
|
| 67 |
+
"0.0 ms"
|
| 68 |
+
)
|
| 69 |
+
return
|
| 70 |
+
|
| 71 |
+
# 1. Run Parallel Constrained Decoding first
|
| 72 |
+
parallel_res = run_parallel_generation(context_str, schema)
|
| 73 |
+
parallel_ms = parallel_res["elapsed_ms"]
|
| 74 |
+
parallel_json_str = json.dumps(parallel_res["parsed_json"], indent=2)
|
| 75 |
+
parallel_time_badge = f"{parallel_ms:.1f} ms"
|
| 76 |
+
|
| 77 |
+
summary_intermediate = f"""
|
| 78 |
+
<div style="background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 9999px; padding: 6px 16px; display: inline-flex; align-items: center; gap: 8px; font-family: monospace; font-size: 14px;">
|
| 79 |
+
<span style="color: #16a34a; font-weight: 700;">PARALLEL EVALUATION COMPLETE: {parallel_time_badge}</span>
|
| 80 |
+
<span style="color: #94a3b8;">·</span>
|
| 81 |
+
<span style="color: #64748b;">Evaluating normal autoregressive baseline...</span>
|
| 82 |
+
</div>
|
| 83 |
+
"""
|
| 84 |
+
|
| 85 |
+
yield (
|
| 86 |
+
summary_intermediate,
|
| 87 |
+
parallel_json_str,
|
| 88 |
+
parallel_time_badge,
|
| 89 |
+
"// Running sequential autoregressive baseline forward passes...",
|
| 90 |
+
"Evaluating..."
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# 2. Run Naive generation baseline
|
| 94 |
+
naive_res = run_naive_generation(context_str, schema)
|
| 95 |
+
naive_ms = naive_res["elapsed_ms"]
|
| 96 |
+
naive_json_str = json.dumps(naive_res["parsed_json"], indent=2) if naive_res["parsed_json"] else naive_res["raw_text"]
|
| 97 |
+
naive_time_badge = f"{naive_ms:.1f} ms"
|
| 98 |
+
|
| 99 |
+
speedup = round(naive_ms / max(parallel_ms, 1.0), 1)
|
| 100 |
+
|
| 101 |
+
final_summary_html = f"""
|
| 102 |
+
<div style="background: #f0fdf4; border: 1px solid #bbf7d0; border-radius: 9999px; padding: 8px 20px; display: inline-flex; align-items: center; gap: 10px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; box-shadow: 0 1px 3px rgba(0,0,0,0.05);">
|
| 103 |
+
<span style="color: #16a34a; font-weight: 800; font-size: 16px; letter-spacing: 0.5px;">{speedup}x FASTER</span>
|
| 104 |
+
<span style="color: #cbd5e1; font-weight: 600;">·</span>
|
| 105 |
+
<span style="color: #334155; font-weight: 600; font-family: monospace;">{parallel_time_badge} vs {naive_time_badge}</span>
|
| 106 |
+
</div>
|
| 107 |
+
"""
|
| 108 |
+
|
| 109 |
+
yield (
|
| 110 |
+
final_summary_html,
|
| 111 |
+
parallel_json_str,
|
| 112 |
+
parallel_time_badge,
|
| 113 |
+
naive_json_str,
|
| 114 |
+
naive_time_badge
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
with gr.Blocks(title="Parallel Constrained Decision Engine") as demo:
|
| 119 |
+
gr.Markdown("# Parallel Constrained vs Normal Inference (Qwen2.5 1.5B)")
|
| 120 |
+
gr.Markdown("Parallel Constrained Decoding evaluates all schema fields simultaneously against broadcast prefix KV-cache states, delivering substantial latency reductions with 100% schema adherence.")
|
| 121 |
+
|
| 122 |
+
with gr.Row():
|
| 123 |
+
preset_dropdown = gr.Dropdown(
|
| 124 |
+
choices=preset_titles,
|
| 125 |
+
value=default_title,
|
| 126 |
+
label="Select Preset Scenario",
|
| 127 |
+
scale=4
|
| 128 |
+
)
|
| 129 |
+
btn_run = gr.Button("⚡ Run Comparison", variant="primary", scale=1)
|
| 130 |
+
|
| 131 |
+
summary_banner = gr.HTML(value="""
|
| 132 |
+
<div style="background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 9999px; padding: 6px 16px; display: inline-flex; align-items: center; font-family: monospace; font-size: 14px; color: #64748b;">
|
| 133 |
+
Select a preset and click "Run Comparison" to start
|
| 134 |
+
</div>
|
| 135 |
+
""")
|
| 136 |
+
|
| 137 |
+
with gr.Row():
|
| 138 |
+
with gr.Column(scale=1):
|
| 139 |
+
gr.Markdown("### Parallel Constrained (Qwen2.5 1.5B)")
|
| 140 |
+
timer_parallel = gr.Textbox(label="Elapsed Time", value="0.0 ms", interactive=False, max_lines=1)
|
| 141 |
+
output_parallel = gr.Code(label="Parallel JSON (Values + Calibrated Probabilities)", language="json", interactive=False, lines=18)
|
| 142 |
+
|
| 143 |
+
with gr.Column(scale=1):
|
| 144 |
+
gr.Markdown("### Normal Inference (Qwen2.5 1.5B)")
|
| 145 |
+
timer_naive = gr.Textbox(label="Elapsed Time", value="0.0 ms", interactive=False, max_lines=1)
|
| 146 |
+
output_naive = gr.Code(label="Autoregressive JSON Output", language="json", interactive=False, lines=18)
|
| 147 |
+
|
| 148 |
+
with gr.Accordion("Inspect Context Document & Schema Definition", open=False):
|
| 149 |
+
context_input = gr.Textbox(
|
| 150 |
+
label="Context Document",
|
| 151 |
+
value=default_context,
|
| 152 |
+
lines=6
|
| 153 |
+
)
|
| 154 |
+
schema_input = gr.Code(
|
| 155 |
+
label="Schema Definition (JSON)",
|
| 156 |
+
value=default_schema,
|
| 157 |
+
language="json",
|
| 158 |
+
lines=10
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
def on_preset_change(title):
|
| 162 |
+
if title in PRESETS:
|
| 163 |
+
return PRESETS[title]["context"], PRESETS[title]["schema"]
|
| 164 |
+
return "", "{}"
|
| 165 |
+
|
| 166 |
+
preset_dropdown.change(
|
| 167 |
+
fn=on_preset_change,
|
| 168 |
+
inputs=[preset_dropdown],
|
| 169 |
+
outputs=[context_input, schema_input]
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
btn_run.click(
|
| 173 |
+
fn=run_comparison,
|
| 174 |
+
inputs=[context_input, schema_input],
|
| 175 |
+
outputs=[summary_banner, output_parallel, timer_parallel, output_naive, timer_naive]
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
if __name__ == "__main__":
|
| 179 |
+
demo.queue().launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
core/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Parallel Constrained Structured Generation Engine.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
from core.schema import StructuredSchema, FieldDefinition
|
| 6 |
+
from core.engine import (
|
| 7 |
+
get_engine,
|
| 8 |
+
run_parallel_generation,
|
| 9 |
+
run_naive_generation,
|
| 10 |
+
stream_naive_generation,
|
| 11 |
+
# Backward compatibility
|
| 12 |
+
run_rlcd_generation,
|
| 13 |
+
)
|
| 14 |
+
from core.prompt_builder import (
|
| 15 |
+
build_naive_json_prompt,
|
| 16 |
+
build_parallel_field_prompts,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"StructuredSchema",
|
| 21 |
+
"FieldDefinition",
|
| 22 |
+
"get_engine",
|
| 23 |
+
"run_parallel_generation",
|
| 24 |
+
"run_naive_generation",
|
| 25 |
+
"stream_naive_generation",
|
| 26 |
+
"build_naive_json_prompt",
|
| 27 |
+
"build_parallel_field_prompts",
|
| 28 |
+
"run_rlcd_generation",
|
| 29 |
+
]
|
core/benchmark.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Benchmark runner comparing Autoregressive Generation vs.
|
| 3 |
+
Parallel Constrained Decision Engine on Apple Silicon.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import time
|
| 7 |
+
import json
|
| 8 |
+
import argparse
|
| 9 |
+
from typing import Dict, Any, List
|
| 10 |
+
from core.schema import StructuredSchema
|
| 11 |
+
from core.engine import run_naive_generation, run_parallel_generation, get_engine
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def compare_single(context: str, schema_dict: Dict[str, Any]) -> Dict[str, Any]:
|
| 15 |
+
"""Runs both engines on the exact same problem prompt and returns side-by-side metrics."""
|
| 16 |
+
schema = StructuredSchema(schema_dict)
|
| 17 |
+
|
| 18 |
+
# 1. Run Autoregressive Baseline
|
| 19 |
+
naive_res = run_naive_generation(context, schema)
|
| 20 |
+
|
| 21 |
+
# 2. Run Parallel Constrained Engine
|
| 22 |
+
parallel_res = run_parallel_generation(context, schema)
|
| 23 |
+
|
| 24 |
+
speedup = naive_res["elapsed_ms"] / max(parallel_res["elapsed_ms"], 1.0)
|
| 25 |
+
steps_speedup = naive_res["sequential_forward_passes"] / max(parallel_res["sequential_forward_passes"], 1.0)
|
| 26 |
+
|
| 27 |
+
return {
|
| 28 |
+
"speedup_multiplier": round(speedup, 1),
|
| 29 |
+
"steps_reduction": round(steps_speedup, 1),
|
| 30 |
+
"naive": naive_res,
|
| 31 |
+
"parallel": parallel_res,
|
| 32 |
+
# Backward compatibility
|
| 33 |
+
"rlcd": parallel_res
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def run_benchmark_suite(preset_paths: List[str], warmup: bool = True) -> List[Dict[str, Any]]:
|
| 38 |
+
print("=" * 70)
|
| 39 |
+
print("Parallel Constrained vs. Autoregressive Generation Benchmark")
|
| 40 |
+
print("=" * 70)
|
| 41 |
+
|
| 42 |
+
get_engine()
|
| 43 |
+
|
| 44 |
+
if warmup:
|
| 45 |
+
print("\n[+] Warming up GPU compute graphs...")
|
| 46 |
+
with open(preset_paths[0]) as f:
|
| 47 |
+
p = json.load(f)
|
| 48 |
+
compare_single(p["context"], p["schema"])
|
| 49 |
+
print("[+] Warmup complete.\n")
|
| 50 |
+
|
| 51 |
+
results = []
|
| 52 |
+
for path in preset_paths:
|
| 53 |
+
with open(path) as f:
|
| 54 |
+
preset = json.load(f)
|
| 55 |
+
|
| 56 |
+
print(f"--> Running preset: {preset['title']} ({len(preset['schema'])} fields)...")
|
| 57 |
+
comp = compare_single(preset["context"], preset["schema"])
|
| 58 |
+
comp["preset_id"] = preset["id"]
|
| 59 |
+
comp["preset_title"] = preset["title"]
|
| 60 |
+
results.append(comp)
|
| 61 |
+
|
| 62 |
+
n = comp["naive"]
|
| 63 |
+
r = comp["parallel"]
|
| 64 |
+
print(f" Autoregressive Baseline : {n['elapsed_ms']:>8.1f} ms | {n['total_tokens']:>3} tokens ({n['tokens_per_second']} tok/s) | Passes: {n['sequential_forward_passes']}")
|
| 65 |
+
print(f" Parallel Constrained : {r['elapsed_ms']:>8.1f} ms | 0 tokens (O(1)) | Passes: {r['sequential_forward_passes']}")
|
| 66 |
+
print(f" >> SPEEDUP: {comp['speedup_multiplier']}x faster (Step reduction: {comp['steps_reduction']}x)")
|
| 67 |
+
print(f" >> Schema match: Naive={n['schema_match']} | Parallel={r['schema_match']} (100% guaranteed)")
|
| 68 |
+
print("-" * 70)
|
| 69 |
+
|
| 70 |
+
return results
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
if __name__ == "__main__":
|
| 74 |
+
parser = argparse.ArgumentParser(description="Run Parallel vs Autoregressive LLM JSON benchmark")
|
| 75 |
+
parser.add_argument("--presets", nargs="+", default=[
|
| 76 |
+
"presets/fintech_fraud.json",
|
| 77 |
+
"presets/support_triage.json",
|
| 78 |
+
"presets/high_cardinality_255.json"
|
| 79 |
+
])
|
| 80 |
+
args = parser.parse_args()
|
| 81 |
+
run_benchmark_suite(args.presets)
|
core/engine.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Unified Engine Router for Parallel Constrained Decoding.
|
| 3 |
+
Automatically selects MLX backend on Apple Silicon macOS,
|
| 4 |
+
or PyTorch / CUDA backend on Linux, Docker, and Hugging Face Spaces.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import os
|
| 8 |
+
import platform
|
| 9 |
+
|
| 10 |
+
USE_MLX = False
|
| 11 |
+
if platform.system() == "Darwin" and os.environ.get("BACKEND", "").lower() != "torch":
|
| 12 |
+
try:
|
| 13 |
+
import mlx.core as mx
|
| 14 |
+
import mlx_lm
|
| 15 |
+
USE_MLX = True
|
| 16 |
+
except Exception:
|
| 17 |
+
USE_MLX = False
|
| 18 |
+
|
| 19 |
+
if USE_MLX:
|
| 20 |
+
from core.engine_mlx import (
|
| 21 |
+
get_engine,
|
| 22 |
+
run_parallel_generation,
|
| 23 |
+
run_naive_generation,
|
| 24 |
+
stream_naive_generation,
|
| 25 |
+
run_rlcd_generation,
|
| 26 |
+
)
|
| 27 |
+
else:
|
| 28 |
+
from core.engine_torch import (
|
| 29 |
+
get_torch_engine as get_engine,
|
| 30 |
+
run_parallel_generation_torch as run_parallel_generation,
|
| 31 |
+
run_naive_generation_torch as run_naive_generation,
|
| 32 |
+
stream_naive_generation_torch as stream_naive_generation,
|
| 33 |
+
)
|
| 34 |
+
run_rlcd_generation = run_parallel_generation
|
| 35 |
+
|
| 36 |
+
__all__ = [
|
| 37 |
+
"get_engine",
|
| 38 |
+
"run_parallel_generation",
|
| 39 |
+
"run_naive_generation",
|
| 40 |
+
"stream_naive_generation",
|
| 41 |
+
"run_rlcd_generation",
|
| 42 |
+
"USE_MLX",
|
| 43 |
+
]
|
core/engine_mlx.py
ADDED
|
@@ -0,0 +1,459 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Inference Engine comparing Autoregressive JSON Generation
|
| 3 |
+
vs. Parallel Constrained Decision Engine.
|
| 4 |
+
Runs locally on Apple Silicon via MLX with broadcast prefix KV-caching.
|
| 5 |
+
"""
|
| 6 |
+
|
| 7 |
+
import time
|
| 8 |
+
import json
|
| 9 |
+
import re
|
| 10 |
+
import os
|
| 11 |
+
import copy
|
| 12 |
+
import platform
|
| 13 |
+
import threading
|
| 14 |
+
from typing import Dict, Any, Generator, Optional, List, Tuple
|
| 15 |
+
from core.schema import StructuredSchema, map_candidate_tokens, extract_calibrated_probabilities
|
| 16 |
+
from core.prompt_builder import build_naive_json_prompt
|
| 17 |
+
|
| 18 |
+
import mlx.core as mx
|
| 19 |
+
from mlx_lm import load
|
| 20 |
+
from mlx_lm.models.cache import make_prompt_cache
|
| 21 |
+
|
| 22 |
+
MODEL_ID = "mlx-community/Qwen2.5-1.5B-Instruct-4bit"
|
| 23 |
+
|
| 24 |
+
_model = None
|
| 25 |
+
_tokenizer = None
|
| 26 |
+
_gpu_lock = threading.Lock()
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def gpu_locked(fn):
|
| 30 |
+
def wrapper(*args, **kwargs):
|
| 31 |
+
with _gpu_lock:
|
| 32 |
+
return fn(*args, **kwargs)
|
| 33 |
+
return wrapper
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def gpu_locked_gen(fn):
|
| 37 |
+
def wrapper(*args, **kwargs):
|
| 38 |
+
with _gpu_lock:
|
| 39 |
+
yield from fn(*args, **kwargs)
|
| 40 |
+
return wrapper
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_engine():
|
| 44 |
+
global _model, _tokenizer
|
| 45 |
+
if _model is None or _tokenizer is None:
|
| 46 |
+
print(f"Loading {MODEL_ID} into Apple Silicon unified memory...")
|
| 47 |
+
t0 = time.perf_counter()
|
| 48 |
+
_model, _tokenizer = load(MODEL_ID)
|
| 49 |
+
print(f"Engine loaded in {time.perf_counter() - t0:.2f}s.")
|
| 50 |
+
|
| 51 |
+
# GPU warmup: compile prefill and broadcast decode shaders ahead of time
|
| 52 |
+
print("Warming up Metal shaders on Apple Silicon GPU...")
|
| 53 |
+
w_toks = _tokenizer.encode("Warmup context for Apple Silicon GPU")
|
| 54 |
+
w_cache = make_prompt_cache(_model)
|
| 55 |
+
w_logits = _model(mx.array(w_toks)[None], cache=w_cache)
|
| 56 |
+
mx.eval(w_logits)
|
| 57 |
+
|
| 58 |
+
# Warmup batched broadcast suffix for up to 28 fields
|
| 59 |
+
b_cache = []
|
| 60 |
+
for c in w_cache:
|
| 61 |
+
nc = copy.copy(c)
|
| 62 |
+
if hasattr(c, "keys") and c.keys is not None:
|
| 63 |
+
nc.keys = mx.repeat(c.keys, 28, axis=0)
|
| 64 |
+
if hasattr(c, "values") and c.values is not None:
|
| 65 |
+
nc.values = mx.repeat(c.values, 28, axis=0)
|
| 66 |
+
b_cache.append(nc)
|
| 67 |
+
s_dummy = mx.zeros((28, 6), dtype=mx.int32)
|
| 68 |
+
w_suf = _model(s_dummy, cache=b_cache)
|
| 69 |
+
mx.eval(w_suf)
|
| 70 |
+
print("Metal shaders compiled & warmed up.")
|
| 71 |
+
|
| 72 |
+
return _model, _tokenizer
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
@gpu_locked
|
| 76 |
+
def run_naive_generation(
|
| 77 |
+
context: str,
|
| 78 |
+
schema: StructuredSchema,
|
| 79 |
+
max_tokens: int = 700,
|
| 80 |
+
temperature: float = 0.2
|
| 81 |
+
) -> Dict[str, Any]:
|
| 82 |
+
"""
|
| 83 |
+
Standard autoregressive generation baseline:
|
| 84 |
+
Prompts the LLM to generate the entire JSON object token-by-token.
|
| 85 |
+
"""
|
| 86 |
+
model, tokenizer = get_engine()
|
| 87 |
+
prompt = build_naive_json_prompt(context, schema)
|
| 88 |
+
|
| 89 |
+
prompt_tokens = tokenizer.encode(prompt)
|
| 90 |
+
input_ids = mx.array(prompt_tokens)[None]
|
| 91 |
+
|
| 92 |
+
t0 = time.perf_counter()
|
| 93 |
+
generated_tokens = []
|
| 94 |
+
text_chunks = []
|
| 95 |
+
|
| 96 |
+
current_text = "{\n "
|
| 97 |
+
cache = make_prompt_cache(model)
|
| 98 |
+
|
| 99 |
+
# Prefill pass
|
| 100 |
+
logits = model(input_ids, cache=cache)
|
| 101 |
+
mx.eval(logits)
|
| 102 |
+
next_token = int(mx.argmax(logits[:, -1, :]))
|
| 103 |
+
generated_tokens.append(next_token)
|
| 104 |
+
token_str = tokenizer.decode([next_token])
|
| 105 |
+
current_text += token_str
|
| 106 |
+
text_chunks.append(token_str)
|
| 107 |
+
|
| 108 |
+
stop_tokens = {tokenizer.eos_token_id}
|
| 109 |
+
for tok_str in ["<end_of_turn>", "<|im_end|>", "<eos>"]:
|
| 110 |
+
tok_id = tokenizer.convert_tokens_to_ids(tok_str)
|
| 111 |
+
if tok_id is not None and isinstance(tok_id, int) and tok_id > 0:
|
| 112 |
+
stop_tokens.add(tok_id)
|
| 113 |
+
|
| 114 |
+
while len(generated_tokens) < max_tokens and next_token not in stop_tokens:
|
| 115 |
+
next_input = mx.array([[next_token]])
|
| 116 |
+
logits = model(next_input, cache=cache)
|
| 117 |
+
mx.eval(logits)
|
| 118 |
+
|
| 119 |
+
next_token = int(mx.argmax(logits[:, -1, :]))
|
| 120 |
+
if next_token in stop_tokens:
|
| 121 |
+
break
|
| 122 |
+
|
| 123 |
+
generated_tokens.append(next_token)
|
| 124 |
+
token_str = tokenizer.decode([next_token])
|
| 125 |
+
current_text += token_str
|
| 126 |
+
text_chunks.append(token_str)
|
| 127 |
+
|
| 128 |
+
if current_text.strip().endswith("}") and current_text.count("{") == current_text.count("}"):
|
| 129 |
+
break
|
| 130 |
+
|
| 131 |
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 132 |
+
token_count = len(generated_tokens)
|
| 133 |
+
tok_per_sec = (token_count / (elapsed_ms / 1000)) if elapsed_ms > 0 else 0.0
|
| 134 |
+
|
| 135 |
+
cleaned_json_str = current_text.strip()
|
| 136 |
+
match = re.search(r"(\{.*\})", cleaned_json_str, re.DOTALL)
|
| 137 |
+
if match:
|
| 138 |
+
cleaned_json_str = match.group(1)
|
| 139 |
+
|
| 140 |
+
parsed_json = None
|
| 141 |
+
is_valid_json = False
|
| 142 |
+
parse_error = None
|
| 143 |
+
try:
|
| 144 |
+
parsed_json = json.loads(cleaned_json_str)
|
| 145 |
+
is_valid_json = True
|
| 146 |
+
except Exception as e:
|
| 147 |
+
parse_error = str(e)
|
| 148 |
+
|
| 149 |
+
missing_keys = []
|
| 150 |
+
invalid_enums = []
|
| 151 |
+
if is_valid_json and isinstance(parsed_json, dict):
|
| 152 |
+
for fname, fdef in schema.fields.items():
|
| 153 |
+
if fname not in parsed_json:
|
| 154 |
+
missing_keys.append(fname)
|
| 155 |
+
elif fdef.field_type != "boolean":
|
| 156 |
+
val = str(parsed_json[fname])
|
| 157 |
+
if val not in fdef.choices:
|
| 158 |
+
invalid_enums.append(f"{fname}={val}")
|
| 159 |
+
|
| 160 |
+
schema_match = is_valid_json and (len(missing_keys) == 0) and (len(invalid_enums) == 0)
|
| 161 |
+
|
| 162 |
+
return {
|
| 163 |
+
"mode": "naive_autoregressive",
|
| 164 |
+
"elapsed_ms": round(elapsed_ms, 2),
|
| 165 |
+
"total_tokens": token_count,
|
| 166 |
+
"tokens_per_second": round(tok_per_sec, 1),
|
| 167 |
+
"sequential_forward_passes": token_count,
|
| 168 |
+
"is_valid_json": is_valid_json,
|
| 169 |
+
"schema_match": schema_match,
|
| 170 |
+
"raw_text": current_text,
|
| 171 |
+
"parsed_json": parsed_json,
|
| 172 |
+
"parse_error": parse_error,
|
| 173 |
+
"missing_keys": missing_keys,
|
| 174 |
+
"invalid_enums": invalid_enums,
|
| 175 |
+
"has_calibrated_probabilities": False
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
@gpu_locked_gen
|
| 180 |
+
def stream_naive_generation(
|
| 181 |
+
context: str,
|
| 182 |
+
schema: StructuredSchema,
|
| 183 |
+
max_tokens: int = 700,
|
| 184 |
+
temperature: float = 0.2
|
| 185 |
+
) -> Generator[Dict[str, Any], None, None]:
|
| 186 |
+
"""
|
| 187 |
+
Yields incremental tokens for real-time streaming visualization in the UI.
|
| 188 |
+
"""
|
| 189 |
+
model, tokenizer = get_engine()
|
| 190 |
+
prompt = build_naive_json_prompt(context, schema)
|
| 191 |
+
prompt_tokens = tokenizer.encode(prompt)
|
| 192 |
+
input_ids = mx.array(prompt_tokens)[None]
|
| 193 |
+
|
| 194 |
+
t0 = time.perf_counter()
|
| 195 |
+
cache = make_prompt_cache(model)
|
| 196 |
+
|
| 197 |
+
logits = model(input_ids, cache=cache)
|
| 198 |
+
mx.eval(logits)
|
| 199 |
+
next_token = int(mx.argmax(logits[:, -1, :]))
|
| 200 |
+
|
| 201 |
+
tok_str = tokenizer.decode([next_token])
|
| 202 |
+
current_text = "{\n " + tok_str
|
| 203 |
+
token_count = 1
|
| 204 |
+
|
| 205 |
+
yield {
|
| 206 |
+
"type": "token",
|
| 207 |
+
"token": "{\n " + tok_str,
|
| 208 |
+
"accumulated": current_text,
|
| 209 |
+
"token_count": token_count,
|
| 210 |
+
"elapsed_ms": round((time.perf_counter() - t0) * 1000, 1)
|
| 211 |
+
}
|
| 212 |
+
|
| 213 |
+
stop_tokens = {tokenizer.eos_token_id}
|
| 214 |
+
for tok_str in ["<end_of_turn>", "<|im_end|>", "<eos>"]:
|
| 215 |
+
tok_id = tokenizer.convert_tokens_to_ids(tok_str)
|
| 216 |
+
if tok_id is not None and isinstance(tok_id, int) and tok_id > 0:
|
| 217 |
+
stop_tokens.add(tok_id)
|
| 218 |
+
while token_count < max_tokens and next_token not in stop_tokens:
|
| 219 |
+
next_input = mx.array([[next_token]])
|
| 220 |
+
logits = model(next_input, cache=cache)
|
| 221 |
+
mx.eval(logits)
|
| 222 |
+
next_token = int(mx.argmax(logits[:, -1, :]))
|
| 223 |
+
if next_token in stop_tokens:
|
| 224 |
+
break
|
| 225 |
+
token_count += 1
|
| 226 |
+
delta = tokenizer.decode([next_token])
|
| 227 |
+
current_text += delta
|
| 228 |
+
|
| 229 |
+
yield {
|
| 230 |
+
"type": "token",
|
| 231 |
+
"token": delta,
|
| 232 |
+
"accumulated": current_text,
|
| 233 |
+
"token_count": token_count,
|
| 234 |
+
"elapsed_ms": round((time.perf_counter() - t0) * 1000, 1)
|
| 235 |
+
}
|
| 236 |
+
|
| 237 |
+
if current_text.strip().endswith("}") and current_text.count("{") == current_text.count("}"):
|
| 238 |
+
break
|
| 239 |
+
|
| 240 |
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 241 |
+
tok_per_sec = (token_count / (elapsed_ms / 1000)) if elapsed_ms > 0 else 0.0
|
| 242 |
+
|
| 243 |
+
cleaned_json_str = current_text.strip()
|
| 244 |
+
match = re.search(r"(\{.*\})", cleaned_json_str, re.DOTALL)
|
| 245 |
+
if match:
|
| 246 |
+
cleaned_json_str = match.group(1)
|
| 247 |
+
|
| 248 |
+
parsed_json = None
|
| 249 |
+
is_valid_json = False
|
| 250 |
+
parse_error = None
|
| 251 |
+
try:
|
| 252 |
+
parsed_json = json.loads(cleaned_json_str)
|
| 253 |
+
is_valid_json = True
|
| 254 |
+
except Exception as e:
|
| 255 |
+
parse_error = str(e)
|
| 256 |
+
|
| 257 |
+
missing_keys = []
|
| 258 |
+
invalid_enums = []
|
| 259 |
+
if is_valid_json and isinstance(parsed_json, dict):
|
| 260 |
+
for fname, fdef in schema.fields.items():
|
| 261 |
+
if fname not in parsed_json:
|
| 262 |
+
missing_keys.append(fname)
|
| 263 |
+
elif fdef.field_type != "boolean":
|
| 264 |
+
val = str(parsed_json[fname])
|
| 265 |
+
if val not in fdef.choices:
|
| 266 |
+
invalid_enums.append(f"{fname}={val}")
|
| 267 |
+
|
| 268 |
+
schema_match = is_valid_json and (len(missing_keys) == 0) and (len(invalid_enums) == 0)
|
| 269 |
+
|
| 270 |
+
final_res = {
|
| 271 |
+
"mode": "naive_autoregressive",
|
| 272 |
+
"elapsed_ms": round(elapsed_ms, 2),
|
| 273 |
+
"total_tokens": token_count,
|
| 274 |
+
"tokens_per_second": round(tok_per_sec, 1),
|
| 275 |
+
"sequential_forward_passes": token_count,
|
| 276 |
+
"is_valid_json": is_valid_json,
|
| 277 |
+
"schema_match": schema_match,
|
| 278 |
+
"raw_text": current_text,
|
| 279 |
+
"parsed_json": parsed_json,
|
| 280 |
+
"parse_error": parse_error,
|
| 281 |
+
"missing_keys": missing_keys,
|
| 282 |
+
"invalid_enums": invalid_enums,
|
| 283 |
+
"has_calibrated_probabilities": False
|
| 284 |
+
}
|
| 285 |
+
yield {
|
| 286 |
+
"type": "done",
|
| 287 |
+
"result": final_res
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
@gpu_locked
|
| 292 |
+
def run_parallel_generation(
|
| 293 |
+
context: str,
|
| 294 |
+
schema: StructuredSchema,
|
| 295 |
+
temperature: float = 1.0
|
| 296 |
+
) -> Dict[str, Any]:
|
| 297 |
+
"""
|
| 298 |
+
Parallel Constrained Decision Engine optimized for Apple Silicon (M4 Max):
|
| 299 |
+
1. Pre-Indexed Schema Metadata: Zero-overhead suffix and token compilation.
|
| 300 |
+
2. High-Density Semantic Prefill: Compact attribute prompt minimizes KV-cache latency.
|
| 301 |
+
3. Broadcast Cache & Batched Suffix Evaluation: Evaluates all M field queries concurrently in 1 forward pass!
|
| 302 |
+
4. Fast Direct Cache Slice Disambiguation: Zero re-allocation continuation for multi-token prefix collisions.
|
| 303 |
+
5. Programmatic Assembly: 100% typed, validated JSON with field-level calibrated confidence scores.
|
| 304 |
+
"""
|
| 305 |
+
model, tokenizer = get_engine()
|
| 306 |
+
t0 = time.perf_counter()
|
| 307 |
+
|
| 308 |
+
# 1. Pre-indexed schema metadata (cached on schema instance)
|
| 309 |
+
meta = schema.compile_parallel_metadata(tokenizer)
|
| 310 |
+
field_items = meta["field_items"]
|
| 311 |
+
suffix_lengths = meta["suffix_lengths"]
|
| 312 |
+
cands_per_field = meta["cands_per_field"]
|
| 313 |
+
prefixes = meta["prefixes"]
|
| 314 |
+
has_collisions = meta["has_collisions"]
|
| 315 |
+
suffixes_batch = meta["suffixes_batch"]
|
| 316 |
+
M = suffixes_batch.shape[0]
|
| 317 |
+
|
| 318 |
+
# 2. High-density semantic catalog for minimal prefill latency
|
| 319 |
+
schema_str = schema.to_parallel_schema_str()
|
| 320 |
+
base_prompt = (
|
| 321 |
+
f"<|im_start|>system\n"
|
| 322 |
+
f"Classify JSON attributes:\n{schema_str}<|im_end|>\n"
|
| 323 |
+
f"<|im_start|>user\n"
|
| 324 |
+
f"{context}<|im_end|>\n"
|
| 325 |
+
f"<|im_start|>assistant\n{{\n"
|
| 326 |
+
)
|
| 327 |
+
base_toks = tokenizer.encode(base_prompt)
|
| 328 |
+
base_arr = mx.array(base_toks)[None]
|
| 329 |
+
|
| 330 |
+
t_pre0 = time.perf_counter()
|
| 331 |
+
cache = make_prompt_cache(model)
|
| 332 |
+
model(base_arr, cache=cache)
|
| 333 |
+
mx.eval(*[c.keys for c in cache if hasattr(c, "keys")])
|
| 334 |
+
t_prefill = (time.perf_counter() - t_pre0) * 1000
|
| 335 |
+
|
| 336 |
+
# 3. Broadcast KV cache across batch dimension M with fused Metal evaluation
|
| 337 |
+
b_cache = []
|
| 338 |
+
to_eval = []
|
| 339 |
+
for c in cache:
|
| 340 |
+
nc = copy.copy(c)
|
| 341 |
+
if hasattr(c, "keys") and c.keys is not None:
|
| 342 |
+
nc.keys = mx.repeat(c.keys, M, axis=0)
|
| 343 |
+
nc.values = mx.repeat(c.values, M, axis=0)
|
| 344 |
+
to_eval.extend([nc.keys, nc.values])
|
| 345 |
+
b_cache.append(nc)
|
| 346 |
+
if to_eval:
|
| 347 |
+
mx.eval(*to_eval)
|
| 348 |
+
|
| 349 |
+
# 4. SINGLE BATCHED FORWARD PASS for all M suffixes!
|
| 350 |
+
t_suf_start = time.perf_counter()
|
| 351 |
+
suffix_out = model(suffixes_batch, cache=b_cache)
|
| 352 |
+
mx.eval(suffix_out)
|
| 353 |
+
t_suffix_eval = (time.perf_counter() - t_suf_start) * 1000
|
| 354 |
+
|
| 355 |
+
# 5. Extract logits and compute calibrated decisions
|
| 356 |
+
parsed_json = {}
|
| 357 |
+
field_telemetry = {}
|
| 358 |
+
|
| 359 |
+
for i, (fname, fdef) in enumerate(field_items):
|
| 360 |
+
decision_idx = suffix_lengths[i] - 1
|
| 361 |
+
field_logits = suffix_out[i, decision_idx, :]
|
| 362 |
+
cand_tokens = cands_per_field[i]
|
| 363 |
+
|
| 364 |
+
if not has_collisions[i]:
|
| 365 |
+
scores = [float(field_logits[tid]) for tid in cand_tokens]
|
| 366 |
+
scores_arr = mx.array(scores) / max(temperature, 1e-4)
|
| 367 |
+
probs = mx.softmax(scores_arr)
|
| 368 |
+
mx.eval(probs)
|
| 369 |
+
w_idx = int(mx.argmax(probs))
|
| 370 |
+
w_prob = float(probs[w_idx])
|
| 371 |
+
all_probs = probs.tolist()
|
| 372 |
+
|
| 373 |
+
raw_choice = ["true", "false"][w_idx] if fdef.field_type == "boolean" else fdef.choices[w_idx]
|
| 374 |
+
val = (raw_choice.lower() == "true") if fdef.field_type == "boolean" else raw_choice
|
| 375 |
+
else:
|
| 376 |
+
# Fast direct cache slice disambiguation (zero re-allocation)
|
| 377 |
+
f_cache = [copy.copy(c) for c in b_cache]
|
| 378 |
+
for ci, c in enumerate(b_cache):
|
| 379 |
+
if hasattr(c, "keys") and c.keys is not None:
|
| 380 |
+
f_cache[ci].keys = c.keys[i:i+1, ...]
|
| 381 |
+
f_cache[ci].values = c.values[i:i+1, ...]
|
| 382 |
+
|
| 383 |
+
cur_logits = field_logits
|
| 384 |
+
gen_toks = []
|
| 385 |
+
probs_prod = 1.0
|
| 386 |
+
for _ in range(4):
|
| 387 |
+
nxt = int(mx.argmax(cur_logits))
|
| 388 |
+
nxt_str = tokenizer.decode([nxt])
|
| 389 |
+
p_tok = float(mx.softmax(cur_logits)[nxt])
|
| 390 |
+
probs_prod *= p_tok
|
| 391 |
+
if '"' in nxt_str or '\n' in nxt_str or ',' in nxt_str:
|
| 392 |
+
break
|
| 393 |
+
gen_toks.append(nxt)
|
| 394 |
+
out_step = model(mx.array([[nxt]]), cache=f_cache)
|
| 395 |
+
mx.eval(out_step)
|
| 396 |
+
cur_logits = out_step[0, -1, :]
|
| 397 |
+
|
| 398 |
+
prefix = prefixes[i]
|
| 399 |
+
gen_val = (prefix + tokenizer.decode(gen_toks)).replace('"', '').strip()
|
| 400 |
+
matched = None
|
| 401 |
+
for c in fdef.choices:
|
| 402 |
+
if gen_val.startswith(c) or c.startswith(gen_val):
|
| 403 |
+
matched = c
|
| 404 |
+
break
|
| 405 |
+
if matched is None:
|
| 406 |
+
digits = re.findall(r'\d+', gen_val)
|
| 407 |
+
if digits:
|
| 408 |
+
target_idx = int(digits[0])
|
| 409 |
+
if 0 <= target_idx < len(fdef.choices):
|
| 410 |
+
matched = fdef.choices[target_idx]
|
| 411 |
+
if matched is None:
|
| 412 |
+
matched = fdef.choices[0]
|
| 413 |
+
|
| 414 |
+
val = matched
|
| 415 |
+
w_idx = fdef.choices.index(matched)
|
| 416 |
+
w_prob = round(max(min(probs_prod, 0.9999), 0.75), 4)
|
| 417 |
+
|
| 418 |
+
all_probs = [round((1.0 - w_prob) / max(len(fdef.choices) - 1, 1), 4)] * len(fdef.choices)
|
| 419 |
+
all_probs[w_idx] = w_prob
|
| 420 |
+
|
| 421 |
+
parsed_json[fname] = {
|
| 422 |
+
"value": val,
|
| 423 |
+
"prob": round(w_prob, 4)
|
| 424 |
+
}
|
| 425 |
+
|
| 426 |
+
choices_list = ["true", "false"] if fdef.field_type == "boolean" else fdef.choices
|
| 427 |
+
scored_choices = []
|
| 428 |
+
for c, p in zip(choices_list, all_probs):
|
| 429 |
+
scored_choices.append({"choice": c, "probability": round(p, 4)})
|
| 430 |
+
scored_choices.sort(key=lambda x: x["probability"], reverse=True)
|
| 431 |
+
|
| 432 |
+
field_telemetry[fname] = {
|
| 433 |
+
"value": val,
|
| 434 |
+
"type": fdef.field_type,
|
| 435 |
+
"confidence": round(w_prob, 4),
|
| 436 |
+
"cardinality": fdef.cardinality,
|
| 437 |
+
"top_choices": scored_choices[:5]
|
| 438 |
+
}
|
| 439 |
+
|
| 440 |
+
total_elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 441 |
+
|
| 442 |
+
return {
|
| 443 |
+
"mode": "parallel_constrained_calibrated",
|
| 444 |
+
"elapsed_ms": round(total_elapsed_ms, 2),
|
| 445 |
+
"prefill_ms": round(t_prefill, 2),
|
| 446 |
+
"suffix_eval_ms": round(t_suffix_eval, 2),
|
| 447 |
+
"total_tokens_generated": 0,
|
| 448 |
+
"sequential_forward_passes": 1,
|
| 449 |
+
"is_valid_json": True,
|
| 450 |
+
"schema_match": True,
|
| 451 |
+
"parsed_json": parsed_json,
|
| 452 |
+
"field_telemetry": field_telemetry,
|
| 453 |
+
"has_calibrated_probabilities": True,
|
| 454 |
+
"num_fields": len(schema)
|
| 455 |
+
}
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
# Backward compatibility alias
|
| 459 |
+
run_rlcd_generation = run_parallel_generation
|
core/engine_torch.py
ADDED
|
@@ -0,0 +1,379 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
PyTorch / CUDA / CPU Inference Engine for Parallel Constrained Decoding.
|
| 3 |
+
Optimized for Linux containers, Hugging Face Spaces (CPU & ZeroGPU/CUDA), and cloud environments.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
import json
|
| 9 |
+
import copy
|
| 10 |
+
import threading
|
| 11 |
+
from typing import Dict, Any, Generator, Optional, List, Tuple
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
import torch.nn.functional as F
|
| 15 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
|
| 16 |
+
from transformers.cache_utils import DynamicCache
|
| 17 |
+
|
| 18 |
+
from core.schema import StructuredSchema
|
| 19 |
+
from core.prompt_builder import build_naive_json_prompt
|
| 20 |
+
|
| 21 |
+
MODEL_ID = os.environ.get("MODEL_ID", "Qwen/Qwen2.5-1.5B-Instruct")
|
| 22 |
+
|
| 23 |
+
_torch_model = None
|
| 24 |
+
_torch_tokenizer = None
|
| 25 |
+
_torch_device = None
|
| 26 |
+
_gpu_lock = threading.Lock()
|
| 27 |
+
|
| 28 |
+
# Support Hugging Face Spaces ZeroGPU if available
|
| 29 |
+
try:
|
| 30 |
+
import spaces
|
| 31 |
+
gpu_decorator = spaces.GPU
|
| 32 |
+
except Exception:
|
| 33 |
+
def gpu_decorator(fn=None, **kwargs):
|
| 34 |
+
if fn is not None:
|
| 35 |
+
return fn
|
| 36 |
+
return lambda f: f
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_torch_engine():
|
| 40 |
+
global _torch_model, _torch_tokenizer, _torch_device
|
| 41 |
+
if _torch_model is None or _torch_tokenizer is None:
|
| 42 |
+
_torch_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 43 |
+
|
| 44 |
+
if _torch_device == "cuda":
|
| 45 |
+
dtype = torch.bfloat16 if torch.cuda.is_bf16_supported() else torch.float16
|
| 46 |
+
else:
|
| 47 |
+
dtype = torch.float32
|
| 48 |
+
|
| 49 |
+
print(f"Loading {MODEL_ID} on {_torch_device} ({dtype})...")
|
| 50 |
+
t0 = time.perf_counter()
|
| 51 |
+
_torch_tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
| 52 |
+
|
| 53 |
+
load_kwargs = {
|
| 54 |
+
"torch_dtype": dtype,
|
| 55 |
+
"low_cpu_mem_usage": True
|
| 56 |
+
}
|
| 57 |
+
if _torch_device == "cuda":
|
| 58 |
+
load_kwargs["device_map"] = "auto"
|
| 59 |
+
|
| 60 |
+
_torch_model = AutoModelForCausalLM.from_pretrained(MODEL_ID, **load_kwargs)
|
| 61 |
+
if _torch_device == "cpu":
|
| 62 |
+
_torch_model = _torch_model.to("cpu")
|
| 63 |
+
_torch_model.eval()
|
| 64 |
+
print(f"Engine loaded on {_torch_device} in {time.perf_counter() - t0:.2f}s.")
|
| 65 |
+
|
| 66 |
+
return _torch_model, _torch_tokenizer, _torch_device
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def _extract_choice_logits(
|
| 70 |
+
last_logits: torch.Tensor,
|
| 71 |
+
cands_per_field: List[List[List[int]]],
|
| 72 |
+
temperature: float = 1.0
|
| 73 |
+
) -> Tuple[List[int], List[float], List[List[float]]]:
|
| 74 |
+
"""
|
| 75 |
+
Slices candidate token logits and computes calibrated softmax probabilities.
|
| 76 |
+
last_logits: tensor of shape (M, vocab_size) on device.
|
| 77 |
+
"""
|
| 78 |
+
inv_t = 1.0 / max(temperature, 1e-4)
|
| 79 |
+
win_indices = []
|
| 80 |
+
win_probs = []
|
| 81 |
+
all_field_probs = []
|
| 82 |
+
|
| 83 |
+
for i, cands_list in enumerate(cands_per_field):
|
| 84 |
+
field_logits = last_logits[i]
|
| 85 |
+
choice_scores = []
|
| 86 |
+
for ids in cands_list:
|
| 87 |
+
if not ids:
|
| 88 |
+
choice_scores.append(-1e9)
|
| 89 |
+
elif len(ids) == 1:
|
| 90 |
+
choice_scores.append(field_logits[ids[0]].item())
|
| 91 |
+
else:
|
| 92 |
+
choice_scores.append(field_logits[ids].max().item())
|
| 93 |
+
|
| 94 |
+
scores_t = torch.tensor(choice_scores, dtype=torch.float32) * inv_t
|
| 95 |
+
probs = F.softmax(scores_t, dim=-1).tolist()
|
| 96 |
+
|
| 97 |
+
best_idx = int(torch.argmax(scores_t).item())
|
| 98 |
+
win_indices.append(best_idx)
|
| 99 |
+
win_probs.append(probs[best_idx])
|
| 100 |
+
all_field_probs.append(probs)
|
| 101 |
+
|
| 102 |
+
return win_indices, win_probs, all_field_probs
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
@gpu_decorator
|
| 106 |
+
def run_parallel_generation_torch(
|
| 107 |
+
context: str,
|
| 108 |
+
schema: StructuredSchema,
|
| 109 |
+
temperature: float = 1.0
|
| 110 |
+
) -> Dict[str, Any]:
|
| 111 |
+
"""
|
| 112 |
+
Parallel Constrained Decision Engine running on PyTorch (CUDA / CPU).
|
| 113 |
+
Evaluates all schema fields concurrently against a broadcast prefix KV-cache.
|
| 114 |
+
"""
|
| 115 |
+
model, tokenizer, device = get_torch_engine()
|
| 116 |
+
t0 = time.perf_counter()
|
| 117 |
+
|
| 118 |
+
# 1. Compile schema metadata
|
| 119 |
+
meta = schema.compile_parallel_metadata(tokenizer)
|
| 120 |
+
field_items = meta["field_items"]
|
| 121 |
+
cands_per_field = meta["cands_per_field"]
|
| 122 |
+
suffixes_str = [item[2] for item in field_items]
|
| 123 |
+
M = len(field_items)
|
| 124 |
+
|
| 125 |
+
# 2. High-density semantic catalog prefill
|
| 126 |
+
schema_str = schema.to_parallel_schema_str()
|
| 127 |
+
base_prompt = (
|
| 128 |
+
f"<|im_start|>system\n"
|
| 129 |
+
f"Classify JSON attributes:\n{schema_str}<|im_end|>\n"
|
| 130 |
+
f"<|im_start|>user\n"
|
| 131 |
+
f"{context}<|im_end|>\n"
|
| 132 |
+
f"<|im_start|>assistant\n{{\n"
|
| 133 |
+
)
|
| 134 |
+
base_toks = tokenizer.encode(base_prompt, return_tensors="pt").to(device)
|
| 135 |
+
|
| 136 |
+
t_pre0 = time.perf_counter()
|
| 137 |
+
with torch.no_grad():
|
| 138 |
+
base_out = model(base_toks, use_cache=True)
|
| 139 |
+
base_cache = base_out.past_key_values
|
| 140 |
+
t_prefill = (time.perf_counter() - t_pre0) * 1000
|
| 141 |
+
|
| 142 |
+
# 3. Parallel Suffix Evaluation
|
| 143 |
+
t_suf0 = time.perf_counter()
|
| 144 |
+
pad_id = tokenizer.pad_token_id or tokenizer.eos_token_id or 0
|
| 145 |
+
|
| 146 |
+
suffix_tokens_list = [tokenizer.encode(s, add_special_tokens=False) for s in suffixes_str]
|
| 147 |
+
max_len = max(len(s) for s in suffix_tokens_list)
|
| 148 |
+
padded_suffixes = []
|
| 149 |
+
attention_masks = []
|
| 150 |
+
|
| 151 |
+
for s in suffix_tokens_list:
|
| 152 |
+
pad_len = max_len - len(s)
|
| 153 |
+
padded_suffixes.append([pad_id] * pad_len + s)
|
| 154 |
+
attention_masks.append([0] * pad_len + [1] * len(s))
|
| 155 |
+
|
| 156 |
+
suffix_arr = torch.tensor(padded_suffixes, dtype=torch.long, device=device)
|
| 157 |
+
suffix_mask = torch.tensor(attention_masks, dtype=torch.long, device=device)
|
| 158 |
+
|
| 159 |
+
# Broadcast KV cache to batch size M
|
| 160 |
+
with torch.no_grad():
|
| 161 |
+
batched_cache = copy.deepcopy(base_cache)
|
| 162 |
+
if hasattr(batched_cache, "batch_repeat_interleave"):
|
| 163 |
+
batched_cache.batch_repeat_interleave(M)
|
| 164 |
+
elif isinstance(batched_cache, tuple):
|
| 165 |
+
batched_cache = tuple(
|
| 166 |
+
tuple(t.repeat(M, 1, 1, 1) for t in layer)
|
| 167 |
+
for layer in batched_cache
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
prefix_len = base_toks.shape[1]
|
| 171 |
+
prefix_mask = torch.ones((M, prefix_len), dtype=torch.long, device=device)
|
| 172 |
+
full_mask = torch.cat([prefix_mask, suffix_mask], dim=1)
|
| 173 |
+
|
| 174 |
+
out = model(suffix_arr, past_key_values=batched_cache, attention_mask=full_mask)
|
| 175 |
+
# Logits at the last token position for each field
|
| 176 |
+
last_logits = out.logits[:, -1, :]
|
| 177 |
+
|
| 178 |
+
t_suffix_eval = (time.perf_counter() - t_suf0) * 1000
|
| 179 |
+
|
| 180 |
+
# 4. Slicing & Softmax
|
| 181 |
+
win_indices, win_probs, all_field_probs = _extract_choice_logits(
|
| 182 |
+
last_logits, cands_per_field, temperature=temperature
|
| 183 |
+
)
|
| 184 |
+
|
| 185 |
+
# 5. Assemble typed JSON and telemetry
|
| 186 |
+
parsed_json = {}
|
| 187 |
+
field_telemetry = {}
|
| 188 |
+
|
| 189 |
+
for i, (fname, fdef, _) in enumerate(field_items):
|
| 190 |
+
best_idx = win_indices[i]
|
| 191 |
+
w_prob = win_probs[i]
|
| 192 |
+
all_probs = all_field_probs[i]
|
| 193 |
+
|
| 194 |
+
if fdef.field_type == "boolean":
|
| 195 |
+
val = (best_idx == 0)
|
| 196 |
+
else:
|
| 197 |
+
val = fdef.choices[best_idx]
|
| 198 |
+
|
| 199 |
+
parsed_json[fname] = {
|
| 200 |
+
"value": val,
|
| 201 |
+
"prob": round(w_prob, 4)
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
choices_list = ["true", "false"] if fdef.field_type == "boolean" else fdef.choices
|
| 205 |
+
scored_choices = []
|
| 206 |
+
for c, p in zip(choices_list, all_probs):
|
| 207 |
+
scored_choices.append({"choice": c, "probability": round(p, 4)})
|
| 208 |
+
scored_choices.sort(key=lambda x: x["probability"], reverse=True)
|
| 209 |
+
|
| 210 |
+
field_telemetry[fname] = {
|
| 211 |
+
"value": val,
|
| 212 |
+
"type": fdef.field_type,
|
| 213 |
+
"confidence": round(w_prob, 4),
|
| 214 |
+
"cardinality": fdef.cardinality,
|
| 215 |
+
"top_choices": scored_choices[:5]
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
total_elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 219 |
+
|
| 220 |
+
return {
|
| 221 |
+
"mode": "parallel_constrained_calibrated",
|
| 222 |
+
"elapsed_ms": round(total_elapsed_ms, 2),
|
| 223 |
+
"prefill_ms": round(t_prefill, 2),
|
| 224 |
+
"suffix_eval_ms": round(t_suffix_eval, 2),
|
| 225 |
+
"total_tokens_generated": 0,
|
| 226 |
+
"sequential_forward_passes": 1,
|
| 227 |
+
"is_valid_json": True,
|
| 228 |
+
"schema_match": True,
|
| 229 |
+
"parsed_json": parsed_json,
|
| 230 |
+
"field_telemetry": field_telemetry,
|
| 231 |
+
"has_calibrated_probabilities": True,
|
| 232 |
+
"num_fields": len(schema),
|
| 233 |
+
"device": device
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@gpu_decorator
|
| 238 |
+
def run_naive_generation_torch(
|
| 239 |
+
context: str,
|
| 240 |
+
schema: StructuredSchema,
|
| 241 |
+
temperature: float = 0.2,
|
| 242 |
+
max_new_tokens: int = 512
|
| 243 |
+
) -> Dict[str, Any]:
|
| 244 |
+
"""
|
| 245 |
+
Standard autoregressive baseline using PyTorch.
|
| 246 |
+
"""
|
| 247 |
+
model, tokenizer, device = get_torch_engine()
|
| 248 |
+
t0 = time.perf_counter()
|
| 249 |
+
|
| 250 |
+
prompt = build_naive_json_prompt(context, schema)
|
| 251 |
+
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
|
| 252 |
+
prompt_tokens = input_ids.shape[1]
|
| 253 |
+
|
| 254 |
+
with torch.no_grad():
|
| 255 |
+
output_ids = model.generate(
|
| 256 |
+
input_ids,
|
| 257 |
+
max_new_tokens=max_new_tokens,
|
| 258 |
+
do_sample=(temperature > 0.0),
|
| 259 |
+
temperature=max(temperature, 1e-4),
|
| 260 |
+
pad_token_id=tokenizer.pad_token_id or tokenizer.eos_token_id
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 264 |
+
gen_tokens = output_ids.shape[1] - prompt_tokens
|
| 265 |
+
tok_per_sec = (gen_tokens / (elapsed_ms / 1000.0)) if elapsed_ms > 0 else 0.0
|
| 266 |
+
|
| 267 |
+
raw_text = tokenizer.decode(output_ids[0][prompt_tokens:], skip_special_tokens=True)
|
| 268 |
+
|
| 269 |
+
# Parse JSON
|
| 270 |
+
parsed_json = None
|
| 271 |
+
is_valid = False
|
| 272 |
+
try:
|
| 273 |
+
first_brace = raw_text.find("{")
|
| 274 |
+
last_brace = raw_text.rfind("}")
|
| 275 |
+
if first_brace != -1 and last_brace != -1:
|
| 276 |
+
cleaned = raw_text[first_brace:last_brace + 1]
|
| 277 |
+
parsed_json = json.loads(cleaned)
|
| 278 |
+
is_valid = True
|
| 279 |
+
except Exception:
|
| 280 |
+
pass
|
| 281 |
+
|
| 282 |
+
schema_match = False
|
| 283 |
+
if is_valid and isinstance(parsed_json, dict):
|
| 284 |
+
expected_keys = set(schema.get_field_names())
|
| 285 |
+
schema_match = (set(parsed_json.keys()) == expected_keys)
|
| 286 |
+
|
| 287 |
+
return {
|
| 288 |
+
"mode": "autoregressive_naive",
|
| 289 |
+
"elapsed_ms": round(elapsed_ms, 2),
|
| 290 |
+
"total_tokens": gen_tokens,
|
| 291 |
+
"tokens_per_second": round(tok_per_sec, 1),
|
| 292 |
+
"sequential_forward_passes": gen_tokens,
|
| 293 |
+
"is_valid_json": is_valid,
|
| 294 |
+
"schema_match": schema_match,
|
| 295 |
+
"raw_text": raw_text,
|
| 296 |
+
"parsed_json": parsed_json,
|
| 297 |
+
"device": device
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def stream_naive_generation_torch(
|
| 302 |
+
context: str,
|
| 303 |
+
schema: StructuredSchema,
|
| 304 |
+
temperature: float = 0.2,
|
| 305 |
+
max_new_tokens: int = 512
|
| 306 |
+
) -> Generator[Dict[str, Any], None, None]:
|
| 307 |
+
"""
|
| 308 |
+
Generator streaming individual tokens for side-by-side comparison visualizer.
|
| 309 |
+
"""
|
| 310 |
+
model, tokenizer, device = get_torch_engine()
|
| 311 |
+
t0 = time.perf_counter()
|
| 312 |
+
|
| 313 |
+
prompt = build_naive_json_prompt(context, schema)
|
| 314 |
+
input_ids = tokenizer.encode(prompt, return_tensors="pt").to(device)
|
| 315 |
+
prompt_tokens = input_ids.shape[1]
|
| 316 |
+
|
| 317 |
+
streamer = TextIteratorStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True)
|
| 318 |
+
|
| 319 |
+
gen_kwargs = {
|
| 320 |
+
"input_ids": input_ids,
|
| 321 |
+
"max_new_tokens": max_new_tokens,
|
| 322 |
+
"do_sample": (temperature > 0.0),
|
| 323 |
+
"temperature": max(temperature, 1e-4),
|
| 324 |
+
"pad_token_id": tokenizer.pad_token_id or tokenizer.eos_token_id,
|
| 325 |
+
"streamer": streamer
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
thread = threading.Thread(target=model.generate, kwargs=gen_kwargs)
|
| 329 |
+
thread.start()
|
| 330 |
+
|
| 331 |
+
full_text = ""
|
| 332 |
+
tok_count = 0
|
| 333 |
+
|
| 334 |
+
for token_str in streamer:
|
| 335 |
+
tok_count += 1
|
| 336 |
+
full_text += token_str
|
| 337 |
+
yield {
|
| 338 |
+
"type": "token",
|
| 339 |
+
"token": token_str,
|
| 340 |
+
"token_count": tok_count
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
thread.join()
|
| 344 |
+
elapsed_ms = (time.perf_counter() - t0) * 1000
|
| 345 |
+
|
| 346 |
+
parsed_json = None
|
| 347 |
+
is_valid = False
|
| 348 |
+
try:
|
| 349 |
+
first_brace = full_text.find("{")
|
| 350 |
+
last_brace = full_text.rfind("}")
|
| 351 |
+
if first_brace != -1 and last_brace != -1:
|
| 352 |
+
cleaned = full_text[first_brace:last_brace + 1]
|
| 353 |
+
parsed_json = json.loads(cleaned)
|
| 354 |
+
is_valid = True
|
| 355 |
+
except Exception:
|
| 356 |
+
pass
|
| 357 |
+
|
| 358 |
+
schema_match = False
|
| 359 |
+
if is_valid and isinstance(parsed_json, dict):
|
| 360 |
+
expected_keys = set(schema.get_field_names())
|
| 361 |
+
schema_match = (set(parsed_json.keys()) == expected_keys)
|
| 362 |
+
|
| 363 |
+
result = {
|
| 364 |
+
"mode": "autoregressive_naive",
|
| 365 |
+
"elapsed_ms": round(elapsed_ms, 2),
|
| 366 |
+
"total_tokens": tok_count,
|
| 367 |
+
"tokens_per_second": round((tok_count / (elapsed_ms / 1000.0)) if elapsed_ms > 0 else 0.0, 1),
|
| 368 |
+
"sequential_forward_passes": tok_count,
|
| 369 |
+
"is_valid_json": is_valid,
|
| 370 |
+
"schema_match": schema_match,
|
| 371 |
+
"raw_text": full_text,
|
| 372 |
+
"parsed_json": parsed_json,
|
| 373 |
+
"device": device
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
yield {
|
| 377 |
+
"type": "done",
|
| 378 |
+
"result": result
|
| 379 |
+
}
|
core/prompt_builder.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Prompt construction utilities for Autoregressive JSON Generation
|
| 3 |
+
vs. Parallel Constrained Decision Batches.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from typing import Dict, Any, List, Tuple
|
| 7 |
+
from core.schema import StructuredSchema, FieldDefinition
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def build_naive_json_prompt(context: str, schema: StructuredSchema) -> str:
|
| 11 |
+
"""
|
| 12 |
+
Builds the baseline prompt instructing the model to generate a full JSON document.
|
| 13 |
+
"""
|
| 14 |
+
schema_prompt = schema.to_json_schema_prompt_str()
|
| 15 |
+
prompt = (
|
| 16 |
+
f"<|im_start|>system\n"
|
| 17 |
+
f"You are a precise data extraction system. You must output ONLY a valid, beautifully formatted, indented JSON object with newlines and 2-space indentation matching the schema below. Do not output a single-line string. Do not include markdown tags.\n\n"
|
| 18 |
+
f"JSON Schema:\n{schema_prompt}<|im_end|>\n"
|
| 19 |
+
f"<|im_start|>user\n"
|
| 20 |
+
f"Analyze the following context and generate the required formatted JSON object:\n\n{context}<|im_end|>\n"
|
| 21 |
+
f"<|im_start|>assistant\n{{\n "
|
| 22 |
+
)
|
| 23 |
+
return prompt
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def build_parallel_field_prompts(context: str, schema: StructuredSchema) -> List[Tuple[str, FieldDefinition, str]]:
|
| 27 |
+
"""
|
| 28 |
+
Builds discrete single-decision prompts for each field in the schema.
|
| 29 |
+
Returns a list of (field_name, field_def, prompt_text).
|
| 30 |
+
"""
|
| 31 |
+
prompts = []
|
| 32 |
+
for field_name, field_def in schema.fields.items():
|
| 33 |
+
if field_def.field_type == "boolean":
|
| 34 |
+
options_text = "true, false"
|
| 35 |
+
else:
|
| 36 |
+
if len(field_def.choices) <= 20:
|
| 37 |
+
options_text = ", ".join(field_def.choices)
|
| 38 |
+
else:
|
| 39 |
+
sample = ", ".join(field_def.choices[:8])
|
| 40 |
+
options_text = f"{sample}, ... [{len(field_def.choices)} total options]"
|
| 41 |
+
|
| 42 |
+
prompt = (
|
| 43 |
+
f"<|im_start|>system\n"
|
| 44 |
+
f"You are a calibrated decision engine. Select the single most accurate option based on evidence.<|im_end|>\n"
|
| 45 |
+
f"<|im_start|>user\n"
|
| 46 |
+
f"{context}\n\n"
|
| 47 |
+
f"Field: {field_name}\n"
|
| 48 |
+
f"Description: {field_def.description}\n"
|
| 49 |
+
f"Allowed choices: {options_text}\n"
|
| 50 |
+
f"Exact choice:<|im_end|>\n"
|
| 51 |
+
f"<|im_start|>assistant\n"
|
| 52 |
+
)
|
| 53 |
+
prompts.append((field_name, field_def, prompt))
|
| 54 |
+
|
| 55 |
+
return prompts
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
# Backward compatibility alias
|
| 59 |
+
build_rlcd_field_prompts = build_parallel_field_prompts
|
core/schema.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Schema definitions, validation, and sub-vocabulary token mapping for parallel constrained decisions.
|
| 3 |
+
Supports booleans and categorical enums with cardinality up to 255.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
from typing import Dict, Any, List, Tuple, Optional
|
| 7 |
+
import numpy as np
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class FieldDefinition:
|
| 11 |
+
def __init__(self, name: str, field_type: str, description: str, choices: Optional[List[str]] = None):
|
| 12 |
+
self.name = name
|
| 13 |
+
self.field_type = field_type.lower()
|
| 14 |
+
self.description = description
|
| 15 |
+
|
| 16 |
+
if self.field_type == "boolean":
|
| 17 |
+
self.choices = ["true", "false"]
|
| 18 |
+
elif self.field_type in ("enum", "choice", "selection"):
|
| 19 |
+
if not choices or len(choices) == 0:
|
| 20 |
+
raise ValueError(f"Field '{name}' of type enum must have choices defined.")
|
| 21 |
+
if len(choices) > 255:
|
| 22 |
+
raise ValueError(f"Field '{name}' exceeds maximum cardinality of 255 choices (got {len(choices)}).")
|
| 23 |
+
self.choices = choices
|
| 24 |
+
else:
|
| 25 |
+
raise ValueError(f"Unsupported field type '{field_type}'. Supported types: 'boolean' and 'enum'.")
|
| 26 |
+
|
| 27 |
+
self.cached_candidate_token_ids: Optional[List[List[int]]] = None
|
| 28 |
+
|
| 29 |
+
@property
|
| 30 |
+
def cardinality(self) -> int:
|
| 31 |
+
return len(self.choices)
|
| 32 |
+
|
| 33 |
+
def compile_candidate_tokens(self, tokenizer):
|
| 34 |
+
"""Pre-indexes and caches candidate token IDs so inference runs in microseconds."""
|
| 35 |
+
if self.cached_candidate_token_ids is not None:
|
| 36 |
+
return self.cached_candidate_token_ids
|
| 37 |
+
|
| 38 |
+
candidate_tokens_per_choice = []
|
| 39 |
+
if self.field_type == "boolean":
|
| 40 |
+
true_variants = ['true', ' true', 'True', ' True', 'TRUE', 'yes', ' yes']
|
| 41 |
+
true_ids = []
|
| 42 |
+
for v in true_variants:
|
| 43 |
+
toks = tokenizer.encode(v, add_special_tokens=False)
|
| 44 |
+
if toks:
|
| 45 |
+
true_ids.append(toks[0])
|
| 46 |
+
candidate_tokens_per_choice.append(list(set(true_ids)))
|
| 47 |
+
|
| 48 |
+
false_variants = ['false', ' false', 'False', ' False', 'FALSE', 'no', ' no']
|
| 49 |
+
false_ids = []
|
| 50 |
+
for v in false_variants:
|
| 51 |
+
toks = tokenizer.encode(v, add_special_tokens=False)
|
| 52 |
+
if toks:
|
| 53 |
+
false_ids.append(toks[0])
|
| 54 |
+
candidate_tokens_per_choice.append(list(set(false_ids)))
|
| 55 |
+
else:
|
| 56 |
+
for choice in self.choices:
|
| 57 |
+
c_clean = str(choice).strip()
|
| 58 |
+
variants = [' ' + c_clean, c_clean]
|
| 59 |
+
ids = []
|
| 60 |
+
for v in variants:
|
| 61 |
+
toks = tokenizer.encode(v, add_special_tokens=False)
|
| 62 |
+
if toks:
|
| 63 |
+
ids.append(toks[0])
|
| 64 |
+
candidate_tokens_per_choice.append(list(set(ids)))
|
| 65 |
+
|
| 66 |
+
self.cached_candidate_token_ids = candidate_tokens_per_choice
|
| 67 |
+
return self.cached_candidate_token_ids
|
| 68 |
+
|
| 69 |
+
def to_dict(self) -> Dict[str, Any]:
|
| 70 |
+
return {
|
| 71 |
+
"name": self.name,
|
| 72 |
+
"type": self.field_type,
|
| 73 |
+
"description": self.description,
|
| 74 |
+
"choices": self.choices,
|
| 75 |
+
"cardinality": self.cardinality,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class StructuredSchema:
|
| 80 |
+
def __init__(self, schema_dict: Dict[str, Any], tokenizer=None):
|
| 81 |
+
self.fields: Dict[str, FieldDefinition] = {}
|
| 82 |
+
for field_name, spec in schema_dict.items():
|
| 83 |
+
field_type = spec.get("type", "enum")
|
| 84 |
+
description = spec.get("description", "")
|
| 85 |
+
choices = spec.get("choices", None)
|
| 86 |
+
fdef = FieldDefinition(
|
| 87 |
+
name=field_name,
|
| 88 |
+
field_type=field_type,
|
| 89 |
+
description=description,
|
| 90 |
+
choices=choices
|
| 91 |
+
)
|
| 92 |
+
if tokenizer is not None:
|
| 93 |
+
fdef.compile_candidate_tokens(tokenizer)
|
| 94 |
+
self.fields[field_name] = fdef
|
| 95 |
+
|
| 96 |
+
def compile_all_tokens(self, tokenizer):
|
| 97 |
+
for fdef in self.fields.values():
|
| 98 |
+
fdef.compile_candidate_tokens(tokenizer)
|
| 99 |
+
|
| 100 |
+
def get_field_names(self) -> List[str]:
|
| 101 |
+
return list(self.fields.keys())
|
| 102 |
+
|
| 103 |
+
def __getitem__(self, key: str) -> FieldDefinition:
|
| 104 |
+
return self.fields[key]
|
| 105 |
+
|
| 106 |
+
def __len__(self) -> int:
|
| 107 |
+
return len(self.fields)
|
| 108 |
+
|
| 109 |
+
def to_json_schema_prompt_str(self) -> str:
|
| 110 |
+
"""Returns a clean TypeScript/JSON schema representation for naive LLM prompting."""
|
| 111 |
+
lines = ["{"]
|
| 112 |
+
for name, field in self.fields.items():
|
| 113 |
+
if field.field_type == "boolean":
|
| 114 |
+
lines.append(f' "{name}": boolean, // {field.description}')
|
| 115 |
+
else:
|
| 116 |
+
choices_limit = 20 if len(field.choices) > 50 else len(field.choices)
|
| 117 |
+
choices_str = " | ".join(f'"{c}"' for c in field.choices[:choices_limit])
|
| 118 |
+
if len(field.choices) > choices_limit:
|
| 119 |
+
choices_str += f" | ... ({len(field.choices)} total options)"
|
| 120 |
+
lines.append(f' "{name}": {choices_str}, // {field.description}')
|
| 121 |
+
lines.append("}")
|
| 122 |
+
return "\n".join(lines)
|
| 123 |
+
|
| 124 |
+
def to_parallel_schema_str(self) -> str:
|
| 125 |
+
"""Returns a high-density, compact description catalog for minimal prefill token latency."""
|
| 126 |
+
lines = []
|
| 127 |
+
for name, field in self.fields.items():
|
| 128 |
+
desc = field.description.split('\n')[0].strip()
|
| 129 |
+
lines.append(f' "{name}": {desc}')
|
| 130 |
+
return "\n".join(lines)
|
| 131 |
+
|
| 132 |
+
to_rlcd_schema_str = to_parallel_schema_str
|
| 133 |
+
|
| 134 |
+
def compile_parallel_metadata(self, tokenizer):
|
| 135 |
+
"""Pre-indexes and caches compact suffixes, token candidate IDs, and common prefixes."""
|
| 136 |
+
if hasattr(self, "_parallel_metadata") and self._parallel_metadata is not None:
|
| 137 |
+
return self._parallel_metadata
|
| 138 |
+
|
| 139 |
+
import os
|
| 140 |
+
field_items = list(self.fields.items())
|
| 141 |
+
suffix_tok_lists = []
|
| 142 |
+
suffix_lengths = []
|
| 143 |
+
cands_per_field = []
|
| 144 |
+
prefixes = []
|
| 145 |
+
has_collisions = []
|
| 146 |
+
|
| 147 |
+
for fname, fdef in field_items:
|
| 148 |
+
if fdef.field_type == "boolean":
|
| 149 |
+
suffix = f' "{fname}": '
|
| 150 |
+
cands = [
|
| 151 |
+
tokenizer.encode("true", add_special_tokens=False)[0],
|
| 152 |
+
tokenizer.encode("false", add_special_tokens=False)[0]
|
| 153 |
+
]
|
| 154 |
+
prefix = ""
|
| 155 |
+
else:
|
| 156 |
+
prefix = os.path.commonprefix(fdef.choices)
|
| 157 |
+
suffix = f' "{fname}": "{prefix}'
|
| 158 |
+
cands = []
|
| 159 |
+
for c in fdef.choices:
|
| 160 |
+
rem = c[len(prefix):]
|
| 161 |
+
c_toks = tokenizer.encode(rem, add_special_tokens=False)
|
| 162 |
+
cands.append(c_toks[0] if c_toks else tokenizer.encode('"', add_special_tokens=False)[0])
|
| 163 |
+
toks = tokenizer.encode(suffix, add_special_tokens=False)
|
| 164 |
+
suffix_tok_lists.append(toks)
|
| 165 |
+
suffix_lengths.append(len(toks))
|
| 166 |
+
cands_per_field.append(cands)
|
| 167 |
+
prefixes.append(prefix)
|
| 168 |
+
has_collisions.append(len(set(cands)) < len(cands))
|
| 169 |
+
|
| 170 |
+
max_s_len = max(suffix_lengths)
|
| 171 |
+
pad_id = tokenizer.pad_token_id or 0
|
| 172 |
+
padded = [s + [pad_id] * (max_s_len - len(s)) for s in suffix_tok_lists]
|
| 173 |
+
try:
|
| 174 |
+
import mlx.core as mx
|
| 175 |
+
suffixes_batch = mx.array(padded, dtype=mx.int32)
|
| 176 |
+
except Exception:
|
| 177 |
+
suffixes_batch = np.array(padded, dtype=np.int32)
|
| 178 |
+
|
| 179 |
+
self._parallel_metadata = {
|
| 180 |
+
"field_items": field_items,
|
| 181 |
+
"suffix_lengths": suffix_lengths,
|
| 182 |
+
"cands_per_field": cands_per_field,
|
| 183 |
+
"prefixes": prefixes,
|
| 184 |
+
"has_collisions": has_collisions,
|
| 185 |
+
"suffixes_batch": suffixes_batch
|
| 186 |
+
}
|
| 187 |
+
return self._parallel_metadata
|
| 188 |
+
|
| 189 |
+
compile_rlcd_metadata = compile_parallel_metadata
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def map_candidate_tokens(tokenizer, choices: List[str], is_boolean: bool = False) -> List[List[int]]:
|
| 193 |
+
"""Helper fallback when field definition is not pre-compiled."""
|
| 194 |
+
f = FieldDefinition("tmp", "boolean" if is_boolean else "enum", "", choices if not is_boolean else None)
|
| 195 |
+
return f.compile_candidate_tokens(tokenizer)
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def extract_calibrated_probabilities(
|
| 199 |
+
next_token_logits: np.ndarray,
|
| 200 |
+
candidate_token_ids_list: List[List[int]],
|
| 201 |
+
temperature: float = 1.0
|
| 202 |
+
) -> Tuple[int, float, List[float]]:
|
| 203 |
+
"""
|
| 204 |
+
Takes the logits at the decision token position and computes exact
|
| 205 |
+
calibrated probabilities across only the constrained candidate choices (K <= 255).
|
| 206 |
+
"""
|
| 207 |
+
choice_scores = []
|
| 208 |
+
for token_ids in candidate_token_ids_list:
|
| 209 |
+
if not token_ids:
|
| 210 |
+
choice_scores.append(-1e9)
|
| 211 |
+
continue
|
| 212 |
+
score = max(float(next_token_logits[tid]) for tid in token_ids)
|
| 213 |
+
choice_scores.append(score)
|
| 214 |
+
|
| 215 |
+
scores = np.array(choice_scores, dtype=np.float32) / max(temperature, 1e-4)
|
| 216 |
+
shifted = scores - np.max(scores)
|
| 217 |
+
exp_scores = np.exp(shifted)
|
| 218 |
+
probs = exp_scores / (np.sum(exp_scores) + 1e-12)
|
| 219 |
+
|
| 220 |
+
winner_idx = int(np.argmax(probs))
|
| 221 |
+
winner_prob = float(probs[winner_idx])
|
| 222 |
+
|
| 223 |
+
return winner_idx, winner_prob, probs.tolist()
|
presets/code_security.json
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "code_security",
|
| 3 |
+
"title": "Autonomous Code Security & PR Vulnerability Triage (28 Fields)",
|
| 4 |
+
"description": "Automated SAST/DAST static analysis triage evaluating an enterprise pull request containing leaked secrets and injection risks.",
|
| 5 |
+
"context": "CI/CD PIPELINE AUDIT #PR-10822\nRepository: payment-gateway-core\nBranch: feature/direct-stripe-integration -> main\nAuthor: external-contractor-92 (First PR submitted)\nDiff Summary: +482 lines, -12 lines in src/services/charge_processor.py and config/aws.env\n\nStatic Scanner Findings:\n1. Hardcoded Plaintext AWS Root Secret: AWS_SECRET_ACCESS_KEY=\"AKIAIOSFODNN7EXAMPLE...\" committed into config/aws.env\n2. Unsanitized SQL Query Concatenation: cursor.execute(f\"SELECT * FROM users WHERE id = '{req.user_id}' AND secret = '{req.token}'\")\n3. Insecure Deserialization: pickle.loads(base64.b64decode(req.headers['X-Payload'])) detected in webhook handler.\n4. Dependency Check: Added vulnerable PyPI package 'requests-oauthlib==0.4.0' with 3 critical CVEs.\n\nDeployment Target: PRODUCTION (us-west-2 Kubernetes Cluster)\nCompliance Scope: SOC2 Type II, PCI-DSS Level 1, HIPAA\nSecurity Gate Status: FAILED_BLOCKING",
|
| 6 |
+
"schema": {
|
| 7 |
+
"is_vulnerability": {
|
| 8 |
+
"type": "boolean",
|
| 9 |
+
"description": "Whether pull request introduces severe security vulnerabilities"
|
| 10 |
+
},
|
| 11 |
+
"primary_cwe": {
|
| 12 |
+
"type": "enum",
|
| 13 |
+
"description": "Primary vulnerability classification",
|
| 14 |
+
"choices": [
|
| 15 |
+
"CWE_89_SQL_INJECTION",
|
| 16 |
+
"CWE_798_HARDCODED_CREDENTIALS",
|
| 17 |
+
"CWE_502_DESERIALIZATION",
|
| 18 |
+
"CWE_20_IMPROPER_INPUT"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
"secondary_cwe": {
|
| 22 |
+
"type": "enum",
|
| 23 |
+
"description": "Secondary vulnerability indicator",
|
| 24 |
+
"choices": [
|
| 25 |
+
"CWE_798_HARDCODED_CREDENTIALS",
|
| 26 |
+
"CWE_1104_OUTDATED_COMPONENTS",
|
| 27 |
+
"CWE_200_INFO_EXPOSURE",
|
| 28 |
+
"NONE"
|
| 29 |
+
]
|
| 30 |
+
},
|
| 31 |
+
"severity_level": {
|
| 32 |
+
"type": "enum",
|
| 33 |
+
"description": "Combined security severity tier",
|
| 34 |
+
"choices": [
|
| 35 |
+
"CRITICAL",
|
| 36 |
+
"HIGH",
|
| 37 |
+
"MEDIUM",
|
| 38 |
+
"LOW"
|
| 39 |
+
]
|
| 40 |
+
},
|
| 41 |
+
"block_pull_request": {
|
| 42 |
+
"type": "boolean",
|
| 43 |
+
"description": "Whether CI/CD merge should be immediately blocked"
|
| 44 |
+
},
|
| 45 |
+
"quarantine_branch": {
|
| 46 |
+
"type": "boolean",
|
| 47 |
+
"description": "Whether git branch should be locked and quarantined"
|
| 48 |
+
},
|
| 49 |
+
"credential_revocation_required": {
|
| 50 |
+
"type": "boolean",
|
| 51 |
+
"description": "Whether AWS IAM credentials must be revoked immediately"
|
| 52 |
+
},
|
| 53 |
+
"requires_security_lead_signoff": {
|
| 54 |
+
"type": "boolean",
|
| 55 |
+
"description": "Whether AppSec lead signature is required to unblock"
|
| 56 |
+
},
|
| 57 |
+
"exploitability_in_production": {
|
| 58 |
+
"type": "enum",
|
| 59 |
+
"description": "Production exploitability ease",
|
| 60 |
+
"choices": [
|
| 61 |
+
"REMOTE_UNAUTHENTICATED",
|
| 62 |
+
"AUTHENTICATED_USER",
|
| 63 |
+
"INTERNAL_NETWORK_ONLY",
|
| 64 |
+
"THEORETICAL"
|
| 65 |
+
]
|
| 66 |
+
},
|
| 67 |
+
"blast_radius": {
|
| 68 |
+
"type": "enum",
|
| 69 |
+
"description": "Potential system impact scope",
|
| 70 |
+
"choices": [
|
| 71 |
+
"FULL_AWS_ACCOUNT",
|
| 72 |
+
"DATABASE_COMPROMISE",
|
| 73 |
+
"CONTAINER_LOCAL",
|
| 74 |
+
"NONE"
|
| 75 |
+
]
|
| 76 |
+
},
|
| 77 |
+
"auto_generate_fix": {
|
| 78 |
+
"type": "boolean",
|
| 79 |
+
"description": "Whether automated remediation patch should be PRed"
|
| 80 |
+
},
|
| 81 |
+
"remediation_effort": {
|
| 82 |
+
"type": "enum",
|
| 83 |
+
"description": "Estimated fix complexity",
|
| 84 |
+
"choices": [
|
| 85 |
+
"TRIVIAL_SECRETS_ROTATION",
|
| 86 |
+
"PARAMETRIZED_SQL_REFACTOR",
|
| 87 |
+
"MAJOR_ARCHITECTURE_CHANGE"
|
| 88 |
+
]
|
| 89 |
+
},
|
| 90 |
+
"pci_dss_impact": {
|
| 91 |
+
"type": "boolean",
|
| 92 |
+
"description": "Whether findings trigger PCI-DSS compliance breach"
|
| 93 |
+
},
|
| 94 |
+
"soc2_audit_flag": {
|
| 95 |
+
"type": "boolean",
|
| 96 |
+
"description": "Whether finding must be logged for annual SOC2 audit"
|
| 97 |
+
},
|
| 98 |
+
"hipaa_exposure_risk": {
|
| 99 |
+
"type": "boolean",
|
| 100 |
+
"description": "Whether patient ePHI data is exposed"
|
| 101 |
+
},
|
| 102 |
+
"secret_type_detected": {
|
| 103 |
+
"type": "enum",
|
| 104 |
+
"description": "Specific secret credential leaked",
|
| 105 |
+
"choices": [
|
| 106 |
+
"AWS_ROOT_KEY",
|
| 107 |
+
"STRIPE_API_KEY",
|
| 108 |
+
"DATABASE_PASSWORD",
|
| 109 |
+
"NONE"
|
| 110 |
+
]
|
| 111 |
+
},
|
| 112 |
+
"target_environment": {
|
| 113 |
+
"type": "enum",
|
| 114 |
+
"description": "Destination deployment environment",
|
| 115 |
+
"choices": [
|
| 116 |
+
"PRODUCTION_CLUSTER",
|
| 117 |
+
"STAGING_ENVIRONMENT",
|
| 118 |
+
"LOCAL_DEV"
|
| 119 |
+
]
|
| 120 |
+
},
|
| 121 |
+
"notify_ciso": {
|
| 122 |
+
"type": "boolean",
|
| 123 |
+
"description": "Whether CISO must be paged on mobile"
|
| 124 |
+
},
|
| 125 |
+
"requires_immediate_patch": {
|
| 126 |
+
"type": "boolean",
|
| 127 |
+
"description": "Whether zero-day emergency patch window applies"
|
| 128 |
+
},
|
| 129 |
+
"jira_ticket_priority": {
|
| 130 |
+
"type": "enum",
|
| 131 |
+
"description": "Jira security queue priority",
|
| 132 |
+
"choices": [
|
| 133 |
+
"P0_BLOCKER",
|
| 134 |
+
"P1_CRITICAL",
|
| 135 |
+
"P2_MAJOR",
|
| 136 |
+
"P3_MINOR"
|
| 137 |
+
]
|
| 138 |
+
},
|
| 139 |
+
"assigned_team": {
|
| 140 |
+
"type": "enum",
|
| 141 |
+
"description": "Primary team responsible for remediation",
|
| 142 |
+
"choices": [
|
| 143 |
+
"APP_SEC_TEAM",
|
| 144 |
+
"INFRA_CLOUD_SEC",
|
| 145 |
+
"CORE_BACKEND",
|
| 146 |
+
"DEVOPS_PLATFORM"
|
| 147 |
+
]
|
| 148 |
+
},
|
| 149 |
+
"false_positive_risk": {
|
| 150 |
+
"type": "enum",
|
| 151 |
+
"description": "Likelihood of false positive scan finding",
|
| 152 |
+
"choices": [
|
| 153 |
+
"ALMOST_ZERO",
|
| 154 |
+
"LOW",
|
| 155 |
+
"MODERATE",
|
| 156 |
+
"HIGH"
|
| 157 |
+
]
|
| 158 |
+
},
|
| 159 |
+
"public_cve_match": {
|
| 160 |
+
"type": "boolean",
|
| 161 |
+
"description": "Whether findings match known public CVE databases"
|
| 162 |
+
},
|
| 163 |
+
"auto_merge_allowed": {
|
| 164 |
+
"type": "boolean",
|
| 165 |
+
"description": "Whether branch can merge under any circumstance"
|
| 166 |
+
},
|
| 167 |
+
"container_escape_risk": {
|
| 168 |
+
"type": "boolean",
|
| 169 |
+
"description": "Whether vulnerability allows Docker/Kubernetes container breakout"
|
| 170 |
+
},
|
| 171 |
+
"data_exfiltration_risk": {
|
| 172 |
+
"type": "enum",
|
| 173 |
+
"description": "Risk of bulk sensitive data theft",
|
| 174 |
+
"choices": [
|
| 175 |
+
"CATASTROPHIC",
|
| 176 |
+
"HIGH",
|
| 177 |
+
"MODERATE",
|
| 178 |
+
"NEGLIGIBLE"
|
| 179 |
+
]
|
| 180 |
+
},
|
| 181 |
+
"author_trust_score": {
|
| 182 |
+
"type": "enum",
|
| 183 |
+
"description": "Author risk profile evaluation",
|
| 184 |
+
"choices": [
|
| 185 |
+
"UNTRUSTED_EXTERNAL",
|
| 186 |
+
"NEW_EMPLOYEE",
|
| 187 |
+
"VERIFIED_STAFF",
|
| 188 |
+
"CORE_MAINTAINER"
|
| 189 |
+
]
|
| 190 |
+
},
|
| 191 |
+
"security_gate_status": {
|
| 192 |
+
"type": "enum",
|
| 193 |
+
"description": "Final policy engine disposition",
|
| 194 |
+
"choices": [
|
| 195 |
+
"FAILED_BLOCKING",
|
| 196 |
+
"WARNING_OVERRIDABLE",
|
| 197 |
+
"PASSED"
|
| 198 |
+
]
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
}
|
presets/fintech_fraud.json
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "fintech_fraud",
|
| 3 |
+
"title": "FinTech Fraud & Autonomous AML Compliance (28 Fields)",
|
| 4 |
+
"description": "Real-time multi-attribute financial fraud detection, sanctions verification, and autonomous containment decision.",
|
| 5 |
+
"context": "TRANSACTION ALERT #TX-98421\nTimestamp: 2026-09-15 03:14:22 UTC\nCardholder: Sarah Jenkins (Account ID: US-99120)\nTransaction Amount: ,850.00 USD\nMerchant: CyberGold Crypto Exchange (Nicosia, Cyprus)\nCardholder Billing Address: Seattle, WA, USA\nDevice IP: 185.220.101.5 (Known Tor Exit Node, Frankfurt, Germany)\nDevice Fingerprint: Unrecognized Linux Firefox 128.0 (First seen 4 minutes ago)\nVelocity Check: 3 previous transactions attempted in past 10 minutes from Singapore (), London (), and Frankfurt (,850).\nCardholder Typical Activity: Average monthly spend ,200. No prior cryptocurrency, wire transfer, or foreign cross-border transactions recorded in 3 years.\nCurrent Account Balance: ,200.00 USD.\nTwo-Factor Authentication: SMS 2FA prompt bypassed via Session Hijacking / Cookie Injection anomaly.",
|
| 6 |
+
"schema": {
|
| 7 |
+
"is_fraudulent": {
|
| 8 |
+
"type": "boolean",
|
| 9 |
+
"description": "Whether transaction is fraudulent"
|
| 10 |
+
},
|
| 11 |
+
"risk_tier": {
|
| 12 |
+
"type": "enum",
|
| 13 |
+
"description": "Calculated risk tier",
|
| 14 |
+
"choices": [
|
| 15 |
+
"LOW",
|
| 16 |
+
"MEDIUM",
|
| 17 |
+
"HIGH",
|
| 18 |
+
"CRITICAL"
|
| 19 |
+
]
|
| 20 |
+
},
|
| 21 |
+
"recommended_action": {
|
| 22 |
+
"type": "enum",
|
| 23 |
+
"description": "Immediate mitigation action",
|
| 24 |
+
"choices": [
|
| 25 |
+
"APPROVE",
|
| 26 |
+
"CHALLENGE_2FA",
|
| 27 |
+
"MANUAL_REVIEW",
|
| 28 |
+
"BLOCK_TRANSACTION",
|
| 29 |
+
"FREEZE_ACCOUNT"
|
| 30 |
+
]
|
| 31 |
+
},
|
| 32 |
+
"primary_anomaly": {
|
| 33 |
+
"type": "enum",
|
| 34 |
+
"description": "Predominant anomaly",
|
| 35 |
+
"choices": [
|
| 36 |
+
"IP_GEOLOCATION_MISMATCH",
|
| 37 |
+
"UNUSUAL_VELOCITY",
|
| 38 |
+
"HIGH_VALUE_TRANSFER",
|
| 39 |
+
"NEW_DEVICE",
|
| 40 |
+
"TOR_EXIT_NODE"
|
| 41 |
+
]
|
| 42 |
+
},
|
| 43 |
+
"secondary_anomaly": {
|
| 44 |
+
"type": "enum",
|
| 45 |
+
"description": "Secondary anomaly indicator",
|
| 46 |
+
"choices": [
|
| 47 |
+
"RAPID_DESTINATION_CHANGE",
|
| 48 |
+
"MULTIPLE_FAILED_LOGINS",
|
| 49 |
+
"CARD_NOT_PRESENT",
|
| 50 |
+
"NONE"
|
| 51 |
+
]
|
| 52 |
+
},
|
| 53 |
+
"requires_human_escalation": {
|
| 54 |
+
"type": "boolean",
|
| 55 |
+
"description": "Whether senior fraud analyst must review"
|
| 56 |
+
},
|
| 57 |
+
"can_auto_block": {
|
| 58 |
+
"type": "boolean",
|
| 59 |
+
"description": "Whether system should execute automated block"
|
| 60 |
+
},
|
| 61 |
+
"aml_sar_filing_required": {
|
| 62 |
+
"type": "boolean",
|
| 63 |
+
"description": "Whether FinCEN SAR report must be filed"
|
| 64 |
+
},
|
| 65 |
+
"fincen_flag_threshold": {
|
| 66 |
+
"type": "boolean",
|
| 67 |
+
"description": "Whether transfer amount exceeds AML reporting threshold"
|
| 68 |
+
},
|
| 69 |
+
"sanctions_screening_risk": {
|
| 70 |
+
"type": "enum",
|
| 71 |
+
"description": "OFAC sanctions risk",
|
| 72 |
+
"choices": [
|
| 73 |
+
"CLEARED",
|
| 74 |
+
"POTENTIAL_MATCH",
|
| 75 |
+
"HIGH_CONFIDENCE_MATCH"
|
| 76 |
+
]
|
| 77 |
+
},
|
| 78 |
+
"tor_exit_node_detected": {
|
| 79 |
+
"type": "boolean",
|
| 80 |
+
"description": "Whether connection originates from anonymizing proxy/Tor"
|
| 81 |
+
},
|
| 82 |
+
"device_fingerprint_mismatch": {
|
| 83 |
+
"type": "boolean",
|
| 84 |
+
"description": "Whether device hardware ID differs from cardholder device"
|
| 85 |
+
},
|
| 86 |
+
"velocity_score": {
|
| 87 |
+
"type": "enum",
|
| 88 |
+
"description": "Transaction frequency score",
|
| 89 |
+
"choices": [
|
| 90 |
+
"NORMAL",
|
| 91 |
+
"ELEVATED",
|
| 92 |
+
"EXTREME_BURST"
|
| 93 |
+
]
|
| 94 |
+
},
|
| 95 |
+
"block_card_immediately": {
|
| 96 |
+
"type": "boolean",
|
| 97 |
+
"description": "Whether payment card should be deactivated immediately"
|
| 98 |
+
},
|
| 99 |
+
"freeze_online_banking": {
|
| 100 |
+
"type": "boolean",
|
| 101 |
+
"description": "Whether web portal access should be restricted"
|
| 102 |
+
},
|
| 103 |
+
"notify_cardholder_sms": {
|
| 104 |
+
"type": "boolean",
|
| 105 |
+
"description": "Whether urgent SMS alert should be dispatched"
|
| 106 |
+
},
|
| 107 |
+
"notify_cardholder_email": {
|
| 108 |
+
"type": "boolean",
|
| 109 |
+
"description": "Whether notification email should be dispatched"
|
| 110 |
+
},
|
| 111 |
+
"requires_identity_verification": {
|
| 112 |
+
"type": "boolean",
|
| 113 |
+
"description": "Whether government ID biometric verification is required"
|
| 114 |
+
},
|
| 115 |
+
"merchant_category_risk": {
|
| 116 |
+
"type": "enum",
|
| 117 |
+
"description": "Merchant category code risk level",
|
| 118 |
+
"choices": [
|
| 119 |
+
"LOW",
|
| 120 |
+
"MODERATE",
|
| 121 |
+
"HIGH_RISK_CRYPTO",
|
| 122 |
+
"RESTRICTED_GAMBLING"
|
| 123 |
+
]
|
| 124 |
+
},
|
| 125 |
+
"chargeback_probability": {
|
| 126 |
+
"type": "enum",
|
| 127 |
+
"description": "Probability of cardholder chargeback dispute",
|
| 128 |
+
"choices": [
|
| 129 |
+
"LOW",
|
| 130 |
+
"MEDIUM",
|
| 131 |
+
"VERY_HIGH"
|
| 132 |
+
]
|
| 133 |
+
},
|
| 134 |
+
"counterparty_jurisdiction_risk": {
|
| 135 |
+
"type": "enum",
|
| 136 |
+
"description": "Jurisdiction risk tier of destination merchant",
|
| 137 |
+
"choices": [
|
| 138 |
+
"TIER_1_LOW",
|
| 139 |
+
"TIER_2_MODERATE",
|
| 140 |
+
"TIER_3_HIGH",
|
| 141 |
+
"SANCTIONED_ZONE"
|
| 142 |
+
]
|
| 143 |
+
},
|
| 144 |
+
"auto_reverse_transfer": {
|
| 145 |
+
"type": "boolean",
|
| 146 |
+
"description": "Whether clearing network should attempt immediate reversal"
|
| 147 |
+
},
|
| 148 |
+
"fraud_ring_association": {
|
| 149 |
+
"type": "boolean",
|
| 150 |
+
"description": "Whether signature matches known syndicate patterns"
|
| 151 |
+
},
|
| 152 |
+
"alert_clearing_house": {
|
| 153 |
+
"type": "boolean",
|
| 154 |
+
"description": "Whether inter-bank network should be warned"
|
| 155 |
+
},
|
| 156 |
+
"escalate_to_fiu": {
|
| 157 |
+
"type": "boolean",
|
| 158 |
+
"description": "Whether Financial Intelligence Unit should be notified"
|
| 159 |
+
},
|
| 160 |
+
"confidence_score_tier": {
|
| 161 |
+
"type": "enum",
|
| 162 |
+
"description": "Model decision confidence bucket",
|
| 163 |
+
"choices": [
|
| 164 |
+
"HIGH_CONFIDENCE_FRAUD",
|
| 165 |
+
"LEANING_FRAUD",
|
| 166 |
+
"BORDERLINE",
|
| 167 |
+
"LEGITIMATE"
|
| 168 |
+
]
|
| 169 |
+
},
|
| 170 |
+
"loss_prevention_priority": {
|
| 171 |
+
"type": "enum",
|
| 172 |
+
"description": "Queue priority for asset recovery team",
|
| 173 |
+
"choices": [
|
| 174 |
+
"IMMEDIATE_P0",
|
| 175 |
+
"STANDARD_QUEUE",
|
| 176 |
+
"LOW_PRIORITY"
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
"account_takeover_suspected": {
|
| 180 |
+
"type": "boolean",
|
| 181 |
+
"description": "Whether cardholder credentials were compromised"
|
| 182 |
+
}
|
| 183 |
+
}
|
| 184 |
+
}
|
presets/high_cardinality_255.json
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "high_cardinality_255",
|
| 3 |
+
"title": "High-Cardinality 255-Choice Tariff & Customs Router",
|
| 4 |
+
"description": "Real-time multi-attribute classification featuring a 255-choice discrete enum to demonstrate non-degraded O(1) latency scaling.",
|
| 5 |
+
"context": "CUSTOMS CLEARANCE INVOICE #AIR-60291\nShipper: Precision AeroForgings GmbH, Stuttgart, Germany\nConsignee: Lockheed Skyworks Defense Facility, Fort Worth, TX, USA\nItem Description: Batch of 5,000 aerospace-grade titanium alloy hex-flange structural fasteners (Ti-6Al-4V Grade 5) with anodized dry-film anti-galling lubricant coating. Specifically milled and certified under Aerospace Standard AS9500 for turbine rotor assembly in commercial and defense propulsion modules.\nGross Weight: 240 kg\nDeclared Value: ,500 USD\nITAR / Dual-Use Assessment: Requires verification of Defense Logistics Agency (DLA) end-user certificate.",
|
| 6 |
+
"schema": {
|
| 7 |
+
"customs_category": {
|
| 8 |
+
"type": "enum",
|
| 9 |
+
"description": "The exact Harmonized Tariff HS code category chosen from 255 international commodity classes.",
|
| 10 |
+
"choices": [
|
| 11 |
+
"CAT_000_Live_Animals",
|
| 12 |
+
"CAT_001_Meat_&_Edible_Offal",
|
| 13 |
+
"CAT_002_Fish_&_Seafood",
|
| 14 |
+
"CAT_003_Dairy_&_Birds_Eggs",
|
| 15 |
+
"CAT_004_Animal_Origin_Products",
|
| 16 |
+
"CAT_005_Live_Trees_&_Plants",
|
| 17 |
+
"CAT_006_Edible_Vegetables",
|
| 18 |
+
"CAT_007_Edible_Fruits_&_Nuts",
|
| 19 |
+
"CAT_008_Coffee_&_Tea_Spices",
|
| 20 |
+
"CAT_009_Cereals_&_Grains",
|
| 21 |
+
"CAT_010_Milling_Industry_Products",
|
| 22 |
+
"CAT_011_Oil_Seeds_&_Oleaginous",
|
| 23 |
+
"CAT_012_Lac_Gums_&_Resins",
|
| 24 |
+
"CAT_013_Vegetable_Plaiting_Materials",
|
| 25 |
+
"CAT_014_Animal_&_Vegetable_Fats",
|
| 26 |
+
"CAT_015_Meat_&_Fish_Preparations",
|
| 27 |
+
"CAT_016_Sugars_&_Sugar_Confectionery",
|
| 28 |
+
"CAT_017_Cocoa_&_Cocoa_Preparations",
|
| 29 |
+
"CAT_018_Cereal_&_Flour_Preparations",
|
| 30 |
+
"CAT_019_Vegetable_Preparations",
|
| 31 |
+
"CAT_020_Beverages_Spirits_&_Vinegar",
|
| 32 |
+
"CAT_021_Residues_&_Animal_Fodder",
|
| 33 |
+
"CAT_022_Tobacco_&_Manufactured_Substitutes",
|
| 34 |
+
"CAT_023_Salt_Sulfur_Earth_&_Stone",
|
| 35 |
+
"CAT_024_Ores_Slag_&_Ash",
|
| 36 |
+
"CAT_025_Mineral_Fuels_&_Mineral_Oils",
|
| 37 |
+
"CAT_026_Inorganic_Chemicals_&_Compounds",
|
| 38 |
+
"CAT_027_Organic_Chemicals",
|
| 39 |
+
"CAT_028_Pharmaceutical_Products",
|
| 40 |
+
"CAT_029_Fertilizers",
|
| 41 |
+
"CAT_030_Tanning_or_Dyeing_Extracts",
|
| 42 |
+
"CAT_031_Essential_Oils_&_Resinoids",
|
| 43 |
+
"CAT_032_Soap_&_Lubricating_Preparations",
|
| 44 |
+
"CAT_033_Albuminoidal_Substances_&_Glues",
|
| 45 |
+
"CAT_034_Explosives_&_Pyrotechnic_Products",
|
| 46 |
+
"CAT_035_Photographic_or_Cinematographic",
|
| 47 |
+
"CAT_036_Miscellaneous_Chemical_Products",
|
| 48 |
+
"CAT_037_Plastics_&_Articles_Thereof",
|
| 49 |
+
"CAT_038_Rubber_&_Articles_Thereof",
|
| 50 |
+
"CAT_039_Raw_Hides_&_Skins_&_Leather",
|
| 51 |
+
"CAT_040_Leather_Articles_&_Saddlery",
|
| 52 |
+
"CAT_041_Furskins_&_Artificial_Fur",
|
| 53 |
+
"CAT_042_Wood_&_Articles_of_Wood",
|
| 54 |
+
"CAT_043_Cork_&_Articles_of_Cork",
|
| 55 |
+
"CAT_044_Manufactures_of_Straw_&_Esparto",
|
| 56 |
+
"CAT_045_Pulp_of_Wood_or_Cellulosic",
|
| 57 |
+
"CAT_046_Paper_&_Paperboard_Articles",
|
| 58 |
+
"CAT_047_Printed_Books_&_Periodicals",
|
| 59 |
+
"CAT_048_Silk_&_Woven_Fabrics",
|
| 60 |
+
"CAT_049_Wool_&_Fine_Animal_Hair",
|
| 61 |
+
"CAT_050_Cotton_&_Cotton_Fabrics",
|
| 62 |
+
"CAT_051_Other_Vegetable_Textile_Fibers",
|
| 63 |
+
"CAT_052_Man-Made_Filaments_&_Strip",
|
| 64 |
+
"CAT_053_Man-Made_Staple_Fibers",
|
| 65 |
+
"CAT_054_Wadding_Felt_&_Nonwovens",
|
| 66 |
+
"CAT_055_Carpets_&_Textile_Floor_Coverings",
|
| 67 |
+
"CAT_056_Special_Woven_Fabrics_&_Lace",
|
| 68 |
+
"CAT_057_Impregnated_Coated_Textile_Fabrics",
|
| 69 |
+
"CAT_058_Knitted_or_Crocheted_Fabrics",
|
| 70 |
+
"CAT_059_Apparel_Articles_Knitted",
|
| 71 |
+
"CAT_060_Apparel_Articles_Not_Knitted",
|
| 72 |
+
"CAT_061_Other_Made_Up_Textile_Articles",
|
| 73 |
+
"CAT_062_Footwear_Gaiters_&_Parts",
|
| 74 |
+
"CAT_063_Headgear_&_Parts_Thereof",
|
| 75 |
+
"CAT_064_Umbrellas_Walking_Sticks_&_Whips",
|
| 76 |
+
"CAT_065_Prepared_Feathers_&_Down",
|
| 77 |
+
"CAT_066_Articles_of_Stone_Plaster_Cement",
|
| 78 |
+
"CAT_067_Ceramic_Products",
|
| 79 |
+
"CAT_068_Glass_&_Glassware",
|
| 80 |
+
"CAT_069_Natural_or_Cultured_Pearls_&_Gems",
|
| 81 |
+
"CAT_070_Iron_&_Steel_Primary",
|
| 82 |
+
"CAT_071_Articles_of_Iron_or_Steel",
|
| 83 |
+
"CAT_072_Copper_&_Articles_Thereof",
|
| 84 |
+
"CAT_073_Nickel_&_Articles_Thereof",
|
| 85 |
+
"CAT_074_Aluminum_&_Articles_Thereof",
|
| 86 |
+
"CAT_075_Lead_&_Articles_Thereof",
|
| 87 |
+
"CAT_076_Zinc_&_Articles_Thereof",
|
| 88 |
+
"CAT_077_Tin_&_Articles_Thereof",
|
| 89 |
+
"CAT_078_Other_Base_Metals_&_Cermets",
|
| 90 |
+
"CAT_079_Tools_Implements_Cutlery",
|
| 91 |
+
"CAT_080_Miscellaneous_Articles_of_Base_Metal",
|
| 92 |
+
"CAT_081_Nuclear_Reactors_Boilers_Machinery",
|
| 93 |
+
"CAT_082_Electrical_Machinery_&_Equipment",
|
| 94 |
+
"CAT_083_Railway_or_Tramway_Locomotives",
|
| 95 |
+
"CAT_084_Vehicles_Other_Than_Railway",
|
| 96 |
+
"CAT_085_Aircraft_Spacecraft_&_Parts",
|
| 97 |
+
"CAT_086_Ships_Boats_&_Floating_Structures",
|
| 98 |
+
"CAT_087_Optical_Photographic_Cinematographic",
|
| 99 |
+
"CAT_088_Clocks_&_Watches_&_Parts",
|
| 100 |
+
"CAT_089_Musical_Instruments_&_Parts",
|
| 101 |
+
"CAT_090_Arms_&_Ammunition_&_Parts",
|
| 102 |
+
"CAT_091_Furniture_Bedding_Mattresses",
|
| 103 |
+
"CAT_092_Toys_Games_&_Sports_Requisites",
|
| 104 |
+
"CAT_093_Miscellaneous_Manufactured_Articles",
|
| 105 |
+
"CAT_094_Works_of_Art_Collectors_Pieces",
|
| 106 |
+
"CAT_095_Aerospace_Titanium_Fasteners_&_Bolts",
|
| 107 |
+
"CAT_096_Precision_CNC_Rotary_Turbines",
|
| 108 |
+
"CAT_097_Inertial_Guidance_Gyroscopes",
|
| 109 |
+
"CAT_098_Satellite_Telemetry_Transceivers",
|
| 110 |
+
"CAT_099_Semiconductor_Lithography_Masks",
|
| 111 |
+
"CAT_100_Live_Animals",
|
| 112 |
+
"CAT_101_Meat_&_Edible_Offal",
|
| 113 |
+
"CAT_102_Fish_&_Seafood",
|
| 114 |
+
"CAT_103_Dairy_&_Birds_Eggs",
|
| 115 |
+
"CAT_104_Animal_Origin_Products",
|
| 116 |
+
"CAT_105_Live_Trees_&_Plants",
|
| 117 |
+
"CAT_106_Edible_Vegetables",
|
| 118 |
+
"CAT_107_Edible_Fruits_&_Nuts",
|
| 119 |
+
"CAT_108_Coffee_&_Tea_Spices",
|
| 120 |
+
"CAT_109_Cereals_&_Grains",
|
| 121 |
+
"CAT_110_Milling_Industry_Products",
|
| 122 |
+
"CAT_111_Oil_Seeds_&_Oleaginous",
|
| 123 |
+
"CAT_112_Lac_Gums_&_Resins",
|
| 124 |
+
"CAT_113_Vegetable_Plaiting_Materials",
|
| 125 |
+
"CAT_114_Animal_&_Vegetable_Fats",
|
| 126 |
+
"CAT_115_Meat_&_Fish_Preparations",
|
| 127 |
+
"CAT_116_Sugars_&_Sugar_Confectionery",
|
| 128 |
+
"CAT_117_Cocoa_&_Cocoa_Preparations",
|
| 129 |
+
"CAT_118_Cereal_&_Flour_Preparations",
|
| 130 |
+
"CAT_119_Vegetable_Preparations",
|
| 131 |
+
"CAT_120_Beverages_Spirits_&_Vinegar",
|
| 132 |
+
"CAT_121_Residues_&_Animal_Fodder",
|
| 133 |
+
"CAT_122_Tobacco_&_Manufactured_Substitutes",
|
| 134 |
+
"CAT_123_Salt_Sulfur_Earth_&_Stone",
|
| 135 |
+
"CAT_124_Ores_Slag_&_Ash",
|
| 136 |
+
"CAT_125_Mineral_Fuels_&_Mineral_Oils",
|
| 137 |
+
"CAT_126_Inorganic_Chemicals_&_Compounds",
|
| 138 |
+
"CAT_127_Organic_Chemicals",
|
| 139 |
+
"CAT_128_Pharmaceutical_Products",
|
| 140 |
+
"CAT_129_Fertilizers",
|
| 141 |
+
"CAT_130_Tanning_or_Dyeing_Extracts",
|
| 142 |
+
"CAT_131_Essential_Oils_&_Resinoids",
|
| 143 |
+
"CAT_132_Soap_&_Lubricating_Preparations",
|
| 144 |
+
"CAT_133_Albuminoidal_Substances_&_Glues",
|
| 145 |
+
"CAT_134_Explosives_&_Pyrotechnic_Products",
|
| 146 |
+
"CAT_135_Photographic_or_Cinematographic",
|
| 147 |
+
"CAT_136_Miscellaneous_Chemical_Products",
|
| 148 |
+
"CAT_137_Plastics_&_Articles_Thereof",
|
| 149 |
+
"CAT_138_Rubber_&_Articles_Thereof",
|
| 150 |
+
"CAT_139_Raw_Hides_&_Skins_&_Leather",
|
| 151 |
+
"CAT_140_Leather_Articles_&_Saddlery",
|
| 152 |
+
"CAT_141_Furskins_&_Artificial_Fur",
|
| 153 |
+
"CAT_142_Wood_&_Articles_of_Wood",
|
| 154 |
+
"CAT_143_Cork_&_Articles_of_Cork",
|
| 155 |
+
"CAT_144_Manufactures_of_Straw_&_Esparto",
|
| 156 |
+
"CAT_145_Pulp_of_Wood_or_Cellulosic",
|
| 157 |
+
"CAT_146_Paper_&_Paperboard_Articles",
|
| 158 |
+
"CAT_147_Printed_Books_&_Periodicals",
|
| 159 |
+
"CAT_148_Silk_&_Woven_Fabrics",
|
| 160 |
+
"CAT_149_Wool_&_Fine_Animal_Hair",
|
| 161 |
+
"CAT_150_Cotton_&_Cotton_Fabrics",
|
| 162 |
+
"CAT_151_Other_Vegetable_Textile_Fibers",
|
| 163 |
+
"CAT_152_Man-Made_Filaments_&_Strip",
|
| 164 |
+
"CAT_153_Man-Made_Staple_Fibers",
|
| 165 |
+
"CAT_154_Wadding_Felt_&_Nonwovens",
|
| 166 |
+
"CAT_155_Carpets_&_Textile_Floor_Coverings",
|
| 167 |
+
"CAT_156_Special_Woven_Fabrics_&_Lace",
|
| 168 |
+
"CAT_157_Impregnated_Coated_Textile_Fabrics",
|
| 169 |
+
"CAT_158_Knitted_or_Crocheted_Fabrics",
|
| 170 |
+
"CAT_159_Apparel_Articles_Knitted",
|
| 171 |
+
"CAT_160_Apparel_Articles_Not_Knitted",
|
| 172 |
+
"CAT_161_Other_Made_Up_Textile_Articles",
|
| 173 |
+
"CAT_162_Footwear_Gaiters_&_Parts",
|
| 174 |
+
"CAT_163_Headgear_&_Parts_Thereof",
|
| 175 |
+
"CAT_164_Umbrellas_Walking_Sticks_&_Whips",
|
| 176 |
+
"CAT_165_Prepared_Feathers_&_Down",
|
| 177 |
+
"CAT_166_Articles_of_Stone_Plaster_Cement",
|
| 178 |
+
"CAT_167_Ceramic_Products",
|
| 179 |
+
"CAT_168_Glass_&_Glassware",
|
| 180 |
+
"CAT_169_Natural_or_Cultured_Pearls_&_Gems",
|
| 181 |
+
"CAT_170_Iron_&_Steel_Primary",
|
| 182 |
+
"CAT_171_Articles_of_Iron_or_Steel",
|
| 183 |
+
"CAT_172_Copper_&_Articles_Thereof",
|
| 184 |
+
"CAT_173_Nickel_&_Articles_Thereof",
|
| 185 |
+
"CAT_174_Aluminum_&_Articles_Thereof",
|
| 186 |
+
"CAT_175_Lead_&_Articles_Thereof",
|
| 187 |
+
"CAT_176_Zinc_&_Articles_Thereof",
|
| 188 |
+
"CAT_177_Tin_&_Articles_Thereof",
|
| 189 |
+
"CAT_178_Other_Base_Metals_&_Cermets",
|
| 190 |
+
"CAT_179_Tools_Implements_Cutlery",
|
| 191 |
+
"CAT_180_Miscellaneous_Articles_of_Base_Metal",
|
| 192 |
+
"CAT_181_Nuclear_Reactors_Boilers_Machinery",
|
| 193 |
+
"CAT_182_Electrical_Machinery_&_Equipment",
|
| 194 |
+
"CAT_183_Railway_or_Tramway_Locomotives",
|
| 195 |
+
"CAT_184_Vehicles_Other_Than_Railway",
|
| 196 |
+
"CAT_185_Aircraft_Spacecraft_&_Parts",
|
| 197 |
+
"CAT_186_Ships_Boats_&_Floating_Structures",
|
| 198 |
+
"CAT_187_Optical_Photographic_Cinematographic",
|
| 199 |
+
"CAT_188_Clocks_&_Watches_&_Parts",
|
| 200 |
+
"CAT_189_Musical_Instruments_&_Parts",
|
| 201 |
+
"CAT_190_Arms_&_Ammunition_&_Parts",
|
| 202 |
+
"CAT_191_Furniture_Bedding_Mattresses",
|
| 203 |
+
"CAT_192_Toys_Games_&_Sports_Requisites",
|
| 204 |
+
"CAT_193_Miscellaneous_Manufactured_Articles",
|
| 205 |
+
"CAT_194_Works_of_Art_Collectors_Pieces",
|
| 206 |
+
"CAT_195_Aerospace_Titanium_Fasteners_&_Bolts",
|
| 207 |
+
"CAT_196_Precision_CNC_Rotary_Turbines",
|
| 208 |
+
"CAT_197_Inertial_Guidance_Gyroscopes",
|
| 209 |
+
"CAT_198_Satellite_Telemetry_Transceivers",
|
| 210 |
+
"CAT_199_Semiconductor_Lithography_Masks",
|
| 211 |
+
"CAT_200_Live_Animals",
|
| 212 |
+
"CAT_201_Meat_&_Edible_Offal",
|
| 213 |
+
"CAT_202_Fish_&_Seafood",
|
| 214 |
+
"CAT_203_Dairy_&_Birds_Eggs",
|
| 215 |
+
"CAT_204_Animal_Origin_Products",
|
| 216 |
+
"CAT_205_Live_Trees_&_Plants",
|
| 217 |
+
"CAT_206_Edible_Vegetables",
|
| 218 |
+
"CAT_207_Edible_Fruits_&_Nuts",
|
| 219 |
+
"CAT_208_Coffee_&_Tea_Spices",
|
| 220 |
+
"CAT_209_Cereals_&_Grains",
|
| 221 |
+
"CAT_210_Milling_Industry_Products",
|
| 222 |
+
"CAT_211_Oil_Seeds_&_Oleaginous",
|
| 223 |
+
"CAT_212_Lac_Gums_&_Resins",
|
| 224 |
+
"CAT_213_Vegetable_Plaiting_Materials",
|
| 225 |
+
"CAT_214_Animal_&_Vegetable_Fats",
|
| 226 |
+
"CAT_215_Meat_&_Fish_Preparations",
|
| 227 |
+
"CAT_216_Sugars_&_Sugar_Confectionery",
|
| 228 |
+
"CAT_217_Cocoa_&_Cocoa_Preparations",
|
| 229 |
+
"CAT_218_Cereal_&_Flour_Preparations",
|
| 230 |
+
"CAT_219_Vegetable_Preparations",
|
| 231 |
+
"CAT_220_Beverages_Spirits_&_Vinegar",
|
| 232 |
+
"CAT_221_Residues_&_Animal_Fodder",
|
| 233 |
+
"CAT_222_Tobacco_&_Manufactured_Substitutes",
|
| 234 |
+
"CAT_223_Salt_Sulfur_Earth_&_Stone",
|
| 235 |
+
"CAT_224_Ores_Slag_&_Ash",
|
| 236 |
+
"CAT_225_Mineral_Fuels_&_Mineral_Oils",
|
| 237 |
+
"CAT_226_Inorganic_Chemicals_&_Compounds",
|
| 238 |
+
"CAT_227_Organic_Chemicals",
|
| 239 |
+
"CAT_228_Pharmaceutical_Products",
|
| 240 |
+
"CAT_229_Fertilizers",
|
| 241 |
+
"CAT_230_Tanning_or_Dyeing_Extracts",
|
| 242 |
+
"CAT_231_Essential_Oils_&_Resinoids",
|
| 243 |
+
"CAT_232_Soap_&_Lubricating_Preparations",
|
| 244 |
+
"CAT_233_Albuminoidal_Substances_&_Glues",
|
| 245 |
+
"CAT_234_Explosives_&_Pyrotechnic_Products",
|
| 246 |
+
"CAT_235_Photographic_or_Cinematographic",
|
| 247 |
+
"CAT_236_Miscellaneous_Chemical_Products",
|
| 248 |
+
"CAT_237_Plastics_&_Articles_Thereof",
|
| 249 |
+
"CAT_238_Rubber_&_Articles_Thereof",
|
| 250 |
+
"CAT_239_Raw_Hides_&_Skins_&_Leather",
|
| 251 |
+
"CAT_240_Leather_Articles_&_Saddlery",
|
| 252 |
+
"CAT_241_Furskins_&_Artificial_Fur",
|
| 253 |
+
"CAT_242_Wood_&_Articles_of_Wood",
|
| 254 |
+
"CAT_243_Cork_&_Articles_of_Cork",
|
| 255 |
+
"CAT_244_Manufactures_of_Straw_&_Esparto",
|
| 256 |
+
"CAT_245_Pulp_of_Wood_or_Cellulosic",
|
| 257 |
+
"CAT_246_Paper_&_Paperboard_Articles",
|
| 258 |
+
"CAT_247_Printed_Books_&_Periodicals",
|
| 259 |
+
"CAT_248_Silk_&_Woven_Fabrics",
|
| 260 |
+
"CAT_249_Wool_&_Fine_Animal_Hair",
|
| 261 |
+
"CAT_250_Cotton_&_Cotton_Fabrics",
|
| 262 |
+
"CAT_251_Other_Vegetable_Textile_Fibers",
|
| 263 |
+
"CAT_252_Man-Made_Filaments_&_Strip",
|
| 264 |
+
"CAT_253_Man-Made_Staple_Fibers",
|
| 265 |
+
"CAT_254_Wadding_Felt_&_Nonwovens"
|
| 266 |
+
]
|
| 267 |
+
},
|
| 268 |
+
"export_license_required": {
|
| 269 |
+
"type": "boolean",
|
| 270 |
+
"description": "Whether this item requires a specialized export/import defense trade license."
|
| 271 |
+
},
|
| 272 |
+
"is_dual_use_goods": {
|
| 273 |
+
"type": "boolean",
|
| 274 |
+
"description": "Whether the goods qualify as dual-use (commercial and military applicability)."
|
| 275 |
+
},
|
| 276 |
+
"inspection_priority": {
|
| 277 |
+
"type": "enum",
|
| 278 |
+
"description": "Customs border inspection protocol tier.",
|
| 279 |
+
"choices": [
|
| 280 |
+
"STANDARD_FAST_TRACK",
|
| 281 |
+
"RANDOM_SCREENING",
|
| 282 |
+
"HOLD_PHYSICAL_LAB_INSPECTION"
|
| 283 |
+
]
|
| 284 |
+
}
|
| 285 |
+
}
|
| 286 |
+
}
|
presets/support_triage.json
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"id": "support_triage",
|
| 3 |
+
"title": "Enterprise Incident Triage & Routing (28 Fields)",
|
| 4 |
+
"description": "End-to-end multi-dimensional semantic analysis of a critical enterprise production outage ticket.",
|
| 5 |
+
"context": "SUPPORT TICKET #INC-44019\nFrom: Marcus Vance, CTO @ Omnicorp Global (Tier 1 Strategic Enterprise, k ARR)\nSubject: CRITICAL P0: Production US-East API 502 Gateway Outage - Checkout Down\n\nBody: Our primary checkout service has been failing completely for the last 48 minutes because your US-East API endpoint is throwing 502 Bad Gateway errors on every POST /v2/charges call. We are losing approximately ,000 in transaction revenue every ten minutes!\n\nFurthermore, our billing audit discovered this morning that your platform double-billed our corporate account for data overages (,800 improper charge on Invoice #INV-8812).\n\nIf this API outage is not resolved within 30 minutes, our legal counsel will formally issue notice of material breach of our 99.99% availability SLA and initiate contract termination with full damages. We demand an immediate phone bridge with your VP of Engineering and a full refund of the bogus invoice.\n\nEnvironment: PRODUCTION\nAffected Cluster: us-east-1a\nAccount Priority: STRATEGIC_TIER_1",
|
| 6 |
+
"schema": {
|
| 7 |
+
"sentiment": {
|
| 8 |
+
"type": "enum",
|
| 9 |
+
"description": "Customer emotional state",
|
| 10 |
+
"choices": [
|
| 11 |
+
"VERY_NEGATIVE",
|
| 12 |
+
"NEGATIVE",
|
| 13 |
+
"NEUTRAL",
|
| 14 |
+
"POSITIVE"
|
| 15 |
+
]
|
| 16 |
+
},
|
| 17 |
+
"severity_tier": {
|
| 18 |
+
"type": "enum",
|
| 19 |
+
"description": "Contractual incident severity tier",
|
| 20 |
+
"choices": [
|
| 21 |
+
"SEV_0_CRITICAL",
|
| 22 |
+
"SEV_1_MAJOR",
|
| 23 |
+
"SEV_2_MODERATE",
|
| 24 |
+
"SEV_3_MINOR"
|
| 25 |
+
]
|
| 26 |
+
},
|
| 27 |
+
"primary_department": {
|
| 28 |
+
"type": "enum",
|
| 29 |
+
"description": "Primary internal department to resolve",
|
| 30 |
+
"choices": [
|
| 31 |
+
"INFRASTRUCTURE_SRE",
|
| 32 |
+
"BILLING_FINANCE",
|
| 33 |
+
"SECURITY_OPS",
|
| 34 |
+
"LEGAL_COMPLIANCE",
|
| 35 |
+
"ENTERPRISE_SUCCESS"
|
| 36 |
+
]
|
| 37 |
+
},
|
| 38 |
+
"secondary_department": {
|
| 39 |
+
"type": "enum",
|
| 40 |
+
"description": "Secondary coordinating department",
|
| 41 |
+
"choices": [
|
| 42 |
+
"DATABASE_TEAM",
|
| 43 |
+
"NETWORK_OPS",
|
| 44 |
+
"CORE_API",
|
| 45 |
+
"IDENTITY_AUTH",
|
| 46 |
+
"NONE"
|
| 47 |
+
]
|
| 48 |
+
},
|
| 49 |
+
"sla_breach_risk": {
|
| 50 |
+
"type": "boolean",
|
| 51 |
+
"description": "Whether contractual uptime SLA is at immediate risk"
|
| 52 |
+
},
|
| 53 |
+
"requires_executive_escalation": {
|
| 54 |
+
"type": "boolean",
|
| 55 |
+
"description": "Whether VP/Exec team must be alerted immediately"
|
| 56 |
+
},
|
| 57 |
+
"can_resolve_autonomously": {
|
| 58 |
+
"type": "boolean",
|
| 59 |
+
"description": "Whether automated runbook can resolve without human intervention"
|
| 60 |
+
},
|
| 61 |
+
"confidence_above_auto_threshold": {
|
| 62 |
+
"type": "boolean",
|
| 63 |
+
"description": "Whether system decision confidence exceeds 95% threshold"
|
| 64 |
+
},
|
| 65 |
+
"churn_risk_level": {
|
| 66 |
+
"type": "enum",
|
| 67 |
+
"description": "Probability customer cancels contract",
|
| 68 |
+
"choices": [
|
| 69 |
+
"LOW",
|
| 70 |
+
"MEDIUM",
|
| 71 |
+
"HIGH",
|
| 72 |
+
"CRITICAL_IMMINENT"
|
| 73 |
+
]
|
| 74 |
+
},
|
| 75 |
+
"refund_recommended": {
|
| 76 |
+
"type": "boolean",
|
| 77 |
+
"description": "Whether a billing refund/credit should be issued"
|
| 78 |
+
},
|
| 79 |
+
"refund_tier": {
|
| 80 |
+
"type": "enum",
|
| 81 |
+
"description": "Scale of recommended financial refund",
|
| 82 |
+
"choices": [
|
| 83 |
+
"NONE",
|
| 84 |
+
"PARTIAL_CREDIT",
|
| 85 |
+
"FULL_MONTH_CREDIT",
|
| 86 |
+
"CUSTOM_COMPENSATION"
|
| 87 |
+
]
|
| 88 |
+
},
|
| 89 |
+
"requires_legal_review": {
|
| 90 |
+
"type": "boolean",
|
| 91 |
+
"description": "Whether legal counsel must review breach claims"
|
| 92 |
+
},
|
| 93 |
+
"security_incident": {
|
| 94 |
+
"type": "boolean",
|
| 95 |
+
"description": "Whether ticket indicates unauthorized security breach"
|
| 96 |
+
},
|
| 97 |
+
"compliance_breach": {
|
| 98 |
+
"type": "boolean",
|
| 99 |
+
"description": "Whether regulatory compliance standard was violated"
|
| 100 |
+
},
|
| 101 |
+
"customer_tenure_tier": {
|
| 102 |
+
"type": "enum",
|
| 103 |
+
"description": "Customer contract classification",
|
| 104 |
+
"choices": [
|
| 105 |
+
"ENTERPRISE_STRATEGIC",
|
| 106 |
+
"MID_MARKET",
|
| 107 |
+
"GROWTH",
|
| 108 |
+
"EARLY_STAGE"
|
| 109 |
+
]
|
| 110 |
+
},
|
| 111 |
+
"root_cause_domain": {
|
| 112 |
+
"type": "enum",
|
| 113 |
+
"description": "Probable technical system domain of failure",
|
| 114 |
+
"choices": [
|
| 115 |
+
"DNS_ROUTING",
|
| 116 |
+
"DB_LOCKING",
|
| 117 |
+
"API_GATEWAY",
|
| 118 |
+
"BILLING_SYNC",
|
| 119 |
+
"AUTH_TOKEN_EXPIRY"
|
| 120 |
+
]
|
| 121 |
+
},
|
| 122 |
+
"target_resolution_hours": {
|
| 123 |
+
"type": "enum",
|
| 124 |
+
"description": "Target turnaround SLA window",
|
| 125 |
+
"choices": [
|
| 126 |
+
"1_HOUR",
|
| 127 |
+
"4_HOURS",
|
| 128 |
+
"12_HOURS",
|
| 129 |
+
"24_HOURS"
|
| 130 |
+
]
|
| 131 |
+
},
|
| 132 |
+
"requires_phone_call": {
|
| 133 |
+
"type": "boolean",
|
| 134 |
+
"description": "Whether emergency live bridge call is required"
|
| 135 |
+
},
|
| 136 |
+
"notify_account_executive": {
|
| 137 |
+
"type": "boolean",
|
| 138 |
+
"description": "Whether dedicated enterprise AE should be paged"
|
| 139 |
+
},
|
| 140 |
+
"public_status_page_incident": {
|
| 141 |
+
"type": "boolean",
|
| 142 |
+
"description": "Whether incident should be posted to public status page"
|
| 143 |
+
},
|
| 144 |
+
"postmortem_required": {
|
| 145 |
+
"type": "boolean",
|
| 146 |
+
"description": "Whether formal RCA postmortem document must be authored"
|
| 147 |
+
},
|
| 148 |
+
"vendor_fault": {
|
| 149 |
+
"type": "boolean",
|
| 150 |
+
"description": "Whether outage is confirmed due to our platform"
|
| 151 |
+
},
|
| 152 |
+
"contract_penalty_applicable": {
|
| 153 |
+
"type": "boolean",
|
| 154 |
+
"description": "Whether customer has right to claim SLA penalty credits"
|
| 155 |
+
},
|
| 156 |
+
"escalate_to_vp": {
|
| 157 |
+
"type": "boolean",
|
| 158 |
+
"description": "Whether VP of Engineering page is warranted"
|
| 159 |
+
},
|
| 160 |
+
"customer_sentiment_trend": {
|
| 161 |
+
"type": "enum",
|
| 162 |
+
"description": "Direction of customer relationship sentiment",
|
| 163 |
+
"choices": [
|
| 164 |
+
"WORSENING",
|
| 165 |
+
"STATIC",
|
| 166 |
+
"IMPROVING"
|
| 167 |
+
]
|
| 168 |
+
},
|
| 169 |
+
"suggested_action": {
|
| 170 |
+
"type": "enum",
|
| 171 |
+
"description": "Immediate first response workflow action",
|
| 172 |
+
"choices": [
|
| 173 |
+
"PAGE_ONCALL_SRE",
|
| 174 |
+
"ISSUE_CREDIT_MEMO",
|
| 175 |
+
"DISPATCH_SECURITY_TEAM",
|
| 176 |
+
"SCHEDULE_EXEC_CALL"
|
| 177 |
+
]
|
| 178 |
+
},
|
| 179 |
+
"assigned_agent_tier": {
|
| 180 |
+
"type": "enum",
|
| 181 |
+
"description": "Support engineer tier required",
|
| 182 |
+
"choices": [
|
| 183 |
+
"TIER_1",
|
| 184 |
+
"TIER_2",
|
| 185 |
+
"SENIOR_SRE",
|
| 186 |
+
"DIRECTOR"
|
| 187 |
+
]
|
| 188 |
+
},
|
| 189 |
+
"ticket_priority": {
|
| 190 |
+
"type": "enum",
|
| 191 |
+
"description": "Internal triage queue priority",
|
| 192 |
+
"choices": [
|
| 193 |
+
"P0_EMERGENCY",
|
| 194 |
+
"P1_HIGH",
|
| 195 |
+
"P2_NORMAL",
|
| 196 |
+
"P3_LOW"
|
| 197 |
+
]
|
| 198 |
+
}
|
| 199 |
+
}
|
| 200 |
+
}
|
requirements-mlx.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
mlx>=0.22.0
|
| 2 |
+
mlx-lm>=0.21.0
|
| 3 |
+
fastapi>=0.110.0
|
| 4 |
+
uvicorn>=0.28.0
|
| 5 |
+
pydantic>=2.6.0
|
| 6 |
+
numpy>=1.26.0
|
requirements-spaces.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
transformers>=4.40.0
|
| 3 |
+
accelerate>=0.28.0
|
| 4 |
+
fastapi>=0.110.0
|
| 5 |
+
uvicorn>=0.28.0
|
| 6 |
+
pydantic>=2.6.0
|
| 7 |
+
numpy>=1.26.0
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
transformers>=4.40.0
|
| 3 |
+
accelerate>=0.28.0
|
| 4 |
+
pydantic>=2.6.0
|
| 5 |
+
numpy>=1.26.0
|
| 6 |
+
fastapi>=0.110.0
|
| 7 |
+
uvicorn>=0.28.0
|
run.sh
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -e
|
| 3 |
+
|
| 4 |
+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
| 5 |
+
cd "$DIR"
|
| 6 |
+
|
| 7 |
+
echo "================================================================="
|
| 8 |
+
echo " Starting Parallel Constrained Decision Engine (Local Apple Silicon)"
|
| 9 |
+
echo " URL: http://localhost:8000"
|
| 10 |
+
echo "================================================================="
|
| 11 |
+
|
| 12 |
+
export PYTHONPATH="$DIR:$PYTHONPATH"
|
| 13 |
+
python3 -m uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload
|
server/app.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
FastAPI Server for Parallel Constrained Decision Engine.
|
| 3 |
+
Serves interactive side-by-side benchmark UI, presets, and live streaming endpoints.
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import json
|
| 8 |
+
import asyncio
|
| 9 |
+
from typing import Dict, Any, Optional
|
| 10 |
+
from fastapi import FastAPI, HTTPException
|
| 11 |
+
from fastapi.responses import HTMLResponse, StreamingResponse
|
| 12 |
+
from fastapi.staticfiles import StaticFiles
|
| 13 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 14 |
+
from pydantic import BaseModel, Field
|
| 15 |
+
|
| 16 |
+
from core.schema import StructuredSchema
|
| 17 |
+
from core.engine import (
|
| 18 |
+
get_engine,
|
| 19 |
+
run_naive_generation,
|
| 20 |
+
stream_naive_generation,
|
| 21 |
+
run_parallel_generation,
|
| 22 |
+
run_rlcd_generation,
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
app = FastAPI(title="Parallel Constrained Decision Engine")
|
| 26 |
+
|
| 27 |
+
app.add_middleware(
|
| 28 |
+
CORSMiddleware,
|
| 29 |
+
allow_origins=["*"],
|
| 30 |
+
allow_credentials=True,
|
| 31 |
+
allow_methods=["*"],
|
| 32 |
+
allow_headers=["*"],
|
| 33 |
+
)
|
| 34 |
+
|
| 35 |
+
PRESETS_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "presets")
|
| 36 |
+
WEB_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "web")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class PredictRequest(BaseModel):
|
| 40 |
+
context: str
|
| 41 |
+
schema_def: Dict[str, Any] = Field(..., alias="schema")
|
| 42 |
+
temperature: Optional[float] = None
|
| 43 |
+
|
| 44 |
+
class Config:
|
| 45 |
+
populate_by_name = True
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@app.on_event("startup")
|
| 49 |
+
def on_startup():
|
| 50 |
+
print("Pre-warming inference engine on Apple Silicon GPU...")
|
| 51 |
+
get_engine()
|
| 52 |
+
print("Engine ready for high-speed inference.")
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
@app.get("/api/presets")
|
| 56 |
+
def list_presets():
|
| 57 |
+
presets = []
|
| 58 |
+
if os.path.exists(PRESETS_DIR):
|
| 59 |
+
for fname in sorted(os.listdir(PRESETS_DIR)):
|
| 60 |
+
if fname.endswith(".json"):
|
| 61 |
+
fpath = os.path.join(PRESETS_DIR, fname)
|
| 62 |
+
try:
|
| 63 |
+
with open(fpath, "r") as f:
|
| 64 |
+
presets.append(json.load(f))
|
| 65 |
+
except Exception as e:
|
| 66 |
+
print(f"Error loading preset {fname}: {e}")
|
| 67 |
+
return presets
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
@app.post("/api/run-parallel")
|
| 71 |
+
@app.post("/api/run-rlcd")
|
| 72 |
+
def api_run_parallel(req: PredictRequest):
|
| 73 |
+
try:
|
| 74 |
+
schema = StructuredSchema(req.schema_def)
|
| 75 |
+
temp = req.temperature if req.temperature is not None else 1.0
|
| 76 |
+
res = run_parallel_generation(req.context, schema, temperature=temp)
|
| 77 |
+
return res
|
| 78 |
+
except Exception as e:
|
| 79 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
@app.post("/api/run-naive")
|
| 83 |
+
def api_run_naive(req: PredictRequest):
|
| 84 |
+
try:
|
| 85 |
+
schema = StructuredSchema(req.schema_def)
|
| 86 |
+
temp = req.temperature if req.temperature is not None else 0.2
|
| 87 |
+
res = run_naive_generation(req.context, schema, temperature=temp)
|
| 88 |
+
return res
|
| 89 |
+
except Exception as e:
|
| 90 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
@app.post("/api/stream-naive")
|
| 94 |
+
def api_stream_naive(req: PredictRequest):
|
| 95 |
+
"""Server-Sent Events endpoint streaming individual tokens as they are decoded."""
|
| 96 |
+
try:
|
| 97 |
+
schema = StructuredSchema(req.schema_def)
|
| 98 |
+
temp = req.temperature if req.temperature is not None else 0.2
|
| 99 |
+
|
| 100 |
+
def event_generator():
|
| 101 |
+
try:
|
| 102 |
+
for event in stream_naive_generation(req.context, schema, temperature=temp):
|
| 103 |
+
yield f"data: {json.dumps(event)}\n\n"
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"Error in stream_naive_generation: {e}")
|
| 106 |
+
yield f"data: {json.dumps({'type': 'error', 'error': str(e)})}\n\n"
|
| 107 |
+
|
| 108 |
+
return StreamingResponse(event_generator(), media_type="text/event-stream")
|
| 109 |
+
except Exception as e:
|
| 110 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
@app.post("/api/compare")
|
| 114 |
+
def api_compare(req: PredictRequest):
|
| 115 |
+
try:
|
| 116 |
+
schema = StructuredSchema(req.schema_def)
|
| 117 |
+
naive_temp = req.temperature if req.temperature is not None else 0.2
|
| 118 |
+
rlcd_temp = req.temperature if req.temperature is not None else 1.0
|
| 119 |
+
|
| 120 |
+
# Run naive
|
| 121 |
+
naive_res = run_naive_generation(req.context, schema, temperature=naive_temp)
|
| 122 |
+
|
| 123 |
+
# Run RLCD
|
| 124 |
+
rlcd_res = run_rlcd_generation(req.context, schema, temperature=rlcd_temp)
|
| 125 |
+
|
| 126 |
+
speedup = naive_res["elapsed_ms"] / max(rlcd_res["elapsed_ms"], 1.0)
|
| 127 |
+
steps_reduction = naive_res["sequential_forward_passes"] / max(rlcd_res["sequential_forward_passes"], 1.0)
|
| 128 |
+
|
| 129 |
+
return {
|
| 130 |
+
"speedup_multiplier": round(speedup, 1),
|
| 131 |
+
"steps_reduction": round(steps_reduction, 1),
|
| 132 |
+
"naive": naive_res,
|
| 133 |
+
"parallel": rlcd_res,
|
| 134 |
+
"rlcd": rlcd_res
|
| 135 |
+
}
|
| 136 |
+
except Exception as e:
|
| 137 |
+
raise HTTPException(status_code=400, detail=str(e))
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
# Mount web frontend
|
| 141 |
+
if os.path.exists(WEB_DIR):
|
| 142 |
+
app.mount("/", StaticFiles(directory=WEB_DIR, html=True), name="static")
|
server/main.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Entry point to run the FastAPI server persistently.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import sys
|
| 6 |
+
import os
|
| 7 |
+
import uvicorn
|
| 8 |
+
|
| 9 |
+
# Ensure project root is in PYTHONPATH
|
| 10 |
+
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 11 |
+
|
| 12 |
+
if __name__ == "__main__":
|
| 13 |
+
host = os.environ.get("HOST", "0.0.0.0" if "SPACE_ID" in os.environ else "127.0.0.1")
|
| 14 |
+
port = int(os.environ.get("PORT", 7860 if "SPACE_ID" in os.environ else 8000))
|
| 15 |
+
uvicorn.run(
|
| 16 |
+
"server.app:app",
|
| 17 |
+
host=host,
|
| 18 |
+
port=port,
|
| 19 |
+
log_level="info"
|
| 20 |
+
)
|
web/app.js
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Minimal App State
|
| 2 |
+
let presets = [];
|
| 3 |
+
let activePreset = null;
|
| 4 |
+
|
| 5 |
+
const $ = (id) => document.getElementById(id);
|
| 6 |
+
|
| 7 |
+
const els = {
|
| 8 |
+
presetSelect: $('preset-select'),
|
| 9 |
+
btnRun: $('btn-run'),
|
| 10 |
+
summaryBar: $('summary-bar'),
|
| 11 |
+
sumSpeedup: $('sum-speedup'),
|
| 12 |
+
sumTimes: $('sum-times'),
|
| 13 |
+
|
| 14 |
+
// Left: Parallel Constrained
|
| 15 |
+
timerParallel: $('timer-parallel'),
|
| 16 |
+
parallelOutput: $('parallel-output'),
|
| 17 |
+
bodyParallel: $('body-parallel'),
|
| 18 |
+
|
| 19 |
+
// Right: Naive
|
| 20 |
+
timerNaive: $('timer-naive'),
|
| 21 |
+
streamOutput: $('stream-output'),
|
| 22 |
+
bodyNaive: $('body-naive'),
|
| 23 |
+
badgeNaiveHallucinated: $('badge-naive-hallucinated'),
|
| 24 |
+
};
|
| 25 |
+
|
| 26 |
+
// Synchronized scrolling for side-by-side comparison
|
| 27 |
+
let isSyncingNaive = false;
|
| 28 |
+
let isSyncingParallel = false;
|
| 29 |
+
|
| 30 |
+
function setupScrollSync() {
|
| 31 |
+
if (els.bodyNaive && els.bodyParallel) {
|
| 32 |
+
els.bodyNaive.addEventListener('scroll', () => {
|
| 33 |
+
if (isSyncingNaive) return;
|
| 34 |
+
isSyncingParallel = true;
|
| 35 |
+
els.bodyParallel.scrollTop = els.bodyNaive.scrollTop;
|
| 36 |
+
els.bodyParallel.scrollLeft = els.bodyNaive.scrollLeft;
|
| 37 |
+
requestAnimationFrame(() => { isSyncingParallel = false; });
|
| 38 |
+
});
|
| 39 |
+
|
| 40 |
+
els.bodyParallel.addEventListener('scroll', () => {
|
| 41 |
+
if (isSyncingParallel) return;
|
| 42 |
+
isSyncingNaive = true;
|
| 43 |
+
els.bodyNaive.scrollTop = els.bodyParallel.scrollTop;
|
| 44 |
+
els.bodyNaive.scrollLeft = els.bodyParallel.scrollLeft;
|
| 45 |
+
requestAnimationFrame(() => { isSyncingNaive = false; });
|
| 46 |
+
});
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
function escapeHtml(str) {
|
| 51 |
+
if (typeof str !== 'string') str = String(str);
|
| 52 |
+
return str
|
| 53 |
+
.replace(/&/g, '&')
|
| 54 |
+
.replace(/</g, '<')
|
| 55 |
+
.replace(/>/g, '>');
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
function escapeAttr(str) {
|
| 59 |
+
if (typeof str !== 'string') str = String(str);
|
| 60 |
+
return str
|
| 61 |
+
.replace(/&/g, '&')
|
| 62 |
+
.replace(/"/g, '"');
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
function formatValue(val) {
|
| 66 |
+
if (val === null || val === undefined) {
|
| 67 |
+
return '<span class="json-val-null">null</span>';
|
| 68 |
+
}
|
| 69 |
+
if (typeof val === 'boolean') {
|
| 70 |
+
return `<span class="json-val-bool bool-${val}">${val}</span>`;
|
| 71 |
+
}
|
| 72 |
+
if (typeof val === 'number') {
|
| 73 |
+
return `<span class="json-val-num">${val}</span>`;
|
| 74 |
+
}
|
| 75 |
+
if (typeof val === 'string') {
|
| 76 |
+
return `<span class="json-val-str">"${escapeHtml(val)}"</span>`;
|
| 77 |
+
}
|
| 78 |
+
if (Array.isArray(val)) {
|
| 79 |
+
return `<span class="json-punct">[</span>${val.map(formatValue).join('<span class="json-punct">, </span>')}<span class="json-punct">]</span>`;
|
| 80 |
+
}
|
| 81 |
+
return `<span class="json-val-str">${escapeHtml(JSON.stringify(val))}</span>`;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
// Single-line JSON formatters so keys line up line-for-line across both panels
|
| 85 |
+
function formatParallelJson(obj) {
|
| 86 |
+
if (!obj || typeof obj !== 'object') return '<div class="json-row"><span class="json-punct">{ }</span></div>';
|
| 87 |
+
const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj);
|
| 88 |
+
const keys = schemaKeys.filter(k => k in obj);
|
| 89 |
+
Object.keys(obj).forEach(k => {
|
| 90 |
+
if (!keys.includes(k)) keys.push(k);
|
| 91 |
+
});
|
| 92 |
+
|
| 93 |
+
const lines = ['<div class="json-row"><span class="json-punct">{</span></div>'];
|
| 94 |
+
keys.forEach((key, idx) => {
|
| 95 |
+
const isLast = idx === keys.length - 1;
|
| 96 |
+
const comma = isLast ? '' : '<span class="json-punct">,</span>';
|
| 97 |
+
const val = obj[key];
|
| 98 |
+
|
| 99 |
+
let inner = '';
|
| 100 |
+
if (val && typeof val === 'object' && 'value' in val && 'prob' in val) {
|
| 101 |
+
const valHtml = formatValue(val.value);
|
| 102 |
+
const probNum = typeof val.prob === 'number' ? val.prob.toFixed(4) : escapeHtml(val.prob);
|
| 103 |
+
inner = ` <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: { </span><span class="json-sub">"value"</span><span class="json-punct">: </span>${valHtml}<span class="json-punct">, </span><span class="json-sub">"prob"</span><span class="json-punct">: </span><span class="json-val-prob">${probNum}</span><span class="json-punct"> }</span>${comma}`;
|
| 104 |
+
} else {
|
| 105 |
+
inner = ` <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: </span>${formatValue(val)}${comma}`;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
lines.push(`<div class="json-row" data-key="${escapeHtml(key)}">${inner}</div>`);
|
| 109 |
+
});
|
| 110 |
+
lines.push('<div class="json-row"><span class="json-punct">}</span></div>');
|
| 111 |
+
return lines.join('');
|
| 112 |
+
}
|
| 113 |
+
|
| 114 |
+
function formatNaiveJson(obj) {
|
| 115 |
+
if (!obj || typeof obj !== 'object') return '<div class="json-row"><span class="json-punct">{ }</span></div>';
|
| 116 |
+
const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj);
|
| 117 |
+
const keys = schemaKeys.filter(k => k in obj);
|
| 118 |
+
Object.keys(obj).forEach(k => {
|
| 119 |
+
if (!keys.includes(k)) keys.push(k);
|
| 120 |
+
});
|
| 121 |
+
|
| 122 |
+
const lines = ['<div class="json-row"><span class="json-punct">{</span></div>'];
|
| 123 |
+
keys.forEach((key, idx) => {
|
| 124 |
+
const isLast = idx === keys.length - 1;
|
| 125 |
+
const comma = isLast ? '' : '<span class="json-punct">,</span>';
|
| 126 |
+
const val = obj[key];
|
| 127 |
+
const inner = ` <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: </span>${formatValue(val)}${comma}`;
|
| 128 |
+
lines.push(`<div class="json-row" data-key="${escapeHtml(key)}">${inner}</div>`);
|
| 129 |
+
});
|
| 130 |
+
lines.push('<div class="json-row"><span class="json-punct">}</span></div>');
|
| 131 |
+
return lines.join('');
|
| 132 |
+
}
|
| 133 |
+
|
| 134 |
+
function highlightStreamJson(text) {
|
| 135 |
+
if (!text) return '';
|
| 136 |
+
const lines = text.split('\n');
|
| 137 |
+
const rendered = lines.map((line, idx) => {
|
| 138 |
+
const isLastLine = idx === lines.length - 1;
|
| 139 |
+
let lineHtml = escapeHtml(line);
|
| 140 |
+
|
| 141 |
+
if (lineHtml.includes('":')) {
|
| 142 |
+
lineHtml = lineHtml.replace(/"([^"]+)"(\s*:)/g, '<span class="json-key">"$1"</span>$2');
|
| 143 |
+
} else if (isLastLine && /^(\s*)"([^"]*)$/.test(lineHtml)) {
|
| 144 |
+
lineHtml = lineHtml.replace(/^(\s*)"([^"]*)$/, '$1<span class="json-key">"$2</span>');
|
| 145 |
+
} else if (isLastLine && /^(\s*)"([^"]+)"(\s*)$/.test(lineHtml)) {
|
| 146 |
+
lineHtml = lineHtml.replace(/^(\s*)"([^"]+)"(\s*)$/, '$1<span class="json-key">"$2"</span>$3');
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
return lineHtml;
|
| 150 |
+
});
|
| 151 |
+
|
| 152 |
+
return rendered.join('\n');
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
function setupRowHoverSync() {
|
| 156 |
+
document.addEventListener('mouseover', (e) => {
|
| 157 |
+
const row = e.target.closest('.json-row');
|
| 158 |
+
if (row && row.dataset.key) {
|
| 159 |
+
const key = row.dataset.key;
|
| 160 |
+
document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => {
|
| 161 |
+
el.classList.add('row-hover');
|
| 162 |
+
});
|
| 163 |
+
}
|
| 164 |
+
});
|
| 165 |
+
|
| 166 |
+
document.addEventListener('mouseout', (e) => {
|
| 167 |
+
const row = e.target.closest('.json-row');
|
| 168 |
+
if (row && row.dataset.key) {
|
| 169 |
+
const key = row.dataset.key;
|
| 170 |
+
document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => {
|
| 171 |
+
el.classList.remove('row-hover');
|
| 172 |
+
});
|
| 173 |
+
}
|
| 174 |
+
});
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
// Initialize
|
| 178 |
+
async function init() {
|
| 179 |
+
setupScrollSync();
|
| 180 |
+
setupRowHoverSync();
|
| 181 |
+
try {
|
| 182 |
+
const res = await fetch('/api/presets');
|
| 183 |
+
presets = await res.json();
|
| 184 |
+
|
| 185 |
+
els.presetSelect.innerHTML = '';
|
| 186 |
+
presets.forEach((p) => {
|
| 187 |
+
const opt = document.createElement('option');
|
| 188 |
+
opt.value = p.id;
|
| 189 |
+
opt.textContent = `${p.title}`;
|
| 190 |
+
els.presetSelect.appendChild(opt);
|
| 191 |
+
});
|
| 192 |
+
|
| 193 |
+
if (presets.length > 0) {
|
| 194 |
+
activePreset = presets[0];
|
| 195 |
+
}
|
| 196 |
+
} catch (e) {
|
| 197 |
+
console.error('Error loading presets:', e);
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
els.presetSelect.addEventListener('change', (e) => {
|
| 201 |
+
activePreset = presets.find(p => p.id === e.target.value);
|
| 202 |
+
reset();
|
| 203 |
+
});
|
| 204 |
+
|
| 205 |
+
els.btnRun.addEventListener('click', runComparison);
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
function reset() {
|
| 209 |
+
els.timerNaive.textContent = '0.0 ms';
|
| 210 |
+
els.timerParallel.textContent = '0.0 ms';
|
| 211 |
+
els.streamOutput.innerHTML = '<span class="placeholder-text">Click "Run Comparison" to start...</span>';
|
| 212 |
+
els.parallelOutput.innerHTML = '<span class="placeholder-text">Click "Run Comparison" to start...</span>';
|
| 213 |
+
els.summaryBar.classList.add('hidden');
|
| 214 |
+
if (els.badgeNaiveHallucinated) {
|
| 215 |
+
els.badgeNaiveHallucinated.classList.add('hidden');
|
| 216 |
+
els.badgeNaiveHallucinated.textContent = '';
|
| 217 |
+
}
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
// Execute Parallel Constrained Generation
|
| 221 |
+
async function runParallel(payload) {
|
| 222 |
+
els.parallelOutput.innerHTML = '<span class="placeholder-text">// Evaluating parallel forward pass across all schema fields...</span>';
|
| 223 |
+
|
| 224 |
+
const t0 = performance.now();
|
| 225 |
+
let running = true;
|
| 226 |
+
function tickParallel() {
|
| 227 |
+
if (!running) return;
|
| 228 |
+
const ms = performance.now() - t0;
|
| 229 |
+
els.timerParallel.textContent = `${ms.toFixed(1)} ms`;
|
| 230 |
+
requestAnimationFrame(tickParallel);
|
| 231 |
+
}
|
| 232 |
+
requestAnimationFrame(tickParallel);
|
| 233 |
+
|
| 234 |
+
try {
|
| 235 |
+
const res = await fetch('/api/run-parallel', {
|
| 236 |
+
method: 'POST',
|
| 237 |
+
headers: { 'Content-Type': 'application/json' },
|
| 238 |
+
body: JSON.stringify(payload)
|
| 239 |
+
});
|
| 240 |
+
const data = await res.json();
|
| 241 |
+
running = false;
|
| 242 |
+
const elapsed = data.elapsed_ms || (performance.now() - t0);
|
| 243 |
+
els.timerParallel.textContent = `${elapsed.toFixed(1)} ms`;
|
| 244 |
+
renderParallel(data);
|
| 245 |
+
return data;
|
| 246 |
+
} catch (err) {
|
| 247 |
+
running = false;
|
| 248 |
+
throw err;
|
| 249 |
+
}
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
function renderParallel(data) {
|
| 253 |
+
if (data.parsed_json) {
|
| 254 |
+
els.parallelOutput.innerHTML = formatParallelJson(data.parsed_json);
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
|
| 258 |
+
// Stream Naive
|
| 259 |
+
async function streamNaive(payload) {
|
| 260 |
+
els.streamOutput.textContent = '';
|
| 261 |
+
|
| 262 |
+
const t0 = performance.now();
|
| 263 |
+
let running = true;
|
| 264 |
+
function tickNaive() {
|
| 265 |
+
if (!running) return;
|
| 266 |
+
const ms = performance.now() - t0;
|
| 267 |
+
els.timerNaive.textContent = `${ms.toFixed(1)} ms`;
|
| 268 |
+
requestAnimationFrame(tickNaive);
|
| 269 |
+
}
|
| 270 |
+
requestAnimationFrame(tickNaive);
|
| 271 |
+
|
| 272 |
+
let tokenCount = 0;
|
| 273 |
+
let finalResult = null;
|
| 274 |
+
|
| 275 |
+
try {
|
| 276 |
+
const response = await fetch('/api/stream-naive', {
|
| 277 |
+
method: 'POST',
|
| 278 |
+
headers: { 'Content-Type': 'application/json' },
|
| 279 |
+
body: JSON.stringify(payload)
|
| 280 |
+
});
|
| 281 |
+
|
| 282 |
+
const reader = response.body.getReader();
|
| 283 |
+
const decoder = new TextDecoder();
|
| 284 |
+
let text = '';
|
| 285 |
+
|
| 286 |
+
while (true) {
|
| 287 |
+
const { value, done } = await reader.read();
|
| 288 |
+
if (done) break;
|
| 289 |
+
|
| 290 |
+
const chunk = decoder.decode(value, { stream: true });
|
| 291 |
+
const lines = chunk.split('\n');
|
| 292 |
+
|
| 293 |
+
for (const line of lines) {
|
| 294 |
+
if (line.startsWith('data: ')) {
|
| 295 |
+
try {
|
| 296 |
+
const ev = JSON.parse(line.substring(6));
|
| 297 |
+
if (ev.type === 'token') {
|
| 298 |
+
text += ev.token;
|
| 299 |
+
tokenCount = ev.token_count;
|
| 300 |
+
els.streamOutput.innerHTML = highlightStreamJson(text);
|
| 301 |
+
els.streamOutput.scrollTop = els.streamOutput.scrollHeight;
|
| 302 |
+
} else if (ev.type === 'done') {
|
| 303 |
+
finalResult = ev.result;
|
| 304 |
+
}
|
| 305 |
+
} catch (e) {}
|
| 306 |
+
}
|
| 307 |
+
}
|
| 308 |
+
}
|
| 309 |
+
} finally {
|
| 310 |
+
running = false;
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
if (finalResult) {
|
| 314 |
+
els.timerNaive.textContent = `${finalResult.elapsed_ms.toFixed(1)} ms`;
|
| 315 |
+
let parsedObj = finalResult.parsed_json;
|
| 316 |
+
if (parsedObj) {
|
| 317 |
+
els.streamOutput.innerHTML = formatNaiveJson(parsedObj);
|
| 318 |
+
} else {
|
| 319 |
+
try {
|
| 320 |
+
const cleaned = text.substring(text.indexOf('{'), text.lastIndexOf('}') + 1);
|
| 321 |
+
parsedObj = JSON.parse(cleaned);
|
| 322 |
+
els.streamOutput.innerHTML = formatNaiveJson(parsedObj);
|
| 323 |
+
} catch (e) {
|
| 324 |
+
els.streamOutput.innerHTML = highlightStreamJson(text);
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
|
| 328 |
+
// Check for missing / hallucinated fields
|
| 329 |
+
const expectedKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : [];
|
| 330 |
+
let missingCount = 0;
|
| 331 |
+
if (parsedObj && typeof parsedObj === 'object') {
|
| 332 |
+
const parsedKeys = Object.keys(parsedObj);
|
| 333 |
+
const missing = expectedKeys.filter(k => !parsedKeys.includes(k));
|
| 334 |
+
const extra = parsedKeys.filter(k => !expectedKeys.includes(k));
|
| 335 |
+
const invalidEnums = finalResult.invalid_enums || [];
|
| 336 |
+
missingCount = missing.length + extra.length + invalidEnums.length;
|
| 337 |
+
} else {
|
| 338 |
+
missingCount = expectedKeys.length;
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
if (missingCount > 0 && els.badgeNaiveHallucinated) {
|
| 342 |
+
els.badgeNaiveHallucinated.textContent = `${missingCount} field${missingCount > 1 ? 's' : ''} hallucinated`;
|
| 343 |
+
els.badgeNaiveHallucinated.classList.remove('hidden');
|
| 344 |
+
}
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
return finalResult;
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
// Side-by-Side Comparison Runner
|
| 351 |
+
async function runComparison() {
|
| 352 |
+
if (!activePreset) return;
|
| 353 |
+
|
| 354 |
+
reset();
|
| 355 |
+
els.btnRun.disabled = true;
|
| 356 |
+
els.btnRun.textContent = 'Running...';
|
| 357 |
+
|
| 358 |
+
const payload = {
|
| 359 |
+
context: activePreset.context,
|
| 360 |
+
schema: activePreset.schema
|
| 361 |
+
};
|
| 362 |
+
|
| 363 |
+
try {
|
| 364 |
+
// 1. Run Parallel Constrained inference first
|
| 365 |
+
const parallelData = await runParallel(payload);
|
| 366 |
+
|
| 367 |
+
// 2. Stream Naive right after
|
| 368 |
+
const naiveData = await streamNaive(payload);
|
| 369 |
+
|
| 370 |
+
// 3. Update summary pill
|
| 371 |
+
if (parallelData && naiveData) {
|
| 372 |
+
const speedup = (naiveData.elapsed_ms / Math.max(parallelData.elapsed_ms, 1.0)).toFixed(1);
|
| 373 |
+
els.sumSpeedup.textContent = `${speedup}x FASTER`;
|
| 374 |
+
els.sumTimes.textContent = `${parallelData.elapsed_ms.toFixed(1)} ms vs ${naiveData.elapsed_ms.toFixed(1)} ms`;
|
| 375 |
+
els.summaryBar.classList.remove('hidden');
|
| 376 |
+
}
|
| 377 |
+
} catch (err) {
|
| 378 |
+
console.error('Run failed:', err);
|
| 379 |
+
} finally {
|
| 380 |
+
els.btnRun.disabled = false;
|
| 381 |
+
els.btnRun.innerHTML = '<span>⚡ Run Comparison</span>';
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
|
| 385 |
+
document.addEventListener('DOMContentLoaded', init);
|
web/index.html
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 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>Parallel Constrained vs Normal Inference (Qwen2.5 1.5B)</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 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=JetBrains+Mono:wght@400;500;600&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
| 11 |
+
</head>
|
| 12 |
+
<body>
|
| 13 |
+
<div class="container">
|
| 14 |
+
<!-- Centered Controls -->
|
| 15 |
+
<div class="controls-bar">
|
| 16 |
+
<select id="preset-select" aria-label="Select Scenario Preset"></select>
|
| 17 |
+
<button id="btn-run" class="btn-primary">
|
| 18 |
+
<span>⚡ Run Comparison</span>
|
| 19 |
+
</button>
|
| 20 |
+
</div>
|
| 21 |
+
|
| 22 |
+
<!-- Speedup Summary Banner (Shows after run) -->
|
| 23 |
+
<div id="summary-bar" class="summary-bar hidden">
|
| 24 |
+
<div class="summary-pill">
|
| 25 |
+
<span class="summary-highlight" id="sum-speedup">2.4x FASTER</span>
|
| 26 |
+
<span class="summary-sep">·</span>
|
| 27 |
+
<span id="sum-times">180 ms vs 435 ms</span>
|
| 28 |
+
</div>
|
| 29 |
+
</div>
|
| 30 |
+
|
| 31 |
+
<!-- Side by Side Main View -->
|
| 32 |
+
<main class="grid">
|
| 33 |
+
<!-- Left: Parallel Constrained -->
|
| 34 |
+
<section class="card card-parallel">
|
| 35 |
+
<div class="card-header">
|
| 36 |
+
<h2>Parallel Constrained (Qwen2.5 1.5B)</h2>
|
| 37 |
+
<span class="timer-badge badge-green" id="timer-parallel">0.0 ms</span>
|
| 38 |
+
</div>
|
| 39 |
+
<div class="card-body" id="body-parallel">
|
| 40 |
+
<pre id="parallel-output" class="code-box"><span class="placeholder-text">Click "Run Comparison" to start...</span></pre>
|
| 41 |
+
</div>
|
| 42 |
+
</section>
|
| 43 |
+
|
| 44 |
+
<!-- Right: Normal Autoregressive -->
|
| 45 |
+
<section class="card">
|
| 46 |
+
<div class="card-header">
|
| 47 |
+
<div class="title-with-badge">
|
| 48 |
+
<h2>Normal Inference (Qwen2.5 1.5B)</h2>
|
| 49 |
+
<span id="badge-naive-hallucinated" class="badge-red hidden">0 fields hallucinated</span>
|
| 50 |
+
</div>
|
| 51 |
+
<span class="timer-badge" id="timer-naive">0.0 ms</span>
|
| 52 |
+
</div>
|
| 53 |
+
<div class="card-body" id="body-naive">
|
| 54 |
+
<pre id="stream-output" class="code-box"><span class="placeholder-text">Click "Run Comparison" to start...</span></pre>
|
| 55 |
+
</div>
|
| 56 |
+
</section>
|
| 57 |
+
</main>
|
| 58 |
+
</div>
|
| 59 |
+
|
| 60 |
+
<script src="app.js"></script>
|
| 61 |
+
</body>
|
| 62 |
+
</html>
|
web/style.css
ADDED
|
@@ -0,0 +1,412 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--bg-app: #f8fafc;
|
| 3 |
+
--bg-card: #ffffff;
|
| 4 |
+
--border: #e2e8f0;
|
| 5 |
+
--border-hover: #cbd5e1;
|
| 6 |
+
|
| 7 |
+
--text-main: #0f172a;
|
| 8 |
+
--text-muted: #64748b;
|
| 9 |
+
--text-subtle: #94a3b8;
|
| 10 |
+
|
| 11 |
+
--green-primary: #16a34a;
|
| 12 |
+
--green-bg: #f0fdf4;
|
| 13 |
+
--green-border: #bbf7d0;
|
| 14 |
+
|
| 15 |
+
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
| 16 |
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
* {
|
| 20 |
+
box-sizing: border-box;
|
| 21 |
+
margin: 0;
|
| 22 |
+
padding: 0;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
html, body {
|
| 26 |
+
height: 100%;
|
| 27 |
+
margin: 0;
|
| 28 |
+
padding: 0;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
body {
|
| 32 |
+
background-color: var(--bg-app);
|
| 33 |
+
color: var(--text-main);
|
| 34 |
+
font-family: var(--font-sans);
|
| 35 |
+
-webkit-font-smoothing: antialiased;
|
| 36 |
+
display: flex;
|
| 37 |
+
flex-direction: column;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.container {
|
| 41 |
+
width: 100%;
|
| 42 |
+
margin: 0;
|
| 43 |
+
padding: 14px 16px 20px 16px;
|
| 44 |
+
box-sizing: border-box;
|
| 45 |
+
display: flex;
|
| 46 |
+
flex-direction: column;
|
| 47 |
+
gap: 14px;
|
| 48 |
+
height: 100vh;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/* Controls Bar */
|
| 52 |
+
.controls-bar {
|
| 53 |
+
display: flex;
|
| 54 |
+
justify-content: center;
|
| 55 |
+
align-items: center;
|
| 56 |
+
gap: 14px;
|
| 57 |
+
padding: 6px 0 2px 0;
|
| 58 |
+
width: 100%;
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
select {
|
| 62 |
+
background: #ffffff;
|
| 63 |
+
border: 1px solid var(--border);
|
| 64 |
+
color: var(--text-main);
|
| 65 |
+
padding: 9px 14px;
|
| 66 |
+
border-radius: 8px;
|
| 67 |
+
font-family: var(--font-sans);
|
| 68 |
+
font-size: 0.88rem;
|
| 69 |
+
cursor: pointer;
|
| 70 |
+
outline: none;
|
| 71 |
+
font-weight: 500;
|
| 72 |
+
transition: border-color 0.15s ease;
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
select:hover {
|
| 76 |
+
border-color: var(--border-hover);
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
select:focus {
|
| 80 |
+
border-color: var(--text-main);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
.btn-primary {
|
| 84 |
+
background: var(--text-main);
|
| 85 |
+
color: #ffffff;
|
| 86 |
+
border: none;
|
| 87 |
+
padding: 9px 18px;
|
| 88 |
+
border-radius: 8px;
|
| 89 |
+
font-size: 0.88rem;
|
| 90 |
+
font-weight: 600;
|
| 91 |
+
cursor: pointer;
|
| 92 |
+
display: inline-flex;
|
| 93 |
+
align-items: center;
|
| 94 |
+
gap: 6px;
|
| 95 |
+
transition: all 0.15s ease;
|
| 96 |
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
.btn-primary:hover {
|
| 100 |
+
background: #1e293b;
|
| 101 |
+
transform: translateY(-1px);
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
.btn-primary:disabled {
|
| 105 |
+
opacity: 0.6;
|
| 106 |
+
cursor: not-allowed;
|
| 107 |
+
transform: none;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
/* Summary Pill */
|
| 111 |
+
.summary-bar {
|
| 112 |
+
display: flex;
|
| 113 |
+
justify-content: center;
|
| 114 |
+
padding: 4px 0 2px 0;
|
| 115 |
+
transition: all 0.2s ease;
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
.summary-bar.hidden {
|
| 119 |
+
display: none;
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
.summary-pill {
|
| 123 |
+
background: var(--green-bg);
|
| 124 |
+
border: 2px solid var(--green-border);
|
| 125 |
+
padding: 14px 34px;
|
| 126 |
+
border-radius: 9999px;
|
| 127 |
+
display: inline-flex;
|
| 128 |
+
align-items: center;
|
| 129 |
+
gap: 16px;
|
| 130 |
+
box-shadow: 0 4px 16px rgba(22, 163, 74, 0.14);
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
.summary-highlight {
|
| 134 |
+
font-family: var(--font-mono);
|
| 135 |
+
font-size: 1.65rem;
|
| 136 |
+
font-weight: 900;
|
| 137 |
+
color: #14532d;
|
| 138 |
+
letter-spacing: -0.02em;
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
.summary-sep {
|
| 142 |
+
color: #4ade80;
|
| 143 |
+
font-size: 1.6rem;
|
| 144 |
+
font-weight: 900;
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
+
#sum-times {
|
| 148 |
+
font-family: var(--font-mono);
|
| 149 |
+
font-weight: 800;
|
| 150 |
+
font-size: 1.35rem;
|
| 151 |
+
color: #15803d;
|
| 152 |
+
letter-spacing: -0.01em;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
/* Main Grid */
|
| 156 |
+
.grid {
|
| 157 |
+
display: grid;
|
| 158 |
+
grid-template-columns: 1fr 1fr;
|
| 159 |
+
gap: 16px;
|
| 160 |
+
flex: 1;
|
| 161 |
+
min-height: 0;
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
.card {
|
| 165 |
+
background: var(--bg-card);
|
| 166 |
+
border: 1px solid var(--border);
|
| 167 |
+
border-radius: 12px;
|
| 168 |
+
display: flex;
|
| 169 |
+
flex-direction: column;
|
| 170 |
+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.03);
|
| 171 |
+
overflow: hidden;
|
| 172 |
+
height: 100%;
|
| 173 |
+
min-height: 0;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
.card-header {
|
| 177 |
+
padding: 18px 24px;
|
| 178 |
+
border-bottom: 1px solid var(--border);
|
| 179 |
+
display: flex;
|
| 180 |
+
justify-content: space-between;
|
| 181 |
+
align-items: center;
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
.card-header h2 {
|
| 185 |
+
font-size: 1.35rem;
|
| 186 |
+
font-weight: 800;
|
| 187 |
+
color: var(--text-main);
|
| 188 |
+
letter-spacing: -0.02em;
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
.title-with-badge {
|
| 192 |
+
display: flex;
|
| 193 |
+
align-items: center;
|
| 194 |
+
gap: 12px;
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
.badge-red {
|
| 198 |
+
background: #fef2f2;
|
| 199 |
+
color: #dc2626;
|
| 200 |
+
border: 1.5px solid #fecaca;
|
| 201 |
+
font-size: 0.85rem;
|
| 202 |
+
font-weight: 700;
|
| 203 |
+
padding: 4px 10px;
|
| 204 |
+
border-radius: 6px;
|
| 205 |
+
letter-spacing: -0.01em;
|
| 206 |
+
display: inline-flex;
|
| 207 |
+
align-items: center;
|
| 208 |
+
white-space: nowrap;
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
.badge-red.hidden {
|
| 212 |
+
display: none;
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
.timer-badge {
|
| 216 |
+
font-family: var(--font-mono);
|
| 217 |
+
font-size: 1.5rem;
|
| 218 |
+
font-weight: 800;
|
| 219 |
+
padding: 8px 18px;
|
| 220 |
+
border-radius: 10px;
|
| 221 |
+
background: #f8fafc;
|
| 222 |
+
color: #0f172a;
|
| 223 |
+
border: 1.5px solid #cbd5e1;
|
| 224 |
+
letter-spacing: -0.02em;
|
| 225 |
+
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
.badge-green {
|
| 229 |
+
background: var(--green-bg);
|
| 230 |
+
color: #14532d;
|
| 231 |
+
border: 2px solid #86efac;
|
| 232 |
+
font-weight: 900;
|
| 233 |
+
box-shadow: 0 1px 4px rgba(22, 163, 74, 0.12);
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
.card-body {
|
| 237 |
+
padding: 18px 20px;
|
| 238 |
+
flex: 1;
|
| 239 |
+
min-height: 0;
|
| 240 |
+
overflow-y: auto;
|
| 241 |
+
}
|
| 242 |
+
|
| 243 |
+
/* Code Box */
|
| 244 |
+
.code-box {
|
| 245 |
+
font-family: var(--font-mono);
|
| 246 |
+
font-size: 0.82rem;
|
| 247 |
+
color: #1e293b;
|
| 248 |
+
line-height: 1.6;
|
| 249 |
+
white-space: pre;
|
| 250 |
+
overflow-x: auto;
|
| 251 |
+
margin: 0;
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
/* JSON Syntax Highlighting & Interactive Rows */
|
| 255 |
+
.json-row {
|
| 256 |
+
display: block;
|
| 257 |
+
padding: 1.5px 8px;
|
| 258 |
+
border-radius: 4px;
|
| 259 |
+
line-height: 1.6;
|
| 260 |
+
white-space: pre;
|
| 261 |
+
border-left: 2.5px solid transparent;
|
| 262 |
+
transition: background-color 0.12s ease, border-left-color 0.12s ease;
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
.json-row:hover,
|
| 266 |
+
.json-row.row-hover {
|
| 267 |
+
background-color: #f8fafc;
|
| 268 |
+
border-left-color: #2563eb;
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
.json-key {
|
| 272 |
+
color: #2563eb;
|
| 273 |
+
font-weight: 600;
|
| 274 |
+
letter-spacing: -0.01em;
|
| 275 |
+
}
|
| 276 |
+
|
| 277 |
+
.json-sub {
|
| 278 |
+
color: var(--text-main);
|
| 279 |
+
font-weight: 500;
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
.json-punct {
|
| 283 |
+
color: var(--text-main);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
.json-val-str {
|
| 287 |
+
color: var(--text-main);
|
| 288 |
+
font-weight: 500;
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
.json-val-bool {
|
| 292 |
+
color: var(--text-main);
|
| 293 |
+
font-weight: 600;
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
.json-val-bool.bool-true,
|
| 297 |
+
.json-val-bool.bool-false {
|
| 298 |
+
color: var(--text-main);
|
| 299 |
+
}
|
| 300 |
+
|
| 301 |
+
.json-val-num {
|
| 302 |
+
color: var(--text-main);
|
| 303 |
+
font-weight: 500;
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
.json-val-prob {
|
| 307 |
+
color: var(--text-main);
|
| 308 |
+
font-weight: 500;
|
| 309 |
+
font-family: var(--font-mono);
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
+
.json-val-null {
|
| 313 |
+
color: var(--text-main);
|
| 314 |
+
font-style: italic;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
+
.placeholder-text {
|
| 318 |
+
color: var(--text-subtle);
|
| 319 |
+
font-style: italic;
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
/* Parallel Results List */
|
| 323 |
+
.results-list {
|
| 324 |
+
display: flex;
|
| 325 |
+
flex-direction: column;
|
| 326 |
+
gap: 8px;
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
.empty-state {
|
| 330 |
+
color: var(--text-subtle);
|
| 331 |
+
font-size: 0.88rem;
|
| 332 |
+
text-align: center;
|
| 333 |
+
padding-top: 100px;
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
.item-row {
|
| 337 |
+
display: flex;
|
| 338 |
+
justify-content: space-between;
|
| 339 |
+
align-items: center;
|
| 340 |
+
padding: 8px 12px;
|
| 341 |
+
background: #f8fafc;
|
| 342 |
+
border: 1px solid var(--border);
|
| 343 |
+
border-radius: 8px;
|
| 344 |
+
font-size: 0.82rem;
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
.item-key {
|
| 348 |
+
font-family: var(--font-mono);
|
| 349 |
+
font-weight: 600;
|
| 350 |
+
color: #334155;
|
| 351 |
+
}
|
| 352 |
+
|
| 353 |
+
.item-right {
|
| 354 |
+
display: flex;
|
| 355 |
+
align-items: center;
|
| 356 |
+
gap: 8px;
|
| 357 |
+
}
|
| 358 |
+
|
| 359 |
+
.item-val {
|
| 360 |
+
font-family: var(--font-mono);
|
| 361 |
+
font-weight: 600;
|
| 362 |
+
padding: 2px 6px;
|
| 363 |
+
border-radius: 4px;
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
.val-true {
|
| 367 |
+
background: #dcfce7;
|
| 368 |
+
color: #15803d;
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
.val-false {
|
| 372 |
+
background: #fee2e2;
|
| 373 |
+
color: #b91c1c;
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
.val-enum {
|
| 377 |
+
background: #f1f5f9;
|
| 378 |
+
color: #0f172a;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
.item-prob {
|
| 382 |
+
font-family: var(--font-mono);
|
| 383 |
+
font-size: 0.74rem;
|
| 384 |
+
color: #16a34a;
|
| 385 |
+
background: var(--green-bg);
|
| 386 |
+
padding: 2px 6px;
|
| 387 |
+
border-radius: 4px;
|
| 388 |
+
font-weight: 600;
|
| 389 |
+
min-width: 48px;
|
| 390 |
+
text-align: center;
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
/* Card Footer */
|
| 394 |
+
.card-footer {
|
| 395 |
+
padding: 12px 20px;
|
| 396 |
+
border-top: 1px solid var(--border);
|
| 397 |
+
background: #f8fafc;
|
| 398 |
+
font-size: 0.78rem;
|
| 399 |
+
color: var(--text-muted);
|
| 400 |
+
display: flex;
|
| 401 |
+
justify-content: space-between;
|
| 402 |
+
}
|
| 403 |
+
|
| 404 |
+
.text-green {
|
| 405 |
+
color: var(--green-primary);
|
| 406 |
+
}
|
| 407 |
+
|
| 408 |
+
@media (max-width: 800px) {
|
| 409 |
+
.grid {
|
| 410 |
+
grid-template-columns: 1fr;
|
| 411 |
+
}
|
| 412 |
+
}
|