refactor(ai_client): migrate 3 run_tier4_* sites to Result[T] (Phase 10 sites 7+8+9)
All 3 run_tier4_* functions had the same pattern:
try: ... AI call ...
except Exception as e: return '[XXX FAILED] {e}' (or None)
Per TIER1_REVIEW: empty-default return = MIGRATE to Result[T].
New helpers:
- _run_tier4_analysis_result(stderr: str) -> Result[str]
Returns Result(data=analysis) on success, Result(data='', errors=[ErrorInfo])
on SDK failure. Empty stderr short-circuits to Result(data='').
- _run_tier4_patch_callback_result(stderr: str, base_dir: str) -> Result[Optional[str]]
Returns Result(data=patch) on valid diff, Result(data=None) when no
valid diff, Result(data=None, errors=[ErrorInfo]) on SDK failure.
- _run_tier4_patch_generation_result(error: str, file_context: str) -> Result[str]
Returns Result(data=patch) on success, Result(data='', errors=[ErrorInfo])
on SDK failure. Empty error short-circuits to Result(data='').
Legacy wrappers delegate to _result helpers and return result.data,
preserving original signatures (str for sites 7,9; Optional[str] for site 8).
Existing tier4 tests pass (13/13 in test_tier4_patch_generation +
test_tier4_interceptor).
Audit: ai_client BC 3 -> 0. All 9 Phase 10 BC sites migrated.
This commit is contained in:
+61
-21
@@ -2970,17 +2970,22 @@ def _get_llama_cost_tracking() -> bool:
|
||||
|
||||
#region: Tier 4 Analysis
|
||||
|
||||
def run_tier4_analysis(stderr: str) -> str:
|
||||
def _run_tier4_analysis_result(stderr: str) -> Result[str]:
|
||||
"""Tier 4 QA agent: analyze stderr and propose a fix in ~20 words.
|
||||
|
||||
Returns Result(data=analysis) on success, Result(data="", errors=[ErrorInfo])
|
||||
on SDK failure. The legacy caller (run_tier4_analysis) returns result.data
|
||||
(preserving the original str signature; failures surface as empty string
|
||||
to keep the qa_callback contract).
|
||||
"""
|
||||
"""
|
||||
genai = _require_warmed("google.genai")
|
||||
types = genai.types
|
||||
if not stderr or not stderr.strip():
|
||||
return ""
|
||||
return Result(data="")
|
||||
try:
|
||||
_ensure_gemini_client()
|
||||
if not _gemini_client:
|
||||
return ""
|
||||
return Result(data="")
|
||||
genai = _require_warmed("google.genai")
|
||||
types = genai.types
|
||||
prompt = (
|
||||
f"You are a Tier 4 QA Agent specializing in error analysis.\n"
|
||||
f"Analyze the following stderr output from a PowerShell command:\n\n"
|
||||
@@ -2997,15 +3002,29 @@ def run_tier4_analysis(stderr: str) -> str:
|
||||
)
|
||||
)
|
||||
analysis = resp.text.strip() if resp.text else ""
|
||||
return analysis
|
||||
return Result(data=analysis)
|
||||
except Exception as e:
|
||||
return f"[QA ANALYSIS FAILED] {e}"
|
||||
return Result(
|
||||
data="",
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"[QA ANALYSIS FAILED] {e}", source="ai_client._run_tier4_analysis_result", original=e)],
|
||||
)
|
||||
|
||||
|
||||
def run_tier4_analysis(stderr: str) -> str:
|
||||
return _run_tier4_analysis_result(stderr).data
|
||||
|
||||
#endregion: Tier 4 Analysis
|
||||
|
||||
#region: Session & Public API
|
||||
|
||||
def run_tier4_patch_callback(stderr: str, base_dir: str) -> Optional[str]:
|
||||
def _run_tier4_patch_callback_result(stderr: str, base_dir: str) -> Result[Optional[str]]:
|
||||
"""Tier 4 QA agent: propose a unified-diff patch for the stderr.
|
||||
|
||||
Returns Result(data=patch) when a valid diff is produced, Result(data=None)
|
||||
when no valid diff, Result(data=None, errors=[ErrorInfo]) on SDK failure.
|
||||
The legacy caller (run_tier4_patch_callback) returns result.data
|
||||
(preserving the original Optional[str] signature).
|
||||
"""
|
||||
try:
|
||||
file_items = project_manager.get_current_file_items()
|
||||
file_context = ""
|
||||
@@ -3015,23 +3034,34 @@ def run_tier4_patch_callback(stderr: str, base_dir: str) -> Optional[str]:
|
||||
file_context += f"\n\nFile: {path}\n```\n{content}\n```\n"
|
||||
patch = run_tier4_patch_generation(stderr, file_context)
|
||||
if patch and "---" in patch and "+++" in patch:
|
||||
return patch
|
||||
return None
|
||||
return Result(data=patch)
|
||||
return Result(data=None)
|
||||
except Exception as e:
|
||||
return None
|
||||
return Result(
|
||||
data=None,
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"tier4 patch callback failed: {e}", source="ai_client._run_tier4_patch_callback_result", original=e)],
|
||||
)
|
||||
|
||||
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
||||
|
||||
def run_tier4_patch_callback(stderr: str, base_dir: str) -> Optional[str]:
|
||||
return _run_tier4_patch_callback_result(stderr, base_dir).data
|
||||
|
||||
def _run_tier4_patch_generation_result(error: str, file_context: str) -> Result[str]:
|
||||
"""Tier 4 QA agent: generate a unified-diff patch for the given error.
|
||||
|
||||
Returns Result(data=patch) on success, Result(data="", errors=[ErrorInfo])
|
||||
on SDK failure. The legacy caller (run_tier4_patch_generation) returns
|
||||
result.data (preserving the original str signature; failures surface as
|
||||
empty string to keep callers' downstream code working).
|
||||
"""
|
||||
[C: src/gui_2.py:App.request_patch_from_tier4, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_calls_ai, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_empty_error, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_returns_diff]
|
||||
"""
|
||||
genai = _require_warmed("google.genai")
|
||||
types = genai.types
|
||||
if not error or not error.strip():
|
||||
return ""
|
||||
return Result(data="")
|
||||
try:
|
||||
_ensure_gemini_client()
|
||||
if not _gemini_client:
|
||||
return ""
|
||||
return Result(data="")
|
||||
genai = _require_warmed("google.genai")
|
||||
types = genai.types
|
||||
prompt = (
|
||||
f"{mma_prompts.TIER4_PATCH_PROMPT}\n\n"
|
||||
f"Error:\n```\n{error}\n```\n\n"
|
||||
@@ -3047,9 +3077,19 @@ def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
||||
)
|
||||
)
|
||||
patch = resp.text.strip() if resp.text else ""
|
||||
return patch
|
||||
return Result(data=patch)
|
||||
except Exception as e:
|
||||
return f"[PATCH GENERATION FAILED] {e}"
|
||||
return Result(
|
||||
data="",
|
||||
errors=[ErrorInfo(kind=ErrorKind.INTERNAL, message=f"[PATCH GENERATION FAILED] {e}", source="ai_client._run_tier4_patch_generation_result", original=e)],
|
||||
)
|
||||
|
||||
|
||||
def run_tier4_patch_generation(error: str, file_context: str) -> str:
|
||||
"""
|
||||
[C: src/gui_2.py:App.request_patch_from_tier4, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_calls_ai, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_empty_error, tests/test_tier4_patch_generation.py:test_run_tier4_patch_generation_returns_diff]
|
||||
"""
|
||||
return _run_tier4_patch_generation_result(error, file_context).data
|
||||
|
||||
def get_token_stats(md_content: str) -> dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
"""Phase 10 sites 7+8+9: run_tier4_* Result helpers.
|
||||
|
||||
Site 7 (run_tier4_analysis): returns str with '[QA ANALYSIS FAILED]' on error.
|
||||
Site 8 (run_tier4_patch_callback): returns Optional[str] with None on error.
|
||||
Site 9 (run_tier4_patch_generation): returns str with '[PATCH GENERATION FAILED]' on error.
|
||||
|
||||
All 3 follow the same pattern:
|
||||
try: ...AI call...
|
||||
except Exception as e: return "[XXX FAILED] {e}" (or None)
|
||||
|
||||
Migrate via Result[str] / Result[Optional[str]] helpers.
|
||||
"""
|
||||
import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
|
||||
def test_phase10_sites789_run_tier4_analysis_result_exists():
|
||||
import src.ai_client
|
||||
assert hasattr(src.ai_client, "_run_tier4_analysis_result"), \
|
||||
"_run_tier4_analysis_result helper missing"
|
||||
|
||||
|
||||
def test_phase10_sites789_run_tier4_patch_callback_result_exists():
|
||||
import src.ai_client
|
||||
assert hasattr(src.ai_client, "_run_tier4_patch_callback_result"), \
|
||||
"_run_tier4_patch_callback_result helper missing"
|
||||
|
||||
|
||||
def test_phase10_sites789_run_tier4_patch_generation_result_exists():
|
||||
import src.ai_client
|
||||
assert hasattr(src.ai_client, "_run_tier4_patch_generation_result"), \
|
||||
"_run_tier4_patch_generation_result helper missing"
|
||||
|
||||
|
||||
def test_phase10_sites789_all_helpers_return_result():
|
||||
import src.ai_client
|
||||
import inspect
|
||||
for name in ("_run_tier4_analysis_result",
|
||||
"_run_tier4_patch_callback_result",
|
||||
"_run_tier4_patch_generation_result"):
|
||||
fn = getattr(src.ai_client, name)
|
||||
sig = inspect.signature(fn)
|
||||
assert "Result" in str(sig.return_annotation), \
|
||||
f"{name} return must be Result, got {sig.return_annotation}"
|
||||
|
||||
|
||||
def test_phase10_sites789_legacy_unchanged():
|
||||
"""Legacy functions must still exist + be callable."""
|
||||
import src.ai_client
|
||||
for name in ("run_tier4_analysis",
|
||||
"run_tier4_patch_callback",
|
||||
"run_tier4_patch_generation"):
|
||||
assert hasattr(src.ai_client, name), f"{name} missing"
|
||||
assert callable(getattr(src.ai_client, name)), f"{name} not callable"
|
||||
Reference in New Issue
Block a user