oev-demo / oev /convert_commands.py
divyanshudhruv's picture
Upload folder using huggingface_hub
51da1ee verified
Raw History Blame Contribute Delete
6.23 kB
"""Synthetic command-intent dataset for the router specialist (seeded, no download).
python -m oev.convert_commands
"""
import json
import os
import random
INTENTS = ["music", "search", "note", "calculator", "app", "unknown"]
INSTRUCTIONS_INTENT = ("What does the user want the computer to do? "
"unknown covers partial commands, bare triggers and chat.")
APPS = ["spotify", "chrome", "notepad", "calculator", "vs code", "discord",
"steam", "explorer", "settings", "terminal"]
SONGS = ["blinding lights", "bohemian rhapsody", "take five", "gimme shelter",
"fly me to the moon", "dream on", "landslide", "redbone",
"electric feel", "ain't no mountain high enough"]
ARTISTS = ["the weeknd", "queen", "dave brubeck", "nina simone", "daft punk",
"fleetwood mac", "tame impala", "miles davis", "chaka khan"]
QUERIES = ["rust async programming", "latest nvidia drivers", "weather tomorrow",
"best noise cancelling headphones", "python deque vs list",
"how to fold a fitted sheet", "distance to the moon",
"cheap flights to lisbon", "markdown table syntax", "is pluto a planet"]
TASKS = ["finish the benchmark writeup", "call the dentist", "water the plants",
"back up the laptop", "renew the domain", "ship the release notes"]
ITEMS = ["oat milk", "printer paper", "a new mouse", "birthday candles", "coffee beans"]
DAYS = ["saturday", "monday", "friday", "tomorrow"]
PEOPLE = ["sam", "priya", "marco", "lena"]
CHAT = ["hello", "hey there", "good morning", "how are you", "thanks a lot",
"what time is it", "tell me a joke", "never mind", "actually stop"]
MUSIC_T = ["play {song} on spotify", "open spotify and play {song}",
"put on {artist}", "play some {artist} songs", "queue up {song}",
"play {song} by {artist}", "start {artist} radio"]
SEARCH_T = ["search the web for {query}", "search youtube for {query}",
"google {query}", "look up {query}", "find {query} on the web"]
NOTE_T = ["write in my notes that I need to {task}", "take a note: {task}",
"remind me to {task}", "note that I have to {task}"]
CALC_T = ["calculate {a} times {b}", "what is {a} plus {b}", "what is {a} minus {b}",
"calculate {a} divided by {b}", "{a} times {b}"]
APP_T = ["open {app}", "launch {app}", "start {app}", "switch to {app}",
"open up {app}"]
def _fill(templates, rng, **pools):
t = rng.choice(templates)
return t.format(**{k: rng.choice(v) for k, v in pools.items()})
def _full_command(rng):
kind = rng.choice(["music", "search", "note", "calculator", "app"])
if kind == "music":
return _fill(MUSIC_T, rng, song=SONGS, artist=ARTISTS), "music"
if kind == "search":
return _fill(SEARCH_T, rng, query=QUERIES), "search"
if kind == "note":
task = rng.choice(TASKS) + (" " + rng.choice(["today", "this week", "by friday"])
if rng.random() < 0.4 else "")
return rng.choice(NOTE_T).format(task=task), "note"
if kind == "calculator":
a, b = rng.randint(2, 999), rng.randint(2, 99)
return _fill(CALC_T, rng, a=[a], b=[b]), "calculator"
return _fill(APP_T, rng, app=APPS), "app"
def _partial(full, rng):
words = full.split()
cut = rng.randint(1, max(1, len(words) - 2))
return " ".join(words[:cut])
def _case(i, state, intent, complete, prefix):
return {
"id": f"{prefix}-{i:06d}",
"domain": "commands",
"state": state,
"questions": [
{"name": "intent", "type": "choice", "instructions": INSTRUCTIONS_INTENT,
"options": INTENTS, "answer": intent},
{"name": "complete", "type": "noul", "options": ["no", "yes"],
"instructions": "Is this a finished command, or is the user still speaking?",
"answer": "yes" if complete else "no"},
],
}
def generate(n, seed, prefix):
# balanced: equal intent quotas; the full/partial/trigger/chat mix inside
# each class teaches the finished-vs-still-speaking split. repeats are
# re-randomized rather than dropped so no class is starved by dedupe.
rng = random.Random(seed)
# one slot per case, cycling intent classes so every class gets n/len(INTENTS)
slots = [INTENTS[i % len(INTENTS)] for i in range(n)]
rng.shuffle(slots)
out = []
for state_slot in slots:
r = rng.random()
if state_slot == "unknown":
# unknown = partials, bare triggers, chat (never full commands)
if r < 0.55:
full, _ = _full_command(rng)
state = _partial(full, rng)
elif r < 0.8:
state = rng.choice(["open", "play", "search", "write", "calculate",
"can you", "hey", "um", "launch"])
else:
state = rng.choice(CHAT)
out.append(_case(len(out), state, "unknown", rng.random() < 0.3, prefix))
continue
# a real intent: 60% full command, 25% partial, 15% bare trigger of that intent
full, _ = _full_command(rng)
# force the template family to match the slot
for _ in range(50):
full, kind = _full_command(rng)
if kind == state_slot:
break
if r < 0.6:
out.append(_case(len(out), full, state_slot, True, prefix))
elif r < 0.85:
out.append(_case(len(out), _partial(full, rng), state_slot, False, prefix))
else:
trigger = full.split()[0]
out.append(_case(len(out), trigger, state_slot, False, prefix))
return out
def main(out_dir="data/commands"):
os.makedirs(out_dir, exist_ok=True)
sizes = {"train": 8000, "valid": 1000, "test": 1000}
seeds = {"train": 11, "valid": 22, "test": 33}
for split, n in sizes.items():
path = os.path.join(out_dir, f"{split}.jsonl")
with open(path, "w", encoding="utf-8") as f:
f.writelines(json.dumps(c) + "\n" for c in generate(n, seeds[split], split[:2]))
print(f"wrote {n} cases to {path} (intents: {', '.join(INTENTS)})")
if __name__ == "__main__":
main()