File size: 4,755 Bytes
b296ad4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
"""Directly paraphrase concrete training scenarios, keeping the original reference action."""
import argparse
import concurrent.futures
import json
import random
import re
import time
import urllib.request
from pathlib import Path
from tinyquery.data import compact,serialize
from tinyquery.recipes import LANGUAGES


def paraphrase(row,base,seed):
    task={'schema':row['context']['schema'],'backend':row['backend'],'reference_request':row['question'],
          'reference_action':row['target']}
    prompt=('Write one natural user request in each of four styles that has exactly the reference meaning. '
            'Use conversational phrasing, not instructions about writing SQL. Keep every numeric bound, '
            'literal value, selected field, sorting and limit unchanged. Do not add a new condition. '
            'You may use ordinary English/Hindi words for obvious schema fields, but preserve database '
            'literal values exactly. Output JSON keys en, noisy_en, hi, hinglish with string values. '
            'hi must be Devanagari Hindi, hinglish Romanized Hindi, noisy_en imperfect English. Task: '+compact(task))
    payload={'model':'Qwen/Qwen3.8-27B-FP8','messages':[{'role':'user','content':prompt}],
             'max_tokens':500,'temperature':.7,'seed':seed,'response_format':{'type':'json_object'},
             'chat_template_kwargs':{'enable_thinking':False}}
    request=urllib.request.Request(base+'/chat/completions',data=compact(payload).encode(),headers={'Content-Type':'application/json'})
    with urllib.request.urlopen(request,timeout=180) as response: result=json.load(response)
    parsed=json.loads(result['choices'][0]['message']['content'])
    accepted=[]
    for lang in LANGUAGES:
        question=parsed.get(lang)
        if not isinstance(question,str) or not 5<len(question)<900 or '{' in question: continue
        if lang=='hi' and not re.search('[\u0900-\u097f]',question): continue
        if re.search('[\u0600-\u06ff]',question): continue
        # Reject changed numeric bounds and quoted database literals in constrained SQL recipes.
        source_numbers=set(re.findall(r'(?<![a-zA-Z_])\d+(?:\.\d+)?',row['question']))
        output_numbers=set(re.findall(r'(?<![a-zA-Z_])\d+(?:\.\d+)?',question))
        if source_numbers!=output_numbers: continue
        required={'eq':['value'],'project_eq':['value'],'and':['value'],'or':['value','other'],
                  'count_eq':['value'],'contains':['value'],'join_filter':['value']}.get(row['operation'],[])
        if any(str(row['slots'][k]).casefold() not in question.casefold() for k in required): continue
        new=dict(row); new['id']=row['scenario_id']+'_concrete_'+lang; new['language']=lang
        new['question']=question; new['prompt']=serialize(new['context'],question)
        new['provenance']='Qwen3.8-27B-FP8 direct concrete paraphrase; numeric/literal checks; semantic audit separate'
        accepted.append(new)
    return {'scenario_id':row['scenario_id'],'request':payload,'raw_response':result,'accepted':accepted}


def main():
    p=argparse.ArgumentParser(); p.add_argument('--data',required=True); p.add_argument('--out',required=True)
    p.add_argument('--count',type=int,default=2500); p.add_argument('--workers',type=int,default=32)
    p.add_argument('--base',default='http://127.0.0.1:18000/v1'); args=p.parse_args()
    from tinyquery.data import SQL_OPS
    seen=set(); candidates=[]
    with open(args.data) as stream:
        for line in stream:
            r=json.loads(line)
            if r['scenario_id'] not in seen and r['operation'] in SQL_OPS:
                seen.add(r['scenario_id']); candidates.append(r)
    random.Random(887).shuffle(candidates); candidates=candidates[:args.count]
    out=Path(args.out); out.parent.mkdir(parents=True,exist_ok=True)
    done=set()
    if out.exists():
        done={json.loads(line)['scenario_id'] for line in out.read_text().splitlines()}
    start=time.time(); total=0; completed=0
    with out.open('a') as stream, concurrent.futures.ThreadPoolExecutor(args.workers) as pool:
        jobs={pool.submit(paraphrase,r,args.base,2100+i):r['scenario_id'] for i,r in enumerate(candidates) if r['scenario_id'] not in done}
        for future in concurrent.futures.as_completed(jobs):
            completed+=1
            try:
                result=future.result(); stream.write(compact(result)+'\n'); stream.flush()
                total+=len(result['accepted'])
            except Exception as exc:
                print(compact({'error':str(exc),'scenario_id':jobs[future]}),flush=True)
            if completed%25==0: print(compact({'completed':completed,'accepted':total,'seconds':time.time()-start}),flush=True)


if __name__=='__main__': main()