Constrain nested bootstrap findings with versioned native output shapes
This commit is contained in:
1 parent
67843454e4
commit
f5a87dfca3
14 files changed
+173
-12
No files matched your search
@@ -24,6 +24,7 @@ class InvestigationConfig:
|
||||
lean_checkpoint_steps: int = 4
|
||||
lean_protocol: int = 1
|
||||
bootstrap_json_framing: bool = False
|
||||
bootstrap_framing_version: int = 2
|
||||
|
||||
def __post_init__(self):
|
||||
if any(type(value) is not int or value <= 0 for name,value in asdict(self).items() if name not in {'lean_loop','lean_checkpoint_steps','bootstrap_json_framing'}):
|
||||
@@ -33,6 +34,8 @@ class InvestigationConfig:
|
||||
raise InvestigationError('INVESTIGATION_CONFIG')
|
||||
if type(self.lean_loop) is not bool or type(self.bootstrap_json_framing) is not bool:
|
||||
raise InvestigationError('INVESTIGATION_CONFIG')
|
||||
if self.bootstrap_framing_version not in {1,2}:
|
||||
raise InvestigationError('INVESTIGATION_CONFIG')
|
||||
|
||||
@classmethod
|
||||
def load(cls, path):
|
||||
|
||||
@@ -150,7 +150,8 @@ def _build_prompt(context):
|
||||
from .investigation_plan import framed_view
|
||||
visible = framed_view(context,visible)
|
||||
task = 'advance_investigation_goal'
|
||||
return {"prompt_version":"investigation_bootstrap_v21" if context.get("bootstrap_json_framing") else version,"privacy":"configured","output_schema":schema,
|
||||
bootstrap_version='investigation_bootstrap_v22' if context.get('bootstrap_framing_version')==2 else 'investigation_bootstrap_v21'
|
||||
return {"prompt_version":bootstrap_version if context.get("bootstrap_json_framing") else version,"privacy":"configured","output_schema":schema,
|
||||
"messages":[{"role":"system","content":files("viano_bug_analysis.llm.prompts").joinpath(prompt_file+".txt").read_text(encoding="utf-8")},
|
||||
{"role":"user","content":encode({"task":task,"input":visible,"output_schema":schema})}]}
|
||||
|
||||
|
||||
@@ -291,7 +291,10 @@ class Investigator:
|
||||
if state['policy'].get('lean_protocol',1)==1 or state['hypotheses'][hid]['iterations']>0:
|
||||
context['lean_session']=copy.deepcopy(state['hypotheses'][hid].get('lean_session',{'turns':[],'checkpoint':False}))
|
||||
if state['policy'].get('lean_protocol',1)==2:context['lean_protocol']=2
|
||||
elif state['policy'].get('bootstrap_json_framing'):context['bootstrap_json_framing']=True
|
||||
elif state['policy'].get('bootstrap_json_framing'):
|
||||
context['bootstrap_json_framing']=True
|
||||
if state['policy'].get('bootstrap_framing_version',1)==2:
|
||||
context['bootstrap_framing_version']=2
|
||||
if len(json_bytes(context))>self.limits.max_context_bytes:raise InvestigationError("INVESTIGATION_CONTEXT_LIMIT")
|
||||
validate(context,"investigation-context")
|
||||
return context
|
||||
@@ -318,7 +321,10 @@ class Investigator:
|
||||
if state is None:
|
||||
with PackageReader(self._latest(case_id,package_id),self.store.limits) as reader:state=self._begin(reader)
|
||||
state,_=self._save(state)
|
||||
if self.config_explicit and {**asdict(InvestigationConfig()),**state["policy"]}!=asdict(self.config):raise InvestigationError("INVESTIGATION_POLICY_CHANGED")
|
||||
effective_policy={**asdict(InvestigationConfig()),**state['policy']}
|
||||
if state['policy'].get('bootstrap_json_framing') and 'bootstrap_framing_version' not in state['policy']:
|
||||
effective_policy['bootstrap_framing_version']=1
|
||||
if self.config_explicit and effective_policy!=asdict(self.config):raise InvestigationError("INVESTIGATION_POLICY_CHANGED")
|
||||
if state["active"] and state["active"]["phase"]=="executing":return self._execute_plan(state,budget)
|
||||
if state["active"] is None:
|
||||
state.setdefault('early_stop_protocol',1)
|
||||
|
||||
@@ -28,7 +28,7 @@ def measure_package(path):
|
||||
contexts[hashlib.sha256(json_bytes(original['messages'])).hexdigest()] = context
|
||||
elif name.startswith('llm/operations/') and name.endswith('/request.json'):
|
||||
req = json.loads(reader.read_bytes(name))
|
||||
if req.get('prompt_version','').startswith('investigation_project_'):
|
||||
if req.get('prompt_version','').startswith(('investigation_project_','investigation_bootstrap_')):
|
||||
requests.append(req)
|
||||
samples, unmatched = [], 0
|
||||
for req in requests:
|
||||
|
||||
@@ -155,7 +155,7 @@ class LLMGateway:
|
||||
LOGGER.warning("LLM endpoint unavailable provider=%s code=%s", provider.id, code)
|
||||
except ValidationError as invalid:
|
||||
info={'validation_keyword':invalid.validator}
|
||||
if request['prompt_version'] in {'investigation_bootstrap_v21','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}:
|
||||
if request['prompt_version'] in {'investigation_bootstrap_v22','investigation_bootstrap_v21','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}:
|
||||
from .diagnostics import schema_feedback
|
||||
info.update(schema_feedback(invalid,include_keyword=True))
|
||||
error = LLMError("LLM_OUTPUT_SCHEMA",diagnostics=info)
|
||||
|
||||
@@ -21,6 +21,23 @@ from .provider_base import ProviderResponse
|
||||
from .diagnostics import count, sanitize, parse_response_object
|
||||
|
||||
|
||||
def bootstrap_shape(schema):
|
||||
"""Nested syntax contract only; full controller validation remains authoritative."""
|
||||
def shape(rule):
|
||||
result={k:rule[k] for k in ('type','required','additionalProperties') if k in rule}
|
||||
if 'properties' in rule:result['properties']={k:shape(v) for k,v in rule['properties'].items()}
|
||||
if 'items' in rule:result['items']=shape(rule['items'])
|
||||
if 'anyOf' in rule:result['anyOf']=[shape(v) for v in rule['anyOf']]
|
||||
if 'const' in rule:
|
||||
from .diagnostics import json_type
|
||||
result['type']=json_type(rule['const'])
|
||||
return result
|
||||
return {'type':'object','additionalProperties':False,'required':['assessment','update','next'],
|
||||
'properties':{'assessment':shape(schema['properties']['assessment']),
|
||||
'update':shape(schema['properties']['update']),
|
||||
'next':{'type':'object','additionalProperties':True}}}
|
||||
|
||||
|
||||
class GeminiProvider:
|
||||
def __init__(self):
|
||||
self._cache = {}
|
||||
@@ -132,6 +149,8 @@ class GeminiProvider:
|
||||
'update':{'type':'object','additionalProperties':True},
|
||||
'next':{'type':'object','additionalProperties':True}},
|
||||
'required':['assessment','update','next'],'additionalProperties':False}
|
||||
if request['prompt_version']=='investigation_bootstrap_v22' and config.response_format=='json_object':
|
||||
generation['responseJsonSchema']=bootstrap_shape(request['output_schema'])
|
||||
full = {'contents': prefix + contents, 'generationConfig': generation}
|
||||
if system['parts']:
|
||||
full['systemInstruction'] = system
|
||||
@@ -142,7 +161,7 @@ class GeminiProvider:
|
||||
if config.use_context_cache:
|
||||
status = 'below_minimum'
|
||||
if (len(json_bytes([system, prefix]).decode('utf-8')) >= config.cache_min_prefix_chars
|
||||
or request['prompt_version']=='investigation_bootstrap_v21' or request['prompt_version'] in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}):
|
||||
or request['prompt_version'] in {'investigation_bootstrap_v21','investigation_bootstrap_v22'} or request['prompt_version'] in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18','investigation_project_v19','investigation_project_v20'}):
|
||||
with self._lock:
|
||||
now = time.monotonic()
|
||||
self._cache = {k: v for k, v in self._cache.items() if v[1] > now}
|
||||
|
||||
@@ -10,7 +10,7 @@ from viano_bug_analysis.lifecycle.storage import json_bytes
|
||||
|
||||
VERSION = 'stable_case_v1'
|
||||
SUPPORTED_PROMPTS = {f'investigation_project_v{n}' for n in (4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)}
|
||||
SUPPORTED_PROMPTS.add('investigation_bootstrap_v21')
|
||||
SUPPORTED_PROMPTS.update({'investigation_bootstrap_v21','investigation_bootstrap_v22'})
|
||||
STABLE_FIELDS = frozenset({'format', 'format_version', 'prompt_version', 'case',
|
||||
'sources', 'interpretation', 'budget_policy', 'policy', 'tool_schema',
|
||||
'unread_sources', 'unread_inventory_scope'})
|
||||
|
||||
@@ -110,6 +110,13 @@
|
||||
},
|
||||
"bootstrap_json_framing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bootstrap_framing_version": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -802,6 +809,9 @@
|
||||
},
|
||||
"tool_guidance_version": {
|
||||
"const": 1
|
||||
},
|
||||
"bootstrap_framing_version": {
|
||||
"const": 2
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +106,13 @@
|
||||
},
|
||||
"bootstrap_json_framing": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"bootstrap_framing_version": {
|
||||
"type": "integer",
|
||||
"enum": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user