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:
@@ -74,13 +74,24 @@ async def mock_tests() -> None:
|
||||
"mention_3": {"choice": "دفتر", "confidence": 0.5,
|
||||
"probabilities": {"دفتر": 0.52, "_none": 0.48}},
|
||||
})
|
||||
orig_fallback = matching.match_with_deepseek
|
||||
async def mock_fallback_below_floor(*args, **kwargs):
|
||||
ms = kwargs.get("mentions") or (args[1] if len(args) > 1 else [])
|
||||
return {normalize_text(v): {"status": "new", "field": m.get("field"), "variants": m.get("variants"), "id": None}
|
||||
for m in ms for v in m.get("variants", [])}
|
||||
matching.match_with_deepseek = mock_fallback_below_floor
|
||||
|
||||
mentions = [
|
||||
{"field": "project", "variants": ["منشیانه"]},
|
||||
{"field": "project", "variants": ["دفتر", "دفر"]},
|
||||
{"field": "project", "variants": ["چیز عجیب"]},
|
||||
{"field": "project", "variants": ["دفر دو"]},
|
||||
]
|
||||
res = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
|
||||
try:
|
||||
res = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
|
||||
finally:
|
||||
matching.match_with_deepseek = orig_fallback
|
||||
|
||||
r0 = matching.lookup(res, "منشیانه")
|
||||
r1 = matching.lookup(res, "دفر") # variant key resolves too
|
||||
r2 = matching.lookup(res, "چیز عجیب")
|
||||
@@ -90,15 +101,23 @@ async def mock_tests() -> None:
|
||||
check("variant of same mention shares one result", r1 is r0 if False else
|
||||
(r1 and matching.lookup(res, "دفتر") is r1))
|
||||
check("_none / low conf -> new", r2 and r2["status"] == "new")
|
||||
check("below-floor pick -> NO result (difflib fallback keeps its chance)",
|
||||
matching.lookup(res, "دفر دو") is None)
|
||||
check("below-floor pick -> routed to crisis fallback",
|
||||
matching.lookup(res, "دفر دو") and matching.lookup(res, "دفر دو")["status"] == "new")
|
||||
check("below-floor pick did not pollute other mentions", r1 and r1["status"] == "ambiguity")
|
||||
|
||||
# 2) Jev failure -> empty map (callers fall back to difflib)
|
||||
# 2) Jev failure -> DeepSeek crisis fallback
|
||||
matching.jev_ask = make_fake_ask({})
|
||||
matching.jev_ask.fail = True
|
||||
res_fail = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
|
||||
check("jev failure -> empty results (difflib fallback path)", res_fail == {})
|
||||
async def mock_deepseek(*args, **kwargs):
|
||||
return {normalize_text("منشیانه"): {"status": "exact", "id": "p2", "name": "منشانه",
|
||||
"field": "project", "variants": ["منشیانه"]}}
|
||||
matching.match_with_deepseek = mock_deepseek
|
||||
try:
|
||||
res_fail = await matching.resolve_mentions(ctx, {"items": []}, mentions, None)
|
||||
check("jev failure -> triggers crisis fallback",
|
||||
normalize_text("منشیانه") in res_fail and res_fail[normalize_text("منشیانه")]["status"] == "exact")
|
||||
finally:
|
||||
matching.match_with_deepseek = orig_fallback
|
||||
|
||||
# 3) apply_exact writes supplier/project ids into a payload
|
||||
payload = {"items": []}
|
||||
|
||||
Reference in New Issue
Block a user