Теги учитывают надписи с фотографии
Алекс пересоздавал теги для вещи «Светодиодная лампа LED (навигатор)»
и получил «лампа для навигатора», «лампа для GPS-навигатора», «навигационная
лампа». Модель прочитала «навигатор» как назначение, а не как марку Navigator,
и по таким тегам вещь уже не найти.
Правкой промпта это лечилось лишь наполовину: правила и запреты убирали часть
мусора, но «лампа для навигатора» держалась. Написание марки латиницей
помогало, но переименовывать все вещи — не решение.
Настоящее решение подсказал сам Алекс: у вещи есть фотография, и на упаковке
написана настоящая маркировка. Теперь пересоздание тегов сначала читает
надписи с фото и отдаёт их модели как достоверные данные.
Результат на той же вещи:
было: лампа для навигатора, лампа для GPS-навигатора
стало: лампа Navigator, встроенный светодиодный светильник, лампа DOWNLIGHT,
лампа 10 Вт, лампа с холодным белым светом
С коробки прочиталось: Navigator, DOWNLIGHT, 10Вт, IP44, 90°, холодный белый
свет — заодно выяснилось, что это не лампочка, а встраиваемый светильник.
В промпт добавлены два разобранных примера: марка в названии — это марка,
а не назначение вещи; и не приписывать артикулы, которых никто не давал
(на «шуруповёрт Makita» модель выдумывала «Makita XA020, 18 В»).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -60,6 +60,21 @@ qwen3: изолента → «изоляционная лента»,
|
|||||||
После смены у записанных вещей теги остаются прежними; обновить их можно
|
После смены у записанных вещей теги остаются прежними; обновить их можно
|
||||||
кнопкой **«🔄 Пересоздать теги»** в карточке.
|
кнопкой **«🔄 Пересоздать теги»** в карточке.
|
||||||
|
|
||||||
|
**Пересоздание учитывает фотографию.** Если у вещи есть снимок, приложение
|
||||||
|
сначала читает с него надписи и отдаёт их модели как достоверную маркировку.
|
||||||
|
Разница на живом примере — вещь «Светодиодная лампа LED (навигатор)»:
|
||||||
|
|
||||||
|
```
|
||||||
|
без фото: «лампа для навигатора», «лампа для GPS-навигатора» ← модель
|
||||||
|
решила, что это лампа для навигатора
|
||||||
|
с фото: «лампа Navigator», «встроенный светодиодный светильник»,
|
||||||
|
«лампа DOWNLIGHT», «лампа 10 Вт»
|
||||||
|
```
|
||||||
|
|
||||||
|
С коробки прочиталось `Navigator, DOWNLIGHT, 10Вт, IP44, 90°, холодный белый
|
||||||
|
свет` — и выяснилось, что это вообще не лампочка, а встраиваемый светильник.
|
||||||
|
На упаковке написана настоящая маркировка, гадать по названию не нужно.
|
||||||
|
|
||||||
## Установка
|
## Установка
|
||||||
|
|
||||||
Положите `install.cmd` в пустую папку и запустите — он скачает проект, создаст
|
Положите `install.cmd` в пустую папку и запустите — он скачает проект, создаст
|
||||||
|
|||||||
+19
-7
@@ -495,12 +495,22 @@ def api_retag_item(item_id):
|
|||||||
return jsonify({"ok": False,
|
return jsonify({"ok": False,
|
||||||
"reply": "Модель недоступна — теги придумывать некому."}), 503
|
"reply": "Модель недоступна — теги придумывать некому."}), 503
|
||||||
|
|
||||||
# Вторая попытка не роскошь: если только что распознавали фото, в памяти
|
# Если у вещи есть фотография — сначала читаем надписи с неё. На упаковке
|
||||||
# Ollama лежит vision-модель, и первый запрос уходит на её выгрузку
|
# настоящая маркировка, и модели не приходится гадать по названию.
|
||||||
# и загрузку текстовой — он может вернуться пустым.
|
seen = None
|
||||||
fresh = brain.aliases(item.get("name"), item.get("note"))
|
if item.get("photo") and config.VISION_MODEL:
|
||||||
|
path = PHOTOS_DIR / item["photo"]
|
||||||
|
if path.exists():
|
||||||
|
guess = brain.describe_photo(path.read_bytes())
|
||||||
|
if guess and guess.get("text"):
|
||||||
|
seen = guess["text"]
|
||||||
|
|
||||||
|
# Вторая попытка не роскошь: после обращения к vision-модели в памяти
|
||||||
|
# Ollama лежит она, и первый запрос уходит на её выгрузку и загрузку
|
||||||
|
# текстовой — он может вернуться пустым.
|
||||||
|
fresh = brain.aliases(item.get("name"), item.get("note"), seen=seen)
|
||||||
if not fresh:
|
if not fresh:
|
||||||
fresh = brain.aliases(item.get("name"), item.get("note"))
|
fresh = brain.aliases(item.get("name"), item.get("note"), seen=seen)
|
||||||
if not fresh:
|
if not fresh:
|
||||||
return jsonify({"ok": False, "reply": "Модель не вернула ни одного тега."})
|
return jsonify({"ok": False, "reply": "Модель не вернула ни одного тега."})
|
||||||
|
|
||||||
@@ -517,8 +527,10 @@ def api_retag_item(item_id):
|
|||||||
item["emb"] = None # словарь предмета изменился
|
item["emb"] = None # словарь предмета изменился
|
||||||
search.ensure_embeddings(db)
|
search.ensure_embeddings(db)
|
||||||
store.save(db)
|
store.save(db)
|
||||||
return jsonify({"ok": True, "item": _card(db, item),
|
reply = "Теги пересозданы: %s" % ", ".join(item["aliases"])
|
||||||
"reply": "Теги пересозданы: %s" % ", ".join(item["aliases"])})
|
if seen:
|
||||||
|
reply += "\n(прочитано с фото: %s)" % seen
|
||||||
|
return jsonify({"ok": True, "item": _card(db, item), "seen": seen, "reply": reply})
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/containers/<code>", methods=["PATCH"])
|
@app.route("/api/containers/<code>", methods=["PATCH"])
|
||||||
|
|||||||
+22
-2
@@ -79,6 +79,16 @@ ALIAS_SYSTEM = """Ты придумываешь альтернативные н
|
|||||||
|
|
||||||
«Стабилизатор 7805» → ["LM7805","стабилизатор 5 вольт","линейный регулятор напряжения","КР142ЕН5А","стабилизатор питания"]
|
«Стабилизатор 7805» → ["LM7805","стабилизатор 5 вольт","линейный регулятор напряжения","КР142ЕН5А","стабилизатор питания"]
|
||||||
«изолента» → ["изоляционная лента","синяя лента","ПВХ-лента","электроизоляционная лента"]
|
«изолента» → ["изоляционная лента","синяя лента","ПВХ-лента","электроизоляционная лента"]
|
||||||
|
|
||||||
|
Марка в названии — это МАРКА, а не назначение вещи:
|
||||||
|
«Светодиодная лампа LED (навигатор)» → ["лампа Navigator","светодиодная лампа","LED лампа","лампочка светодиодная","лампа навигатор"]
|
||||||
|
Здесь «навигатор» — марка Navigator. Вещь остаётся ЛАМПОЙ, поэтому «лампа для
|
||||||
|
навигатора» и «навигационная лампа» — НЕВЕРНО: это описания другой вещи.
|
||||||
|
Каждый тег обязан обозначать ту же самую вещь, только другими словами.
|
||||||
|
|
||||||
|
Не приписывай вещи артикулы и характеристики, которых нет в её названии:
|
||||||
|
«шуруповёрт Makita» → ["шуруповерт Makita","шуруповерт Макита","аккумуляторный шуруповерт","электрошуруповерт"]
|
||||||
|
а не «Makita XA020» и не «Makita 18 В» — этих данных тебе никто не давал.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_THINK_RE = re.compile(r"<think(?:ing)?>.*?</think(?:ing)?>", re.IGNORECASE | re.DOTALL)
|
_THINK_RE = re.compile(r"<think(?:ing)?>.*?</think(?:ing)?>", re.IGNORECASE | re.DOTALL)
|
||||||
@@ -338,13 +348,23 @@ def describe_photo(image_bytes):
|
|||||||
# ============================================================
|
# ============================================================
|
||||||
# Синонимы
|
# Синонимы
|
||||||
# ============================================================
|
# ============================================================
|
||||||
def aliases(name, note=None, model=None):
|
def aliases(name, note=None, model=None, seen=None):
|
||||||
"""3-7 альтернативных названий. Пустой список, если модель недоступна."""
|
"""3-7 альтернативных названий. Пустой список, если модель недоступна.
|
||||||
|
|
||||||
|
seen — надписи, прочитанные с фотографии вещи. Это лучший источник тегов:
|
||||||
|
на упаковке написана настоящая маркировка, и модели не приходится гадать.
|
||||||
|
Без неё «Светодиодная лампа LED (навигатор)» превращалась в «лампу для
|
||||||
|
GPS-навигатора», хотя на коробке ясно написано Navigator.
|
||||||
|
"""
|
||||||
name = (name or "").strip()
|
name = (name or "").strip()
|
||||||
if not name:
|
if not name:
|
||||||
return []
|
return []
|
||||||
model = model or config.CHAT_MODEL
|
model = model or config.CHAT_MODEL
|
||||||
|
|
||||||
prompt = name + (" (" + note + ")" if note else "")
|
prompt = name + (" (" + note + ")" if note else "")
|
||||||
|
if seen:
|
||||||
|
prompt += ("\nНа фотографии этой вещи видны надписи: " + seen
|
||||||
|
+ "\nИспользуй их: это настоящая маркировка, а не догадка.")
|
||||||
# 500, а не 256: модель может ответить многословно (объектами вместо строк),
|
# 500, а не 256: модель может ответить многословно (объектами вместо строк),
|
||||||
# и тогда на 256 токенах JSON обрывается на полуслове — разбирать нечего.
|
# и тогда на 256 токенах JSON обрывается на полуслове — разбирать нечего.
|
||||||
content = ollama.chat(model, [
|
content = ollama.chat(model, [
|
||||||
|
|||||||
Reference in New Issue
Block a user