agent-apprenticeship 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -197
- package/bin/agent-apprenticeship.js +85 -37
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/agent_apprenticeship_trace/__init__.py +1 -1
- package/src/agent_apprenticeship_trace/actual_outputs_normalizer.py +104 -28
- package/src/agent_apprenticeship_trace/apprentice_adapters.py +3 -1
- package/src/agent_apprenticeship_trace/artifact_resolver.py +6 -0
- package/src/agent_apprenticeship_trace/bundle_exporter.py +132 -2
- package/src/agent_apprenticeship_trace/cli.py +1229 -269
- package/src/agent_apprenticeship_trace/codex_runner.py +89 -6
- package/src/agent_apprenticeship_trace/config.py +251 -32
- package/src/agent_apprenticeship_trace/ecosystem_r2.py +515 -0
- package/src/agent_apprenticeship_trace/env.py +5 -5
- package/src/agent_apprenticeship_trace/evaluator.py +127 -11
- package/src/agent_apprenticeship_trace/grader.py +12 -11
- package/src/agent_apprenticeship_trace/learning.py +270 -1
- package/src/agent_apprenticeship_trace/llm_output_normalizer.py +8 -7
- package/src/agent_apprenticeship_trace/loop.py +103 -4
- package/src/agent_apprenticeship_trace/loop_review.py +686 -0
- package/src/agent_apprenticeship_trace/mentor_checkpoints.py +49 -21
- package/src/agent_apprenticeship_trace/openai_structured.py +84 -14
- package/src/agent_apprenticeship_trace/package_exporter.py +1 -1
- package/src/agent_apprenticeship_trace/progress.py +2 -1
- package/src/agent_apprenticeship_trace/public_run.py +369 -57
- package/src/agent_apprenticeship_trace/public_sanitizer.py +40 -3
- package/src/agent_apprenticeship_trace/rubric_generation.py +11 -10
- package/src/agent_apprenticeship_trace/schemas.py +378 -3
- package/src/agent_apprenticeship_trace/task_intake.py +43 -9
- package/src/agent_apprenticeship_trace/tdo.py +2304 -0
- package/src/agent_apprenticeship_trace/trace_normalizer.py +351 -17
- package/src/agent_apprenticeship_trace/trace_prompt.py +38 -11
- package/src/agent_apprenticeship_trace/training_signals.py +1 -1
- package/src/agent_apprenticeship_trace/verifier.py +12 -6
|
@@ -24,33 +24,147 @@ class LLMEvaluatorOutput(BaseModel):
|
|
|
24
24
|
revision_plan: RevisionPlan
|
|
25
25
|
|
|
26
26
|
def deterministic_feedback(grader: GraderResult) -> EvaluatorFeedback:
|
|
27
|
-
|
|
27
|
+
failed = grader.failed_criteria if grader.score_source == 'structural_guardrail' else grader.failed_criteria
|
|
28
|
+
summary = 'Mentor evaluator unavailable; no outcome judgment was produced. Structural guardrails suggest preserving valid artifacts and repairing missing deliverables.'
|
|
29
|
+
return EvaluatorFeedback(feedback_id=f'feedback_{grader.attempt_id}', task_id=grader.task_id, attempt_id=grader.attempt_id, target_actor='apprentice', feedback_type='artifact_missing' if failed else 'other', failed_rubric_items=failed, evidence_refs=grader.evidence_refs, artifact_refs=grader.evidence_refs, feedback_summary=summary, actionable_feedback=['Preserve any valid generated artifacts.', 'Repair missing deliverables reported by structural guardrails.'] if failed else ['Preserve valid artifacts for any future revision.'], suggested_revision='Repair missing deliverables if the user or Mentor asks for a revision.', revision_priority='medium' if failed else 'low', confidence=0.35, hidden_reference_used=False, hidden_reference_leaked=False, failed_or_weak_rubric_items=failed, artifact_specific_comments=[], trace_specific_comments=[], revision_plan='Repair missing deliverables only if a revision is requested.', model=None, provider=None, metadata_json={'mentor_evaluator_status':'unavailable','structural_guidance':True})
|
|
28
30
|
|
|
29
31
|
def _revision_plan_from_feedback(fb: EvaluatorFeedback, target_attempt_id: str) -> RevisionPlan:
|
|
30
32
|
return RevisionPlan(revision_plan_id=f'revision_plan_{fb.task_id}', task_id=fb.task_id, source_attempt_id=fb.attempt_id, target_attempt_id=target_attempt_id, revision_kind='local_fix', revision_reason=fb.feedback_summary, failed_rubric_items=fb.failed_rubric_items, planned_changes=fb.actionable_feedback, expected_score_improvement=0.1, risk_of_regression='low', uses_evaluator_feedback=True, metadata_json={})
|
|
31
33
|
|
|
32
|
-
def
|
|
34
|
+
def _latest_review_packet(package_root: Path | None, attempt_kind: str) -> dict:
|
|
35
|
+
if not package_root:
|
|
36
|
+
return {}
|
|
37
|
+
packets = sorted((package_root / "loops" / "iterations").glob("*/review_packet.json"))
|
|
38
|
+
if not packets:
|
|
39
|
+
return {}
|
|
40
|
+
preferred = []
|
|
41
|
+
for path in packets:
|
|
42
|
+
try:
|
|
43
|
+
packet = read_json(path)
|
|
44
|
+
except Exception:
|
|
45
|
+
continue
|
|
46
|
+
actual_ref = str(packet.get("actual_outputs_ref") or "")
|
|
47
|
+
if f"attempts/{attempt_kind}/" in actual_ref:
|
|
48
|
+
preferred.append((path, packet))
|
|
49
|
+
if preferred:
|
|
50
|
+
path, packet = preferred[-1]
|
|
51
|
+
packet = dict(packet)
|
|
52
|
+
packet["review_packet_ref"] = str(path.relative_to(package_root))
|
|
53
|
+
return packet
|
|
54
|
+
try:
|
|
55
|
+
packet = read_json(packets[-1])
|
|
56
|
+
except Exception:
|
|
57
|
+
return {}
|
|
58
|
+
packet = dict(packet)
|
|
59
|
+
packet["review_packet_ref"] = str(packets[-1].relative_to(package_root))
|
|
60
|
+
return packet
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _compact_trace_summary(trace: AgentTrace | None) -> dict:
|
|
64
|
+
if trace is None:
|
|
65
|
+
return {}
|
|
66
|
+
data = trace.model_dump()
|
|
67
|
+
steps = data.get("steps") or []
|
|
68
|
+
return {
|
|
69
|
+
"trace_id": data.get("trace_id"),
|
|
70
|
+
"attempt_id": data.get("attempt_id"),
|
|
71
|
+
"attempt_kind": data.get("attempt_kind"),
|
|
72
|
+
"step_count": len(steps),
|
|
73
|
+
"key_steps": [
|
|
74
|
+
{
|
|
75
|
+
"step": step.get("step"),
|
|
76
|
+
"operation": step.get("operation"),
|
|
77
|
+
"interaction_surface": step.get("interaction_surface"),
|
|
78
|
+
"environment_domain": step.get("environment_domain"),
|
|
79
|
+
"step_outcome": step.get("step_outcome"),
|
|
80
|
+
"artifact_refs": (step.get("artifact_refs") or [])[:5],
|
|
81
|
+
"state_change": str(step.get("state_change") or "")[:240],
|
|
82
|
+
}
|
|
83
|
+
for step in steps[:10]
|
|
84
|
+
if isinstance(step, dict) and step.get("action") != "user_message"
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _compact_grader(grader: GraderResult) -> dict:
|
|
90
|
+
data = grader.model_dump()
|
|
91
|
+
return {
|
|
92
|
+
"grader_result_id": data.get("grader_result_id"),
|
|
93
|
+
"attempt_id": data.get("attempt_id"),
|
|
94
|
+
"passed": data.get("passed"),
|
|
95
|
+
"score": data.get("score"),
|
|
96
|
+
"final_score": data.get("final_score"),
|
|
97
|
+
"score_source": data.get("score_source"),
|
|
98
|
+
"score_reliability": data.get("score_reliability"),
|
|
99
|
+
"failed_criteria": data.get("failed_criteria") or [],
|
|
100
|
+
"evidence_refs": (data.get("evidence_refs") or [])[:20],
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _compact_verifier(verifier: VerifierResult) -> dict:
|
|
105
|
+
data = verifier.model_dump()
|
|
106
|
+
return {
|
|
107
|
+
"verifier_result_id": data.get("verifier_result_id"),
|
|
108
|
+
"attempt_id": data.get("attempt_id"),
|
|
109
|
+
"passed": data.get("passed"),
|
|
110
|
+
"verdict": data.get("verdict"),
|
|
111
|
+
"failed_checks": data.get("failed_checks") or data.get("failed_criteria") or [],
|
|
112
|
+
"evidence_refs": (data.get("evidence_refs") or [])[:20],
|
|
113
|
+
"notes": str(data.get("notes") or data.get("verifier_notes") or "")[:1000],
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _compact_actual(outputs: ActualOutputs) -> dict:
|
|
118
|
+
data = outputs.model_dump()
|
|
119
|
+
return {
|
|
120
|
+
"task_id": data.get("task_id"),
|
|
121
|
+
"attempt_id": data.get("attempt_id"),
|
|
122
|
+
"attempt_kind": data.get("attempt_kind"),
|
|
123
|
+
"status": data.get("status"),
|
|
124
|
+
"output_summary": data.get("output_summary"),
|
|
125
|
+
"primary_output_ref": data.get("primary_output_ref"),
|
|
126
|
+
"deliverable_refs": data.get("deliverable_refs") or [],
|
|
127
|
+
"artifact_refs": data.get("artifact_refs") or [],
|
|
128
|
+
"files_created": data.get("files_created") or [],
|
|
129
|
+
"error_type": data.get("error_type"),
|
|
130
|
+
"error_message": data.get("error_message"),
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _compact_artifact_previews(bundle: dict | None) -> dict:
|
|
135
|
+
bundle = bundle or {}
|
|
136
|
+
return {
|
|
137
|
+
"artifact_content_refs": (bundle.get("artifact_content_refs") or [])[:12],
|
|
138
|
+
"artifact_content_previews": (bundle.get("artifact_content_previews") or [])[:8],
|
|
139
|
+
"artifact_content_hashes": (bundle.get("artifact_content_hashes") or {}) if len(str(bundle.get("artifact_content_hashes") or {})) < 2000 else {},
|
|
140
|
+
"artifact_content_preview_truncated": bundle.get("artifact_content_preview_truncated"),
|
|
141
|
+
"model_grading_basis": bundle.get("model_grading_basis"),
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _evaluator_prompt(grader: GraderResult, verifier: VerifierResult, outputs: ActualOutputs, trace: AgentTrace | None, target_attempt_id: str, artifact_preview_bundle: dict | None=None, review_packet: dict | None=None) -> str:
|
|
33
146
|
return """Return only valid JSON. Do not include markdown. Do not add extra top-level fields; place extras under metadata_json.extra_model_fields.
|
|
34
|
-
Required skeleton: {"evaluator_feedback":{"feedback_id":"feedback_<task_id>_<attempt_kind>","task_id":"...","attempt_id":"...","target_actor":"
|
|
35
|
-
Evaluate the attempt
|
|
36
|
-
Target revised attempt id: """ + target_attempt_id + "\
|
|
147
|
+
Required skeleton: {"evaluator_feedback":{"feedback_id":"feedback_<task_id>_<attempt_kind>","task_id":"...","attempt_id":"...","target_actor":"apprentice","feedback_type":"other","failed_rubric_items":[],"evidence_refs":[],"artifact_refs":[],"feedback_summary":"...","actionable_feedback":[],"suggested_revision":"...","revision_priority":"medium","confidence":0.7,"hidden_reference_used":false,"hidden_reference_leaked":false,"failed_or_weak_rubric_items":[],"artifact_specific_comments":[],"trace_specific_comments":[],"metadata_json":{}},"revision_plan":{"revision_plan_id":"revision_plan_<task_id>","task_id":"...","source_attempt_id":"...","target_attempt_id":"...","revision_kind":"local_fix","revision_reason":"...","failed_rubric_items":[],"planned_changes":[],"risk_of_regression":"medium","uses_evaluator_feedback":true,"metadata_json":{}}}.
|
|
148
|
+
Evaluate the attempt by first reading the loop review packet when provided, then checking compact Mentor grader/verifier outputs, actual_outputs.json, artifact refs, artifact content previews, and trace evidence. Treat the review packet as the current iteration dossier: it summarizes outputs, artifacts, trace refs, rubric/verifier status, known failures, diff evidence, and recommended review focus. Produce grounded actionable feedback and a concrete revision plan whose revision guidance can be mapped to feedback_type/feedback_content in future traces. Keep the response concise. Do not rely on Apprentice Agent self-assessment or self evaluation as outcome evidence. Do not use deterministic fallback judgement; structural guardrails are integrity checks only and are not outcome judgments.
|
|
149
|
+
Target revised attempt id: """ + target_attempt_id + "\nLoop Review Packet JSON:\n" + json.dumps(review_packet or {}, sort_keys=True)[:5000] + "\nCompact GraderResult JSON:\n" + json.dumps(_compact_grader(grader), sort_keys=True) + "\nCompact VerifierResult JSON:\n" + json.dumps(_compact_verifier(verifier), sort_keys=True) + "\nCompact ActualOutputs JSON:\n" + json.dumps(_compact_actual(outputs), sort_keys=True) + "\nArtifact content previews JSON:\n" + json.dumps(_compact_artifact_previews(artifact_preview_bundle), sort_keys=True)[:5000] + "\nCompact Trace summary JSON:\n" + json.dumps(_compact_trace_summary(trace), sort_keys=True)[:3000]
|
|
37
150
|
|
|
38
151
|
def evaluate_attempt(grader: GraderResult, verifier: VerifierResult, outputs: ActualOutputs, trace: AgentTrace | None, target_attempt_id: str, role_root: Path | None=None, package_root: Path | None=None) -> tuple[EvaluatorFeedback, RevisionPlan]:
|
|
39
152
|
settings=get_settings(); role_root=role_root or Path('outputs/roles')
|
|
40
153
|
artifact_preview_bundle=build_artifact_previews(package_root, grader.evidence_refs or outputs.deliverable_refs or outputs.artifact_refs or outputs.files_created)
|
|
41
|
-
|
|
42
|
-
|
|
154
|
+
review_packet=_latest_review_packet(package_root, outputs.attempt_kind)
|
|
155
|
+
if _model_evaluator_active(settings) and settings.llm_evaluator_enabled:
|
|
156
|
+
prompt=_evaluator_prompt(grader, verifier, outputs, trace, target_attempt_id, artifact_preview_bundle, review_packet)
|
|
43
157
|
try:
|
|
44
158
|
provider=_mentor_provider_id()
|
|
45
159
|
model_override=settings.llm_evaluator_model if provider == 'openai' else None
|
|
46
|
-
rr=run_structured_role('evaluator_agent', prompt, LLMEvaluatorOutput, role_root/'evaluator_agent'/outputs.attempt_kind, allow_fallback=settings.allow_deterministic_eval_fallback, model_override=model_override, normalizer_context={'task_id': outputs.task_id, 'attempt_id': outputs.attempt_id, 'attempt_kind': outputs.attempt_kind, 'target_attempt_id': target_attempt_id, 'grader_result_id': grader.grader_result_id, 'verifier_result_id': verifier.verifier_result_id, 'artifact_content_refs': artifact_preview_bundle.get('artifact_content_refs'), 'artifact_content_previews': artifact_preview_bundle.get('artifact_content_previews'), 'artifact_content_hashes': artifact_preview_bundle.get('artifact_content_hashes'), 'artifact_content_preview_truncated': artifact_preview_bundle.get('artifact_content_preview_truncated'), 'model_grading_basis': artifact_preview_bundle.get('model_grading_basis'), 'model': model_override or settings.model_provider_model, 'provider':provider})
|
|
160
|
+
rr=run_structured_role('evaluator_agent', prompt, LLMEvaluatorOutput, role_root/'evaluator_agent'/outputs.attempt_kind, allow_fallback=settings.allow_deterministic_eval_fallback, model_override=model_override, normalizer_context={'task_id': outputs.task_id, 'attempt_id': outputs.attempt_id, 'attempt_kind': outputs.attempt_kind, 'target_attempt_id': target_attempt_id, 'grader_result_id': grader.grader_result_id, 'verifier_result_id': verifier.verifier_result_id, 'artifact_content_refs': artifact_preview_bundle.get('artifact_content_refs'), 'artifact_content_previews': artifact_preview_bundle.get('artifact_content_previews'), 'artifact_content_hashes': artifact_preview_bundle.get('artifact_content_hashes'), 'artifact_content_preview_truncated': artifact_preview_bundle.get('artifact_content_preview_truncated'), 'model_grading_basis': artifact_preview_bundle.get('model_grading_basis'), 'review_packet_ref': review_packet.get('review_packet_ref'), 'model': model_override or settings.model_provider_model, 'provider':provider})
|
|
47
161
|
if rr.live_call_ok and rr.structured_output_validation_ok:
|
|
48
162
|
parsed=read_json(role_root/'evaluator_agent'/outputs.attempt_kind/'parsed_output.json')
|
|
49
163
|
fb=EvaluatorFeedback.model_validate(parsed['evaluator_feedback'])
|
|
50
164
|
rp=RevisionPlan.model_validate(parsed['revision_plan'])
|
|
51
165
|
fb.provider=rr.provider; fb.model=rr.model; fb.revision_plan=fb.revision_plan or rp.revision_reason
|
|
52
|
-
fb.metadata_json.update({'llm_prompt_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'prompt.md'),'llm_response_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'raw_output.txt'),'prompt_hash':sha256_text(prompt),'public_response_summary':'Model evaluator generated
|
|
53
|
-
rp.metadata_json.update({'provider':rr.provider,'model':rr.model,'llm_prompt_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'prompt.md'),'llm_response_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'raw_output.txt')})
|
|
166
|
+
fb.metadata_json.update({'llm_prompt_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'prompt.md'),'llm_response_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'raw_output.txt'),'prompt_hash':sha256_text(prompt),'public_response_summary':'Mentor Model evaluator generated grounded feedback and a revision plan from the loop review packet and package evidence.','review_source':'review_packet','review_packet_ref':review_packet.get('review_packet_ref'), **artifact_preview_bundle})
|
|
167
|
+
rp.metadata_json.update({'provider':rr.provider,'model':rr.model,'review_source':'review_packet','review_packet_ref':review_packet.get('review_packet_ref'),'llm_prompt_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'prompt.md'),'llm_response_ref_internal':str(role_root/'evaluator_agent'/outputs.attempt_kind/'raw_output.txt')})
|
|
54
168
|
return fb,rp
|
|
55
169
|
if settings.evaluation_mode == 'llm_required' or settings.llm_fail_closed:
|
|
56
170
|
raise RuntimeError(rr.error_message or 'Model evaluator failed')
|
|
@@ -59,6 +173,8 @@ def evaluate_attempt(grader: GraderResult, verifier: VerifierResult, outputs: Ac
|
|
|
59
173
|
raise
|
|
60
174
|
fb=deterministic_feedback(grader)
|
|
61
175
|
rp=_revision_plan_from_feedback(fb, target_attempt_id)
|
|
176
|
+
fb.metadata_json.update({'review_source':'review_packet', 'review_packet_ref':review_packet.get('review_packet_ref')})
|
|
177
|
+
rp.metadata_json.update({'review_source':'review_packet', 'review_packet_ref':review_packet.get('review_packet_ref')})
|
|
62
178
|
if _mentor_provider_can_attempt() and settings.llm_evaluator_enabled and settings.evaluation_mode != 'deterministic_only':
|
|
63
|
-
fb.metadata_json.update({'model_evaluator_enabled': True, 'llm_evaluator_enabled': True, 'llm_unavailable': True, '
|
|
179
|
+
fb.metadata_json.update({'model_evaluator_enabled': True, 'llm_evaluator_enabled': True, 'llm_unavailable': True, 'mentor_evaluator_status': 'unavailable', 'review_source':'review_packet', 'review_packet_ref':review_packet.get('review_packet_ref'), **artifact_preview_bundle})
|
|
64
180
|
return fb,rp
|
|
@@ -124,15 +124,15 @@ def deterministic_grade(rubric: RubricSpec, outputs: ActualOutputs, attempt_kind
|
|
|
124
124
|
item_evidence=[e for e in evidence if Path(e).name in req] or evidence
|
|
125
125
|
item_missing=[m for m in missing if m in req]
|
|
126
126
|
ok = outputs.status == 'success' and not item_missing and bool(item_evidence)
|
|
127
|
-
item_scores.append(RubricItemScore(rubric_item_id=item.rubric_item_id, criterion_name=item.criterion_name, score=1.0 if ok else 0.0, max_score=1.0, passed=ok, evidence_refs=item_evidence, failure_mode=None if ok else 'missing_artifact', notes='
|
|
127
|
+
item_scores.append(RubricItemScore(rubric_item_id=item.rubric_item_id, criterion_name=item.criterion_name, score=1.0 if ok else 0.0, max_score=1.0, passed=ok, evidence_refs=item_evidence, failure_mode=None if ok else 'missing_artifact', notes='Structural artifact-contract guardrail only; semantic correctness is not judged.', confidence=0.8, artifact_presence_ok=ok, semantic_correctness_score=None, reasoning_summary='Required artifact paths were resolved.' if ok else 'One or more required artifacts were not resolved.', improvement_suggestion=None if ok else 'Create the missing required artifact files.'))
|
|
128
128
|
score=artifact_contract_score
|
|
129
|
-
return GraderResult(grader_result_id=f'grader_{outputs.attempt_id}', task_id=outputs.task_id, attempt_id=outputs.attempt_id, attempt_kind=attempt_kind, rubric_id=rubric.rubric_id, grader_kind='
|
|
129
|
+
return GraderResult(grader_result_id=f'grader_{outputs.attempt_id}', task_id=outputs.task_id, attempt_id=outputs.attempt_id, attempt_kind=attempt_kind, rubric_id=rubric.rubric_id, grader_kind='structural_guardrail', score_source='structural_guardrail', score=score, max_score=1.0, passed=artifact_contract_passed, rubric_item_scores=item_scores, failed_criteria=[s.rubric_item_id for s in item_scores if not s.passed], passed_criteria=[s.rubric_item_id for s in item_scores if s.passed], evidence_refs=evidence, confidence=0.8, reasoning_summary='Structural guardrail checked artifact presence/readiness only; Mentor grading is unavailable unless a Mentor Model or human grader runs.', limitations=['semantic_quality_not_judged','mentor_grade_unavailable'], hidden_reference_used=False, hidden_reference_leaked=False, artifact_contract_score=artifact_contract_score, semantic_score=None, final_score=score, model=None, provider=None, deterministic_precheck_ref=None, public_response_summary='Structural artifact-contract guardrail completed.', score_reliability='needs_review', metadata_json={'structural_guardrail': True, 'role_status': 'mentor_grader_unavailable', 'artifact_contract_passed': artifact_contract_passed, 'semantic_quality_not_judged': True, 'missing_artifacts': missing, **match})
|
|
130
130
|
|
|
131
131
|
def _grader_prompt(rubric: RubricSpec, outputs: ActualOutputs, trace: AgentTrace | None, precheck: GraderResult, artifact_preview_bundle: dict | None=None) -> str:
|
|
132
132
|
return """Return only valid JSON matching GraderResult. Do not include markdown.
|
|
133
|
-
You are the
|
|
133
|
+
You are the Mentor Model grader. Score each rubric item using the Apprentice-generated plus Mentor-audited task rubric, attempt trace, actual_outputs.json, artifact refs, and artifact content previews when available. Return a JSON object with exactly these required top-level fields: grader_result_id, task_id, attempt_id, attempt_kind, rubric_id, grader_kind, score_source, artifact_contract_score, model_score, semantic_score, final_score, score, max_score, passed, confidence, evidence_refs, rubric_item_scores, limitations, hidden_reference_used, hidden_reference_leaked, metadata_json. Each rubric_item_scores entry must include artifact_presence_ok, semantic_correctness_score, evidence_refs, reasoning_summary, confidence, failure_mode, and improvement_suggestion. Cite evidence_refs from actual_outputs/artifacts/trace, distinguish artifact presence from Mentor-judged correctness, and set score_source=model_judged and grader_kind=model. Do not rely on Apprentice Agent self-assessment or self evaluation. Do not use deterministic fallback judgement; structural artifact-contract guardrails are auxiliary only and are not grader judgments.
|
|
134
134
|
Rubric JSON:
|
|
135
|
-
""" + json.dumps(rubric.model_dump(), sort_keys=True) + "\nActualOutputs JSON:\n" + json.dumps(outputs.model_dump(), sort_keys=True) + "\nArtifact content previews JSON:\n" + json.dumps(artifact_preview_bundle or {}, sort_keys=True)[:16000] + "\nTrace summary JSON:\n" + json.dumps((trace.model_dump() if trace else {}), sort_keys=True)[:12000] + "\
|
|
135
|
+
""" + json.dumps(rubric.model_dump(), sort_keys=True) + "\nActualOutputs JSON:\n" + json.dumps(outputs.model_dump(), sort_keys=True) + "\nArtifact content previews JSON:\n" + json.dumps(artifact_preview_bundle or {}, sort_keys=True)[:16000] + "\nTrace summary JSON:\n" + json.dumps((trace.model_dump() if trace else {}), sort_keys=True)[:12000] + "\nStructural guardrail JSON:\n" + json.dumps(precheck.model_dump(), sort_keys=True)
|
|
136
136
|
|
|
137
137
|
def grade_attempt(rubric: RubricSpec, outputs: ActualOutputs, attempt_kind: str, trace: AgentTrace | None=None, role_root: Path | None=None, package_root: Path | None=None) -> GraderResult:
|
|
138
138
|
settings=get_settings()
|
|
@@ -140,10 +140,11 @@ def grade_attempt(rubric: RubricSpec, outputs: ActualOutputs, attempt_kind: str,
|
|
|
140
140
|
role_root=role_root or Path('outputs/roles')
|
|
141
141
|
artifact_preview_bundle=build_artifact_previews(package_root, pre.evidence_refs or outputs.deliverable_refs or outputs.artifact_refs or outputs.files_created)
|
|
142
142
|
pre.metadata_json.update(artifact_preview_bundle)
|
|
143
|
-
if _model_grading_active(settings) and
|
|
143
|
+
if _model_grading_active(settings) and settings.llm_grader_enabled:
|
|
144
144
|
prompt=_grader_prompt(rubric, outputs, trace, pre, artifact_preview_bundle)
|
|
145
145
|
(role_root/'grader_agent'/attempt_kind).mkdir(parents=True, exist_ok=True)
|
|
146
|
-
|
|
146
|
+
structural_ref = role_root/'grader_agent'/attempt_kind/'structural_guardrail.json'
|
|
147
|
+
write_json(structural_ref, pre)
|
|
147
148
|
try:
|
|
148
149
|
provider=_mentor_provider_id()
|
|
149
150
|
model_override=settings.llm_grader_model if provider == 'openai' else None
|
|
@@ -151,8 +152,8 @@ def grade_attempt(rubric: RubricSpec, outputs: ActualOutputs, attempt_kind: str,
|
|
|
151
152
|
if rr.live_call_ok and rr.structured_output_validation_ok:
|
|
152
153
|
parsed=read_json(role_root/'grader_agent'/attempt_kind/'parsed_output.json')
|
|
153
154
|
g=GraderResult.model_validate(parsed)
|
|
154
|
-
g.grader_kind='model'; g.score_source='model_judged'; g.legacy_score_source='llm_semantic'; g.artifact_contract_score=pre.artifact_contract_score; g.semantic_score=g.semantic_score if g.semantic_score is not None else g.score; g.model_score=g.model_score if g.model_score is not None else g.semantic_score; g.legacy_semantic_score=g.legacy_semantic_score if g.legacy_semantic_score is not None else g.semantic_score; g.final_score=g.final_score if g.final_score is not None else g.semantic_score; g.score=g.final_score; g.model=rr.model; g.provider=rr.provider; g.deterministic_precheck_ref=
|
|
155
|
-
g.metadata_json.update({'
|
|
155
|
+
g.grader_kind='model'; g.score_source='model_judged'; g.legacy_score_source='llm_semantic'; g.artifact_contract_score=pre.artifact_contract_score; g.semantic_score=g.semantic_score if g.semantic_score is not None else g.score; g.model_score=g.model_score if g.model_score is not None else g.semantic_score; g.legacy_semantic_score=g.legacy_semantic_score if g.legacy_semantic_score is not None else g.semantic_score; g.final_score=g.final_score if g.final_score is not None else g.semantic_score; g.score=g.final_score; g.model=rr.model; g.provider=rr.provider; g.deterministic_precheck_ref=None; g.llm_prompt_ref_internal=str(role_root/'grader_agent'/attempt_kind/'prompt.md'); g.llm_response_ref_internal=str(role_root/'grader_agent'/attempt_kind/'raw_output.txt'); g.public_prompt_hash=sha256_text(prompt); g.public_response_summary=g.public_response_summary or 'Mentor Model grader scored rubric items with evidence refs and artifact previews when available.'
|
|
156
|
+
g.metadata_json.update({'structural_guardrail_ref': str(structural_ref), 'structural_guardrail': pre.model_dump(), **artifact_preview_bundle, 'llm_role_result_ref_internal':str(role_root/'grader_agent'/attempt_kind/'role_result.json')})
|
|
156
157
|
return g
|
|
157
158
|
if settings.evaluation_mode == 'llm_required' or settings.llm_fail_closed:
|
|
158
159
|
raise RuntimeError(rr.error_message or 'Model grader failed')
|
|
@@ -162,11 +163,11 @@ def grade_attempt(rubric: RubricSpec, outputs: ActualOutputs, attempt_kind: str,
|
|
|
162
163
|
if settings.evaluation_mode == 'deterministic_only':
|
|
163
164
|
pre.semantic_score=None; pre.final_score=pre.score
|
|
164
165
|
elif _mentor_provider_can_attempt() and settings.llm_grader_enabled:
|
|
165
|
-
pre.grader_kind='
|
|
166
|
+
pre.grader_kind='structural_guardrail'; pre.score_source='structural_guardrail'; pre.semantic_score=None; pre.final_score=pre.score
|
|
166
167
|
provider=_mentor_provider_id()
|
|
167
168
|
pre.model=settings.model_provider_model or settings.llm_grader_model; pre.provider=provider
|
|
168
|
-
pre.limitations.append('Model grader unavailable;
|
|
169
|
-
pre.metadata_json.update({'llm_grader_enabled': True, 'llm_unavailable': True, '
|
|
169
|
+
pre.limitations.append('Mentor Model grader unavailable; only structural guardrails ran.')
|
|
170
|
+
pre.metadata_json.update({'llm_grader_enabled': True, 'llm_unavailable': True, 'role_status': 'mentor_grader_unavailable'})
|
|
170
171
|
return pre
|
|
171
172
|
|
|
172
173
|
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import hashlib
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
import shutil
|
|
7
8
|
from datetime import datetime, timezone
|
|
8
9
|
from pathlib import Path
|
|
@@ -10,7 +11,7 @@ from typing import Any
|
|
|
10
11
|
|
|
11
12
|
from .config import Settings, get_settings
|
|
12
13
|
from .env import contains_secret
|
|
13
|
-
from .io import read_json, read_jsonl, write_json
|
|
14
|
+
from .io import append_jsonl, read_json, read_jsonl, write_json
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
PACK_STATUSES = {"draft", "active", "reverted", "removed"}
|
|
@@ -69,6 +70,18 @@ def experience_root(settings: Settings | None = None) -> Path:
|
|
|
69
70
|
return root
|
|
70
71
|
|
|
71
72
|
|
|
73
|
+
def installed_skills_root(settings: Settings | None = None) -> Path:
|
|
74
|
+
s = settings or get_settings()
|
|
75
|
+
root = s.app_home / "installed_skills"
|
|
76
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
return root
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def installed_skills_index_path(settings: Settings | None = None) -> Path:
|
|
81
|
+
s = settings or get_settings()
|
|
82
|
+
return s.app_home / "installed_skills_index.jsonl"
|
|
83
|
+
|
|
84
|
+
|
|
72
85
|
def _load_json_or_jsonl(path: Path) -> list[dict[str, Any]]:
|
|
73
86
|
if not path.exists():
|
|
74
87
|
return []
|
|
@@ -620,6 +633,262 @@ def compare_replay(before: dict[str, Any], after: dict[str, Any]) -> dict[str, A
|
|
|
620
633
|
}
|
|
621
634
|
|
|
622
635
|
|
|
636
|
+
def _find_bundle_root(path: Path) -> Path:
|
|
637
|
+
path = path.expanduser()
|
|
638
|
+
if path.is_file():
|
|
639
|
+
path = path.parent
|
|
640
|
+
candidates = [
|
|
641
|
+
path,
|
|
642
|
+
path / "contribution_bundle",
|
|
643
|
+
path / "package",
|
|
644
|
+
]
|
|
645
|
+
for candidate in candidates:
|
|
646
|
+
if (candidate / "contribution_manifest.json").exists() or (candidate / "experience_compiler").exists():
|
|
647
|
+
return candidate
|
|
648
|
+
for candidate in path.rglob("contribution_manifest.json"):
|
|
649
|
+
return candidate.parent
|
|
650
|
+
raise FileNotFoundError(f"Experience Compilation not found: {path}")
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _safe_copy_file(src: Path, dst: Path, files: list[str], root: Path) -> None:
|
|
654
|
+
if not src.exists() or not src.is_file():
|
|
655
|
+
return
|
|
656
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
657
|
+
shutil.copy2(src, dst)
|
|
658
|
+
files.append(dst.relative_to(root).as_posix())
|
|
659
|
+
|
|
660
|
+
|
|
661
|
+
def _safe_copytree(src: Path, dst: Path, files: list[str], root: Path) -> None:
|
|
662
|
+
if not src.exists() or not src.is_dir():
|
|
663
|
+
return
|
|
664
|
+
shutil.copytree(src, dst, dirs_exist_ok=True)
|
|
665
|
+
for file_path in sorted(dst.rglob("*")):
|
|
666
|
+
if file_path.is_file():
|
|
667
|
+
files.append(file_path.relative_to(root).as_posix())
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _read_manifest_field(bundle: Path, key: str, default: Any = None) -> Any:
|
|
671
|
+
for rel in ["contribution_manifest.json", "experience_compiler/compiler_manifest.json"]:
|
|
672
|
+
path = bundle / rel
|
|
673
|
+
if path.exists():
|
|
674
|
+
try:
|
|
675
|
+
data = read_json(path)
|
|
676
|
+
except Exception:
|
|
677
|
+
continue
|
|
678
|
+
if isinstance(data, dict) and data.get(key) is not None:
|
|
679
|
+
return data.get(key)
|
|
680
|
+
return default
|
|
681
|
+
|
|
682
|
+
|
|
683
|
+
def install_runtime_training_from_bundle(
|
|
684
|
+
source_item_id: str,
|
|
685
|
+
bundle_path: Path,
|
|
686
|
+
*,
|
|
687
|
+
settings: Settings | None = None,
|
|
688
|
+
agent_target: str = "current",
|
|
689
|
+
dry_run: bool = False,
|
|
690
|
+
) -> dict[str, Any]:
|
|
691
|
+
"""Install runtime-useful Experience Compiler outputs into the local registry."""
|
|
692
|
+
s = settings or get_settings()
|
|
693
|
+
bundle = _find_bundle_root(bundle_path)
|
|
694
|
+
compiler = bundle / "experience_compiler"
|
|
695
|
+
skill_dir = compiler / "skill_pack"
|
|
696
|
+
runtime_dir = compiler / "runtime_learning_package"
|
|
697
|
+
skill_md = skill_dir / "skill.md"
|
|
698
|
+
if not skill_md.exists():
|
|
699
|
+
raise FileNotFoundError(f"Runtime training skill.md not found in {bundle}")
|
|
700
|
+
package_id = str(_read_manifest_field(bundle, "bundle_id") or _read_manifest_field(bundle, "source_package_id") or bundle.name)
|
|
701
|
+
title = str(_read_manifest_field(bundle, "title") or _read_manifest_field(bundle, "source_task_id") or source_item_id)
|
|
702
|
+
digest = hashlib.sha256(f"{source_item_id}|{package_id}|{skill_md.read_text(errors='replace')[:5000]}".encode()).hexdigest()[:10]
|
|
703
|
+
installed_skill_id = f"aa-skill-{slugify(title, 'runtime-training')}-{digest}"
|
|
704
|
+
target = installed_skills_root(s) / installed_skill_id
|
|
705
|
+
files: list[str] = []
|
|
706
|
+
if dry_run:
|
|
707
|
+
return {
|
|
708
|
+
"installed_skill_id": installed_skill_id,
|
|
709
|
+
"source_item_id": source_item_id,
|
|
710
|
+
"source_package_id": package_id,
|
|
711
|
+
"dry_run": True,
|
|
712
|
+
"would_install": [
|
|
713
|
+
"skill.md",
|
|
714
|
+
"canonical_skill.json",
|
|
715
|
+
"retrieval_card.json",
|
|
716
|
+
"runtime_learning_package/",
|
|
717
|
+
"install_manifest.json",
|
|
718
|
+
],
|
|
719
|
+
"target": str(target),
|
|
720
|
+
}
|
|
721
|
+
if target.exists():
|
|
722
|
+
shutil.rmtree(target)
|
|
723
|
+
target.mkdir(parents=True, exist_ok=True)
|
|
724
|
+
_safe_copy_file(skill_md, target / "skill.md", files, target)
|
|
725
|
+
_safe_copytree(runtime_dir, target / "runtime_learning_package", files, target)
|
|
726
|
+
for src_name, dst_name in [
|
|
727
|
+
("canonical_skill.json", "canonical_skill.json"),
|
|
728
|
+
("retrieval_card.json", "retrieval_card.json"),
|
|
729
|
+
("skill_card.json", "skill_card.json"),
|
|
730
|
+
("verifier_spec.json", "verifier_spec.json"),
|
|
731
|
+
("verifier_checklist.json", "verifier_checklist.json"),
|
|
732
|
+
("tool_recipe.json", "tool_recipe.json"),
|
|
733
|
+
]:
|
|
734
|
+
_safe_copy_file(skill_dir / src_name, target / dst_name, files, target)
|
|
735
|
+
if not (target / "canonical_skill.json").exists():
|
|
736
|
+
write_json(
|
|
737
|
+
target / "canonical_skill.json",
|
|
738
|
+
{
|
|
739
|
+
"skill_id": installed_skill_id,
|
|
740
|
+
"title": title,
|
|
741
|
+
"source_item_id": source_item_id,
|
|
742
|
+
"source_package_id": package_id,
|
|
743
|
+
"skill_ref": "skill.md",
|
|
744
|
+
},
|
|
745
|
+
)
|
|
746
|
+
files.append("canonical_skill.json")
|
|
747
|
+
if not (target / "retrieval_card.json").exists():
|
|
748
|
+
write_json(
|
|
749
|
+
target / "retrieval_card.json",
|
|
750
|
+
{
|
|
751
|
+
"skill_id": installed_skill_id,
|
|
752
|
+
"title": title,
|
|
753
|
+
"domains": _read_manifest_field(bundle, "domains", []) or [],
|
|
754
|
+
"source_item_id": source_item_id,
|
|
755
|
+
"summary": f"Runtime training compiled from {title}.",
|
|
756
|
+
},
|
|
757
|
+
)
|
|
758
|
+
files.append("retrieval_card.json")
|
|
759
|
+
manifest = {
|
|
760
|
+
"installed_skill_id": installed_skill_id,
|
|
761
|
+
"source_item_id": source_item_id,
|
|
762
|
+
"source_package_id": package_id,
|
|
763
|
+
"source_url": _read_manifest_field(bundle, "source_url"),
|
|
764
|
+
"agent_target": agent_target,
|
|
765
|
+
"install_mode": "agent_apprenticeship_runtime",
|
|
766
|
+
"installed_at": utc_now(),
|
|
767
|
+
"enabled": True,
|
|
768
|
+
"title": title,
|
|
769
|
+
"files_installed": sorted(set(files + ["install_manifest.json"])),
|
|
770
|
+
"backup_refs": [],
|
|
771
|
+
"reversible": True,
|
|
772
|
+
"generation_notes": ["native_agent_files_unchanged"],
|
|
773
|
+
}
|
|
774
|
+
write_json(target / "install_manifest.json", manifest)
|
|
775
|
+
append_jsonl(installed_skills_index_path(s), manifest)
|
|
776
|
+
return manifest
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
def list_installed_skills(settings: Settings | None = None, include_disabled: bool = True) -> list[dict[str, Any]]:
|
|
780
|
+
root = installed_skills_root(settings)
|
|
781
|
+
rows: list[dict[str, Any]] = []
|
|
782
|
+
for path in sorted(root.iterdir()) if root.exists() else []:
|
|
783
|
+
manifest = path / "install_manifest.json"
|
|
784
|
+
if not manifest.exists():
|
|
785
|
+
continue
|
|
786
|
+
try:
|
|
787
|
+
data = read_json(manifest)
|
|
788
|
+
except Exception:
|
|
789
|
+
continue
|
|
790
|
+
if not include_disabled and not data.get("enabled", True):
|
|
791
|
+
continue
|
|
792
|
+
data = dict(data)
|
|
793
|
+
data["path"] = str(path)
|
|
794
|
+
rows.append(data)
|
|
795
|
+
return rows
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
def _installed_skill_path(installed_skill_id: str, settings: Settings | None = None) -> Path:
|
|
799
|
+
path = installed_skills_root(settings) / installed_skill_id
|
|
800
|
+
if not (path / "install_manifest.json").exists():
|
|
801
|
+
raise FileNotFoundError(f"Installed runtime training not found: {installed_skill_id}")
|
|
802
|
+
return path
|
|
803
|
+
|
|
804
|
+
|
|
805
|
+
def set_installed_skill_enabled(installed_skill_id: str, enabled: bool, settings: Settings | None = None) -> dict[str, Any]:
|
|
806
|
+
path = _installed_skill_path(installed_skill_id, settings)
|
|
807
|
+
manifest = read_json(path / "install_manifest.json")
|
|
808
|
+
manifest["enabled"] = bool(enabled)
|
|
809
|
+
manifest["updated_at"] = utc_now()
|
|
810
|
+
write_json(path / "install_manifest.json", manifest)
|
|
811
|
+
append_jsonl(installed_skills_index_path(settings), manifest)
|
|
812
|
+
return manifest
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
def uninstall_installed_skill(installed_skill_id: str, settings: Settings | None = None) -> Path:
|
|
816
|
+
root = installed_skills_root(settings)
|
|
817
|
+
path = _installed_skill_path(installed_skill_id, settings)
|
|
818
|
+
manifest = read_json(path / "install_manifest.json")
|
|
819
|
+
manifest["enabled"] = False
|
|
820
|
+
manifest["uninstalled_at"] = utc_now()
|
|
821
|
+
removed = root / "_removed" / installed_skill_id
|
|
822
|
+
removed.parent.mkdir(parents=True, exist_ok=True)
|
|
823
|
+
if removed.exists():
|
|
824
|
+
shutil.rmtree(removed)
|
|
825
|
+
write_json(path / "install_manifest.json", manifest)
|
|
826
|
+
shutil.move(str(path), str(removed))
|
|
827
|
+
append_jsonl(installed_skills_index_path(settings), manifest)
|
|
828
|
+
return removed
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
def _skill_keywords(skill: dict[str, Any]) -> set[str]:
|
|
832
|
+
text = " ".join(
|
|
833
|
+
str(skill.get(key) or "")
|
|
834
|
+
for key in ["installed_skill_id", "title", "source_item_id", "source_package_id"]
|
|
835
|
+
)
|
|
836
|
+
path = Path(str(skill.get("path") or ""))
|
|
837
|
+
for rel in ["retrieval_card.json", "canonical_skill.json", "skill.md"]:
|
|
838
|
+
file_path = path / rel
|
|
839
|
+
if file_path.exists():
|
|
840
|
+
text += " " + file_path.read_text(errors="ignore")[:3000]
|
|
841
|
+
return {token.lower() for token in re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", text)}
|
|
842
|
+
|
|
843
|
+
|
|
844
|
+
def select_runtime_training_for_run(
|
|
845
|
+
instruction: str,
|
|
846
|
+
*,
|
|
847
|
+
settings: Settings | None = None,
|
|
848
|
+
force: bool = False,
|
|
849
|
+
limit: int = 2,
|
|
850
|
+
) -> tuple[list[dict[str, Any]], str]:
|
|
851
|
+
enabled = list_installed_skills(settings, include_disabled=False)
|
|
852
|
+
if not enabled:
|
|
853
|
+
return [], ""
|
|
854
|
+
query_terms = {token.lower() for token in re.findall(r"[A-Za-z][A-Za-z0-9_-]{2,}", instruction)}
|
|
855
|
+
scored: list[tuple[int, dict[str, Any]]] = []
|
|
856
|
+
for skill in enabled:
|
|
857
|
+
overlap = len(query_terms & _skill_keywords(skill))
|
|
858
|
+
if force or overlap:
|
|
859
|
+
scored.append((overlap, skill))
|
|
860
|
+
scored.sort(key=lambda item: item[0], reverse=True)
|
|
861
|
+
selected = [skill for _, skill in scored[:limit]]
|
|
862
|
+
if not selected:
|
|
863
|
+
return [], ""
|
|
864
|
+
sections = ["\n\n---\nInstalled Runtime Training\nUse these enabled skills only when they are relevant. The task instruction remains authoritative.\n"]
|
|
865
|
+
for skill in selected:
|
|
866
|
+
path = Path(str(skill.get("path")))
|
|
867
|
+
skill_text = (path / "skill.md").read_text(errors="replace") if (path / "skill.md").exists() else ""
|
|
868
|
+
sections.append(
|
|
869
|
+
f"\n## {skill.get('title') or skill.get('installed_skill_id')}\n"
|
|
870
|
+
f"installed_skill_id: {skill.get('installed_skill_id')}\n"
|
|
871
|
+
f"selection_reason: matched runtime training against the task request.\n\n"
|
|
872
|
+
f"{skill_text[:5000]}\n"
|
|
873
|
+
)
|
|
874
|
+
sections.append("---\n")
|
|
875
|
+
return selected, "\n".join(sections)
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def installed_skill_run_refs(skills: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
879
|
+
refs: list[dict[str, Any]] = []
|
|
880
|
+
for skill in skills:
|
|
881
|
+
refs.append(
|
|
882
|
+
{
|
|
883
|
+
"installed_skill_id": skill.get("installed_skill_id"),
|
|
884
|
+
"title": skill.get("title"),
|
|
885
|
+
"source_item_id": skill.get("source_item_id"),
|
|
886
|
+
"source_package_id": skill.get("source_package_id"),
|
|
887
|
+
}
|
|
888
|
+
)
|
|
889
|
+
return refs
|
|
890
|
+
|
|
891
|
+
|
|
623
892
|
def write_before_after_result(pack_path: Path, result: dict[str, Any]) -> Path:
|
|
624
893
|
result = dict(result)
|
|
625
894
|
result["created_at"] = utc_now()
|
|
@@ -34,6 +34,7 @@ class RoleContext:
|
|
|
34
34
|
task_instruction: str | None = None
|
|
35
35
|
target_attempt_id: str | None = None
|
|
36
36
|
verifier_result_id: str | None = None
|
|
37
|
+
review_packet_ref: str | None = None
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
def _as_list(v: Any) -> list[Any]:
|
|
@@ -188,7 +189,7 @@ def _rubric_item(raw_item: Any, idx: int, weight: float, required_artifacts: lis
|
|
|
188
189
|
evidence=raw_item.get('observable_evidence') or raw_item.get('evidence') or raw_item.get('evidence_requirements') or raw_item.get('success_criteria') or required_artifacts or ['final answer and artifacts']
|
|
189
190
|
artifacts=raw_item.get('required_artifacts') or raw_item.get('artifacts') or required_artifacts or ['final_answer']
|
|
190
191
|
scoring_method=raw_item.get('scoring_method')
|
|
191
|
-
if not isinstance(scoring_method, str) or scoring_method not in {'llm_rubric_judge','deterministic','schema_match','regex','unit_test','hybrid','human_future'}:
|
|
192
|
+
if not isinstance(scoring_method, str) or scoring_method not in {'llm_rubric_judge','deterministic','schema_match','regex','unit_test','hybrid','human_future','structural_guardrail'}:
|
|
192
193
|
scoring_method='llm_rubric_judge'
|
|
193
194
|
return {
|
|
194
195
|
'rubric_item_id': str(raw_item.get('rubric_item_id') or raw_item.get('id') or f'ri_{idx}'),
|
|
@@ -253,7 +254,7 @@ def normalize_rubric_result(raw: dict[str, Any], context: RoleContext | None=Non
|
|
|
253
254
|
'required_artifacts': required,
|
|
254
255
|
'disqualifying_errors': [str(x) for x in _as_list(rub_raw.get('disqualifying_errors'))],
|
|
255
256
|
'partial_credit_allowed': _bool(rub_raw.get('partial_credit_allowed'), True),
|
|
256
|
-
'grader_kind': rub_raw.get('grader_kind') if rub_raw.get('grader_kind') in {'llm_rubric_judge','deterministic','hybrid','human_future'} else 'llm_rubric_judge',
|
|
257
|
+
'grader_kind': rub_raw.get('grader_kind') if rub_raw.get('grader_kind') in {'llm_rubric_judge','deterministic','hybrid','human_future','structural_guardrail','unavailable'} else 'llm_rubric_judge',
|
|
257
258
|
'rubric_generation_source': rub_raw.get('rubric_generation_source') if rub_raw.get('rubric_generation_source') in {'agent_assisted','family_template','task_specific_agent_draft','expert_override','deterministic_seed'} else 'task_specific_agent_draft',
|
|
258
259
|
'rubric_generation_agent_provider': rub_raw.get('rubric_generation_agent_provider') or context.provider,
|
|
259
260
|
'rubric_generation_agent_model': rub_raw.get('rubric_generation_agent_model') or context.model,
|
|
@@ -282,12 +283,12 @@ def normalize_evaluator_result(raw: dict[str, Any], context: RoleContext | None=
|
|
|
282
283
|
actionable=[str(x) for x in _as_list(actionable)]
|
|
283
284
|
weak=[str(x) for x in _as_list(fb_raw.get('failed_or_weak_rubric_items') or fb_raw.get('failed_rubric_items'))]
|
|
284
285
|
md=_metadata(fb_raw, EVALUATOR_ALLOWED)
|
|
285
|
-
md.update({'attempt_kind': fb_raw.get('attempt_kind') or context.attempt_kind, 'grader_result_id': context.grader_result_id, 'verifier_result_id': context.verifier_result_id})
|
|
286
|
+
md.update({'attempt_kind': fb_raw.get('attempt_kind') or context.attempt_kind, 'grader_result_id': context.grader_result_id, 'verifier_result_id': context.verifier_result_id, 'review_source': 'review_packet' if context.review_packet_ref else md.get('review_source'), 'review_packet_ref': context.review_packet_ref})
|
|
286
287
|
feedback={
|
|
287
288
|
'feedback_id': str(fb_raw.get('feedback_id') or f'feedback_{task_id}_{context.attempt_kind}'),
|
|
288
289
|
'task_id': task_id,
|
|
289
290
|
'attempt_id': attempt_id,
|
|
290
|
-
'target_actor': fb_raw.get('target_actor') if fb_raw.get('target_actor') in {'worker','reviser','apprentice'} else '
|
|
291
|
+
'target_actor': fb_raw.get('target_actor') if fb_raw.get('target_actor') in {'worker','reviser','apprentice'} else 'apprentice',
|
|
291
292
|
'feedback_type': fb_raw.get('feedback_type') if fb_raw.get('feedback_type') in {'criteria_failure','artifact_missing','format_error','logic_error','tool_error','quality_gap','strategy_gap','safety_or_privacy','other'} else 'other',
|
|
292
293
|
'failed_rubric_items': weak,
|
|
293
294
|
'evidence_refs': [str(x) for x in _as_list(fb_raw.get('evidence_refs'))],
|
|
@@ -322,7 +323,7 @@ def normalize_evaluator_result(raw: dict[str, Any], context: RoleContext | None=
|
|
|
322
323
|
'uses_evaluator_feedback': _bool(rp_raw.get('uses_evaluator_feedback'), True),
|
|
323
324
|
'metadata_json': rp_md,
|
|
324
325
|
}
|
|
325
|
-
revision['metadata_json'].update({'source_attempt_kind': context.attempt_kind, 'revision_goal': summary, 'priority_items': weak, 'expected_improvements': []})
|
|
326
|
+
revision['metadata_json'].update({'source_attempt_kind': context.attempt_kind, 'revision_goal': summary, 'priority_items': weak, 'expected_improvements': [], 'review_source': 'review_packet' if context.review_packet_ref else revision['metadata_json'].get('review_source'), 'review_packet_ref': context.review_packet_ref})
|
|
326
327
|
return {'evaluator_feedback': feedback, 'revision_plan': revision}
|
|
327
328
|
|
|
328
329
|
def normalize_grader_result(raw: dict[str, Any], context: RoleContext | None=None) -> dict[str, Any]:
|
|
@@ -339,7 +340,7 @@ def normalize_grader_result(raw: dict[str, Any], context: RoleContext | None=Non
|
|
|
339
340
|
semantic_score=0.0
|
|
340
341
|
final_score=0.0
|
|
341
342
|
limitations.append('Semantic grading impossible because artifact content was unavailable; score reflects missing outputs/log evidence.')
|
|
342
|
-
md['
|
|
343
|
+
md['structural_guardrail']={'artifact_contract_score': artifact_contract_score}
|
|
343
344
|
basis=context.model_grading_basis or ('artifact_content' if artifact_contract_score else ('logs_only' if refs else 'missing_outputs'))
|
|
344
345
|
md['evidence_materialization_status']='materialized' if artifact_contract_score else 'missing_or_unverified'
|
|
345
346
|
md['referenced_but_missing_artifacts']=[str(x) for x in _as_list(raw.get('referenced_but_missing_artifacts'))]
|
|
@@ -386,7 +387,7 @@ def normalize_grader_result(raw: dict[str, Any], context: RoleContext | None=Non
|
|
|
386
387
|
'final_score': final_score,
|
|
387
388
|
'model': raw.get('model') or context.model,
|
|
388
389
|
'provider': raw.get('provider') or context.provider,
|
|
389
|
-
'deterministic_precheck_ref':
|
|
390
|
+
'deterministic_precheck_ref': None,
|
|
390
391
|
'llm_prompt_ref_internal': raw.get('llm_prompt_ref_internal'),
|
|
391
392
|
'llm_response_ref_internal': raw.get('llm_response_ref_internal'),
|
|
392
393
|
'public_prompt_hash': raw.get('public_prompt_hash'),
|