TinyQuery-140M / tinyquery /teacher_templates.py
karmx's picture
Release TinyQuery 139.7M from scratch with frozen weights, reproducible Mac evaluations and runtime source
b296ad4 verified
Raw History Blame Contribute Delete
4.13 kB
"""Generate new multilingual slot-preserving templates, with complete provenance."""
import argparse
import concurrent.futures
import json
import re
import time
import urllib.request
from pathlib import Path
from tinyquery.recipes import RECIPES, LANGUAGES
def slots(s):
return set(re.findall(r'\{([a-z_]+)\}', s))
def generate(op, seed, base, count):
source = RECIPES[op]
required = slots(source)
prompt = (
'Create diverse natural user requests that mean EXACTLY the same thing as the source. '
'Return a JSON object with keys en, noisy_en, hi, hinglish; each value is a list of '
f'{count} different strings. en: natural English, noisy_en: imperfect English that '
'still preserves every constraint, hi: natural Devanagari Hindi, hinglish: Romanized Hindi. '
'Preserve ALL literal {placeholder} slots exactly, including braces. Do not add slots. '
'Do not replace slots with example values. Do not add or remove conditions, columns, '
'sorting, limits, negation, or inclusivity. Vary sentence structure and tone. '
'Do not provide SQL or explanations. Source: ' + source
)
payload = {'model': 'Qwen/Qwen3.8-27B-FP8', 'messages': [{'role':'user','content':prompt}],
'temperature':0.8,'max_tokens':4096,'seed':seed,
'chat_template_kwargs':{'enable_thinking':False},
'response_format':{'type':'json_object'}}
request = urllib.request.Request(base+'/chat/completions', data=json.dumps(payload).encode(),
headers={'Content-Type':'application/json'})
started = time.time()
with urllib.request.urlopen(request, timeout=240) as response:
result = json.load(response)
raw = result['choices'][0]['message']['content']
parsed = json.loads(raw)
accepted = {}; rejected = []
for lang in LANGUAGES:
accepted[lang] = []
for text in parsed.get(lang, []):
if not isinstance(text,str) or slots(text) != required or len(text)>900:
rejected.append({'language':lang,'text':text,'reason':'slot_or_length'})
continue
if lang == 'hi' and not re.search('[\u0900-\u097f]', text):
rejected.append({'language':lang,'text':text,'reason':'missing_devanagari'})
continue
if text not in accepted[lang]: accepted[lang].append(text)
return {'op':op,'source':source,'templates':accepted,'rejected':rejected,
'teacher':payload['model'],'revision':'017b9c7af6b5689d5dd426a76e0bc077eb5ca20a',
'request':payload,'raw_response':raw,'usage':result.get('usage'),
'elapsed_seconds':time.time()-started,'finish_reason':result['choices'][0]['finish_reason']}
def main():
parser=argparse.ArgumentParser()
parser.add_argument('--out',default='data/tinyquery/templates.jsonl')
parser.add_argument('--base',default='http://127.0.0.1:18000/v1')
parser.add_argument('--count',type=int,default=12)
parser.add_argument('--workers',type=int,default=16)
args=parser.parse_args()
path=Path(args.out); path.parent.mkdir(parents=True,exist_ok=True)
done=set()
if path.exists():
done={json.loads(line)['op'] for line in path.read_text().splitlines()}
started=time.time()
with path.open('a') as output, concurrent.futures.ThreadPoolExecutor(args.workers) as pool:
jobs={pool.submit(generate,op,900+i,args.base,args.count):op
for i,op in enumerate(RECIPES) if op not in done}
for future in concurrent.futures.as_completed(jobs):
op=jobs[future]
try:
result=future.result()
output.write(json.dumps(result,ensure_ascii=False)+'\n'); output.flush()
print(json.dumps({'op':op,'accepted':{k:len(v) for k,v in result['templates'].items()},
'seconds':round(time.time()-started,1)}),flush=True)
except Exception as exc:
print(json.dumps({'op':op,'error':str(exc)}),flush=True)
if __name__ == '__main__': main()