Files
VianoBugAnalysis/src/viano_bug_analysis/analysis/investigation_prompt.py
T

202 lines
14 KiB
Python

import json
from importlib.resources import files
from viano_bug_analysis.lifecycle.storage import json_bytes
PROMPT_VERSION="investigation_v1"
def output_schema(context=None, *, strict=False):
if context and context.get('lean_protocol')==2:
from .investigation_lean_v20 import schema
return schema(context,strict=strict)
if context and "lean_session" in context:
from .investigation_lean import schema
return schema(context,strict=strict)
name = "investigation-project-output-v1.schema.json" if context and "x-project-bindings" in context["tool_schema"] else "investigation-output-v1.schema.json"
schema=json.loads(files("viano_bug_analysis.schemas").joinpath(name).read_text())
if context and context['tool_schema'].get('x-project-bindings', {}).get('relation_profiles'):
from viano_bug_analysis.tools.database_relations import extend_tool_schema
schema['properties']['next'] = extend_tool_schema(schema['properties']['next'])
if context and context['tool_schema'].get('x-project-bindings', {}).get('source_analysis'):
from viano_bug_analysis.tools.source_context import extend_tool_schema
protocol=context['tool_schema']['x-project-bindings']['source_analysis']['version']
schema['properties']['next'] = extend_tool_schema(schema['properties']['next'],protocol)
if context and context.get('prompt_version') in {'investigation_project_v12','investigation_project_v13','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
schema['properties']['update']['properties']['causal_review'] = json.loads(
files('viano_bug_analysis.schemas').joinpath('investigation-causal-review-v1.schema.json').read_text())
schema['properties']['update']['required'].append('causal_review')
if context and context.get('prompt_version') == 'investigation_project_v13':
schema['properties']['update']['properties']['working_brief'] = json.loads(
files('viano_bug_analysis.schemas').joinpath('investigation-working-brief-v1.schema.json').read_text())
schema['properties']['update']['properties']['working_brief']['properties'].pop('next_question_details',None)
schema['properties']['update']['required'].append('working_brief')
schema['properties']['update']['properties']['note']['maxLength'] = 400
if context and context.get("prompt_version") in {"investigation_v2","investigation_project_v3","investigation_v3","investigation_project_v4","investigation_project_v5","investigation_project_v6","investigation_project_v7","investigation_project_v8","investigation_project_v9","investigation_project_v10","investigation_project_v11","investigation_project_v12","investigation_project_v13","investigation_project_v14","investigation_project_v15","investigation_project_v16","investigation_project_v17","investigation_project_v18"}:
schema["properties"]["next"]["oneOf"].extend(json.loads(files("viano_bug_analysis.schemas").joinpath("investigation-early-actions-v1.schema.json").read_text())["oneOf"])
if context and context.get('prompt_version') in {'investigation_project_v8','investigation_project_v9'}:
schema['properties']['next']['oneOf'].append({
'type':'object','additionalProperties':False,'required':['kind','evidence_refs'],
'properties':{'kind':{'const':'recall'},'evidence_refs':{
'type':'array','minItems':1,'maxItems':4,'uniqueItems':True,
'items':{'type':'string','pattern':'^E[0-9]{6,}$'}}}})
if context and context.get('prompt_version') in {'investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
schema['properties']['update'] = json.loads(files('viano_bug_analysis.schemas').joinpath('investigation-update-v14.schema.json').read_text())
for branch in schema['properties']['assessment']['anyOf']:
if branch.get('type') == 'object':
branch['properties'].pop('evidence_refs')
branch['required'].remove('evidence_refs')
if context and context.get('prompt_version') in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
branches=schema['properties']['next']['oneOf']
operations=[b['properties']['operation']['const'] for b in branches if b['properties']['kind'].get('const')=='tool']
if not strict:
branches=[b for b in branches if b['properties']['kind'].get('const')!='tool']
branches.append({'type':'object','additionalProperties':False,'required':['kind','operation','arguments'],
'properties':{'kind':{'const':'tool'},'operation':{'enum':operations},'arguments':{'type':'object'}}})
branches.append({'type':'object','additionalProperties':False,'required':['kind','evidence_refs'],
'properties':{'kind':{'const':'recall'},'evidence_refs':{'type':'array','minItems':1,'maxItems':4,
'uniqueItems':True,'items':{'type':'string','pattern':'^E[0-9]{6,}$'}}}})
schema['properties']['next']['oneOf']=branches
if not strict:
schema['properties']['next']={'type':'object','required':['kind'],
'properties':{'kind':{'enum':['tool','test','finish','reject','request_diagnosis','defer','recall']}}}
if not strict:
def compact(value):
if isinstance(value,list):return [compact(v) for v in value]
if not isinstance(value,dict):return value
return {k:compact(v) for k,v in value.items() if k not in {'$schema','pattern','minLength','maxLength','uniqueItems'}}
schema=compact(schema)
if context and context.get('prompt_version') in {'investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
from .investigation_compact import preparing_review
if preparing_review(context):
schema['properties']['next'] = {'type':'object','additionalProperties':False,
'required':['kind','reason'], 'properties':{
'kind':{'enum':['defer','reject']}, 'reason':{'type':'string','minLength':1,'maxLength':2000}}}
schema['properties']['update']['properties']['add_questions']['maxItems'] = 0
if context and context.get('prompt_version') == 'investigation_project_v18':
from .investigation_compact import preparing_review
if not preparing_review(context):
schema['properties']['update']['properties']['next_step'] = json.loads(
files('viano_bug_analysis.schemas').joinpath('investigation-next-step-v1.schema.json').read_text())
schema['properties']['update']['required'].append('next_step')
return schema
def build_prompt(context):
if context.get('response_repair'):
from viano_bug_analysis.llm.response_recovery import repair_request
return repair_request(context)
request = _build_prompt(context)
from .tool_guidance import instructions, HEADER
guide = instructions(context)
if guide and HEADER not in request['messages'][0]['content']:
request['messages'][0]['content'] += guide
from .tool_guidance import finding_repair_instructions
request['messages'][0]['content'] += finding_repair_instructions(context)
if context.get('policy',{}).get('response_recovery_version')==1:
from viano_bug_analysis.llm.response_recovery import MARKER
request['output_schema'][MARKER]=1
body=json.loads(request['messages'][1]['content'])
if 'output_schema' in body:body['output_schema']=request['output_schema']
request['messages'][1]['content']=json_bytes(body).decode('utf-8')
if context.get('format_notice'):
body=json.loads(request['messages'][1]['content'])
body['format_notice']={'normalized_previous_reply':context['format_notice'],
'instruction':'Use ordinary JSON objects next time; no extra research or reply needed.'}
request['messages'][1]['content']=json_bytes(body).decode('utf-8')
return request
def _build_prompt(context):
if context.get('lean_protocol')==2:
from .investigation_lean_v20 import build
return build(context)
if "lean_session" in context:
from .investigation_lean import build
return build(context)
schema=output_schema(context)
version = "investigation_project_v1" if "x-project-bindings" in context["tool_schema"] else PROMPT_VERSION
if context.get('prompt_version') in {'investigation_project_v2','investigation_project_v3','investigation_project_v4','investigation_project_v5','investigation_project_v6','investigation_project_v7','investigation_project_v8','investigation_project_v9','investigation_project_v10','investigation_project_v11','investigation_project_v12','investigation_project_v13','investigation_project_v14','investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
from .investigation_view import working_view
from .model_context import model_json
version = context['prompt_version']
if version in {'investigation_project_v15','investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
from .investigation_compact import model_view
visible = model_view(context)
elif version in {'investigation_project_v13','investigation_project_v14'}:
from .working_brief import model_view
visible = model_view(context)
elif version in {'investigation_project_v10','investigation_project_v11','investigation_project_v12'}:
from .investigation_focus import full_history_view
visible = full_history_view(context)
elif version in {'investigation_project_v8','investigation_project_v9'}:
from .investigation_focus import focused_view
visible = focused_view(context)
elif version == 'investigation_project_v7':
from .investigation_blocks import block_view
visible = block_view(context)
elif version == 'investigation_project_v6':
from .investigation_consolidation import consolidated_view
visible = consolidated_view(context)
elif version == 'investigation_project_v5':
from .investigation_view import thin_view
visible = thin_view(context)
else:
visible = working_view(context)
encode = model_json
else:
visible = compact_context(context) if version != PROMPT_VERSION else context
encode = lambda value: json_bytes(value).decode()
if context.get("prompt_version") in {"investigation_v2","investigation_v3"}:version=context["prompt_version"]
prompt_file = version
task = 'investigate_hypothesis'
if version in {'investigation_project_v16','investigation_project_v17','investigation_project_v18'}:
from .investigation_compact import preparing_review, review_view
if preparing_review(context):
visible = review_view(context)
prompt_file = 'investigation_review_v16'
task = 'assess_pending_result_for_early_review'
elif version == 'investigation_project_v17':
from .investigation_step import step_view
visible['investigation_step'] = step_view(context)
task = 'advance_selected_question'
elif version == 'investigation_project_v18':
from .investigation_plan import framed_view
visible = framed_view(context,visible)
task = 'advance_investigation_goal'
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})}]}
def compact_context(context):
"""A reproducible prompt view; the full immutable context stays in the case.
Retain every evidence ID and provenance. Only the selected hypothesis's
evidence carries payload; other hypotheses are available as bounded summaries.
The previous result is already in evidence and need not be sent twice.
"""
import copy
view=copy.deepcopy(context)
hid=context["selected_hypothesis"]
view["case"].pop("inputs",None)
quality=view["case"].get("data_quality",{})
quality.pop("unclaimed_files",None)
for block in ("source_context","database_context"):
refs=view["case"][block]["refs"]
view["case"][block]={"reference_count":len(refs), "refs":refs[:12], "references_truncated":len(refs)>12}
for other, hypothesis in view["hypotheses"].items():
if other != hid:
hypothesis["notes"] = hypothesis["notes"][-2:]
for evidence_id,entry in view["evidence"].items():
if entry["record"]["hypothesis_id"] != hid:
entry["content"]={"kind":entry["content"]["kind"],"scope":entry["content"]["scope"],
"coverage":entry["content"]["coverage"],"items_omitted":True}
previous=context["previous_result"]
if previous:
view["previous_result"]={"evidence_id":previous["record"]["id"],"see":"evidence"}
view["tool_schema"]={"project":context["tool_schema"]["x-project-bindings"],
"request_arguments":"Use the schema of next/tool in output_schema"}
return view