Ship v2 intent extraction with API, demo UI, eval, and benchmark suite.
Browse filesAdd intent training pipeline, parameter-aware inference, and Pocket Automator
benchmark tooling. Exclude generated train_intent.jsonl from git (HF 10MB limit).
Co-authored-by: Cursor <cursoragent@cursor.com>
- .gitignore +3 -0
- README.md +44 -4
- app.py +99 -18
- data/eval_intent_prompts.json +425 -0
- data/pocket_benchmark_prompts.json +2975 -0
- data/skill_schemas.json +153 -0
- modal_apps/evaluate_intent_modal.py +302 -0
- modal_apps/evaluate_pocket_benchmark_modal.py +223 -0
- modal_apps/predict_api.py +27 -54
- modal_apps/train_modal.py +14 -3
- scripts/generate_intent_dataset.py +949 -0
- scripts/generate_pocket_benchmark.py +404 -0
- src/classifier_prompt.py +18 -1
- src/evaluate_intent.py +198 -0
- src/evaluate_pocket_benchmark.py +200 -0
- src/pocket_benchmark.py +316 -0
- src/skill_utils.py +1 -0
.gitignore
CHANGED
|
@@ -13,6 +13,9 @@ trained_model/
|
|
| 13 |
result.json
|
| 14 |
*.log
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
# OS / editor
|
| 17 |
.DS_Store
|
| 18 |
.idea/
|
|
|
|
| 13 |
result.json
|
| 14 |
*.log
|
| 15 |
|
| 16 |
+
# Training data (generated locally; uploaded to Modal volume — too large for HF Space)
|
| 17 |
+
data/train_intent.jsonl
|
| 18 |
+
|
| 19 |
# OS / editor
|
| 20 |
.DS_Store
|
| 21 |
.idea/
|
README.md
CHANGED
|
@@ -24,7 +24,7 @@ Turn everyday phone requests into structured Android automation skills. A fine-t
|
|
| 24 |
|
| 25 |
- **Demo video:** [YouTube Short](https://youtube.com/shorts/IQRHf7HfTDA?si=9kEoZKRus6FzHYbB)
|
| 26 |
- **Android recorder:** [Pocket Automator](https://github.com/kriyanshii/pocket-automator)
|
| 27 |
-
- **Social post:**
|
| 28 |
- **Space:** [android-skill-router](https://huggingface.co/spaces/build-small-hackathon/android-skill-router)
|
| 29 |
|
| 30 |
## The idea
|
|
@@ -47,7 +47,7 @@ UI traces in `trajectories/` were captured with **[Pocket Automator](https://git
|
|
| 47 |
| --- | --- |
|
| 48 |
| **Base model** | [Qwen/Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) |
|
| 49 |
| **Fine-tune** | 4-bit QLoRA + SFT with [Unsloth](https://github.com/unslothai/unsloth) on Modal (`modal_apps/train_modal.py`) |
|
| 50 |
-
| **Inference** | Modal GPU API (`modal_apps/predict_api.py`) —
|
| 51 |
| **Demo UI** | Gradio (`app.py`) |
|
| 52 |
| **Recorder** | [Pocket Automator](https://github.com/kriyanshii/pocket-automator) — Android accessibility capture & replay |
|
| 53 |
| **Data** | 15 Android trajectories → `data/skills.jsonl` → ~510 prompt variations in `data/train.jsonl` |
|
|
@@ -62,10 +62,11 @@ UI traces in `trajectories/` were captured with **[Pocket Automator](https://git
|
|
| 62 |
## Quick start (local dev)
|
| 63 |
|
| 64 |
```bash
|
| 65 |
-
# 1. Train on Modal (uploads data/
|
| 66 |
pip install modal
|
| 67 |
modal setup
|
| 68 |
-
|
|
|
|
| 69 |
|
| 70 |
# 2. Deploy inference API
|
| 71 |
modal deploy modal_apps/predict_api.py
|
|
@@ -77,6 +78,12 @@ export MODAL_PREDICT_URL="https://<workspace>--android-skill-predict-api-skillpr
|
|
| 77 |
python app.py
|
| 78 |
```
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
### Hugging Face Space setup
|
| 81 |
|
| 82 |
1. Create a **Gradio Space** inside the [build-small-hackathon](https://huggingface.co/build-small-hackathon) org.
|
|
@@ -129,6 +136,39 @@ python scripts/generate_skill_dataset.py # trajectories → data/skills.jsonl
|
|
| 129 |
python scripts/generate_training_data.py # data/skills.jsonl → data/train.jsonl
|
| 130 |
```
|
| 131 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
## License
|
| 133 |
|
| 134 |
Apache 2.0. Base model weights subject to [Qwen license](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct).
|
|
|
|
| 24 |
|
| 25 |
- **Demo video:** [YouTube Short](https://youtube.com/shorts/IQRHf7HfTDA?si=9kEoZKRus6FzHYbB)
|
| 26 |
- **Android recorder:** [Pocket Automator](https://github.com/kriyanshii/pocket-automator)
|
| 27 |
+
- **Social post:** [Twitter Post](https://x.com/kriyanshii/status/2066587828839141634?s=20)
|
| 28 |
- **Space:** [android-skill-router](https://huggingface.co/spaces/build-small-hackathon/android-skill-router)
|
| 29 |
|
| 30 |
## The idea
|
|
|
|
| 47 |
| --- | --- |
|
| 48 |
| **Base model** | [Qwen/Qwen2.5-3B-Instruct](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct) |
|
| 49 |
| **Fine-tune** | 4-bit QLoRA + SFT with [Unsloth](https://github.com/unslothai/unsloth) on Modal (`modal_apps/train_modal.py`) |
|
| 50 |
+
| **Inference** | Modal GPU API (`modal_apps/predict_api.py`) — returns skill + parameters |
|
| 51 |
| **Demo UI** | Gradio (`app.py`) |
|
| 52 |
| **Recorder** | [Pocket Automator](https://github.com/kriyanshii/pocket-automator) — Android accessibility capture & replay |
|
| 53 |
| **Data** | 15 Android trajectories → `data/skills.jsonl` → ~510 prompt variations in `data/train.jsonl` |
|
|
|
|
| 62 |
## Quick start (local dev)
|
| 63 |
|
| 64 |
```bash
|
| 65 |
+
# 1. Train intent model on Modal (uploads data/train_intent.jsonl, saves adapter to volume)
|
| 66 |
pip install modal
|
| 67 |
modal setup
|
| 68 |
+
python scripts/generate_intent_dataset.py
|
| 69 |
+
modal run modal_apps/train_modal.py --dataset train_intent.jsonl
|
| 70 |
|
| 71 |
# 2. Deploy inference API
|
| 72 |
modal deploy modal_apps/predict_api.py
|
|
|
|
| 78 |
python app.py
|
| 79 |
```
|
| 80 |
|
| 81 |
+
The `/predict` endpoint returns structured intents:
|
| 82 |
+
|
| 83 |
+
```json
|
| 84 |
+
{"skill": "whatsapp_send_message", "parameters": {"contact": "ri", "message": "see you soon"}}
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
### Hugging Face Space setup
|
| 88 |
|
| 89 |
1. Create a **Gradio Space** inside the [build-small-hackathon](https://huggingface.co/build-small-hackathon) org.
|
|
|
|
| 136 |
python scripts/generate_training_data.py # data/skills.jsonl → data/train.jsonl
|
| 137 |
```
|
| 138 |
|
| 139 |
+
## V2: Intent extraction
|
| 140 |
+
|
| 141 |
+
V1 maps prompts to a skill label only. V2 extracts structured intents:
|
| 142 |
+
|
| 143 |
+
"text mom on whatsapp i'm on my way"
|
| 144 |
+
→ {"skill": "whatsapp_send_message", "parameters": {"contact": "mom", "message": "i'm on my way"}}
|
| 145 |
+
|
| 146 |
+
The Gradio demo and Modal `/predict` API both return skill + parameters.
|
| 147 |
+
|
| 148 |
+
### Data
|
| 149 |
+
- `data/skill_schemas.json` — parameter definitions per skill
|
| 150 |
+
- `data/train_intent.jsonl` — ~15k synthetic SFT examples (generated locally via script; gitignored — upload to Modal for training)
|
| 151 |
+
- `data/eval_intent_prompts.json` — held-out intent eval set
|
| 152 |
+
- `data/pocket_benchmark_prompts.json` — 200 real-world messy prompts
|
| 153 |
+
|
| 154 |
+
### Train & evaluate
|
| 155 |
+
```bash
|
| 156 |
+
python scripts/generate_intent_dataset.py
|
| 157 |
+
modal run modal_apps/train_modal.py --dataset train_intent.jsonl
|
| 158 |
+
modal run modal_apps/evaluate_intent_modal.py
|
| 159 |
+
modal run modal_apps/evaluate_pocket_benchmark_modal.py
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
### Benchmark results (Pocket Automator, 200 prompts)
|
| 163 |
+
|
| 164 |
+
| Metric | Score |
|
| 165 |
+
|--------|-------|
|
| 166 |
+
| Skill accuracy | 99.0% |
|
| 167 |
+
| Parameter accuracy | 86.0% |
|
| 168 |
+
| Exact JSON match | 85.5% |
|
| 169 |
+
|
| 170 |
+
Trajectory parameterization (slot-filling at replay time) is planned next.
|
| 171 |
+
|
| 172 |
## License
|
| 173 |
|
| 174 |
Apache 2.0. Base model weights subject to [Qwen license](https://huggingface.co/Qwen/Qwen2.5-3B-Instruct).
|
app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
"""
|
| 2 |
Android Skill Router — Gradio demo for the Build Small hackathon.
|
| 3 |
|
| 4 |
-
Natural language in → fine-tuned Qwen2.5-3B
|
| 5 |
|
| 6 |
Inference runs on Modal (deploy modal_apps/predict_api.py). Set MODAL_PREDICT_URL in Space secrets.
|
| 7 |
"""
|
|
@@ -22,6 +22,21 @@ from src.skill_utils import resolve_skill
|
|
| 22 |
|
| 23 |
MODAL_PREDICT_URL = os.environ.get("MODAL_PREDICT_URL", "").rstrip("/")
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
EXAMPLE_PROMPTS = [
|
| 26 |
"play my workout playlist",
|
| 27 |
"turn bluetooth on",
|
|
@@ -66,19 +81,20 @@ IDLE_RESULT_HTML = """
|
|
| 66 |
<div class="result-card result-card--idle">
|
| 67 |
<div class="result-card-header">Automation Result</div>
|
| 68 |
<p class="result-placeholder">
|
| 69 |
-
Describe what you want your phone to do — the
|
| 70 |
-
|
| 71 |
</p>
|
| 72 |
<div class="result-metrics result-metrics--empty">
|
| 73 |
<div class="metric-tile"><span class="metric-label">Detected Skill</span><span class="metric-value muted">—</span></div>
|
|
|
|
| 74 |
<div class="metric-tile"><span class="metric-label">Predicted Confidence</span><span class="metric-value muted">—</span></div>
|
| 75 |
-
<div class="metric-tile"><span class="metric-label">Android Trajectory</span><span class="metric-value muted">—</span></div>
|
| 76 |
</div>
|
| 77 |
</div>
|
| 78 |
"""
|
| 79 |
|
| 80 |
|
| 81 |
-
def
|
| 82 |
if not MODAL_PREDICT_URL:
|
| 83 |
raise gr.Error(
|
| 84 |
"MODAL_PREDICT_URL is not set. Deploy modal_apps/predict_api.py on Modal and add the "
|
|
@@ -104,23 +120,49 @@ def _predict_skill(prompt: str) -> str:
|
|
| 104 |
raise gr.Error(
|
| 105 |
f"Could not route prompt to a known skill (model returned {raw_skill!r})."
|
| 106 |
)
|
| 107 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
|
| 109 |
|
| 110 |
-
def _demo_confidence_percent(prompt: str, skill: str) -> float:
|
| 111 |
-
"""Deterministic demo confidence for the hackathon UI
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
| 113 |
return 94.0 + (int(digest[:4], 16) % 56) / 10.0
|
| 114 |
|
| 115 |
|
| 116 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
label = SKILL_LABELS.get(skill, skill.replace("_", " ").title())
|
| 118 |
-
|
|
|
|
| 119 |
data = load_trajectory(skill)
|
| 120 |
steps = data.get("steps", [])
|
| 121 |
task = html.escape(str(data.get("task", "—")))
|
| 122 |
app_pkg = html.escape(str(data.get("app", "—")))
|
| 123 |
trajectory_file = html.escape(Path(SKILL_TO_TRAJECTORY[skill]).name)
|
|
|
|
| 124 |
|
| 125 |
return f"""
|
| 126 |
<div class="result-card">
|
|
@@ -141,6 +183,10 @@ def _build_result_card(skill: str, prompt: str) -> str:
|
|
| 141 |
</div>
|
| 142 |
<span class="metric-sub">Greedy decode · fine-tuned Qwen2.5-3B</span>
|
| 143 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
<div class="metric-tile metric-tile--wide">
|
| 145 |
<span class="metric-label">Associated Android Trajectory</span>
|
| 146 |
<span class="metric-value">{task}</span>
|
|
@@ -168,12 +214,13 @@ def _build_skills_catalog_card() -> str:
|
|
| 168 |
"""
|
| 169 |
|
| 170 |
|
| 171 |
-
def _trajectory_summary(skill: str) -> tuple[str, str, str]:
|
| 172 |
data = load_trajectory(skill)
|
| 173 |
steps = data.get("steps", [])
|
| 174 |
task = data.get("task", "—")
|
| 175 |
app_pkg = data.get("app", "—")
|
| 176 |
trajectory_file = Path(SKILL_TO_TRAJECTORY[skill]).name
|
|
|
|
| 177 |
|
| 178 |
summary = (
|
| 179 |
f"### {SKILL_LABELS.get(skill, skill)}\n\n"
|
|
@@ -183,9 +230,13 @@ def _trajectory_summary(skill: str) -> tuple[str, str, str]:
|
|
| 183 |
f"**Steps** · {len(steps)} UI snapshots \n"
|
| 184 |
f"**File** · `{trajectory_file}`"
|
| 185 |
)
|
|
|
|
|
|
|
|
|
|
| 186 |
|
| 187 |
preview = {
|
| 188 |
"skill": skill,
|
|
|
|
| 189 |
"task": task,
|
| 190 |
"app": app_pkg,
|
| 191 |
"steps": len(steps),
|
|
@@ -199,10 +250,12 @@ def route_prompt(prompt: str) -> tuple[str, gr.update, str]:
|
|
| 199 |
if not prompt:
|
| 200 |
raise gr.Error("Enter a prompt to classify.")
|
| 201 |
|
| 202 |
-
|
| 203 |
-
|
|
|
|
|
|
|
| 204 |
return (
|
| 205 |
-
_build_result_card(skill, prompt),
|
| 206 |
gr.update(value=preview_json, visible=True),
|
| 207 |
trajectory_json,
|
| 208 |
)
|
|
@@ -645,6 +698,34 @@ footer { display: none !important; }
|
|
| 645 |
min-width: 3.5rem;
|
| 646 |
text-align: right;
|
| 647 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
.skills-catalog {
|
| 649 |
list-style: none;
|
| 650 |
margin: 0;
|
|
@@ -879,7 +960,7 @@ with gr.Blocks(
|
|
| 879 |
<span class="pipeline-arrow">↓</span>
|
| 880 |
<span class="pipeline-step highlight">Qwen 2.5 3B</span>
|
| 881 |
<span class="pipeline-arrow">↓</span>
|
| 882 |
-
<span class="pipeline-step">
|
| 883 |
<span class="pipeline-arrow">↓</span>
|
| 884 |
<span class="pipeline-step">Replay Engine</span>
|
| 885 |
<span class="pipeline-arrow">↓</span>
|
|
@@ -905,7 +986,7 @@ with gr.Blocks(
|
|
| 905 |
show_label=False,
|
| 906 |
)
|
| 907 |
with gr.Row(elem_classes=["btn-row"]):
|
| 908 |
-
submit = gr.Button("Run
|
| 909 |
skills_btn = gr.Button("Browse skills", variant="secondary", scale=1)
|
| 910 |
with gr.Group(elem_classes=["examples-wrap"]):
|
| 911 |
gr.Examples(
|
|
@@ -922,7 +1003,7 @@ with gr.Blocks(
|
|
| 922 |
show_label=False,
|
| 923 |
)
|
| 924 |
preview_json = gr.Code(
|
| 925 |
-
label="
|
| 926 |
language="json",
|
| 927 |
elem_classes=["code-block"],
|
| 928 |
visible=False,
|
|
|
|
| 1 |
"""
|
| 2 |
Android Skill Router — Gradio demo for the Build Small hackathon.
|
| 3 |
|
| 4 |
+
Natural language in → fine-tuned Qwen2.5-3B intent out → Android UI trajectory.
|
| 5 |
|
| 6 |
Inference runs on Modal (deploy modal_apps/predict_api.py). Set MODAL_PREDICT_URL in Space secrets.
|
| 7 |
"""
|
|
|
|
| 22 |
|
| 23 |
MODAL_PREDICT_URL = os.environ.get("MODAL_PREDICT_URL", "").rstrip("/")
|
| 24 |
|
| 25 |
+
PARAMETER_LABELS = {
|
| 26 |
+
"contact": "Contact",
|
| 27 |
+
"message": "Message",
|
| 28 |
+
"recipient": "Recipient",
|
| 29 |
+
"time": "Time",
|
| 30 |
+
"day": "Day",
|
| 31 |
+
"title": "Title",
|
| 32 |
+
"date": "Date",
|
| 33 |
+
"channel": "Channel",
|
| 34 |
+
"playlist": "Playlist",
|
| 35 |
+
"query": "Query",
|
| 36 |
+
"destination": "Destination",
|
| 37 |
+
"name": "Name",
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
EXAMPLE_PROMPTS = [
|
| 41 |
"play my workout playlist",
|
| 42 |
"turn bluetooth on",
|
|
|
|
| 81 |
<div class="result-card result-card--idle">
|
| 82 |
<div class="result-card-header">Automation Result</div>
|
| 83 |
<p class="result-placeholder">
|
| 84 |
+
Describe what you want your phone to do — the model will extract the skill and parameters,
|
| 85 |
+
then surface the matching Android trajectory.
|
| 86 |
</p>
|
| 87 |
<div class="result-metrics result-metrics--empty">
|
| 88 |
<div class="metric-tile"><span class="metric-label">Detected Skill</span><span class="metric-value muted">—</span></div>
|
| 89 |
+
<div class="metric-tile"><span class="metric-label">Extracted Parameters</span><span class="metric-value muted">—</span></div>
|
| 90 |
<div class="metric-tile"><span class="metric-label">Predicted Confidence</span><span class="metric-value muted">—</span></div>
|
| 91 |
+
<div class="metric-tile metric-tile--wide"><span class="metric-label">Android Trajectory</span><span class="metric-value muted">—</span></div>
|
| 92 |
</div>
|
| 93 |
</div>
|
| 94 |
"""
|
| 95 |
|
| 96 |
|
| 97 |
+
def _predict_intent(prompt: str) -> dict:
|
| 98 |
if not MODAL_PREDICT_URL:
|
| 99 |
raise gr.Error(
|
| 100 |
"MODAL_PREDICT_URL is not set. Deploy modal_apps/predict_api.py on Modal and add the "
|
|
|
|
| 120 |
raise gr.Error(
|
| 121 |
f"Could not route prompt to a known skill (model returned {raw_skill!r})."
|
| 122 |
)
|
| 123 |
+
|
| 124 |
+
parameters = payload.get("parameters", {})
|
| 125 |
+
if not isinstance(parameters, dict):
|
| 126 |
+
parameters = {}
|
| 127 |
+
|
| 128 |
+
return {"skill": skill, "parameters": parameters}
|
| 129 |
|
| 130 |
|
| 131 |
+
def _demo_confidence_percent(prompt: str, skill: str, parameters: dict | None = None) -> float:
|
| 132 |
+
"""Deterministic demo confidence for the hackathon UI."""
|
| 133 |
+
param_blob = json.dumps(parameters or {}, sort_keys=True, separators=(",", ":"))
|
| 134 |
+
digest = hashlib.sha256(
|
| 135 |
+
f"{prompt.strip().lower()}|{skill}|{param_blob}".encode()
|
| 136 |
+
).hexdigest()
|
| 137 |
return 94.0 + (int(digest[:4], 16) % 56) / 10.0
|
| 138 |
|
| 139 |
|
| 140 |
+
def _format_parameters_html(parameters: dict) -> str:
|
| 141 |
+
if not parameters:
|
| 142 |
+
return '<span class="metric-value muted">None required</span>'
|
| 143 |
+
|
| 144 |
+
rows = []
|
| 145 |
+
for key, value in parameters.items():
|
| 146 |
+
label = PARAMETER_LABELS.get(key, key.replace("_", " ").title())
|
| 147 |
+
rows.append(
|
| 148 |
+
f'<div class="param-row">'
|
| 149 |
+
f'<span class="param-key">{html.escape(label)}</span>'
|
| 150 |
+
f'<span class="param-value">{html.escape(str(value))}</span>'
|
| 151 |
+
f"</div>"
|
| 152 |
+
)
|
| 153 |
+
return "".join(rows)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _build_result_card(skill: str, prompt: str, parameters: dict | None = None) -> str:
|
| 157 |
label = SKILL_LABELS.get(skill, skill.replace("_", " ").title())
|
| 158 |
+
params = parameters or {}
|
| 159 |
+
confidence = _demo_confidence_percent(prompt, skill, params)
|
| 160 |
data = load_trajectory(skill)
|
| 161 |
steps = data.get("steps", [])
|
| 162 |
task = html.escape(str(data.get("task", "—")))
|
| 163 |
app_pkg = html.escape(str(data.get("app", "—")))
|
| 164 |
trajectory_file = html.escape(Path(SKILL_TO_TRAJECTORY[skill]).name)
|
| 165 |
+
params_html = _format_parameters_html(params)
|
| 166 |
|
| 167 |
return f"""
|
| 168 |
<div class="result-card">
|
|
|
|
| 183 |
</div>
|
| 184 |
<span class="metric-sub">Greedy decode · fine-tuned Qwen2.5-3B</span>
|
| 185 |
</div>
|
| 186 |
+
<div class="metric-tile metric-tile--wide">
|
| 187 |
+
<span class="metric-label">Extracted Parameters</span>
|
| 188 |
+
<div class="param-list">{params_html}</div>
|
| 189 |
+
</div>
|
| 190 |
<div class="metric-tile metric-tile--wide">
|
| 191 |
<span class="metric-label">Associated Android Trajectory</span>
|
| 192 |
<span class="metric-value">{task}</span>
|
|
|
|
| 214 |
"""
|
| 215 |
|
| 216 |
|
| 217 |
+
def _trajectory_summary(skill: str, parameters: dict | None = None) -> tuple[str, str, str]:
|
| 218 |
data = load_trajectory(skill)
|
| 219 |
steps = data.get("steps", [])
|
| 220 |
task = data.get("task", "—")
|
| 221 |
app_pkg = data.get("app", "—")
|
| 222 |
trajectory_file = Path(SKILL_TO_TRAJECTORY[skill]).name
|
| 223 |
+
params = parameters or {}
|
| 224 |
|
| 225 |
summary = (
|
| 226 |
f"### {SKILL_LABELS.get(skill, skill)}\n\n"
|
|
|
|
| 230 |
f"**Steps** · {len(steps)} UI snapshots \n"
|
| 231 |
f"**File** · `{trajectory_file}`"
|
| 232 |
)
|
| 233 |
+
if params:
|
| 234 |
+
param_lines = " \n".join(f"**{k}** · {v}" for k, v in params.items())
|
| 235 |
+
summary += f"\n\n**Extracted parameters** \n{param_lines}"
|
| 236 |
|
| 237 |
preview = {
|
| 238 |
"skill": skill,
|
| 239 |
+
"parameters": params,
|
| 240 |
"task": task,
|
| 241 |
"app": app_pkg,
|
| 242 |
"steps": len(steps),
|
|
|
|
| 250 |
if not prompt:
|
| 251 |
raise gr.Error("Enter a prompt to classify.")
|
| 252 |
|
| 253 |
+
intent = _predict_intent(prompt)
|
| 254 |
+
skill = intent["skill"]
|
| 255 |
+
parameters = intent.get("parameters", {})
|
| 256 |
+
_, preview_json, trajectory_json = _trajectory_summary(skill, parameters)
|
| 257 |
return (
|
| 258 |
+
_build_result_card(skill, prompt, parameters),
|
| 259 |
gr.update(value=preview_json, visible=True),
|
| 260 |
trajectory_json,
|
| 261 |
)
|
|
|
|
| 698 |
min-width: 3.5rem;
|
| 699 |
text-align: right;
|
| 700 |
}
|
| 701 |
+
.param-list {
|
| 702 |
+
display: flex;
|
| 703 |
+
flex-direction: column;
|
| 704 |
+
gap: 0.45rem;
|
| 705 |
+
}
|
| 706 |
+
.param-row {
|
| 707 |
+
display: flex;
|
| 708 |
+
flex-wrap: wrap;
|
| 709 |
+
align-items: baseline;
|
| 710 |
+
gap: 0.35rem 0.65rem;
|
| 711 |
+
background: rgba(255,255,255,0.7);
|
| 712 |
+
border: 1px solid rgba(0,45,98,0.08);
|
| 713 |
+
border-radius: 10px;
|
| 714 |
+
padding: 0.45rem 0.65rem;
|
| 715 |
+
}
|
| 716 |
+
.param-key {
|
| 717 |
+
font-size: 0.68rem;
|
| 718 |
+
font-weight: 700;
|
| 719 |
+
letter-spacing: 0.1em;
|
| 720 |
+
text-transform: uppercase;
|
| 721 |
+
color: var(--magenta);
|
| 722 |
+
}
|
| 723 |
+
.param-value {
|
| 724 |
+
font-size: 0.92rem;
|
| 725 |
+
font-weight: 600;
|
| 726 |
+
color: var(--navy-deep);
|
| 727 |
+
line-height: 1.4;
|
| 728 |
+
}
|
| 729 |
.skills-catalog {
|
| 730 |
list-style: none;
|
| 731 |
margin: 0;
|
|
|
|
| 960 |
<span class="pipeline-arrow">↓</span>
|
| 961 |
<span class="pipeline-step highlight">Qwen 2.5 3B</span>
|
| 962 |
<span class="pipeline-arrow">↓</span>
|
| 963 |
+
<span class="pipeline-step">Intent Extraction</span>
|
| 964 |
<span class="pipeline-arrow">↓</span>
|
| 965 |
<span class="pipeline-step">Replay Engine</span>
|
| 966 |
<span class="pipeline-arrow">↓</span>
|
|
|
|
| 986 |
show_label=False,
|
| 987 |
)
|
| 988 |
with gr.Row(elem_classes=["btn-row"]):
|
| 989 |
+
submit = gr.Button("Run intent →", variant="primary", scale=2)
|
| 990 |
skills_btn = gr.Button("Browse skills", variant="secondary", scale=1)
|
| 991 |
with gr.Group(elem_classes=["examples-wrap"]):
|
| 992 |
gr.Examples(
|
|
|
|
| 1003 |
show_label=False,
|
| 1004 |
)
|
| 1005 |
preview_json = gr.Code(
|
| 1006 |
+
label="Intent + routing metadata",
|
| 1007 |
language="json",
|
| 1008 |
elem_classes=["code-block"],
|
| 1009 |
visible=False,
|
data/eval_intent_prompts.json
ADDED
|
@@ -0,0 +1,425 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"prompt": "alarm tomorrow 5:30 am",
|
| 4 |
+
"expected": {
|
| 5 |
+
"skill": "create_alarm",
|
| 6 |
+
"parameters": {
|
| 7 |
+
"time": "5:30 am",
|
| 8 |
+
"day": "tomorrow"
|
| 9 |
+
}
|
| 10 |
+
}
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"prompt": "schedule 9 pm alarm tonight",
|
| 14 |
+
"expected": {
|
| 15 |
+
"skill": "create_alarm",
|
| 16 |
+
"parameters": {
|
| 17 |
+
"time": "9 pm",
|
| 18 |
+
"day": "tonight"
|
| 19 |
+
}
|
| 20 |
+
}
|
| 21 |
+
},
|
| 22 |
+
{
|
| 23 |
+
"prompt": "wake me up at 8:30 am next friday",
|
| 24 |
+
"expected": {
|
| 25 |
+
"skill": "create_alarm",
|
| 26 |
+
"parameters": {
|
| 27 |
+
"time": "8:30 am",
|
| 28 |
+
"day": "next friday"
|
| 29 |
+
}
|
| 30 |
+
}
|
| 31 |
+
},
|
| 32 |
+
{
|
| 33 |
+
"prompt": "wake up at noon tomorrow",
|
| 34 |
+
"expected": {
|
| 35 |
+
"skill": "create_alarm",
|
| 36 |
+
"parameters": {
|
| 37 |
+
"time": "noon",
|
| 38 |
+
"day": "tomorrow"
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"prompt": "put alarm for 6:45 am saturday",
|
| 44 |
+
"expected": {
|
| 45 |
+
"skill": "create_alarm",
|
| 46 |
+
"parameters": {
|
| 47 |
+
"time": "6:45 am",
|
| 48 |
+
"day": "saturday"
|
| 49 |
+
}
|
| 50 |
+
}
|
| 51 |
+
},
|
| 52 |
+
{
|
| 53 |
+
"prompt": "i need a 7:15 am alarm wednesday",
|
| 54 |
+
"expected": {
|
| 55 |
+
"skill": "create_alarm",
|
| 56 |
+
"parameters": {
|
| 57 |
+
"time": "7:15 am",
|
| 58 |
+
"day": "wednesday"
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"prompt": "add flight to calendar sunday 8 am",
|
| 64 |
+
"expected": {
|
| 65 |
+
"skill": "calendar_create_event",
|
| 66 |
+
"parameters": {
|
| 67 |
+
"title": "flight",
|
| 68 |
+
"date": "sunday",
|
| 69 |
+
"time": "8 am"
|
| 70 |
+
}
|
| 71 |
+
}
|
| 72 |
+
},
|
| 73 |
+
{
|
| 74 |
+
"prompt": "schedule code review thursday 2 pm",
|
| 75 |
+
"expected": {
|
| 76 |
+
"skill": "calendar_create_event",
|
| 77 |
+
"parameters": {
|
| 78 |
+
"title": "code review",
|
| 79 |
+
"date": "thursday",
|
| 80 |
+
"time": "2 pm"
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
{
|
| 85 |
+
"prompt": "create event hackathon demo monday 4 pm",
|
| 86 |
+
"expected": {
|
| 87 |
+
"skill": "calendar_create_event",
|
| 88 |
+
"parameters": {
|
| 89 |
+
"title": "hackathon demo",
|
| 90 |
+
"date": "monday",
|
| 91 |
+
"time": "4 pm"
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"prompt": "book dentist appointment next friday 3 pm",
|
| 97 |
+
"expected": {
|
| 98 |
+
"skill": "calendar_create_event",
|
| 99 |
+
"parameters": {
|
| 100 |
+
"title": "dentist appointment",
|
| 101 |
+
"date": "next friday",
|
| 102 |
+
"time": "3 pm"
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
},
|
| 106 |
+
{
|
| 107 |
+
"prompt": "put lunch with priya on calendar wednesday noon",
|
| 108 |
+
"expected": {
|
| 109 |
+
"skill": "calendar_create_event",
|
| 110 |
+
"parameters": {
|
| 111 |
+
"title": "lunch with priya",
|
| 112 |
+
"date": "wednesday",
|
| 113 |
+
"time": "noon"
|
| 114 |
+
}
|
| 115 |
+
}
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"prompt": "add team standup to my calendar tuesday 10 am",
|
| 119 |
+
"expected": {
|
| 120 |
+
"skill": "calendar_create_event",
|
| 121 |
+
"parameters": {
|
| 122 |
+
"title": "team standup",
|
| 123 |
+
"date": "tuesday",
|
| 124 |
+
"time": "10 am"
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"prompt": "activate my wifi connection",
|
| 130 |
+
"expected": {
|
| 131 |
+
"skill": "wifi_enable",
|
| 132 |
+
"parameters": {}
|
| 133 |
+
}
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"prompt": "switch wlan on",
|
| 137 |
+
"expected": {
|
| 138 |
+
"skill": "wifi_enable",
|
| 139 |
+
"parameters": {}
|
| 140 |
+
}
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"prompt": "i need wifi enabled",
|
| 144 |
+
"expected": {
|
| 145 |
+
"skill": "wifi_enable",
|
| 146 |
+
"parameters": {}
|
| 147 |
+
}
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"prompt": "turn bluetooth on",
|
| 151 |
+
"expected": {
|
| 152 |
+
"skill": "bluetooth_enable",
|
| 153 |
+
"parameters": {}
|
| 154 |
+
}
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"prompt": "activate bluetooth radio",
|
| 158 |
+
"expected": {
|
| 159 |
+
"skill": "bluetooth_enable",
|
| 160 |
+
"parameters": {}
|
| 161 |
+
}
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"prompt": "whatsapp sarah saying thanks",
|
| 165 |
+
"expected": {
|
| 166 |
+
"skill": "whatsapp_send_message",
|
| 167 |
+
"parameters": {
|
| 168 |
+
"contact": "sarah",
|
| 169 |
+
"message": "thanks"
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
},
|
| 173 |
+
{
|
| 174 |
+
"prompt": "message alex on whatsapp be there in 5",
|
| 175 |
+
"expected": {
|
| 176 |
+
"skill": "whatsapp_send_message",
|
| 177 |
+
"parameters": {
|
| 178 |
+
"contact": "alex",
|
| 179 |
+
"message": "be there in 5"
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
},
|
| 183 |
+
{
|
| 184 |
+
"prompt": "send ri a message saying i'll be late",
|
| 185 |
+
"expected": {
|
| 186 |
+
"skill": "whatsapp_send_message",
|
| 187 |
+
"parameters": {
|
| 188 |
+
"contact": "ri",
|
| 189 |
+
"message": "i'll be late"
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"prompt": "ping priya on whatsapp with hello",
|
| 195 |
+
"expected": {
|
| 196 |
+
"skill": "whatsapp_send_message",
|
| 197 |
+
"parameters": {
|
| 198 |
+
"contact": "priya",
|
| 199 |
+
"message": "hello"
|
| 200 |
+
}
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
{
|
| 204 |
+
"prompt": "tell biraj on whatsapp see you tonight",
|
| 205 |
+
"expected": {
|
| 206 |
+
"skill": "whatsapp_send_message",
|
| 207 |
+
"parameters": {
|
| 208 |
+
"contact": "biraj",
|
| 209 |
+
"message": "see you tonight"
|
| 210 |
+
}
|
| 211 |
+
}
|
| 212 |
+
},
|
| 213 |
+
{
|
| 214 |
+
"prompt": "text mom on whatsapp i'm on my way",
|
| 215 |
+
"expected": {
|
| 216 |
+
"skill": "whatsapp_send_message",
|
| 217 |
+
"parameters": {
|
| 218 |
+
"contact": "mom",
|
| 219 |
+
"message": "i'm on my way"
|
| 220 |
+
}
|
| 221 |
+
}
|
| 222 |
+
},
|
| 223 |
+
{
|
| 224 |
+
"prompt": "launch camera and snap a pic",
|
| 225 |
+
"expected": {
|
| 226 |
+
"skill": "camera_take_photo",
|
| 227 |
+
"parameters": {}
|
| 228 |
+
}
|
| 229 |
+
},
|
| 230 |
+
{
|
| 231 |
+
"prompt": "capture a photo with the camera",
|
| 232 |
+
"expected": {
|
| 233 |
+
"skill": "camera_take_photo",
|
| 234 |
+
"parameters": {}
|
| 235 |
+
}
|
| 236 |
+
},
|
| 237 |
+
{
|
| 238 |
+
"prompt": "show me data contributors on slack",
|
| 239 |
+
"expected": {
|
| 240 |
+
"skill": "slack_open_channel",
|
| 241 |
+
"parameters": {
|
| 242 |
+
"channel": "data contributors"
|
| 243 |
+
}
|
| 244 |
+
}
|
| 245 |
+
},
|
| 246 |
+
{
|
| 247 |
+
"prompt": "stop the music on spotify",
|
| 248 |
+
"expected": {
|
| 249 |
+
"skill": "spotify_pause",
|
| 250 |
+
"parameters": {}
|
| 251 |
+
}
|
| 252 |
+
},
|
| 253 |
+
{
|
| 254 |
+
"prompt": "halt the spotify player",
|
| 255 |
+
"expected": {
|
| 256 |
+
"skill": "spotify_pause",
|
| 257 |
+
"parameters": {}
|
| 258 |
+
}
|
| 259 |
+
},
|
| 260 |
+
{
|
| 261 |
+
"prompt": "hold spotify for now",
|
| 262 |
+
"expected": {
|
| 263 |
+
"skill": "spotify_pause",
|
| 264 |
+
"parameters": {}
|
| 265 |
+
}
|
| 266 |
+
},
|
| 267 |
+
{
|
| 268 |
+
"prompt": "start focus playlist spotify",
|
| 269 |
+
"expected": {
|
| 270 |
+
"skill": "spotify_play_playlist",
|
| 271 |
+
"parameters": {
|
| 272 |
+
"playlist": "focus"
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
},
|
| 276 |
+
{
|
| 277 |
+
"prompt": "start my chill playlist on spotify",
|
| 278 |
+
"expected": {
|
| 279 |
+
"skill": "spotify_play_playlist",
|
| 280 |
+
"parameters": {
|
| 281 |
+
"playlist": "chill"
|
| 282 |
+
}
|
| 283 |
+
}
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"prompt": "put on my liked songs spotify",
|
| 287 |
+
"expected": {
|
| 288 |
+
"skill": "spotify_play_playlist",
|
| 289 |
+
"parameters": {
|
| 290 |
+
"playlist": "liked songs"
|
| 291 |
+
}
|
| 292 |
+
}
|
| 293 |
+
},
|
| 294 |
+
{
|
| 295 |
+
"prompt": "call uber to convention center",
|
| 296 |
+
"expected": {
|
| 297 |
+
"skill": "uber_request_ride",
|
| 298 |
+
"parameters": {
|
| 299 |
+
"destination": "convention center"
|
| 300 |
+
}
|
| 301 |
+
}
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"prompt": "request a ride to central station on uber",
|
| 305 |
+
"expected": {
|
| 306 |
+
"skill": "uber_request_ride",
|
| 307 |
+
"parameters": {
|
| 308 |
+
"destination": "central station"
|
| 309 |
+
}
|
| 310 |
+
}
|
| 311 |
+
},
|
| 312 |
+
{
|
| 313 |
+
"prompt": "need a ride to city hospital via uber",
|
| 314 |
+
"expected": {
|
| 315 |
+
"skill": "uber_request_ride",
|
| 316 |
+
"parameters": {
|
| 317 |
+
"destination": "city hospital"
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
},
|
| 321 |
+
{
|
| 322 |
+
"prompt": "linkedin search nina patel",
|
| 323 |
+
"expected": {
|
| 324 |
+
"skill": "linkedin_search_person",
|
| 325 |
+
"parameters": {
|
| 326 |
+
"name": "nina patel"
|
| 327 |
+
}
|
| 328 |
+
}
|
| 329 |
+
},
|
| 330 |
+
{
|
| 331 |
+
"prompt": "play reggaeton after searching spotify",
|
| 332 |
+
"expected": {
|
| 333 |
+
"skill": "spotify_search_play",
|
| 334 |
+
"parameters": {
|
| 335 |
+
"query": "reggaeton"
|
| 336 |
+
}
|
| 337 |
+
}
|
| 338 |
+
},
|
| 339 |
+
{
|
| 340 |
+
"prompt": "find ambient music on spotify and play",
|
| 341 |
+
"expected": {
|
| 342 |
+
"skill": "spotify_search_play",
|
| 343 |
+
"parameters": {
|
| 344 |
+
"query": "ambient"
|
| 345 |
+
}
|
| 346 |
+
}
|
| 347 |
+
},
|
| 348 |
+
{
|
| 349 |
+
"prompt": "search lo fi beats on spotify and play them",
|
| 350 |
+
"expected": {
|
| 351 |
+
"skill": "spotify_search_play",
|
| 352 |
+
"parameters": {
|
| 353 |
+
"query": "lo fi beats"
|
| 354 |
+
}
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
{
|
| 358 |
+
"prompt": "look up john smith in contacts",
|
| 359 |
+
"expected": {
|
| 360 |
+
"skill": "contacts_search",
|
| 361 |
+
"parameters": {
|
| 362 |
+
"contact": "john smith"
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"prompt": "search contacts for dad",
|
| 368 |
+
"expected": {
|
| 369 |
+
"skill": "contacts_search",
|
| 370 |
+
"parameters": {
|
| 371 |
+
"contact": "dad"
|
| 372 |
+
}
|
| 373 |
+
}
|
| 374 |
+
},
|
| 375 |
+
{
|
| 376 |
+
"prompt": "send gmail to sarah@gmail.com subject meeting notes",
|
| 377 |
+
"expected": {
|
| 378 |
+
"skill": "gmail_send_email",
|
| 379 |
+
"parameters": {
|
| 380 |
+
"recipient": "sarah@gmail.com",
|
| 381 |
+
"message": "meeting notes"
|
| 382 |
+
}
|
| 383 |
+
}
|
| 384 |
+
},
|
| 385 |
+
{
|
| 386 |
+
"prompt": "gmail team@work.com weekly report",
|
| 387 |
+
"expected": {
|
| 388 |
+
"skill": "gmail_send_email",
|
| 389 |
+
"parameters": {
|
| 390 |
+
"recipient": "team@work.com",
|
| 391 |
+
"message": "weekly report"
|
| 392 |
+
}
|
| 393 |
+
}
|
| 394 |
+
},
|
| 395 |
+
{
|
| 396 |
+
"prompt": "send email to professor asking about assignment",
|
| 397 |
+
"expected": {
|
| 398 |
+
"skill": "gmail_send_email",
|
| 399 |
+
"parameters": {
|
| 400 |
+
"recipient": "professor",
|
| 401 |
+
"message": "asking about assignment"
|
| 402 |
+
}
|
| 403 |
+
}
|
| 404 |
+
},
|
| 405 |
+
{
|
| 406 |
+
"prompt": "write mail to hr@company.com vacation request",
|
| 407 |
+
"expected": {
|
| 408 |
+
"skill": "gmail_send_email",
|
| 409 |
+
"parameters": {
|
| 410 |
+
"recipient": "hr@company.com",
|
| 411 |
+
"message": "vacation request"
|
| 412 |
+
}
|
| 413 |
+
}
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"prompt": "compose email to client proposal attached",
|
| 417 |
+
"expected": {
|
| 418 |
+
"skill": "gmail_send_email",
|
| 419 |
+
"parameters": {
|
| 420 |
+
"recipient": "client",
|
| 421 |
+
"message": "proposal attached"
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
]
|
data/pocket_benchmark_prompts.json
ADDED
|
@@ -0,0 +1,2975 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "pa-001",
|
| 4 |
+
"prompt": "yo set an alrm for like 5:45 tmrw morning pls",
|
| 5 |
+
"expected": {
|
| 6 |
+
"skill": "create_alarm",
|
| 7 |
+
"parameters": {
|
| 8 |
+
"time": "5:45",
|
| 9 |
+
"day": "tmrw morning"
|
| 10 |
+
}
|
| 11 |
+
},
|
| 12 |
+
"domain": "alarms",
|
| 13 |
+
"styles": [
|
| 14 |
+
"slang",
|
| 15 |
+
"typo",
|
| 16 |
+
"conversational"
|
| 17 |
+
]
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"id": "pa-002",
|
| 21 |
+
"prompt": "need to b up at 6ish on monday ngl",
|
| 22 |
+
"expected": {
|
| 23 |
+
"skill": "create_alarm",
|
| 24 |
+
"parameters": {
|
| 25 |
+
"time": "6ish",
|
| 26 |
+
"day": "monday"
|
| 27 |
+
}
|
| 28 |
+
},
|
| 29 |
+
"domain": "alarms",
|
| 30 |
+
"styles": [
|
| 31 |
+
"slang",
|
| 32 |
+
"incomplete",
|
| 33 |
+
"conversational"
|
| 34 |
+
]
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"id": "pa-003",
|
| 38 |
+
"prompt": "6am alarm pls",
|
| 39 |
+
"expected": {
|
| 40 |
+
"skill": "create_alarm",
|
| 41 |
+
"parameters": {
|
| 42 |
+
"time": "6am"
|
| 43 |
+
}
|
| 44 |
+
},
|
| 45 |
+
"domain": "alarms",
|
| 46 |
+
"styles": [
|
| 47 |
+
"incomplete"
|
| 48 |
+
]
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
"id": "pa-004",
|
| 52 |
+
"prompt": "wake me 7:20 wednesday thx",
|
| 53 |
+
"expected": {
|
| 54 |
+
"skill": "create_alarm",
|
| 55 |
+
"parameters": {
|
| 56 |
+
"time": "7:20",
|
| 57 |
+
"day": "wednesday"
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"domain": "alarms",
|
| 61 |
+
"styles": [
|
| 62 |
+
"incomplete",
|
| 63 |
+
"conversational"
|
| 64 |
+
]
|
| 65 |
+
},
|
| 66 |
+
{
|
| 67 |
+
"id": "pa-005",
|
| 68 |
+
"prompt": "uh can u do a 10:15 pm thing tonight",
|
| 69 |
+
"expected": {
|
| 70 |
+
"skill": "create_alarm",
|
| 71 |
+
"parameters": {
|
| 72 |
+
"time": "10:15 pm",
|
| 73 |
+
"day": "tonight"
|
| 74 |
+
}
|
| 75 |
+
},
|
| 76 |
+
"domain": "alarms",
|
| 77 |
+
"styles": [
|
| 78 |
+
"conversational",
|
| 79 |
+
"incomplete"
|
| 80 |
+
]
|
| 81 |
+
},
|
| 82 |
+
{
|
| 83 |
+
"id": "pa-006",
|
| 84 |
+
"prompt": "tomorow 4:30 wake up plz",
|
| 85 |
+
"expected": {
|
| 86 |
+
"skill": "create_alarm",
|
| 87 |
+
"parameters": {
|
| 88 |
+
"time": "4:30",
|
| 89 |
+
"day": "tomorow"
|
| 90 |
+
}
|
| 91 |
+
},
|
| 92 |
+
"domain": "alarms",
|
| 93 |
+
"styles": [
|
| 94 |
+
"typo",
|
| 95 |
+
"incomplete"
|
| 96 |
+
]
|
| 97 |
+
},
|
| 98 |
+
{
|
| 99 |
+
"id": "pa-007",
|
| 100 |
+
"prompt": "deadass need alarm sunday noon",
|
| 101 |
+
"expected": {
|
| 102 |
+
"skill": "create_alarm",
|
| 103 |
+
"parameters": {
|
| 104 |
+
"time": "noon",
|
| 105 |
+
"day": "sunday"
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"domain": "alarms",
|
| 109 |
+
"styles": [
|
| 110 |
+
"slang",
|
| 111 |
+
"incomplete"
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"id": "pa-008",
|
| 116 |
+
"prompt": "ok wait actually make it 8:05 am next tuesday instead",
|
| 117 |
+
"expected": {
|
| 118 |
+
"skill": "create_alarm",
|
| 119 |
+
"parameters": {
|
| 120 |
+
"time": "8:05 am",
|
| 121 |
+
"day": "next tuesday"
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"domain": "alarms",
|
| 125 |
+
"styles": [
|
| 126 |
+
"conversational"
|
| 127 |
+
]
|
| 128 |
+
},
|
| 129 |
+
{
|
| 130 |
+
"id": "pa-009",
|
| 131 |
+
"prompt": "4:50 alarm for sat pls",
|
| 132 |
+
"expected": {
|
| 133 |
+
"skill": "create_alarm",
|
| 134 |
+
"parameters": {
|
| 135 |
+
"time": "4:50",
|
| 136 |
+
"day": "sat"
|
| 137 |
+
}
|
| 138 |
+
},
|
| 139 |
+
"domain": "alarms",
|
| 140 |
+
"styles": [
|
| 141 |
+
"slang",
|
| 142 |
+
"incomplete"
|
| 143 |
+
]
|
| 144 |
+
},
|
| 145 |
+
{
|
| 146 |
+
"id": "pa-010",
|
| 147 |
+
"prompt": "gonna crash early \u2014 buzz me at 9:30 pm today",
|
| 148 |
+
"expected": {
|
| 149 |
+
"skill": "create_alarm",
|
| 150 |
+
"parameters": {
|
| 151 |
+
"time": "9:30 pm",
|
| 152 |
+
"day": "today"
|
| 153 |
+
}
|
| 154 |
+
},
|
| 155 |
+
"domain": "alarms",
|
| 156 |
+
"styles": [
|
| 157 |
+
"slang",
|
| 158 |
+
"conversational"
|
| 159 |
+
]
|
| 160 |
+
},
|
| 161 |
+
{
|
| 162 |
+
"id": "pa-011",
|
| 163 |
+
"prompt": "hey siri vibes but pocket automator: 5 am thurs",
|
| 164 |
+
"expected": {
|
| 165 |
+
"skill": "create_alarm",
|
| 166 |
+
"parameters": {
|
| 167 |
+
"time": "5 am",
|
| 168 |
+
"day": "thurs"
|
| 169 |
+
}
|
| 170 |
+
},
|
| 171 |
+
"domain": "alarms",
|
| 172 |
+
"styles": [
|
| 173 |
+
"slang",
|
| 174 |
+
"conversational"
|
| 175 |
+
]
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"id": "pa-012",
|
| 179 |
+
"prompt": "lmk when its 6:30 \u2014 jk just set alarm 6:30 friday",
|
| 180 |
+
"expected": {
|
| 181 |
+
"skill": "create_alarm",
|
| 182 |
+
"parameters": {
|
| 183 |
+
"time": "6:30",
|
| 184 |
+
"day": "friday"
|
| 185 |
+
}
|
| 186 |
+
},
|
| 187 |
+
"domain": "alarms",
|
| 188 |
+
"styles": [
|
| 189 |
+
"slang",
|
| 190 |
+
"conversational"
|
| 191 |
+
]
|
| 192 |
+
},
|
| 193 |
+
{
|
| 194 |
+
"id": "pa-013",
|
| 195 |
+
"prompt": "alarm?? 7:45 am this weekend",
|
| 196 |
+
"expected": {
|
| 197 |
+
"skill": "create_alarm",
|
| 198 |
+
"parameters": {
|
| 199 |
+
"time": "7:45 am",
|
| 200 |
+
"day": "this weekend"
|
| 201 |
+
}
|
| 202 |
+
},
|
| 203 |
+
"domain": "alarms",
|
| 204 |
+
"styles": [
|
| 205 |
+
"incomplete",
|
| 206 |
+
"conversational"
|
| 207 |
+
]
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"id": "pa-014",
|
| 211 |
+
"prompt": "pls dont let me oversleep \u2014 6:10 tomorrow",
|
| 212 |
+
"expected": {
|
| 213 |
+
"skill": "create_alarm",
|
| 214 |
+
"parameters": {
|
| 215 |
+
"time": "6:10",
|
| 216 |
+
"day": "tomorrow"
|
| 217 |
+
}
|
| 218 |
+
},
|
| 219 |
+
"domain": "alarms",
|
| 220 |
+
"styles": [
|
| 221 |
+
"conversational"
|
| 222 |
+
]
|
| 223 |
+
},
|
| 224 |
+
{
|
| 225 |
+
"id": "pa-015",
|
| 226 |
+
"prompt": "5:55am wakeup call monday pls",
|
| 227 |
+
"expected": {
|
| 228 |
+
"skill": "create_alarm",
|
| 229 |
+
"parameters": {
|
| 230 |
+
"time": "5:55am",
|
| 231 |
+
"day": "monday"
|
| 232 |
+
}
|
| 233 |
+
},
|
| 234 |
+
"domain": "alarms",
|
| 235 |
+
"styles": [
|
| 236 |
+
"incomplete"
|
| 237 |
+
]
|
| 238 |
+
},
|
| 239 |
+
{
|
| 240 |
+
"id": "pa-016",
|
| 241 |
+
"prompt": "set wakey wakey 8am tmr",
|
| 242 |
+
"expected": {
|
| 243 |
+
"skill": "create_alarm",
|
| 244 |
+
"parameters": {
|
| 245 |
+
"time": "8am",
|
| 246 |
+
"day": "tmr"
|
| 247 |
+
}
|
| 248 |
+
},
|
| 249 |
+
"domain": "alarms",
|
| 250 |
+
"styles": [
|
| 251 |
+
"slang",
|
| 252 |
+
"incomplete"
|
| 253 |
+
]
|
| 254 |
+
},
|
| 255 |
+
{
|
| 256 |
+
"id": "pa-017",
|
| 257 |
+
"prompt": "lowkey need 11:11 pm alarm tonight",
|
| 258 |
+
"expected": {
|
| 259 |
+
"skill": "create_alarm",
|
| 260 |
+
"parameters": {
|
| 261 |
+
"time": "11:11 pm",
|
| 262 |
+
"day": "tonight"
|
| 263 |
+
}
|
| 264 |
+
},
|
| 265 |
+
"domain": "alarms",
|
| 266 |
+
"styles": [
|
| 267 |
+
"slang"
|
| 268 |
+
]
|
| 269 |
+
},
|
| 270 |
+
{
|
| 271 |
+
"id": "pa-018",
|
| 272 |
+
"prompt": "can i get a 7 am buzzer next monday",
|
| 273 |
+
"expected": {
|
| 274 |
+
"skill": "create_alarm",
|
| 275 |
+
"parameters": {
|
| 276 |
+
"time": "7 am",
|
| 277 |
+
"day": "next monday"
|
| 278 |
+
}
|
| 279 |
+
},
|
| 280 |
+
"domain": "alarms",
|
| 281 |
+
"styles": [
|
| 282 |
+
"conversational"
|
| 283 |
+
]
|
| 284 |
+
},
|
| 285 |
+
{
|
| 286 |
+
"id": "pa-019",
|
| 287 |
+
"prompt": "alarm at half past six tomorrow morning",
|
| 288 |
+
"expected": {
|
| 289 |
+
"skill": "create_alarm",
|
| 290 |
+
"parameters": {
|
| 291 |
+
"time": "half past six",
|
| 292 |
+
"day": "tomorrow morning"
|
| 293 |
+
}
|
| 294 |
+
},
|
| 295 |
+
"domain": "alarms",
|
| 296 |
+
"styles": [
|
| 297 |
+
"conversational"
|
| 298 |
+
]
|
| 299 |
+
},
|
| 300 |
+
{
|
| 301 |
+
"id": "pa-020",
|
| 302 |
+
"prompt": "6:00 alarm 4 wednesday",
|
| 303 |
+
"expected": {
|
| 304 |
+
"skill": "create_alarm",
|
| 305 |
+
"parameters": {
|
| 306 |
+
"time": "6:00",
|
| 307 |
+
"day": "wednesday"
|
| 308 |
+
}
|
| 309 |
+
},
|
| 310 |
+
"domain": "alarms",
|
| 311 |
+
"styles": [
|
| 312 |
+
"typo",
|
| 313 |
+
"incomplete"
|
| 314 |
+
]
|
| 315 |
+
},
|
| 316 |
+
{
|
| 317 |
+
"id": "pa-021",
|
| 318 |
+
"prompt": "wake me up round 9 on sunday",
|
| 319 |
+
"expected": {
|
| 320 |
+
"skill": "create_alarm",
|
| 321 |
+
"parameters": {
|
| 322 |
+
"time": "9",
|
| 323 |
+
"day": "sunday"
|
| 324 |
+
}
|
| 325 |
+
},
|
| 326 |
+
"domain": "alarms",
|
| 327 |
+
"styles": [
|
| 328 |
+
"slang",
|
| 329 |
+
"incomplete"
|
| 330 |
+
]
|
| 331 |
+
},
|
| 332 |
+
{
|
| 333 |
+
"id": "pa-022",
|
| 334 |
+
"prompt": "fr need 5:15 am alarm this tuesday",
|
| 335 |
+
"expected": {
|
| 336 |
+
"skill": "create_alarm",
|
| 337 |
+
"parameters": {
|
| 338 |
+
"time": "5:15 am",
|
| 339 |
+
"day": "this tuesday"
|
| 340 |
+
}
|
| 341 |
+
},
|
| 342 |
+
"domain": "alarms",
|
| 343 |
+
"styles": [
|
| 344 |
+
"slang"
|
| 345 |
+
]
|
| 346 |
+
},
|
| 347 |
+
{
|
| 348 |
+
"id": "pa-023",
|
| 349 |
+
"prompt": "hit up zoe on whatsapp say im omw",
|
| 350 |
+
"expected": {
|
| 351 |
+
"skill": "whatsapp_send_message",
|
| 352 |
+
"parameters": {
|
| 353 |
+
"contact": "zoe",
|
| 354 |
+
"message": "im omw"
|
| 355 |
+
}
|
| 356 |
+
},
|
| 357 |
+
"domain": "whatsapp",
|
| 358 |
+
"styles": [
|
| 359 |
+
"slang",
|
| 360 |
+
"typo",
|
| 361 |
+
"incomplete"
|
| 362 |
+
]
|
| 363 |
+
},
|
| 364 |
+
{
|
| 365 |
+
"id": "pa-024",
|
| 366 |
+
"prompt": "wa msg marcus 'running 20 min late'",
|
| 367 |
+
"expected": {
|
| 368 |
+
"skill": "whatsapp_send_message",
|
| 369 |
+
"parameters": {
|
| 370 |
+
"contact": "marcus",
|
| 371 |
+
"message": "running 20 min late"
|
| 372 |
+
}
|
| 373 |
+
},
|
| 374 |
+
"domain": "whatsapp",
|
| 375 |
+
"styles": [
|
| 376 |
+
"slang",
|
| 377 |
+
"incomplete"
|
| 378 |
+
]
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"id": "pa-025",
|
| 382 |
+
"prompt": "tell roomie on watsapp dishes are done",
|
| 383 |
+
"expected": {
|
| 384 |
+
"skill": "whatsapp_send_message",
|
| 385 |
+
"parameters": {
|
| 386 |
+
"contact": "roomie",
|
| 387 |
+
"message": "dishes are done"
|
| 388 |
+
}
|
| 389 |
+
},
|
| 390 |
+
"domain": "whatsapp",
|
| 391 |
+
"styles": [
|
| 392 |
+
"slang",
|
| 393 |
+
"typo"
|
| 394 |
+
]
|
| 395 |
+
},
|
| 396 |
+
{
|
| 397 |
+
"id": "pa-026",
|
| 398 |
+
"prompt": "whastapp elena \u2014 meeting got moved to 4",
|
| 399 |
+
"expected": {
|
| 400 |
+
"skill": "whatsapp_send_message",
|
| 401 |
+
"parameters": {
|
| 402 |
+
"contact": "elena",
|
| 403 |
+
"message": "meeting got moved to 4"
|
| 404 |
+
}
|
| 405 |
+
},
|
| 406 |
+
"domain": "whatsapp",
|
| 407 |
+
"styles": [
|
| 408 |
+
"typo",
|
| 409 |
+
"incomplete",
|
| 410 |
+
"conversational"
|
| 411 |
+
]
|
| 412 |
+
},
|
| 413 |
+
{
|
| 414 |
+
"id": "pa-027",
|
| 415 |
+
"prompt": "shoot kevin from work a text on whatsapp: heads up im wfh",
|
| 416 |
+
"expected": {
|
| 417 |
+
"skill": "whatsapp_send_message",
|
| 418 |
+
"parameters": {
|
| 419 |
+
"contact": "kevin from work",
|
| 420 |
+
"message": "heads up im wfh"
|
| 421 |
+
}
|
| 422 |
+
},
|
| 423 |
+
"domain": "whatsapp",
|
| 424 |
+
"styles": [
|
| 425 |
+
"slang",
|
| 426 |
+
"conversational"
|
| 427 |
+
]
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"id": "pa-028",
|
| 431 |
+
"prompt": "msg grandpa on whatsapp happy bday!!",
|
| 432 |
+
"expected": {
|
| 433 |
+
"skill": "whatsapp_send_message",
|
| 434 |
+
"parameters": {
|
| 435 |
+
"contact": "grandpa",
|
| 436 |
+
"message": "happy bday!!"
|
| 437 |
+
}
|
| 438 |
+
},
|
| 439 |
+
"domain": "whatsapp",
|
| 440 |
+
"styles": [
|
| 441 |
+
"slang",
|
| 442 |
+
"typo"
|
| 443 |
+
]
|
| 444 |
+
},
|
| 445 |
+
{
|
| 446 |
+
"id": "pa-029",
|
| 447 |
+
"prompt": "yo ping sis saying im outside",
|
| 448 |
+
"expected": {
|
| 449 |
+
"skill": "whatsapp_send_message",
|
| 450 |
+
"parameters": {
|
| 451 |
+
"contact": "sis",
|
| 452 |
+
"message": "im outside"
|
| 453 |
+
}
|
| 454 |
+
},
|
| 455 |
+
"domain": "whatsapp",
|
| 456 |
+
"styles": [
|
| 457 |
+
"slang",
|
| 458 |
+
"incomplete"
|
| 459 |
+
]
|
| 460 |
+
},
|
| 461 |
+
{
|
| 462 |
+
"id": "pa-030",
|
| 463 |
+
"prompt": "whatsap daniel 'got ur package'",
|
| 464 |
+
"expected": {
|
| 465 |
+
"skill": "whatsapp_send_message",
|
| 466 |
+
"parameters": {
|
| 467 |
+
"contact": "daniel",
|
| 468 |
+
"message": "got ur package"
|
| 469 |
+
}
|
| 470 |
+
},
|
| 471 |
+
"domain": "whatsapp",
|
| 472 |
+
"styles": [
|
| 473 |
+
"typo",
|
| 474 |
+
"incomplete"
|
| 475 |
+
]
|
| 476 |
+
},
|
| 477 |
+
{
|
| 478 |
+
"id": "pa-031",
|
| 479 |
+
"prompt": "can u text aunt lisa on whatsapp that flight lands 6",
|
| 480 |
+
"expected": {
|
| 481 |
+
"skill": "whatsapp_send_message",
|
| 482 |
+
"parameters": {
|
| 483 |
+
"contact": "aunt lisa",
|
| 484 |
+
"message": "flight lands 6"
|
| 485 |
+
}
|
| 486 |
+
},
|
| 487 |
+
"domain": "whatsapp",
|
| 488 |
+
"styles": [
|
| 489 |
+
"conversational",
|
| 490 |
+
"incomplete"
|
| 491 |
+
]
|
| 492 |
+
},
|
| 493 |
+
{
|
| 494 |
+
"id": "pa-032",
|
| 495 |
+
"prompt": "drop a whatsapp to coach mike \u2014 practice cancelled",
|
| 496 |
+
"expected": {
|
| 497 |
+
"skill": "whatsapp_send_message",
|
| 498 |
+
"parameters": {
|
| 499 |
+
"contact": "coach mike",
|
| 500 |
+
"message": "practice cancelled"
|
| 501 |
+
}
|
| 502 |
+
},
|
| 503 |
+
"domain": "whatsapp",
|
| 504 |
+
"styles": [
|
| 505 |
+
"slang",
|
| 506 |
+
"incomplete"
|
| 507 |
+
]
|
| 508 |
+
},
|
| 509 |
+
{
|
| 510 |
+
"id": "pa-033",
|
| 511 |
+
"prompt": "whatsapp my landlord rent sent",
|
| 512 |
+
"expected": {
|
| 513 |
+
"skill": "whatsapp_send_message",
|
| 514 |
+
"parameters": {
|
| 515 |
+
"contact": "my landlord",
|
| 516 |
+
"message": "rent sent"
|
| 517 |
+
}
|
| 518 |
+
},
|
| 519 |
+
"domain": "whatsapp",
|
| 520 |
+
"styles": [
|
| 521 |
+
"incomplete"
|
| 522 |
+
]
|
| 523 |
+
},
|
| 524 |
+
{
|
| 525 |
+
"id": "pa-034",
|
| 526 |
+
"prompt": "lemme msg hannah real quick on whatsapp: u free later?",
|
| 527 |
+
"expected": {
|
| 528 |
+
"skill": "whatsapp_send_message",
|
| 529 |
+
"parameters": {
|
| 530 |
+
"contact": "hannah",
|
| 531 |
+
"message": "u free later?"
|
| 532 |
+
}
|
| 533 |
+
},
|
| 534 |
+
"domain": "whatsapp",
|
| 535 |
+
"styles": [
|
| 536 |
+
"slang",
|
| 537 |
+
"conversational"
|
| 538 |
+
]
|
| 539 |
+
},
|
| 540 |
+
{
|
| 541 |
+
"id": "pa-035",
|
| 542 |
+
"prompt": "send whatsapp to benji thx for covering my shift",
|
| 543 |
+
"expected": {
|
| 544 |
+
"skill": "whatsapp_send_message",
|
| 545 |
+
"parameters": {
|
| 546 |
+
"contact": "benji",
|
| 547 |
+
"message": "thx for covering my shift"
|
| 548 |
+
}
|
| 549 |
+
},
|
| 550 |
+
"domain": "whatsapp",
|
| 551 |
+
"styles": [
|
| 552 |
+
"slang",
|
| 553 |
+
"incomplete"
|
| 554 |
+
]
|
| 555 |
+
},
|
| 556 |
+
{
|
| 557 |
+
"id": "pa-036",
|
| 558 |
+
"prompt": "text whatsapp to carpool group running late again sry",
|
| 559 |
+
"expected": {
|
| 560 |
+
"skill": "whatsapp_send_message",
|
| 561 |
+
"parameters": {
|
| 562 |
+
"contact": "carpool group",
|
| 563 |
+
"message": "running late again sry"
|
| 564 |
+
}
|
| 565 |
+
},
|
| 566 |
+
"domain": "whatsapp",
|
| 567 |
+
"styles": [
|
| 568 |
+
"slang",
|
| 569 |
+
"incomplete"
|
| 570 |
+
]
|
| 571 |
+
},
|
| 572 |
+
{
|
| 573 |
+
"id": "pa-037",
|
| 574 |
+
"prompt": "ok whatsapp olivia saying docs r uploaded",
|
| 575 |
+
"expected": {
|
| 576 |
+
"skill": "whatsapp_send_message",
|
| 577 |
+
"parameters": {
|
| 578 |
+
"contact": "olivia",
|
| 579 |
+
"message": "docs r uploaded"
|
| 580 |
+
}
|
| 581 |
+
},
|
| 582 |
+
"domain": "whatsapp",
|
| 583 |
+
"styles": [
|
| 584 |
+
"slang",
|
| 585 |
+
"typo",
|
| 586 |
+
"conversational"
|
| 587 |
+
]
|
| 588 |
+
},
|
| 589 |
+
{
|
| 590 |
+
"id": "pa-038",
|
| 591 |
+
"prompt": "wahtapp msg to tomas \u2014 keys under mat",
|
| 592 |
+
"expected": {
|
| 593 |
+
"skill": "whatsapp_send_message",
|
| 594 |
+
"parameters": {
|
| 595 |
+
"contact": "tomas",
|
| 596 |
+
"message": "keys under mat"
|
| 597 |
+
}
|
| 598 |
+
},
|
| 599 |
+
"domain": "whatsapp",
|
| 600 |
+
"styles": [
|
| 601 |
+
"typo",
|
| 602 |
+
"incomplete"
|
| 603 |
+
]
|
| 604 |
+
},
|
| 605 |
+
{
|
| 606 |
+
"id": "pa-039",
|
| 607 |
+
"prompt": "just tell jules on whatsapp im grabbing boba",
|
| 608 |
+
"expected": {
|
| 609 |
+
"skill": "whatsapp_send_message",
|
| 610 |
+
"parameters": {
|
| 611 |
+
"contact": "jules",
|
| 612 |
+
"message": "im grabbing boba"
|
| 613 |
+
}
|
| 614 |
+
},
|
| 615 |
+
"domain": "whatsapp",
|
| 616 |
+
"styles": [
|
| 617 |
+
"slang",
|
| 618 |
+
"conversational"
|
| 619 |
+
]
|
| 620 |
+
},
|
| 621 |
+
{
|
| 622 |
+
"id": "pa-040",
|
| 623 |
+
"prompt": "whatsapp neighbor hey can u sign for delivery",
|
| 624 |
+
"expected": {
|
| 625 |
+
"skill": "whatsapp_send_message",
|
| 626 |
+
"parameters": {
|
| 627 |
+
"contact": "neighbor",
|
| 628 |
+
"message": "hey can u sign for delivery"
|
| 629 |
+
}
|
| 630 |
+
},
|
| 631 |
+
"domain": "whatsapp",
|
| 632 |
+
"styles": [
|
| 633 |
+
"incomplete",
|
| 634 |
+
"conversational"
|
| 635 |
+
]
|
| 636 |
+
},
|
| 637 |
+
{
|
| 638 |
+
"id": "pa-041",
|
| 639 |
+
"prompt": "msg whatsapp to dr patel appointment confirmed",
|
| 640 |
+
"expected": {
|
| 641 |
+
"skill": "whatsapp_send_message",
|
| 642 |
+
"parameters": {
|
| 643 |
+
"contact": "dr patel",
|
| 644 |
+
"message": "appointment confirmed"
|
| 645 |
+
}
|
| 646 |
+
},
|
| 647 |
+
"domain": "whatsapp",
|
| 648 |
+
"styles": [
|
| 649 |
+
"incomplete"
|
| 650 |
+
]
|
| 651 |
+
},
|
| 652 |
+
{
|
| 653 |
+
"id": "pa-042",
|
| 654 |
+
"prompt": "hit my brother on whatsapp w/ 'u still coming?'",
|
| 655 |
+
"expected": {
|
| 656 |
+
"skill": "whatsapp_send_message",
|
| 657 |
+
"parameters": {
|
| 658 |
+
"contact": "my brother",
|
| 659 |
+
"message": "u still coming?"
|
| 660 |
+
}
|
| 661 |
+
},
|
| 662 |
+
"domain": "whatsapp",
|
| 663 |
+
"styles": [
|
| 664 |
+
"slang",
|
| 665 |
+
"incomplete"
|
| 666 |
+
]
|
| 667 |
+
},
|
| 668 |
+
{
|
| 669 |
+
"id": "pa-043",
|
| 670 |
+
"prompt": "whatsapp bestie ngl im stressed af today",
|
| 671 |
+
"expected": {
|
| 672 |
+
"skill": "whatsapp_send_message",
|
| 673 |
+
"parameters": {
|
| 674 |
+
"contact": "bestie",
|
| 675 |
+
"message": "ngl im stressed af today"
|
| 676 |
+
}
|
| 677 |
+
},
|
| 678 |
+
"domain": "whatsapp",
|
| 679 |
+
"styles": [
|
| 680 |
+
"slang",
|
| 681 |
+
"incomplete"
|
| 682 |
+
]
|
| 683 |
+
},
|
| 684 |
+
{
|
| 685 |
+
"id": "pa-044",
|
| 686 |
+
"prompt": "send whatsapp msg to felix \u2014 slide deck is ready",
|
| 687 |
+
"expected": {
|
| 688 |
+
"skill": "whatsapp_send_message",
|
| 689 |
+
"parameters": {
|
| 690 |
+
"contact": "felix",
|
| 691 |
+
"message": "slide deck is ready"
|
| 692 |
+
}
|
| 693 |
+
},
|
| 694 |
+
"domain": "whatsapp",
|
| 695 |
+
"styles": [
|
| 696 |
+
"incomplete"
|
| 697 |
+
]
|
| 698 |
+
},
|
| 699 |
+
{
|
| 700 |
+
"id": "pa-045",
|
| 701 |
+
"prompt": "spotfy stop rn",
|
| 702 |
+
"expected": {
|
| 703 |
+
"skill": "spotify_pause",
|
| 704 |
+
"parameters": {}
|
| 705 |
+
},
|
| 706 |
+
"domain": "spotify",
|
| 707 |
+
"styles": [
|
| 708 |
+
"typo",
|
| 709 |
+
"slang",
|
| 710 |
+
"incomplete"
|
| 711 |
+
]
|
| 712 |
+
},
|
| 713 |
+
{
|
| 714 |
+
"id": "pa-046",
|
| 715 |
+
"prompt": "yo pause the music",
|
| 716 |
+
"expected": {
|
| 717 |
+
"skill": "spotify_pause",
|
| 718 |
+
"parameters": {}
|
| 719 |
+
},
|
| 720 |
+
"domain": "spotify",
|
| 721 |
+
"styles": [
|
| 722 |
+
"slang",
|
| 723 |
+
"incomplete"
|
| 724 |
+
]
|
| 725 |
+
},
|
| 726 |
+
{
|
| 727 |
+
"id": "pa-047",
|
| 728 |
+
"prompt": "mute spotify pls im on a call",
|
| 729 |
+
"expected": {
|
| 730 |
+
"skill": "spotify_pause",
|
| 731 |
+
"parameters": {}
|
| 732 |
+
},
|
| 733 |
+
"domain": "spotify",
|
| 734 |
+
"styles": [
|
| 735 |
+
"conversational"
|
| 736 |
+
]
|
| 737 |
+
},
|
| 738 |
+
{
|
| 739 |
+
"id": "pa-048",
|
| 740 |
+
"prompt": "can u kill the spotify track",
|
| 741 |
+
"expected": {
|
| 742 |
+
"skill": "spotify_pause",
|
| 743 |
+
"parameters": {}
|
| 744 |
+
},
|
| 745 |
+
"domain": "spotify",
|
| 746 |
+
"styles": [
|
| 747 |
+
"slang",
|
| 748 |
+
"conversational"
|
| 749 |
+
]
|
| 750 |
+
},
|
| 751 |
+
{
|
| 752 |
+
"id": "pa-049",
|
| 753 |
+
"prompt": "hold up pause spotify",
|
| 754 |
+
"expected": {
|
| 755 |
+
"skill": "spotify_pause",
|
| 756 |
+
"parameters": {}
|
| 757 |
+
},
|
| 758 |
+
"domain": "spotify",
|
| 759 |
+
"styles": [
|
| 760 |
+
"slang",
|
| 761 |
+
"incomplete"
|
| 762 |
+
]
|
| 763 |
+
},
|
| 764 |
+
{
|
| 765 |
+
"id": "pa-050",
|
| 766 |
+
"prompt": "spoitfy pause rq",
|
| 767 |
+
"expected": {
|
| 768 |
+
"skill": "spotify_pause",
|
| 769 |
+
"parameters": {}
|
| 770 |
+
},
|
| 771 |
+
"domain": "spotify",
|
| 772 |
+
"styles": [
|
| 773 |
+
"typo",
|
| 774 |
+
"slang",
|
| 775 |
+
"incomplete"
|
| 776 |
+
]
|
| 777 |
+
},
|
| 778 |
+
{
|
| 779 |
+
"id": "pa-051",
|
| 780 |
+
"prompt": "stop spotify playback thx",
|
| 781 |
+
"expected": {
|
| 782 |
+
"skill": "spotify_pause",
|
| 783 |
+
"parameters": {}
|
| 784 |
+
},
|
| 785 |
+
"domain": "spotify",
|
| 786 |
+
"styles": [
|
| 787 |
+
"conversational"
|
| 788 |
+
]
|
| 789 |
+
},
|
| 790 |
+
{
|
| 791 |
+
"id": "pa-052",
|
| 792 |
+
"prompt": "shhh pause spotify",
|
| 793 |
+
"expected": {
|
| 794 |
+
"skill": "spotify_pause",
|
| 795 |
+
"parameters": {}
|
| 796 |
+
},
|
| 797 |
+
"domain": "spotify",
|
| 798 |
+
"styles": [
|
| 799 |
+
"incomplete",
|
| 800 |
+
"conversational"
|
| 801 |
+
]
|
| 802 |
+
},
|
| 803 |
+
{
|
| 804 |
+
"id": "pa-053",
|
| 805 |
+
"prompt": "spotify off for a sec",
|
| 806 |
+
"expected": {
|
| 807 |
+
"skill": "spotify_pause",
|
| 808 |
+
"parameters": {}
|
| 809 |
+
},
|
| 810 |
+
"domain": "spotify",
|
| 811 |
+
"styles": [
|
| 812 |
+
"slang",
|
| 813 |
+
"incomplete"
|
| 814 |
+
]
|
| 815 |
+
},
|
| 816 |
+
{
|
| 817 |
+
"id": "pa-054",
|
| 818 |
+
"prompt": "pause whats playing on spotify",
|
| 819 |
+
"expected": {
|
| 820 |
+
"skill": "spotify_pause",
|
| 821 |
+
"parameters": {}
|
| 822 |
+
},
|
| 823 |
+
"domain": "spotify",
|
| 824 |
+
"styles": [
|
| 825 |
+
"incomplete"
|
| 826 |
+
]
|
| 827 |
+
},
|
| 828 |
+
{
|
| 829 |
+
"id": "pa-055",
|
| 830 |
+
"prompt": "ok stop spotify music",
|
| 831 |
+
"expected": {
|
| 832 |
+
"skill": "spotify_pause",
|
| 833 |
+
"parameters": {}
|
| 834 |
+
},
|
| 835 |
+
"domain": "spotify",
|
| 836 |
+
"styles": [
|
| 837 |
+
"conversational",
|
| 838 |
+
"incomplete"
|
| 839 |
+
]
|
| 840 |
+
},
|
| 841 |
+
{
|
| 842 |
+
"id": "pa-056",
|
| 843 |
+
"prompt": "spotify halt plz",
|
| 844 |
+
"expected": {
|
| 845 |
+
"skill": "spotify_pause",
|
| 846 |
+
"parameters": {}
|
| 847 |
+
},
|
| 848 |
+
"domain": "spotify",
|
| 849 |
+
"styles": [
|
| 850 |
+
"incomplete"
|
| 851 |
+
]
|
| 852 |
+
},
|
| 853 |
+
{
|
| 854 |
+
"id": "pa-057",
|
| 855 |
+
"prompt": "silence spotify real quick",
|
| 856 |
+
"expected": {
|
| 857 |
+
"skill": "spotify_pause",
|
| 858 |
+
"parameters": {}
|
| 859 |
+
},
|
| 860 |
+
"domain": "spotify",
|
| 861 |
+
"styles": [
|
| 862 |
+
"conversational"
|
| 863 |
+
]
|
| 864 |
+
},
|
| 865 |
+
{
|
| 866 |
+
"id": "pa-058",
|
| 867 |
+
"prompt": "pause spotify \u2014 landlord knocking",
|
| 868 |
+
"expected": {
|
| 869 |
+
"skill": "spotify_pause",
|
| 870 |
+
"parameters": {}
|
| 871 |
+
},
|
| 872 |
+
"domain": "spotify",
|
| 873 |
+
"styles": [
|
| 874 |
+
"conversational"
|
| 875 |
+
]
|
| 876 |
+
},
|
| 877 |
+
{
|
| 878 |
+
"id": "pa-059",
|
| 879 |
+
"prompt": "spotify stop the banger lol",
|
| 880 |
+
"expected": {
|
| 881 |
+
"skill": "spotify_pause",
|
| 882 |
+
"parameters": {}
|
| 883 |
+
},
|
| 884 |
+
"domain": "spotify",
|
| 885 |
+
"styles": [
|
| 886 |
+
"slang",
|
| 887 |
+
"conversational"
|
| 888 |
+
]
|
| 889 |
+
},
|
| 890 |
+
{
|
| 891 |
+
"id": "pa-060",
|
| 892 |
+
"prompt": "cut spotify audio",
|
| 893 |
+
"expected": {
|
| 894 |
+
"skill": "spotify_pause",
|
| 895 |
+
"parameters": {}
|
| 896 |
+
},
|
| 897 |
+
"domain": "spotify",
|
| 898 |
+
"styles": [
|
| 899 |
+
"slang",
|
| 900 |
+
"incomplete"
|
| 901 |
+
]
|
| 902 |
+
},
|
| 903 |
+
{
|
| 904 |
+
"id": "pa-061",
|
| 905 |
+
"prompt": "spotify pause pls headphones dying",
|
| 906 |
+
"expected": {
|
| 907 |
+
"skill": "spotify_pause",
|
| 908 |
+
"parameters": {}
|
| 909 |
+
},
|
| 910 |
+
"domain": "spotify",
|
| 911 |
+
"styles": [
|
| 912 |
+
"conversational"
|
| 913 |
+
]
|
| 914 |
+
},
|
| 915 |
+
{
|
| 916 |
+
"id": "pa-062",
|
| 917 |
+
"prompt": "uh pause spotify",
|
| 918 |
+
"expected": {
|
| 919 |
+
"skill": "spotify_pause",
|
| 920 |
+
"parameters": {}
|
| 921 |
+
},
|
| 922 |
+
"domain": "spotify",
|
| 923 |
+
"styles": [
|
| 924 |
+
"conversational",
|
| 925 |
+
"incomplete"
|
| 926 |
+
]
|
| 927 |
+
},
|
| 928 |
+
{
|
| 929 |
+
"id": "pa-063",
|
| 930 |
+
"prompt": "stop the spotify pls",
|
| 931 |
+
"expected": {
|
| 932 |
+
"skill": "spotify_pause",
|
| 933 |
+
"parameters": {}
|
| 934 |
+
},
|
| 935 |
+
"domain": "spotify",
|
| 936 |
+
"styles": [
|
| 937 |
+
"incomplete"
|
| 938 |
+
]
|
| 939 |
+
},
|
| 940 |
+
{
|
| 941 |
+
"id": "pa-064",
|
| 942 |
+
"prompt": "spotify freeze for now pls",
|
| 943 |
+
"expected": {
|
| 944 |
+
"skill": "spotify_pause",
|
| 945 |
+
"parameters": {}
|
| 946 |
+
},
|
| 947 |
+
"domain": "spotify",
|
| 948 |
+
"styles": [
|
| 949 |
+
"incomplete",
|
| 950 |
+
"conversational"
|
| 951 |
+
]
|
| 952 |
+
},
|
| 953 |
+
{
|
| 954 |
+
"id": "pa-065",
|
| 955 |
+
"prompt": "pause my spotify session",
|
| 956 |
+
"expected": {
|
| 957 |
+
"skill": "spotify_pause",
|
| 958 |
+
"parameters": {}
|
| 959 |
+
},
|
| 960 |
+
"domain": "spotify",
|
| 961 |
+
"styles": [
|
| 962 |
+
"incomplete"
|
| 963 |
+
]
|
| 964 |
+
},
|
| 965 |
+
{
|
| 966 |
+
"id": "pa-066",
|
| 967 |
+
"prompt": "spotify quiet for now",
|
| 968 |
+
"expected": {
|
| 969 |
+
"skill": "spotify_pause",
|
| 970 |
+
"parameters": {}
|
| 971 |
+
},
|
| 972 |
+
"domain": "spotify",
|
| 973 |
+
"styles": [
|
| 974 |
+
"incomplete",
|
| 975 |
+
"conversational"
|
| 976 |
+
]
|
| 977 |
+
},
|
| 978 |
+
{
|
| 979 |
+
"id": "pa-067",
|
| 980 |
+
"prompt": "put on my midnight drives mix on spotify",
|
| 981 |
+
"expected": {
|
| 982 |
+
"skill": "spotify_play_playlist",
|
| 983 |
+
"parameters": {
|
| 984 |
+
"playlist": "midnight drives mix"
|
| 985 |
+
}
|
| 986 |
+
},
|
| 987 |
+
"domain": "spotify",
|
| 988 |
+
"styles": [
|
| 989 |
+
"conversational"
|
| 990 |
+
]
|
| 991 |
+
},
|
| 992 |
+
{
|
| 993 |
+
"id": "pa-068",
|
| 994 |
+
"prompt": "spotify play sunday scaries",
|
| 995 |
+
"expected": {
|
| 996 |
+
"skill": "spotify_play_playlist",
|
| 997 |
+
"parameters": {
|
| 998 |
+
"playlist": "sunday scaries"
|
| 999 |
+
}
|
| 1000 |
+
},
|
| 1001 |
+
"domain": "spotify",
|
| 1002 |
+
"styles": [
|
| 1003 |
+
"incomplete"
|
| 1004 |
+
]
|
| 1005 |
+
},
|
| 1006 |
+
{
|
| 1007 |
+
"id": "pa-069",
|
| 1008 |
+
"prompt": "queue up deep focus vibes on spotify",
|
| 1009 |
+
"expected": {
|
| 1010 |
+
"skill": "spotify_play_playlist",
|
| 1011 |
+
"parameters": {
|
| 1012 |
+
"playlist": "deep focus vibes"
|
| 1013 |
+
}
|
| 1014 |
+
},
|
| 1015 |
+
"domain": "spotify",
|
| 1016 |
+
"styles": [
|
| 1017 |
+
"slang"
|
| 1018 |
+
]
|
| 1019 |
+
},
|
| 1020 |
+
{
|
| 1021 |
+
"id": "pa-070",
|
| 1022 |
+
"prompt": "play my 2010s throwbacks spotify",
|
| 1023 |
+
"expected": {
|
| 1024 |
+
"skill": "spotify_play_playlist",
|
| 1025 |
+
"parameters": {
|
| 1026 |
+
"playlist": "2010s throwbacks"
|
| 1027 |
+
}
|
| 1028 |
+
},
|
| 1029 |
+
"domain": "spotify",
|
| 1030 |
+
"styles": [
|
| 1031 |
+
"incomplete"
|
| 1032 |
+
]
|
| 1033 |
+
},
|
| 1034 |
+
{
|
| 1035 |
+
"id": "pa-071",
|
| 1036 |
+
"prompt": "spotfy playlist: rainy day jazz",
|
| 1037 |
+
"expected": {
|
| 1038 |
+
"skill": "spotify_play_playlist",
|
| 1039 |
+
"parameters": {
|
| 1040 |
+
"playlist": "rainy day jazz"
|
| 1041 |
+
}
|
| 1042 |
+
},
|
| 1043 |
+
"domain": "spotify",
|
| 1044 |
+
"styles": [
|
| 1045 |
+
"typo",
|
| 1046 |
+
"incomplete"
|
| 1047 |
+
]
|
| 1048 |
+
},
|
| 1049 |
+
{
|
| 1050 |
+
"id": "pa-072",
|
| 1051 |
+
"prompt": "yo spin the gym beast mode playlist",
|
| 1052 |
+
"expected": {
|
| 1053 |
+
"skill": "spotify_play_playlist",
|
| 1054 |
+
"parameters": {
|
| 1055 |
+
"playlist": "gym beast mode"
|
| 1056 |
+
}
|
| 1057 |
+
},
|
| 1058 |
+
"domain": "spotify",
|
| 1059 |
+
"styles": [
|
| 1060 |
+
"slang",
|
| 1061 |
+
"incomplete"
|
| 1062 |
+
]
|
| 1063 |
+
},
|
| 1064 |
+
{
|
| 1065 |
+
"id": "pa-073",
|
| 1066 |
+
"prompt": "spotify \u2014 my cooking playlist pls",
|
| 1067 |
+
"expected": {
|
| 1068 |
+
"skill": "spotify_play_playlist",
|
| 1069 |
+
"parameters": {
|
| 1070 |
+
"playlist": "cooking"
|
| 1071 |
+
}
|
| 1072 |
+
},
|
| 1073 |
+
"domain": "spotify",
|
| 1074 |
+
"styles": [
|
| 1075 |
+
"incomplete",
|
| 1076 |
+
"conversational"
|
| 1077 |
+
]
|
| 1078 |
+
},
|
| 1079 |
+
{
|
| 1080 |
+
"id": "pa-074",
|
| 1081 |
+
"prompt": "start spotify playlist called roadtrip 2024",
|
| 1082 |
+
"expected": {
|
| 1083 |
+
"skill": "spotify_play_playlist",
|
| 1084 |
+
"parameters": {
|
| 1085 |
+
"playlist": "roadtrip 2024"
|
| 1086 |
+
}
|
| 1087 |
+
},
|
| 1088 |
+
"domain": "spotify",
|
| 1089 |
+
"styles": [
|
| 1090 |
+
"incomplete"
|
| 1091 |
+
]
|
| 1092 |
+
},
|
| 1093 |
+
{
|
| 1094 |
+
"id": "pa-075",
|
| 1095 |
+
"prompt": "play hyperpop mix on spotify",
|
| 1096 |
+
"expected": {
|
| 1097 |
+
"skill": "spotify_play_playlist",
|
| 1098 |
+
"parameters": {
|
| 1099 |
+
"playlist": "hyperpop mix"
|
| 1100 |
+
}
|
| 1101 |
+
},
|
| 1102 |
+
"domain": "spotify",
|
| 1103 |
+
"styles": [
|
| 1104 |
+
"incomplete"
|
| 1105 |
+
]
|
| 1106 |
+
},
|
| 1107 |
+
{
|
| 1108 |
+
"id": "pa-076",
|
| 1109 |
+
"prompt": "spotify my chillhop essentials thx",
|
| 1110 |
+
"expected": {
|
| 1111 |
+
"skill": "spotify_play_playlist",
|
| 1112 |
+
"parameters": {
|
| 1113 |
+
"playlist": "chillhop essentials"
|
| 1114 |
+
}
|
| 1115 |
+
},
|
| 1116 |
+
"domain": "spotify",
|
| 1117 |
+
"styles": [
|
| 1118 |
+
"incomplete",
|
| 1119 |
+
"conversational"
|
| 1120 |
+
]
|
| 1121 |
+
},
|
| 1122 |
+
{
|
| 1123 |
+
"id": "pa-077",
|
| 1124 |
+
"prompt": "can u play the study grind playlist on spotify",
|
| 1125 |
+
"expected": {
|
| 1126 |
+
"skill": "spotify_play_playlist",
|
| 1127 |
+
"parameters": {
|
| 1128 |
+
"playlist": "study grind"
|
| 1129 |
+
}
|
| 1130 |
+
},
|
| 1131 |
+
"domain": "spotify",
|
| 1132 |
+
"styles": [
|
| 1133 |
+
"conversational"
|
| 1134 |
+
]
|
| 1135 |
+
},
|
| 1136 |
+
{
|
| 1137 |
+
"id": "pa-078",
|
| 1138 |
+
"prompt": "spotify playlist sleep sounds go",
|
| 1139 |
+
"expected": {
|
| 1140 |
+
"skill": "spotify_play_playlist",
|
| 1141 |
+
"parameters": {
|
| 1142 |
+
"playlist": "sleep sounds"
|
| 1143 |
+
}
|
| 1144 |
+
},
|
| 1145 |
+
"domain": "spotify",
|
| 1146 |
+
"styles": [
|
| 1147 |
+
"incomplete"
|
| 1148 |
+
]
|
| 1149 |
+
},
|
| 1150 |
+
{
|
| 1151 |
+
"id": "pa-079",
|
| 1152 |
+
"prompt": "throw on indie roadtrip on spotify",
|
| 1153 |
+
"expected": {
|
| 1154 |
+
"skill": "spotify_play_playlist",
|
| 1155 |
+
"parameters": {
|
| 1156 |
+
"playlist": "indie roadtrip"
|
| 1157 |
+
}
|
| 1158 |
+
},
|
| 1159 |
+
"domain": "spotify",
|
| 1160 |
+
"styles": [
|
| 1161 |
+
"slang",
|
| 1162 |
+
"incomplete"
|
| 1163 |
+
]
|
| 1164 |
+
},
|
| 1165 |
+
{
|
| 1166 |
+
"id": "pa-080",
|
| 1167 |
+
"prompt": "spotify play my liked tracks playlist",
|
| 1168 |
+
"expected": {
|
| 1169 |
+
"skill": "spotify_play_playlist",
|
| 1170 |
+
"parameters": {
|
| 1171 |
+
"playlist": "liked tracks"
|
| 1172 |
+
}
|
| 1173 |
+
},
|
| 1174 |
+
"domain": "spotify",
|
| 1175 |
+
"styles": [
|
| 1176 |
+
"incomplete"
|
| 1177 |
+
]
|
| 1178 |
+
},
|
| 1179 |
+
{
|
| 1180 |
+
"id": "pa-081",
|
| 1181 |
+
"prompt": "play spotify playlist bossa nova mornings",
|
| 1182 |
+
"expected": {
|
| 1183 |
+
"skill": "spotify_play_playlist",
|
| 1184 |
+
"parameters": {
|
| 1185 |
+
"playlist": "bossa nova mornings"
|
| 1186 |
+
}
|
| 1187 |
+
},
|
| 1188 |
+
"domain": "spotify",
|
| 1189 |
+
"styles": [
|
| 1190 |
+
"incomplete"
|
| 1191 |
+
]
|
| 1192 |
+
},
|
| 1193 |
+
{
|
| 1194 |
+
"id": "pa-082",
|
| 1195 |
+
"prompt": "spotify \u2014 queue phonk workout",
|
| 1196 |
+
"expected": {
|
| 1197 |
+
"skill": "spotify_play_playlist",
|
| 1198 |
+
"parameters": {
|
| 1199 |
+
"playlist": "phonk workout"
|
| 1200 |
+
}
|
| 1201 |
+
},
|
| 1202 |
+
"domain": "spotify",
|
| 1203 |
+
"styles": [
|
| 1204 |
+
"incomplete"
|
| 1205 |
+
]
|
| 1206 |
+
},
|
| 1207 |
+
{
|
| 1208 |
+
"id": "pa-083",
|
| 1209 |
+
"prompt": "put spotify on my cozy rainy day list",
|
| 1210 |
+
"expected": {
|
| 1211 |
+
"skill": "spotify_play_playlist",
|
| 1212 |
+
"parameters": {
|
| 1213 |
+
"playlist": "cozy rainy day"
|
| 1214 |
+
}
|
| 1215 |
+
},
|
| 1216 |
+
"domain": "spotify",
|
| 1217 |
+
"styles": [
|
| 1218 |
+
"conversational",
|
| 1219 |
+
"incomplete"
|
| 1220 |
+
]
|
| 1221 |
+
},
|
| 1222 |
+
{
|
| 1223 |
+
"id": "pa-084",
|
| 1224 |
+
"prompt": "play the spotify playlist french cafe",
|
| 1225 |
+
"expected": {
|
| 1226 |
+
"skill": "spotify_play_playlist",
|
| 1227 |
+
"parameters": {
|
| 1228 |
+
"playlist": "french cafe"
|
| 1229 |
+
}
|
| 1230 |
+
},
|
| 1231 |
+
"domain": "spotify",
|
| 1232 |
+
"styles": [
|
| 1233 |
+
"incomplete"
|
| 1234 |
+
]
|
| 1235 |
+
},
|
| 1236 |
+
{
|
| 1237 |
+
"id": "pa-085",
|
| 1238 |
+
"prompt": "spotify start my pregame playlist",
|
| 1239 |
+
"expected": {
|
| 1240 |
+
"skill": "spotify_play_playlist",
|
| 1241 |
+
"parameters": {
|
| 1242 |
+
"playlist": "pregame"
|
| 1243 |
+
}
|
| 1244 |
+
},
|
| 1245 |
+
"domain": "spotify",
|
| 1246 |
+
"styles": [
|
| 1247 |
+
"slang",
|
| 1248 |
+
"incomplete"
|
| 1249 |
+
]
|
| 1250 |
+
},
|
| 1251 |
+
{
|
| 1252 |
+
"id": "pa-086",
|
| 1253 |
+
"prompt": "ok spotify play downtempo focus",
|
| 1254 |
+
"expected": {
|
| 1255 |
+
"skill": "spotify_play_playlist",
|
| 1256 |
+
"parameters": {
|
| 1257 |
+
"playlist": "downtempo focus"
|
| 1258 |
+
}
|
| 1259 |
+
},
|
| 1260 |
+
"domain": "spotify",
|
| 1261 |
+
"styles": [
|
| 1262 |
+
"conversational",
|
| 1263 |
+
"incomplete"
|
| 1264 |
+
]
|
| 1265 |
+
},
|
| 1266 |
+
{
|
| 1267 |
+
"id": "pa-087",
|
| 1268 |
+
"prompt": "spotify my discover mix playlist pls",
|
| 1269 |
+
"expected": {
|
| 1270 |
+
"skill": "spotify_play_playlist",
|
| 1271 |
+
"parameters": {
|
| 1272 |
+
"playlist": "discover mix"
|
| 1273 |
+
}
|
| 1274 |
+
},
|
| 1275 |
+
"domain": "spotify",
|
| 1276 |
+
"styles": [
|
| 1277 |
+
"incomplete"
|
| 1278 |
+
]
|
| 1279 |
+
},
|
| 1280 |
+
{
|
| 1281 |
+
"id": "pa-088",
|
| 1282 |
+
"prompt": "play playlist morning commute on spotify",
|
| 1283 |
+
"expected": {
|
| 1284 |
+
"skill": "spotify_play_playlist",
|
| 1285 |
+
"parameters": {
|
| 1286 |
+
"playlist": "morning commute"
|
| 1287 |
+
}
|
| 1288 |
+
},
|
| 1289 |
+
"domain": "spotify",
|
| 1290 |
+
"styles": [
|
| 1291 |
+
"incomplete"
|
| 1292 |
+
]
|
| 1293 |
+
},
|
| 1294 |
+
{
|
| 1295 |
+
"id": "pa-089",
|
| 1296 |
+
"prompt": "spotify find bad bunny and play",
|
| 1297 |
+
"expected": {
|
| 1298 |
+
"skill": "spotify_search_play",
|
| 1299 |
+
"parameters": {
|
| 1300 |
+
"query": "bad bunny"
|
| 1301 |
+
}
|
| 1302 |
+
},
|
| 1303 |
+
"domain": "spotify",
|
| 1304 |
+
"styles": [
|
| 1305 |
+
"incomplete"
|
| 1306 |
+
]
|
| 1307 |
+
},
|
| 1308 |
+
{
|
| 1309 |
+
"id": "pa-090",
|
| 1310 |
+
"prompt": "search spotify for lofi hip hop then play",
|
| 1311 |
+
"expected": {
|
| 1312 |
+
"skill": "spotify_search_play",
|
| 1313 |
+
"parameters": {
|
| 1314 |
+
"query": "lofi hip hop"
|
| 1315 |
+
}
|
| 1316 |
+
},
|
| 1317 |
+
"domain": "spotify",
|
| 1318 |
+
"styles": [
|
| 1319 |
+
"conversational"
|
| 1320 |
+
]
|
| 1321 |
+
},
|
| 1322 |
+
{
|
| 1323 |
+
"id": "pa-091",
|
| 1324 |
+
"prompt": "play some arctic monkeys on spotify",
|
| 1325 |
+
"expected": {
|
| 1326 |
+
"skill": "spotify_search_play",
|
| 1327 |
+
"parameters": {
|
| 1328 |
+
"query": "arctic monkeys"
|
| 1329 |
+
}
|
| 1330 |
+
},
|
| 1331 |
+
"domain": "spotify",
|
| 1332 |
+
"styles": [
|
| 1333 |
+
"conversational",
|
| 1334 |
+
"incomplete"
|
| 1335 |
+
]
|
| 1336 |
+
},
|
| 1337 |
+
{
|
| 1338 |
+
"id": "pa-092",
|
| 1339 |
+
"prompt": "spotfy search billie eilish play it",
|
| 1340 |
+
"expected": {
|
| 1341 |
+
"skill": "spotify_search_play",
|
| 1342 |
+
"parameters": {
|
| 1343 |
+
"query": "billie eilish"
|
| 1344 |
+
}
|
| 1345 |
+
},
|
| 1346 |
+
"domain": "spotify",
|
| 1347 |
+
"styles": [
|
| 1348 |
+
"typo",
|
| 1349 |
+
"incomplete"
|
| 1350 |
+
]
|
| 1351 |
+
},
|
| 1352 |
+
{
|
| 1353 |
+
"id": "pa-093",
|
| 1354 |
+
"prompt": "look up sabrina carpenter on spotify n play",
|
| 1355 |
+
"expected": {
|
| 1356 |
+
"skill": "spotify_search_play",
|
| 1357 |
+
"parameters": {
|
| 1358 |
+
"query": "sabrina carpenter"
|
| 1359 |
+
}
|
| 1360 |
+
},
|
| 1361 |
+
"domain": "spotify",
|
| 1362 |
+
"styles": [
|
| 1363 |
+
"slang",
|
| 1364 |
+
"incomplete"
|
| 1365 |
+
]
|
| 1366 |
+
},
|
| 1367 |
+
{
|
| 1368 |
+
"id": "pa-094",
|
| 1369 |
+
"prompt": "spotify: search neo soul and hit play",
|
| 1370 |
+
"expected": {
|
| 1371 |
+
"skill": "spotify_search_play",
|
| 1372 |
+
"parameters": {
|
| 1373 |
+
"query": "neo soul"
|
| 1374 |
+
}
|
| 1375 |
+
},
|
| 1376 |
+
"domain": "spotify",
|
| 1377 |
+
"styles": [
|
| 1378 |
+
"incomplete"
|
| 1379 |
+
]
|
| 1380 |
+
},
|
| 1381 |
+
{
|
| 1382 |
+
"id": "pa-095",
|
| 1383 |
+
"prompt": "find tyler the creator on spotify and play",
|
| 1384 |
+
"expected": {
|
| 1385 |
+
"skill": "spotify_search_play",
|
| 1386 |
+
"parameters": {
|
| 1387 |
+
"query": "tyler the creator"
|
| 1388 |
+
}
|
| 1389 |
+
},
|
| 1390 |
+
"domain": "spotify",
|
| 1391 |
+
"styles": [
|
| 1392 |
+
"incomplete"
|
| 1393 |
+
]
|
| 1394 |
+
},
|
| 1395 |
+
{
|
| 1396 |
+
"id": "pa-096",
|
| 1397 |
+
"prompt": "spotify play after searching radiohead",
|
| 1398 |
+
"expected": {
|
| 1399 |
+
"skill": "spotify_search_play",
|
| 1400 |
+
"parameters": {
|
| 1401 |
+
"query": "radiohead"
|
| 1402 |
+
}
|
| 1403 |
+
},
|
| 1404 |
+
"domain": "spotify",
|
| 1405 |
+
"styles": [
|
| 1406 |
+
"incomplete"
|
| 1407 |
+
]
|
| 1408 |
+
},
|
| 1409 |
+
{
|
| 1410 |
+
"id": "pa-097",
|
| 1411 |
+
"prompt": "yo spotify search dnb and play",
|
| 1412 |
+
"expected": {
|
| 1413 |
+
"skill": "spotify_search_play",
|
| 1414 |
+
"parameters": {
|
| 1415 |
+
"query": "dnb"
|
| 1416 |
+
}
|
| 1417 |
+
},
|
| 1418 |
+
"domain": "spotify",
|
| 1419 |
+
"styles": [
|
| 1420 |
+
"slang",
|
| 1421 |
+
"incomplete"
|
| 1422 |
+
]
|
| 1423 |
+
},
|
| 1424 |
+
{
|
| 1425 |
+
"id": "pa-098",
|
| 1426 |
+
"prompt": "can u find frank ocean on spotify and play it",
|
| 1427 |
+
"expected": {
|
| 1428 |
+
"skill": "spotify_search_play",
|
| 1429 |
+
"parameters": {
|
| 1430 |
+
"query": "frank ocean"
|
| 1431 |
+
}
|
| 1432 |
+
},
|
| 1433 |
+
"domain": "spotify",
|
| 1434 |
+
"styles": [
|
| 1435 |
+
"conversational"
|
| 1436 |
+
]
|
| 1437 |
+
},
|
| 1438 |
+
{
|
| 1439 |
+
"id": "pa-099",
|
| 1440 |
+
"prompt": "spotify search classical piano play",
|
| 1441 |
+
"expected": {
|
| 1442 |
+
"skill": "spotify_search_play",
|
| 1443 |
+
"parameters": {
|
| 1444 |
+
"query": "classical piano"
|
| 1445 |
+
}
|
| 1446 |
+
},
|
| 1447 |
+
"domain": "spotify",
|
| 1448 |
+
"styles": [
|
| 1449 |
+
"incomplete"
|
| 1450 |
+
]
|
| 1451 |
+
},
|
| 1452 |
+
{
|
| 1453 |
+
"id": "pa-100",
|
| 1454 |
+
"prompt": "spotify search play some drake tracks",
|
| 1455 |
+
"expected": {
|
| 1456 |
+
"skill": "spotify_search_play",
|
| 1457 |
+
"parameters": {
|
| 1458 |
+
"query": "drake tracks"
|
| 1459 |
+
}
|
| 1460 |
+
},
|
| 1461 |
+
"domain": "spotify",
|
| 1462 |
+
"styles": [
|
| 1463 |
+
"incomplete"
|
| 1464 |
+
]
|
| 1465 |
+
},
|
| 1466 |
+
{
|
| 1467 |
+
"id": "pa-101",
|
| 1468 |
+
"prompt": "spotify find latin reggaeton and play",
|
| 1469 |
+
"expected": {
|
| 1470 |
+
"skill": "spotify_search_play",
|
| 1471 |
+
"parameters": {
|
| 1472 |
+
"query": "latin reggaeton"
|
| 1473 |
+
}
|
| 1474 |
+
},
|
| 1475 |
+
"domain": "spotify",
|
| 1476 |
+
"styles": [
|
| 1477 |
+
"incomplete"
|
| 1478 |
+
]
|
| 1479 |
+
},
|
| 1480 |
+
{
|
| 1481 |
+
"id": "pa-102",
|
| 1482 |
+
"prompt": "search spotify for aphex twin then play",
|
| 1483 |
+
"expected": {
|
| 1484 |
+
"skill": "spotify_search_play",
|
| 1485 |
+
"parameters": {
|
| 1486 |
+
"query": "aphex twin"
|
| 1487 |
+
}
|
| 1488 |
+
},
|
| 1489 |
+
"domain": "spotify",
|
| 1490 |
+
"styles": [
|
| 1491 |
+
"incomplete"
|
| 1492 |
+
]
|
| 1493 |
+
},
|
| 1494 |
+
{
|
| 1495 |
+
"id": "pa-103",
|
| 1496 |
+
"prompt": "spotify play whatever u find for synthwave",
|
| 1497 |
+
"expected": {
|
| 1498 |
+
"skill": "spotify_search_play",
|
| 1499 |
+
"parameters": {
|
| 1500 |
+
"query": "synthwave"
|
| 1501 |
+
}
|
| 1502 |
+
},
|
| 1503 |
+
"domain": "spotify",
|
| 1504 |
+
"styles": [
|
| 1505 |
+
"slang",
|
| 1506 |
+
"conversational"
|
| 1507 |
+
]
|
| 1508 |
+
},
|
| 1509 |
+
{
|
| 1510 |
+
"id": "pa-104",
|
| 1511 |
+
"prompt": "find mitski on spotify play pls",
|
| 1512 |
+
"expected": {
|
| 1513 |
+
"skill": "spotify_search_play",
|
| 1514 |
+
"parameters": {
|
| 1515 |
+
"query": "mitski"
|
| 1516 |
+
}
|
| 1517 |
+
},
|
| 1518 |
+
"domain": "spotify",
|
| 1519 |
+
"styles": [
|
| 1520 |
+
"incomplete",
|
| 1521 |
+
"conversational"
|
| 1522 |
+
]
|
| 1523 |
+
},
|
| 1524 |
+
{
|
| 1525 |
+
"id": "pa-105",
|
| 1526 |
+
"prompt": "soptify search play khruangbin",
|
| 1527 |
+
"expected": {
|
| 1528 |
+
"skill": "spotify_search_play",
|
| 1529 |
+
"parameters": {
|
| 1530 |
+
"query": "khruangbin"
|
| 1531 |
+
}
|
| 1532 |
+
},
|
| 1533 |
+
"domain": "spotify",
|
| 1534 |
+
"styles": [
|
| 1535 |
+
"typo",
|
| 1536 |
+
"incomplete"
|
| 1537 |
+
]
|
| 1538 |
+
},
|
| 1539 |
+
{
|
| 1540 |
+
"id": "pa-106",
|
| 1541 |
+
"prompt": "look up burna boy spotify and play",
|
| 1542 |
+
"expected": {
|
| 1543 |
+
"skill": "spotify_search_play",
|
| 1544 |
+
"parameters": {
|
| 1545 |
+
"query": "burna boy"
|
| 1546 |
+
}
|
| 1547 |
+
},
|
| 1548 |
+
"domain": "spotify",
|
| 1549 |
+
"styles": [
|
| 1550 |
+
"incomplete"
|
| 1551 |
+
]
|
| 1552 |
+
},
|
| 1553 |
+
{
|
| 1554 |
+
"id": "pa-107",
|
| 1555 |
+
"prompt": "spotify search play some afrobeats",
|
| 1556 |
+
"expected": {
|
| 1557 |
+
"skill": "spotify_search_play",
|
| 1558 |
+
"parameters": {
|
| 1559 |
+
"query": "afrobeats"
|
| 1560 |
+
}
|
| 1561 |
+
},
|
| 1562 |
+
"domain": "spotify",
|
| 1563 |
+
"styles": [
|
| 1564 |
+
"incomplete"
|
| 1565 |
+
]
|
| 1566 |
+
},
|
| 1567 |
+
{
|
| 1568 |
+
"id": "pa-108",
|
| 1569 |
+
"prompt": "find fred again on spotify n play it",
|
| 1570 |
+
"expected": {
|
| 1571 |
+
"skill": "spotify_search_play",
|
| 1572 |
+
"parameters": {
|
| 1573 |
+
"query": "fred again"
|
| 1574 |
+
}
|
| 1575 |
+
},
|
| 1576 |
+
"domain": "spotify",
|
| 1577 |
+
"styles": [
|
| 1578 |
+
"slang",
|
| 1579 |
+
"incomplete"
|
| 1580 |
+
]
|
| 1581 |
+
},
|
| 1582 |
+
{
|
| 1583 |
+
"id": "pa-109",
|
| 1584 |
+
"prompt": "spotify search play 90s r&b",
|
| 1585 |
+
"expected": {
|
| 1586 |
+
"skill": "spotify_search_play",
|
| 1587 |
+
"parameters": {
|
| 1588 |
+
"query": "90s r&b"
|
| 1589 |
+
}
|
| 1590 |
+
},
|
| 1591 |
+
"domain": "spotify",
|
| 1592 |
+
"styles": [
|
| 1593 |
+
"incomplete"
|
| 1594 |
+
]
|
| 1595 |
+
},
|
| 1596 |
+
{
|
| 1597 |
+
"id": "pa-110",
|
| 1598 |
+
"prompt": "search spotify play tame impala",
|
| 1599 |
+
"expected": {
|
| 1600 |
+
"skill": "spotify_search_play",
|
| 1601 |
+
"parameters": {
|
| 1602 |
+
"query": "tame impala"
|
| 1603 |
+
}
|
| 1604 |
+
},
|
| 1605 |
+
"domain": "spotify",
|
| 1606 |
+
"styles": [
|
| 1607 |
+
"incomplete"
|
| 1608 |
+
]
|
| 1609 |
+
},
|
| 1610 |
+
{
|
| 1611 |
+
"id": "pa-111",
|
| 1612 |
+
"prompt": "ubber to jfk pls",
|
| 1613 |
+
"expected": {
|
| 1614 |
+
"skill": "uber_request_ride",
|
| 1615 |
+
"parameters": {
|
| 1616 |
+
"destination": "jfk"
|
| 1617 |
+
}
|
| 1618 |
+
},
|
| 1619 |
+
"domain": "uber",
|
| 1620 |
+
"styles": [
|
| 1621 |
+
"typo",
|
| 1622 |
+
"incomplete",
|
| 1623 |
+
"conversational"
|
| 1624 |
+
]
|
| 1625 |
+
},
|
| 1626 |
+
{
|
| 1627 |
+
"id": "pa-112",
|
| 1628 |
+
"prompt": "need a ride to 42 oak street on uber",
|
| 1629 |
+
"expected": {
|
| 1630 |
+
"skill": "uber_request_ride",
|
| 1631 |
+
"parameters": {
|
| 1632 |
+
"destination": "42 oak street"
|
| 1633 |
+
}
|
| 1634 |
+
},
|
| 1635 |
+
"domain": "uber",
|
| 1636 |
+
"styles": [
|
| 1637 |
+
"conversational"
|
| 1638 |
+
]
|
| 1639 |
+
},
|
| 1640 |
+
{
|
| 1641 |
+
"id": "pa-113",
|
| 1642 |
+
"prompt": "hop in an uber to the vet clinic",
|
| 1643 |
+
"expected": {
|
| 1644 |
+
"skill": "uber_request_ride",
|
| 1645 |
+
"parameters": {
|
| 1646 |
+
"destination": "the vet clinic"
|
| 1647 |
+
}
|
| 1648 |
+
},
|
| 1649 |
+
"domain": "uber",
|
| 1650 |
+
"styles": [
|
| 1651 |
+
"slang"
|
| 1652 |
+
]
|
| 1653 |
+
},
|
| 1654 |
+
{
|
| 1655 |
+
"id": "pa-114",
|
| 1656 |
+
"prompt": "ubr me to whole foods on 5th",
|
| 1657 |
+
"expected": {
|
| 1658 |
+
"skill": "uber_request_ride",
|
| 1659 |
+
"parameters": {
|
| 1660 |
+
"destination": "whole foods on 5th"
|
| 1661 |
+
}
|
| 1662 |
+
},
|
| 1663 |
+
"domain": "uber",
|
| 1664 |
+
"styles": [
|
| 1665 |
+
"typo",
|
| 1666 |
+
"slang",
|
| 1667 |
+
"incomplete"
|
| 1668 |
+
]
|
| 1669 |
+
},
|
| 1670 |
+
{
|
| 1671 |
+
"id": "pa-115",
|
| 1672 |
+
"prompt": "get uber to coworking space downtown",
|
| 1673 |
+
"expected": {
|
| 1674 |
+
"skill": "uber_request_ride",
|
| 1675 |
+
"parameters": {
|
| 1676 |
+
"destination": "coworking space downtown"
|
| 1677 |
+
}
|
| 1678 |
+
},
|
| 1679 |
+
"domain": "uber",
|
| 1680 |
+
"styles": [
|
| 1681 |
+
"incomplete"
|
| 1682 |
+
]
|
| 1683 |
+
},
|
| 1684 |
+
{
|
| 1685 |
+
"id": "pa-116",
|
| 1686 |
+
"prompt": "book ride to mom's house via uber",
|
| 1687 |
+
"expected": {
|
| 1688 |
+
"skill": "uber_request_ride",
|
| 1689 |
+
"parameters": {
|
| 1690 |
+
"destination": "mom's house"
|
| 1691 |
+
}
|
| 1692 |
+
},
|
| 1693 |
+
"domain": "uber",
|
| 1694 |
+
"styles": [
|
| 1695 |
+
"incomplete"
|
| 1696 |
+
]
|
| 1697 |
+
},
|
| 1698 |
+
{
|
| 1699 |
+
"id": "pa-117",
|
| 1700 |
+
"prompt": "uber \u2014 take me to pier 39",
|
| 1701 |
+
"expected": {
|
| 1702 |
+
"skill": "uber_request_ride",
|
| 1703 |
+
"parameters": {
|
| 1704 |
+
"destination": "pier 39"
|
| 1705 |
+
}
|
| 1706 |
+
},
|
| 1707 |
+
"domain": "uber",
|
| 1708 |
+
"styles": [
|
| 1709 |
+
"incomplete",
|
| 1710 |
+
"conversational"
|
| 1711 |
+
]
|
| 1712 |
+
},
|
| 1713 |
+
{
|
| 1714 |
+
"id": "pa-118",
|
| 1715 |
+
"prompt": "can u call uber to stanford campus",
|
| 1716 |
+
"expected": {
|
| 1717 |
+
"skill": "uber_request_ride",
|
| 1718 |
+
"parameters": {
|
| 1719 |
+
"destination": "stanford campus"
|
| 1720 |
+
}
|
| 1721 |
+
},
|
| 1722 |
+
"domain": "uber",
|
| 1723 |
+
"styles": [
|
| 1724 |
+
"conversational"
|
| 1725 |
+
]
|
| 1726 |
+
},
|
| 1727 |
+
{
|
| 1728 |
+
"id": "pa-119",
|
| 1729 |
+
"prompt": "ride to airport terminal 2 uber",
|
| 1730 |
+
"expected": {
|
| 1731 |
+
"skill": "uber_request_ride",
|
| 1732 |
+
"parameters": {
|
| 1733 |
+
"destination": "airport terminal 2"
|
| 1734 |
+
}
|
| 1735 |
+
},
|
| 1736 |
+
"domain": "uber",
|
| 1737 |
+
"styles": [
|
| 1738 |
+
"incomplete"
|
| 1739 |
+
]
|
| 1740 |
+
},
|
| 1741 |
+
{
|
| 1742 |
+
"id": "pa-120",
|
| 1743 |
+
"prompt": "uber to the sushi place on main",
|
| 1744 |
+
"expected": {
|
| 1745 |
+
"skill": "uber_request_ride",
|
| 1746 |
+
"parameters": {
|
| 1747 |
+
"destination": "the sushi place on main"
|
| 1748 |
+
}
|
| 1749 |
+
},
|
| 1750 |
+
"domain": "uber",
|
| 1751 |
+
"styles": [
|
| 1752 |
+
"incomplete",
|
| 1753 |
+
"conversational"
|
| 1754 |
+
]
|
| 1755 |
+
},
|
| 1756 |
+
{
|
| 1757 |
+
"id": "pa-121",
|
| 1758 |
+
"prompt": "need uber to ikea rn",
|
| 1759 |
+
"expected": {
|
| 1760 |
+
"skill": "uber_request_ride",
|
| 1761 |
+
"parameters": {
|
| 1762 |
+
"destination": "ikea"
|
| 1763 |
+
}
|
| 1764 |
+
},
|
| 1765 |
+
"domain": "uber",
|
| 1766 |
+
"styles": [
|
| 1767 |
+
"slang",
|
| 1768 |
+
"incomplete"
|
| 1769 |
+
]
|
| 1770 |
+
},
|
| 1771 |
+
{
|
| 1772 |
+
"id": "pa-122",
|
| 1773 |
+
"prompt": "request uber to brooklyn bridge park",
|
| 1774 |
+
"expected": {
|
| 1775 |
+
"skill": "uber_request_ride",
|
| 1776 |
+
"parameters": {
|
| 1777 |
+
"destination": "brooklyn bridge park"
|
| 1778 |
+
}
|
| 1779 |
+
},
|
| 1780 |
+
"domain": "uber",
|
| 1781 |
+
"styles": [
|
| 1782 |
+
"incomplete"
|
| 1783 |
+
]
|
| 1784 |
+
},
|
| 1785 |
+
{
|
| 1786 |
+
"id": "pa-123",
|
| 1787 |
+
"prompt": "uber drop me at la guardia",
|
| 1788 |
+
"expected": {
|
| 1789 |
+
"skill": "uber_request_ride",
|
| 1790 |
+
"parameters": {
|
| 1791 |
+
"destination": "la guardia"
|
| 1792 |
+
}
|
| 1793 |
+
},
|
| 1794 |
+
"domain": "uber",
|
| 1795 |
+
"styles": [
|
| 1796 |
+
"slang",
|
| 1797 |
+
"incomplete"
|
| 1798 |
+
]
|
| 1799 |
+
},
|
| 1800 |
+
{
|
| 1801 |
+
"id": "pa-124",
|
| 1802 |
+
"prompt": "get a cab on uber to dentist on maple ave",
|
| 1803 |
+
"expected": {
|
| 1804 |
+
"skill": "uber_request_ride",
|
| 1805 |
+
"parameters": {
|
| 1806 |
+
"destination": "dentist on maple ave"
|
| 1807 |
+
}
|
| 1808 |
+
},
|
| 1809 |
+
"domain": "uber",
|
| 1810 |
+
"styles": [
|
| 1811 |
+
"conversational"
|
| 1812 |
+
]
|
| 1813 |
+
},
|
| 1814 |
+
{
|
| 1815 |
+
"id": "pa-125",
|
| 1816 |
+
"prompt": "uber to friends apartment 8b",
|
| 1817 |
+
"expected": {
|
| 1818 |
+
"skill": "uber_request_ride",
|
| 1819 |
+
"parameters": {
|
| 1820 |
+
"destination": "friends apartment 8b"
|
| 1821 |
+
}
|
| 1822 |
+
},
|
| 1823 |
+
"domain": "uber",
|
| 1824 |
+
"styles": [
|
| 1825 |
+
"incomplete"
|
| 1826 |
+
]
|
| 1827 |
+
},
|
| 1828 |
+
{
|
| 1829 |
+
"id": "pa-126",
|
| 1830 |
+
"prompt": "yo uber to the concert venue",
|
| 1831 |
+
"expected": {
|
| 1832 |
+
"skill": "uber_request_ride",
|
| 1833 |
+
"parameters": {
|
| 1834 |
+
"destination": "the concert venue"
|
| 1835 |
+
}
|
| 1836 |
+
},
|
| 1837 |
+
"domain": "uber",
|
| 1838 |
+
"styles": [
|
| 1839 |
+
"slang",
|
| 1840 |
+
"incomplete"
|
| 1841 |
+
]
|
| 1842 |
+
},
|
| 1843 |
+
{
|
| 1844 |
+
"id": "pa-127",
|
| 1845 |
+
"prompt": "book uber ride to seattle tacoma airport",
|
| 1846 |
+
"expected": {
|
| 1847 |
+
"skill": "uber_request_ride",
|
| 1848 |
+
"parameters": {
|
| 1849 |
+
"destination": "seattle tacoma airport"
|
| 1850 |
+
}
|
| 1851 |
+
},
|
| 1852 |
+
"domain": "uber",
|
| 1853 |
+
"styles": [
|
| 1854 |
+
"incomplete"
|
| 1855 |
+
]
|
| 1856 |
+
},
|
| 1857 |
+
{
|
| 1858 |
+
"id": "pa-128",
|
| 1859 |
+
"prompt": "uber pls to union station",
|
| 1860 |
+
"expected": {
|
| 1861 |
+
"skill": "uber_request_ride",
|
| 1862 |
+
"parameters": {
|
| 1863 |
+
"destination": "union station"
|
| 1864 |
+
}
|
| 1865 |
+
},
|
| 1866 |
+
"domain": "uber",
|
| 1867 |
+
"styles": [
|
| 1868 |
+
"incomplete",
|
| 1869 |
+
"conversational"
|
| 1870 |
+
]
|
| 1871 |
+
},
|
| 1872 |
+
{
|
| 1873 |
+
"id": "pa-129",
|
| 1874 |
+
"prompt": "ride share to target on broadway uber",
|
| 1875 |
+
"expected": {
|
| 1876 |
+
"skill": "uber_request_ride",
|
| 1877 |
+
"parameters": {
|
| 1878 |
+
"destination": "target on broadway"
|
| 1879 |
+
}
|
| 1880 |
+
},
|
| 1881 |
+
"domain": "uber",
|
| 1882 |
+
"styles": [
|
| 1883 |
+
"incomplete"
|
| 1884 |
+
]
|
| 1885 |
+
},
|
| 1886 |
+
{
|
| 1887 |
+
"id": "pa-130",
|
| 1888 |
+
"prompt": "uber me over to the marriott hotel",
|
| 1889 |
+
"expected": {
|
| 1890 |
+
"skill": "uber_request_ride",
|
| 1891 |
+
"parameters": {
|
| 1892 |
+
"destination": "the marriott hotel"
|
| 1893 |
+
}
|
| 1894 |
+
},
|
| 1895 |
+
"domain": "uber",
|
| 1896 |
+
"styles": [
|
| 1897 |
+
"slang",
|
| 1898 |
+
"incomplete"
|
| 1899 |
+
]
|
| 1900 |
+
},
|
| 1901 |
+
{
|
| 1902 |
+
"id": "pa-131",
|
| 1903 |
+
"prompt": "need uber to physio appointment on elm",
|
| 1904 |
+
"expected": {
|
| 1905 |
+
"skill": "uber_request_ride",
|
| 1906 |
+
"parameters": {
|
| 1907 |
+
"destination": "physio appointment on elm"
|
| 1908 |
+
}
|
| 1909 |
+
},
|
| 1910 |
+
"domain": "uber",
|
| 1911 |
+
"styles": [
|
| 1912 |
+
"incomplete"
|
| 1913 |
+
]
|
| 1914 |
+
},
|
| 1915 |
+
{
|
| 1916 |
+
"id": "pa-132",
|
| 1917 |
+
"prompt": "call uber to the dog park",
|
| 1918 |
+
"expected": {
|
| 1919 |
+
"skill": "uber_request_ride",
|
| 1920 |
+
"parameters": {
|
| 1921 |
+
"destination": "the dog park"
|
| 1922 |
+
}
|
| 1923 |
+
},
|
| 1924 |
+
"domain": "uber",
|
| 1925 |
+
"styles": [
|
| 1926 |
+
"incomplete"
|
| 1927 |
+
]
|
| 1928 |
+
},
|
| 1929 |
+
{
|
| 1930 |
+
"id": "pa-133",
|
| 1931 |
+
"prompt": "gmal email recruiter saying im interested in the role",
|
| 1932 |
+
"expected": {
|
| 1933 |
+
"skill": "gmail_send_email",
|
| 1934 |
+
"parameters": {
|
| 1935 |
+
"recipient": "recruiter",
|
| 1936 |
+
"message": "im interested in the role"
|
| 1937 |
+
}
|
| 1938 |
+
},
|
| 1939 |
+
"domain": "gmail",
|
| 1940 |
+
"styles": [
|
| 1941 |
+
"typo",
|
| 1942 |
+
"incomplete"
|
| 1943 |
+
]
|
| 1944 |
+
},
|
| 1945 |
+
{
|
| 1946 |
+
"id": "pa-134",
|
| 1947 |
+
"prompt": "shoot an email via gmail to finance@acme.co \u2014 invoice overdue",
|
| 1948 |
+
"expected": {
|
| 1949 |
+
"skill": "gmail_send_email",
|
| 1950 |
+
"parameters": {
|
| 1951 |
+
"recipient": "finance@acme.co",
|
| 1952 |
+
"message": "invoice overdue"
|
| 1953 |
+
}
|
| 1954 |
+
},
|
| 1955 |
+
"domain": "gmail",
|
| 1956 |
+
"styles": [
|
| 1957 |
+
"slang",
|
| 1958 |
+
"conversational"
|
| 1959 |
+
]
|
| 1960 |
+
},
|
| 1961 |
+
{
|
| 1962 |
+
"id": "pa-135",
|
| 1963 |
+
"prompt": "gmail landlord asking about lease renewal",
|
| 1964 |
+
"expected": {
|
| 1965 |
+
"skill": "gmail_send_email",
|
| 1966 |
+
"parameters": {
|
| 1967 |
+
"recipient": "landlord",
|
| 1968 |
+
"message": "asking about lease renewal"
|
| 1969 |
+
}
|
| 1970 |
+
},
|
| 1971 |
+
"domain": "gmail",
|
| 1972 |
+
"styles": [
|
| 1973 |
+
"incomplete"
|
| 1974 |
+
]
|
| 1975 |
+
},
|
| 1976 |
+
{
|
| 1977 |
+
"id": "pa-136",
|
| 1978 |
+
"prompt": "email my manager on gmail: heads up sick day tomorrow",
|
| 1979 |
+
"expected": {
|
| 1980 |
+
"skill": "gmail_send_email",
|
| 1981 |
+
"parameters": {
|
| 1982 |
+
"recipient": "my manager",
|
| 1983 |
+
"message": "heads up sick day tomorrow"
|
| 1984 |
+
}
|
| 1985 |
+
},
|
| 1986 |
+
"domain": "gmail",
|
| 1987 |
+
"styles": [
|
| 1988 |
+
"conversational"
|
| 1989 |
+
]
|
| 1990 |
+
},
|
| 1991 |
+
{
|
| 1992 |
+
"id": "pa-137",
|
| 1993 |
+
"prompt": "compose gmail to nora.park@design.io portfolio link attached",
|
| 1994 |
+
"expected": {
|
| 1995 |
+
"skill": "gmail_send_email",
|
| 1996 |
+
"parameters": {
|
| 1997 |
+
"recipient": "nora.park@design.io",
|
| 1998 |
+
"message": "portfolio link attached"
|
| 1999 |
+
}
|
| 2000 |
+
},
|
| 2001 |
+
"domain": "gmail",
|
| 2002 |
+
"styles": [
|
| 2003 |
+
"incomplete"
|
| 2004 |
+
]
|
| 2005 |
+
},
|
| 2006 |
+
{
|
| 2007 |
+
"id": "pa-138",
|
| 2008 |
+
"prompt": "gmail msg to thesis advisor draft attached for review",
|
| 2009 |
+
"expected": {
|
| 2010 |
+
"skill": "gmail_send_email",
|
| 2011 |
+
"parameters": {
|
| 2012 |
+
"recipient": "thesis advisor",
|
| 2013 |
+
"message": "draft attached for review"
|
| 2014 |
+
}
|
| 2015 |
+
},
|
| 2016 |
+
"domain": "gmail",
|
| 2017 |
+
"styles": [
|
| 2018 |
+
"incomplete"
|
| 2019 |
+
]
|
| 2020 |
+
},
|
| 2021 |
+
{
|
| 2022 |
+
"id": "pa-139",
|
| 2023 |
+
"prompt": "send gmail to carpool buddy running 10 late",
|
| 2024 |
+
"expected": {
|
| 2025 |
+
"skill": "gmail_send_email",
|
| 2026 |
+
"parameters": {
|
| 2027 |
+
"recipient": "carpool buddy",
|
| 2028 |
+
"message": "running 10 late"
|
| 2029 |
+
}
|
| 2030 |
+
},
|
| 2031 |
+
"domain": "gmail",
|
| 2032 |
+
"styles": [
|
| 2033 |
+
"slang",
|
| 2034 |
+
"incomplete"
|
| 2035 |
+
]
|
| 2036 |
+
},
|
| 2037 |
+
{
|
| 2038 |
+
"id": "pa-140",
|
| 2039 |
+
"prompt": "write gmail to billing@saas.com cancel subscription pls",
|
| 2040 |
+
"expected": {
|
| 2041 |
+
"skill": "gmail_send_email",
|
| 2042 |
+
"parameters": {
|
| 2043 |
+
"recipient": "billing@saas.com",
|
| 2044 |
+
"message": "cancel subscription pls"
|
| 2045 |
+
}
|
| 2046 |
+
},
|
| 2047 |
+
"domain": "gmail",
|
| 2048 |
+
"styles": [
|
| 2049 |
+
"conversational"
|
| 2050 |
+
]
|
| 2051 |
+
},
|
| 2052 |
+
{
|
| 2053 |
+
"id": "pa-141",
|
| 2054 |
+
"prompt": "gmail email mentor thx for the intro",
|
| 2055 |
+
"expected": {
|
| 2056 |
+
"skill": "gmail_send_email",
|
| 2057 |
+
"parameters": {
|
| 2058 |
+
"recipient": "mentor",
|
| 2059 |
+
"message": "thx for the intro"
|
| 2060 |
+
}
|
| 2061 |
+
},
|
| 2062 |
+
"domain": "gmail",
|
| 2063 |
+
"styles": [
|
| 2064 |
+
"slang",
|
| 2065 |
+
"incomplete"
|
| 2066 |
+
]
|
| 2067 |
+
},
|
| 2068 |
+
{
|
| 2069 |
+
"id": "pa-142",
|
| 2070 |
+
"prompt": "ok gmail to ops-team@startup.dev deploy failed rollback done",
|
| 2071 |
+
"expected": {
|
| 2072 |
+
"skill": "gmail_send_email",
|
| 2073 |
+
"parameters": {
|
| 2074 |
+
"recipient": "ops-team@startup.dev",
|
| 2075 |
+
"message": "deploy failed rollback done"
|
| 2076 |
+
}
|
| 2077 |
+
},
|
| 2078 |
+
"domain": "gmail",
|
| 2079 |
+
"styles": [
|
| 2080 |
+
"conversational",
|
| 2081 |
+
"incomplete"
|
| 2082 |
+
]
|
| 2083 |
+
},
|
| 2084 |
+
{
|
| 2085 |
+
"id": "pa-143",
|
| 2086 |
+
"prompt": "email thru gmail to dentist office reschedule appt",
|
| 2087 |
+
"expected": {
|
| 2088 |
+
"skill": "gmail_send_email",
|
| 2089 |
+
"parameters": {
|
| 2090 |
+
"recipient": "dentist office",
|
| 2091 |
+
"message": "reschedule appt"
|
| 2092 |
+
}
|
| 2093 |
+
},
|
| 2094 |
+
"domain": "gmail",
|
| 2095 |
+
"styles": [
|
| 2096 |
+
"incomplete"
|
| 2097 |
+
]
|
| 2098 |
+
},
|
| 2099 |
+
{
|
| 2100 |
+
"id": "pa-144",
|
| 2101 |
+
"prompt": "gmail send to contractor quote approved lets proceed",
|
| 2102 |
+
"expected": {
|
| 2103 |
+
"skill": "gmail_send_email",
|
| 2104 |
+
"parameters": {
|
| 2105 |
+
"recipient": "contractor",
|
| 2106 |
+
"message": "quote approved lets proceed"
|
| 2107 |
+
}
|
| 2108 |
+
},
|
| 2109 |
+
"domain": "gmail",
|
| 2110 |
+
"styles": [
|
| 2111 |
+
"incomplete"
|
| 2112 |
+
]
|
| 2113 |
+
},
|
| 2114 |
+
{
|
| 2115 |
+
"id": "pa-145",
|
| 2116 |
+
"prompt": "hey gmail my professor question about deadline extension",
|
| 2117 |
+
"expected": {
|
| 2118 |
+
"skill": "gmail_send_email",
|
| 2119 |
+
"parameters": {
|
| 2120 |
+
"recipient": "my professor",
|
| 2121 |
+
"message": "question about deadline extension"
|
| 2122 |
+
}
|
| 2123 |
+
},
|
| 2124 |
+
"domain": "gmail",
|
| 2125 |
+
"styles": [
|
| 2126 |
+
"conversational",
|
| 2127 |
+
"incomplete"
|
| 2128 |
+
]
|
| 2129 |
+
},
|
| 2130 |
+
{
|
| 2131 |
+
"id": "pa-146",
|
| 2132 |
+
"prompt": "gmail to hr@corp.net pto request for next week",
|
| 2133 |
+
"expected": {
|
| 2134 |
+
"skill": "gmail_send_email",
|
| 2135 |
+
"parameters": {
|
| 2136 |
+
"recipient": "hr@corp.net",
|
| 2137 |
+
"message": "pto request for next week"
|
| 2138 |
+
}
|
| 2139 |
+
},
|
| 2140 |
+
"domain": "gmail",
|
| 2141 |
+
"styles": [
|
| 2142 |
+
"incomplete"
|
| 2143 |
+
]
|
| 2144 |
+
},
|
| 2145 |
+
{
|
| 2146 |
+
"id": "pa-147",
|
| 2147 |
+
"prompt": "send email gmail to vendor payment sent today",
|
| 2148 |
+
"expected": {
|
| 2149 |
+
"skill": "gmail_send_email",
|
| 2150 |
+
"parameters": {
|
| 2151 |
+
"recipient": "vendor",
|
| 2152 |
+
"message": "payment sent today"
|
| 2153 |
+
}
|
| 2154 |
+
},
|
| 2155 |
+
"domain": "gmail",
|
| 2156 |
+
"styles": [
|
| 2157 |
+
"incomplete"
|
| 2158 |
+
]
|
| 2159 |
+
},
|
| 2160 |
+
{
|
| 2161 |
+
"id": "pa-148",
|
| 2162 |
+
"prompt": "gmial client@studio.com mockups ready for feedback",
|
| 2163 |
+
"expected": {
|
| 2164 |
+
"skill": "gmail_send_email",
|
| 2165 |
+
"parameters": {
|
| 2166 |
+
"recipient": "client@studio.com",
|
| 2167 |
+
"message": "mockups ready for feedback"
|
| 2168 |
+
}
|
| 2169 |
+
},
|
| 2170 |
+
"domain": "gmail",
|
| 2171 |
+
"styles": [
|
| 2172 |
+
"typo",
|
| 2173 |
+
"incomplete"
|
| 2174 |
+
]
|
| 2175 |
+
},
|
| 2176 |
+
{
|
| 2177 |
+
"id": "pa-149",
|
| 2178 |
+
"prompt": "compose email on gmail to roommate utilities split attached",
|
| 2179 |
+
"expected": {
|
| 2180 |
+
"skill": "gmail_send_email",
|
| 2181 |
+
"parameters": {
|
| 2182 |
+
"recipient": "roommate",
|
| 2183 |
+
"message": "utilities split attached"
|
| 2184 |
+
}
|
| 2185 |
+
},
|
| 2186 |
+
"domain": "gmail",
|
| 2187 |
+
"styles": [
|
| 2188 |
+
"incomplete"
|
| 2189 |
+
]
|
| 2190 |
+
},
|
| 2191 |
+
{
|
| 2192 |
+
"id": "pa-150",
|
| 2193 |
+
"prompt": "gmail to travel agent pls confirm hotel booking",
|
| 2194 |
+
"expected": {
|
| 2195 |
+
"skill": "gmail_send_email",
|
| 2196 |
+
"parameters": {
|
| 2197 |
+
"recipient": "travel agent",
|
| 2198 |
+
"message": "pls confirm hotel booking"
|
| 2199 |
+
}
|
| 2200 |
+
},
|
| 2201 |
+
"domain": "gmail",
|
| 2202 |
+
"styles": [
|
| 2203 |
+
"conversational"
|
| 2204 |
+
]
|
| 2205 |
+
},
|
| 2206 |
+
{
|
| 2207 |
+
"id": "pa-151",
|
| 2208 |
+
"prompt": "email gmail coach thanks for the session notes",
|
| 2209 |
+
"expected": {
|
| 2210 |
+
"skill": "gmail_send_email",
|
| 2211 |
+
"parameters": {
|
| 2212 |
+
"recipient": "coach",
|
| 2213 |
+
"message": "thanks for the session notes"
|
| 2214 |
+
}
|
| 2215 |
+
},
|
| 2216 |
+
"domain": "gmail",
|
| 2217 |
+
"styles": [
|
| 2218 |
+
"incomplete"
|
| 2219 |
+
]
|
| 2220 |
+
},
|
| 2221 |
+
{
|
| 2222 |
+
"id": "pa-152",
|
| 2223 |
+
"prompt": "gmail msg to editor final chapter attached",
|
| 2224 |
+
"expected": {
|
| 2225 |
+
"skill": "gmail_send_email",
|
| 2226 |
+
"parameters": {
|
| 2227 |
+
"recipient": "editor",
|
| 2228 |
+
"message": "final chapter attached"
|
| 2229 |
+
}
|
| 2230 |
+
},
|
| 2231 |
+
"domain": "gmail",
|
| 2232 |
+
"styles": [
|
| 2233 |
+
"incomplete"
|
| 2234 |
+
]
|
| 2235 |
+
},
|
| 2236 |
+
{
|
| 2237 |
+
"id": "pa-153",
|
| 2238 |
+
"prompt": "send gmail to internship lead following up on application",
|
| 2239 |
+
"expected": {
|
| 2240 |
+
"skill": "gmail_send_email",
|
| 2241 |
+
"parameters": {
|
| 2242 |
+
"recipient": "internship lead",
|
| 2243 |
+
"message": "following up on application"
|
| 2244 |
+
}
|
| 2245 |
+
},
|
| 2246 |
+
"domain": "gmail",
|
| 2247 |
+
"styles": [
|
| 2248 |
+
"incomplete"
|
| 2249 |
+
]
|
| 2250 |
+
},
|
| 2251 |
+
{
|
| 2252 |
+
"id": "pa-154",
|
| 2253 |
+
"prompt": "gmail email to dad flight details attached",
|
| 2254 |
+
"expected": {
|
| 2255 |
+
"skill": "gmail_send_email",
|
| 2256 |
+
"parameters": {
|
| 2257 |
+
"recipient": "dad",
|
| 2258 |
+
"message": "flight details attached"
|
| 2259 |
+
}
|
| 2260 |
+
},
|
| 2261 |
+
"domain": "gmail",
|
| 2262 |
+
"styles": [
|
| 2263 |
+
"incomplete"
|
| 2264 |
+
]
|
| 2265 |
+
},
|
| 2266 |
+
{
|
| 2267 |
+
"id": "pa-155",
|
| 2268 |
+
"prompt": "put dentist on calender next thurs 3pm",
|
| 2269 |
+
"expected": {
|
| 2270 |
+
"skill": "calendar_create_event",
|
| 2271 |
+
"parameters": {
|
| 2272 |
+
"title": "dentist",
|
| 2273 |
+
"date": "next thurs",
|
| 2274 |
+
"time": "3pm"
|
| 2275 |
+
}
|
| 2276 |
+
},
|
| 2277 |
+
"domain": "calendar",
|
| 2278 |
+
"styles": [
|
| 2279 |
+
"typo",
|
| 2280 |
+
"incomplete"
|
| 2281 |
+
]
|
| 2282 |
+
},
|
| 2283 |
+
{
|
| 2284 |
+
"id": "pa-156",
|
| 2285 |
+
"prompt": "schedule 1:1 w/ manager monday 11am",
|
| 2286 |
+
"expected": {
|
| 2287 |
+
"skill": "calendar_create_event",
|
| 2288 |
+
"parameters": {
|
| 2289 |
+
"title": "1:1 w/ manager",
|
| 2290 |
+
"date": "monday",
|
| 2291 |
+
"time": "11am"
|
| 2292 |
+
}
|
| 2293 |
+
},
|
| 2294 |
+
"domain": "calendar",
|
| 2295 |
+
"styles": [
|
| 2296 |
+
"slang",
|
| 2297 |
+
"incomplete"
|
| 2298 |
+
]
|
| 2299 |
+
},
|
| 2300 |
+
{
|
| 2301 |
+
"id": "pa-157",
|
| 2302 |
+
"prompt": "add calendar block focus time tuesday 9-11",
|
| 2303 |
+
"expected": {
|
| 2304 |
+
"skill": "calendar_create_event",
|
| 2305 |
+
"parameters": {
|
| 2306 |
+
"title": "focus time",
|
| 2307 |
+
"date": "tuesday",
|
| 2308 |
+
"time": "9-11"
|
| 2309 |
+
}
|
| 2310 |
+
},
|
| 2311 |
+
"domain": "calendar",
|
| 2312 |
+
"styles": [
|
| 2313 |
+
"incomplete"
|
| 2314 |
+
]
|
| 2315 |
+
},
|
| 2316 |
+
{
|
| 2317 |
+
"id": "pa-158",
|
| 2318 |
+
"prompt": "book therapy session friday 5 pm on calendar",
|
| 2319 |
+
"expected": {
|
| 2320 |
+
"skill": "calendar_create_event",
|
| 2321 |
+
"parameters": {
|
| 2322 |
+
"title": "therapy session",
|
| 2323 |
+
"date": "friday",
|
| 2324 |
+
"time": "5 pm"
|
| 2325 |
+
}
|
| 2326 |
+
},
|
| 2327 |
+
"domain": "calendar",
|
| 2328 |
+
"styles": [
|
| 2329 |
+
"incomplete"
|
| 2330 |
+
]
|
| 2331 |
+
},
|
| 2332 |
+
{
|
| 2333 |
+
"id": "pa-159",
|
| 2334 |
+
"prompt": "cal event: team retro wednesday 4pm",
|
| 2335 |
+
"expected": {
|
| 2336 |
+
"skill": "calendar_create_event",
|
| 2337 |
+
"parameters": {
|
| 2338 |
+
"title": "team retro",
|
| 2339 |
+
"date": "wednesday",
|
| 2340 |
+
"time": "4pm"
|
| 2341 |
+
}
|
| 2342 |
+
},
|
| 2343 |
+
"domain": "calendar",
|
| 2344 |
+
"styles": [
|
| 2345 |
+
"slang",
|
| 2346 |
+
"incomplete"
|
| 2347 |
+
]
|
| 2348 |
+
},
|
| 2349 |
+
{
|
| 2350 |
+
"id": "pa-160",
|
| 2351 |
+
"prompt": "put parent teacher conf on calendar oct 12 6pm",
|
| 2352 |
+
"expected": {
|
| 2353 |
+
"skill": "calendar_create_event",
|
| 2354 |
+
"parameters": {
|
| 2355 |
+
"title": "parent teacher conf",
|
| 2356 |
+
"date": "oct 12",
|
| 2357 |
+
"time": "6pm"
|
| 2358 |
+
}
|
| 2359 |
+
},
|
| 2360 |
+
"domain": "calendar",
|
| 2361 |
+
"styles": [
|
| 2362 |
+
"incomplete"
|
| 2363 |
+
]
|
| 2364 |
+
},
|
| 2365 |
+
{
|
| 2366 |
+
"id": "pa-161",
|
| 2367 |
+
"prompt": "schedule gym class saturday 8 am calendar",
|
| 2368 |
+
"expected": {
|
| 2369 |
+
"skill": "calendar_create_event",
|
| 2370 |
+
"parameters": {
|
| 2371 |
+
"title": "gym class",
|
| 2372 |
+
"date": "saturday",
|
| 2373 |
+
"time": "8 am"
|
| 2374 |
+
}
|
| 2375 |
+
},
|
| 2376 |
+
"domain": "calendar",
|
| 2377 |
+
"styles": [
|
| 2378 |
+
"incomplete"
|
| 2379 |
+
]
|
| 2380 |
+
},
|
| 2381 |
+
{
|
| 2382 |
+
"id": "pa-162",
|
| 2383 |
+
"prompt": "add calendar entry flight to nyc sunday 7:45 am",
|
| 2384 |
+
"expected": {
|
| 2385 |
+
"skill": "calendar_create_event",
|
| 2386 |
+
"parameters": {
|
| 2387 |
+
"title": "flight to nyc",
|
| 2388 |
+
"date": "sunday",
|
| 2389 |
+
"time": "7:45 am"
|
| 2390 |
+
}
|
| 2391 |
+
},
|
| 2392 |
+
"domain": "calendar",
|
| 2393 |
+
"styles": [
|
| 2394 |
+
"incomplete"
|
| 2395 |
+
]
|
| 2396 |
+
},
|
| 2397 |
+
{
|
| 2398 |
+
"id": "pa-163",
|
| 2399 |
+
"prompt": "calendar \u2014 lunch w/ priya tomorrow noon",
|
| 2400 |
+
"expected": {
|
| 2401 |
+
"skill": "calendar_create_event",
|
| 2402 |
+
"parameters": {
|
| 2403 |
+
"title": "lunch w/ priya",
|
| 2404 |
+
"date": "tomorrow",
|
| 2405 |
+
"time": "noon"
|
| 2406 |
+
}
|
| 2407 |
+
},
|
| 2408 |
+
"domain": "calendar",
|
| 2409 |
+
"styles": [
|
| 2410 |
+
"slang",
|
| 2411 |
+
"incomplete",
|
| 2412 |
+
"conversational"
|
| 2413 |
+
]
|
| 2414 |
+
},
|
| 2415 |
+
{
|
| 2416 |
+
"id": "pa-164",
|
| 2417 |
+
"prompt": "set cal event sprint planning next monday 10",
|
| 2418 |
+
"expected": {
|
| 2419 |
+
"skill": "calendar_create_event",
|
| 2420 |
+
"parameters": {
|
| 2421 |
+
"title": "sprint planning",
|
| 2422 |
+
"date": "next monday",
|
| 2423 |
+
"time": "10"
|
| 2424 |
+
}
|
| 2425 |
+
},
|
| 2426 |
+
"domain": "calendar",
|
| 2427 |
+
"styles": [
|
| 2428 |
+
"slang",
|
| 2429 |
+
"incomplete"
|
| 2430 |
+
]
|
| 2431 |
+
},
|
| 2432 |
+
{
|
| 2433 |
+
"id": "pa-165",
|
| 2434 |
+
"prompt": "put car inspection on calendar tuesday 2:30 pm",
|
| 2435 |
+
"expected": {
|
| 2436 |
+
"skill": "calendar_create_event",
|
| 2437 |
+
"parameters": {
|
| 2438 |
+
"title": "car inspection",
|
| 2439 |
+
"date": "tuesday",
|
| 2440 |
+
"time": "2:30 pm"
|
| 2441 |
+
}
|
| 2442 |
+
},
|
| 2443 |
+
"domain": "calendar",
|
| 2444 |
+
"styles": [
|
| 2445 |
+
"incomplete"
|
| 2446 |
+
]
|
| 2447 |
+
},
|
| 2448 |
+
{
|
| 2449 |
+
"id": "pa-166",
|
| 2450 |
+
"prompt": "scheduel interview prep wednesday 6 pm",
|
| 2451 |
+
"expected": {
|
| 2452 |
+
"skill": "calendar_create_event",
|
| 2453 |
+
"parameters": {
|
| 2454 |
+
"title": "interview prep",
|
| 2455 |
+
"date": "wednesday",
|
| 2456 |
+
"time": "6 pm"
|
| 2457 |
+
}
|
| 2458 |
+
},
|
| 2459 |
+
"domain": "calendar",
|
| 2460 |
+
"styles": [
|
| 2461 |
+
"typo",
|
| 2462 |
+
"incomplete"
|
| 2463 |
+
]
|
| 2464 |
+
},
|
| 2465 |
+
{
|
| 2466 |
+
"id": "pa-167",
|
| 2467 |
+
"prompt": "add to calendar vet appt thursday 1 pm",
|
| 2468 |
+
"expected": {
|
| 2469 |
+
"skill": "calendar_create_event",
|
| 2470 |
+
"parameters": {
|
| 2471 |
+
"title": "vet appt",
|
| 2472 |
+
"date": "thursday",
|
| 2473 |
+
"time": "1 pm"
|
| 2474 |
+
}
|
| 2475 |
+
},
|
| 2476 |
+
"domain": "calendar",
|
| 2477 |
+
"styles": [
|
| 2478 |
+
"incomplete"
|
| 2479 |
+
]
|
| 2480 |
+
},
|
| 2481 |
+
{
|
| 2482 |
+
"id": "pa-168",
|
| 2483 |
+
"prompt": "calendar event board presentation friday 9 am",
|
| 2484 |
+
"expected": {
|
| 2485 |
+
"skill": "calendar_create_event",
|
| 2486 |
+
"parameters": {
|
| 2487 |
+
"title": "board presentation",
|
| 2488 |
+
"date": "friday",
|
| 2489 |
+
"time": "9 am"
|
| 2490 |
+
}
|
| 2491 |
+
},
|
| 2492 |
+
"domain": "calendar",
|
| 2493 |
+
"styles": [
|
| 2494 |
+
"incomplete"
|
| 2495 |
+
]
|
| 2496 |
+
},
|
| 2497 |
+
{
|
| 2498 |
+
"id": "pa-169",
|
| 2499 |
+
"prompt": "book calendar slot date night saturday 8 pm",
|
| 2500 |
+
"expected": {
|
| 2501 |
+
"skill": "calendar_create_event",
|
| 2502 |
+
"parameters": {
|
| 2503 |
+
"title": "date night",
|
| 2504 |
+
"date": "saturday",
|
| 2505 |
+
"time": "8 pm"
|
| 2506 |
+
}
|
| 2507 |
+
},
|
| 2508 |
+
"domain": "calendar",
|
| 2509 |
+
"styles": [
|
| 2510 |
+
"incomplete"
|
| 2511 |
+
]
|
| 2512 |
+
},
|
| 2513 |
+
{
|
| 2514 |
+
"id": "pa-170",
|
| 2515 |
+
"prompt": "put standup notes review on cal monday 9:30",
|
| 2516 |
+
"expected": {
|
| 2517 |
+
"skill": "calendar_create_event",
|
| 2518 |
+
"parameters": {
|
| 2519 |
+
"title": "standup notes review",
|
| 2520 |
+
"date": "monday",
|
| 2521 |
+
"time": "9:30"
|
| 2522 |
+
}
|
| 2523 |
+
},
|
| 2524 |
+
"domain": "calendar",
|
| 2525 |
+
"styles": [
|
| 2526 |
+
"slang",
|
| 2527 |
+
"incomplete"
|
| 2528 |
+
]
|
| 2529 |
+
},
|
| 2530 |
+
{
|
| 2531 |
+
"id": "pa-171",
|
| 2532 |
+
"prompt": "schedule movers arrival next saturday 10 am",
|
| 2533 |
+
"expected": {
|
| 2534 |
+
"skill": "calendar_create_event",
|
| 2535 |
+
"parameters": {
|
| 2536 |
+
"title": "movers arrival",
|
| 2537 |
+
"date": "next saturday",
|
| 2538 |
+
"time": "10 am"
|
| 2539 |
+
}
|
| 2540 |
+
},
|
| 2541 |
+
"domain": "calendar",
|
| 2542 |
+
"styles": [
|
| 2543 |
+
"incomplete"
|
| 2544 |
+
]
|
| 2545 |
+
},
|
| 2546 |
+
{
|
| 2547 |
+
"id": "pa-172",
|
| 2548 |
+
"prompt": "add calendar tax appointment april 5 11 am",
|
| 2549 |
+
"expected": {
|
| 2550 |
+
"skill": "calendar_create_event",
|
| 2551 |
+
"parameters": {
|
| 2552 |
+
"title": "tax appointment",
|
| 2553 |
+
"date": "april 5",
|
| 2554 |
+
"time": "11 am"
|
| 2555 |
+
}
|
| 2556 |
+
},
|
| 2557 |
+
"domain": "calendar",
|
| 2558 |
+
"styles": [
|
| 2559 |
+
"incomplete"
|
| 2560 |
+
]
|
| 2561 |
+
},
|
| 2562 |
+
{
|
| 2563 |
+
"id": "pa-173",
|
| 2564 |
+
"prompt": "calender block deep work thursday morning 8",
|
| 2565 |
+
"expected": {
|
| 2566 |
+
"skill": "calendar_create_event",
|
| 2567 |
+
"parameters": {
|
| 2568 |
+
"title": "deep work",
|
| 2569 |
+
"date": "thursday morning",
|
| 2570 |
+
"time": "8"
|
| 2571 |
+
}
|
| 2572 |
+
},
|
| 2573 |
+
"domain": "calendar",
|
| 2574 |
+
"styles": [
|
| 2575 |
+
"typo",
|
| 2576 |
+
"incomplete"
|
| 2577 |
+
]
|
| 2578 |
+
},
|
| 2579 |
+
{
|
| 2580 |
+
"id": "pa-174",
|
| 2581 |
+
"prompt": "put wedding rehearsal on calendar june 20 5 pm",
|
| 2582 |
+
"expected": {
|
| 2583 |
+
"skill": "calendar_create_event",
|
| 2584 |
+
"parameters": {
|
| 2585 |
+
"title": "wedding rehearsal",
|
| 2586 |
+
"date": "june 20",
|
| 2587 |
+
"time": "5 pm"
|
| 2588 |
+
}
|
| 2589 |
+
},
|
| 2590 |
+
"domain": "calendar",
|
| 2591 |
+
"styles": [
|
| 2592 |
+
"incomplete"
|
| 2593 |
+
]
|
| 2594 |
+
},
|
| 2595 |
+
{
|
| 2596 |
+
"id": "pa-175",
|
| 2597 |
+
"prompt": "schedule product demo next wednesday 3 pm",
|
| 2598 |
+
"expected": {
|
| 2599 |
+
"skill": "calendar_create_event",
|
| 2600 |
+
"parameters": {
|
| 2601 |
+
"title": "product demo",
|
| 2602 |
+
"date": "next wednesday",
|
| 2603 |
+
"time": "3 pm"
|
| 2604 |
+
}
|
| 2605 |
+
},
|
| 2606 |
+
"domain": "calendar",
|
| 2607 |
+
"styles": [
|
| 2608 |
+
"incomplete"
|
| 2609 |
+
]
|
| 2610 |
+
},
|
| 2611 |
+
{
|
| 2612 |
+
"id": "pa-176",
|
| 2613 |
+
"prompt": "add calendar coffee chat friday 10:30 am",
|
| 2614 |
+
"expected": {
|
| 2615 |
+
"skill": "calendar_create_event",
|
| 2616 |
+
"parameters": {
|
| 2617 |
+
"title": "coffee chat",
|
| 2618 |
+
"date": "friday",
|
| 2619 |
+
"time": "10:30 am"
|
| 2620 |
+
}
|
| 2621 |
+
},
|
| 2622 |
+
"domain": "calendar",
|
| 2623 |
+
"styles": [
|
| 2624 |
+
"incomplete"
|
| 2625 |
+
]
|
| 2626 |
+
},
|
| 2627 |
+
{
|
| 2628 |
+
"id": "pa-177",
|
| 2629 |
+
"prompt": "book calendar orthodontist monday 4:15 pm",
|
| 2630 |
+
"expected": {
|
| 2631 |
+
"skill": "calendar_create_event",
|
| 2632 |
+
"parameters": {
|
| 2633 |
+
"title": "orthodontist",
|
| 2634 |
+
"date": "monday",
|
| 2635 |
+
"time": "4:15 pm"
|
| 2636 |
+
}
|
| 2637 |
+
},
|
| 2638 |
+
"domain": "calendar",
|
| 2639 |
+
"styles": [
|
| 2640 |
+
"incomplete"
|
| 2641 |
+
]
|
| 2642 |
+
},
|
| 2643 |
+
{
|
| 2644 |
+
"id": "pa-178",
|
| 2645 |
+
"prompt": "put hackathon submission deadline on calendar sunday midnight",
|
| 2646 |
+
"expected": {
|
| 2647 |
+
"skill": "calendar_create_event",
|
| 2648 |
+
"parameters": {
|
| 2649 |
+
"title": "hackathon submission deadline",
|
| 2650 |
+
"date": "sunday",
|
| 2651 |
+
"time": "midnight"
|
| 2652 |
+
}
|
| 2653 |
+
},
|
| 2654 |
+
"domain": "calendar",
|
| 2655 |
+
"styles": [
|
| 2656 |
+
"incomplete"
|
| 2657 |
+
]
|
| 2658 |
+
},
|
| 2659 |
+
{
|
| 2660 |
+
"id": "pa-179",
|
| 2661 |
+
"prompt": "open slack #incidents pls",
|
| 2662 |
+
"expected": {
|
| 2663 |
+
"skill": "slack_open_channel",
|
| 2664 |
+
"parameters": {
|
| 2665 |
+
"channel": "incidents"
|
| 2666 |
+
}
|
| 2667 |
+
},
|
| 2668 |
+
"domain": "slack",
|
| 2669 |
+
"styles": [
|
| 2670 |
+
"incomplete",
|
| 2671 |
+
"conversational"
|
| 2672 |
+
]
|
| 2673 |
+
},
|
| 2674 |
+
{
|
| 2675 |
+
"id": "pa-180",
|
| 2676 |
+
"prompt": "jump to product-launch channel on slack",
|
| 2677 |
+
"expected": {
|
| 2678 |
+
"skill": "slack_open_channel",
|
| 2679 |
+
"parameters": {
|
| 2680 |
+
"channel": "product-launch"
|
| 2681 |
+
}
|
| 2682 |
+
},
|
| 2683 |
+
"domain": "slack",
|
| 2684 |
+
"styles": [
|
| 2685 |
+
"slang",
|
| 2686 |
+
"incomplete"
|
| 2687 |
+
]
|
| 2688 |
+
},
|
| 2689 |
+
{
|
| 2690 |
+
"id": "pa-181",
|
| 2691 |
+
"prompt": "slack go to team-watercooler",
|
| 2692 |
+
"expected": {
|
| 2693 |
+
"skill": "slack_open_channel",
|
| 2694 |
+
"parameters": {
|
| 2695 |
+
"channel": "team-watercooler"
|
| 2696 |
+
}
|
| 2697 |
+
},
|
| 2698 |
+
"domain": "slack",
|
| 2699 |
+
"styles": [
|
| 2700 |
+
"incomplete"
|
| 2701 |
+
]
|
| 2702 |
+
},
|
| 2703 |
+
{
|
| 2704 |
+
"id": "pa-182",
|
| 2705 |
+
"prompt": "pull up slack channel ios-bugs",
|
| 2706 |
+
"expected": {
|
| 2707 |
+
"skill": "slack_open_channel",
|
| 2708 |
+
"parameters": {
|
| 2709 |
+
"channel": "ios-bugs"
|
| 2710 |
+
}
|
| 2711 |
+
},
|
| 2712 |
+
"domain": "slack",
|
| 2713 |
+
"styles": [
|
| 2714 |
+
"slang",
|
| 2715 |
+
"incomplete"
|
| 2716 |
+
]
|
| 2717 |
+
},
|
| 2718 |
+
{
|
| 2719 |
+
"id": "pa-183",
|
| 2720 |
+
"prompt": "switch slack to growth-experiments",
|
| 2721 |
+
"expected": {
|
| 2722 |
+
"skill": "slack_open_channel",
|
| 2723 |
+
"parameters": {
|
| 2724 |
+
"channel": "growth-experiments"
|
| 2725 |
+
}
|
| 2726 |
+
},
|
| 2727 |
+
"domain": "slack",
|
| 2728 |
+
"styles": [
|
| 2729 |
+
"incomplete"
|
| 2730 |
+
]
|
| 2731 |
+
},
|
| 2732 |
+
{
|
| 2733 |
+
"id": "pa-184",
|
| 2734 |
+
"prompt": "show slack channel design-system",
|
| 2735 |
+
"expected": {
|
| 2736 |
+
"skill": "slack_open_channel",
|
| 2737 |
+
"parameters": {
|
| 2738 |
+
"channel": "design-system"
|
| 2739 |
+
}
|
| 2740 |
+
},
|
| 2741 |
+
"domain": "slack",
|
| 2742 |
+
"styles": [
|
| 2743 |
+
"incomplete"
|
| 2744 |
+
]
|
| 2745 |
+
},
|
| 2746 |
+
{
|
| 2747 |
+
"id": "pa-185",
|
| 2748 |
+
"prompt": "navigate slack to platform-oncall",
|
| 2749 |
+
"expected": {
|
| 2750 |
+
"skill": "slack_open_channel",
|
| 2751 |
+
"parameters": {
|
| 2752 |
+
"channel": "platform-oncall"
|
| 2753 |
+
}
|
| 2754 |
+
},
|
| 2755 |
+
"domain": "slack",
|
| 2756 |
+
"styles": [
|
| 2757 |
+
"incomplete"
|
| 2758 |
+
]
|
| 2759 |
+
},
|
| 2760 |
+
{
|
| 2761 |
+
"id": "pa-186",
|
| 2762 |
+
"prompt": "open the slack channel ml-research thx",
|
| 2763 |
+
"expected": {
|
| 2764 |
+
"skill": "slack_open_channel",
|
| 2765 |
+
"parameters": {
|
| 2766 |
+
"channel": "ml-research"
|
| 2767 |
+
}
|
| 2768 |
+
},
|
| 2769 |
+
"domain": "slack",
|
| 2770 |
+
"styles": [
|
| 2771 |
+
"conversational"
|
| 2772 |
+
]
|
| 2773 |
+
},
|
| 2774 |
+
{
|
| 2775 |
+
"id": "pa-187",
|
| 2776 |
+
"prompt": "slack channel customer-escalations open it",
|
| 2777 |
+
"expected": {
|
| 2778 |
+
"skill": "slack_open_channel",
|
| 2779 |
+
"parameters": {
|
| 2780 |
+
"channel": "customer-escalations"
|
| 2781 |
+
}
|
| 2782 |
+
},
|
| 2783 |
+
"domain": "slack",
|
| 2784 |
+
"styles": [
|
| 2785 |
+
"incomplete"
|
| 2786 |
+
]
|
| 2787 |
+
},
|
| 2788 |
+
{
|
| 2789 |
+
"id": "pa-188",
|
| 2790 |
+
"prompt": "take me to #backend-standup in slack",
|
| 2791 |
+
"expected": {
|
| 2792 |
+
"skill": "slack_open_channel",
|
| 2793 |
+
"parameters": {
|
| 2794 |
+
"channel": "backend-standup"
|
| 2795 |
+
}
|
| 2796 |
+
},
|
| 2797 |
+
"domain": "slack",
|
| 2798 |
+
"styles": [
|
| 2799 |
+
"incomplete"
|
| 2800 |
+
]
|
| 2801 |
+
},
|
| 2802 |
+
{
|
| 2803 |
+
"id": "pa-189",
|
| 2804 |
+
"prompt": "open slack for channel release-train",
|
| 2805 |
+
"expected": {
|
| 2806 |
+
"skill": "slack_open_channel",
|
| 2807 |
+
"parameters": {
|
| 2808 |
+
"channel": "release-train"
|
| 2809 |
+
}
|
| 2810 |
+
},
|
| 2811 |
+
"domain": "slack",
|
| 2812 |
+
"styles": [
|
| 2813 |
+
"incomplete"
|
| 2814 |
+
]
|
| 2815 |
+
},
|
| 2816 |
+
{
|
| 2817 |
+
"id": "pa-190",
|
| 2818 |
+
"prompt": "slack \u2014 show me hiring-pipeline channel",
|
| 2819 |
+
"expected": {
|
| 2820 |
+
"skill": "slack_open_channel",
|
| 2821 |
+
"parameters": {
|
| 2822 |
+
"channel": "hiring-pipeline"
|
| 2823 |
+
}
|
| 2824 |
+
},
|
| 2825 |
+
"domain": "slack",
|
| 2826 |
+
"styles": [
|
| 2827 |
+
"conversational",
|
| 2828 |
+
"incomplete"
|
| 2829 |
+
]
|
| 2830 |
+
},
|
| 2831 |
+
{
|
| 2832 |
+
"id": "pa-191",
|
| 2833 |
+
"prompt": "go slack channel infra-alerts",
|
| 2834 |
+
"expected": {
|
| 2835 |
+
"skill": "slack_open_channel",
|
| 2836 |
+
"parameters": {
|
| 2837 |
+
"channel": "infra-alerts"
|
| 2838 |
+
}
|
| 2839 |
+
},
|
| 2840 |
+
"domain": "slack",
|
| 2841 |
+
"styles": [
|
| 2842 |
+
"incomplete"
|
| 2843 |
+
]
|
| 2844 |
+
},
|
| 2845 |
+
{
|
| 2846 |
+
"id": "pa-192",
|
| 2847 |
+
"prompt": "open slak channel mobile-crash-reports",
|
| 2848 |
+
"expected": {
|
| 2849 |
+
"skill": "slack_open_channel",
|
| 2850 |
+
"parameters": {
|
| 2851 |
+
"channel": "mobile-crash-reports"
|
| 2852 |
+
}
|
| 2853 |
+
},
|
| 2854 |
+
"domain": "slack",
|
| 2855 |
+
"styles": [
|
| 2856 |
+
"typo",
|
| 2857 |
+
"incomplete"
|
| 2858 |
+
]
|
| 2859 |
+
},
|
| 2860 |
+
{
|
| 2861 |
+
"id": "pa-193",
|
| 2862 |
+
"prompt": "pull slack up on qa-automation channel",
|
| 2863 |
+
"expected": {
|
| 2864 |
+
"skill": "slack_open_channel",
|
| 2865 |
+
"parameters": {
|
| 2866 |
+
"channel": "qa-automation"
|
| 2867 |
+
}
|
| 2868 |
+
},
|
| 2869 |
+
"domain": "slack",
|
| 2870 |
+
"styles": [
|
| 2871 |
+
"incomplete"
|
| 2872 |
+
]
|
| 2873 |
+
},
|
| 2874 |
+
{
|
| 2875 |
+
"id": "pa-194",
|
| 2876 |
+
"prompt": "switch to slack channel docs-and-wiki",
|
| 2877 |
+
"expected": {
|
| 2878 |
+
"skill": "slack_open_channel",
|
| 2879 |
+
"parameters": {
|
| 2880 |
+
"channel": "docs-and-wiki"
|
| 2881 |
+
}
|
| 2882 |
+
},
|
| 2883 |
+
"domain": "slack",
|
| 2884 |
+
"styles": [
|
| 2885 |
+
"incomplete"
|
| 2886 |
+
]
|
| 2887 |
+
},
|
| 2888 |
+
{
|
| 2889 |
+
"id": "pa-195",
|
| 2890 |
+
"prompt": "open slack growth-metrics channel rq",
|
| 2891 |
+
"expected": {
|
| 2892 |
+
"skill": "slack_open_channel",
|
| 2893 |
+
"parameters": {
|
| 2894 |
+
"channel": "growth-metrics"
|
| 2895 |
+
}
|
| 2896 |
+
},
|
| 2897 |
+
"domain": "slack",
|
| 2898 |
+
"styles": [
|
| 2899 |
+
"slang",
|
| 2900 |
+
"incomplete"
|
| 2901 |
+
]
|
| 2902 |
+
},
|
| 2903 |
+
{
|
| 2904 |
+
"id": "pa-196",
|
| 2905 |
+
"prompt": "show me slack #sales-wins",
|
| 2906 |
+
"expected": {
|
| 2907 |
+
"skill": "slack_open_channel",
|
| 2908 |
+
"parameters": {
|
| 2909 |
+
"channel": "sales-wins"
|
| 2910 |
+
}
|
| 2911 |
+
},
|
| 2912 |
+
"domain": "slack",
|
| 2913 |
+
"styles": [
|
| 2914 |
+
"incomplete"
|
| 2915 |
+
]
|
| 2916 |
+
},
|
| 2917 |
+
{
|
| 2918 |
+
"id": "pa-197",
|
| 2919 |
+
"prompt": "navigate to slack channel partner-integrations",
|
| 2920 |
+
"expected": {
|
| 2921 |
+
"skill": "slack_open_channel",
|
| 2922 |
+
"parameters": {
|
| 2923 |
+
"channel": "partner-integrations"
|
| 2924 |
+
}
|
| 2925 |
+
},
|
| 2926 |
+
"domain": "slack",
|
| 2927 |
+
"styles": [
|
| 2928 |
+
"incomplete"
|
| 2929 |
+
]
|
| 2930 |
+
},
|
| 2931 |
+
{
|
| 2932 |
+
"id": "pa-198",
|
| 2933 |
+
"prompt": "open slack channel called observability",
|
| 2934 |
+
"expected": {
|
| 2935 |
+
"skill": "slack_open_channel",
|
| 2936 |
+
"parameters": {
|
| 2937 |
+
"channel": "observability"
|
| 2938 |
+
}
|
| 2939 |
+
},
|
| 2940 |
+
"domain": "slack",
|
| 2941 |
+
"styles": [
|
| 2942 |
+
"incomplete"
|
| 2943 |
+
]
|
| 2944 |
+
},
|
| 2945 |
+
{
|
| 2946 |
+
"id": "pa-199",
|
| 2947 |
+
"prompt": "slack open eng-hiring channel pls",
|
| 2948 |
+
"expected": {
|
| 2949 |
+
"skill": "slack_open_channel",
|
| 2950 |
+
"parameters": {
|
| 2951 |
+
"channel": "eng-hiring"
|
| 2952 |
+
}
|
| 2953 |
+
},
|
| 2954 |
+
"domain": "slack",
|
| 2955 |
+
"styles": [
|
| 2956 |
+
"incomplete",
|
| 2957 |
+
"conversational"
|
| 2958 |
+
]
|
| 2959 |
+
},
|
| 2960 |
+
{
|
| 2961 |
+
"id": "pa-200",
|
| 2962 |
+
"prompt": "jump slack to ai-playground channel",
|
| 2963 |
+
"expected": {
|
| 2964 |
+
"skill": "slack_open_channel",
|
| 2965 |
+
"parameters": {
|
| 2966 |
+
"channel": "ai-playground"
|
| 2967 |
+
}
|
| 2968 |
+
},
|
| 2969 |
+
"domain": "slack",
|
| 2970 |
+
"styles": [
|
| 2971 |
+
"slang",
|
| 2972 |
+
"incomplete"
|
| 2973 |
+
]
|
| 2974 |
+
}
|
| 2975 |
+
]
|
data/skill_schemas.json
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"create_alarm": {
|
| 3 |
+
"description": "Set an alarm at a specific time",
|
| 4 |
+
"parameters": {
|
| 5 |
+
"time": {
|
| 6 |
+
"type": "string",
|
| 7 |
+
"required": true,
|
| 8 |
+
"description": "Alarm time, e.g. 7 am, 6:30 pm"
|
| 9 |
+
},
|
| 10 |
+
"day": {
|
| 11 |
+
"type": "string",
|
| 12 |
+
"required": false,
|
| 13 |
+
"description": "Day or date, e.g. tomorrow, monday, today"
|
| 14 |
+
}
|
| 15 |
+
}
|
| 16 |
+
},
|
| 17 |
+
"calendar_create_event": {
|
| 18 |
+
"description": "Create a calendar event",
|
| 19 |
+
"parameters": {
|
| 20 |
+
"title": {
|
| 21 |
+
"type": "string",
|
| 22 |
+
"required": true,
|
| 23 |
+
"description": "Event title or description"
|
| 24 |
+
},
|
| 25 |
+
"date": {
|
| 26 |
+
"type": "string",
|
| 27 |
+
"required": true,
|
| 28 |
+
"description": "Event date, e.g. tomorrow, friday, next tuesday"
|
| 29 |
+
},
|
| 30 |
+
"time": {
|
| 31 |
+
"type": "string",
|
| 32 |
+
"required": true,
|
| 33 |
+
"description": "Event time, e.g. 4 pm, 10 am, noon"
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"wifi_enable": {
|
| 38 |
+
"description": "Enable Wi-Fi on the device",
|
| 39 |
+
"parameters": {}
|
| 40 |
+
},
|
| 41 |
+
"bluetooth_enable": {
|
| 42 |
+
"description": "Turn on Bluetooth",
|
| 43 |
+
"parameters": {}
|
| 44 |
+
},
|
| 45 |
+
"whatsapp_send_message": {
|
| 46 |
+
"description": "Send a WhatsApp message to a contact",
|
| 47 |
+
"parameters": {
|
| 48 |
+
"contact": {
|
| 49 |
+
"type": "string",
|
| 50 |
+
"required": true,
|
| 51 |
+
"description": "Recipient name or nickname"
|
| 52 |
+
},
|
| 53 |
+
"message": {
|
| 54 |
+
"type": "string",
|
| 55 |
+
"required": true,
|
| 56 |
+
"description": "Message body text"
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
},
|
| 60 |
+
"camera_take_photo": {
|
| 61 |
+
"description": "Open the camera and take a photo",
|
| 62 |
+
"parameters": {}
|
| 63 |
+
},
|
| 64 |
+
"slack_open_channel": {
|
| 65 |
+
"description": "Open a Slack channel",
|
| 66 |
+
"parameters": {
|
| 67 |
+
"channel": {
|
| 68 |
+
"type": "string",
|
| 69 |
+
"required": true,
|
| 70 |
+
"description": "Slack channel name"
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
},
|
| 74 |
+
"spotify_pause": {
|
| 75 |
+
"description": "Pause Spotify playback",
|
| 76 |
+
"parameters": {}
|
| 77 |
+
},
|
| 78 |
+
"spotify_play_playlist": {
|
| 79 |
+
"description": "Play a Spotify playlist",
|
| 80 |
+
"parameters": {
|
| 81 |
+
"playlist": {
|
| 82 |
+
"type": "string",
|
| 83 |
+
"required": true,
|
| 84 |
+
"description": "Playlist name"
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
"uber_request_ride": {
|
| 89 |
+
"description": "Request an Uber ride to a destination",
|
| 90 |
+
"parameters": {
|
| 91 |
+
"destination": {
|
| 92 |
+
"type": "string",
|
| 93 |
+
"required": true,
|
| 94 |
+
"description": "Ride destination address or place name"
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
},
|
| 98 |
+
"linkedin_search_person": {
|
| 99 |
+
"description": "Search for a person on LinkedIn",
|
| 100 |
+
"parameters": {
|
| 101 |
+
"name": {
|
| 102 |
+
"type": "string",
|
| 103 |
+
"required": true,
|
| 104 |
+
"description": "Person's full or partial name"
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
},
|
| 108 |
+
"spotify_search_play": {
|
| 109 |
+
"description": "Search Spotify and play music",
|
| 110 |
+
"parameters": {
|
| 111 |
+
"query": {
|
| 112 |
+
"type": "string",
|
| 113 |
+
"required": true,
|
| 114 |
+
"description": "Music search query"
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
},
|
| 118 |
+
"youtube_search": {
|
| 119 |
+
"description": "Search YouTube for videos",
|
| 120 |
+
"parameters": {
|
| 121 |
+
"query": {
|
| 122 |
+
"type": "string",
|
| 123 |
+
"required": true,
|
| 124 |
+
"description": "YouTube search query"
|
| 125 |
+
}
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
"contacts_search": {
|
| 129 |
+
"description": "Search for a contact in the phone contacts app",
|
| 130 |
+
"parameters": {
|
| 131 |
+
"contact": {
|
| 132 |
+
"type": "string",
|
| 133 |
+
"required": true,
|
| 134 |
+
"description": "Contact name to search for"
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
},
|
| 138 |
+
"gmail_send_email": {
|
| 139 |
+
"description": "Send an email via Gmail",
|
| 140 |
+
"parameters": {
|
| 141 |
+
"recipient": {
|
| 142 |
+
"type": "string",
|
| 143 |
+
"required": true,
|
| 144 |
+
"description": "Email address or recipient name"
|
| 145 |
+
},
|
| 146 |
+
"message": {
|
| 147 |
+
"type": "string",
|
| 148 |
+
"required": true,
|
| 149 |
+
"description": "Email body text"
|
| 150 |
+
}
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
}
|
modal_apps/evaluate_intent_modal.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate the fine-tuned intent-extraction model on Modal.
|
| 3 |
+
|
| 4 |
+
Prerequisites:
|
| 5 |
+
pip install modal
|
| 6 |
+
modal setup
|
| 7 |
+
python scripts/generate_intent_dataset.py
|
| 8 |
+
modal run modal_apps/train_modal.py # train on data/train_intent.jsonl
|
| 9 |
+
|
| 10 |
+
Run evaluation:
|
| 11 |
+
modal run modal_apps/evaluate_intent_modal.py
|
| 12 |
+
|
| 13 |
+
Reads eval_intent_prompts.json locally, uploads it to the dataset volume, loads the
|
| 14 |
+
LoRA adapter from the model volume, and reports per-prompt PASS/FAIL for skill +
|
| 15 |
+
parameters plus overall accuracy.
|
| 16 |
+
"""
|
| 17 |
+
|
| 18 |
+
from __future__ import annotations
|
| 19 |
+
|
| 20 |
+
import json
|
| 21 |
+
import pathlib
|
| 22 |
+
|
| 23 |
+
import modal
|
| 24 |
+
|
| 25 |
+
app = modal.App("android-intent-evaluate")
|
| 26 |
+
|
| 27 |
+
# ---------------------------------------------------------------------------
|
| 28 |
+
# Configuration
|
| 29 |
+
# ---------------------------------------------------------------------------
|
| 30 |
+
|
| 31 |
+
MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"
|
| 32 |
+
PROJECT_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
| 33 |
+
LOCAL_EVAL_PROMPTS = PROJECT_ROOT / "data" / "eval_intent_prompts.json"
|
| 34 |
+
REMOTE_EVAL_PROMPTS = "/data/eval_intent_prompts.json"
|
| 35 |
+
MODEL_DIR = pathlib.Path("/model")
|
| 36 |
+
ADAPTER_DIR = MODEL_DIR / "adapter"
|
| 37 |
+
MAX_SEQ_LENGTH = 2048
|
| 38 |
+
MAX_NEW_TOKENS = 128
|
| 39 |
+
|
| 40 |
+
INTENT_SYSTEM_PROMPT = (
|
| 41 |
+
"You extract structured Android automation intents from natural language. "
|
| 42 |
+
'Reply with JSON only: {"skill": "<skill_name>", "parameters": {<extracted_fields>}}. '
|
| 43 |
+
"Pick exactly one skill. Extract all relevant parameters mentioned in the request "
|
| 44 |
+
"(contact names, messages, times, destinations, channel names, search queries, etc.). "
|
| 45 |
+
"Use an empty object for parameters when the skill needs none. "
|
| 46 |
+
"Use the app or action named in the request (contacts, Gmail, Slack, YouTube, etc.) "
|
| 47 |
+
"to pick the correct skill."
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def build_intent_messages(user_content: str) -> list[dict[str, str]]:
|
| 52 |
+
return [
|
| 53 |
+
{"role": "system", "content": INTENT_SYSTEM_PROMPT},
|
| 54 |
+
{"role": "user", "content": user_content},
|
| 55 |
+
]
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
GPU_TYPE = "A10G"
|
| 59 |
+
TIMEOUT_SECONDS = 30 * 60
|
| 60 |
+
|
| 61 |
+
# ---------------------------------------------------------------------------
|
| 62 |
+
# Volumes
|
| 63 |
+
# ---------------------------------------------------------------------------
|
| 64 |
+
|
| 65 |
+
dataset_volume = modal.Volume.from_name(
|
| 66 |
+
"android-dataset-data",
|
| 67 |
+
create_if_missing=True,
|
| 68 |
+
)
|
| 69 |
+
model_volume = modal.Volume.from_name(
|
| 70 |
+
"android-dataset-model",
|
| 71 |
+
create_if_missing=True,
|
| 72 |
+
)
|
| 73 |
+
model_cache_volume = modal.Volume.from_name(
|
| 74 |
+
"android-dataset-hf-cache",
|
| 75 |
+
create_if_missing=True,
|
| 76 |
+
)
|
| 77 |
+
|
| 78 |
+
# ---------------------------------------------------------------------------
|
| 79 |
+
# Container image
|
| 80 |
+
# ---------------------------------------------------------------------------
|
| 81 |
+
|
| 82 |
+
eval_image = (
|
| 83 |
+
modal.Image.debian_slim(python_version="3.11")
|
| 84 |
+
.pip_install_from_requirements(
|
| 85 |
+
str(pathlib.Path(__file__).parent / "requirements-modal.txt")
|
| 86 |
+
)
|
| 87 |
+
.env(
|
| 88 |
+
{
|
| 89 |
+
"HF_HOME": "/model_cache",
|
| 90 |
+
"HF_HUB_ENABLE_HF_TRANSFER": "1",
|
| 91 |
+
}
|
| 92 |
+
)
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
with eval_image.imports():
|
| 96 |
+
import unsloth # noqa: F401 — must import before trl/transformers/peft
|
| 97 |
+
|
| 98 |
+
import torch
|
| 99 |
+
from peft import PeftModel
|
| 100 |
+
from unsloth import FastLanguageModel
|
| 101 |
+
from unsloth.chat_templates import get_chat_template
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def _parse_json_payload(text: str) -> dict | None:
|
| 105 |
+
text = text.strip()
|
| 106 |
+
if not text:
|
| 107 |
+
return None
|
| 108 |
+
|
| 109 |
+
start = text.find("{")
|
| 110 |
+
end = text.rfind("}")
|
| 111 |
+
if start == -1 or end == -1 or end <= start:
|
| 112 |
+
return None
|
| 113 |
+
|
| 114 |
+
try:
|
| 115 |
+
payload = json.loads(text[start : end + 1])
|
| 116 |
+
except json.JSONDecodeError:
|
| 117 |
+
return None
|
| 118 |
+
|
| 119 |
+
return payload if isinstance(payload, dict) else None
|
| 120 |
+
|
| 121 |
+
|
| 122 |
+
def extract_intent(text: str) -> dict | None:
|
| 123 |
+
payload = _parse_json_payload(text)
|
| 124 |
+
if not payload:
|
| 125 |
+
return None
|
| 126 |
+
|
| 127 |
+
skill = payload.get("skill")
|
| 128 |
+
if not isinstance(skill, str) or not skill:
|
| 129 |
+
return None
|
| 130 |
+
|
| 131 |
+
parameters = payload.get("parameters", {})
|
| 132 |
+
if parameters is None:
|
| 133 |
+
parameters = {}
|
| 134 |
+
if not isinstance(parameters, dict):
|
| 135 |
+
return None
|
| 136 |
+
|
| 137 |
+
return {"skill": skill, "parameters": parameters}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def normalize_param(value: str) -> str:
|
| 141 |
+
return " ".join(value.lower().strip().split())
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
def parameters_match(predicted: dict, expected: dict) -> bool:
|
| 145 |
+
for key, expected_value in expected.items():
|
| 146 |
+
predicted_value = predicted.get(key)
|
| 147 |
+
if predicted_value is None:
|
| 148 |
+
return False
|
| 149 |
+
if normalize_param(str(predicted_value)) != normalize_param(str(expected_value)):
|
| 150 |
+
return False
|
| 151 |
+
return True
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def intent_matches(predicted: dict | None, expected: dict) -> tuple[bool, bool]:
|
| 155 |
+
"""Return (skill_match, full_match)."""
|
| 156 |
+
if not predicted:
|
| 157 |
+
return False, False
|
| 158 |
+
|
| 159 |
+
expected_skill = expected["skill"]
|
| 160 |
+
expected_params = expected.get("parameters", {})
|
| 161 |
+
|
| 162 |
+
skill_match = predicted.get("skill") == expected_skill
|
| 163 |
+
if not skill_match:
|
| 164 |
+
return False, False
|
| 165 |
+
|
| 166 |
+
if not expected_params:
|
| 167 |
+
return True, True
|
| 168 |
+
|
| 169 |
+
params_match = parameters_match(predicted.get("parameters", {}), expected_params)
|
| 170 |
+
return True, params_match
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
# ---------------------------------------------------------------------------
|
| 174 |
+
# Evaluation
|
| 175 |
+
# ---------------------------------------------------------------------------
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
@app.function(
|
| 179 |
+
image=eval_image,
|
| 180 |
+
gpu=GPU_TYPE,
|
| 181 |
+
timeout=TIMEOUT_SECONDS,
|
| 182 |
+
volumes={
|
| 183 |
+
"/data": dataset_volume,
|
| 184 |
+
"/model": model_volume,
|
| 185 |
+
"/model_cache": model_cache_volume,
|
| 186 |
+
},
|
| 187 |
+
)
|
| 188 |
+
def evaluate() -> None:
|
| 189 |
+
dataset_volume.reload()
|
| 190 |
+
model_volume.reload()
|
| 191 |
+
|
| 192 |
+
eval_path = pathlib.Path(REMOTE_EVAL_PROMPTS)
|
| 193 |
+
if not eval_path.exists():
|
| 194 |
+
raise FileNotFoundError(
|
| 195 |
+
f"Eval prompts not found at {eval_path}. "
|
| 196 |
+
"Run `modal run modal_apps/evaluate_intent_modal.py` from the project directory."
|
| 197 |
+
)
|
| 198 |
+
|
| 199 |
+
if not (ADAPTER_DIR / "adapter_config.json").exists():
|
| 200 |
+
raise FileNotFoundError(
|
| 201 |
+
f"LoRA adapter not found at {ADAPTER_DIR}. "
|
| 202 |
+
"Run `modal run modal_apps/train_modal.py` first."
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
with eval_path.open(encoding="utf-8") as handle:
|
| 206 |
+
eval_prompts = json.load(handle)
|
| 207 |
+
|
| 208 |
+
print(f"Loading base model: {MODEL_NAME}")
|
| 209 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 210 |
+
model_name=MODEL_NAME,
|
| 211 |
+
max_seq_length=MAX_SEQ_LENGTH,
|
| 212 |
+
dtype=None,
|
| 213 |
+
load_in_4bit=True,
|
| 214 |
+
)
|
| 215 |
+
|
| 216 |
+
print(f"Loading LoRA adapter from {ADAPTER_DIR}")
|
| 217 |
+
model = PeftModel.from_pretrained(model, str(ADAPTER_DIR))
|
| 218 |
+
|
| 219 |
+
tokenizer = get_chat_template(
|
| 220 |
+
tokenizer,
|
| 221 |
+
chat_template="qwen-2.5",
|
| 222 |
+
)
|
| 223 |
+
|
| 224 |
+
FastLanguageModel.for_inference(model)
|
| 225 |
+
|
| 226 |
+
skill_passed = 0
|
| 227 |
+
full_passed = 0
|
| 228 |
+
total = len(eval_prompts)
|
| 229 |
+
|
| 230 |
+
print(f"Running intent evaluation on {total} prompts...\n")
|
| 231 |
+
|
| 232 |
+
for case in eval_prompts:
|
| 233 |
+
prompt = case["prompt"]
|
| 234 |
+
expected = case["expected"]
|
| 235 |
+
|
| 236 |
+
messages = build_intent_messages(prompt)
|
| 237 |
+
inputs = tokenizer.apply_chat_template(
|
| 238 |
+
messages,
|
| 239 |
+
tokenize=True,
|
| 240 |
+
add_generation_prompt=True,
|
| 241 |
+
return_tensors="pt",
|
| 242 |
+
).to("cuda")
|
| 243 |
+
|
| 244 |
+
with torch.inference_mode():
|
| 245 |
+
outputs = model.generate(
|
| 246 |
+
input_ids=inputs,
|
| 247 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 248 |
+
use_cache=True,
|
| 249 |
+
do_sample=False,
|
| 250 |
+
)
|
| 251 |
+
|
| 252 |
+
generated = outputs[0][inputs.shape[1] :]
|
| 253 |
+
raw_output = tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 254 |
+
predicted = extract_intent(raw_output)
|
| 255 |
+
skill_ok, full_ok = intent_matches(predicted, expected)
|
| 256 |
+
|
| 257 |
+
if skill_ok:
|
| 258 |
+
skill_passed += 1
|
| 259 |
+
if full_ok:
|
| 260 |
+
full_passed += 1
|
| 261 |
+
|
| 262 |
+
print(f"Prompt: {prompt}")
|
| 263 |
+
print(f"Expected: {json.dumps(expected, separators=(',', ':'))}")
|
| 264 |
+
print(f"Predicted: {json.dumps(predicted, separators=(',', ':')) if predicted else raw_output}")
|
| 265 |
+
print(f"Skill: {'PASS' if skill_ok else 'FAIL'} | Full: {'PASS' if full_ok else 'FAIL'}")
|
| 266 |
+
print()
|
| 267 |
+
|
| 268 |
+
skill_accuracy = skill_passed / total if total else 0.0
|
| 269 |
+
full_accuracy = full_passed / total if total else 0.0
|
| 270 |
+
|
| 271 |
+
print("--- Summary ---")
|
| 272 |
+
print(f"Total: {total}")
|
| 273 |
+
print(f"Skill accuracy: {skill_passed}/{total} ({skill_accuracy:.1%})")
|
| 274 |
+
print(f"Full intent accuracy: {full_passed}/{total} ({full_accuracy:.1%})")
|
| 275 |
+
|
| 276 |
+
|
| 277 |
+
# ---------------------------------------------------------------------------
|
| 278 |
+
# Local entrypoint
|
| 279 |
+
# ---------------------------------------------------------------------------
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
@app.local_entrypoint()
|
| 283 |
+
def main() -> None:
|
| 284 |
+
eval_path = pathlib.Path(LOCAL_EVAL_PROMPTS)
|
| 285 |
+
if not eval_path.exists():
|
| 286 |
+
raise FileNotFoundError(
|
| 287 |
+
f"Local eval prompts not found: {eval_path.resolve()}. "
|
| 288 |
+
"Run `python scripts/generate_intent_dataset.py` first."
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
remote_name = "eval_intent_prompts.json"
|
| 292 |
+
try:
|
| 293 |
+
dataset_volume.remove_file(remote_name)
|
| 294 |
+
except Exception:
|
| 295 |
+
pass # file may not exist yet on the volume
|
| 296 |
+
|
| 297 |
+
print(f"Uploading {eval_path} to dataset volume...")
|
| 298 |
+
with dataset_volume.batch_upload() as batch:
|
| 299 |
+
batch.put_file(str(eval_path), remote_name)
|
| 300 |
+
|
| 301 |
+
print("Launching intent evaluation on Modal GPU...")
|
| 302 |
+
evaluate.remote()
|
modal_apps/evaluate_pocket_benchmark_modal.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate the Pocket Automator real-world benchmark on Modal.
|
| 3 |
+
|
| 4 |
+
Prerequisites:
|
| 5 |
+
pip install modal
|
| 6 |
+
modal setup
|
| 7 |
+
python scripts/generate_pocket_benchmark.py
|
| 8 |
+
modal run modal_apps/train_modal.py
|
| 9 |
+
|
| 10 |
+
Run evaluation:
|
| 11 |
+
modal run modal_apps/evaluate_pocket_benchmark_modal.py
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import json
|
| 17 |
+
import pathlib
|
| 18 |
+
|
| 19 |
+
import modal
|
| 20 |
+
|
| 21 |
+
app = modal.App("pocket-automator-benchmark")
|
| 22 |
+
|
| 23 |
+
MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"
|
| 24 |
+
PROJECT_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
| 25 |
+
LOCAL_BENCHMARK_PROMPTS = PROJECT_ROOT / "data" / "pocket_benchmark_prompts.json"
|
| 26 |
+
REMOTE_BENCHMARK_PROMPTS = "/data/pocket_benchmark_prompts.json"
|
| 27 |
+
REMOTE_RESULTS = "/data/pocket_benchmark_results.json"
|
| 28 |
+
REMOTE_REPORT = "/data/pocket_benchmark_report.txt"
|
| 29 |
+
MODEL_DIR = pathlib.Path("/model")
|
| 30 |
+
ADAPTER_DIR = MODEL_DIR / "adapter"
|
| 31 |
+
MAX_SEQ_LENGTH = 2048
|
| 32 |
+
MAX_NEW_TOKENS = 128
|
| 33 |
+
|
| 34 |
+
INTENT_SYSTEM_PROMPT = (
|
| 35 |
+
"You extract structured Android automation intents from natural language. "
|
| 36 |
+
'Reply with JSON only: {"skill": "<skill_name>", "parameters": {<extracted_fields>}}. '
|
| 37 |
+
"Pick exactly one skill. Extract all relevant parameters mentioned in the request "
|
| 38 |
+
"(contact names, messages, times, destinations, channel names, search queries, etc.). "
|
| 39 |
+
"Use an empty object for parameters when the skill needs none. "
|
| 40 |
+
"Use the app or action named in the request (contacts, Gmail, Slack, YouTube, etc.) "
|
| 41 |
+
"to pick the correct skill."
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
GPU_TYPE = "A10G"
|
| 45 |
+
TIMEOUT_SECONDS = 45 * 60
|
| 46 |
+
|
| 47 |
+
dataset_volume = modal.Volume.from_name(
|
| 48 |
+
"android-dataset-data",
|
| 49 |
+
create_if_missing=True,
|
| 50 |
+
)
|
| 51 |
+
model_volume = modal.Volume.from_name(
|
| 52 |
+
"android-dataset-model",
|
| 53 |
+
create_if_missing=True,
|
| 54 |
+
)
|
| 55 |
+
model_cache_volume = modal.Volume.from_name(
|
| 56 |
+
"android-dataset-hf-cache",
|
| 57 |
+
create_if_missing=True,
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
eval_image = (
|
| 61 |
+
modal.Image.debian_slim(python_version="3.11")
|
| 62 |
+
.pip_install_from_requirements(
|
| 63 |
+
str(pathlib.Path(__file__).parent / "requirements-modal.txt")
|
| 64 |
+
)
|
| 65 |
+
.env(
|
| 66 |
+
{
|
| 67 |
+
"HF_HOME": "/model_cache",
|
| 68 |
+
"HF_HUB_ENABLE_HF_TRANSFER": "1",
|
| 69 |
+
"PYTHONPATH": "/root/src",
|
| 70 |
+
}
|
| 71 |
+
)
|
| 72 |
+
.add_local_dir(str(PROJECT_ROOT / "src"), remote_path="/root/src", copy=True)
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def build_intent_messages(user_content: str) -> list[dict[str, str]]:
|
| 77 |
+
return [
|
| 78 |
+
{"role": "system", "content": INTENT_SYSTEM_PROMPT},
|
| 79 |
+
{"role": "user", "content": user_content},
|
| 80 |
+
]
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
with eval_image.imports():
|
| 84 |
+
import unsloth # noqa: F401
|
| 85 |
+
|
| 86 |
+
import torch
|
| 87 |
+
from peft import PeftModel
|
| 88 |
+
from unsloth import FastLanguageModel
|
| 89 |
+
from unsloth.chat_templates import get_chat_template
|
| 90 |
+
|
| 91 |
+
from pocket_benchmark import record_result, save_benchmark_outputs
|
| 92 |
+
from skill_utils import extract_intent
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
@app.function(
|
| 96 |
+
image=eval_image,
|
| 97 |
+
gpu=GPU_TYPE,
|
| 98 |
+
timeout=TIMEOUT_SECONDS,
|
| 99 |
+
volumes={
|
| 100 |
+
"/data": dataset_volume,
|
| 101 |
+
"/model": model_volume,
|
| 102 |
+
"/model_cache": model_cache_volume,
|
| 103 |
+
},
|
| 104 |
+
)
|
| 105 |
+
def evaluate() -> None:
|
| 106 |
+
dataset_volume.reload()
|
| 107 |
+
model_volume.reload()
|
| 108 |
+
|
| 109 |
+
benchmark_path = pathlib.Path(REMOTE_BENCHMARK_PROMPTS)
|
| 110 |
+
if not benchmark_path.exists():
|
| 111 |
+
raise FileNotFoundError(
|
| 112 |
+
f"Benchmark prompts not found at {benchmark_path}. "
|
| 113 |
+
"Run `modal run modal_apps/evaluate_pocket_benchmark_modal.py` locally first."
|
| 114 |
+
)
|
| 115 |
+
|
| 116 |
+
if not (ADAPTER_DIR / "adapter_config.json").exists():
|
| 117 |
+
raise FileNotFoundError(
|
| 118 |
+
f"LoRA adapter not found at {ADAPTER_DIR}. "
|
| 119 |
+
"Run `modal run modal_apps/train_modal.py` first."
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
with benchmark_path.open(encoding="utf-8") as handle:
|
| 123 |
+
cases = json.load(handle)
|
| 124 |
+
|
| 125 |
+
print(f"Loading base model: {MODEL_NAME}")
|
| 126 |
+
model, tokenizer = FastLanguageModel.from_pretrained(
|
| 127 |
+
model_name=MODEL_NAME,
|
| 128 |
+
max_seq_length=MAX_SEQ_LENGTH,
|
| 129 |
+
dtype=None,
|
| 130 |
+
load_in_4bit=True,
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
print(f"Loading LoRA adapter from {ADAPTER_DIR}")
|
| 134 |
+
model = PeftModel.from_pretrained(model, str(ADAPTER_DIR))
|
| 135 |
+
|
| 136 |
+
tokenizer = get_chat_template(
|
| 137 |
+
tokenizer,
|
| 138 |
+
chat_template="qwen-2.5",
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
FastLanguageModel.for_inference(model)
|
| 142 |
+
|
| 143 |
+
results = []
|
| 144 |
+
total = len(cases)
|
| 145 |
+
print(f"Running Pocket Automator benchmark on {total} prompts...\n")
|
| 146 |
+
|
| 147 |
+
for index, case in enumerate(cases, start=1):
|
| 148 |
+
prompt = case["prompt"]
|
| 149 |
+
messages = build_intent_messages(prompt)
|
| 150 |
+
inputs = tokenizer.apply_chat_template(
|
| 151 |
+
messages,
|
| 152 |
+
tokenize=True,
|
| 153 |
+
add_generation_prompt=True,
|
| 154 |
+
return_tensors="pt",
|
| 155 |
+
).to("cuda")
|
| 156 |
+
|
| 157 |
+
with torch.inference_mode():
|
| 158 |
+
outputs = model.generate(
|
| 159 |
+
input_ids=inputs,
|
| 160 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 161 |
+
use_cache=True,
|
| 162 |
+
do_sample=False,
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
generated = outputs[0][inputs.shape[1] :]
|
| 166 |
+
raw_output = tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 167 |
+
predicted = extract_intent(raw_output)
|
| 168 |
+
result = record_result(case, raw_output, predicted)
|
| 169 |
+
results.append(result)
|
| 170 |
+
|
| 171 |
+
print(f"--- [{index}/{total}] {case.get('id', 'n/a')} ---")
|
| 172 |
+
print(f"Prompt: {prompt}")
|
| 173 |
+
print(f"Expected: {json.dumps(case['expected'], separators=(',', ':'))}")
|
| 174 |
+
print(
|
| 175 |
+
f"Predicted: {json.dumps(predicted, separators=(',', ':')) if predicted else raw_output}"
|
| 176 |
+
)
|
| 177 |
+
print(
|
| 178 |
+
f"Skill: {'PASS' if result['skill_correct'] else 'FAIL'} | "
|
| 179 |
+
f"Params: {'PASS' if result['parameter_correct'] else 'FAIL'} | "
|
| 180 |
+
f"Exact JSON: {'PASS' if result['exact_json_match'] else 'FAIL'}"
|
| 181 |
+
)
|
| 182 |
+
print()
|
| 183 |
+
|
| 184 |
+
metrics, report = save_benchmark_outputs(
|
| 185 |
+
results,
|
| 186 |
+
results_path=pathlib.Path(REMOTE_RESULTS),
|
| 187 |
+
report_path=pathlib.Path(REMOTE_REPORT),
|
| 188 |
+
)
|
| 189 |
+
dataset_volume.commit()
|
| 190 |
+
|
| 191 |
+
print("--- Pocket Automator Benchmark Summary ---")
|
| 192 |
+
print(report)
|
| 193 |
+
print(f"Saved results to {REMOTE_RESULTS}")
|
| 194 |
+
print(f"Saved report to {REMOTE_REPORT}")
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
@app.local_entrypoint()
|
| 198 |
+
def main() -> None:
|
| 199 |
+
benchmark_path = pathlib.Path(LOCAL_BENCHMARK_PROMPTS)
|
| 200 |
+
if not benchmark_path.exists():
|
| 201 |
+
raise FileNotFoundError(
|
| 202 |
+
f"Local benchmark prompts not found: {benchmark_path.resolve()}. "
|
| 203 |
+
"Run `python scripts/generate_pocket_benchmark.py` first."
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
remote_name = "pocket_benchmark_prompts.json"
|
| 207 |
+
try:
|
| 208 |
+
dataset_volume.remove_file(remote_name)
|
| 209 |
+
except Exception:
|
| 210 |
+
pass
|
| 211 |
+
|
| 212 |
+
print(f"Uploading {benchmark_path} to dataset volume...")
|
| 213 |
+
with dataset_volume.batch_upload() as batch:
|
| 214 |
+
batch.put_file(str(benchmark_path), remote_name)
|
| 215 |
+
|
| 216 |
+
print("Launching Pocket Automator benchmark on Modal GPU...")
|
| 217 |
+
evaluate.remote()
|
| 218 |
+
|
| 219 |
+
print(
|
| 220 |
+
"Benchmark complete. Results saved to Modal volume 'android-dataset-data':\n"
|
| 221 |
+
" - pocket_benchmark_results.json\n"
|
| 222 |
+
" - pocket_benchmark_report.txt"
|
| 223 |
+
)
|
modal_apps/predict_api.py
CHANGED
|
@@ -1,11 +1,11 @@
|
|
| 1 |
"""
|
| 2 |
-
|
| 3 |
|
| 4 |
Prerequisites:
|
| 5 |
pip install modal
|
| 6 |
pip install -r modal_apps/requirements-modal.txt
|
| 7 |
modal setup
|
| 8 |
-
modal run modal_apps/train_modal.py
|
| 9 |
|
| 10 |
Deploy (persistent HTTPS endpoint):
|
| 11 |
modal deploy modal_apps/predict_api.py
|
|
@@ -16,9 +16,9 @@ The deploy command prints the public URL, e.g.:
|
|
| 16 |
Test:
|
| 17 |
curl -X POST https://<workspace>--android-skill-predict-api-skillpredictor-web.modal.run/predict \\
|
| 18 |
-H "Content-Type: application/json" \\
|
| 19 |
-
-d '{"prompt": "
|
| 20 |
|
| 21 |
-
# -> {"skill":"
|
| 22 |
|
| 23 |
Develop locally (ephemeral URL, hot-reloads on file changes):
|
| 24 |
modal serve modal_apps/predict_api.py
|
|
@@ -29,9 +29,7 @@ Stop a deployed app:
|
|
| 29 |
|
| 30 |
from __future__ import annotations
|
| 31 |
|
| 32 |
-
import json
|
| 33 |
import pathlib
|
| 34 |
-
import re
|
| 35 |
|
| 36 |
import modal
|
| 37 |
|
|
@@ -46,20 +44,7 @@ PROJECT_ROOT = pathlib.Path(__file__).resolve().parent.parent
|
|
| 46 |
MODEL_DIR = pathlib.Path("/model")
|
| 47 |
ADAPTER_DIR = MODEL_DIR / "adapter"
|
| 48 |
MAX_SEQ_LENGTH = 2048
|
| 49 |
-
|
| 50 |
-
SYSTEM_PROMPT = (
|
| 51 |
-
"You classify Android automation requests into exactly one skill. "
|
| 52 |
-
'Reply with JSON only: {"skill": "<skill_name>"}. '
|
| 53 |
-
"Use the app or action named in the request (contacts, Gmail, Slack, YouTube, etc.) "
|
| 54 |
-
"to pick the correct skill."
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
def build_classifier_messages(user_content: str) -> list[dict[str, str]]:
|
| 59 |
-
return [
|
| 60 |
-
{"role": "system", "content": SYSTEM_PROMPT},
|
| 61 |
-
{"role": "user", "content": user_content},
|
| 62 |
-
]
|
| 63 |
|
| 64 |
GPU_TYPE = "A10G"
|
| 65 |
TIMEOUT_SECONDS = 10 * 60
|
|
@@ -99,30 +84,6 @@ with api_image.imports():
|
|
| 99 |
from unsloth.chat_templates import get_chat_template
|
| 100 |
|
| 101 |
|
| 102 |
-
def extract_skill(text: str) -> str | None:
|
| 103 |
-
"""Extract the skill name from model output JSON."""
|
| 104 |
-
text = text.strip()
|
| 105 |
-
if not text:
|
| 106 |
-
return None
|
| 107 |
-
|
| 108 |
-
match = re.search(r'\{[^{}]*"skill"\s*:\s*"([^"]+)"[^{}]*\}', text)
|
| 109 |
-
if match:
|
| 110 |
-
return match.group(1)
|
| 111 |
-
|
| 112 |
-
start = text.find("{")
|
| 113 |
-
end = text.rfind("}")
|
| 114 |
-
if start == -1 or end == -1 or end <= start:
|
| 115 |
-
return None
|
| 116 |
-
|
| 117 |
-
try:
|
| 118 |
-
payload = json.loads(text[start : end + 1])
|
| 119 |
-
except json.JSONDecodeError:
|
| 120 |
-
return None
|
| 121 |
-
|
| 122 |
-
skill = payload.get("skill")
|
| 123 |
-
return skill if isinstance(skill, str) and skill else None
|
| 124 |
-
|
| 125 |
-
|
| 126 |
# ---------------------------------------------------------------------------
|
| 127 |
# API (model loaded once per container via @modal.enter)
|
| 128 |
# ---------------------------------------------------------------------------
|
|
@@ -169,8 +130,11 @@ class SkillPredictor:
|
|
| 169 |
self.tokenizer = tokenizer
|
| 170 |
print("Model ready.")
|
| 171 |
|
| 172 |
-
def
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
| 174 |
inputs = self.tokenizer.apply_chat_template(
|
| 175 |
messages,
|
| 176 |
tokenize=True,
|
|
@@ -181,17 +145,26 @@ class SkillPredictor:
|
|
| 181 |
with torch.inference_mode():
|
| 182 |
outputs = self.model.generate(
|
| 183 |
input_ids=inputs,
|
| 184 |
-
max_new_tokens=
|
| 185 |
use_cache=True,
|
| 186 |
do_sample=False,
|
| 187 |
)
|
| 188 |
|
| 189 |
generated = outputs[0][inputs.shape[1] :]
|
| 190 |
raw_output = self.tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
-
|
| 194 |
-
return resolve_skill(raw_skill, prompt)
|
| 195 |
|
| 196 |
@modal.asgi_app()
|
| 197 |
def web(self):
|
|
@@ -221,16 +194,16 @@ class SkillPredictor:
|
|
| 221 |
},
|
| 222 |
)
|
| 223 |
|
| 224 |
-
|
| 225 |
-
if
|
| 226 |
return JSONResponse(
|
| 227 |
status_code=422,
|
| 228 |
content={
|
| 229 |
"error": "invalid_model_output",
|
| 230 |
-
"message": "Model did not return valid
|
| 231 |
},
|
| 232 |
)
|
| 233 |
-
return JSONResponse(content=
|
| 234 |
|
| 235 |
return Starlette(
|
| 236 |
routes=[
|
|
|
|
| 1 |
"""
|
| 2 |
+
Intent extraction API on Modal (FastAPI + globally loaded QLoRA model).
|
| 3 |
|
| 4 |
Prerequisites:
|
| 5 |
pip install modal
|
| 6 |
pip install -r modal_apps/requirements-modal.txt
|
| 7 |
modal setup
|
| 8 |
+
modal run modal_apps/train_modal.py --dataset train_intent.jsonl
|
| 9 |
|
| 10 |
Deploy (persistent HTTPS endpoint):
|
| 11 |
modal deploy modal_apps/predict_api.py
|
|
|
|
| 16 |
Test:
|
| 17 |
curl -X POST https://<workspace>--android-skill-predict-api-skillpredictor-web.modal.run/predict \\
|
| 18 |
-H "Content-Type: application/json" \\
|
| 19 |
+
-d '{"prompt": "text mom on whatsapp i am on my way"}'
|
| 20 |
|
| 21 |
+
# -> {"skill":"whatsapp_send_message","parameters":{"contact":"mom","message":"i am on my way"}}
|
| 22 |
|
| 23 |
Develop locally (ephemeral URL, hot-reloads on file changes):
|
| 24 |
modal serve modal_apps/predict_api.py
|
|
|
|
| 29 |
|
| 30 |
from __future__ import annotations
|
| 31 |
|
|
|
|
| 32 |
import pathlib
|
|
|
|
| 33 |
|
| 34 |
import modal
|
| 35 |
|
|
|
|
| 44 |
MODEL_DIR = pathlib.Path("/model")
|
| 45 |
ADAPTER_DIR = MODEL_DIR / "adapter"
|
| 46 |
MAX_SEQ_LENGTH = 2048
|
| 47 |
+
MAX_NEW_TOKENS = 128
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
GPU_TYPE = "A10G"
|
| 50 |
TIMEOUT_SECONDS = 10 * 60
|
|
|
|
| 84 |
from unsloth.chat_templates import get_chat_template
|
| 85 |
|
| 86 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
# ---------------------------------------------------------------------------
|
| 88 |
# API (model loaded once per container via @modal.enter)
|
| 89 |
# ---------------------------------------------------------------------------
|
|
|
|
| 130 |
self.tokenizer = tokenizer
|
| 131 |
print("Model ready.")
|
| 132 |
|
| 133 |
+
def _generate_intent(self, prompt: str) -> dict | None:
|
| 134 |
+
from classifier_prompt import build_intent_messages
|
| 135 |
+
from skill_utils import extract_intent, resolve_skill
|
| 136 |
+
|
| 137 |
+
messages = build_intent_messages(prompt)
|
| 138 |
inputs = self.tokenizer.apply_chat_template(
|
| 139 |
messages,
|
| 140 |
tokenize=True,
|
|
|
|
| 145 |
with torch.inference_mode():
|
| 146 |
outputs = self.model.generate(
|
| 147 |
input_ids=inputs,
|
| 148 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 149 |
use_cache=True,
|
| 150 |
do_sample=False,
|
| 151 |
)
|
| 152 |
|
| 153 |
generated = outputs[0][inputs.shape[1] :]
|
| 154 |
raw_output = self.tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 155 |
+
intent = extract_intent(raw_output)
|
| 156 |
+
if not intent:
|
| 157 |
+
return None
|
| 158 |
+
|
| 159 |
+
skill = resolve_skill(intent.get("skill"), prompt)
|
| 160 |
+
if not skill:
|
| 161 |
+
return None
|
| 162 |
+
|
| 163 |
+
parameters = intent.get("parameters", {})
|
| 164 |
+
if not isinstance(parameters, dict):
|
| 165 |
+
parameters = {}
|
| 166 |
|
| 167 |
+
return {"skill": skill, "parameters": parameters}
|
|
|
|
| 168 |
|
| 169 |
@modal.asgi_app()
|
| 170 |
def web(self):
|
|
|
|
| 194 |
},
|
| 195 |
)
|
| 196 |
|
| 197 |
+
intent = self._generate_intent(prompt.strip())
|
| 198 |
+
if intent is None:
|
| 199 |
return JSONResponse(
|
| 200 |
status_code=422,
|
| 201 |
content={
|
| 202 |
"error": "invalid_model_output",
|
| 203 |
+
"message": "Model did not return valid intent JSON.",
|
| 204 |
},
|
| 205 |
)
|
| 206 |
+
return JSONResponse(content=intent)
|
| 207 |
|
| 208 |
return Starlette(
|
| 209 |
routes=[
|
modal_apps/train_modal.py
CHANGED
|
@@ -220,16 +220,27 @@ def train() -> None:
|
|
| 220 |
|
| 221 |
|
| 222 |
@app.local_entrypoint()
|
| 223 |
-
def main() -> None:
|
| 224 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
if not dataset_path.exists():
|
| 226 |
raise FileNotFoundError(
|
| 227 |
f"Local dataset not found: {dataset_path.resolve()}"
|
| 228 |
)
|
| 229 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 230 |
print(f"Uploading {dataset_path} to dataset volume...")
|
| 231 |
with dataset_volume.batch_upload() as batch:
|
| 232 |
-
batch.put_file(str(dataset_path),
|
| 233 |
|
| 234 |
print("Launching training on Modal GPU...")
|
| 235 |
train.remote()
|
|
|
|
| 220 |
|
| 221 |
|
| 222 |
@app.local_entrypoint()
|
| 223 |
+
def main(dataset: str = "train_intent.jsonl") -> None:
|
| 224 |
+
"""Upload dataset and launch training.
|
| 225 |
+
|
| 226 |
+
Args:
|
| 227 |
+
dataset: Filename under data/ — use train_intent.jsonl for intent extraction.
|
| 228 |
+
"""
|
| 229 |
+
dataset_path = PROJECT_ROOT / "data" / dataset
|
| 230 |
if not dataset_path.exists():
|
| 231 |
raise FileNotFoundError(
|
| 232 |
f"Local dataset not found: {dataset_path.resolve()}"
|
| 233 |
)
|
| 234 |
|
| 235 |
+
remote_name = "train.jsonl"
|
| 236 |
+
try:
|
| 237 |
+
dataset_volume.remove_file(remote_name)
|
| 238 |
+
except Exception:
|
| 239 |
+
pass # file may not exist yet on the volume
|
| 240 |
+
|
| 241 |
print(f"Uploading {dataset_path} to dataset volume...")
|
| 242 |
with dataset_volume.batch_upload() as batch:
|
| 243 |
+
batch.put_file(str(dataset_path), remote_name)
|
| 244 |
|
| 245 |
print("Launching training on Modal GPU...")
|
| 246 |
train.remote()
|
scripts/generate_intent_dataset.py
ADDED
|
@@ -0,0 +1,949 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate intent-extraction training data with skill + parameters.
|
| 3 |
+
|
| 4 |
+
Produces:
|
| 5 |
+
data/train_intent.jsonl — SFT examples for Unsloth (1000+ per skill)
|
| 6 |
+
data/eval_intent_prompts.json — held-out evaluation prompts with expected intents
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
python scripts/generate_intent_dataset.py
|
| 10 |
+
python scripts/generate_intent_dataset.py --examples-per-skill 1200
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import random
|
| 18 |
+
import sys
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 22 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 23 |
+
|
| 24 |
+
from src.classifier_prompt import INTENT_SYSTEM_PROMPT # noqa: E402
|
| 25 |
+
|
| 26 |
+
SCHEMAS_FILE = PROJECT_ROOT / "data" / "skill_schemas.json"
|
| 27 |
+
SKILLS_FILE = PROJECT_ROOT / "data" / "skills.jsonl"
|
| 28 |
+
TRAIN_OUTPUT = PROJECT_ROOT / "data" / "train_intent.jsonl"
|
| 29 |
+
EVAL_OUTPUT = PROJECT_ROOT / "data" / "eval_intent_prompts.json"
|
| 30 |
+
|
| 31 |
+
DEFAULT_EXAMPLES_PER_SKILL = 1000
|
| 32 |
+
EVAL_PROMPTS_PER_SKILL = 6
|
| 33 |
+
RANDOM_SEED = 42
|
| 34 |
+
|
| 35 |
+
# ---------------------------------------------------------------------------
|
| 36 |
+
# Synthetic entity pools
|
| 37 |
+
# ---------------------------------------------------------------------------
|
| 38 |
+
|
| 39 |
+
CONTACTS = [
|
| 40 |
+
"Ri", "Biraj", "Sarah", "Alex", "Mom", "Dad", "Priya", "Maya", "Rahul",
|
| 41 |
+
"Neha", "Sam", "Anita", "Karan", "Boss", "John Smith", "Parag Shah",
|
| 42 |
+
"Arya Sheth", "Jane Doe", "Emma Wilson", "David Chen", "Lisa Park",
|
| 43 |
+
"Michael Brown", "Sophie Martin", "James Lee", "Nina Patel", "Tom",
|
| 44 |
+
"Ravi", "Sneha", "Vikram", "Ananya", "Chris", "Jordan", "Taylor",
|
| 45 |
+
"Morgan", "Casey", "Jamie", "Riley", "Avery", "Quinn", "Drew",
|
| 46 |
+
"grandma", "uncle raj", "aunt meera", "my roommate", "the landlord",
|
| 47 |
+
"dentist office", "doctor", "plumber", "electrician",
|
| 48 |
+
]
|
| 49 |
+
|
| 50 |
+
MESSAGES = [
|
| 51 |
+
"I'll be late", "see you soon", "thanks", "good morning", "on my way",
|
| 52 |
+
"be there in 5", "running late", "call me", "what's up", "good night",
|
| 53 |
+
"hello", "hi there", "ok", "sounds good", "got it", "no worries",
|
| 54 |
+
"can we reschedule", "meeting moved to 3 pm", "i'm stuck in traffic",
|
| 55 |
+
"leaving now", "almost there", "happy birthday", "congrats",
|
| 56 |
+
"let me know when you're free", "pick up milk on the way home",
|
| 57 |
+
"dinner at 8", "see you tonight", "thanks for your help",
|
| 58 |
+
"project update attached", "weekly report is ready",
|
| 59 |
+
"i'll send the docs tomorrow", "running 10 minutes late",
|
| 60 |
+
"are you available for a call", "let's catch up this weekend",
|
| 61 |
+
"the package arrived", "payment sent", "invoice attached",
|
| 62 |
+
"reminder for tomorrow's meeting", "flight lands at 6 pm",
|
| 63 |
+
]
|
| 64 |
+
|
| 65 |
+
ALARM_TIMES = [
|
| 66 |
+
"5 am", "5:30 am", "6 am", "6:15 am", "6:30 am", "7 am", "7:30 am",
|
| 67 |
+
"8 am", "8:15 am", "9 am", "10 am", "11 am", "noon", "12:30 pm",
|
| 68 |
+
"1 pm", "2 pm", "3 pm", "4 pm", "5 pm", "6 pm", "7 pm", "8 pm",
|
| 69 |
+
"9 pm", "10 pm", "11 pm",
|
| 70 |
+
]
|
| 71 |
+
|
| 72 |
+
ALARM_DAYS = [
|
| 73 |
+
"today", "tomorrow", "tomorrow morning", "monday", "tuesday",
|
| 74 |
+
"wednesday", "thursday", "friday", "saturday", "sunday",
|
| 75 |
+
"next monday", "next tuesday", "next friday", "this weekend",
|
| 76 |
+
]
|
| 77 |
+
|
| 78 |
+
CALENDAR_TITLES = [
|
| 79 |
+
"team standup", "dentist appointment", "lunch with alex", "project review",
|
| 80 |
+
"doctor visit", "interview", "gym session", "coffee with sam",
|
| 81 |
+
"presentation", "flight", "call with client", "dinner reservation",
|
| 82 |
+
"study session", "yoga class", "1 on 1 with manager", "birthday party",
|
| 83 |
+
"meeting with kriyanshi", "sync with team", "board meeting",
|
| 84 |
+
"parent teacher conference", "car service", "vet appointment",
|
| 85 |
+
"book club", "therapy session", "haircut", "tax appointment",
|
| 86 |
+
]
|
| 87 |
+
|
| 88 |
+
CALENDAR_DATES = [
|
| 89 |
+
"tomorrow", "today", "monday", "tuesday", "wednesday", "thursday",
|
| 90 |
+
"friday", "saturday", "sunday", "next tuesday", "next friday",
|
| 91 |
+
"next monday", "this weekend", "next week",
|
| 92 |
+
]
|
| 93 |
+
|
| 94 |
+
CALENDAR_TIMES = [
|
| 95 |
+
"7 am", "8 am", "9 am", "10 am", "11 am", "noon", "1 pm", "2 pm",
|
| 96 |
+
"3 pm", "4 pm", "5 pm", "6 pm", "7 pm", "8 pm",
|
| 97 |
+
]
|
| 98 |
+
|
| 99 |
+
PLAYLISTS = [
|
| 100 |
+
"liked songs", "workout", "chill", "edm", "discover weekly", "daily mix",
|
| 101 |
+
"release radar", "party", "road trip", "morning", "focus", "jazz",
|
| 102 |
+
"top hits", "running", "study", "acoustic", "hip hop", "classical",
|
| 103 |
+
"bollywood", "lo fi", "rain sounds", "sleep", "meditation", "rock",
|
| 104 |
+
"indie", "pop hits", "throwback", "summer vibes", "late night",
|
| 105 |
+
"gym pump", "coding focus", "driving", "cooking", "cleaning",
|
| 106 |
+
]
|
| 107 |
+
|
| 108 |
+
YOUTUBE_QUERIES = [
|
| 109 |
+
"pasta recipes", "workout videos", "python tutorials", "cat videos",
|
| 110 |
+
"lo fi beats", "travel vlogs", "meditation music", "guitar lessons",
|
| 111 |
+
"phone review", "stand up comedy", "how to bake bread", "documentary",
|
| 112 |
+
"asmr videos", "yoga classes", "morning routine", "news",
|
| 113 |
+
"diy crafts", "ghibli food", "korean street food", "home renovation",
|
| 114 |
+
"machine learning basics", "react tutorial", "interview tips",
|
| 115 |
+
"stretching routine", "origami tutorial", "watercolor painting",
|
| 116 |
+
"budget travel tips", "meal prep ideas", "indoor plants care",
|
| 117 |
+
"car maintenance", "photography tips", "skincare routine",
|
| 118 |
+
]
|
| 119 |
+
|
| 120 |
+
SPOTIFY_QUERIES = [
|
| 121 |
+
"jazz", "lo fi beats", "taylor swift", "classical music", "workout music",
|
| 122 |
+
"bollywood songs", "rock music", "chill vibes", "drake", "hip hop",
|
| 123 |
+
"acoustic covers", "study music", "pop hits", "rain sounds", "edm",
|
| 124 |
+
"indie folk", "synthwave", "reggaeton", "k-pop", "country hits",
|
| 125 |
+
"piano covers", "ambient", "80s hits", "90s throwback", "focus beats",
|
| 126 |
+
]
|
| 127 |
+
|
| 128 |
+
SLACK_CHANNELS = [
|
| 129 |
+
"general", "engineering", "data contributors", "random", "announcements",
|
| 130 |
+
"product team", "design reviews", "support tickets", "marketing",
|
| 131 |
+
"devops alerts", "project alpha", "team updates", "hiring", "backend team",
|
| 132 |
+
"daily standup", "sales", "customer success", "incidents", "on-call",
|
| 133 |
+
"frontend", "mobile", "qa", "releases", "watercooler", "leadership",
|
| 134 |
+
]
|
| 135 |
+
|
| 136 |
+
DESTINATIONS = [
|
| 137 |
+
"the airport", "downtown", "123 main street", "my office", "central station",
|
| 138 |
+
"home", "the mall", "union square", "marina bay", "work", "the restaurant",
|
| 139 |
+
"the train station", "pratishtha apartment unnamed road", "grand central",
|
| 140 |
+
"times square", "golden gate bridge", "hotel california", "city hospital",
|
| 141 |
+
"university campus", "grocery store", "gym", "coffee shop on 5th ave",
|
| 142 |
+
"convention center", "stadium", "library", "pharmacy", "post office",
|
| 143 |
+
]
|
| 144 |
+
|
| 145 |
+
LINKEDIN_NAMES = [
|
| 146 |
+
"arya sheth", "parag shah", "john smith", "jane doe", "priya mehta",
|
| 147 |
+
"david chen", "emma wilson", "michael brown", "sophie martin", "james lee",
|
| 148 |
+
"nina patel", "sarah johnson", "robert kim", "lisa anderson", "mark taylor",
|
| 149 |
+
"amanda white", "chris evans", "rachel green", "kevin hart", "olivia brown",
|
| 150 |
+
]
|
| 151 |
+
|
| 152 |
+
EMAIL_RECIPIENTS = [
|
| 153 |
+
"boss", "team", "mom", "hr", "client", "professor", "partner",
|
| 154 |
+
"john@company.com", "sarah@gmail.com", "team@work.com", "hr@company.com",
|
| 155 |
+
"alex@gmail.com", "client@startup.io", "professor@university.edu",
|
| 156 |
+
"kriyanshishah06@gmail.com", "contact@gmail.com", "partner@gmail.com",
|
| 157 |
+
"manager@corp.com", "support@service.com", "billing@company.com",
|
| 158 |
+
"recruiter@jobs.com", "design@agency.com", "dev@startup.io",
|
| 159 |
+
]
|
| 160 |
+
|
| 161 |
+
EMAIL_MESSAGES = [
|
| 162 |
+
"project update", "i'll be late", "hello", "thanks for your help",
|
| 163 |
+
"weekly report", "vacation request", "proposal attached", "meeting notes",
|
| 164 |
+
"follow up", "hello-world", "invoice attached", "contract for review",
|
| 165 |
+
"interview confirmation", "quarterly results", "onboarding docs",
|
| 166 |
+
"feedback on the design", "can we reschedule", "out of office notice",
|
| 167 |
+
"happy birthday", "congratulations on the promotion",
|
| 168 |
+
]
|
| 169 |
+
|
| 170 |
+
NO_PARAM_PROMPTS: dict[str, list[str]] = {
|
| 171 |
+
"wifi_enable": [
|
| 172 |
+
"enable wifi", "turn on wifi", "switch on wifi", "activate wifi",
|
| 173 |
+
"wifi on", "turn wifi on please", "enable wifi on my phone",
|
| 174 |
+
"switch wifi on", "start wifi", "put wifi on", "can you turn on wifi",
|
| 175 |
+
"wifi enable", "turn on my wifi", "enable wireless network",
|
| 176 |
+
"activate wifi connection", "switch on my wifi", "wifi on now",
|
| 177 |
+
"please enable wifi", "turn wifi back on", "enable wifi settings",
|
| 178 |
+
"flip wifi on", "get wifi running", "power on wifi", "i need wifi on",
|
| 179 |
+
"enable wlan", "turn wlan on", "wifi should be on",
|
| 180 |
+
"make sure wifi is enabled", "set wifi to on", "connect to wifi",
|
| 181 |
+
"please turn on wifi", "wifi needs to be on", "switch wlan on now",
|
| 182 |
+
"turn the wifi on", "enable my wifi connection", "get wifi on",
|
| 183 |
+
"wifi activation please", "power up wifi", "wifi switch on",
|
| 184 |
+
],
|
| 185 |
+
"bluetooth_enable": [
|
| 186 |
+
"turn on bluetooth", "enable bluetooth", "switch on bluetooth",
|
| 187 |
+
"activate bluetooth", "bluetooth on", "turn bluetooth on please",
|
| 188 |
+
"enable bluetooth on my phone", "switch bluetooth on", "start bluetooth",
|
| 189 |
+
"put bluetooth on", "can you turn on bluetooth", "bluetooth enable",
|
| 190 |
+
"turn on my bluetooth", "enable the bluetooth radio",
|
| 191 |
+
"activate bluetooth connection", "switch on my bluetooth",
|
| 192 |
+
"bluetooth on now", "please enable bluetooth", "turn bluetooth back on",
|
| 193 |
+
"enable bluetooth settings", "flip bluetooth on", "get bluetooth running",
|
| 194 |
+
"power on bluetooth", "connect bluetooth turn it on", "i need bluetooth on",
|
| 195 |
+
"enable bt", "turn bt on", "bluetooth should be on",
|
| 196 |
+
"make sure bluetooth is on", "set bluetooth to on", "turn bluetooth on now",
|
| 197 |
+
"bluetooth needs to be enabled", "switch bt on", "activate my bluetooth",
|
| 198 |
+
],
|
| 199 |
+
"spotify_pause": [
|
| 200 |
+
"pause spotify", "stop spotify music", "pause the song on spotify",
|
| 201 |
+
"stop playing on spotify", "pause playback spotify", "hold the music on spotify",
|
| 202 |
+
"pause spotify playback", "stop spotify for now", "pause what's playing on spotify",
|
| 203 |
+
"mute spotify pause it", "pause my spotify music", "stop the music spotify",
|
| 204 |
+
"pause current track spotify", "spotify pause", "pause the spotify player",
|
| 205 |
+
"stop spotify song", "pause spotify please", "halt spotify music",
|
| 206 |
+
"pause spotify now", "stop playback on spotify", "pause the audio on spotify",
|
| 207 |
+
"spotify stop playing", "pause my song on spotify", "freeze spotify playback",
|
| 208 |
+
"pause spotify music player", "stop spotify temporarily",
|
| 209 |
+
"pause whatever is on spotify", "spotify pause music", "hold spotify",
|
| 210 |
+
"stop the spotify track", "pause music on spotify", "silence spotify",
|
| 211 |
+
],
|
| 212 |
+
"camera_take_photo": [
|
| 213 |
+
"take a photo with the camera", "open camera and take a picture",
|
| 214 |
+
"snap a photo", "click a picture with camera", "take a picture now",
|
| 215 |
+
"open the camera app and shoot", "capture a photo", "take a selfie",
|
| 216 |
+
"open camera and snap a pic", "shoot a picture", "click a photo please",
|
| 217 |
+
"take photo with rear camera", "open camera take picture",
|
| 218 |
+
"snap a quick photo", "capture an image with camera", "take a picture of this",
|
| 219 |
+
"open camera and photograph", "click picture using camera",
|
| 220 |
+
"take a shot with camera", "launch camera and take photo",
|
| 221 |
+
"grab a photo with camera", "take a camera picture",
|
| 222 |
+
"open camera snap photo", "photograph this with camera",
|
| 223 |
+
"take pic with camera app", "shoot photo now",
|
| 224 |
+
"camera open and click picture", "take a quick picture",
|
| 225 |
+
"open camera capture photo", "open camera and click a picture",
|
| 226 |
+
],
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
# ---------------------------------------------------------------------------
|
| 230 |
+
# Prompt templates (placeholders: {contact}, {message}, {time}, {day}, etc.)
|
| 231 |
+
# ---------------------------------------------------------------------------
|
| 232 |
+
|
| 233 |
+
ALARM_TEMPLATES = [
|
| 234 |
+
"create alarm for {time} {day}",
|
| 235 |
+
"set alarm for {time} {day}",
|
| 236 |
+
"wake me up at {time} {day}",
|
| 237 |
+
"alarm {day} {time}",
|
| 238 |
+
"set a {time} alarm for {day}",
|
| 239 |
+
"put an alarm for {time} {day}",
|
| 240 |
+
"schedule alarm {time} {day}",
|
| 241 |
+
"set wake up alarm {time} {day}",
|
| 242 |
+
"alarm at {time} {day}",
|
| 243 |
+
"set my alarm for {time} {day}",
|
| 244 |
+
"need alarm {day} {time}",
|
| 245 |
+
"{time} alarm {day} please",
|
| 246 |
+
"wake up alarm {day} {time}",
|
| 247 |
+
"alarm me at {time} {day}",
|
| 248 |
+
"wake me at {time} {day}",
|
| 249 |
+
"alarm for {time} {day}",
|
| 250 |
+
"schedule {time} wake up {day}",
|
| 251 |
+
"remind me at {time} {day}",
|
| 252 |
+
"wake me up at {time}",
|
| 253 |
+
"set an alarm for {time} {day}",
|
| 254 |
+
"can you set alarm {time} {day}",
|
| 255 |
+
"i need to wake up at {time} {day}",
|
| 256 |
+
"please alarm {time} {day}",
|
| 257 |
+
"set {day} morning alarm {time}",
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
CALENDAR_TEMPLATES = [
|
| 261 |
+
"create calendar event for {date} {time} {title}",
|
| 262 |
+
"add a meeting {date} at {time} {title}",
|
| 263 |
+
"schedule {title} {date} {time}",
|
| 264 |
+
"put {title} on my calendar {date} at {time}",
|
| 265 |
+
"create event {date} {time} {title}",
|
| 266 |
+
"add calendar event {date} {time} {title}",
|
| 267 |
+
"book a meeting {date} at {time} {title}",
|
| 268 |
+
"create a calendar entry {date} {time} {title}",
|
| 269 |
+
"set up a meeting {date} {time} {title}",
|
| 270 |
+
"calendar meeting {date} {time} {title}",
|
| 271 |
+
"add {title} to calendar {date} {time}",
|
| 272 |
+
"schedule {title} on {date} at {time}",
|
| 273 |
+
"put {title} on calendar {date} {time}",
|
| 274 |
+
"create meeting {date} {time} {title}",
|
| 275 |
+
"add {title} appointment {date} {time}",
|
| 276 |
+
]
|
| 277 |
+
|
| 278 |
+
WHATSAPP_TEMPLATES = [
|
| 279 |
+
"message {message} to {contact} on whatsapp",
|
| 280 |
+
"text {message} to {contact} on whatsapp",
|
| 281 |
+
"send {message} to {contact} on whatsapp",
|
| 282 |
+
"whatsapp {contact} saying {message}",
|
| 283 |
+
"message {contact} on whatsapp {message}",
|
| 284 |
+
"text {contact} {message} on whatsapp",
|
| 285 |
+
"send a whatsapp to {contact} saying {message}",
|
| 286 |
+
"whatsapp message {contact} {message}",
|
| 287 |
+
"tell {contact} {message} on whatsapp",
|
| 288 |
+
"shoot {contact} a whatsapp saying {message}",
|
| 289 |
+
"send {message} via whatsapp to {contact}",
|
| 290 |
+
"drop {contact} a whatsapp saying {message}",
|
| 291 |
+
"ping {contact} on whatsapp with {message}",
|
| 292 |
+
"write to {contact} on whatsapp {message}",
|
| 293 |
+
"whatsapp {contact} {message}",
|
| 294 |
+
"send {contact} a whatsapp text saying {message}",
|
| 295 |
+
"text {contact} on whatsapp saying {message}",
|
| 296 |
+
]
|
| 297 |
+
|
| 298 |
+
SLACK_TEMPLATES = [
|
| 299 |
+
"open slack channel {channel}",
|
| 300 |
+
"go to {channel} channel in slack",
|
| 301 |
+
"open the {channel} slack channel",
|
| 302 |
+
"switch to {channel} in slack",
|
| 303 |
+
"show me {channel} channel on slack",
|
| 304 |
+
"navigate to {channel} on slack",
|
| 305 |
+
"open slack {channel}",
|
| 306 |
+
"pull up {channel} slack channel",
|
| 307 |
+
"take me to {channel} on slack",
|
| 308 |
+
"open channel {channel} in slack",
|
| 309 |
+
"slack open {channel}",
|
| 310 |
+
"go to slack channel {channel}",
|
| 311 |
+
"open the slack channel called {channel}",
|
| 312 |
+
"show {channel} slack",
|
| 313 |
+
"open the {channel} channel in slack",
|
| 314 |
+
"navigate to {channel} slack channel",
|
| 315 |
+
"switch to #{channel} in slack",
|
| 316 |
+
]
|
| 317 |
+
|
| 318 |
+
PLAYLIST_TEMPLATES = [
|
| 319 |
+
"play my {playlist} on spotify",
|
| 320 |
+
"start my {playlist} playlist spotify",
|
| 321 |
+
"put on {playlist} spotify",
|
| 322 |
+
"play {playlist} on spotify",
|
| 323 |
+
"play {playlist} playlist from spotify",
|
| 324 |
+
"open {playlist} playlist spotify",
|
| 325 |
+
"spotify play my {playlist}",
|
| 326 |
+
"play music from my {playlist}",
|
| 327 |
+
"start playing my {playlist} spotify playlist",
|
| 328 |
+
"play my spotify {playlist}",
|
| 329 |
+
"put on my {playlist} playlist on spotify",
|
| 330 |
+
"start {playlist} on spotify",
|
| 331 |
+
"play the {playlist} playlist",
|
| 332 |
+
"spotify start {playlist}",
|
| 333 |
+
"queue up {playlist} on spotify",
|
| 334 |
+
]
|
| 335 |
+
|
| 336 |
+
UBER_TEMPLATES = [
|
| 337 |
+
"get an uber to {destination}",
|
| 338 |
+
"uber to {destination}",
|
| 339 |
+
"book uber to {destination}",
|
| 340 |
+
"call an uber to {destination}",
|
| 341 |
+
"request uber ride to {destination}",
|
| 342 |
+
"uber me to {destination}",
|
| 343 |
+
"get a ride to {destination} on uber",
|
| 344 |
+
"book a cab to {destination} via uber",
|
| 345 |
+
"uber to {destination} now",
|
| 346 |
+
"need an uber to {destination}",
|
| 347 |
+
"schedule uber to {destination}",
|
| 348 |
+
"ride to {destination} using uber",
|
| 349 |
+
"open uber and go to {destination}",
|
| 350 |
+
"find uber to {destination}",
|
| 351 |
+
"get ride to {destination}",
|
| 352 |
+
"uber for {destination} please",
|
| 353 |
+
"book an uber to {destination}",
|
| 354 |
+
"search and uber for {destination}",
|
| 355 |
+
]
|
| 356 |
+
|
| 357 |
+
LINKEDIN_TEMPLATES = [
|
| 358 |
+
"search {name} on linkedin",
|
| 359 |
+
"look up {name} on linkedin",
|
| 360 |
+
"find {name}'s linkedin profile",
|
| 361 |
+
"linkedin search for {name}",
|
| 362 |
+
"open linkedin and search {name}",
|
| 363 |
+
"can you find {name} on linkedin",
|
| 364 |
+
"search for {name} on linked in",
|
| 365 |
+
"look for {name} on linkedin",
|
| 366 |
+
"find {name} on linkedin",
|
| 367 |
+
"linkedin find {name}",
|
| 368 |
+
"search linkedin for {name}",
|
| 369 |
+
"pull up {name} on linkedin",
|
| 370 |
+
"show me {name} on linkedin",
|
| 371 |
+
"i need to find {name} on linkedin",
|
| 372 |
+
"help me search linkedin for {name}",
|
| 373 |
+
]
|
| 374 |
+
|
| 375 |
+
SPOTIFY_SEARCH_TEMPLATES = [
|
| 376 |
+
"search {query} on spotify and play",
|
| 377 |
+
"find {query} on spotify and play it",
|
| 378 |
+
"play {query} music on spotify",
|
| 379 |
+
"search for {query} on spotify and start playing",
|
| 380 |
+
"look up {query} tracks on spotify and play",
|
| 381 |
+
"spotify search {query} and play",
|
| 382 |
+
"find and play {query} on spotify",
|
| 383 |
+
"search spotify for {query} music and play",
|
| 384 |
+
"play some {query} on spotify",
|
| 385 |
+
"open spotify search {query} and play",
|
| 386 |
+
"search {query} playlist spotify play now",
|
| 387 |
+
"find {query} spotify play",
|
| 388 |
+
"spotify play {query} after searching",
|
| 389 |
+
"search and play {query} tracks spotify",
|
| 390 |
+
"put on {query} from spotify search",
|
| 391 |
+
]
|
| 392 |
+
|
| 393 |
+
YOUTUBE_TEMPLATES = [
|
| 394 |
+
"search {query} on youtube",
|
| 395 |
+
"look up {query} on youtube",
|
| 396 |
+
"find {query} videos on youtube",
|
| 397 |
+
"youtube search {query}",
|
| 398 |
+
"search for {query} on youtube",
|
| 399 |
+
"play {query} on youtube",
|
| 400 |
+
"show me {query} on youtube",
|
| 401 |
+
"open youtube and search {query}",
|
| 402 |
+
"find {query} on youtube",
|
| 403 |
+
"youtube {query}",
|
| 404 |
+
"look for {query} videos on youtube",
|
| 405 |
+
"search youtube for {query}",
|
| 406 |
+
"find videos about {query} on youtube",
|
| 407 |
+
"youtube look up {query}",
|
| 408 |
+
]
|
| 409 |
+
|
| 410 |
+
CONTACTS_TEMPLATES = [
|
| 411 |
+
"search {contact} in contacts",
|
| 412 |
+
"find {contact} in contacts",
|
| 413 |
+
"look up {contact} in my contacts",
|
| 414 |
+
"search contact {contact}",
|
| 415 |
+
"find {contact} contact",
|
| 416 |
+
"search contacts for {contact}",
|
| 417 |
+
"look for {contact} in phone contacts",
|
| 418 |
+
"open contacts and search {contact}",
|
| 419 |
+
"find {contact}'s number in contacts",
|
| 420 |
+
"search my contacts for {contact}",
|
| 421 |
+
"contacts find {contact}",
|
| 422 |
+
"look up {contact} contact info",
|
| 423 |
+
"search my phone book for {contact}",
|
| 424 |
+
"contacts lookup {contact}",
|
| 425 |
+
"find {contact} phone number contacts",
|
| 426 |
+
"search address book {contact}",
|
| 427 |
+
"open contacts search {contact}",
|
| 428 |
+
"find parag shah in contacts",
|
| 429 |
+
]
|
| 430 |
+
|
| 431 |
+
GMAIL_TEMPLATES = [
|
| 432 |
+
"send mail using gmail to {recipient} saying {message}",
|
| 433 |
+
"email {recipient} saying {message}",
|
| 434 |
+
"send gmail to {recipient} saying {message}",
|
| 435 |
+
"write mail to {recipient} saying {message}",
|
| 436 |
+
"compose an email to {recipient} {message}",
|
| 437 |
+
"email {recipient} {message} via gmail",
|
| 438 |
+
"compose gmail to {recipient} saying {message}",
|
| 439 |
+
"send email to {recipient} {message}",
|
| 440 |
+
"gmail {recipient} with {message}",
|
| 441 |
+
"write email to {recipient} saying {message}",
|
| 442 |
+
"send a gmail message {message} to {recipient}",
|
| 443 |
+
"email {recipient} from gmail {message}",
|
| 444 |
+
"compose email {message} to {recipient}",
|
| 445 |
+
"send {message} email to {recipient} gmail",
|
| 446 |
+
"shoot an email to {recipient} {message}",
|
| 447 |
+
"mail {recipient} with {message}",
|
| 448 |
+
"send gmail {message} to {recipient}",
|
| 449 |
+
"compose and send {message} to {recipient}",
|
| 450 |
+
]
|
| 451 |
+
|
| 452 |
+
# ---------------------------------------------------------------------------
|
| 453 |
+
# Helpers
|
| 454 |
+
# ---------------------------------------------------------------------------
|
| 455 |
+
|
| 456 |
+
|
| 457 |
+
def load_schemas(path: Path) -> dict:
|
| 458 |
+
with path.open(encoding="utf-8") as handle:
|
| 459 |
+
return json.load(handle)
|
| 460 |
+
|
| 461 |
+
|
| 462 |
+
def load_skills(path: Path) -> list[dict]:
|
| 463 |
+
skills = []
|
| 464 |
+
with path.open(encoding="utf-8") as handle:
|
| 465 |
+
for line in handle:
|
| 466 |
+
line = line.strip()
|
| 467 |
+
if line:
|
| 468 |
+
skills.append(json.loads(line))
|
| 469 |
+
return skills
|
| 470 |
+
|
| 471 |
+
|
| 472 |
+
def format_intent(skill: str, parameters: dict) -> str:
|
| 473 |
+
return json.dumps({"skill": skill, "parameters": parameters}, separators=(",", ":"))
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
def make_record(prompt: str, skill: str, parameters: dict) -> dict:
|
| 477 |
+
return {
|
| 478 |
+
"messages": [
|
| 479 |
+
{"role": "system", "content": INTENT_SYSTEM_PROMPT},
|
| 480 |
+
{"role": "user", "content": prompt},
|
| 481 |
+
{"role": "assistant", "content": format_intent(skill, parameters)},
|
| 482 |
+
]
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
|
| 486 |
+
def expand_combinations(
|
| 487 |
+
templates: list[str],
|
| 488 |
+
param_pools: dict[str, list[str]],
|
| 489 |
+
limit: int,
|
| 490 |
+
rng: random.Random,
|
| 491 |
+
) -> list[tuple[str, dict]]:
|
| 492 |
+
"""Sample template × parameter combinations without building the full cartesian product."""
|
| 493 |
+
pool_keys = list(param_pools.keys())
|
| 494 |
+
examples: list[tuple[str, dict]] = []
|
| 495 |
+
seen: set[str] = set()
|
| 496 |
+
attempts = 0
|
| 497 |
+
max_attempts = limit * 50
|
| 498 |
+
|
| 499 |
+
fillers = ["", " thanks", " asap", " please", " now"]
|
| 500 |
+
while len(examples) < limit and attempts < max_attempts:
|
| 501 |
+
attempts += 1
|
| 502 |
+
template = rng.choice(templates)
|
| 503 |
+
params = {key: rng.choice(pool) for key, pool in param_pools.items()}
|
| 504 |
+
try:
|
| 505 |
+
prompt = template.format(**params)
|
| 506 |
+
except KeyError:
|
| 507 |
+
continue
|
| 508 |
+
if rng.random() > 0.7:
|
| 509 |
+
prompt = f"{rng.choice(['please ', 'can you ', 'i need to ', ''] )}{prompt}{rng.choice(fillers)}"
|
| 510 |
+
key = prompt.lower().strip()
|
| 511 |
+
if not key or key in seen:
|
| 512 |
+
continue
|
| 513 |
+
seen.add(key)
|
| 514 |
+
examples.append((prompt.strip(), params))
|
| 515 |
+
|
| 516 |
+
return examples
|
| 517 |
+
|
| 518 |
+
|
| 519 |
+
def generate_no_param_examples(skill: str, limit: int, rng: random.Random) -> list[tuple[str, dict]]:
|
| 520 |
+
prompts = list(NO_PARAM_PROMPTS.get(skill, []))
|
| 521 |
+
rng.shuffle(prompts)
|
| 522 |
+
|
| 523 |
+
prefixes = ["please ", "can you ", "i need to ", "help me ", "quickly ", "just "]
|
| 524 |
+
suffixes = [" please", " now", " for me", " on my phone", " right away"]
|
| 525 |
+
seen: set[str] = set()
|
| 526 |
+
result: list[tuple[str, dict]] = []
|
| 527 |
+
|
| 528 |
+
def add(prompt: str) -> bool:
|
| 529 |
+
key = prompt.lower().strip()
|
| 530 |
+
if not key or key in seen:
|
| 531 |
+
return False
|
| 532 |
+
seen.add(key)
|
| 533 |
+
result.append((prompt.strip(), {}))
|
| 534 |
+
return True
|
| 535 |
+
|
| 536 |
+
for prompt in prompts:
|
| 537 |
+
add(prompt)
|
| 538 |
+
for prefix in prefixes:
|
| 539 |
+
add(f"{prefix}{prompt}")
|
| 540 |
+
for suffix in suffixes:
|
| 541 |
+
add(f"{prompt}{suffix}")
|
| 542 |
+
if len(result) >= limit:
|
| 543 |
+
return result[:limit]
|
| 544 |
+
|
| 545 |
+
fillers = ["thanks", "asap", "when you can", "if possible", "real quick"]
|
| 546 |
+
attempt = 0
|
| 547 |
+
while len(result) < limit and attempt < limit * 20:
|
| 548 |
+
attempt += 1
|
| 549 |
+
base = rng.choice(prompts)
|
| 550 |
+
variant = (
|
| 551 |
+
f"{rng.choice(prefixes)}{base}{rng.choice(suffixes)}"
|
| 552 |
+
f"{'' if rng.random() > 0.3 else ' ' + rng.choice(fillers)}"
|
| 553 |
+
)
|
| 554 |
+
add(variant)
|
| 555 |
+
|
| 556 |
+
return result[:limit]
|
| 557 |
+
|
| 558 |
+
|
| 559 |
+
def generate_skill_examples(
|
| 560 |
+
skill: str,
|
| 561 |
+
limit: int,
|
| 562 |
+
rng: random.Random,
|
| 563 |
+
) -> list[tuple[str, dict]]:
|
| 564 |
+
if skill in NO_PARAM_PROMPTS:
|
| 565 |
+
return generate_no_param_examples(skill, limit, rng)
|
| 566 |
+
|
| 567 |
+
generators = {
|
| 568 |
+
"create_alarm": lambda: expand_combinations(
|
| 569 |
+
ALARM_TEMPLATES,
|
| 570 |
+
{"time": ALARM_TIMES, "day": ALARM_DAYS},
|
| 571 |
+
limit,
|
| 572 |
+
rng,
|
| 573 |
+
),
|
| 574 |
+
"calendar_create_event": lambda: expand_combinations(
|
| 575 |
+
CALENDAR_TEMPLATES,
|
| 576 |
+
{"title": CALENDAR_TITLES, "date": CALENDAR_DATES, "time": CALENDAR_TIMES},
|
| 577 |
+
limit,
|
| 578 |
+
rng,
|
| 579 |
+
),
|
| 580 |
+
"whatsapp_send_message": lambda: expand_combinations(
|
| 581 |
+
WHATSAPP_TEMPLATES,
|
| 582 |
+
{"contact": CONTACTS, "message": MESSAGES},
|
| 583 |
+
limit,
|
| 584 |
+
rng,
|
| 585 |
+
),
|
| 586 |
+
"slack_open_channel": lambda: expand_combinations(
|
| 587 |
+
SLACK_TEMPLATES,
|
| 588 |
+
{"channel": SLACK_CHANNELS},
|
| 589 |
+
limit,
|
| 590 |
+
rng,
|
| 591 |
+
),
|
| 592 |
+
"spotify_play_playlist": lambda: expand_combinations(
|
| 593 |
+
PLAYLIST_TEMPLATES,
|
| 594 |
+
{"playlist": PLAYLISTS},
|
| 595 |
+
limit,
|
| 596 |
+
rng,
|
| 597 |
+
),
|
| 598 |
+
"uber_request_ride": lambda: expand_combinations(
|
| 599 |
+
UBER_TEMPLATES,
|
| 600 |
+
{"destination": DESTINATIONS},
|
| 601 |
+
limit,
|
| 602 |
+
rng,
|
| 603 |
+
),
|
| 604 |
+
"linkedin_search_person": lambda: expand_combinations(
|
| 605 |
+
LINKEDIN_TEMPLATES,
|
| 606 |
+
{"name": LINKEDIN_NAMES},
|
| 607 |
+
limit,
|
| 608 |
+
rng,
|
| 609 |
+
),
|
| 610 |
+
"spotify_search_play": lambda: expand_combinations(
|
| 611 |
+
SPOTIFY_SEARCH_TEMPLATES,
|
| 612 |
+
{"query": SPOTIFY_QUERIES},
|
| 613 |
+
limit,
|
| 614 |
+
rng,
|
| 615 |
+
),
|
| 616 |
+
"youtube_search": lambda: expand_combinations(
|
| 617 |
+
YOUTUBE_TEMPLATES,
|
| 618 |
+
{"query": YOUTUBE_QUERIES},
|
| 619 |
+
limit,
|
| 620 |
+
rng,
|
| 621 |
+
),
|
| 622 |
+
"contacts_search": lambda: expand_combinations(
|
| 623 |
+
CONTACTS_TEMPLATES,
|
| 624 |
+
{"contact": CONTACTS},
|
| 625 |
+
limit,
|
| 626 |
+
rng,
|
| 627 |
+
),
|
| 628 |
+
"gmail_send_email": lambda: expand_combinations(
|
| 629 |
+
GMAIL_TEMPLATES,
|
| 630 |
+
{"recipient": EMAIL_RECIPIENTS, "message": EMAIL_MESSAGES},
|
| 631 |
+
limit,
|
| 632 |
+
rng,
|
| 633 |
+
),
|
| 634 |
+
}
|
| 635 |
+
|
| 636 |
+
generator = generators.get(skill)
|
| 637 |
+
if not generator:
|
| 638 |
+
return []
|
| 639 |
+
|
| 640 |
+
return generator()
|
| 641 |
+
|
| 642 |
+
|
| 643 |
+
def generate_contrastive_examples() -> list[dict]:
|
| 644 |
+
"""Hard negatives: same entity, different app → different skill + parameters."""
|
| 645 |
+
records: list[dict] = []
|
| 646 |
+
names = ["parag shah", "arya sheth", "john smith", "mom", "sarah"]
|
| 647 |
+
|
| 648 |
+
for name in names:
|
| 649 |
+
records.extend(
|
| 650 |
+
[
|
| 651 |
+
make_record(f"search {name} in contacts", "contacts_search", {"contact": name}),
|
| 652 |
+
make_record(f"find {name} in my contacts", "contacts_search", {"contact": name}),
|
| 653 |
+
make_record(f"search {name} on linkedin", "linkedin_search_person", {"name": name}),
|
| 654 |
+
make_record(f"find {name} on linkedin", "linkedin_search_person", {"name": name}),
|
| 655 |
+
make_record(f"search {name} on youtube", "youtube_search", {"query": name}),
|
| 656 |
+
make_record(f"look up {name} videos on youtube", "youtube_search", {"query": name}),
|
| 657 |
+
make_record(
|
| 658 |
+
f"search {name} on spotify and play",
|
| 659 |
+
"spotify_search_play",
|
| 660 |
+
{"query": name},
|
| 661 |
+
),
|
| 662 |
+
make_record(
|
| 663 |
+
f"find {name} music on spotify and play it",
|
| 664 |
+
"spotify_search_play",
|
| 665 |
+
{"query": name},
|
| 666 |
+
),
|
| 667 |
+
]
|
| 668 |
+
)
|
| 669 |
+
|
| 670 |
+
records.extend(
|
| 671 |
+
[
|
| 672 |
+
make_record(
|
| 673 |
+
"email boss saying i'll be late",
|
| 674 |
+
"gmail_send_email",
|
| 675 |
+
{"recipient": "boss", "message": "i'll be late"},
|
| 676 |
+
),
|
| 677 |
+
make_record(
|
| 678 |
+
"message boss on whatsapp running late",
|
| 679 |
+
"whatsapp_send_message",
|
| 680 |
+
{"contact": "boss", "message": "running late"},
|
| 681 |
+
),
|
| 682 |
+
make_record(
|
| 683 |
+
"open the engineering channel in slack",
|
| 684 |
+
"slack_open_channel",
|
| 685 |
+
{"channel": "engineering"},
|
| 686 |
+
),
|
| 687 |
+
make_record(
|
| 688 |
+
"search pasta recipes on youtube",
|
| 689 |
+
"youtube_search",
|
| 690 |
+
{"query": "pasta recipes"},
|
| 691 |
+
),
|
| 692 |
+
make_record(
|
| 693 |
+
"find parag shah in contacts",
|
| 694 |
+
"contacts_search",
|
| 695 |
+
{"contact": "parag shah"},
|
| 696 |
+
),
|
| 697 |
+
make_record(
|
| 698 |
+
"send ri a message on whatsapp saying see you soon",
|
| 699 |
+
"whatsapp_send_message",
|
| 700 |
+
{"contact": "ri", "message": "see you soon"},
|
| 701 |
+
),
|
| 702 |
+
make_record(
|
| 703 |
+
"play my workout playlist",
|
| 704 |
+
"spotify_play_playlist",
|
| 705 |
+
{"playlist": "workout"},
|
| 706 |
+
),
|
| 707 |
+
make_record(
|
| 708 |
+
"wake me up tomorrow morning",
|
| 709 |
+
"create_alarm",
|
| 710 |
+
{"time": "7 am", "day": "tomorrow morning"},
|
| 711 |
+
),
|
| 712 |
+
]
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
return records
|
| 716 |
+
|
| 717 |
+
|
| 718 |
+
def generate_eval_prompts(
|
| 719 |
+
train_prompts: set[str],
|
| 720 |
+
per_skill: int,
|
| 721 |
+
rng: random.Random,
|
| 722 |
+
) -> list[dict]:
|
| 723 |
+
"""Generate held-out eval prompts not present in training."""
|
| 724 |
+
eval_cases: list[dict] = []
|
| 725 |
+
|
| 726 |
+
eval_templates = {
|
| 727 |
+
"create_alarm": [
|
| 728 |
+
("set a 6 am alarm for monday", {"time": "6 am", "day": "monday"}),
|
| 729 |
+
("wake me up at 8:30 am next friday", {"time": "8:30 am", "day": "next friday"}),
|
| 730 |
+
("alarm tomorrow 5:30 am", {"time": "5:30 am", "day": "tomorrow"}),
|
| 731 |
+
("schedule 9 pm alarm tonight", {"time": "9 pm", "day": "tonight"}),
|
| 732 |
+
("i need a 7:15 am alarm wednesday", {"time": "7:15 am", "day": "wednesday"}),
|
| 733 |
+
("put alarm for 6:45 am saturday", {"time": "6:45 am", "day": "saturday"}),
|
| 734 |
+
("wake up at noon tomorrow", {"time": "noon", "day": "tomorrow"}),
|
| 735 |
+
],
|
| 736 |
+
"calendar_create_event": [
|
| 737 |
+
("add team standup to my calendar tuesday 10 am", {"title": "team standup", "date": "tuesday", "time": "10 am"}),
|
| 738 |
+
("book dentist appointment next friday 3 pm", {"title": "dentist appointment", "date": "next friday", "time": "3 pm"}),
|
| 739 |
+
("put lunch with priya on calendar wednesday noon", {"title": "lunch with priya", "date": "wednesday", "time": "noon"}),
|
| 740 |
+
("schedule code review thursday 2 pm", {"title": "code review", "date": "thursday", "time": "2 pm"}),
|
| 741 |
+
("add flight to calendar sunday 8 am", {"title": "flight", "date": "sunday", "time": "8 am"}),
|
| 742 |
+
("create event hackathon demo monday 4 pm", {"title": "hackathon demo", "date": "monday", "time": "4 pm"}),
|
| 743 |
+
],
|
| 744 |
+
"wifi_enable": [
|
| 745 |
+
("turn on wifi", {}),
|
| 746 |
+
("please enable wifi on my phone", {}),
|
| 747 |
+
("switch wlan on", {}),
|
| 748 |
+
("activate my wifi connection", {}),
|
| 749 |
+
("i need wifi enabled", {}),
|
| 750 |
+
("get wifi running please", {}),
|
| 751 |
+
],
|
| 752 |
+
"bluetooth_enable": [
|
| 753 |
+
("turn bluetooth on", {}),
|
| 754 |
+
("enable bluetooth please", {}),
|
| 755 |
+
("switch on my bluetooth", {}),
|
| 756 |
+
("bluetooth should be on", {}),
|
| 757 |
+
("power on bluetooth now", {}),
|
| 758 |
+
("activate bluetooth radio", {}),
|
| 759 |
+
],
|
| 760 |
+
"whatsapp_send_message": [
|
| 761 |
+
("text mom on whatsapp i'm on my way", {"contact": "mom", "message": "i'm on my way"}),
|
| 762 |
+
("whatsapp sarah saying thanks", {"contact": "sarah", "message": "thanks"}),
|
| 763 |
+
("message alex on whatsapp be there in 5", {"contact": "alex", "message": "be there in 5"}),
|
| 764 |
+
("send ri a message saying i'll be late", {"contact": "ri", "message": "i'll be late"}),
|
| 765 |
+
("tell biraj on whatsapp see you tonight", {"contact": "biraj", "message": "see you tonight"}),
|
| 766 |
+
("ping priya on whatsapp with hello", {"contact": "priya", "message": "hello"}),
|
| 767 |
+
],
|
| 768 |
+
"camera_take_photo": [
|
| 769 |
+
("snap a quick photo", {}),
|
| 770 |
+
("open camera and take a picture", {}),
|
| 771 |
+
("capture a photo with the camera", {}),
|
| 772 |
+
("take a selfie now", {}),
|
| 773 |
+
("shoot a picture please", {}),
|
| 774 |
+
("launch camera and snap a pic", {}),
|
| 775 |
+
],
|
| 776 |
+
"slack_open_channel": [
|
| 777 |
+
("go to general channel in slack", {"channel": "general"}),
|
| 778 |
+
("open slack channel random", {"channel": "random"}),
|
| 779 |
+
("navigate to design reviews slack channel", {"channel": "design reviews"}),
|
| 780 |
+
("open the engineering channel in slack", {"channel": "engineering"}),
|
| 781 |
+
("switch to announcements in slack", {"channel": "announcements"}),
|
| 782 |
+
("show me data contributors on slack", {"channel": "data contributors"}),
|
| 783 |
+
],
|
| 784 |
+
"spotify_pause": [
|
| 785 |
+
("pause spotify playback", {}),
|
| 786 |
+
("stop the music on spotify", {}),
|
| 787 |
+
("hold spotify for now", {}),
|
| 788 |
+
("freeze spotify playback", {}),
|
| 789 |
+
("halt the spotify player", {}),
|
| 790 |
+
("spotify stop playing", {}),
|
| 791 |
+
],
|
| 792 |
+
"spotify_play_playlist": [
|
| 793 |
+
("start my chill playlist on spotify", {"playlist": "chill"}),
|
| 794 |
+
("play discover weekly on spotify", {"playlist": "discover weekly"}),
|
| 795 |
+
("put on my liked songs spotify", {"playlist": "liked songs"}),
|
| 796 |
+
("play my workout playlist", {"playlist": "workout"}),
|
| 797 |
+
("queue up road trip on spotify", {"playlist": "road trip"}),
|
| 798 |
+
("start focus playlist spotify", {"playlist": "focus"}),
|
| 799 |
+
],
|
| 800 |
+
"uber_request_ride": [
|
| 801 |
+
("get an uber to the airport", {"destination": "the airport"}),
|
| 802 |
+
("book uber to downtown", {"destination": "downtown"}),
|
| 803 |
+
("request a ride to central station on uber", {"destination": "central station"}),
|
| 804 |
+
("uber me to golden gate bridge", {"destination": "golden gate bridge"}),
|
| 805 |
+
("need a ride to city hospital via uber", {"destination": "city hospital"}),
|
| 806 |
+
("call uber to convention center", {"destination": "convention center"}),
|
| 807 |
+
],
|
| 808 |
+
"linkedin_search_person": [
|
| 809 |
+
("look up jane doe on linkedin", {"name": "jane doe"}),
|
| 810 |
+
("find parag shah's linkedin profile", {"name": "parag shah"}),
|
| 811 |
+
("search linkedin for john smith", {"name": "john smith"}),
|
| 812 |
+
("pull up sophie martin on linkedin", {"name": "sophie martin"}),
|
| 813 |
+
("find david chen on linkedin", {"name": "david chen"}),
|
| 814 |
+
("linkedin search nina patel", {"name": "nina patel"}),
|
| 815 |
+
],
|
| 816 |
+
"spotify_search_play": [
|
| 817 |
+
("find jazz on spotify and play it", {"query": "jazz"}),
|
| 818 |
+
("search lo fi beats on spotify and play them", {"query": "lo fi beats"}),
|
| 819 |
+
("spotify search taylor swift and play", {"query": "taylor swift"}),
|
| 820 |
+
("search k-pop on spotify and play", {"query": "k-pop"}),
|
| 821 |
+
("find ambient music on spotify and play", {"query": "ambient"}),
|
| 822 |
+
("play reggaeton after searching spotify", {"query": "reggaeton"}),
|
| 823 |
+
],
|
| 824 |
+
"youtube_search": [
|
| 825 |
+
("search pasta recipes on youtube", {"query": "pasta recipes"}),
|
| 826 |
+
("find workout videos on youtube", {"query": "workout videos"}),
|
| 827 |
+
("look up python tutorials on youtube", {"query": "python tutorials"}),
|
| 828 |
+
("youtube search morning routine", {"query": "morning routine"}),
|
| 829 |
+
("find korean street food on youtube", {"query": "korean street food"}),
|
| 830 |
+
("search machine learning basics on youtube", {"query": "machine learning basics"}),
|
| 831 |
+
],
|
| 832 |
+
"contacts_search": [
|
| 833 |
+
("find mom in my contacts", {"contact": "mom"}),
|
| 834 |
+
("search contacts for dad", {"contact": "dad"}),
|
| 835 |
+
("look up john smith in contacts", {"contact": "john smith"}),
|
| 836 |
+
("find dentist office in contacts", {"contact": "dentist office"}),
|
| 837 |
+
("search my contacts for maya", {"contact": "maya"}),
|
| 838 |
+
("contacts lookup ri", {"contact": "ri"}),
|
| 839 |
+
],
|
| 840 |
+
"gmail_send_email": [
|
| 841 |
+
("email boss saying i'll be late", {"recipient": "boss", "message": "i'll be late"}),
|
| 842 |
+
("send gmail to sarah@gmail.com subject meeting notes", {"recipient": "sarah@gmail.com", "message": "meeting notes"}),
|
| 843 |
+
("compose email to client proposal attached", {"recipient": "client", "message": "proposal attached"}),
|
| 844 |
+
("write mail to hr@company.com vacation request", {"recipient": "hr@company.com", "message": "vacation request"}),
|
| 845 |
+
("send email to professor asking about assignment", {"recipient": "professor", "message": "asking about assignment"}),
|
| 846 |
+
("gmail team@work.com weekly report", {"recipient": "team@work.com", "message": "weekly report"}),
|
| 847 |
+
],
|
| 848 |
+
}
|
| 849 |
+
|
| 850 |
+
for skill, templates in eval_templates.items():
|
| 851 |
+
added = 0
|
| 852 |
+
rng.shuffle(templates)
|
| 853 |
+
for prompt, params in templates:
|
| 854 |
+
key = prompt.lower().strip()
|
| 855 |
+
if key in train_prompts:
|
| 856 |
+
continue
|
| 857 |
+
eval_cases.append(
|
| 858 |
+
{
|
| 859 |
+
"prompt": prompt,
|
| 860 |
+
"expected": {"skill": skill, "parameters": params},
|
| 861 |
+
}
|
| 862 |
+
)
|
| 863 |
+
added += 1
|
| 864 |
+
if added >= per_skill:
|
| 865 |
+
break
|
| 866 |
+
|
| 867 |
+
return eval_cases
|
| 868 |
+
|
| 869 |
+
|
| 870 |
+
def main() -> None:
|
| 871 |
+
parser = argparse.ArgumentParser(description="Generate intent extraction dataset.")
|
| 872 |
+
parser.add_argument(
|
| 873 |
+
"--examples-per-skill",
|
| 874 |
+
type=int,
|
| 875 |
+
default=DEFAULT_EXAMPLES_PER_SKILL,
|
| 876 |
+
help=f"Training examples per skill (default: {DEFAULT_EXAMPLES_PER_SKILL})",
|
| 877 |
+
)
|
| 878 |
+
parser.add_argument(
|
| 879 |
+
"--eval-per-skill",
|
| 880 |
+
type=int,
|
| 881 |
+
default=EVAL_PROMPTS_PER_SKILL,
|
| 882 |
+
help=f"Eval prompts per skill (default: {EVAL_PROMPTS_PER_SKILL})",
|
| 883 |
+
)
|
| 884 |
+
parser.add_argument(
|
| 885 |
+
"--seed",
|
| 886 |
+
type=int,
|
| 887 |
+
default=RANDOM_SEED,
|
| 888 |
+
help=f"Random seed (default: {RANDOM_SEED})",
|
| 889 |
+
)
|
| 890 |
+
args = parser.parse_args()
|
| 891 |
+
|
| 892 |
+
rng = random.Random(args.seed)
|
| 893 |
+
schemas = load_schemas(SCHEMAS_FILE)
|
| 894 |
+
skills = load_skills(SKILLS_FILE)
|
| 895 |
+
|
| 896 |
+
records: list[dict] = []
|
| 897 |
+
train_prompts: set[str] = set()
|
| 898 |
+
skill_counts: dict[str, int] = {}
|
| 899 |
+
|
| 900 |
+
for entry in skills:
|
| 901 |
+
skill = entry["skill"]
|
| 902 |
+
if skill not in schemas:
|
| 903 |
+
print(f"Warning: no schema for skill {skill}, skipping")
|
| 904 |
+
continue
|
| 905 |
+
|
| 906 |
+
examples = generate_skill_examples(skill, args.examples_per_skill, rng)
|
| 907 |
+
for prompt, params in examples:
|
| 908 |
+
key = prompt.lower().strip()
|
| 909 |
+
if key in train_prompts:
|
| 910 |
+
continue
|
| 911 |
+
train_prompts.add(key)
|
| 912 |
+
records.append(make_record(prompt, skill, params))
|
| 913 |
+
skill_counts[skill] = skill_counts.get(skill, 0) + 1
|
| 914 |
+
|
| 915 |
+
for record in generate_contrastive_examples():
|
| 916 |
+
prompt = record["messages"][1]["content"]
|
| 917 |
+
key = prompt.lower().strip()
|
| 918 |
+
if key in train_prompts:
|
| 919 |
+
continue
|
| 920 |
+
train_prompts.add(key)
|
| 921 |
+
records.append(record)
|
| 922 |
+
skill = json.loads(record["messages"][2]["content"])["skill"]
|
| 923 |
+
skill_counts[skill] = skill_counts.get(skill, 0) + 1
|
| 924 |
+
|
| 925 |
+
rng.shuffle(records)
|
| 926 |
+
|
| 927 |
+
with TRAIN_OUTPUT.open("w", encoding="utf-8") as handle:
|
| 928 |
+
for record in records:
|
| 929 |
+
handle.write(json.dumps(record) + "\n")
|
| 930 |
+
|
| 931 |
+
eval_prompts = generate_eval_prompts(train_prompts, args.eval_per_skill, rng)
|
| 932 |
+
with EVAL_OUTPUT.open("w", encoding="utf-8") as handle:
|
| 933 |
+
json.dump(eval_prompts, handle, indent=2)
|
| 934 |
+
handle.write("\n")
|
| 935 |
+
|
| 936 |
+
print(f"Wrote {len(records)} training examples to {TRAIN_OUTPUT}")
|
| 937 |
+
print(f"Wrote {len(eval_prompts)} eval prompts to {EVAL_OUTPUT}")
|
| 938 |
+
print(f"Skills: {len(skill_counts)}")
|
| 939 |
+
for skill, count in sorted(skill_counts.items()):
|
| 940 |
+
status = "OK" if count >= args.examples_per_skill else "LOW"
|
| 941 |
+
print(f" {skill}: {count} [{status}]")
|
| 942 |
+
|
| 943 |
+
low_skills = [s for s, c in skill_counts.items() if c < args.examples_per_skill]
|
| 944 |
+
if low_skills:
|
| 945 |
+
print(f"\nWarning: {len(low_skills)} skills below target count: {', '.join(low_skills)}")
|
| 946 |
+
|
| 947 |
+
|
| 948 |
+
if __name__ == "__main__":
|
| 949 |
+
main()
|
scripts/generate_pocket_benchmark.py
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate the Pocket Automator real-world benchmark suite.
|
| 3 |
+
|
| 4 |
+
Produces data/pocket_benchmark_prompts.json — 200 held-out prompts designed to
|
| 5 |
+
measure generalization with slang, typos, incomplete phrasing, and casual speech.
|
| 6 |
+
Prompts are filtered against train_intent.jsonl and eval_intent_prompts.json.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
python scripts/generate_pocket_benchmark.py
|
| 10 |
+
python scripts/generate_pocket_benchmark.py --validate-only
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from __future__ import annotations
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import re
|
| 18 |
+
import sys
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
|
| 21 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 22 |
+
sys.path.insert(0, str(PROJECT_ROOT))
|
| 23 |
+
|
| 24 |
+
from src.pocket_benchmark import BENCHMARK_SKILLS, DOMAIN_BY_SKILL # noqa: E402
|
| 25 |
+
|
| 26 |
+
TRAIN_PATH = PROJECT_ROOT / "data" / "train_intent.jsonl"
|
| 27 |
+
EVAL_PATH = PROJECT_ROOT / "data" / "eval_intent_prompts.json"
|
| 28 |
+
OUTPUT_PATH = PROJECT_ROOT / "data" / "pocket_benchmark_prompts.json"
|
| 29 |
+
TARGET_COUNT = 200
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _case(
|
| 33 |
+
prompt: str,
|
| 34 |
+
skill: str,
|
| 35 |
+
parameters: dict,
|
| 36 |
+
*,
|
| 37 |
+
styles: list[str],
|
| 38 |
+
) -> dict:
|
| 39 |
+
return {
|
| 40 |
+
"prompt": prompt,
|
| 41 |
+
"expected": {"skill": skill, "parameters": parameters},
|
| 42 |
+
"domain": DOMAIN_BY_SKILL[skill],
|
| 43 |
+
"styles": styles,
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
# ---------------------------------------------------------------------------
|
| 48 |
+
# Curated benchmark cases — phrasing intentionally unlike training templates
|
| 49 |
+
# ---------------------------------------------------------------------------
|
| 50 |
+
|
| 51 |
+
BENCHMARK_CASES: list[dict] = [
|
| 52 |
+
# --- Alarms (22) ---
|
| 53 |
+
_case("yo set an alrm for like 5:45 tmrw morning pls", "create_alarm", {"time": "5:45", "day": "tmrw morning"}, styles=["slang", "typo", "conversational"]),
|
| 54 |
+
_case("need to b up at 6ish on monday ngl", "create_alarm", {"time": "6ish", "day": "monday"}, styles=["slang", "incomplete", "conversational"]),
|
| 55 |
+
_case("6am alarm pls", "create_alarm", {"time": "6am"}, styles=["incomplete"]),
|
| 56 |
+
_case("wake me 7:20 wednesday thx", "create_alarm", {"time": "7:20", "day": "wednesday"}, styles=["incomplete", "conversational"]),
|
| 57 |
+
_case("uh can u do a 10:15 pm thing tonight", "create_alarm", {"time": "10:15 pm", "day": "tonight"}, styles=["conversational", "incomplete"]),
|
| 58 |
+
_case("tomorow 4:30 wake up plz", "create_alarm", {"time": "4:30", "day": "tomorow"}, styles=["typo", "incomplete"]),
|
| 59 |
+
_case("deadass need alarm sunday noon", "create_alarm", {"time": "noon", "day": "sunday"}, styles=["slang", "incomplete"]),
|
| 60 |
+
_case("ok wait actually make it 8:05 am next tuesday instead", "create_alarm", {"time": "8:05 am", "day": "next tuesday"}, styles=["conversational"]),
|
| 61 |
+
_case("4:50 alarm for sat pls", "create_alarm", {"time": "4:50", "day": "sat"}, styles=["slang", "incomplete"]),
|
| 62 |
+
_case("gonna crash early — buzz me at 9:30 pm today", "create_alarm", {"time": "9:30 pm", "day": "today"}, styles=["slang", "conversational"]),
|
| 63 |
+
_case("hey siri vibes but pocket automator: 5 am thurs", "create_alarm", {"time": "5 am", "day": "thurs"}, styles=["slang", "conversational"]),
|
| 64 |
+
_case("lmk when its 6:30 — jk just set alarm 6:30 friday", "create_alarm", {"time": "6:30", "day": "friday"}, styles=["slang", "conversational"]),
|
| 65 |
+
_case("alarm?? 7:45 am this weekend", "create_alarm", {"time": "7:45 am", "day": "this weekend"}, styles=["incomplete", "conversational"]),
|
| 66 |
+
_case("pls dont let me oversleep — 6:10 tomorrow", "create_alarm", {"time": "6:10", "day": "tomorrow"}, styles=["conversational"]),
|
| 67 |
+
_case("5:55am wakeup call monday pls", "create_alarm", {"time": "5:55am", "day": "monday"}, styles=["incomplete"]),
|
| 68 |
+
_case("set wakey wakey 8am tmr", "create_alarm", {"time": "8am", "day": "tmr"}, styles=["slang", "incomplete"]),
|
| 69 |
+
_case("lowkey need 11:11 pm alarm tonight", "create_alarm", {"time": "11:11 pm", "day": "tonight"}, styles=["slang"]),
|
| 70 |
+
_case("can i get a 7 am buzzer next monday", "create_alarm", {"time": "7 am", "day": "next monday"}, styles=["conversational"]),
|
| 71 |
+
_case("alarm at half past six tomorrow morning", "create_alarm", {"time": "half past six", "day": "tomorrow morning"}, styles=["conversational"]),
|
| 72 |
+
_case("6:00 alarm 4 wednesday", "create_alarm", {"time": "6:00", "day": "wednesday"}, styles=["typo", "incomplete"]),
|
| 73 |
+
_case("wake me up round 9 on sunday", "create_alarm", {"time": "9", "day": "sunday"}, styles=["slang", "incomplete"]),
|
| 74 |
+
_case("fr need 5:15 am alarm this tuesday", "create_alarm", {"time": "5:15 am", "day": "this tuesday"}, styles=["slang"]),
|
| 75 |
+
|
| 76 |
+
# --- WhatsApp (22) ---
|
| 77 |
+
_case("hit up zoe on whatsapp say im omw", "whatsapp_send_message", {"contact": "zoe", "message": "im omw"}, styles=["slang", "typo", "incomplete"]),
|
| 78 |
+
_case("wa msg marcus 'running 20 min late'", "whatsapp_send_message", {"contact": "marcus", "message": "running 20 min late"}, styles=["slang", "incomplete"]),
|
| 79 |
+
_case("tell roomie on watsapp dishes are done", "whatsapp_send_message", {"contact": "roomie", "message": "dishes are done"}, styles=["slang", "typo"]),
|
| 80 |
+
_case("whastapp elena — meeting got moved to 4", "whatsapp_send_message", {"contact": "elena", "message": "meeting got moved to 4"}, styles=["typo", "incomplete", "conversational"]),
|
| 81 |
+
_case("shoot kevin from work a text on whatsapp: heads up im wfh", "whatsapp_send_message", {"contact": "kevin from work", "message": "heads up im wfh"}, styles=["slang", "conversational"]),
|
| 82 |
+
_case("msg grandpa on whatsapp happy bday!!", "whatsapp_send_message", {"contact": "grandpa", "message": "happy bday!!"}, styles=["slang", "typo"]),
|
| 83 |
+
_case("yo ping sis saying im outside", "whatsapp_send_message", {"contact": "sis", "message": "im outside"}, styles=["slang", "incomplete"]),
|
| 84 |
+
_case("whatsap daniel 'got ur package'", "whatsapp_send_message", {"contact": "daniel", "message": "got ur package"}, styles=["typo", "incomplete"]),
|
| 85 |
+
_case("can u text aunt lisa on whatsapp that flight lands 6", "whatsapp_send_message", {"contact": "aunt lisa", "message": "flight lands 6"}, styles=["conversational", "incomplete"]),
|
| 86 |
+
_case("drop a whatsapp to coach mike — practice cancelled", "whatsapp_send_message", {"contact": "coach mike", "message": "practice cancelled"}, styles=["slang", "incomplete"]),
|
| 87 |
+
_case("whatsapp my landlord rent sent", "whatsapp_send_message", {"contact": "my landlord", "message": "rent sent"}, styles=["incomplete"]),
|
| 88 |
+
_case("lemme msg hannah real quick on whatsapp: u free later?", "whatsapp_send_message", {"contact": "hannah", "message": "u free later?"}, styles=["slang", "conversational"]),
|
| 89 |
+
_case("send whatsapp to benji thx for covering my shift", "whatsapp_send_message", {"contact": "benji", "message": "thx for covering my shift"}, styles=["slang", "incomplete"]),
|
| 90 |
+
_case("text whatsapp to carpool group running late again sry", "whatsapp_send_message", {"contact": "carpool group", "message": "running late again sry"}, styles=["slang", "incomplete"]),
|
| 91 |
+
_case("ok whatsapp olivia saying docs r uploaded", "whatsapp_send_message", {"contact": "olivia", "message": "docs r uploaded"}, styles=["slang", "typo", "conversational"]),
|
| 92 |
+
_case("wahtapp msg to tomas — keys under mat", "whatsapp_send_message", {"contact": "tomas", "message": "keys under mat"}, styles=["typo", "incomplete"]),
|
| 93 |
+
_case("just tell jules on whatsapp im grabbing boba", "whatsapp_send_message", {"contact": "jules", "message": "im grabbing boba"}, styles=["slang", "conversational"]),
|
| 94 |
+
_case("whatsapp neighbor hey can u sign for delivery", "whatsapp_send_message", {"contact": "neighbor", "message": "hey can u sign for delivery"}, styles=["incomplete", "conversational"]),
|
| 95 |
+
_case("msg whatsapp to dr patel appointment confirmed", "whatsapp_send_message", {"contact": "dr patel", "message": "appointment confirmed"}, styles=["incomplete"]),
|
| 96 |
+
_case("hit my brother on whatsapp w/ 'u still coming?'", "whatsapp_send_message", {"contact": "my brother", "message": "u still coming?"}, styles=["slang", "incomplete"]),
|
| 97 |
+
_case("whatsapp bestie ngl im stressed af today", "whatsapp_send_message", {"contact": "bestie", "message": "ngl im stressed af today"}, styles=["slang", "incomplete"]),
|
| 98 |
+
_case("send whatsapp msg to felix — slide deck is ready", "whatsapp_send_message", {"contact": "felix", "message": "slide deck is ready"}, styles=["incomplete"]),
|
| 99 |
+
|
| 100 |
+
# --- Spotify pause (22) ---
|
| 101 |
+
_case("spotfy stop rn", "spotify_pause", {}, styles=["typo", "slang", "incomplete"]),
|
| 102 |
+
_case("yo pause the music", "spotify_pause", {}, styles=["slang", "incomplete"]),
|
| 103 |
+
_case("mute spotify pls im on a call", "spotify_pause", {}, styles=["conversational"]),
|
| 104 |
+
_case("can u kill the spotify track", "spotify_pause", {}, styles=["slang", "conversational"]),
|
| 105 |
+
_case("hold up pause spotify", "spotify_pause", {}, styles=["slang", "incomplete"]),
|
| 106 |
+
_case("spoitfy pause rq", "spotify_pause", {}, styles=["typo", "slang", "incomplete"]),
|
| 107 |
+
_case("stop spotify playback thx", "spotify_pause", {}, styles=["conversational"]),
|
| 108 |
+
_case("shhh pause spotify", "spotify_pause", {}, styles=["incomplete", "conversational"]),
|
| 109 |
+
_case("spotify off for a sec", "spotify_pause", {}, styles=["slang", "incomplete"]),
|
| 110 |
+
_case("pause whats playing on spotify", "spotify_pause", {}, styles=["incomplete"]),
|
| 111 |
+
_case("ok stop spotify music", "spotify_pause", {}, styles=["conversational", "incomplete"]),
|
| 112 |
+
_case("spotify halt plz", "spotify_pause", {}, styles=["incomplete"]),
|
| 113 |
+
_case("silence spotify real quick", "spotify_pause", {}, styles=["conversational"]),
|
| 114 |
+
_case("pause spotify — landlord knocking", "spotify_pause", {}, styles=["conversational"]),
|
| 115 |
+
_case("spotify stop the banger lol", "spotify_pause", {}, styles=["slang", "conversational"]),
|
| 116 |
+
_case("cut spotify audio", "spotify_pause", {}, styles=["slang", "incomplete"]),
|
| 117 |
+
_case("spotify pause pls headphones dying", "spotify_pause", {}, styles=["conversational"]),
|
| 118 |
+
_case("uh pause spotify", "spotify_pause", {}, styles=["conversational", "incomplete"]),
|
| 119 |
+
_case("stop the spotify pls", "spotify_pause", {}, styles=["incomplete"]),
|
| 120 |
+
_case("spotify freeze for now pls", "spotify_pause", {}, styles=["incomplete", "conversational"]),
|
| 121 |
+
_case("pause my spotify session", "spotify_pause", {}, styles=["incomplete"]),
|
| 122 |
+
_case("spotify quiet for now", "spotify_pause", {}, styles=["incomplete", "conversational"]),
|
| 123 |
+
|
| 124 |
+
# --- Spotify play playlist (22) ---
|
| 125 |
+
_case("put on my midnight drives mix on spotify", "spotify_play_playlist", {"playlist": "midnight drives mix"}, styles=["conversational"]),
|
| 126 |
+
_case("spotify play sunday scaries", "spotify_play_playlist", {"playlist": "sunday scaries"}, styles=["incomplete"]),
|
| 127 |
+
_case("queue up deep focus vibes on spotify", "spotify_play_playlist", {"playlist": "deep focus vibes"}, styles=["slang"]),
|
| 128 |
+
_case("play my 2010s throwbacks spotify", "spotify_play_playlist", {"playlist": "2010s throwbacks"}, styles=["incomplete"]),
|
| 129 |
+
_case("spotfy playlist: rainy day jazz", "spotify_play_playlist", {"playlist": "rainy day jazz"}, styles=["typo", "incomplete"]),
|
| 130 |
+
_case("yo spin the gym beast mode playlist", "spotify_play_playlist", {"playlist": "gym beast mode"}, styles=["slang", "incomplete"]),
|
| 131 |
+
_case("spotify — my cooking playlist pls", "spotify_play_playlist", {"playlist": "cooking"}, styles=["incomplete", "conversational"]),
|
| 132 |
+
_case("start spotify playlist called roadtrip 2024", "spotify_play_playlist", {"playlist": "roadtrip 2024"}, styles=["incomplete"]),
|
| 133 |
+
_case("play hyperpop mix on spotify", "spotify_play_playlist", {"playlist": "hyperpop mix"}, styles=["incomplete"]),
|
| 134 |
+
_case("spotify my chillhop essentials thx", "spotify_play_playlist", {"playlist": "chillhop essentials"}, styles=["incomplete", "conversational"]),
|
| 135 |
+
_case("can u play the study grind playlist on spotify", "spotify_play_playlist", {"playlist": "study grind"}, styles=["conversational"]),
|
| 136 |
+
_case("spotify playlist sleep sounds go", "spotify_play_playlist", {"playlist": "sleep sounds"}, styles=["incomplete"]),
|
| 137 |
+
_case("throw on indie roadtrip on spotify", "spotify_play_playlist", {"playlist": "indie roadtrip"}, styles=["slang", "incomplete"]),
|
| 138 |
+
_case("spotify play my liked tracks playlist", "spotify_play_playlist", {"playlist": "liked tracks"}, styles=["incomplete"]),
|
| 139 |
+
_case("play spotify playlist bossa nova mornings", "spotify_play_playlist", {"playlist": "bossa nova mornings"}, styles=["incomplete"]),
|
| 140 |
+
_case("spotify — queue phonk workout", "spotify_play_playlist", {"playlist": "phonk workout"}, styles=["incomplete"]),
|
| 141 |
+
_case("put spotify on my cozy rainy day list", "spotify_play_playlist", {"playlist": "cozy rainy day"}, styles=["conversational", "incomplete"]),
|
| 142 |
+
_case("play the spotify playlist french cafe", "spotify_play_playlist", {"playlist": "french cafe"}, styles=["incomplete"]),
|
| 143 |
+
_case("spotify start my pregame playlist", "spotify_play_playlist", {"playlist": "pregame"}, styles=["slang", "incomplete"]),
|
| 144 |
+
_case("ok spotify play downtempo focus", "spotify_play_playlist", {"playlist": "downtempo focus"}, styles=["conversational", "incomplete"]),
|
| 145 |
+
_case("spotify my discover mix playlist pls", "spotify_play_playlist", {"playlist": "discover mix"}, styles=["incomplete"]),
|
| 146 |
+
_case("play playlist morning commute on spotify", "spotify_play_playlist", {"playlist": "morning commute"}, styles=["incomplete"]),
|
| 147 |
+
|
| 148 |
+
# --- Spotify search play (22) ---
|
| 149 |
+
_case("spotify find bad bunny and play", "spotify_search_play", {"query": "bad bunny"}, styles=["incomplete"]),
|
| 150 |
+
_case("search spotify for lofi hip hop then play", "spotify_search_play", {"query": "lofi hip hop"}, styles=["conversational"]),
|
| 151 |
+
_case("play some arctic monkeys on spotify", "spotify_search_play", {"query": "arctic monkeys"}, styles=["conversational", "incomplete"]),
|
| 152 |
+
_case("spotfy search billie eilish play it", "spotify_search_play", {"query": "billie eilish"}, styles=["typo", "incomplete"]),
|
| 153 |
+
_case("look up sabrina carpenter on spotify n play", "spotify_search_play", {"query": "sabrina carpenter"}, styles=["slang", "incomplete"]),
|
| 154 |
+
_case("spotify: search neo soul and hit play", "spotify_search_play", {"query": "neo soul"}, styles=["incomplete"]),
|
| 155 |
+
_case("find tyler the creator on spotify and play", "spotify_search_play", {"query": "tyler the creator"}, styles=["incomplete"]),
|
| 156 |
+
_case("spotify play after searching radiohead", "spotify_search_play", {"query": "radiohead"}, styles=["incomplete"]),
|
| 157 |
+
_case("yo spotify search dnb and play", "spotify_search_play", {"query": "dnb"}, styles=["slang", "incomplete"]),
|
| 158 |
+
_case("can u find frank ocean on spotify and play it", "spotify_search_play", {"query": "frank ocean"}, styles=["conversational"]),
|
| 159 |
+
_case("spotify search classical piano play", "spotify_search_play", {"query": "classical piano"}, styles=["incomplete"]),
|
| 160 |
+
_case("spotify search play some drake tracks", "spotify_search_play", {"query": "drake tracks"}, styles=["incomplete"]),
|
| 161 |
+
_case("spotify find latin reggaeton and play", "spotify_search_play", {"query": "latin reggaeton"}, styles=["incomplete"]),
|
| 162 |
+
_case("search spotify for aphex twin then play", "spotify_search_play", {"query": "aphex twin"}, styles=["incomplete"]),
|
| 163 |
+
_case("spotify play whatever u find for synthwave", "spotify_search_play", {"query": "synthwave"}, styles=["slang", "conversational"]),
|
| 164 |
+
_case("find mitski on spotify play pls", "spotify_search_play", {"query": "mitski"}, styles=["incomplete", "conversational"]),
|
| 165 |
+
_case("soptify search play khruangbin", "spotify_search_play", {"query": "khruangbin"}, styles=["typo", "incomplete"]),
|
| 166 |
+
_case("look up burna boy spotify and play", "spotify_search_play", {"query": "burna boy"}, styles=["incomplete"]),
|
| 167 |
+
_case("spotify search play some afrobeats", "spotify_search_play", {"query": "afrobeats"}, styles=["incomplete"]),
|
| 168 |
+
_case("find fred again on spotify n play it", "spotify_search_play", {"query": "fred again"}, styles=["slang", "incomplete"]),
|
| 169 |
+
_case("spotify search play 90s r&b", "spotify_search_play", {"query": "90s r&b"}, styles=["incomplete"]),
|
| 170 |
+
_case("search spotify play tame impala", "spotify_search_play", {"query": "tame impala"}, styles=["incomplete"]),
|
| 171 |
+
|
| 172 |
+
# --- Uber (22) ---
|
| 173 |
+
_case("ubber to jfk pls", "uber_request_ride", {"destination": "jfk"}, styles=["typo", "incomplete", "conversational"]),
|
| 174 |
+
_case("need a ride to 42 oak street on uber", "uber_request_ride", {"destination": "42 oak street"}, styles=["conversational"]),
|
| 175 |
+
_case("hop in an uber to the vet clinic", "uber_request_ride", {"destination": "the vet clinic"}, styles=["slang"]),
|
| 176 |
+
_case("ubr me to whole foods on 5th", "uber_request_ride", {"destination": "whole foods on 5th"}, styles=["typo", "slang", "incomplete"]),
|
| 177 |
+
_case("get uber to coworking space downtown", "uber_request_ride", {"destination": "coworking space downtown"}, styles=["incomplete"]),
|
| 178 |
+
_case("book ride to mom's house via uber", "uber_request_ride", {"destination": "mom's house"}, styles=["incomplete"]),
|
| 179 |
+
_case("uber — take me to pier 39", "uber_request_ride", {"destination": "pier 39"}, styles=["incomplete", "conversational"]),
|
| 180 |
+
_case("can u call uber to stanford campus", "uber_request_ride", {"destination": "stanford campus"}, styles=["conversational"]),
|
| 181 |
+
_case("ride to airport terminal 2 uber", "uber_request_ride", {"destination": "airport terminal 2"}, styles=["incomplete"]),
|
| 182 |
+
_case("uber to the sushi place on main", "uber_request_ride", {"destination": "the sushi place on main"}, styles=["incomplete", "conversational"]),
|
| 183 |
+
_case("need uber to ikea rn", "uber_request_ride", {"destination": "ikea"}, styles=["slang", "incomplete"]),
|
| 184 |
+
_case("request uber to brooklyn bridge park", "uber_request_ride", {"destination": "brooklyn bridge park"}, styles=["incomplete"]),
|
| 185 |
+
_case("uber drop me at la guardia", "uber_request_ride", {"destination": "la guardia"}, styles=["slang", "incomplete"]),
|
| 186 |
+
_case("get a cab on uber to dentist on maple ave", "uber_request_ride", {"destination": "dentist on maple ave"}, styles=["conversational"]),
|
| 187 |
+
_case("uber to friends apartment 8b", "uber_request_ride", {"destination": "friends apartment 8b"}, styles=["incomplete"]),
|
| 188 |
+
_case("yo uber to the concert venue", "uber_request_ride", {"destination": "the concert venue"}, styles=["slang", "incomplete"]),
|
| 189 |
+
_case("book uber ride to seattle tacoma airport", "uber_request_ride", {"destination": "seattle tacoma airport"}, styles=["incomplete"]),
|
| 190 |
+
_case("uber pls to union station", "uber_request_ride", {"destination": "union station"}, styles=["incomplete", "conversational"]),
|
| 191 |
+
_case("ride share to target on broadway uber", "uber_request_ride", {"destination": "target on broadway"}, styles=["incomplete"]),
|
| 192 |
+
_case("uber me over to the marriott hotel", "uber_request_ride", {"destination": "the marriott hotel"}, styles=["slang", "incomplete"]),
|
| 193 |
+
_case("need uber to physio appointment on elm", "uber_request_ride", {"destination": "physio appointment on elm"}, styles=["incomplete"]),
|
| 194 |
+
_case("call uber to the dog park", "uber_request_ride", {"destination": "the dog park"}, styles=["incomplete"]),
|
| 195 |
+
|
| 196 |
+
# --- Gmail (22) ---
|
| 197 |
+
_case("gmal email recruiter saying im interested in the role", "gmail_send_email", {"recipient": "recruiter", "message": "im interested in the role"}, styles=["typo", "incomplete"]),
|
| 198 |
+
_case("shoot an email via gmail to finance@acme.co — invoice overdue", "gmail_send_email", {"recipient": "finance@acme.co", "message": "invoice overdue"}, styles=["slang", "conversational"]),
|
| 199 |
+
_case("gmail landlord asking about lease renewal", "gmail_send_email", {"recipient": "landlord", "message": "asking about lease renewal"}, styles=["incomplete"]),
|
| 200 |
+
_case("email my manager on gmail: heads up sick day tomorrow", "gmail_send_email", {"recipient": "my manager", "message": "heads up sick day tomorrow"}, styles=["conversational"]),
|
| 201 |
+
_case("compose gmail to nora.park@design.io portfolio link attached", "gmail_send_email", {"recipient": "nora.park@design.io", "message": "portfolio link attached"}, styles=["incomplete"]),
|
| 202 |
+
_case("gmail msg to thesis advisor draft attached for review", "gmail_send_email", {"recipient": "thesis advisor", "message": "draft attached for review"}, styles=["incomplete"]),
|
| 203 |
+
_case("send gmail to carpool buddy running 10 late", "gmail_send_email", {"recipient": "carpool buddy", "message": "running 10 late"}, styles=["slang", "incomplete"]),
|
| 204 |
+
_case("write gmail to billing@saas.com cancel subscription pls", "gmail_send_email", {"recipient": "billing@saas.com", "message": "cancel subscription pls"}, styles=["conversational"]),
|
| 205 |
+
_case("gmail email mentor thx for the intro", "gmail_send_email", {"recipient": "mentor", "message": "thx for the intro"}, styles=["slang", "incomplete"]),
|
| 206 |
+
_case("ok gmail to ops-team@startup.dev deploy failed rollback done", "gmail_send_email", {"recipient": "ops-team@startup.dev", "message": "deploy failed rollback done"}, styles=["conversational", "incomplete"]),
|
| 207 |
+
_case("email thru gmail to dentist office reschedule appt", "gmail_send_email", {"recipient": "dentist office", "message": "reschedule appt"}, styles=["incomplete"]),
|
| 208 |
+
_case("gmail send to contractor quote approved lets proceed", "gmail_send_email", {"recipient": "contractor", "message": "quote approved lets proceed"}, styles=["incomplete"]),
|
| 209 |
+
_case("hey gmail my professor question about deadline extension", "gmail_send_email", {"recipient": "my professor", "message": "question about deadline extension"}, styles=["conversational", "incomplete"]),
|
| 210 |
+
_case("gmail to hr@corp.net pto request for next week", "gmail_send_email", {"recipient": "hr@corp.net", "message": "pto request for next week"}, styles=["incomplete"]),
|
| 211 |
+
_case("send email gmail to vendor payment sent today", "gmail_send_email", {"recipient": "vendor", "message": "payment sent today"}, styles=["incomplete"]),
|
| 212 |
+
_case("gmial client@studio.com mockups ready for feedback", "gmail_send_email", {"recipient": "client@studio.com", "message": "mockups ready for feedback"}, styles=["typo", "incomplete"]),
|
| 213 |
+
_case("compose email on gmail to roommate utilities split attached", "gmail_send_email", {"recipient": "roommate", "message": "utilities split attached"}, styles=["incomplete"]),
|
| 214 |
+
_case("gmail to travel agent pls confirm hotel booking", "gmail_send_email", {"recipient": "travel agent", "message": "pls confirm hotel booking"}, styles=["conversational"]),
|
| 215 |
+
_case("email gmail coach thanks for the session notes", "gmail_send_email", {"recipient": "coach", "message": "thanks for the session notes"}, styles=["incomplete"]),
|
| 216 |
+
_case("gmail msg to editor final chapter attached", "gmail_send_email", {"recipient": "editor", "message": "final chapter attached"}, styles=["incomplete"]),
|
| 217 |
+
_case("send gmail to internship lead following up on application", "gmail_send_email", {"recipient": "internship lead", "message": "following up on application"}, styles=["incomplete"]),
|
| 218 |
+
_case("gmail email to dad flight details attached", "gmail_send_email", {"recipient": "dad", "message": "flight details attached"}, styles=["incomplete"]),
|
| 219 |
+
|
| 220 |
+
# --- Calendar (24) ---
|
| 221 |
+
_case("put dentist on calender next thurs 3pm", "calendar_create_event", {"title": "dentist", "date": "next thurs", "time": "3pm"}, styles=["typo", "incomplete"]),
|
| 222 |
+
_case("schedule 1:1 w/ manager monday 11am", "calendar_create_event", {"title": "1:1 w/ manager", "date": "monday", "time": "11am"}, styles=["slang", "incomplete"]),
|
| 223 |
+
_case("add calendar block focus time tuesday 9-11", "calendar_create_event", {"title": "focus time", "date": "tuesday", "time": "9-11"}, styles=["incomplete"]),
|
| 224 |
+
_case("book therapy session friday 5 pm on calendar", "calendar_create_event", {"title": "therapy session", "date": "friday", "time": "5 pm"}, styles=["incomplete"]),
|
| 225 |
+
_case("cal event: team retro wednesday 4pm", "calendar_create_event", {"title": "team retro", "date": "wednesday", "time": "4pm"}, styles=["slang", "incomplete"]),
|
| 226 |
+
_case("put parent teacher conf on calendar oct 12 6pm", "calendar_create_event", {"title": "parent teacher conf", "date": "oct 12", "time": "6pm"}, styles=["incomplete"]),
|
| 227 |
+
_case("schedule gym class saturday 8 am calendar", "calendar_create_event", {"title": "gym class", "date": "saturday", "time": "8 am"}, styles=["incomplete"]),
|
| 228 |
+
_case("add calendar entry flight to nyc sunday 7:45 am", "calendar_create_event", {"title": "flight to nyc", "date": "sunday", "time": "7:45 am"}, styles=["incomplete"]),
|
| 229 |
+
_case("calendar — lunch w/ priya tomorrow noon", "calendar_create_event", {"title": "lunch w/ priya", "date": "tomorrow", "time": "noon"}, styles=["slang", "incomplete", "conversational"]),
|
| 230 |
+
_case("set cal event sprint planning next monday 10", "calendar_create_event", {"title": "sprint planning", "date": "next monday", "time": "10"}, styles=["slang", "incomplete"]),
|
| 231 |
+
_case("put car inspection on calendar tuesday 2:30 pm", "calendar_create_event", {"title": "car inspection", "date": "tuesday", "time": "2:30 pm"}, styles=["incomplete"]),
|
| 232 |
+
_case("scheduel interview prep wednesday 6 pm", "calendar_create_event", {"title": "interview prep", "date": "wednesday", "time": "6 pm"}, styles=["typo", "incomplete"]),
|
| 233 |
+
_case("add to calendar vet appt thursday 1 pm", "calendar_create_event", {"title": "vet appt", "date": "thursday", "time": "1 pm"}, styles=["incomplete"]),
|
| 234 |
+
_case("calendar event board presentation friday 9 am", "calendar_create_event", {"title": "board presentation", "date": "friday", "time": "9 am"}, styles=["incomplete"]),
|
| 235 |
+
_case("book calendar slot date night saturday 8 pm", "calendar_create_event", {"title": "date night", "date": "saturday", "time": "8 pm"}, styles=["incomplete"]),
|
| 236 |
+
_case("put standup notes review on cal monday 9:30", "calendar_create_event", {"title": "standup notes review", "date": "monday", "time": "9:30"}, styles=["slang", "incomplete"]),
|
| 237 |
+
_case("schedule movers arrival next saturday 10 am", "calendar_create_event", {"title": "movers arrival", "date": "next saturday", "time": "10 am"}, styles=["incomplete"]),
|
| 238 |
+
_case("add calendar tax appointment april 5 11 am", "calendar_create_event", {"title": "tax appointment", "date": "april 5", "time": "11 am"}, styles=["incomplete"]),
|
| 239 |
+
_case("calender block deep work thursday morning 8", "calendar_create_event", {"title": "deep work", "date": "thursday morning", "time": "8"}, styles=["typo", "incomplete"]),
|
| 240 |
+
_case("put wedding rehearsal on calendar june 20 5 pm", "calendar_create_event", {"title": "wedding rehearsal", "date": "june 20", "time": "5 pm"}, styles=["incomplete"]),
|
| 241 |
+
_case("schedule product demo next wednesday 3 pm", "calendar_create_event", {"title": "product demo", "date": "next wednesday", "time": "3 pm"}, styles=["incomplete"]),
|
| 242 |
+
_case("add calendar coffee chat friday 10:30 am", "calendar_create_event", {"title": "coffee chat", "date": "friday", "time": "10:30 am"}, styles=["incomplete"]),
|
| 243 |
+
_case("book calendar orthodontist monday 4:15 pm", "calendar_create_event", {"title": "orthodontist", "date": "monday", "time": "4:15 pm"}, styles=["incomplete"]),
|
| 244 |
+
_case("put hackathon submission deadline on calendar sunday midnight", "calendar_create_event", {"title": "hackathon submission deadline", "date": "sunday", "time": "midnight"}, styles=["incomplete"]),
|
| 245 |
+
|
| 246 |
+
# --- Slack (22) ---
|
| 247 |
+
_case("open slack #incidents pls", "slack_open_channel", {"channel": "incidents"}, styles=["incomplete", "conversational"]),
|
| 248 |
+
_case("jump to product-launch channel on slack", "slack_open_channel", {"channel": "product-launch"}, styles=["slang", "incomplete"]),
|
| 249 |
+
_case("slack go to team-watercooler", "slack_open_channel", {"channel": "team-watercooler"}, styles=["incomplete"]),
|
| 250 |
+
_case("pull up slack channel ios-bugs", "slack_open_channel", {"channel": "ios-bugs"}, styles=["slang", "incomplete"]),
|
| 251 |
+
_case("switch slack to growth-experiments", "slack_open_channel", {"channel": "growth-experiments"}, styles=["incomplete"]),
|
| 252 |
+
_case("show slack channel design-system", "slack_open_channel", {"channel": "design-system"}, styles=["incomplete"]),
|
| 253 |
+
_case("navigate slack to platform-oncall", "slack_open_channel", {"channel": "platform-oncall"}, styles=["incomplete"]),
|
| 254 |
+
_case("open the slack channel ml-research thx", "slack_open_channel", {"channel": "ml-research"}, styles=["conversational"]),
|
| 255 |
+
_case("slack channel customer-escalations open it", "slack_open_channel", {"channel": "customer-escalations"}, styles=["incomplete"]),
|
| 256 |
+
_case("take me to #backend-standup in slack", "slack_open_channel", {"channel": "backend-standup"}, styles=["incomplete"]),
|
| 257 |
+
_case("open slack for channel release-train", "slack_open_channel", {"channel": "release-train"}, styles=["incomplete"]),
|
| 258 |
+
_case("slack — show me hiring-pipeline channel", "slack_open_channel", {"channel": "hiring-pipeline"}, styles=["conversational", "incomplete"]),
|
| 259 |
+
_case("go slack channel infra-alerts", "slack_open_channel", {"channel": "infra-alerts"}, styles=["incomplete"]),
|
| 260 |
+
_case("open slak channel mobile-crash-reports", "slack_open_channel", {"channel": "mobile-crash-reports"}, styles=["typo", "incomplete"]),
|
| 261 |
+
_case("pull slack up on qa-automation channel", "slack_open_channel", {"channel": "qa-automation"}, styles=["incomplete"]),
|
| 262 |
+
_case("switch to slack channel docs-and-wiki", "slack_open_channel", {"channel": "docs-and-wiki"}, styles=["incomplete"]),
|
| 263 |
+
_case("open slack growth-metrics channel rq", "slack_open_channel", {"channel": "growth-metrics"}, styles=["slang", "incomplete"]),
|
| 264 |
+
_case("show me slack #sales-wins", "slack_open_channel", {"channel": "sales-wins"}, styles=["incomplete"]),
|
| 265 |
+
_case("navigate to slack channel partner-integrations", "slack_open_channel", {"channel": "partner-integrations"}, styles=["incomplete"]),
|
| 266 |
+
_case("open slack channel called observability", "slack_open_channel", {"channel": "observability"}, styles=["incomplete"]),
|
| 267 |
+
_case("slack open eng-hiring channel pls", "slack_open_channel", {"channel": "eng-hiring"}, styles=["incomplete", "conversational"]),
|
| 268 |
+
_case("jump slack to ai-playground channel", "slack_open_channel", {"channel": "ai-playground"}, styles=["slang", "incomplete"]),
|
| 269 |
+
]
|
| 270 |
+
|
| 271 |
+
|
| 272 |
+
def normalize_prompt(text: str) -> str:
|
| 273 |
+
return " ".join(text.lower().strip().split())
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def token_set(text: str) -> set[str]:
|
| 277 |
+
return set(re.findall(r"[a-z0-9@]+", text.lower()))
|
| 278 |
+
|
| 279 |
+
|
| 280 |
+
def jaccard_similarity(left: str, right: str) -> float:
|
| 281 |
+
left_tokens = token_set(left)
|
| 282 |
+
right_tokens = token_set(right)
|
| 283 |
+
if not left_tokens or not right_tokens:
|
| 284 |
+
return 0.0
|
| 285 |
+
return len(left_tokens & right_tokens) / len(left_tokens | right_tokens)
|
| 286 |
+
|
| 287 |
+
|
| 288 |
+
def load_existing_prompts() -> set[str]:
|
| 289 |
+
prompts: set[str] = set()
|
| 290 |
+
|
| 291 |
+
if TRAIN_PATH.exists():
|
| 292 |
+
with TRAIN_PATH.open(encoding="utf-8") as handle:
|
| 293 |
+
for line in handle:
|
| 294 |
+
record = json.loads(line)
|
| 295 |
+
for message in record["messages"]:
|
| 296 |
+
if message["role"] == "user":
|
| 297 |
+
prompts.add(normalize_prompt(message["content"]))
|
| 298 |
+
|
| 299 |
+
if EVAL_PATH.exists():
|
| 300 |
+
with EVAL_PATH.open(encoding="utf-8") as handle:
|
| 301 |
+
for case in json.load(handle):
|
| 302 |
+
prompts.add(normalize_prompt(case["prompt"]))
|
| 303 |
+
|
| 304 |
+
return prompts
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def validate_cases(cases: list[dict], existing: set[str]) -> list[str]:
|
| 308 |
+
errors: list[str] = []
|
| 309 |
+
seen: set[str] = set()
|
| 310 |
+
|
| 311 |
+
if len(cases) != TARGET_COUNT:
|
| 312 |
+
errors.append(f"Expected {TARGET_COUNT} prompts, got {len(cases)}")
|
| 313 |
+
|
| 314 |
+
skill_counts: dict[str, int] = {}
|
| 315 |
+
style_counts: dict[str, int] = {}
|
| 316 |
+
|
| 317 |
+
for index, case in enumerate(cases, start=1):
|
| 318 |
+
prompt = case["prompt"]
|
| 319 |
+
normalized = normalize_prompt(prompt)
|
| 320 |
+
skill = case["expected"]["skill"]
|
| 321 |
+
|
| 322 |
+
if normalized in seen:
|
| 323 |
+
errors.append(f"Duplicate benchmark prompt: {prompt}")
|
| 324 |
+
seen.add(normalized)
|
| 325 |
+
|
| 326 |
+
if normalized in existing:
|
| 327 |
+
errors.append(f"Exact overlap with training/eval: {prompt}")
|
| 328 |
+
|
| 329 |
+
for existing_prompt in existing:
|
| 330 |
+
if jaccard_similarity(normalized, existing_prompt) >= 0.85:
|
| 331 |
+
errors.append(
|
| 332 |
+
f"High similarity ({jaccard_similarity(normalized, existing_prompt):.2f}) "
|
| 333 |
+
f"with existing prompt '{existing_prompt}': {prompt}"
|
| 334 |
+
)
|
| 335 |
+
break
|
| 336 |
+
|
| 337 |
+
skill_counts[skill] = skill_counts.get(skill, 0) + 1
|
| 338 |
+
for style in case.get("styles", []):
|
| 339 |
+
style_counts[style] = style_counts.get(style, 0) + 1
|
| 340 |
+
|
| 341 |
+
if skill not in BENCHMARK_SKILLS:
|
| 342 |
+
errors.append(f"Case {index}: unsupported skill {skill}")
|
| 343 |
+
|
| 344 |
+
for skill in BENCHMARK_SKILLS:
|
| 345 |
+
count = skill_counts.get(skill, 0)
|
| 346 |
+
if count == 0:
|
| 347 |
+
errors.append(f"Missing skill coverage: {skill}")
|
| 348 |
+
|
| 349 |
+
for style in ("slang", "typo", "incomplete", "conversational"):
|
| 350 |
+
count = style_counts.get(style, 0)
|
| 351 |
+
if count < 20:
|
| 352 |
+
errors.append(f"Insufficient '{style}' coverage: {count} (need >= 20)")
|
| 353 |
+
|
| 354 |
+
return errors
|
| 355 |
+
|
| 356 |
+
|
| 357 |
+
def assign_ids(cases: list[dict]) -> list[dict]:
|
| 358 |
+
output: list[dict] = []
|
| 359 |
+
for index, case in enumerate(cases, start=1):
|
| 360 |
+
output.append({"id": f"pa-{index:03d}", **case})
|
| 361 |
+
return output
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def main() -> None:
|
| 365 |
+
parser = argparse.ArgumentParser(description="Generate Pocket Automator benchmark prompts.")
|
| 366 |
+
parser.add_argument(
|
| 367 |
+
"--validate-only",
|
| 368 |
+
action="store_true",
|
| 369 |
+
help="Validate existing benchmark file without rewriting it.",
|
| 370 |
+
)
|
| 371 |
+
args = parser.parse_args()
|
| 372 |
+
|
| 373 |
+
existing = load_existing_prompts()
|
| 374 |
+
cases = assign_ids(BENCHMARK_CASES)
|
| 375 |
+
errors = validate_cases(cases, existing)
|
| 376 |
+
|
| 377 |
+
if errors:
|
| 378 |
+
print("Validation failed:")
|
| 379 |
+
for error in errors:
|
| 380 |
+
print(f" - {error}")
|
| 381 |
+
sys.exit(1)
|
| 382 |
+
|
| 383 |
+
if args.validate_only:
|
| 384 |
+
print(f"Validation passed for {len(cases)} benchmark prompts.")
|
| 385 |
+
return
|
| 386 |
+
|
| 387 |
+
OUTPUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
| 388 |
+
with OUTPUT_PATH.open("w", encoding="utf-8") as handle:
|
| 389 |
+
json.dump(cases, handle, indent=2)
|
| 390 |
+
handle.write("\n")
|
| 391 |
+
|
| 392 |
+
skill_counts = {}
|
| 393 |
+
for case in cases:
|
| 394 |
+
skill = case["expected"]["skill"]
|
| 395 |
+
skill_counts[skill] = skill_counts.get(skill, 0) + 1
|
| 396 |
+
|
| 397 |
+
print(f"Wrote {len(cases)} benchmark prompts to {OUTPUT_PATH}")
|
| 398 |
+
print("Skill distribution:")
|
| 399 |
+
for skill in BENCHMARK_SKILLS:
|
| 400 |
+
print(f" {skill}: {skill_counts.get(skill, 0)}")
|
| 401 |
+
|
| 402 |
+
|
| 403 |
+
if __name__ == "__main__":
|
| 404 |
+
main()
|
src/classifier_prompt.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
"""Shared system
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
@@ -9,9 +9,26 @@ SYSTEM_PROMPT = (
|
|
| 9 |
"to pick the correct skill."
|
| 10 |
)
|
| 11 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
|
| 13 |
def build_classifier_messages(user_content: str) -> list[dict[str, str]]:
|
| 14 |
return [
|
| 15 |
{"role": "system", "content": SYSTEM_PROMPT},
|
| 16 |
{"role": "user", "content": user_content},
|
| 17 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Shared system prompts for skill classification and intent extraction."""
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
| 9 |
"to pick the correct skill."
|
| 10 |
)
|
| 11 |
|
| 12 |
+
INTENT_SYSTEM_PROMPT = (
|
| 13 |
+
"You extract structured Android automation intents from natural language. "
|
| 14 |
+
'Reply with JSON only: {"skill": "<skill_name>", "parameters": {<extracted_fields>}}. '
|
| 15 |
+
"Pick exactly one skill. Extract all relevant parameters mentioned in the request "
|
| 16 |
+
"(contact names, messages, times, destinations, channel names, search queries, etc.). "
|
| 17 |
+
"Use an empty object for parameters when the skill needs none. "
|
| 18 |
+
"Use the app or action named in the request (contacts, Gmail, Slack, YouTube, etc.) "
|
| 19 |
+
"to pick the correct skill."
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
|
| 23 |
def build_classifier_messages(user_content: str) -> list[dict[str, str]]:
|
| 24 |
return [
|
| 25 |
{"role": "system", "content": SYSTEM_PROMPT},
|
| 26 |
{"role": "user", "content": user_content},
|
| 27 |
]
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def build_intent_messages(user_content: str) -> list[dict[str, str]]:
|
| 31 |
+
return [
|
| 32 |
+
{"role": "system", "content": INTENT_SYSTEM_PROMPT},
|
| 33 |
+
{"role": "user", "content": user_content},
|
| 34 |
+
]
|
src/evaluate_intent.py
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate the fine-tuned intent-extraction model locally.
|
| 3 |
+
|
| 4 |
+
Run:
|
| 5 |
+
python -m src.evaluate_intent
|
| 6 |
+
python -m src.evaluate_intent --model-path ./trained_model/adapter
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import argparse
|
| 12 |
+
import json
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
from peft import PeftModel
|
| 17 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 18 |
+
|
| 19 |
+
from src.classifier_prompt import build_intent_messages
|
| 20 |
+
from src.paths import DATA_DIR, TRAINED_MODEL_DIR
|
| 21 |
+
from src.skill_utils import extract_intent, intent_matches
|
| 22 |
+
|
| 23 |
+
BASE_MODEL = "Qwen/Qwen2.5-3B-Instruct"
|
| 24 |
+
MAX_SEQ_LENGTH = 2048
|
| 25 |
+
MAX_NEW_TOKENS = 128
|
| 26 |
+
DEFAULT_MODEL_PATH = TRAINED_MODEL_DIR / "adapter"
|
| 27 |
+
FALLBACK_MODEL_PATH = TRAINED_MODEL_DIR / "merged"
|
| 28 |
+
EVAL_PROMPTS_PATH = DATA_DIR / "eval_intent_prompts.json"
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def load_eval_prompts() -> list[dict]:
|
| 32 |
+
with EVAL_PROMPTS_PATH.open(encoding="utf-8") as handle:
|
| 33 |
+
return json.load(handle)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def pick_device() -> str:
|
| 37 |
+
if torch.cuda.is_available():
|
| 38 |
+
return "cuda"
|
| 39 |
+
if torch.backends.mps.is_available():
|
| 40 |
+
return "mps"
|
| 41 |
+
return "cpu"
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def is_adapter_path(model_path: Path) -> bool:
|
| 45 |
+
return (model_path / "adapter_config.json").exists()
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def is_complete_merged_model(model_path: Path) -> bool:
|
| 49 |
+
config_path = model_path / "config.json"
|
| 50 |
+
if not config_path.exists() or config_path.stat().st_size == 0:
|
| 51 |
+
return False
|
| 52 |
+
|
| 53 |
+
index_path = model_path / "model.safetensors.index.json"
|
| 54 |
+
if index_path.exists():
|
| 55 |
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
| 56 |
+
shard_names = set(index.get("weight_map", {}).values())
|
| 57 |
+
return all(
|
| 58 |
+
(model_path / shard).exists() and (model_path / shard).stat().st_size > 0
|
| 59 |
+
for shard in shard_names
|
| 60 |
+
)
|
| 61 |
+
|
| 62 |
+
single_shard = model_path / "model.safetensors"
|
| 63 |
+
return single_shard.exists() and single_shard.stat().st_size > 0
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def resolve_model_path(path: str) -> Path:
|
| 67 |
+
model_path = Path(path)
|
| 68 |
+
if model_path.exists():
|
| 69 |
+
if is_adapter_path(model_path):
|
| 70 |
+
return model_path
|
| 71 |
+
if is_complete_merged_model(model_path):
|
| 72 |
+
return model_path
|
| 73 |
+
print(f"Warning: {model_path} looks incomplete; trying adapter fallback.")
|
| 74 |
+
|
| 75 |
+
adapter_path = DEFAULT_MODEL_PATH
|
| 76 |
+
if adapter_path.exists() and is_adapter_path(adapter_path):
|
| 77 |
+
print(f"Using LoRA adapter at {adapter_path}")
|
| 78 |
+
return adapter_path
|
| 79 |
+
|
| 80 |
+
merged_path = FALLBACK_MODEL_PATH
|
| 81 |
+
if merged_path.exists() and is_complete_merged_model(merged_path):
|
| 82 |
+
print(f"Using merged model at {merged_path}")
|
| 83 |
+
return merged_path
|
| 84 |
+
|
| 85 |
+
raise FileNotFoundError(
|
| 86 |
+
"No usable trained model found. Expected a complete merged model or "
|
| 87 |
+
f"LoRA adapter at {DEFAULT_MODEL_PATH}."
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def load_model(model_path: Path, device: str):
|
| 92 |
+
dtype = torch.float16 if device in {"cuda", "mps"} else torch.float32
|
| 93 |
+
|
| 94 |
+
if is_adapter_path(model_path):
|
| 95 |
+
print(f"Loading base model: {BASE_MODEL}")
|
| 96 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 97 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 98 |
+
BASE_MODEL,
|
| 99 |
+
dtype=dtype,
|
| 100 |
+
low_cpu_mem_usage=True,
|
| 101 |
+
)
|
| 102 |
+
model = PeftModel.from_pretrained(base_model, str(model_path))
|
| 103 |
+
else:
|
| 104 |
+
print("Loading merged model weights")
|
| 105 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 106 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 107 |
+
model_path,
|
| 108 |
+
dtype=dtype,
|
| 109 |
+
low_cpu_mem_usage=True,
|
| 110 |
+
)
|
| 111 |
+
|
| 112 |
+
model.to(device)
|
| 113 |
+
model.eval()
|
| 114 |
+
return model, tokenizer
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def generate_intent(model, tokenizer, prompt: str, device: str) -> str:
|
| 118 |
+
messages = build_intent_messages(prompt)
|
| 119 |
+
inputs = tokenizer.apply_chat_template(
|
| 120 |
+
messages,
|
| 121 |
+
tokenize=True,
|
| 122 |
+
add_generation_prompt=True,
|
| 123 |
+
return_tensors="pt",
|
| 124 |
+
).to(device)
|
| 125 |
+
|
| 126 |
+
with torch.inference_mode():
|
| 127 |
+
outputs = model.generate(
|
| 128 |
+
input_ids=inputs,
|
| 129 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 130 |
+
use_cache=True,
|
| 131 |
+
do_sample=False,
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
generated = outputs[0][inputs.shape[1] :]
|
| 135 |
+
return tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def evaluate(model, tokenizer, test_prompts: list[dict], device: str) -> tuple[float, float]:
|
| 139 |
+
skill_correct = 0
|
| 140 |
+
full_correct = 0
|
| 141 |
+
|
| 142 |
+
for index, case in enumerate(test_prompts, start=1):
|
| 143 |
+
prompt = case["prompt"]
|
| 144 |
+
expected = case["expected"]
|
| 145 |
+
expected_skill = expected["skill"]
|
| 146 |
+
expected_params = expected.get("parameters", {})
|
| 147 |
+
|
| 148 |
+
raw_output = generate_intent(model, tokenizer, prompt, device)
|
| 149 |
+
predicted = extract_intent(raw_output)
|
| 150 |
+
|
| 151 |
+
skill_pass = predicted is not None and predicted.get("skill") == expected_skill
|
| 152 |
+
full_pass = intent_matches(predicted, expected_skill, expected_params)
|
| 153 |
+
|
| 154 |
+
skill_correct += int(skill_pass)
|
| 155 |
+
full_correct += int(full_pass)
|
| 156 |
+
|
| 157 |
+
print(f"--- [{index}/{len(test_prompts)}] ---")
|
| 158 |
+
print(f"Prompt: {prompt}")
|
| 159 |
+
print(f"Expected: {json.dumps(expected, separators=(',', ':'))}")
|
| 160 |
+
print(f"Predicted: {json.dumps(predicted, separators=(',', ':')) if predicted else raw_output}")
|
| 161 |
+
print(f"Skill: {'PASS' if skill_pass else 'FAIL'}")
|
| 162 |
+
print(f"Full: {'PASS' if full_pass else 'FAIL'}")
|
| 163 |
+
print()
|
| 164 |
+
|
| 165 |
+
skill_accuracy = skill_correct / len(test_prompts)
|
| 166 |
+
full_accuracy = full_correct / len(test_prompts)
|
| 167 |
+
print(f"Skill accuracy: {skill_correct}/{len(test_prompts)} ({skill_accuracy:.1%})")
|
| 168 |
+
print(f"Full intent accuracy: {full_correct}/{len(test_prompts)} ({full_accuracy:.1%})")
|
| 169 |
+
return skill_accuracy, full_accuracy
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def main() -> None:
|
| 173 |
+
parser = argparse.ArgumentParser(description="Evaluate the intent extraction model.")
|
| 174 |
+
parser.add_argument(
|
| 175 |
+
"--model-path",
|
| 176 |
+
default=str(DEFAULT_MODEL_PATH),
|
| 177 |
+
help=f"Path to LoRA adapter or merged model (default: {DEFAULT_MODEL_PATH})",
|
| 178 |
+
)
|
| 179 |
+
args = parser.parse_args()
|
| 180 |
+
|
| 181 |
+
if not EVAL_PROMPTS_PATH.exists():
|
| 182 |
+
raise FileNotFoundError(
|
| 183 |
+
f"Eval prompts not found at {EVAL_PROMPTS_PATH}. "
|
| 184 |
+
"Run `python scripts/generate_intent_dataset.py` first."
|
| 185 |
+
)
|
| 186 |
+
|
| 187 |
+
test_prompts = load_eval_prompts()
|
| 188 |
+
device = pick_device()
|
| 189 |
+
model_path = resolve_model_path(args.model_path)
|
| 190 |
+
print(f"Device: {device}")
|
| 191 |
+
print(f"Loading model from {model_path.resolve()}")
|
| 192 |
+
model, tokenizer = load_model(model_path, device)
|
| 193 |
+
print(f"Running intent evaluation on {len(test_prompts)} prompts...\n")
|
| 194 |
+
evaluate(model, tokenizer, test_prompts, device)
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
if __name__ == "__main__":
|
| 198 |
+
main()
|
src/evaluate_pocket_benchmark.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Evaluate the Pocket Automator real-world benchmark locally.
|
| 3 |
+
|
| 4 |
+
Run:
|
| 5 |
+
python scripts/generate_pocket_benchmark.py
|
| 6 |
+
python -m src.evaluate_pocket_benchmark
|
| 7 |
+
python -m src.evaluate_pocket_benchmark --model-path ./trained_model/adapter
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import argparse
|
| 13 |
+
import json
|
| 14 |
+
from pathlib import Path
|
| 15 |
+
|
| 16 |
+
import torch
|
| 17 |
+
from peft import PeftModel
|
| 18 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 19 |
+
|
| 20 |
+
from src.classifier_prompt import build_intent_messages
|
| 21 |
+
from src.paths import TRAINED_MODEL_DIR
|
| 22 |
+
from src.pocket_benchmark import (
|
| 23 |
+
BENCHMARK_PROMPTS_PATH,
|
| 24 |
+
load_benchmark_prompts,
|
| 25 |
+
record_result,
|
| 26 |
+
save_benchmark_outputs,
|
| 27 |
+
)
|
| 28 |
+
from src.skill_utils import extract_intent
|
| 29 |
+
|
| 30 |
+
BASE_MODEL = "Qwen/Qwen2.5-3B-Instruct"
|
| 31 |
+
MAX_SEQ_LENGTH = 2048
|
| 32 |
+
MAX_NEW_TOKENS = 128
|
| 33 |
+
DEFAULT_MODEL_PATH = TRAINED_MODEL_DIR / "adapter"
|
| 34 |
+
FALLBACK_MODEL_PATH = TRAINED_MODEL_DIR / "merged"
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def pick_device() -> str:
|
| 38 |
+
if torch.cuda.is_available():
|
| 39 |
+
return "cuda"
|
| 40 |
+
if torch.backends.mps.is_available():
|
| 41 |
+
return "mps"
|
| 42 |
+
return "cpu"
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def is_adapter_path(model_path: Path) -> bool:
|
| 46 |
+
return (model_path / "adapter_config.json").exists()
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def is_complete_merged_model(model_path: Path) -> bool:
|
| 50 |
+
config_path = model_path / "config.json"
|
| 51 |
+
if not config_path.exists() or config_path.stat().st_size == 0:
|
| 52 |
+
return False
|
| 53 |
+
|
| 54 |
+
index_path = model_path / "model.safetensors.index.json"
|
| 55 |
+
if index_path.exists():
|
| 56 |
+
index = json.loads(index_path.read_text(encoding="utf-8"))
|
| 57 |
+
shard_names = set(index.get("weight_map", {}).values())
|
| 58 |
+
return all(
|
| 59 |
+
(model_path / shard).exists() and (model_path / shard).stat().st_size > 0
|
| 60 |
+
for shard in shard_names
|
| 61 |
+
)
|
| 62 |
+
|
| 63 |
+
single_shard = model_path / "model.safetensors"
|
| 64 |
+
return single_shard.exists() and single_shard.stat().st_size > 0
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def resolve_model_path(path: str) -> Path:
|
| 68 |
+
model_path = Path(path)
|
| 69 |
+
if model_path.exists():
|
| 70 |
+
if is_adapter_path(model_path):
|
| 71 |
+
return model_path
|
| 72 |
+
if is_complete_merged_model(model_path):
|
| 73 |
+
return model_path
|
| 74 |
+
print(f"Warning: {model_path} looks incomplete; trying adapter fallback.")
|
| 75 |
+
|
| 76 |
+
adapter_path = DEFAULT_MODEL_PATH
|
| 77 |
+
if adapter_path.exists() and is_adapter_path(adapter_path):
|
| 78 |
+
print(f"Using LoRA adapter at {adapter_path}")
|
| 79 |
+
return adapter_path
|
| 80 |
+
|
| 81 |
+
merged_path = FALLBACK_MODEL_PATH
|
| 82 |
+
if merged_path.exists() and is_complete_merged_model(merged_path):
|
| 83 |
+
print(f"Using merged model at {merged_path}")
|
| 84 |
+
return merged_path
|
| 85 |
+
|
| 86 |
+
raise FileNotFoundError(
|
| 87 |
+
"No usable trained model found. Expected a complete merged model or "
|
| 88 |
+
f"LoRA adapter at {DEFAULT_MODEL_PATH}."
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def load_model(model_path: Path, device: str):
|
| 93 |
+
dtype = torch.float16 if device in {"cuda", "mps"} else torch.float32
|
| 94 |
+
|
| 95 |
+
if is_adapter_path(model_path):
|
| 96 |
+
print(f"Loading base model: {BASE_MODEL}")
|
| 97 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 98 |
+
base_model = AutoModelForCausalLM.from_pretrained(
|
| 99 |
+
BASE_MODEL,
|
| 100 |
+
dtype=dtype,
|
| 101 |
+
low_cpu_mem_usage=True,
|
| 102 |
+
)
|
| 103 |
+
model = PeftModel.from_pretrained(base_model, str(model_path))
|
| 104 |
+
else:
|
| 105 |
+
print("Loading merged model weights")
|
| 106 |
+
tokenizer = AutoTokenizer.from_pretrained(model_path)
|
| 107 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 108 |
+
model_path,
|
| 109 |
+
dtype=dtype,
|
| 110 |
+
low_cpu_mem_usage=True,
|
| 111 |
+
)
|
| 112 |
+
|
| 113 |
+
model.to(device)
|
| 114 |
+
model.eval()
|
| 115 |
+
return model, tokenizer
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def generate_intent(model, tokenizer, prompt: str, device: str) -> str:
|
| 119 |
+
messages = build_intent_messages(prompt)
|
| 120 |
+
inputs = tokenizer.apply_chat_template(
|
| 121 |
+
messages,
|
| 122 |
+
tokenize=True,
|
| 123 |
+
add_generation_prompt=True,
|
| 124 |
+
return_tensors="pt",
|
| 125 |
+
).to(device)
|
| 126 |
+
|
| 127 |
+
with torch.inference_mode():
|
| 128 |
+
outputs = model.generate(
|
| 129 |
+
input_ids=inputs,
|
| 130 |
+
max_new_tokens=MAX_NEW_TOKENS,
|
| 131 |
+
use_cache=True,
|
| 132 |
+
do_sample=False,
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
generated = outputs[0][inputs.shape[1] :]
|
| 136 |
+
return tokenizer.decode(generated, skip_special_tokens=True).strip()
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
def evaluate(model, tokenizer, cases: list[dict], device: str) -> list[dict]:
|
| 140 |
+
results: list[dict] = []
|
| 141 |
+
|
| 142 |
+
for index, case in enumerate(cases, start=1):
|
| 143 |
+
prompt = case["prompt"]
|
| 144 |
+
raw_output = generate_intent(model, tokenizer, prompt, device)
|
| 145 |
+
predicted = extract_intent(raw_output)
|
| 146 |
+
result = record_result(case, raw_output, predicted)
|
| 147 |
+
results.append(result)
|
| 148 |
+
|
| 149 |
+
print(f"--- [{index}/{len(cases)}] {case.get('id', 'n/a')} ---")
|
| 150 |
+
print(f"Prompt: {prompt}")
|
| 151 |
+
print(f"Expected: {json.dumps(case['expected'], separators=(',', ':'))}")
|
| 152 |
+
print(
|
| 153 |
+
f"Predicted: {json.dumps(predicted, separators=(',', ':')) if predicted else raw_output}"
|
| 154 |
+
)
|
| 155 |
+
print(f"Skill: {'PASS' if result['skill_correct'] else 'FAIL'}")
|
| 156 |
+
print(f"Params: {'PASS' if result['parameter_correct'] else 'FAIL'}")
|
| 157 |
+
print(f"Exact JSON:{'PASS' if result['exact_json_match'] else 'FAIL'}")
|
| 158 |
+
print()
|
| 159 |
+
|
| 160 |
+
return results
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
def main() -> None:
|
| 164 |
+
parser = argparse.ArgumentParser(description="Evaluate Pocket Automator benchmark.")
|
| 165 |
+
parser.add_argument(
|
| 166 |
+
"--model-path",
|
| 167 |
+
default=str(DEFAULT_MODEL_PATH),
|
| 168 |
+
help=f"Path to LoRA adapter or merged model (default: {DEFAULT_MODEL_PATH})",
|
| 169 |
+
)
|
| 170 |
+
parser.add_argument(
|
| 171 |
+
"--benchmark-path",
|
| 172 |
+
default=str(BENCHMARK_PROMPTS_PATH),
|
| 173 |
+
help=f"Path to benchmark prompts JSON (default: {BENCHMARK_PROMPTS_PATH})",
|
| 174 |
+
)
|
| 175 |
+
args = parser.parse_args()
|
| 176 |
+
|
| 177 |
+
benchmark_path = Path(args.benchmark_path)
|
| 178 |
+
if not benchmark_path.exists():
|
| 179 |
+
raise FileNotFoundError(
|
| 180 |
+
f"Benchmark prompts not found at {benchmark_path}. "
|
| 181 |
+
"Run `python scripts/generate_pocket_benchmark.py` first."
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
cases = load_benchmark_prompts(benchmark_path)
|
| 185 |
+
device = pick_device()
|
| 186 |
+
model_path = resolve_model_path(args.model_path)
|
| 187 |
+
print(f"Device: {device}")
|
| 188 |
+
print(f"Loading model from {model_path.resolve()}")
|
| 189 |
+
model, tokenizer = load_model(model_path, device)
|
| 190 |
+
print(f"Running Pocket Automator benchmark on {len(cases)} prompts...\n")
|
| 191 |
+
|
| 192 |
+
results = evaluate(model, tokenizer, cases, device)
|
| 193 |
+
metrics, report = save_benchmark_outputs(results)
|
| 194 |
+
|
| 195 |
+
print("--- Pocket Automator Benchmark Summary ---")
|
| 196 |
+
print(report)
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
if __name__ == "__main__":
|
| 200 |
+
main()
|
src/pocket_benchmark.py
ADDED
|
@@ -0,0 +1,316 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Pocket Automator benchmark utilities — metrics, confusion matrix, failure reports."""
|
| 2 |
+
|
| 3 |
+
from __future__ import annotations
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
from collections import defaultdict
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from typing import Any
|
| 9 |
+
|
| 10 |
+
try:
|
| 11 |
+
from src.skill_utils import (
|
| 12 |
+
normalize_param,
|
| 13 |
+
normalize_skill,
|
| 14 |
+
parameters_match,
|
| 15 |
+
)
|
| 16 |
+
except ImportError:
|
| 17 |
+
from skill_utils import (
|
| 18 |
+
normalize_param,
|
| 19 |
+
normalize_skill,
|
| 20 |
+
parameters_match,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
PROJECT_ROOT = Path(__file__).resolve().parent.parent
|
| 24 |
+
DATA_DIR = PROJECT_ROOT / "data"
|
| 25 |
+
|
| 26 |
+
BENCHMARK_PROMPTS_PATH = DATA_DIR / "pocket_benchmark_prompts.json"
|
| 27 |
+
BENCHMARK_RESULTS_PATH = DATA_DIR / "pocket_benchmark_results.json"
|
| 28 |
+
BENCHMARK_REPORT_PATH = DATA_DIR / "pocket_benchmark_report.txt"
|
| 29 |
+
|
| 30 |
+
BENCHMARK_SKILLS: tuple[str, ...] = (
|
| 31 |
+
"create_alarm",
|
| 32 |
+
"whatsapp_send_message",
|
| 33 |
+
"spotify_pause",
|
| 34 |
+
"spotify_play_playlist",
|
| 35 |
+
"spotify_search_play",
|
| 36 |
+
"uber_request_ride",
|
| 37 |
+
"gmail_send_email",
|
| 38 |
+
"calendar_create_event",
|
| 39 |
+
"slack_open_channel",
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
DOMAIN_BY_SKILL: dict[str, str] = {
|
| 43 |
+
"create_alarm": "alarms",
|
| 44 |
+
"whatsapp_send_message": "whatsapp",
|
| 45 |
+
"spotify_pause": "spotify",
|
| 46 |
+
"spotify_play_playlist": "spotify",
|
| 47 |
+
"spotify_search_play": "spotify",
|
| 48 |
+
"uber_request_ride": "uber",
|
| 49 |
+
"gmail_send_email": "gmail",
|
| 50 |
+
"calendar_create_event": "calendar",
|
| 51 |
+
"slack_open_channel": "slack",
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
def load_benchmark_prompts(path: Path | None = None) -> list[dict[str, Any]]:
|
| 56 |
+
benchmark_path = path or BENCHMARK_PROMPTS_PATH
|
| 57 |
+
with benchmark_path.open(encoding="utf-8") as handle:
|
| 58 |
+
return json.load(handle)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def canonical_intent(skill: str, parameters: dict | None = None) -> str:
|
| 62 |
+
params = parameters or {}
|
| 63 |
+
normalized = {key: normalize_param(str(value)) for key, value in sorted(params.items())}
|
| 64 |
+
payload = {"skill": skill, "parameters": normalized}
|
| 65 |
+
return json.dumps(payload, separators=(",", ":"), sort_keys=True)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def exact_intent_match(
|
| 69 |
+
predicted: dict | None,
|
| 70 |
+
expected_skill: str,
|
| 71 |
+
expected_parameters: dict | None = None,
|
| 72 |
+
) -> bool:
|
| 73 |
+
if not predicted:
|
| 74 |
+
return False
|
| 75 |
+
|
| 76 |
+
expected_params = expected_parameters or {}
|
| 77 |
+
predicted_skill = normalize_skill(predicted.get("skill")) or predicted.get("skill")
|
| 78 |
+
if predicted_skill != expected_skill:
|
| 79 |
+
return False
|
| 80 |
+
|
| 81 |
+
predicted_params = predicted.get("parameters") or {}
|
| 82 |
+
if set(predicted_params.keys()) != set(expected_params.keys()):
|
| 83 |
+
return False
|
| 84 |
+
|
| 85 |
+
return parameters_match(predicted_params, expected_params)
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def evaluate_prediction(
|
| 89 |
+
predicted: dict | None,
|
| 90 |
+
expected: dict[str, Any],
|
| 91 |
+
) -> dict[str, bool]:
|
| 92 |
+
expected_skill = expected["skill"]
|
| 93 |
+
expected_params = expected.get("parameters", {})
|
| 94 |
+
|
| 95 |
+
skill_correct = predicted is not None and (
|
| 96 |
+
normalize_skill(predicted.get("skill")) or predicted.get("skill")
|
| 97 |
+
) == expected_skill
|
| 98 |
+
parameter_correct = skill_correct and parameters_match(
|
| 99 |
+
predicted.get("parameters", {}),
|
| 100 |
+
expected_params,
|
| 101 |
+
)
|
| 102 |
+
exact_json = exact_intent_match(predicted, expected_skill, expected_params)
|
| 103 |
+
|
| 104 |
+
return {
|
| 105 |
+
"skill_correct": skill_correct,
|
| 106 |
+
"parameter_correct": parameter_correct,
|
| 107 |
+
"exact_json_match": exact_json,
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def build_confusion_matrix(
|
| 112 |
+
results: list[dict[str, Any]],
|
| 113 |
+
skills: tuple[str, ...] | None = None,
|
| 114 |
+
) -> dict[str, dict[str, int]]:
|
| 115 |
+
skill_list = list(skills or BENCHMARK_SKILLS)
|
| 116 |
+
matrix: dict[str, dict[str, int]] = {
|
| 117 |
+
expected: {predicted: 0 for predicted in skill_list + ["<invalid>"]}
|
| 118 |
+
for expected in skill_list
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
for result in results:
|
| 122 |
+
expected_skill = result["expected"]["skill"]
|
| 123 |
+
predicted = result.get("predicted")
|
| 124 |
+
if predicted and predicted.get("skill"):
|
| 125 |
+
predicted_skill = normalize_skill(predicted.get("skill")) or predicted.get("skill")
|
| 126 |
+
if predicted_skill not in matrix[expected_skill]:
|
| 127 |
+
predicted_skill = "<invalid>"
|
| 128 |
+
else:
|
| 129 |
+
predicted_skill = "<invalid>"
|
| 130 |
+
|
| 131 |
+
matrix[expected_skill][predicted_skill] += 1
|
| 132 |
+
|
| 133 |
+
return matrix
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def summarize_metrics(results: list[dict[str, Any]]) -> dict[str, Any]:
|
| 137 |
+
total = len(results)
|
| 138 |
+
if total == 0:
|
| 139 |
+
return {
|
| 140 |
+
"total": 0,
|
| 141 |
+
"skill_accuracy": 0.0,
|
| 142 |
+
"parameter_accuracy": 0.0,
|
| 143 |
+
"exact_json_match_rate": 0.0,
|
| 144 |
+
"skill_correct": 0,
|
| 145 |
+
"parameter_correct": 0,
|
| 146 |
+
"exact_json_match": 0,
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
skill_correct = sum(1 for result in results if result["skill_correct"])
|
| 150 |
+
parameter_correct = sum(1 for result in results if result["parameter_correct"])
|
| 151 |
+
exact_json_match = sum(1 for result in results if result["exact_json_match"])
|
| 152 |
+
|
| 153 |
+
return {
|
| 154 |
+
"total": total,
|
| 155 |
+
"skill_correct": skill_correct,
|
| 156 |
+
"parameter_correct": parameter_correct,
|
| 157 |
+
"exact_json_match": exact_json_match,
|
| 158 |
+
"skill_accuracy": skill_correct / total,
|
| 159 |
+
"parameter_accuracy": parameter_correct / total,
|
| 160 |
+
"exact_json_match_rate": exact_json_match / total,
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
def build_failure_report(results: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
| 165 |
+
failures: list[dict[str, Any]] = []
|
| 166 |
+
|
| 167 |
+
for result in results:
|
| 168 |
+
if result["exact_json_match"]:
|
| 169 |
+
continue
|
| 170 |
+
|
| 171 |
+
failure_types: list[str] = []
|
| 172 |
+
if not result["skill_correct"]:
|
| 173 |
+
failure_types.append("skill")
|
| 174 |
+
elif not result["parameter_correct"]:
|
| 175 |
+
failure_types.append("parameters")
|
| 176 |
+
else:
|
| 177 |
+
failure_types.append("json_format")
|
| 178 |
+
|
| 179 |
+
failures.append(
|
| 180 |
+
{
|
| 181 |
+
"id": result.get("id"),
|
| 182 |
+
"prompt": result["prompt"],
|
| 183 |
+
"domain": result.get("domain"),
|
| 184 |
+
"styles": result.get("styles", []),
|
| 185 |
+
"expected": result["expected"],
|
| 186 |
+
"predicted": result.get("predicted"),
|
| 187 |
+
"raw_output": result.get("raw_output"),
|
| 188 |
+
"failure_types": failure_types,
|
| 189 |
+
}
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
return failures
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def format_confusion_matrix(matrix: dict[str, dict[str, int]]) -> str:
|
| 196 |
+
predicted_labels = list(next(iter(matrix.values())).keys())
|
| 197 |
+
corner_label = "expected \\ predicted"
|
| 198 |
+
header = f"{corner_label:<28}" + "".join(f"{label:>18}" for label in predicted_labels)
|
| 199 |
+
lines = [header, "-" * len(header)]
|
| 200 |
+
|
| 201 |
+
for expected, counts in matrix.items():
|
| 202 |
+
row = f"{expected:<28}" + "".join(f"{counts[label]:>18}" for label in predicted_labels)
|
| 203 |
+
lines.append(row)
|
| 204 |
+
|
| 205 |
+
return "\n".join(lines)
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
def format_benchmark_report(
|
| 209 |
+
metrics: dict[str, Any],
|
| 210 |
+
matrix: dict[str, dict[str, int]],
|
| 211 |
+
failures: list[dict[str, Any]],
|
| 212 |
+
*,
|
| 213 |
+
title: str = "Pocket Automator Benchmark Report",
|
| 214 |
+
) -> str:
|
| 215 |
+
lines = [
|
| 216 |
+
title,
|
| 217 |
+
"=" * len(title),
|
| 218 |
+
"",
|
| 219 |
+
"Metrics",
|
| 220 |
+
"-------",
|
| 221 |
+
f"Total prompts: {metrics['total']}",
|
| 222 |
+
f"Skill accuracy: {metrics['skill_correct']}/{metrics['total']} ({metrics['skill_accuracy']:.1%})",
|
| 223 |
+
f"Parameter accuracy: {metrics['parameter_correct']}/{metrics['total']} ({metrics['parameter_accuracy']:.1%})",
|
| 224 |
+
f"Exact JSON match: {metrics['exact_json_match']}/{metrics['total']} ({metrics['exact_json_match_rate']:.1%})",
|
| 225 |
+
"",
|
| 226 |
+
"Confusion Matrix (rows=expected, cols=predicted)",
|
| 227 |
+
"------------------------------------------------",
|
| 228 |
+
format_confusion_matrix(matrix),
|
| 229 |
+
"",
|
| 230 |
+
f"Failure Report ({len(failures)} failures)",
|
| 231 |
+
"--------------",
|
| 232 |
+
]
|
| 233 |
+
|
| 234 |
+
if not failures:
|
| 235 |
+
lines.append("No failures — perfect score.")
|
| 236 |
+
else:
|
| 237 |
+
for index, failure in enumerate(failures, start=1):
|
| 238 |
+
lines.extend(
|
| 239 |
+
[
|
| 240 |
+
"",
|
| 241 |
+
f"[{index}] {failure.get('id', 'n/a')} — {', '.join(failure['failure_types'])}",
|
| 242 |
+
f"Prompt: {failure['prompt']}",
|
| 243 |
+
f"Domain: {failure.get('domain', 'n/a')}",
|
| 244 |
+
f"Styles: {', '.join(failure.get('styles') or [])}",
|
| 245 |
+
f"Expected: {json.dumps(failure['expected'], separators=(',', ':'))}",
|
| 246 |
+
f"Predicted: {json.dumps(failure.get('predicted'), separators=(',', ':')) if failure.get('predicted') else failure.get('raw_output', 'null')}",
|
| 247 |
+
]
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
style_failures: dict[str, int] = defaultdict(int)
|
| 251 |
+
domain_failures: dict[str, int] = defaultdict(int)
|
| 252 |
+
for failure in failures:
|
| 253 |
+
for style in failure.get("styles") or []:
|
| 254 |
+
style_failures[style] += 1
|
| 255 |
+
domain = failure.get("domain")
|
| 256 |
+
if domain:
|
| 257 |
+
domain_failures[domain] += 1
|
| 258 |
+
|
| 259 |
+
if style_failures or domain_failures:
|
| 260 |
+
lines.extend(["", "Failure breakdown", "-----------------"])
|
| 261 |
+
if style_failures:
|
| 262 |
+
lines.append("By style:")
|
| 263 |
+
for style, count in sorted(style_failures.items()):
|
| 264 |
+
lines.append(f" {style}: {count}")
|
| 265 |
+
if domain_failures:
|
| 266 |
+
lines.append("By domain:")
|
| 267 |
+
for domain, count in sorted(domain_failures.items()):
|
| 268 |
+
lines.append(f" {domain}: {count}")
|
| 269 |
+
|
| 270 |
+
return "\n".join(lines) + "\n"
|
| 271 |
+
|
| 272 |
+
|
| 273 |
+
def record_result(
|
| 274 |
+
case: dict[str, Any],
|
| 275 |
+
raw_output: str,
|
| 276 |
+
predicted: dict | None,
|
| 277 |
+
) -> dict[str, Any]:
|
| 278 |
+
expected = case["expected"]
|
| 279 |
+
checks = evaluate_prediction(predicted, expected)
|
| 280 |
+
return {
|
| 281 |
+
"id": case.get("id"),
|
| 282 |
+
"prompt": case["prompt"],
|
| 283 |
+
"domain": case.get("domain"),
|
| 284 |
+
"styles": case.get("styles", []),
|
| 285 |
+
"expected": expected,
|
| 286 |
+
"predicted": predicted,
|
| 287 |
+
"raw_output": raw_output,
|
| 288 |
+
**checks,
|
| 289 |
+
}
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
def save_benchmark_outputs(
|
| 293 |
+
results: list[dict[str, Any]],
|
| 294 |
+
*,
|
| 295 |
+
results_path: Path | None = None,
|
| 296 |
+
report_path: Path | None = None,
|
| 297 |
+
) -> tuple[dict[str, Any], str]:
|
| 298 |
+
metrics = summarize_metrics(results)
|
| 299 |
+
matrix = build_confusion_matrix(results)
|
| 300 |
+
failures = build_failure_report(results)
|
| 301 |
+
report = format_benchmark_report(metrics, matrix, failures)
|
| 302 |
+
|
| 303 |
+
resolved_results_path = results_path or BENCHMARK_RESULTS_PATH
|
| 304 |
+
resolved_report_path = report_path or BENCHMARK_REPORT_PATH
|
| 305 |
+
|
| 306 |
+
payload = {
|
| 307 |
+
"metrics": metrics,
|
| 308 |
+
"confusion_matrix": matrix,
|
| 309 |
+
"failures": failures,
|
| 310 |
+
"results": results,
|
| 311 |
+
}
|
| 312 |
+
|
| 313 |
+
resolved_results_path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
|
| 314 |
+
resolved_report_path.write_text(report, encoding="utf-8")
|
| 315 |
+
|
| 316 |
+
return metrics, report
|
src/skill_utils.py
CHANGED
|
@@ -41,6 +41,7 @@ SKILL_ALIASES: dict[str, str] = {
|
|
| 41 |
"slack_channel": "slack_open_channel",
|
| 42 |
"search_contacts": "contacts_search",
|
| 43 |
"contact_search": "contacts_search",
|
|
|
|
| 44 |
}
|
| 45 |
|
| 46 |
|
|
|
|
| 41 |
"slack_channel": "slack_open_channel",
|
| 42 |
"search_contacts": "contacts_search",
|
| 43 |
"contact_search": "contacts_search",
|
| 44 |
+
"spotify_create_playlist": "spotify_play_playlist",
|
| 45 |
}
|
| 46 |
|
| 47 |
|