Private
Public Access
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
|
#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():
|
if not stderr or not stderr.strip():
|
||||||
return ""
|
return Result(data="")
|
||||||
try:
|
try:
|
||||||
_ensure_gemini_client()
|
_ensure_gemini_client()
|
||||||
if not _gemini_client:
|
if not _gemini_client:
|
||||||
return ""
|
return Result(data="")
|
||||||
|
genai = _require_warmed("google.genai")
|
||||||
|
types = genai.types
|
||||||
prompt = (
|
prompt = (
|
||||||
f"You are a Tier 4 QA Agent specializing in error analysis.\n"
|
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"
|
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 ""
|
analysis = resp.text.strip() if resp.text else ""
|
||||||
return analysis
|
return Result(data=analysis)
|
||||||
except Exception as e:
|
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
|
#endregion: Tier 4 Analysis
|
||||||
|
|
||||||
#region: Session & Public API
|
#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:
|
try:
|
||||||
file_items = project_manager.get_current_file_items()
|
file_items = project_manager.get_current_file_items()
|
||||||
file_context = ""
|
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"
|
file_context += f"\n\nFile: {path}\n```\n{content}\n```\n"
|
||||||
patch = run_tier4_patch_generation(stderr, file_context)
|
patch = run_tier4_patch_generation(stderr, file_context)
|
||||||
if patch and "---" in patch and "+++" in patch:
|
if patch and "---" in patch and "+++" in patch:
|
||||||
return patch
|
return Result(data=patch)
|
||||||
return None
|
return Result(data=None)
|
||||||
except Exception as e:
|
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():
|
if not error or not error.strip():
|
||||||
return ""
|
return Result(data="")
|
||||||
try:
|
try:
|
||||||
_ensure_gemini_client()
|
_ensure_gemini_client()
|
||||||
if not _gemini_client:
|
if not _gemini_client:
|
||||||
return ""
|
return Result(data="")
|
||||||
|
genai = _require_warmed("google.genai")
|
||||||
|
types = genai.types
|
||||||
prompt = (
|
prompt = (
|
||||||
f"{mma_prompts.TIER4_PATCH_PROMPT}\n\n"
|
f"{mma_prompts.TIER4_PATCH_PROMPT}\n\n"
|
||||||
f"Error:\n```\n{error}\n```\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 ""
|
patch = resp.text.strip() if resp.text else ""
|
||||||
return patch
|
return Result(data=patch)
|
||||||
except Exception as e:
|
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]:
|
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