Подсказка названия по фотографии
Фото выбрано в форме — приложение показывает его модели и предлагает название
с прочитанными надписями. «Подставить» переносит в поля, «Не надо» прячет.
Само не подставляется, и это принципиально: модели домысливают марки
и артикулы, когда надписей не видно. В замере клипсу для кабеля модель назвала
«конструктором LEGO Technic, 8376, 401 шт.». Запрет на выдумывание в промпте
помог: та же вещь стала «красной щёткой для посуды».
Смотрит gemma3:4b. Выбрана замером на настоящих снимках из базы:
gemma3:4b 3.5 с, отвечает всегда, надписи читает верно
(«Лампа светодиодная C35, Нейтральный белый, 9 Вт LED, E14»);
qwen3-vl:8b 11-32 с и часто пустой ответ — это ризонер, ему нужен бюджет
в 2000+ токенов, и уменьшение картинки не спасает.
Проверялась и гипотеза, что дело в размере снимка: у gemma3 разницы между
1280 и 512 px почти нет, у qwen3-vl время падает с 62 до 41 с, но ответ
всё равно пуст. Картинка всё же уменьшается до 768 px — незачем гонять лишнее.
Генерация тегов чинилась по ходу: на «Стабилизатор 7805 (TO-220)» модель
отвечала массивом объектов с пояснениями, не укладывалась в 256 токенов,
и JSON обрывался на полуслове — теги выходили пустыми. Бюджет поднят до 500,
промпт требует плоский массив строк, а из битого ответа строки теперь
вытаскиваются регуляркой: лучше несколько живых тегов, чем ничего.
Pillow добавлен в зависимости — для уменьшения картинки перед распознаванием.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+29
-3
@@ -116,13 +116,39 @@ r.check("пустое имя — 400",
|
||||
r.check("занятое имя — 400",
|
||||
c.open("/api/item/" + item, method="PATCH", json={"name": "лопата"}).status_code == 400)
|
||||
|
||||
print("\n== Подсказка по фотографии ==")
|
||||
HAS_VISION = bool(config.VISION_MODEL) and HAS_AI and config.VISION_MODEL in ollama.models()
|
||||
if HAS_VISION:
|
||||
real = None
|
||||
for candidate in ("31ee37dada70.jpg", "afbaf962ad16.jpg"):
|
||||
path = config.PROJECT_DIR / "static" / "photos" / candidate
|
||||
if path.exists():
|
||||
real = path
|
||||
break
|
||||
if real:
|
||||
d = c.post("/api/recognize", content_type="multipart/form-data",
|
||||
data={"photo": (io.BytesIO(real.read_bytes()), "p.jpg")}).get_json()
|
||||
r.check("модель что-то увидела", d.get("ok") and d["guess"]["name"], d)
|
||||
r.check("название не пустое и короткое",
|
||||
0 < len(d["guess"]["name"]) <= 80, d["guess"]["name"])
|
||||
print(" предложила: %s" % d["guess"]["name"])
|
||||
else:
|
||||
print(" (реальных фото нет — пропускаю)")
|
||||
else:
|
||||
print(" (vision-модель недоступна — пропускаю)")
|
||||
r.check("без файла — 400", c.post("/api/recognize", content_type="multipart/form-data",
|
||||
data={}).status_code == 400)
|
||||
r.check("для записи без фото — 404",
|
||||
c.post("/api/item/нет-такой/recognize").status_code == 404)
|
||||
|
||||
print("\n== Пересоздание тегов ==")
|
||||
if HAS_AI:
|
||||
j("PATCH", "/api/item/" + item, aliases=["мусорный тег"])
|
||||
d = c.post("/api/item/" + item + "/retag").get_json()
|
||||
r.check("теги пересозданы", d.get("ok") and d["item"]["aliases"], d)
|
||||
r.check("мусорный тег заменён", "мусорный тег" not in d["item"]["aliases"],
|
||||
d["item"]["aliases"])
|
||||
r.check("теги пересозданы", d.get("ok") and d.get("item", {}).get("aliases"), d)
|
||||
r.check("мусорный тег заменён",
|
||||
"мусорный тег" not in d.get("item", {}).get("aliases", ["мусорный тег"]),
|
||||
d.get("item"))
|
||||
d = c.post("/api/item/" + item + "/retag?keep=1").get_json()
|
||||
r.check("режим дополнения работает", d.get("ok"), d)
|
||||
r.check("retag для несуществующей — 404",
|
||||
|
||||
Reference in New Issue
Block a user