fix: address recipe/OCR quality issues from review
This commit is contained in:
parent
7aebe96675
commit
a18b2d2ffe
3 changed files with 21 additions and 16 deletions
|
|
@ -39,7 +39,7 @@ def _try_docuvision(image_path: str | Path) -> str | None:
|
||||||
client = CFOrchClient(cf_orch_url)
|
client = CFOrchClient(cf_orch_url)
|
||||||
with client.allocate(
|
with client.allocate(
|
||||||
service="cf-docuvision",
|
service="cf-docuvision",
|
||||||
model_candidates=[], # cf-docuvision has no model selection
|
model_candidates=["cf-docuvision"],
|
||||||
ttl_s=60.0,
|
ttl_s=60.0,
|
||||||
caller="kiwi-ocr",
|
caller="kiwi-ocr",
|
||||||
) as alloc:
|
) as alloc:
|
||||||
|
|
@ -48,8 +48,9 @@ def _try_docuvision(image_path: str | Path) -> str | None:
|
||||||
doc_client = DocuvisionClient(alloc.url)
|
doc_client = DocuvisionClient(alloc.url)
|
||||||
result = doc_client.extract_text(image_path)
|
result = doc_client.extract_text(image_path)
|
||||||
return result.text if result.text else None
|
return result.text if result.text else None
|
||||||
except Exception:
|
except Exception as exc:
|
||||||
return None # graceful degradation
|
logger.debug("cf-docuvision fast-path failed, falling back: %s", exc)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class VisionLanguageOCR:
|
class VisionLanguageOCR:
|
||||||
|
|
@ -141,15 +142,11 @@ class VisionLanguageOCR:
|
||||||
# Try docuvision fast path first (skips heavy local VLM if available)
|
# Try docuvision fast path first (skips heavy local VLM if available)
|
||||||
docuvision_text = _try_docuvision(image_path)
|
docuvision_text = _try_docuvision(image_path)
|
||||||
if docuvision_text is not None:
|
if docuvision_text is not None:
|
||||||
return {
|
parsed = self._parse_json_from_text(docuvision_text)
|
||||||
"raw_text": docuvision_text,
|
if parsed is not None:
|
||||||
"merchant": {},
|
parsed["raw_text"] = docuvision_text
|
||||||
"transaction": {},
|
return self._validate_result(parsed)
|
||||||
"items": [],
|
# If parsing fails, fall through to local VLM
|
||||||
"totals": {},
|
|
||||||
"confidence": {"overall": None},
|
|
||||||
"warnings": [],
|
|
||||||
}
|
|
||||||
|
|
||||||
self._load_model()
|
self._load_model()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -238,12 +238,16 @@ class LLMRecipeGenerator:
|
||||||
raw_notes = parsed.get("notes", "")
|
raw_notes = parsed.get("notes", "")
|
||||||
notes_str: str = raw_notes if isinstance(raw_notes, str) else ""
|
notes_str: str = raw_notes if isinstance(raw_notes, str) else ""
|
||||||
|
|
||||||
|
all_ingredients: list[str] = list(parsed.get("ingredients", []))
|
||||||
|
pantry_set = {item.lower() for item in (req.pantry_items or [])}
|
||||||
|
missing = [i for i in all_ingredients if i.lower() not in pantry_set]
|
||||||
|
|
||||||
suggestion = RecipeSuggestion(
|
suggestion = RecipeSuggestion(
|
||||||
id=0,
|
id=0,
|
||||||
title=parsed.get("title") or "LLM Recipe",
|
title=parsed.get("title") or "LLM Recipe",
|
||||||
match_count=len(req.pantry_items),
|
match_count=len(req.pantry_items),
|
||||||
element_coverage={},
|
element_coverage={},
|
||||||
missing_ingredients=list(parsed.get("ingredients", [])),
|
missing_ingredients=missing,
|
||||||
directions=directions_list,
|
directions=directions_list,
|
||||||
notes=notes_str,
|
notes=notes_str,
|
||||||
level=req.level,
|
level=req.level,
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,8 @@ def test_generate_returns_result_when_llm_responds(monkeypatch):
|
||||||
assert len(result.suggestions) == 1
|
assert len(result.suggestions) == 1
|
||||||
suggestion = result.suggestions[0]
|
suggestion = result.suggestions[0]
|
||||||
assert suggestion.title == "Mushroom Butter Pasta"
|
assert suggestion.title == "Mushroom Butter Pasta"
|
||||||
assert "butter" in suggestion.missing_ingredients
|
# All LLM ingredients (butter, mushrooms, pasta) are in the pantry, so none are missing
|
||||||
|
assert suggestion.missing_ingredients == []
|
||||||
assert len(suggestion.directions) > 0
|
assert len(suggestion.directions) > 0
|
||||||
assert "parmesan" in suggestion.notes.lower()
|
assert "parmesan" in suggestion.notes.lower()
|
||||||
assert result.element_gaps == ["Brightness"]
|
assert result.element_gaps == ["Brightness"]
|
||||||
|
|
@ -218,8 +219,11 @@ def test_recipe_gen_falls_back_without_cf_orch(monkeypatch):
|
||||||
fake_router = MagicMock()
|
fake_router = MagicMock()
|
||||||
fake_router.complete.side_effect = _fake_complete
|
fake_router.complete.side_effect = _fake_complete
|
||||||
|
|
||||||
# Patch where LLMRouter is imported inside _call_llm
|
# LLMRouter is imported locally inside _call_llm, so patch it at its source module.
|
||||||
with patch("circuitforge_core.llm.router.LLMRouter", return_value=fake_router):
|
# new_callable=MagicMock makes the class itself a MagicMock; set return_value so
|
||||||
|
# that LLMRouter() (instantiation) yields fake_router rather than a new MagicMock.
|
||||||
|
with patch("circuitforge_core.llm.router.LLMRouter", new_callable=MagicMock) as mock_router_cls:
|
||||||
|
mock_router_cls.return_value = fake_router
|
||||||
gen._call_llm("direct path prompt")
|
gen._call_llm("direct path prompt")
|
||||||
|
|
||||||
assert router_called.get("prompt") == "direct path prompt"
|
assert router_called.get("prompt") == "direct path prompt"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue