2026-06-30 02:59:34 -04:00
#!/usr/bin/env python3
2026-06-30 03:39:37 -04:00
""" Rationalize labeled conversations into CoT training rows (STaR / backward distill).
2026-06-30 02:59:34 -04:00
2026-06-30 03:39:37 -04:00
Given ( conversation context - > Quinn ' s ACTUAL next reply), infer the MOVE she ran
and a one - sentence reasoning trace anchored to her real reply ( not a forward
guess ) . This is the high - quality way to manufacture the LoRA training set for
move - classification : ( context - > trace - > move ) , labeled by what she actually did .
2026-06-30 02:59:34 -04:00
2026-06-30 03:39:37 -04:00
Input : a JSON list with ` gold_reply ` and either ` context ` ( sweep ) or ` client_msg `
( mined cluster ) . Default < DATA_DIR > / sweep_labels . json - > the full work - era corpus .
Output : < DATA_DIR > / traincot_ < input - stem > . json .
2026-06-30 02:59:34 -04:00
2026-06-30 03:39:37 -04:00
Env : OSS_URL , DATA_DIR . Arg : input filename ( default sweep_labels . json ) .
2026-06-30 02:59:34 -04:00
"""
2026-06-30 03:39:37 -04:00
import json , os , sys , urllib . request
from collections import Counter
2026-06-30 02:59:34 -04:00
from concurrent . futures import ThreadPoolExecutor , as_completed
OSS_URL = os . environ . get ( " OSS_URL " , " http://localhost:8800/v1/chat/completions " )
DATA = os . environ . get ( " DATA_DIR " , os . path . join ( os . path . dirname ( __file__ ) , " .data " ) )
2026-06-30 04:16:55 -04:00
WORKERS = int ( os . environ . get ( " WORKERS " , " 64 " ) )
2026-06-30 03:39:37 -04:00
INPUT = sys . argv [ 1 ] if len ( sys . argv ) > 1 else " sweep_labels.json "
stem = os . path . splitext ( os . path . basename ( INPUT ) ) [ 0 ]
items = json . load ( open ( os . path . join ( DATA , INPUT ) ) )
2026-06-30 02:59:34 -04:00
2026-06-30 09:30:14 -04:00
MOVES = [ " opener " , " pursue " , " subhour " , " address " , " out_of_area " , " of " , " disengage " , " escalate " ,
" existing_client " , " personal " , " vendor " , " spam " ]
2026-06-30 03:39:37 -04:00
SYSTEM = f """ You build training data by analyzing how Quinn (a touring companion, $1000/hr, incall williamsburg NYC, text only, OnlyFans @transquinnftw) handled a conversation. You are GIVEN her actual next reply, so infer her REAL reasoning -- do not invent a different reply.
2026-06-30 02:59:34 -04:00
2026-06-30 09:30:14 -04:00
FIRST : was this even a COLD PROSPECT ( new person evaluating / booking her ) , or NOT a prospect ? Much traffic is not .
NOT - A - PROSPECT ( classify as these , NOT a prospect move ) :
- existing_client : already her client - - mid - booking logistics , " see you soon " , on - the - way , past - meeting references , ongoing relationship / sexting with someone she ' s met.
- personal : a friend / family / non - work conversation .
- vendor : someone selling HER a service .
- spam : bot / automated / marketing / scam / wrong number .
PROSPECT moves ( one of : { ' , ' . join ( m for m in MOVES if m not in ( ' existing_client ' , ' personal ' , ' vendor ' , ' spam ' ) ) } ) :
2026-06-30 03:39:37 -04:00
- opener : answered a new hello with her intro .
2026-06-30 09:30:14 -04:00
- pursue : engaged a paying prospect / gave rate / answered a preference question / moved toward booking ( even if crude or low , if she pursued ) .
2026-06-30 03:39:37 -04:00
- subhour : gave the < 1 hr / half - hour rate stance .
- address : withheld her address when asked before a locked time .
2026-06-30 09:30:14 -04:00
- out_of_area : told him she ' s not in his city / offered outcall.
- of : redirected to OnlyFans ( harvester / free - content / out - of - budget ) .
- disengage : brushed off a lowballer / hostile / someone offering his body .
- escalate : a collab / photographer / business / opportunity she ' d personally decide.
2026-06-30 02:59:34 -04:00
2026-06-30 09:30:14 -04:00
Then a ONE - sentence trace : prospect or not , the subject , his pay - intent , why her move fits .
2026-06-30 02:59:34 -04:00
2026-06-30 09:30:14 -04:00
Output ONLY JSON : { { " move " : " <one of the classes> " , " trace " : " <one sentence> " } } """
2026-06-30 02:59:34 -04:00
SCHEMA = { " type " : " object " ,
2026-06-30 03:39:37 -04:00
" properties " : { " move " : { " type " : " string " , " enum " : MOVES } , " trace " : { " type " : " string " } } ,
2026-06-30 02:59:34 -04:00
" required " : [ " move " , " trace " ] , " additionalProperties " : False }
2026-06-30 03:39:37 -04:00
def rationalize ( it ) :
ctx = it . get ( " context " ) or ( " CLIENT: " + it . get ( " client_msg " , " " ) )
gold = it . get ( " gold_reply " ) or it . get ( " quinn_reply_gold " , " " )
user = f " { ctx } \n QUINN (actual reply): { gold } "
2026-06-30 02:59:34 -04:00
body = json . dumps ( { " model " : " quinn-oss " ,
" messages " : [ { " role " : " system " , " content " : SYSTEM } , { " role " : " user " , " content " : user } ] ,
2026-06-30 03:39:37 -04:00
" temperature " : 0.2 , " max_tokens " : 250 ,
2026-06-30 02:59:34 -04:00
" response_format " : { " type " : " json_schema " , " json_schema " : { " name " : " r " , " schema " : SCHEMA , " strict " : True } } } ) . encode ( )
req = urllib . request . Request ( OSS_URL , data = body , headers = { " Content-Type " : " application/json " } )
d = json . loads ( json . load ( urllib . request . urlopen ( req , timeout = 120 ) ) [ " choices " ] [ 0 ] [ " message " ] [ " content " ] )
2026-06-30 03:39:37 -04:00
return { " context " : ctx , " gold_reply " : gold , " move " : d [ " move " ] , " trace " : d [ " trace " ] }
2026-06-30 02:59:34 -04:00
rows = [ ]
2026-06-30 04:16:55 -04:00
with ThreadPoolExecutor ( max_workers = WORKERS ) as ex :
2026-06-30 03:39:37 -04:00
futs = [ ex . submit ( rationalize , it ) for it in items if ( it . get ( " gold_reply " ) or it . get ( " quinn_reply_gold " ) ) ]
2026-06-30 02:59:34 -04:00
for f in as_completed ( futs ) :
2026-06-30 03:39:37 -04:00
try : rows . append ( f . result ( ) )
except Exception as e : print ( " ERR " , e , flush = True )
2026-06-30 02:59:34 -04:00
2026-06-30 03:39:37 -04:00
out = os . path . join ( DATA , f " traincot_ { stem } .json " )
2026-06-30 02:59:34 -04:00
json . dump ( rows , open ( out , " w " ) , ensure_ascii = False )
print ( f " rationalized { len ( rows ) } -> { out } " )
2026-06-30 03:39:37 -04:00
print ( " move dist: " , dict ( Counter ( r [ " move " ] for r in rows ) ) )