feat(#92): IMP-92 u1~u5 AI fallback config validation (model ping + operational error classification)
Replaces #84 UI-noise removal plan with positive operational-alert contract. Five-axis stack lands together: (1) default model literal moved to current Opus-family ID, (2) Anthropic SDK error classifier mapping exceptions to quota/billing/auth/other, (3) api_error_kind plumbed through ai_repair_status summary + per-record retention, (4) Step 0 preflight ping gated under ai_fallback_enabled (default OFF preserved) with fail-fast on invalid model/key, (5) frontend formatter rewritten to surface only operational quota/billing/auth toasts (non-operational paths return null per feedback_auto_pipeline_first silent-pipeline policy). u1 - default model literal claude-opus-4-6-20250415 -> claude-opus-4-7 (src/config.py + tests/test_phase_z2_ai_fallback_config.py lock mirror) u2 - classify_operational_error type+status_code dispatch + Step 12 api_error_kind stamp on except path (src/phase_z2_ai_fallback/client.py + src/phase_z2_ai_fallback/step12.py + tests/phase_z2_ai_fallback/test_step12.py) u3 - _summarize_ai_repair_status aggregates api_error_kinds {quota,billing, auth,other}; error_records[i].api_error_kind retained per-record (src/phase_z2_pipeline.py + tests/test_imp47b_failure_surface.py) u4 - _run_step0_ai_preflight + Step0PreflightError; preflight only fires when ai_fallback_enabled=true; one-token ping; invalid key/model => setup failure before Step 1 (src/phase_z2_pipeline.py + tests/phase_z2/test_pipeline_step0_preflight.py NEW) u5 - AiRepairStatus.api_error_kinds? interface + formatAiRepairHumanReview Message rewritten: operational quota/billing/auth -> Korean copy verbatim from issue body (tie-break quota -> billing -> auth); validation/coverage_violated/unsupported_kind/generic-other/legacy payload -> null (Front/client/src/services/designAgentApi.ts + Front/client/tests/imp47b_human_review_toast.test.tsx) Guardrails respected: - feedback_demo_env_toggle_policy: default OFF preserved; preflight skipped when ai_fallback_enabled=false (test_preflight_skipped_when_disabled asserts anthropic.Anthropic() not called). - feedback_auto_pipeline_first: non-operational AI failures stay silent; only quota/billing/auth reach user toast. - feedback_ai_isolation_contract: AI remains fallback-only; no normal-path migration; MDX preserved. - project_imp46_carveout_caveat: cache_key/fingerprints fields untouched on every record; no overlap with #62 cache region. - feedback_no_hardcoding: zero MDX-sample-specific literals; classifier dispatch by SDK type, not by string parsing. - feedback_artifact_status_naming: operational toast scoped to alert axis, not overall PASS signal. Tests: - Targeted u1+u2+u3+u4: 63 passed - u5 vitest (Front/): 10/10 passed - tests/phase_z2_ai_fallback dir regression: 240 passed - tests/phase_z2 dir regression: 323 passed - IMP-92-adjacent (-k "imp47b or ai_fallback or preflight or step12 or step0"): 299 passed (808 deselected) - u1 baseline lock (test_client_mock.py): 8 passed Zero failures, zero regressions outside scope. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -235,6 +235,15 @@ export interface AiRepairStatus {
|
||||
unsupported_kind: number;
|
||||
error: number;
|
||||
};
|
||||
// IMP-92 u3 — per-kind operational error aggregates plumbed from Step 12
|
||||
// (u2 classify_operational_error). Optional for backward compatibility
|
||||
// with pre-u3 payloads — u5 formatter treats absence as silent.
|
||||
api_error_kinds?: {
|
||||
quota: number;
|
||||
billing: number;
|
||||
auth: number;
|
||||
other: number;
|
||||
};
|
||||
unsupported_kind_records: Array<{
|
||||
unit_index?: number | null;
|
||||
source_section_ids: string[];
|
||||
@@ -244,6 +253,8 @@ export interface AiRepairStatus {
|
||||
unit_index?: number | null;
|
||||
source_section_ids: string[];
|
||||
error: string;
|
||||
// IMP-92 u3 — per-record operational error kind (quota|billing|auth|other|null).
|
||||
api_error_kind?: string | null;
|
||||
}>;
|
||||
coverage_status: string;
|
||||
dropped_section_ids: string[];
|
||||
@@ -267,23 +278,32 @@ export interface RunMeta {
|
||||
ai_repair_status: AiRepairStatus | null;
|
||||
}
|
||||
|
||||
// IMP-92 u5 — Operational-only AI repair message formatter.
|
||||
//
|
||||
// Per the #84 operational-vs-non-operational replacement-plan contract, this
|
||||
// returns a user-visible toast string ONLY when ai_repair_status carries one
|
||||
// of the three actionable Anthropic API error kinds plumbed by u3
|
||||
// (quota / billing / auth). Non-operational AI failures (validation,
|
||||
// coverage_violated, unsupported_kind, or generic "other" API errors) return
|
||||
// null so the auto-pipeline stays silent per feedback_auto_pipeline_first.
|
||||
// Messages mirror the issue body copy contract exactly (429/402/401 →
|
||||
// quota/billing/auth Korean strings).
|
||||
export function formatAiRepairHumanReviewMessage(
|
||||
ai: AiRepairStatus | null | undefined,
|
||||
): string | null {
|
||||
if (!ai || !ai.human_review_required) return null;
|
||||
if (ai.status === "error") {
|
||||
const n = ai.counts?.error ?? ai.error_records?.length ?? 0;
|
||||
return `AI 재구성 호출 실패 (${n}건) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
if (!ai) return null;
|
||||
const kinds = ai.api_error_kinds;
|
||||
if (!kinds) return null;
|
||||
if (kinds.quota > 0) {
|
||||
return `API quota 부족 — 충전 필요 (${kinds.quota}건)`;
|
||||
}
|
||||
if (ai.status === "coverage_violated") {
|
||||
const dropped = (ai.dropped_section_ids || []).join(", ");
|
||||
return `AI 재구성 후 콘텐츠 누락 (dropped: ${dropped || "?"}) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
if (kinds.billing > 0) {
|
||||
return `API billing 문제 — 결제 정보 확인 (${kinds.billing}건)`;
|
||||
}
|
||||
if (ai.status === "unsupported_kind") {
|
||||
const n = ai.counts?.unsupported_kind ?? ai.unsupported_kind_records?.length ?? 0;
|
||||
return `AI 제안 형식 미지원 (${n}건) — 다른 frame 선택 또는 수동 편집 필요`;
|
||||
if (kinds.auth > 0) {
|
||||
return `API key 무효 — .env 확인 (${kinds.auth}건)`;
|
||||
}
|
||||
return `AI 재구성 human_review 필요 (status: ${ai.status})`;
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface LoadRunResult {
|
||||
|
||||
Reference in New Issue
Block a user