Spaces:
Sleeping
Sleeping
File size: 5,722 Bytes
c2b7494 | 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 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | """Adapters for typed decision providers using the shared Jev question schema.
Keep provider confidence separate from option probabilities. A fallback to the
maximum option probability is recorded explicitly; it cannot be below 0.5 for
a normalized binary choice. No domain calibration is inferred from that value.
"""
from __future__ import annotations
from functools import lru_cache
import json
import os
from urllib.request import Request, urlopen
from typing import Any
from src.llm.client import LLMCallError, _get_openai_client, call_jev_decisions
DECISION_PROVIDERS = frozenset({'jev', 'openai_decisions', 'laya', 'jpt'})
DEFAULT_DECISION_MODELS = {
'jev': 'jev-latest', 'openai_decisions': 'gpt-6-luna',
'laya': 'multilingual', 'jpt': 'kirp/jpt-4b',
}
def normalize_answers(response, names):
"""Validate and normalize typed answers without inventing missing decisions."""
raw = response.get('answers')
if isinstance(raw, list):
keys = [answer.get('name') for answer in raw]
if len(set(keys)) != len(keys):
raise LLMCallError('Decision provider returned duplicate answer names.')
raw = {answer['name']: answer for answer in raw}
if not isinstance(raw, dict) or any(name not in raw for name in names):
raise LLMCallError('Decision provider omitted one or more requested answers.')
answers = {}
for name in names:
answer = dict(raw[name])
if answer.get('type') == 'refusal' or not isinstance(answer.get('choice'), str):
raise LLMCallError(f'Decision provider did not answer question {name}.')
probabilities = answer.get('probabilities', {})
if isinstance(probabilities, list):
probabilities = {item['value']: item['probability'] for item in probabilities}
probabilities = {key: float(value) for key, value in probabilities.items()}
if not probabilities or any(not 0 <= p <= 1 for p in probabilities.values()):
raise LLMCallError(f'Invalid choice probabilities for question {name}.')
if abs(sum(probabilities.values())-1) > .02 or answer['choice'] not in probabilities:
raise LLMCallError(f'Invalid choice distribution for question {name}.')
if isinstance(names, dict) and set(probabilities) != set(names[name]['criteria']):
raise LLMCallError(f'Unexpected choices for question {name}.')
confidence = answer.get('confidence')
answer['confidence_source'] = 'provider' if confidence is not None else 'max_probability'
confidence = float(confidence) if confidence is not None else max(probabilities.values())
if not 0 <= confidence <= 1:
raise LLMCallError(f'Invalid confidence for question {name}.')
answers[name] = {**answer, 'confidence': confidence, 'probabilities': probabilities}
return {**response, 'answers': answers}
def call_openai_decisions(state, questions, model='gpt-6-luna', max_retries=3):
"""POST /v1/decisions, including SDKs predating the typed resource."""
client = _get_openai_client().with_options(max_retries=max_retries-1)
payload = {'model': model, 'input': state, 'questions': [
{'name': name, 'type': 'choice', 'instructions': question['instructions'],
'choices': [{'value': label, 'description': description}
for label, description in question['criteria'].items()]}
for name, question in questions.items()
]}
response = client.post('/decisions', cast_to=dict[str, Any], body=payload)
return normalize_answers(response, questions)
@lru_cache(maxsize=1)
def _laya_router():
try:
from laya import Router
except ImportError as exc:
raise LLMCallError('Install requirements-decision-models.txt to use Laya.') from exc
from src.llm.model_paths import prepared_model_path
path=prepared_model_path('convaiinnovations/laya')
return Router(models={'multilingual':(path,'multilingual')})
def call_decisions(state, questions, provider='jev', model=None, max_retries=3):
"""Dispatch an existing typed choice request to the selected backend."""
if provider not in DECISION_PROVIDERS:
raise ValueError(f'Unsupported decision provider: {provider}')
model = model or DEFAULT_DECISION_MODELS[provider]
if provider == 'jev':
return call_jev_decisions(state, questions, model=model, max_retries=max_retries)
if provider == 'openai_decisions':
return call_openai_decisions(state, questions, model=model, max_retries=max_retries)
if provider == 'laya':
# Explicit multilingual checkpoint avoids the English router's shorter
# input limit. The model/card documents 8192-token inference support.
result = _laya_router().predict(state, questions, model=model, max_len=8192)
return normalize_answers(result, questions)
if not os.environ.get('JPT_API_URL'):
from src.llm.jpt_runtime import predict_jpt
return normalize_answers(predict_jpt(state, questions, model), questions)
url = os.environ.get('JPT_API_URL', 'http://127.0.0.1:8080/v1/systemone')
payload = json.dumps({'state': state, 'questions': questions}).encode()
headers = {'Content-Type': 'application/json'}
token = os.environ.get('JPT_API_KEY')
if token:
headers['Authorization'] = f'Bearer {token}'
request = Request(url, data=payload, headers=headers, method='POST')
try:
with urlopen(request, timeout=300) as response:
result = json.load(response)
except Exception as exc:
raise LLMCallError('JPT decision endpoint failed. Start llm2jev before running.') from exc
return normalize_answers(result, questions)
|