feat(bot): add DeepSeek crisis fallback engine for Jev failure and remove difflib heuristics
- Add app/fallback_matcher.py for intelligent crisis entity matching with token and latency observability - Route Jev failure/timeouts in app/matching.py to DeepSeek fallback engine - Handle MatchingFailureError defensively in handlers/__init__.py with honest error message - Enforce strict 'new' entity status in handlers/quote.py and implement defensive get_or_create on commit - Clean up app/resolve.py to eliminate difflib and character ratio heuristics - Pass all smoke tests (59/59), fallback matching tests (15/15), and matching unit tests (13/13)
This commit is contained in:
@@ -8,7 +8,7 @@ from app.drafts import (
|
||||
KIND_SUPPLIER, merge_supplier_patch, new_supplier_payload, supplier_missing,
|
||||
)
|
||||
from app.keyboards import pick_buttons, simple_review_keyboard
|
||||
from app.resolve import safe_resolve
|
||||
from app.nlu import normalize_text
|
||||
from app.texts import ERR_GENERIC, esc, supplier_committed_report, supplier_review_text
|
||||
|
||||
logger = logging.getLogger("bot.supplier")
|
||||
@@ -33,17 +33,26 @@ async def handle_entities(ctx, chat_id, user, patch: dict, source: str | None,
|
||||
async def resolve_payload(ctx, payload: dict, trace_id: str | None = None) -> None:
|
||||
options = payload.setdefault("options", {})
|
||||
if not payload.get("category_id") and payload.get("category_name"):
|
||||
res = await safe_resolve(ctx.resolver, "category", payload["category_name"])
|
||||
await obs.log_resolution(trace_id, "category", payload["category_name"], res.status,
|
||||
[{"id": c.id, "name": c.name} for c in (res.candidates or [])[:4]])
|
||||
if res.resolved:
|
||||
payload["category_id"] = res.candidates[0].id
|
||||
payload["category_name"] = res.candidates[0].name
|
||||
norm_cat = normalize_text(payload["category_name"])
|
||||
try:
|
||||
cats = await ctx.resolver.categories()
|
||||
except Exception:
|
||||
cats = []
|
||||
exact = [c for c in cats if normalize_text(c.name) == norm_cat or (c.extra or {}).get("slug") == norm_cat]
|
||||
if len(exact) == 1:
|
||||
payload["category_id"] = exact[0].id
|
||||
payload["category_name"] = exact[0].name
|
||||
options.pop("cat", None)
|
||||
elif res.candidates:
|
||||
options["cat"] = [{"id": c.id, "name": c.name} for c in res.candidates]
|
||||
else:
|
||||
options.pop("cat", None)
|
||||
partial = [c for c in cats if norm_cat in normalize_text(c.name) and len(norm_cat) >= 3]
|
||||
if len(partial) == 1:
|
||||
payload["category_id"] = partial[0].id
|
||||
payload["category_name"] = partial[0].name
|
||||
options.pop("cat", None)
|
||||
elif partial:
|
||||
options["cat"] = [{"id": c.id, "name": c.name} for c in partial]
|
||||
else:
|
||||
options.pop("cat", None)
|
||||
|
||||
|
||||
async def apply_pick(ctx, chat_id, user, draft, field: str, choice: dict) -> None:
|
||||
|
||||
Reference in New Issue
Block a user