Po co kolejne narzędzie do INCI?
Na rynku istnieje kilkanaście narzędzi do analizy składu kosmetyków — INCI Beauty, INCIDecoder, CosDNA, SkinSort i polskie Wizaż. Każde z nich robi mniej więcej to samo: bierze listę składników, koloruje je czerwono-zielono, dodaje notki o alergiach. To dobre dla konsumenta sprawdzającego kosmetyk w drogerii. Ale to za mało dla firmy, która chce produkować własne kosmetyki.
Nasz klient — średniej wielkości producent kosmetyków pielęgnacyjnych — miał inne potrzeby. Brief, który dostaliśmy na pierwszym spotkaniu, brzmiał mniej więcej tak:
„Potrzebujemy narzędzia, które robi trzy rzeczy. Po pierwsze, czyta dowolny skład INCI i mówi nam: co tu jest, jak to działa razem, czego unikać. Po drugie, robi to samo dla naszych konkurentów — chcemy mieć przeszukiwalną bazę 'kto co ma w składzie'. Po trzecie i najważniejsze — chcemy móc opisać produkt w jednym zdaniu i dostać propozycję formuły. Realistyczną. Taką, którą da się oddać do laboratorium i nie być zaśmianym."
To trzy bardzo różne zadania techniczne. Pierwsze to klasyczne NLP plus regulacje. Drugie — wyszukiwanie semantyczne na dużym zbiorze. Trzecie — to już praktycznie kompozycja generatywna z ograniczeniami, problem, którego nie da się rozwiązać samym ChatGPT, bo halucynuje stężenia i miesza składniki, które się zwyczajnie nie lubią chemicznie.
Dlaczego nie wystarczy „spytać ChatGPT"
Gdy poprosiliśmy GPT-4 i Claude o ułożenie formuły kremu nawilżającego ze stężeniami, oba modele wymyślały liczby. Glicerolu 25% (w kremie to byłaby pasta), niacynamidu 20% (toleruje się max 10%), kwas hialuronowy 5% (w kosmetykach używa się 0.1–2%). Modele uniwersalne nie znają realnych norm formulacji — uczyły się na ogólnym internecie, nie na recepturach.
Architektura systemu — trzy warstwy, dwa modele, jeden czat
Po dwóch tygodniach researchu (rozmowy z technologami w laboratorium klienta, audyt istniejących źródeł INCI, weryfikacja regulacji unijnych — Rozporządzenie 1223/2009) zaproponowaliśmy architekturę trójwarstwową:
Kluczowa decyzja architektoniczna: nie jeden model do wszystkiego, tylko trzy wyspecjalizowane warstwy, które rozmawiają ze sobą po API. Każda robi to, w czym jest najmocniejsza:
- RAG nie halucynuje, bo zwraca prawdziwe dokumenty z bazy (a nie „wymyśloną wiedzę")
- Własny model domenowy zna stężenia i interakcje, bo był trenowany na recepturach, a nie na Wikipedii
- Claude jako planner świetnie rozkłada problem na kroki i pisze odpowiedź w naturalnym języku
Warstwa 1 — Knowledge Graph 30 tys. składników INCI
Standardowy RAG (czyli „weź pytanie, znajdź podobne fragmenty tekstu, wstaw do promptu") w naszym przypadku nie wystarczał. Składniki kosmetyczne mają relacje, których nie da się wyłapać przez podobieństwo wektorowe:
- Antagonizmy — witamina C w niskim pH + niacynamid w wysokim pH = degradacja obu
- Synergie — kwas hialuronowy + niacynamid + ceramidy = wzmocnione działanie nawilżające
- Substytucje — czego można użyć zamiast parabenu, gdy klient chce „clean beauty"
- Funkcje — które składniki są emulgatorami, które konserwantami, które substancjami aktywnymi
- Regulacje — które są dozwolone w UE, w jakich stężeniach, dla jakich typów produktów
Zbudowaliśmy więc knowledge graph — relacyjną bazę grafową w Neo4j, gdzie każdy z 30 tys. składników to węzeł z atrybutami (CAS number, EINECS, INCI name, EU Allergen status, funkcja CosIng, typowe stężenie), a krawędzie reprezentują relacje typu „antagonizuje z", „synergizuje z", „zastępuje", „wymaga w pH".
Skąd dane do grafu?
Część z otwartych źródeł (CosIng — European Commission Cosmetic Ingredient Database, COSMOS-standard, Personal Care Products Council INCI Dictionary). Część została wyekstrahowana z literatury (PubMed, Journal of Cosmetic Science) przez własny pipeline NLP — model klasyfikował zdania typu „X enhances the effect of Y" i automatycznie tworzył kandydatów na krawędzie, które potem walidował technolog klienta. Ten etap zajął najdłużej — 6 tygodni pracy.
Wyszukiwanie semantyczne na 350 tys. produktów
Drugi komponent warstwy 1 to baza 350 tysięcy kosmetyków dostępnych na rynku (zebrana z wielu źródeł — drogerie online, baza CPNP, sklepy producentów). Każdy produkt ma swój pełny skład INCI, kategorię (krem, szampon, serum...), zakres cenowy, segment rynkowy.
Tę bazę przeszukujemy hybrydowo:
Pipeline wyszukiwania konkurencyjnego
Warstwa 2 — własny model do receptur (Llama 3.1 70B + LoRA)
Centrum systemu i najtrudniejsza technicznie część. Tutaj musieliśmy nauczyć model rozumieć formułę kosmetyku tak, jak rozumie ją technolog — z fazami, kolejnością dodawania, ograniczeniami fizykochemicznymi.
Wybór modelu bazowego
Rozważaliśmy kilka opcji. Decyzja oparła się na trzech kryteriach: jakość rozumienia chemii, wielkość kontekstu (formuły są długie — typowa receptura ma 30-60 składników z opisami) i możliwość deploymentu on-premise (klient nie chciał wysyłać własnych receptur na serwery OpenAI).
| Model | Plusy | Minusy | Decyzja |
|---|---|---|---|
| GPT-4 / Claude API | Najlepsza jakość bazowa | Dane klienta opuszczają jego infrastrukturę. Brak fine-tuningu (Claude) lub drogi (GPT-4) | Odrzucone |
| Mistral 7B | Lekki, open source, łatwy fine-tuning | Za mały na złożone wnioskowanie chemiczne | Odrzucone |
| Llama 3.1 8B | Dobry stosunek jakość/koszt, mieści się na jednym RTX 5090 | Dla generacji formuł za mały — gubił relacje na długich kontekstach | Testowane, odrzucone |
| Llama 3.1 70B | Świetna jakość, 128k kontekst, fine-tunowalny LoRA | Wymaga 2× A100 80GB lub 4× RTX 5090 do inference | ✓ Wybrane |
Dataset treningowy — co fine-tunujemy
Najtrudniejsza część — zbudowanie dobrego datasetu. Łącznie ~50 000 par (opis intencji → formuła). Skąd:
- ~12k autentycznych receptur klienta (pseudonimizowane — usunięte nazwy własne, dostawcy, marże), z historycznymi notatkami technologa
- ~18k formuł z literatury (Cosmetic Ingredient Review monographs, podręczniki formulacji typu Barel-Paye-Maibach, otwarte publikacje SCC)
- ~10k formuł generycznych (kosmetyki, do których skład jest jawny przez CPNP — wsteczne odtworzenie pełnej receptury)
- ~10k zsyntetyzowanych przykładów przez GPT-4 i Claude (najmniej wartościowe, ale uzupełniają corner cases — np. nietypowe briefy)
Każdy przykład w datasecie miał format:
{
"intent": "Krem nawilżający na dzień, skóra mieszana 25+, bez SLS, bez parabenów, segment masstige (40-60 PLN), tekstura lekka, szybko się wchłaniający",
"constraints": {
"exclude": ["sodium lauryl sulfate", "parabens"],
"price_segment": "masstige",
"texture": "light",
"skin_type": "combination"
},
"formula": {
"phase_a_water": [
{"ingredient": "Aqua", "percent": 68.5, "function": "solvent"},
{"ingredient": "Glycerin", "percent": 4.0, "function": "humectant"},
{"ingredient": "Pentylene Glycol", "percent": 3.0, "function": "humectant_preservative_booster"}
],
"phase_b_oil": [
{"ingredient": "Caprylic/Capric Triglyceride", "percent": 8.0, "function": "emollient"},
{"ingredient": "Squalane", "percent": 3.0, "function": "emollient"},
{"ingredient": "Cetearyl Alcohol", "percent": 2.5, "function": "co_emulsifier"},
{"ingredient": "Glyceryl Stearate Citrate", "percent": 2.0, "function": "emulsifier"}
],
"phase_c_active": [
{"ingredient": "Niacinamide", "percent": 4.0, "function": "active"},
{"ingredient": "Panthenol", "percent": 1.0, "function": "active"}
],
"phase_d_preservation": [
{"ingredient": "Phenoxyethanol", "percent": 0.9, "function": "preservative"},
{"ingredient": "Ethylhexylglycerin", "percent": 0.3, "function": "preservative_booster"}
],
"ph_target": "5.0-5.5",
"manufacturing_notes": "Faza A + B w 75°C, homogenizacja 5 min, schłodzenie do 40°C, dodanie C i D, mieszanie 10 min"
},
"rationale": "Krótkie uzasadnienie wyborów — dlaczego Glyceryl Stearate Citrate (clean beauty friendly), dlaczego niacynamid 4% (typowe stężenie wystarczające bez ryzyka podrażnienia), dlaczego ten zestaw konserwantów (kompatybilny z pH 5.0-5.5)..."
}
Zwróćcie uwagę na pole rationale — to ono robi największą różnicę. Model uczył się nie tylko co położyć w formule, ale i dlaczego. To znacząco poprawiło jakość generacji — model przestał działać jak ślepa autokompletacja, a zaczął jak początkujący technolog.
Fine-tuning — LoRA na 70B
Pełny fine-tuning modelu 70B to projekt na klaster GPU za miliony złotych. My zrobiliśmy LoRA — niskorangową adaptację, która zmienia tylko ułamek wag, ale wystarczająco, żeby model zaczął „myśleć po kosmetycznemu".
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
from trl import SFTTrainer
base = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-70B-Instruct",
torch_dtype=torch.bfloat16,
device_map="auto",
quantization_config=BitsAndBytesConfig(load_in_4bit=True)
)
lora_config = LoraConfig(
r=64, # rank
lora_alpha=128,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
model = get_peft_model(base, lora_config)
# Trenowalnych parametrów: ~430M z 70B (0.6%)
trainer = SFTTrainer(
model=model,
train_dataset=cosmetics_dataset,
max_seq_length=8192,
args=TrainingArguments(
per_device_train_batch_size=1,
gradient_accumulation_steps=16,
learning_rate=1e-4,
num_train_epochs=3,
bf16=True,
gradient_checkpointing=True,
),
)
trainer.train()
Trening na 50k przykładach zajął ~5 dni na klastrze 4× RTX 5090 (po stronie SULI, klient dostał gotowe wagi adaptera). Łącznie ~430 mln trenowalnych parametrów z 70 miliardów — 0.6% modelu, ale to wystarczyło.
Walidacja jakości
Po fine-tuningu zrobiliśmy blind test: dwóch technologów (jeden z klienta, jeden zewnętrzny konsultant) dostało po 50 formuł — 25 wygenerowanych przez nasz model, 25 z prawdziwych receptur klienta z innych projektów. Zadanie: ocenić, czy formuła jest „realna" i jakie ma poziom (1-5). Model zdobył średnio 3.8/5, prawdziwe receptury 4.3/5. Co ważniejsze, technolodzy nie potrafili odróżnić wygenerowanych od prawdziwych w 64% przypadków.
Warstwa 3 — orkiestracja przez Claude jako planner
Mimo że własny model świetnie generuje formuły, nie chcemy, żeby on rozmawiał z użytkownikiem. Dlaczego? Bo użytkownik pyta nieprecyzyjnie. Bo trzeba dopytać o brakujące parametry. Bo trzeba sprawdzić regulacje przed generacją. Bo trzeba ładnie wyjaśnić wynik.
Tutaj wchodzi Claude Sonnet 4.6 jako planner. Otrzymuje dostęp do trzech narzędzi przez MCP:
// 1. Przeszukiwanie konkurencji
{
name: "search_market",
description: "Wyszukuje istniejące produkty na rynku po opisie semantycznym i filtrach",
inputSchema: {
query: string,
filters: { category?, price_range?, must_contain?, must_not_contain? },
top_k: number
}
}
// 2. Sprawdzenie składnika / interakcji w knowledge graph
{
name: "analyze_ingredient",
description: "Zwraca pełny profil składnika: funkcja, regulacje, typowe stężenia, antagonizmy, synergie",
inputSchema: { inci_name: string, ph_context?: number }
}
// 3. Generowanie formuły (własny model)
{
name: "generate_formula",
description: "Generuje kompletną formułę kosmetyku na podstawie briefu",
inputSchema: {
intent: string,
constraints: { exclude?, include?, price_segment?, texture?, skin_type? },
product_category: enum
}
}
// 4. Walidacja regulacyjna
{
name: "validate_eu_compliance",
description: "Sprawdza formułę pod kątem Rozp. 1223/2009 — stężenia, alergeny, restrykcje wiekowe",
inputSchema: { formula: Formula, intended_use: string }
}
Claude w roli planera dostaje system prompt mniej więcej taki:
„Jesteś asystentem technologa kosmetyków. Twoja rola to: rozumieć intencję użytkownika, dopytywać o brakujące parametry, używać dostępnych narzędzi do generowania i walidacji formuł, prezentować wyniki w sposób zrozumiały. NIE zgaduj stężeń. NIE wymyślaj interakcji składników. Zawsze używajanalyze_ingredientigenerate_formulazamiast generować z pamięci. Po wygenerowaniu formuły, zawsze przepuszczaj ją przezvalidate_eu_complianceprzed prezentacją."
Jak to wygląda od strony użytkownika
Pełna ścieżka rzeczywistej rozmowy (przykład syntetyczny, ale wzorowany na realnych sesjach):
Przykładowa sesja — projektowanie nowego serum
search_market → dostaje 15 podobnych produktów na rynku. Analizuje typowe stężenia retinolu w tym segmencie (0.1-0.3%) i ceny (35-65 PLN).
analyze_ingredient("Retinal") + analyze_ingredient("Bisabolol") → potwierdza kompatybilność, sprawdza pH. Wywołuje generate_formula z pełnym briefem.
validate_eu_compliance → wszystko zgodne z 1223/2009.
Całość zajmuje ~2-3 sekundy generacji plus czas Claude'a na ułożenie odpowiedzi w naturalnym języku.
Hybryda z istniejącymi narzędziami branżowymi
Ważny punkt — klient nie chciał zastępować istniejących narzędzi branżowych, tylko zintegrować się z nimi. System rozmawia po API z:
- SpecPRO / Formpak (jeśli klient kupi licencję) — eksport formuły bezpośrednio do systemu zarządzania recepturami
- EWG Skin Deep API — pobieranie aktualnych ocen bezpieczeństwa składników
- CosIng database (Komisja Europejska) — synchronizacja statusu regulacyjnego składników
- System ERP klienta (Comarch XL) — sprawdzenie, czy składnik jest w ich katalogu dostawców i jaka jest cena zakupu
Integracja z ERP — gamechanger
Najciekawszy efekt: gdy model proponuje formułę, system od razu sprawdza w ERP, ile kosztuje wyprodukowanie 1 kg tej formuły (po cenach zakupu surowców z katalogu klienta). Technolog widzi nie tylko formułę, ale i koszt jednostkowy oraz marżę przy zakładanej cenie półkowej. To zmienia projektowanie z hobby w decyzję biznesową w 30 sekund.
Stack i infrastruktura
Stack techniczny
- Llama 3.1 70B + LoRA
- Claude Sonnet 4.6 (planner)
- vLLM (serving 70B)
- Neo4j (knowledge graph)
- PostgreSQL + pgvector
- BAAI/bge-large-en-v1.5 (embeddings)
- BAAI/bge-reranker-large
- FastAPI
- Redis (cache)
- MCP server (TypeScript)
- Docker Compose
- 4× RTX 5090 (on-premise klient)
Wymagania sprzętowe — on-premise
Cały system działa u klienta, w jego serwerowni. To było kluczowe — receptury kosmetyków to know-how, którego nikt nie chce wysyłać do chmury Amazonów. Konfiguracja:
- Maszyna inference — 4× RTX 5090 (32 GB VRAM każda), 256 GB RAM, EPYC 7763. Obsługuje Llama 70B przez vLLM z paralelizmem tensorowym 4-way
- Maszyna baz danych — 64 GB RAM, 4 TB NVMe RAID10, na niej Neo4j + PostgreSQL + pgvector + Redis
- Maszyna aplikacyjna — niewielka (32 GB RAM), FastAPI + Node.js MCP server + reverse proxy
- Backup — codzienne snapshoty wektorów i grafu na zewnętrzny storage
Claude jako planner jedyny komponent, który wychodzi na zewnątrz (Anthropic API) — ale Claude nigdy nie dostaje pełnych receptur klienta, tylko zapytania użytkownika i wyniki narzędzi. Dane wrażliwe nie opuszczają infrastruktury.
Czego nauczyliśmy się przy tym projekcie
1. Nie wszystko musi być w jednym modelu
Modny trend „one model to rule them all" (GPT-4 do wszystkiego) jest praktyczny tylko dla prototypów. W produkcji opłaca się rozdzielać: jeden model do generacji, drugi do orkiestracji, baza wiedzy jako trzeci komponent. Każdy element można niezależnie ulepszać i wymieniać.
2. Dataset jest ważniejszy od modelu
Gdybyśmy fine-tunowali Mistrala 7B na naszym datasecie, dostalibyśmy gorszy wynik niż surowy GPT-4. Ale fine-tuning Llamy 70B na naszych danych pobił surowego GPT-4. Dane > rozmiar modelu. Sześć tygodni czyszczenia datasetu = sześć tygodni najlepszej inwestycji w cały projekt.
3. Knowledge graph + RAG > sam RAG
Klasyczny RAG (wstaw fragmenty tekstu do promptu) nie radzi sobie z domenami, gdzie liczy się struktura relacji. Kosmetyki to dokładnie taka domena — co z czym się lubi, co antagonizuje, co zastępuje. Graf był dla nas konieczny.
4. Walidacja regulacyjna musi być zautomatyzowana
UE ma kilkadziesiąt regulacji dotyczących kosmetyków. Załączniki II-VI Rozporządzenia 1223/2009 plus poprawki. Człowiek tego nie sprawdzi za każdą generacją. Walidator validate_eu_compliance jest jedną z najważniejszych części systemu i jednocześnie najmniej widoczną.
5. Hybryda z ERP = realna wartość biznesowa
Generowanie ładnych formuł to fajne demo. Generowanie formuł, które od razu mają policzony koszt produkcji i sprawdzoną dostępność u dostawców — to jest narzędzie, które klient włącza codziennie i nie chce już bez niego pracować.
Co się zmieniło u klienta
Zacytuję dyrektora R&D (referencja udostępniona za zgodą, dane finansowe usunięte):
„Wcześniej technolog poświęcał na pierwszą wersję nowej formuły 2-3 dni. Researcz konkurencji, sprawdzenie składników, ułożenie szkieletu receptury, kalkulacja kosztów. Teraz dostaje pierwszą propozycję w minutę, a w 30 minut ma trzy warianty z różnymi profilami cenowymi do dyskusji z marketingiem. Skróciliśmy cykl R&D z 6-8 miesięcy do około 3 miesięcy na nowy produkt. To, czego się nie spodziewałem, to że narzędzie czasem proponuje rozwiązania, na które bym sam nie wpadł — bo widzi 350 tysięcy produktów na rynku jednocześnie, a ja w głowie pamiętam może dwieście."
Czas wdrożenia i koszty
- Tygodnie 1-2: research, audyt potrzeb, wybór architektury
- Tygodnie 3-8: budowa knowledge graph + ETL danych (najdłuższy etap)
- Tygodnie 9-12: przygotowanie datasetu + fine-tuning LoRA
- Tygodnie 13-16: warstwa orkiestracji + integracja z ERP klienta
- Tygodnie 17-18: testy z technologami, iteracje, dotrenowanie
- Tygodnie 19-20: wdrożenie produkcyjne, szkolenia, dokumentacja
Łącznie 20 tygodni od briefu do produkcji. Zespół: 1 ML engineer (full-time), 1 fullstack dev (full-time, ostatnie 8 tygodni), 1 domain expert klienta (part-time przez cały okres), konsultacje technologów (~40h łącznie).
Co dalej
System rozwija się dalej. Na roadmapie:
- Modul stabilności — predykcja stabilności emulsji w czasie (na podstawie składu) bez wykonywania testów przyspieszonego starzenia
- Vision component — analiza zdjęć opakowań konkurencji (Cosmetic Vision Encoder) → automatyczne dodawanie produktów do bazy
- Wersjonowanie formuł — git-like control nad iteracjami receptury, z możliwością cofania zmian i porównywania wariantów
- Integracja z Open Beauty Facts — wzbogacenie bazy produktów o globalne dane konsumenckie
Chcesz coś podobnego u siebie?
Beauty tech to nasza specjalizacja. Ale ta sama architektura — RAG + własny model domenowy + knowledge graph + orkiestracja przez Claude — działa wszędzie tam, gdzie domena wymaga twardej wiedzy faktograficznej i kreatywnej kompozycji.
Porozmawiajmy o Twoim wdrożeniu →