"""V3 experimental intent inference wrapper. No networking or automatic model downloads.""" import argparse,hashlib,json,time from pathlib import Path import torch from safetensors.torch import load_file from transformers import AutoConfig,AutoModel,AutoTokenizer from laya_common import DecisionModel,build_sequence,collate_items,render_options,serialize_state,QTYPES def file_hash(path): h=hashlib.sha256() with path.open('rb') as f: for chunk in iter(lambda:f.read(8*1024*1024),b''):h.update(chunk) return h.hexdigest() class Agent: def __init__(self,root=None,device='cpu'): self.root=Path(root) if root else Path(__file__).resolve().parent self.manifest=json.loads((self.root/'manifest.json').read_text()) if self.manifest['status']!='verified':raise ValueError('Model artifact download and verification not complete') for entry in self.manifest['files']: if file_hash(self.root/entry['path'])!=entry['sha256']:raise ValueError('Artifact checksum mismatch: '+entry['path']) if device not in ['cpu','mps','cuda']:raise ValueError('Unsupported device') if device=='mps' and not torch.backends.mps.is_available():raise RuntimeError('MPS unavailable') if device=='cuda' and not torch.cuda.is_available():raise RuntimeError('CUDA unavailable') self.device=torch.device(device);self.tok=AutoTokenizer.from_pretrained(self.root/'tokenizer',local_files_only=True) cfg=AutoConfig.from_pretrained(self.root/'encoder',local_files_only=True);cfg.reference_compile=False self.model=DecisionModel(AutoModel.from_config(cfg,attn_implementation='sdpa'),2,2) self.model.load_state_dict(load_file(str(self.root/'model.safetensors')),strict=True) self.model.to(self.device).eval() def sync(self): if self.device.type=='mps':torch.mps.synchronize() elif self.device.type=='cuda':torch.cuda.synchronize() def predict(self,state,questions): if not questions:raise ValueError('At least one question required') if len(questions)>16:raise ValueError('At most 16 questions per call') self.sync();start=time.perf_counter();items=[];details=[] for qid,q in questions.items(): kind=q.get('type');ins=q.get('instructions') if kind not in ['choice','noul','score'] or not isinstance(ins,str) or not ins.strip():raise ValueError('Invalid question type/instructions') crit=q.get('criteria') if kind!='noul' and (not isinstance(crit,(dict,list)) or len(crit)<2):raise ValueError('At least two choice/score criteria required') query={'t':kind,'ins':ins,'crit':crit} opts=render_options(query) ids,markers=build_sequence(self.tok,state,query,1024,512) empty,_=build_sequence(self.tok,'',query,1024,512) if len(markers)!=len(opts):raise ValueError('Question exceeds candidate budget') if ids.index(self.tok.sep_token_id)-1!=len(self.tok(f'{kind} question: {ins}',add_special_tokens=False)['input_ids']):raise ValueError('Instruction exceeds token budget') boundary=next(i for i in range(markers[-1]+1,len(ids)) if ids[i]==self.tok.sep_token_id) tokens=[tuple(ids[m+1:(markers[i+1] if i+1