Download fev4b/decisions.py from murabcd/fev-4b: direct link, hf CLI and curl.
- Browser
- Download file 5.46 kB
-
https://huggingface.co/murabcd/fev-4b/resolve/main/fev4b/decisions.py
- Command line
-
hf download hf://murabcd/fev-4b/fev4b/decisions.py
-
curl -L -o decisions.py https://huggingface.co/murabcd/fev-4b/resolve/main/fev4b/decisions.py
5.46 kB
| """Text-only typed decisions. No model imports or customer defaults.""" | |
| import copy | |
| import math | |
| MODEL_ID = 'fev-4b' | |
| def _json(value, depth=0): | |
| if depth > 32: | |
| raise ValueError('JSON nesting exceeds 32 levels') | |
| if value is None or isinstance(value, (str, bool, int)): | |
| return | |
| if isinstance(value, float) and math.isfinite(value): | |
| return | |
| if isinstance(value, list): | |
| for item in value: | |
| _json(item, depth + 1) | |
| return | |
| if isinstance(value, dict) and all(isinstance(key, str) for key in value): | |
| for item in value.values(): | |
| _json(item, depth + 1) | |
| return | |
| raise ValueError('Content must be finite JSON') | |
| def _content(value, *, nullable=False): | |
| if value is None and nullable: | |
| return | |
| if not isinstance(value, (str, dict, list)): | |
| raise ValueError('Content must be a string, object or array') | |
| _json(value) | |
| def validate_request(request): | |
| """Validate without changing instructions, state, keys or option order.""" | |
| if not isinstance(request, dict) or set(request) != {'state', 'questions'}: | |
| raise ValueError('Expected exactly state and questions; images and assets are unsupported') | |
| _content(request['state']) | |
| questions = request['questions'] | |
| if not isinstance(questions, dict) or not 1 <= len(questions) <= 64: | |
| raise ValueError('Supply 1 to 64 questions') | |
| for key, question in questions.items(): | |
| if not isinstance(key, str) or not key: | |
| raise ValueError('Question IDs must be nonempty strings') | |
| if not isinstance(question, dict) or set(question) - {'type', 'instructions', 'criteria'}: | |
| raise ValueError('Unsupported question fields') | |
| kind = question.get('type') | |
| if kind not in ('choice', 'noul', 'score'): | |
| raise ValueError('Supported question types: choice, noul, score') | |
| _content(question.get('instructions'), nullable=True) | |
| criteria = question.get('criteria') | |
| if kind == 'choice': | |
| if not isinstance(criteria, dict) or not 1 <= len(criteria) <= 255: | |
| raise ValueError('Choice needs 1 to 255 options') | |
| if any(not isinstance(option, str) or not option for option in criteria): | |
| raise ValueError('Choice option keys must be nonempty strings') | |
| elif kind == 'noul': | |
| if criteria is not None and (not isinstance(criteria, dict) or set(criteria) - {'true', 'false'}): | |
| raise ValueError('Noul criteria may contain only true and false') | |
| elif not isinstance(criteria, list) or not 2 <= len(criteria) <= 255: | |
| raise ValueError('Score needs 2 to 255 ordered levels') | |
| descriptions = criteria.values() if isinstance(criteria, dict) else (criteria or []) | |
| for description in descriptions: | |
| _content(description, nullable=kind != 'score') | |
| return copy.deepcopy(request) | |
| def _number(value, lower, upper): | |
| return (type(value) in (int, float) and math.isfinite(value) and lower <= value <= upper) | |
| def validate_response(request, response): | |
| """Reject incomplete/non-finite native outputs rather than inventing answers.""" | |
| answers = response.get('answers') | |
| if not isinstance(answers, dict) or set(answers) != set(request['questions']): | |
| raise RuntimeError('Incomplete typed decision response') | |
| for key, question in request['questions'].items(): | |
| answer = answers[key] | |
| kind = question['type'] | |
| if answer.get('type') != kind: | |
| raise RuntimeError('Native answer type mismatch') | |
| if kind == 'noul': | |
| if not _number(answer.get('noul'), 0, 1): | |
| raise RuntimeError('Invalid Noul probability') | |
| continue | |
| keys = list(question['criteria']) if kind == 'choice' else [str(i) for i in range(len(question['criteria']))] | |
| probabilities = answer.get('probabilities') | |
| if (not isinstance(probabilities, dict) or set(probabilities) != set(keys) | |
| or any(not _number(p, 0, 1) for p in probabilities.values()) | |
| or not math.isclose(sum(probabilities.values()), 1, abs_tol=1e-5) | |
| or not _number(answer.get('confidence'), 0, 1)): | |
| raise RuntimeError('Invalid typed probability distribution') | |
| if kind == 'choice': | |
| if answer.get('choice') not in keys or probabilities[answer['choice']] != max(probabilities.values()): | |
| raise RuntimeError('Choice does not match distribution') | |
| else: | |
| expected_score = sum(int(k) * p for k, p in probabilities.items()) | |
| if (not _number(answer.get('score'), 0, len(keys) - 1) | |
| or not math.isclose(answer['score'], expected_score, abs_tol=1e-5) | |
| or answer.get('legend') != dict(zip(keys, question['criteria']))): | |
| raise RuntimeError('Score does not match distribution or legend') | |
| usage = response.get('usage', {}) | |
| if (type(usage.get('input_tokens')) is not int or usage['input_tokens'] < 1 | |
| or usage.get('output_tokens') != 0 or usage.get('sequences') != 1): | |
| raise RuntimeError('Typed decisions must use one sequence and generate zero tokens') | |
| return response | |
| def decide_with_predict(request, *, predict): | |
| request = validate_request(request) | |
| response = validate_response(request, predict(request)) | |
| return {'model': MODEL_ID, **response} | |