"""Pure-Python request contract and probability conversion.""" import json, math def text(value): return value if isinstance(value,str) else json.dumps(value,ensure_ascii=False,sort_keys=True,separators=(',',':')) def normalize_question(q): if not isinstance(q,dict): raise ValueError('Each question must be an object.') kind=q.get('type'); instruction=q.get('instructions') if kind not in ('choice','score','noul'): raise ValueError('type must be choice, score, or noul') if instruction is None or not text(instruction).strip(): raise ValueError('instructions must be nonempty') instruction=text(instruction) criteria=q.get('criteria') if kind=='noul': if criteria is not None: if not isinstance(criteria,dict) or set(criteria)!={'true','false'}: raise ValueError('Noul criteria must contain true and false.') instruction+='\nTrue means: '+text(criteria['true'])+'\nFalse means: '+text(criteria['false']) return kind,instruction,[''],['true'] if kind=='choice': if not isinstance(criteria,dict) or not 2<=len(criteria)<=255: raise ValueError('Choice needs 2–255 named criteria.') if any(not isinstance(k,str) or not k.strip() for k in criteria): raise ValueError('Choice keys must be nonempty strings.') keys=list(criteria) candidates=[k if v is None else text(v) for k,v in criteria.items()] else: if not isinstance(criteria,list) or not 2<=len(criteria)<=10: raise ValueError('Score needs 2–10 ordered criteria.') candidates=[text(c) for c in criteria];keys=[str(i) for i in range(len(criteria))] if kind=='score' and len(set(candidates))!=len(candidates): raise ValueError('Score criteria descriptions must be distinct.') return kind,instruction,candidates,keys def hypothesis(kind,instruction,candidate): return instruction if kind=='noul' else instruction+'\nCandidate answer: '+candidate def softmax(values): m=max(values);ex=[math.exp(x-m) for x in values];s=sum(ex) return [v/s for v in ex] def probabilities(logits,kind,temperature=1.,bias=0.): if not math.isfinite(temperature) or temperature<=0: raise ValueError('temperature must be positive and finite') if not logits or any(not math.isfinite(x) for x in logits): raise ValueError('Nonfinite or empty logits') if kind=='noul': x=logits[0]/temperature+bias p=1/(1+math.exp(-x)) if x>=0 else math.exp(x)/(1+math.exp(x)) return [1-p,p] return softmax([x/temperature for x in logits]) def answer(kind,keys,candidates,probs): if kind=='noul':return {'type':'noul','noul':probs[1],'stats':{}} entropy=-sum(p*math.log(p) for p in probs if p>0) confidence=max(0.,min(1.,(len(probs)*max(probs)-1)/(len(probs)-1) if kind=='choice' else 1-entropy/math.log(len(probs)))) result={'type':kind,'probabilities':dict(zip(keys,probs)),'confidence':round(confidence,2),'stats':{}} if kind=='choice':result['choice']=keys[max(range(len(probs)),key=probs.__getitem__)] else: result['score']=round(sum(i*p for i,p in enumerate(probs)),2) result['legend']=dict(zip(keys,candidates)) return result def record_hypothesis(record,index): candidate=record["candidates"][index] if record["kind"]=="choice": key=record["keys"][index] if key!=candidate:candidate=key+": "+candidate return hypothesis(record["kind"],record["instructions"],candidate)