Adapt prompt probe schema to Gemini subset with full local validation

This commit is contained in:
Nico committed 2026-10-17 12:55:34 +02:00
1 parent 7c168182c1
commit 2eaba70083
4 files changed
+99 -18

No files matched your search

@@ -58,7 +58,7 @@ def measure(provider,case,limit,post=None):
result.update(answer=text,finish_reason=candidate.get('finishReason'),model=envelope.get('modelVersion',provider.model))
result['status']='SUCCESS' if candidate.get('finishReason')=='STOP' and text else 'INCOMPLETE'
if 'responseJsonSchema' in case['payload']['generationConfig'] and result['status']=='SUCCESS':
try: valid=Draft202012Validator(case['payload']['generationConfig']['responseJsonSchema']).is_valid(json.loads(text))
try: valid=Draft202012Validator(case.get('validation_schema',case['payload']['generationConfig']['responseJsonSchema'])).is_valid(json.loads(text))
except ValueError: valid=False
result['schema_valid']=valid
if not valid: result['status']='INVALID_JSON'
+36 -3
View File
@@ -12,6 +12,37 @@ from .telemetry.safety import safe_code
DEFAULT = Path('service-data/benchmarks/compact-investigation-20261017/request.json')
def gemini_schema(schema):
"""Provider projection only; full original schema remains the local validator.
Gemini's responseJsonSchema subset omits const and several validation
keywords. In particular, a const-only kind has no usable type after const
is ignored. Translate it rather than handing the internal contract through.
"""
allowed = {'type','format','title','description','enum','items','prefixItems',
'minItems','maxItems','minimum','maximum','anyOf','oneOf',
'properties','additionalProperties','required'}
if not isinstance(schema,dict):
raise LLMError('PROMPT_PROBE_SCHEMA_UNSUPPORTED')
if any(k in schema for k in ('$ref','$defs','allOf','not','if','then','else')):
raise LLMError('PROMPT_PROBE_SCHEMA_UNSUPPORTED')
result = {}
for key,value in schema.items():
if key not in allowed: continue
if key == 'properties': result[key] = {k:gemini_schema(v) for k,v in value.items()}
elif key in {'items','additionalProperties'} and isinstance(value,dict): result[key] = gemini_schema(value)
elif key in {'anyOf','oneOf','prefixItems'}: result['anyOf' if key=='oneOf' else key] = [gemini_schema(v) for v in value]
else: result[key] = value
if 'const' in schema:
value=schema['const']
if not isinstance(value,str): raise LLMError('PROMPT_PROBE_SCHEMA_UNSUPPORTED')
result['type']='string';result['enum']=[value]
if isinstance(result.get('type'),list):
types=result.pop('type')
result['anyOf']=[{'type':value} for value in types]
return result
def prepare(provider,request):
if (set(request)!={'prompt_version','messages','output_schema'} or
not isinstance(request['prompt_version'],str) or not request['prompt_version'].strip()):
@@ -22,12 +53,12 @@ def prepare(provider,request):
raise LLMError('PROMPT_PROBE_REQUEST')
Draft202012Validator.check_schema(request['output_schema'])
generation={'maxOutputTokens':provider.max_output_tokens,'responseMimeType':'application/json',
'responseJsonSchema':request['output_schema']}
'responseJsonSchema':gemini_schema(request['output_schema'])}
if provider.reasoning_effort is not None:generation['thinkingConfig']={'thinkingLevel':provider.reasoning_effort}
payload={'systemInstruction':{'parts':[{'text':messages[0]['content']}]},
'contents':[{'role':'user','parts':[{'text':messages[1]['content']}]}],'generationConfig':generation}
return {'id':request['prompt_version'],'prompt_characters':sum(len(m['content']) for m in messages),
'payload':payload}
'payload':payload,'validation_schema':request['output_schema']}
def main(argv=None):
@@ -49,7 +80,9 @@ def main(argv=None):
if len(json.dumps(case['payload'],ensure_ascii=False).encode('utf-8'))>config.max_request_bytes:raise LLMError('LLM_REQUEST_LIMIT')
metadata={'request_sha256':hashlib.sha256(raw).hexdigest(),'provider':provider.id,'model':provider.model,
'reasoning_effort':provider.reasoning_effort,'max_output_tokens':provider.max_output_tokens,
'timeout_seconds':provider.timeout_seconds,'explicit_cache':False,'research_queries_executed':0}
'timeout_seconds':provider.timeout_seconds,'explicit_cache':False,'research_queries_executed':0,
'wire_schema_sha256':hashlib.sha256(json.dumps(case['payload']['generationConfig']['responseJsonSchema'],sort_keys=True,separators=(',',':')).encode()).hexdigest(),
'schema_transport':'gemini_subset_v1_local_full_validation'}
if not args.run:
print(json.dumps({'status':'PREPARED','network_used':False,**metadata,
'message_characters':case['prompt_characters']}));return 0