Changelog
Všechny podstatné změny projektu GMS Asistent (chatbot pro vyhledávání zboží
na gms.cz). Formát dle Keep a Changelog.
Konvence: každý release má verzi + datum a čas (Europe/Prague). Časy F1–F3
odpovídají git commitům.
[0.15.0] – 2026-07-11 – 5 iteračních regresních kol (40+ konverzací)
Přidáno / opraveno napříč koly 4–8
- PDF katalogové listy: na žádost o katalogový list odpověď dává přímý
markdown odkaz na PDF z dat (katalogovy_list_pdf).
- Otázky ≠ hledání: na „jaký nejmenší průměr…?", „z čeho je vyrobená…?"
se odpovídá přímo (superlativy z dat), bez refine boilerplate (_QUESTION_RE).
- Oborová synonyma médií deterministicky (
expand_media_synonyms):
kejda/močůvka/splašky → fekálie, hobliny → piliny – embedding zákaznický
termín nespojí, LLM instrukce nestačila.
- „Skladem X metrů" nikdy nefiltruje Délku (deterministická pojistka)
a rerank množství/cenu/dostupnost při hodnocení ignoruje.
- Zmínka konkrétního produktu („levnější než CALORTEC 165") jde do
hledaného textu beze změny → pool z příbuzné řady; reference s cenou,
produkt sám se mezi „levnějšími" neuvádí.
- Provozní dotazy: reklamovat/vrátit/dodat/stihnete → shop-meta hláška;
náhrada za produkt = on_topic (bez vymyšlených důvodů náhrady);
prompt-injection → off-topic.
- cm → mm deterministicky přes prompt (2,5 cm = 25 mm, ne palce);
„3 nejlepší/možnosti/varianty" mění počet výsledků; multi-item dotaz
(hadice + spony) vedlejší položku neignoruje mlčky.
Vývoj skóre kol (10 konverzací/kolo, stále těžší scénáře)
kolo 3: 4,45 → kolo 4: 4,4 → kolo 5: 4,5 → kolo 6: 4,5 → kolo 7: 4,4 → kolo 8: ~4,6
[0.14.0] – 2026-07-11 – Konverzační inteligence (2 regresní kola × 10 konverzací)
Přidáno / opraveno (kolo 1)
- Navazující zprávy neztrácí kontext média pro rerank a odpověď (ctx_query).
- České slovní palce: půlcoulka, dvoucoulová, třičtvrtěcoulová, coulová, 2 couly.
- Paměť nabídnutých produktů per session + deterministické rozuzlení
„ta první/druhá/poslední" (resolve_ordinal).
- Refine (výzva k upřesnění) jen u první zprávy session.
- „Nejlevnější?" → řazení podle ceny z dat;
_num()odmítá bool;
„deska A na B metrů" = plocha, ne tloušťka.
Přidáno / opraveno (kolo 2)
- Odkazovaný produkt se přibalí do odpovědi (
search.get_by_name+ pin):
„ta druhá, jakou má cenu?" odpoví cenou konkrétního produktu (dřív „nemáme").
- Krátké meta-dotazy (cena?/sklad?) připnou dřívější nabídku – „bude
skladem aspoň 20 metrů?" odpovídá reálnými čísly skladu dřív nabídnutých
produktů (skladem_mnozstvi v datech odpovědi).
- Negace: pole
excludev extrakci („ne PVC") → filtr kandidátů dle názvu
+ instrukce reranku/odpovědi.
- Provozní dotazy e-shopu (doprava/platba/reklamace) → slušný odkaz na
gms.cz místo „není z naší nabídky".
- Extrakce: „X metrů skladem" není rozměrové omezení; u extrémních chemikálií
odpověď doporučí ověření s technikem GMS.
[0.13.0] – 2026-07-10 – Reálná skladová dostupnost (sklady.xml)
Přidáno
- Objeven a nasazen skladový export
…/api/partnerfeed/get/sklady.xml
(30 558 záznamů: onstock/reserved/onstockfrom, klíč = skus.code, 100%
shoda). app/stock.py: stock_qty = max(onstock − reserved, 0),
restock_date. 15 084 SKU reálně skladem (dřív konstantní
DELIVERY_DATE=0 z Heureky u všeho).
- Dostupnost v odpovědi: „skladem" / „dostupné od 15. 7. 2026" /
„na objednávku" — z reálného skladu; Heureka delivery_days jen fallback
pro SKU bez záznamu; bez dat se vynechá. Pojistka MIN_STOCK_ITEMS.
- Denní refresh: feedy (5) → sync → kategorie → sklad → ceny z Heureky.
Ručně: scripts/apply_stock.py.
- FEED-POZADAVKY.md zjednodušeno: sklad i kategorie vyřešeny z XML; zbývá
hlavně ceny per ordercode, URL skupin a integrita exportů (EAN
odstraněn na žádost zadavatele).
[0.12.0] – 2026-07-10 – Kategorie u 99,7 % sortimentu (číselník skupin z XML)
Přidáno
- Objeven a nasazen číselník skupin
…/api/partnerfeed/get/skupiny.xml
(676 skupin: guid → název, parent) — nikde nedokumentovaný, ale existuje.
app/groups.py: kategorie produktu = nejhlubší z jeho <groups> GUIDů,
cesta průchodem stromu, URL = slug cesty (ověřený vzor). **Pokrytí kategorií
vzrostlo z 2 653 na 5 654 z 5 673 produktů (99,7 %)** — hydraulika, PU
spirály atd. teď mají odkaz na kategorii. Vše z XML (žádný web).
- Skupiny jsou autoritativní zdroj kategorií; Heureka enrich nastavuje už jen
ceny/dostupnost/URL. Denní refresh: feedy → sync → kategorie → enrich.
Pojistka MIN_GROUPS (torzo číselníku nic nepřepíše). Ruční běh:
scripts/apply_categories.py.
- FEED-POZADAVKY.md: P3 (číselník) vyřešeno, zbývá jen nice-to-have
<url>
ve skupinách; P1 sklad, P2 ceny, P4 integrita ověřeny jako skutečně chybějící.
[0.11.0] – 2026-07-10 – Relevance: palce, aliasy průměrů, rozvolnění, oprava slabých dotazů
Z vyhodnocení 20 testovacích dotazů (16× výborné, 2× slabé) – opravy slabých míst:
Přidáno / opraveno
- Palcové průměry deterministicky (
pipeline.apply_inch_diameters): 1/2", 2"
se z dotazu vytěží regexem a převedou na mm jako range ±6 % (2" → 47,8–53,8);
závity (G 1/2") se ignorují. Nespolehlivý převod LLM se přepíše.
→ „spojka kamlok 2\"" teď vrací skutečné KAMLOKy DN 50 (dřív 1 nesouvisející trn),
„zahradní hadice 1/2\"" vrací AQUATEC ⌀ 12,5–13 (dřív se rozměr zahodil).
- Aliasy průměrových parametrů ve filtru (Vnitřní průměr ↔ DN ↔ JS ↔ Určeno
na hadici ↔ Průměr ↔ Trubka) – tvrdý filtr už nevyřadí správný produkt jen
kvůli jinému názvu téhož rozměru.
- Rozvolnění při málo shodách: <
SEARCH_RELAX_MIN(3) přesných výsledků →
pool se doplní rozvolněnými kandidáty; přesné shody nesou označení, které
rerank preferuje (dřív se rozvolňovalo jen při 0 výsledcích).
- Rerank i pro jediného kandidáta – nevhodný smí vyřadit; prázdné pořadí
u ≤ 2 kandidátů se respektuje (konec rozporu „nemám nic vhodného" + 1 výsledek).
- Dedup dle názvu ve výsledcích (2 produkty stejného jména matou).
- Přiznání alternativy: když produkty neodpovídají žádanému materiálu/typu
(„polyamidová tyč" → jen PTFE), asistent to řekne na rovinu.
- Zásada „vše z XML": veškerá vstupní data výhradně z feedů, web se
nescrapuje. Chybějící údaje (kategorie mimo Heureku, sklad, ceny části
sortimentu) řeší požadavky na vývojáře GMS — docs/FEED-POZADAVKY.md
(sklad a ceny per ordercode, číselník skupin s URL, kontrola integrity
exportu; vše aditivní).
[0.10.0] – 2026-07-10 – Sync katalogu (soft-delete) + pojistky proti vadnému exportu
Přidáno
- Denní synchronizace katalogu (
app/sync.py, součást scheduleru; ručně
scripts/sync_catalog.py): položky zrušené na e-shopu se označí
products.active=false (soft-delete) a hledání je vyřadí — data vč.
embeddingů zůstávají (návrat položky = jen reaktivace). Nové položky se
vloží vč. SKU, parametrů, fulltextu a embeddingu; SKU existujících produktů
se přidají/odeberou dle feedu. Zrušené zboží mizí z výsledků do 24 h.
- Pojistky proti vadnému/poloprázdnému exportu (DB se nikdy nevyprázdní):
sync odmítne feed s < INGEST_MIN_PRODUCTS (1000) produkty nebo deaktivaci
SYNC_MAX_DEACTIVATE_PCT(20 %) aktivních; plný import (ingest_partner.py)
odmítne feed menší než INGEST_MIN_KEEP_PCT (90 %) stavu DB — před TRUNCATE,
vynucení --force; enrich přeskočí Heureka feed s < HEUREKA_MIN_PRODUCTS
(500) produkty; fetch_feeds.sh stahuje do .tmp + kontrola velikosti a XML
hlavičky (vadný download nepřepíše funkční feed).
- Ověřeno na DEV s čerstvými feedy: +8 nových produktů (rovnou s embeddingem),
2 zrušené deaktivovány (zmizely z hledání), SKU +132/−16; pojistky odmítly
3-produktový feed (sync i ingest), DB nedotčena.
Změněno
embed_products.py→ helperapp.embed.product_text(sdílený se syncem).- Scheduler: denní běh = feedy → sync katalogu → enrich (dřív jen enrich).
[0.9.0] – 2026-07-10 – Automatický denní refresh dat + monitoring
Přidáno
- Denní automatický refresh dat (compose služba
scheduler,
scripts/scheduler.py): každý den v 5:00 (REFRESH_HOUR, TZ=Europe/Prague)
stáhne feedy z gms.cz (atomicky, sanity-check velikosti a XML hlavičky – při
chybě zůstane starý soubor) a obohatí DB z Heureky (ceny/sklad/kategorie).
Plný reingest katalogu zůstává ruční (chrání embeddingy).
- Monitoring
GET /api/metricschráněný tajným klíčemMETRICS_KEY
(?key= nebo hlavička X-Metrics-Key; bez klíče v configu endpoint
neexistuje – 404). Vrací:
process– perf countery od startu (per endpoint: počet, chyby, avg/max ms),chats– statistiky z chat_logs (celkem/24 h/7 dní, chyby, sessions,
off-topic, avg+p95 latence, tokeny a cena celkem i za 24 h),
data– pokrytí dat (produkty/SKU, s cenou, s kategorií, s embeddingem),app– verze, provider, stav DB.
[0.8.0] – 2026-07-10 – Ceny, dostupnost, kategorie + navádění na upřesnění
Přidáno
- Obohacení z Heureka feedu (
app/heureka.py,scripts/enrich_heureka.py):
ceny („od X Kč" / přesná u spárované varianty), dostupnost („skladem" z
DELIVERY_DATE), kategorie s odkazem (URL = slug cesty, ověřený vzor
gms.cz) a přesné URL produktů/variant. Napojení: produkt přes slug názvu
(93,4 % Heureka produktů), varianta→SKU přes hmotnost / ≥2 číselné
parametry, jen jednoznačné shody (79,0 % variant). Dřívější napojení cen
přes přesnou shodu názvu bylo 0% (tiše rozbité) — nahrazeno.
- Odpověď: u každého produktu cena a dostupnost (jen z dat), na konci
Celá kategorie: název. Nové sloupce v products/skus (viz schema).
- Navádění na upřesnění (režim D): u obecného dotazu („hadice na benzín")
pipeline z dat zjistí, které parametry se u nalezených produktů liší
(refine_hints — průměr, tlak…), asistent vyzve k upřesnění, ale produkty
+ kategorii vypíše vždy. Deterministická detekce (žádná číselná omezení
v dotazu), žádné hádání.
enrich_heureka.pyjde pouštět samostatně (jen UPDATE — refresh cen/skladu
bez ztráty embeddingů, vhodné pro cron) i jako součást ingest_partner.py.
- Testy: heureka parsování/párování, ceny/dostupnost/kategorie v promptu,
refine_hints + integrace.
Známá omezení dat (k dohodě s GMS)
- Heureka pokrývá ~2,8 k z 5,7 k produktů — zbytek bez ceny/kategorie (vynechá se).
DELIVERY_DATE=0 u všech položek — reálný sklad by chtěl rozšířit partnerský
feed (množství per ordercode) + číselník skupin (guid→název).
[0.7.1] – 2026-07-10 – Refactoring do modulů + testovací suite
Změněno (chování beze změny)
- Modularizace pro paralelní vývoj (více vývojářů/agentů může pracovat bez
konfliktů): app/server.py je JEN HTTP vrstva; orchestrace chatu přesunuta do
app/pipeline.py (run_chat, HistoryStore, ChatError, náklady,
normalizace omezení); monolitický app/llm.py rozdělen na balíček
app/llm/ (providers.py, prompts.py, rerank.py, retry.py +
fasáda __init__.py se stabilním API); noise-filtr vyčleněn do app/noise.py.
- Klíčové parametry produktu pro odpověď extrahuje
prompts._key_params
(dřív inline v _answer_prompt).
Přidáno
- Testovací suite (pytest, bez DB a bez sítě – mocky): pipeline helpery
(normalize_constraints, desired_count, cost_usd, HistoryStore),
celé flow run_chat (happy path, off-topic, fallback, rerank chyby, paměť
session), search (slugify, blend, SQL fragmenty omezení), noise-filtr,
prompty, rerank (dedup/rozsahy/fallback), auth (HMAC podpisy, debug heslo),
HTTP vrstva (TestClient). Kořenový conftest.py pro import path.
/api/chatvrací 400empty_requestna prázdnou zprávu (dřív šla do LLM).
Odstraněno
- Větev
feature/rozpoznavani-obrazku(rozpoznání produktu z obrázku, MVP
0.8.0-dev) smazána vč. originu — funkce se nezachovává.
[0.7.0] – 2026-06-20 15:36 – Migrace na Gemini, logování tokenů/ceny, oprava relevance
AI vrstva → Google Gemini (jedna firma)
- Chat: extrakce
gemini-2.5-flash-lite, odpověďgemini-2.5-flash, rerank
gemini-2.5-flash-lite. Vypnuté „thinking" (rychlost+cena). ~4–5× levnější než gpt-4o.
- Embeddingy → Gemini (
gemini-embedding-001, 1536 dim). A/B proti OpenAI
ukázal výrazně lepší relevanci na těžkých dotazech (pára→CALORTEC, kyselina→CHEMITEC,
odsávání pilin→FLEXADUR). Práh relevance rekalibrován na 0.62 (Gemini sims jsou vyšší).
- Role-specific modely + provideři přes
.env(levný extract, kvalitní answer).
Sledování nákladů
- Logování tokenů + modelů každého volání do
chat_logs. - Cena za dotaz se zobrazuje v chatu (💰 $cena · tokeny · ms).
Relevance
- Hybridní blend sémantika ∪ fulltext (oprava „hadice na páru").
Integrace
- API dokumentace pro vývojáře
docs/API.md+ CORS (chat widget do e-shopu). - Architektura
docs/ARCHITECTURE.md. Responzivní UI pro mobil. Přepínač modelu z UI pryč.
[0.6.8] – 2026-06-20 14:21 – Klíčové parametry u každého produktu (default)
Změněno
- Parametry se zobrazují DEFAULTNĚ u každého produktu (dřív jen na vyžádání):
kompaktní klíčová sada – Vnitřní/Vnější průměr, Provozní tlak, Poloměr ohybu,
příp. DN/Délka (max 5). Balast (balení, hmotnost, série, barva, normy) se
nevypisuje. Funguje i v poradním/srovnávacím režimu.
[0.6.7] – 2026-06-20 14:15 – Poradní režim (srovnávání a doporučování)
Přidáno
- Poradní/diskuzní režim: asistent umí odborně PORADIT a SROVNAT (co je lepší
/ vhodnější, rozdíl mezi materiály/typy) na základě oborových znalostí –
nejdřív krátká věcná odpověď, pak doporučené produkty z katalogu.
- Hranice zůstává: konkrétní ČÍSLA o produktu (rozměry, tlaky, teploty, normy,
ceny) si NIKDY nevymýšlí – jen z dat, jinak odkáže na stránku produktu;
produkty doporučuje jen reálné z katalogu. (Princip „nehádat" se týká čísel,
ne obecné rady.)
[0.6.6] – 2026-06-20 14:09 – 5 výsledků, odpovědi na dotazy z DB
Změněno / přidáno
- Standardně 5 produktů (dřív max 3–4); na vyžádání („ukaž víc", „10 hadic")
až 10. Asistent vypisuje VŠECHNY dohledané produkty.
- Odpovědi na dotazy z databáze: když se zákazník na něco zeptá (materiál,
teplota, tlak, norma, atest, vhodnost), asistent odpoví STRUČNĚ a JEN z dat
(detail / dokumentace / parametry). Když údaj v datech není → odkáže na stránku
produktu, nic si nevymýšlí.
- Do dat odpovědi přidána
dokumentace(doc_text: normy/atesty/materiály). - Oprava formátu parametrů: jednotka se nepřidává dvakrát (už ne „300 mm mm").
[0.6.5] – 2026-06-20 13:57 – Parametry na vyžádání + oprava zdůvodnění
Opraveno / přidáno
- Parametry na vyžádání: když zákazník výslovně chce parametry/rozměry, asistent
je teď VYPÍŠE u každého produktu (tvar „Název: hodnota", jen užitečné – bez
Balení/Hmotnost/Série/Barva). Defaultně se parametry stále neukazují.
(Dřív na žádost o parametry nic neukázal.)
- Zdůvodnění odpovídá dotazu: úvodní věta se formuluje podle média/použití
z dotazu zákazníka, nezužuje význam (dřív „hadice na vodu" → „potravinářské").
[0.6.4] – 2026-06-20 13:42 – Reálná diskuze: meta-dotazy a žádost o parametry
Opraveno (na základě reálné diskuze na TESTu)
- Meta-instrukce neředí vyhledávání: do sémantiky jde jen čistá fráze od LLM
(text), ne surová historie+zpráva. Dřív „Ukaž mne parametry daných produktů"
naředilo dotaz a mezi palivové hadice se připletla potravinářská.
- Žádost o parametry/ceny → asistent rovnou vypíše produkty s krátkým popisem
a odkazem (konkrétní čísla neuvádí – přesné parametry/ceny jsou na stránce produktu).
[0.6.3] – 2026-06-20 13:24 – Code review: opravy chyb
Opraveno
- Reranker mohl zahodit vše → pokud LLM-reranker nevybral žádný index, vrátí
se top-k dle podobnosti (dřív „nic nenalezeno" i u relevantních produktů).
- Plýtvání DB dotazy: SKU/parametry se dotahují až pro FINÁLNÍ výsledky
(po reranku, 4), ne pro celý pool 20 kandidátů. Méně round-tripů na chat.
- SKU u rozvolněných omezení: když omezení vynulují varianty (soft/FTS
fallback), _matching_skus ukáže všechny SKU produktu (dřív 0 variant).
- Strop paměti diskuzí (
_HISTORY): max 2000 sessions (proti neomez. růstu).
Ověřeno (review)
- Noise filtr NEodebírá reálné hadice – 144 zachycených „hadic" jsou příslušenství
(trny, spojky, vsuvky, redukce). Funguje správně.
- Žádná SQL injekce (parametrizováno), XSS ošetřené, HMAC
compare_digest.
[0.6.2] – 2026-06-20 11:10 – Chat UI: jen čistá odpověď
Změněno
- Debug chat UI (
static/index.html): pod odpovědí už NEvypisuje velký blok
„Nalezené produkty" s tabulkami SKU/rozměrů/tlaků. Tyto detaily jsou teď uvnitř
sbaleného 🔧 Debug panelu (na rozkliknutí pro ladění). Hlavní chat ukazuje
jen čistou odpověď (produkt + popis + odkaz na e-shop).
[0.6.1] – 2026-06-20 10:57 – Pevný formát odpovědi (jen produkt + popis + odkaz)
Změněno
- Odpověď asistenta má pevný stručný formát: max 3 produkty, každý jako
- název — krátký popis (≤8 slov) + na konci
🔎 [Zobrazit vše na e-shopu]. **Žádné výpisy rozměrů/tlaků/teplot/norem/
materiálů/cen** – ty si zákazník zobrazí na e-shopu.
Pozn.
- Mezi 0.6.0 a 0.6.1 proběhla „kampaň2" zpřesnění relevance (commity v gitu, mimo
changelog): noise filtr balastu (ne-hadice) v hledání + LLM-reranker
(gpt-4o-mini: pool 20 → top 4, vyhazuje příslušenství/koncovky) + on_topic gate
uznává popis použití.
[0.6.0] – 2026-06-19 19:40 – Extrakce dat z dokumentů (PDF+OCR) a zpřesnění
Added — maximální extrakce dat
scripts/extract_docs.py– stáhne a vytěží text ze VŠECH dokumentů produktů:
PDF (text vrstva, PyMuPDF) + interní OCR (tesseract, ces+eng+deu) na scany
i obrázky. Resilientní (cache, resume, limit paměti proti OOM, strip NUL).
→ 9 324 dokumentů, 4 393 produktů má doc_text (~18,7M znaků).
scripts/reindex_fts.py– fulltext zahrnuje idoc_text(normy, atesty, materiály).doc_textv odpovědi asistenta → odpovědi cituji normy/odolnosti/teploty/
atesty z katalogových listů (i cizojazyčných, shrnuté česky). Zpřesnění výstupů.
Zlepšeno — relevance a robustnost (iterativně přes scripts/eval3.py, 20 odborných otázek)
- Soft-constraints: když přesná rozměrová/parametrová omezení vrátí 0, rozvolní se
→ topicky relevantní výsledky (nic se nehádá, odpověď ukáže reálné parametry).
Přesné rozměry zůstávají, když existují (benzín Ø50 → jen Ø50).
- Validace omezení: zahazují se neaplikovatelná (textová/materiálová) omezení
s lo/hi=None (dřív nulovala výsledky, např. „Camlock z nerezu" → 0).
- Deterministická extrakce (OpenAI
temperature=0) → stabilní on_topic/omezení
(konec kolísání stejného dotazu mezi v=0 a v=5).
- Embeddingy ZÁMĚRNĚ bez
doc_text(cizojazyčný OCR text ředil relevanci –
ověřeno: zhoršoval skóre); doc_text se využívá jen ve fulltextu a v odpovědi.
Výsledky (eval3, 20 odborných dotazů, LLM-soudce)
| před | po | |
|---|---|---|
| spokojenost | 4.10/5 | 4.20–4.35/5 (stabilní) |
| false-negativy (v=0) | několik (Camlock, EN 12115…) | 0 |
- Zbývají 2 niche aplikační dotazy (silikon na páru, vrapovaná na piliny) – produkty
existují, ale embedding nadřazuje příbuzné položky; hraniční ranking, ne chyba.
[0.5.2] – 2026-06-19 17:28 – Changelog na webu, UI drobnosti
Added
- Changelog na webu: stránka
/changelog(renderujeCHANGELOG.md,
app/mdrender.py – minimální Markdown→HTML bez závislostí). Odkazy
„Changelog" a „Admin" v hlavičce chatu.
Změněno
- Indikátor psaní v chatu: „typuji…" → „Přemýšlím…".
Pozn. (infrastruktura)
- Cron pro obnovu certu zúžen na
--cert-name gms.x86.cz(neplete se do NPM
certů); dry-run obnovy gms.x86.cz = success.
[0.5.1] – 2026-06-19 17:23 – Recall a HTTPS
Zlepšeno
- Recall vágních dotazů: pro sémantiku se embeduje kombinace čisté fráze
od LLM (dobrá diakritika) + celého dotazu (zachová kontext jako teplota/médium).
„najdi hadici na benzin" → opět 5 výsledků (dřív 1); „pryž nad 150 °C" → 5.
Ověřeno cílenými testy (benzín/polyamid/pryž 150°C/voda = 5, banán = 0).
Nasazení
- HTTPS na gms.x86.cz hotové ✅ – Let's Encrypt cert (certbot webroot v NPM),
443 SSL blok + redirect 80→443 v nginx-proxy-manager:/data/nginx/custom/http.conf.
Ověřeno z internetu: https://gms.x86.cz → 200, http → 301. Cert expiruje
2026-09-17, automatická obnova přes cron na droidu (certbot renew + reload).
[0.5.0] – 2026-06-19 17:06 – F4: Relevance přes evaluaci 55 otázek, robustnost, TEST/rproxy
Zlepšeno (iterativně přes scripts/eval2.py – 55 reálných zákaznických otázek po kategoriích)
- on_topic doména rozšířena podle skutečných kategorií GMS (pryže, gumové/
těsnící desky, podlahoviny, profily, tyče, technické plasty) – přestaly se
chybně zahazovat dotazy typu „polyamidová tyč", „pryž nad 150 °C".
- Sémantika embeduje PŘIROZENÝ dotaz (historie + zpráva), ne LLM-keywordy →
spolehlivější relevance (false-negativy zmizely).
- Poradenské dotazy (rozdíl materiálů, jak vybrat, na míru): když není přesný
produkt, LLM poradí obecně a doptá se (bez výmyslu produktů). Mimo-obor →
stručná hláška bez volání LLM.
- Max 5 produktů (tvrdý strop), klidně méně (práh relevance ořeže slabé).
Robustnost
- Ošetření 429 (rate limit TPM): OpenAI i Anthropic klient
max_retries=6
(automatický exponenciální backoff, respektuje retry-after); zmenšen prompt
odpovědi (méně tokenů). Eval harness má pacing.
Výsledky evaluace (55 otázek, LLM-soudce spokojenosti)
| baseline | po iteracích | |
|---|---|---|
| spokojenost | 3.73/5 | 4.20/5 (+12,6 %) |
| slabé odpovědi (≤2/5) | 12 | 1 |
| nejslabší kategorie | Plasty 2.0, Pryže 3.3 | vše ≥ 4.0 |
Nasazení / infrastruktura
- rproxy hotová: http://gms.x86.cz/ jede (NPM custom include
/data/nginx/custom/http.conf → 192.168.254.199:45080). HTTPS = TODO
(Let's Encrypt přes NPM GUI).
- Changelog: konvence verze + datum a čas u každého releasu (časy z gitu).
[0.4.0] – 2026-06-19 15:16 – F3: Logování, admin, multi-turn, evaluace relevance
Added
- Logování diskuzí (
app/chatlog.py, tabulkachat_logs– idempotentní,
přežije re-ingestaci): každý chat se ukládá (zpráva, extrakce, výsledky, odpověď,
časy, ok/error, session_id).
- Admin rozhraní
static/admin.htmlna/admin+ endpointy/api/logs,
/api/logs/{id} (chráněné DEBUG_PASSWORD): procházení diskuzí, detail,
filtr podle session, „Kopírovat odkaz na log", deep-linky.
- Paměť diskuze (multi-turn): server kumuluje kontext podle
session_id,
LLM extrakce dostává historii → zákazník může postupně upřesňovat (5× pokračovat).
- on_topic gate: LLM rozhodne, zda je dotaz z oboru; mimo-oborové (notebook,
banán, auto) → vrací NIC (radši nic než nerelevantní).
- Laditelný práh relevance
SEARCH_MIN_SIM(env, default 0.40 – kalibrováno:
relevantní ~0.43+, nesmysly ~0.30–0.39).
scripts/eval.py– evaluační harness: 21 simulovaných diskuzí (laická i přesná
zadání, multi-turn), LLM-soudce spokojenosti.
- Odpověď obohacena o technický detail (materiál/teplota/použití z
body_text),
výpis až 5 produktů s odkazy; default limit 5.
Opraveno (přes iterace eval harness)
- Fulltext fallback se NEspouští, když sémantika nic relevantního nenašla
(dřív vracel keyword balast).
- Sémantika dostává PŘIROZENOU frázi (např. „hadice na naftu"), ne holá klíčová
slova (ta embedovala špatně) → opravena regrese vágních dotazů.
- OpenAI/Claude/Gemini extrakce: pole
on_topic, multi-turn historie.
Výsledky evaluace (21 diskuzí, LLM-soudce)
| baseline | po iteracích | |
|---|---|---|
| správné chování | 18/21 (86 %) | 21/21 (100 %) |
| spokojenost (relevantní) | 3.56/5 | 3.72/5 |
| spokojenost (celkem) | 3.48/5 | 3.71/5 |
- Strop spokojenosti drží chybějící ceny (soudce opakovaně „cena chybí")
– přijdou z plné DB.
Analýza PDF (závěr)
- Datasheety lze extrahovat (pypdf), ale jsou často v IT/EN, dokumenty jsou
smíšené (i .jpg, právní prohlášení). body_text už pokrývá podstatu.
- Plošná ingestace PDF se nedoporučuje (okrajový přínos, velká práce/OCR).
Selektivně jen pro produkty s prázdným popisem. Větší přínos = ceny + kategorie.
[0.3.0] – 2026-06-19 14:31 – F2: Sémantika, relevance, ověřeno v Dockeru
Added
app/embed.py+scripts/embed_products.py– sémantické embeddingy
(OpenAI text-embedding-3-small, 1536 dim) do products.embedding (pgvector),
HNSW index. Pro všech 5 665 produktů spočítáno.
app/search.pypřepsán na sémantické vyhledávání s prahem relevance
(MIN_SIM=0.30) – přirozený dotaz řadí embeddingy, rozměry zůstávají tvrdým
filtrem; fulltext (FTS) je fallback. → vrací jen relevantní výsledky.
- Odkaz na produkt v každém výsledku (
url) na eshopové vyhledávání
https://www.gms.cz/?query=<název>; asistent ho uvádí jako markdown odkaz.
scripts/ask.py– ruční test relevance přes API.
Ověřeno v běžícím Dockeru (PostgreSQL+pgvector)
- Ingestace partnerských feedů do Postgresu: **5 665 produktů, 45 593 SKU,
343 878 hodnot parametrů**.
- Hybrid hledání s přesným filtrem rozměrů (benzín+Ø50, silikon+Ø8, tlak≥40 bar) ✓.
- Cíl „najdi hadici na benzin" → jen relevantní palivové hadice (PETROTEC TX/
NBR/FUB/CLC…, FKM ponorná), žádné navijáky/pistole/požární hadice. ✓
- Chat end-to-end přes OpenAI i Gemini (extrakce → hledání → česká odpověď
s odkazy). Klíče v .env (negitováno).
Opraveno
- Pořadí vazby SQL parametrů v hledání (
%spoziční). - OpenAI extrakce přepnuta na striktní
json_schema(constraints jako pole). - Gemini model
gemini-2.0-flash(404) →gemini-2.5-flash. .dockerignore(image bez.venv/dat); feedy mimo image, kopírují se k ingestaci.
Pozn.
- Přímé URL produktu (slug) nejsou v partnerském feedu – dočasně odkazujeme na
eshopové hledání podle názvu; přesné URL doplníme z plné DB.
- Ceny zatím NULL (Heureka join podle názvu nesedí) – doladí se z plné DB.
[0.2.0] – 2026-06-19 14:00 – F1: Microservice (FastAPI + PostgreSQL + AI)
Rozhodnutí (odsouhlaseno zadavatelem)
- Databáze: PostgreSQL + pgvector (docker-compose, 2 kontejnery).
- AI: přepínatelní poskytovatelé Claude / OpenAI / Gemini (default Claude,
model claude-opus-4-8).
- Port služby 8040; doména gms.x86.cz přes RPROXY na PikoDroidu.
Added
db/schema.sql– PostgreSQL schéma (products/skus/params/param_dict,
pgvector embedding, tsvector fulltext s unaccent). ID jako TEXT
(katalogové kódy vč. SERVICE*, NV260347 – o žádnou položku nepřijdeme).
app/feed.py– čisté parsování partnerských feedů (testovatelné bez DB).scripts/ingest_partner.py– ingestace zbozi.xml + parametry.xml do Postgresu
přes COPY, stavba fulltextu, doplnění cen z Heureka feedu.
app/db.py– psycopg3 connection pool (lazy, app jde importovat i bez DB).app/search.py– hybrid retrieval: fulltext + PŘESNÝ strukturovaný filtr
rozměrů nad parsovanými čísly (na úrovni produktu i SKU).
app/llm.py– přepínatelná AI vrstva (Claude/OpenAI/Gemini), structured
extrakce omezení + generace odpovědi nad dohledanými daty (lazy import SDK).
app/auth.py– zabezpečení API pro e-shop: API klíč + HMAC podpis (+ replay
ochrana), volitelné heslo pro debug UI.
app/server.py– FastAPI:/api/health,/api/params,/api/search
(chráněné), /api/chat (LLM + debug info), debug UI na /.
static/index.html– debug chat rozhraní (česky) s debug panelem
(extrahované filtry, časy, nalezené produkty/SKU/rozměry).
Dockerfile,docker-compose.yml(app + pgvector),.env.example,
scripts/fetch_feeds.sh.
- Rozšířena normalizace o teploty (
-30°C až +100°C→ −30..100 °C). - Testy:
tests/test_feed.py(3) +tests/test_normalize.py(9 vč. teplot) =
12 prochází.
Ověřeno
- Všechny moduly se kompilují; aplikace nastartuje a degraduje elegantně bez DB/
klíčů (smoke test TestClient: /api/health 200, UI na /, čisté 503/502).
- Parsování partnerských feedů ověřeno na reálných datech (5 665 produktů,
45 592 SKU, 343 878 hodnot parametrů, 0 sirotků).
Čeká na zadavatele (blokuje finální ověření)
- Spustit Docker (lokálně chybí daemon) → integrační test Postgres+ingestace+
hybrid search + build image.
- GitHub auth (
gh/token chybí) → push do privátního repapikodrak/gms-asistent. - AI klíče do
.env→ ověření/api/chatend-to-end.
[0.1.0] – 2026-06-19 13:40 – F0: Průzkum a datový základ
Added
- Inicializace projektu, profesionální struktura (
app/,scripts/,data/,
docs/, static/, tests/).
- Stažen pracovní produktový feed
data/feed-raw.xml(Heureka XML, 18,8 MB). app/normalize.py– normalizace rozměrů/parametrů. Klíčový princip
„nesmí odhadovat“: uchovává raw hodnotu, numeriku parsuje jen když je
jednoznačná (rozsahy, české čárky, převod MPa/kPa→bar), jinak ponechá text.
scripts/ingest.py– ingestace feedu do SQLite (products/variants/params
+ FTS5 fulltext bez diakritiky).
tests/test_normalize.py– 9 testů normalizace (vše prochází).docs/DESIGN.md– návrh architektury (hybrid retrieval, AI vrstva,
zabezpečené API, debug UI, nasazení).
Zjištění z dat (ověřeno)
- 11 894 variant / 2 829 produktů / 75 550 parametrů / 74 typů parametrů.
- Varianty se liší hlavně rozměry → nutné přesné strukturované filtrování.
- 82,3 % parametrů bezpečně parsováno na čísla; zbytek (závity, placeholdery)
ponechán jako přesný text.
- Ověřeno: hybrid dotaz „benzín + vnitřní Ø 50 mm“ → 1 přesná shoda.
Partnerské feedy (přidáno, bohatší zdroj)
- Staženy
data/zbozi.xml(22 MB) adata/parametry.xml(27 MB). - zbozi.xml: 5 665 produktů, 45 594 objednacích kódů (SKU), dlouhé technické
popisy, 9 324 PDF datasheetů, 12 481 obrázků, vazby skupin a souvisejících.
- parametry.xml: slovník 1 638 parametrů + hodnoty pro 51 259 položek
(produkty i SKU), ø 6,7 parametru/položku.
- Ověřena vazba:
parametryid ↔ produkt/SKU zzbozi= 100 % shoda, 0 sirotků. - Závěr: partnerské feedy budou primární zdroj (F1), Heureka feed záloha + ceny.
Infrastruktura (poznámky)
- Cílová doména: gms.x86.cz na PikoDroidu (DNS hotovo). Nutno přidat záznam
na RPROXY → HTTPS do Docker kontejneru.
- Deploy a privátní GitHub repo dle vzoru
pikodrak-game-marshal-spy.