fix(api): tighten shopping suggestion response rules
Constrain shopping target concerns to SkinConcern enums and add a regression test for invalid values. Simplify the shopping prompt so repurchase suggestions stay practical, use shorter product types, and avoid leaking raw scoring/debug language into user-facing copy.
This commit is contained in:
parent
d91d06455b
commit
e20c18c2ee
2 changed files with 37 additions and 41 deletions
|
|
@ -416,7 +416,7 @@ class ProductSuggestion(PydanticBase):
|
|||
product_type: str
|
||||
priority: Literal["high", "medium", "low"]
|
||||
key_ingredients: list[str]
|
||||
target_concerns: list[str]
|
||||
target_concerns: list[SkinConcern]
|
||||
recommended_time: DayTime
|
||||
frequency: str
|
||||
short_reason: str
|
||||
|
|
@ -440,7 +440,7 @@ class _ProductSuggestionOut(PydanticBase):
|
|||
product_type: str
|
||||
priority: Literal["high", "medium", "low"]
|
||||
key_ingredients: list[str]
|
||||
target_concerns: list[str]
|
||||
target_concerns: list[SkinConcern]
|
||||
recommended_time: DayTime
|
||||
frequency: str
|
||||
short_reason: str
|
||||
|
|
@ -1189,48 +1189,24 @@ def _extract_requested_product_ids(
|
|||
|
||||
_SHOPPING_SYSTEM_PROMPT = """Jesteś asystentem zakupowym w dziedzinie pielęgnacji skóry.
|
||||
|
||||
Twoim zadaniem jest ocenić dwie rzeczy:
|
||||
1. czy w rutynie użytkownika istnieją realne luki produktowe,
|
||||
2. czy któryś z już posiadanych typów produktów warto odkupić teraz z powodu kończącego się zapasu.
|
||||
|
||||
Masz działać konserwatywnie: nie sugeruj zakupu tylko dlatego, że coś mogłoby się przydać.
|
||||
Sugestia ma pojawić się tylko wtedy, gdy istnieje wyraźny powód praktyczny.
|
||||
|
||||
WAŻNE POJĘCIA:
|
||||
- `stock_state` opisuje stan zapasu: `out_of_stock`, `healthy`, `monitor`, `low`, `urgent`
|
||||
- `sealed_backup_count` > 0 zwykle oznacza, że odkup nie jest pilny
|
||||
- `lowest_remaining_level` dotyczy tylko otwartych opakowań i może mieć wartość: `high`, `medium`, `low`, `nearly_empty`
|
||||
- `last_used_on` i `days_since_last_used` pomagają ocenić, czy produkt jest nadal faktycznie używany
|
||||
- `replenishment_score`, `replenishment_priority_hint` i `repurchase_candidate` są backendowym sygnałem pilności odkupu; traktuj je jako mocną wskazówkę
|
||||
- `reason_codes` tłumaczą, dlaczego backend uznał odkup za pilny albo niepilny
|
||||
|
||||
JAK PODEJMOWAĆ DECYZJĘ:
|
||||
0. Sugeruj tylko wtedy, gdy jest realna potrzeba - nie zwracaj stałej liczby produktów
|
||||
1. Najpierw oceń, czy użytkownik ma lukę w rutynie
|
||||
2. Potem oceń, czy któryś istniejący typ produktu jest wart odkupu teraz
|
||||
3. Nie rekomenduj ponownie produktu, który ma zdrowy zapas
|
||||
4. Nie rekomenduj odkupu tylko dlatego, że produkt jest niski, jeśli nie był używany od dawna i nie wygląda na nadal potrzebny
|
||||
5. Dla kategorii podstawowych (`cleanser`, `moisturizer`, `spf`) niski stan i świeże użycie mają większe znaczenie niż dla produktów okazjonalnych
|
||||
6. Dla produktów okazjonalnych (`exfoliant`, `mask`, `spot_treatment`) świeżość użycia jest ważniejsza niż sam niski stan
|
||||
7. Jeśli `sealed_backup_count` > 0, zwykle nie rekomenduj odkupu
|
||||
8. Sugeruj TYLKO typy produktów, NIGDY konkretne marki (np. "Salicylic Acid 2% Masque", nie "La Roche-Posay")
|
||||
9. Bierz pod uwagę aktywne problemy skóry (acne, hyperpigmentacja, aging, etc.)
|
||||
10. Sugeruj realistyczną częstotliwość użycia (dzienna, 2-3x tygodniowo, etc.)
|
||||
11. Zachowaj kolejność warstw: cleanse → toner → serum → moisturizer → SPF
|
||||
12. Jeśli użytkownik ma uszkodzoną barierę, unikaj silnych eksfoliantów i retinoidów
|
||||
13. Zwracaj uwagę na ewentualne konflikty polecanych składników z tymi, które użytkownik już posiada
|
||||
14. Odpowiadaj w języku polskim
|
||||
15. Używaj wyłącznie dozwolonych wartości enumów poniżej - nie twórz synonimów typu "night", "evening" ani "treatment"
|
||||
16. Możesz zwrócić pustą listę suggestions, jeśli nie widzisz realnej potrzeby zakupowej
|
||||
17. Każda sugestia ma mieć charakter decision-support: konkretnie wyjaśnij, dlaczego warto kupić ją teraz, jak wpisuje się w obecną rutynę i jakie są ograniczenia
|
||||
18. `short_reason` ma być krótkim, 1-zdaniowym skrótem decyzji zakupowej
|
||||
19. `reason_to_buy_now` ma być konkretne i praktyczne, bez lania wody
|
||||
20. `reason_not_needed_if_budget_tight` jest opcjonalne - uzupełniaj tylko wtedy, gdy zakup nie jest pilny lub istnieje rozsądny kompromis
|
||||
21. `usage_cautions` ma być krótką listą praktycznych uwag; gdy brak istotnych zastrzeżeń zwróć pustą listę
|
||||
22. `priority` ustawiaj jako: high = wyraźna luka lub pilna potrzeba, medium = sensowne uzupełnienie, low = opcjonalny upgrade
|
||||
Oceń dwie rzeczy: realne luki w rutynie oraz odkupy produktów, które warto uzupełnić teraz.
|
||||
Działaj konserwatywnie: sugeruj tylko wtedy, gdy istnieje wyraźny powód praktyczny.
|
||||
Najpierw rozważ luki w rutynie, potem odkupy.
|
||||
Traktuj `replenishment_score`, `replenishment_priority_hint`, `repurchase_candidate`, `stock_state`, `lowest_remaining_level`, `sealed_backup_count`, `last_used_on` i `days_since_last_used` jako główne sygnały decyzji zakupowej.
|
||||
`sealed_backup_count` odnosi się do zapasu tego produktu lub bardzo zbliżonego typu; inny produkt z tej samej kategorii obniża pilność tylko wtedy, gdy realistycznie pełni podobną funkcję w rutynie.
|
||||
Jeśli zakup nie jest pilny dzięki alternatywie, wyjaśnij, czy chodzi o rzeczywisty zapas tego samego typu produktu, czy o funkcjonalny zamiennik z tej samej kategorii.
|
||||
Jeśli istnieje sealed backup lub bardzo bliski funkcjonalny zamiennik, sugestia zwykle nie powinna mieć `priority=high`, chyba że potrzeba odkupu jest wyraźnie wyjątkowa.
|
||||
`product_type` ma być krótką nazwą typu produktu i opisywać funkcję produktu, a nie opis marketingowy lub pełną specyfikację składu.
|
||||
Przy odkupie możesz odwoływać się do konkretnych już posiadanych produktów, jeśli pomaga to uzasadnić decyzję. Przy lukach w rutynie sugeruj typy produktów, nie marki.
|
||||
Uwzględniaj aktywne problemy skóry, miejsce produktu w rutynie, konflikty składników i bezpieczeństwo przy naruszonej barierze.
|
||||
Pisz po polsku, językiem praktycznym i zakupowym. Unikaj nadmiernie medycznego lub diagnostycznego tonu.
|
||||
Nie używaj w tekstach dla użytkownika surowych sygnałów systemowych ani dosłownych etykiet z warstwy danych, takich jak `id`, `score`, `status low` czy `poziom produktu jest niski`; opisuj naturalnie wniosek, np. jako kończący się zapas, niski zapas lub wysoką pilność odkupu.
|
||||
Możesz zwrócić pustą listę `suggestions`, jeśli nie widzisz realnej potrzeby.
|
||||
`target_concerns` musi używać wyłącznie wartości enumu `SkinConcern` poniżej. `priority` ustawiaj jako: high = wyraźna luka lub pilna potrzeba, medium = sensowne uzupełnienie, low = opcjonalny upgrade.
|
||||
|
||||
DOZWOLONE WARTOŚCI ENUMÓW:
|
||||
- category: "cleanser" | "toner" | "essence" | "serum" | "moisturizer" | "spf" | "mask" | "exfoliant" | "hair_treatment" | "tool" | "spot_treatment" | "oil"
|
||||
- target_concerns: "acne" | "rosacea" | "hyperpigmentation" | "aging" | "dehydration" | "redness" | "damaged_barrier" | "pore_visibility" | "uneven_texture" | "hair_growth" | "sebum_excess"
|
||||
- recommended_time: "am" | "pm" | "both"
|
||||
|
||||
Format odpowiedzi - zwróć wyłącznie JSON zgodny z podanym schematem."""
|
||||
|
|
|
|||
|
|
@ -267,6 +267,26 @@ def test_suggest_shopping_invalid_schema_returns_502(client):
|
|||
assert "suggestions/0/priority" in r.json()["detail"]
|
||||
|
||||
|
||||
def test_suggest_shopping_invalid_target_concern_returns_502(client):
|
||||
with patch(
|
||||
"innercontext.api.products.call_gemini_with_function_tools"
|
||||
) as mock_gemini:
|
||||
mock_response = type(
|
||||
"Response",
|
||||
(),
|
||||
{
|
||||
"text": '{"suggestions": [{"category": "cleanser", "product_type": "cleanser", "priority": "high", "key_ingredients": ["glycerin"], "target_concerns": ["inflammation"], "recommended_time": "am", "frequency": "daily", "short_reason": "Brakuje lagodnego kroku myjacego rano.", "reason_to_buy_now": "Obecnie nie masz delikatnego produktu do porannego oczyszczania i wsparcia bariery.", "fit_with_current_routine": "To domknie podstawowy krok cleanse bez dokladania agresywnych aktywow.", "usage_cautions": []}], "reasoning": "Test shopping"}'
|
||||
},
|
||||
)
|
||||
mock_gemini.return_value = (mock_response, None)
|
||||
|
||||
r = client.post("/products/suggest")
|
||||
|
||||
assert r.status_code == 502
|
||||
assert "LLM returned invalid shopping suggestion schema" in r.json()["detail"]
|
||||
assert "suggestions/0/target_concerns/0" in r.json()["detail"]
|
||||
|
||||
|
||||
def test_shopping_context_medication_skip(session: Session):
|
||||
p = Product(
|
||||
id=uuid.uuid4(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue