Доказать полезность margin-agent через честный boundary replay
This commit is contained in:
+103
-2
@@ -256,6 +256,9 @@ GUARDED_INSUFFICIENCY_PRIMARY_MARKERS = (
|
||||
"\u0442\u043e\u0447\u043d\u044b\u0435",
|
||||
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d",
|
||||
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043d",
|
||||
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0430",
|
||||
"\u043d\u0435\u043b\u044c\u0437\u044f \u0447\u0435\u0441\u0442\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434",
|
||||
"\u043d\u0435\u043b\u044c\u0437\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b",
|
||||
)
|
||||
GUARDED_INSUFFICIENCY_LIMITATION_MARKERS = (
|
||||
"\u043f\u0440\u0435\u0434\u0432\u0430\u0440\u0438\u0442\u0435\u043b\u044c\u043d",
|
||||
@@ -265,12 +268,16 @@ GUARDED_INSUFFICIENCY_LIMITATION_MARKERS = (
|
||||
"\u043d\u0435 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0451\u043d\u043d\u043e\u0435 \u0441\u0430\u043b\u044c\u0434\u043e",
|
||||
"\u043d\u0435 \u0434\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u043e\u0441\u0442\u0430\u0442\u043e\u043a",
|
||||
"\u043d\u0435 \u0444\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0435\u0435\u0441\u0442\u0440",
|
||||
"\u0433\u0440\u0430\u043d\u0438\u0446\u0430 \u043e\u0442\u0432\u0435\u0442\u0430",
|
||||
"\u0440\u0430\u0441\u0447\u0435\u0442\u043d\u0443\u044e \u0431\u0430\u0437\u0443",
|
||||
"\u0440\u0430\u0441\u0447\u0451\u0442\u043d\u0443\u044e \u0431\u0430\u0437\u0443",
|
||||
)
|
||||
GUARDED_INSUFFICIENCY_RESULT_MODES = {"heuristic_candidates"}
|
||||
GUARDED_INSUFFICIENCY_TRUTH_MODES = {"limited"}
|
||||
GUARDED_INSUFFICIENCY_ANSWER_SHAPES = {"limited_with_reason"}
|
||||
BUSINESS_EXPECTED_RESULT_MODES = {
|
||||
"clarification_required",
|
||||
"honest_boundary_with_next_action",
|
||||
"limited_accounting_answer",
|
||||
"evidence_or_honest_boundary",
|
||||
"ranking_or_limited_accounting_answer",
|
||||
@@ -966,6 +973,23 @@ def is_margin_profitability_step(step_output: dict[str, Any]) -> bool:
|
||||
question = str(step_output.get("question_resolved") or step_output.get("question_template") or "")
|
||||
if is_nomenclature_margin_context(step_output, question):
|
||||
return True
|
||||
margin_context_values = [
|
||||
str(step_output.get("scenario_id") or ""),
|
||||
str(step_output.get("target_id") or ""),
|
||||
str(step_output.get("fix_goal") or ""),
|
||||
str(step_output.get("business_mismatch") or ""),
|
||||
str(step_output.get("minimal_patch_direction") or ""),
|
||||
*normalize_string_list(step_output.get("signals")),
|
||||
]
|
||||
margin_context = " ".join(margin_context_values).casefold()
|
||||
if (
|
||||
"inventory_margin_ranking_for_nomenclature" in margin_context
|
||||
or "inventory_inventory_margin_ranking_for_nomenclature" in margin_context
|
||||
or "margin_false_source" in margin_context
|
||||
or "payment_false_source" in margin_context
|
||||
or ("margin" in margin_context and ("оплат" in margin_context or "банк" in margin_context))
|
||||
):
|
||||
return True
|
||||
tokens = [
|
||||
str(step_output.get("expected_business_answer_contract") or ""),
|
||||
str(step_output.get("required_answer_contract") or ""),
|
||||
@@ -978,6 +1002,8 @@ def derive_repair_issue_code(step_output: dict[str, Any], problem_type: str) ->
|
||||
violated = normalize_string_list(step_output.get("violated_invariants"))
|
||||
if "domain_leak_accounting_route" in violated and is_margin_profitability_step(step_output):
|
||||
return "margin_domain_leak_accounting_route"
|
||||
if is_margin_profitability_step(step_output) and problem_type in {"route_gap", "capability_gap", "evidence_gap"}:
|
||||
return "margin_domain_leak_accounting_route"
|
||||
for issue_code in (
|
||||
"technical_garbage_in_answer",
|
||||
"business_direct_answer_missing",
|
||||
@@ -2141,6 +2167,36 @@ def is_nomenclature_margin_context(step_state: dict[str, Any], question: str) ->
|
||||
return has_subject and has_margin_signal and has_rank_signal
|
||||
|
||||
|
||||
def is_margin_false_source_boundary_answer(step_state: dict[str, Any], question: str, assistant_text: str) -> bool:
|
||||
tags = set(normalize_string_list(step_state.get("semantic_tags")))
|
||||
question_text = _review_text(question)
|
||||
answer_text = _review_text(assistant_text)
|
||||
has_false_source_question = (
|
||||
"payment_false_source" in tags
|
||||
or (
|
||||
("марж" in question_text or "прибыл" in question_text)
|
||||
and ("товар" in question_text or "номенклатур" in question_text)
|
||||
and ("оплат" in question_text or "банк" in question_text)
|
||||
)
|
||||
)
|
||||
if not has_false_source_question:
|
||||
return False
|
||||
rejects_source = any(
|
||||
marker in answer_text
|
||||
for marker in (
|
||||
"нельзя",
|
||||
"не подтвержд",
|
||||
"не подтвержда",
|
||||
"не использовать",
|
||||
"не является",
|
||||
"не расчет",
|
||||
"не расчёт",
|
||||
)
|
||||
)
|
||||
names_correct_basis = "выруч" in answer_text and "себестоим" in answer_text
|
||||
return rejects_source and names_correct_basis
|
||||
|
||||
|
||||
def build_business_first_review(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||
question = str(step_state.get("question_resolved") or step_state.get("question_template") or "").strip()
|
||||
assistant_text = str(step_state.get("assistant_text") or "")
|
||||
@@ -2171,11 +2227,18 @@ def build_business_first_review(step_state: dict[str, Any]) -> dict[str, Any]:
|
||||
limited_answer = _has_any_marker(assistant_text, BUSINESS_LIMITED_ANSWER_MARKERS)
|
||||
has_next_action = _has_any_marker(assistant_text, BUSINESS_NEXT_ACTION_MARKERS)
|
||||
nomenclature_margin_context = is_nomenclature_margin_context(step_state, question)
|
||||
wrong_margin_domain_hits = (
|
||||
raw_wrong_margin_domain_hits = (
|
||||
_marker_hits(assistant_text, NOMENCLATURE_MARGIN_WRONG_DOMAIN_ANSWER_MARKERS)
|
||||
if nomenclature_margin_context
|
||||
else []
|
||||
)
|
||||
if raw_wrong_margin_domain_hits and is_margin_false_source_boundary_answer(step_state, question, assistant_text):
|
||||
allowed_false_source_boundary_hits = {"банковск", "списание с расчетного", "списание с расчётного"}
|
||||
wrong_margin_domain_hits = [
|
||||
hit for hit in raw_wrong_margin_domain_hits if hit not in allowed_false_source_boundary_hits
|
||||
]
|
||||
else:
|
||||
wrong_margin_domain_hits = raw_wrong_margin_domain_hits
|
||||
margin_contract_hits = (
|
||||
_marker_hits(assistant_text, NOMENCLATURE_MARGIN_EXPECTED_ANSWER_MARKERS)
|
||||
if nomenclature_margin_context
|
||||
@@ -2440,6 +2503,21 @@ def business_expected_result_mode_matches(expected_result_mode: str, step_state:
|
||||
and reply_type in {"partial_coverage", "factual", "factual_with_explanation"}
|
||||
)
|
||||
|
||||
if expected_result_mode == "honest_boundary_with_next_action":
|
||||
business_review = step_state.get("business_first_review") if isinstance(step_state.get("business_first_review"), dict) else {}
|
||||
return (
|
||||
clean_business_review
|
||||
and bool(assistant_text)
|
||||
and bool(business_review.get("next_action_present"))
|
||||
and (
|
||||
truth_mode in GUARDED_INSUFFICIENCY_TRUTH_MODES
|
||||
or answer_shape in GUARDED_INSUFFICIENCY_ANSWER_SHAPES
|
||||
or step_state.get("balance_confirmed") is False
|
||||
or is_margin_false_source_boundary_answer(step_state, str(step_state.get("question_resolved") or ""), assistant_text)
|
||||
)
|
||||
and reply_type in {"partial_coverage", "factual", "factual_with_explanation"}
|
||||
)
|
||||
|
||||
if expected_result_mode == "ranking_or_limited_accounting_answer":
|
||||
return (
|
||||
clean_business_review
|
||||
@@ -4357,6 +4435,16 @@ def normalize_analyst_priority_repair_target(raw_target: dict[str, Any], index:
|
||||
if not root_cause_layers:
|
||||
root_cause_layers = [problem_type]
|
||||
issue_code = str(raw_target.get("issue_code") or problem_type or "other").strip()
|
||||
issue_probe = {
|
||||
**raw_target,
|
||||
"scenario_id": scenario_id,
|
||||
"target_id": f"{scenario_id}:{step_id}",
|
||||
"problem_type": problem_type,
|
||||
"root_cause_layers": root_cause_layers,
|
||||
"fix_goal": fix_goal,
|
||||
}
|
||||
if issue_code in {"route_gap", "capability_gap", "evidence_gap"} and is_margin_profitability_step(issue_probe):
|
||||
issue_code = "margin_domain_leak_accounting_route"
|
||||
catalog_entry = issue_catalog_entry(issue_code)
|
||||
return {
|
||||
"issue_code": issue_code,
|
||||
@@ -5212,6 +5300,18 @@ def build_issue_catalog_snapshot(repair_targets: dict[str, Any], catalog: dict[s
|
||||
}
|
||||
|
||||
|
||||
def detector_evidence_paths_for_target(target: dict[str, Any]) -> list[str]:
|
||||
explicit = normalize_string_list(target.get("evidence_paths"))
|
||||
if explicit:
|
||||
return explicit
|
||||
refs = target.get("artifact_refs") if isinstance(target.get("artifact_refs"), dict) else {}
|
||||
step_state_path = str(refs.get("step_state_json") or "").strip()
|
||||
if not step_state_path:
|
||||
return []
|
||||
step_state = Path(step_state_path)
|
||||
return [str(step_state.with_name("output.md")), str(step_state.with_name("turn.json"))]
|
||||
|
||||
|
||||
def build_detector_candidates(repair_targets: dict[str, Any], catalog: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
source = catalog if isinstance(catalog, dict) else load_issue_catalog()
|
||||
issues = source.get("issues") if isinstance(source.get("issues"), dict) else {}
|
||||
@@ -5225,6 +5325,7 @@ def build_detector_candidates(repair_targets: dict[str, Any], catalog: dict[str,
|
||||
detectors = normalize_string_list(entry.get("detectors"))
|
||||
if not detectors and issue_code:
|
||||
detectors = [f"{issue_code}_detector"]
|
||||
evidence_paths = detector_evidence_paths_for_target(target)
|
||||
for detector in detectors:
|
||||
key = (issue_code, detector)
|
||||
if key in seen:
|
||||
@@ -5236,7 +5337,7 @@ def build_detector_candidates(repair_targets: dict[str, Any], catalog: dict[str,
|
||||
"detector": detector,
|
||||
"severity": target.get("severity"),
|
||||
"sample_target_id": target.get("target_id"),
|
||||
"evidence_paths": target.get("evidence_paths") or [],
|
||||
"evidence_paths": evidence_paths,
|
||||
}
|
||||
)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user