feat(ai): wire llm_features into compliance, seo, reconciliation
The AI features in llm_features.py (llm_compliance_analyze,
llm_seo_analyze, llm_entity_reconcile, llm_pii_detect,
llm_anomaly_detect) were implemented but never called from the live
code path. The endpoint functions were regex-only, with the LLM
functions sitting in limbo.
This change wires the LLM as a FALLBACK when the regex/heuristic
pass is low-confidence. The user pays nothing extra, gets better
results, and the LLM cost is tracked per-call.
Changes:
- compliance.py run_compliance_check:
When tos_result.confidence == "low" (or no ToS was found),
call llm_compliance_analyze and merge the richer classification
into tos_result. llm_enhanced: True is set.
Pass-through: the LLM fields (provider, cost, risk_summary, etc.)
are now copied into the terms_of_service sub-dict of the response.
- seo_monitor.py analyze_seo:
When title, meta_description, or h1 are empty after the regex
pass, call llm_seo_analyze to suggest content. Best-effort: empty
regex fields are filled in from LLM suggestions, llm_enhanced
flag is set.
- reconciliation.py:
New async function llm_enhance_reconciliation(entities) that
sends low-confidence groups to llm_entity_reconcile for
verification/refutation. Returns a summary dict with counts.
- New test file tests/test_llm_fallback.py with 6 tests:
compliance: 2 tests (merges correctly, degrades on LLM error)
seo: 1 test (fills empty fields, sets llm_enhanced)
reconciliation: 3 tests (function exists, handles no-low-conf,
handles LLM error)
All 6 pass. All existing compliance/seo/reconciliation tests
(28) still pass.
Defaults: the LLM uses the fleet's free Ollama on Talos
(100.100.18.18:11434) when no other provider is configured, so
fallback cost is effectively zero in production.
This commit is contained in:
parent
80b067ea3b
commit
17b16c8666
4 changed files with 267 additions and 0 deletions
|
|
@ -68,6 +68,29 @@ async def analyze_seo(url: str) -> dict[str, Any]:
|
|||
"content_type": resp.headers.get("content-type", ""),
|
||||
"last_modified": resp.headers.get("last-modified", ""),
|
||||
}
|
||||
# LLM enhancement: if critical SEO fields are empty, use the LLM to
|
||||
# suggest better content based on the page. Best-effort: if the LLM
|
||||
# call fails or no provider is configured, we return the regex result.
|
||||
missing_critical = [f for f in ("title", "meta_description", "h1") if not result.get(f)]
|
||||
if missing_critical:
|
||||
try:
|
||||
from llm_features import llm_seo_analyze
|
||||
llm_enhancement = await llm_seo_analyze(
|
||||
url=url,
|
||||
html=resp.text[:6000],
|
||||
missing_fields=missing_critical,
|
||||
)
|
||||
if llm_enhancement:
|
||||
for f in missing_critical:
|
||||
suggestion = llm_enhancement.get(f)
|
||||
if suggestion and not result.get(f):
|
||||
result[f] = suggestion
|
||||
result["llm_enhanced"] = True
|
||||
result["llm_provider"] = llm_enhancement.get("llm_provider", "")
|
||||
result["llm_cost_usd"] = llm_enhancement.get("llm_cost_usd", 0.0)
|
||||
except Exception as e:
|
||||
logger.debug("llm_seo_enhance_failed", extra={"url": url, "error": str(e)[:80]})
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
return {"url": url, "error": str(e)[:200]}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue