feat(api): add INCI tool-calling with normalized tool traces
Enable on-demand INCI retrieval in /routines/suggest through Gemini function calling so detailed ingredient data is fetched only when needed. Persist and normalize tool_trace data in AI logs to make function-call behavior directly inspectable via /ai-logs endpoints.
This commit is contained in:
parent
c0eeb0425d
commit
cfd2485b7e
8 changed files with 455 additions and 29 deletions
|
|
@ -1,26 +1,28 @@
|
|||
from datetime import date, timedelta
|
||||
import uuid
|
||||
from datetime import date, timedelta
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
from innercontext.api.routines import (
|
||||
_contains_minoxidil_text,
|
||||
_is_minoxidil_product,
|
||||
_ev,
|
||||
_build_skin_context,
|
||||
_build_grooming_context,
|
||||
_build_recent_history,
|
||||
_build_products_context,
|
||||
_build_objectives_context,
|
||||
_build_day_context,
|
||||
_build_grooming_context,
|
||||
_build_inci_tool_handler,
|
||||
_build_objectives_context,
|
||||
_build_products_context,
|
||||
_build_recent_history,
|
||||
_build_skin_context,
|
||||
_contains_minoxidil_text,
|
||||
_ev,
|
||||
_get_available_products,
|
||||
_is_minoxidil_product,
|
||||
)
|
||||
from innercontext.models import (
|
||||
Product,
|
||||
SkinConditionSnapshot,
|
||||
GroomingSchedule,
|
||||
Product,
|
||||
ProductInventory,
|
||||
Routine,
|
||||
RoutineStep,
|
||||
ProductInventory,
|
||||
SkinConditionSnapshot,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -242,3 +244,95 @@ def test_build_day_context():
|
|||
assert _build_day_context(None) == ""
|
||||
assert "Leaving home: yes" in _build_day_context(True)
|
||||
assert "Leaving home: no" in _build_day_context(False)
|
||||
|
||||
|
||||
def test_get_available_products_respects_filters(session: Session):
|
||||
regular_med = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="Tretinoin",
|
||||
category="serum",
|
||||
is_medication=True,
|
||||
brand="Test",
|
||||
recommended_time="pm",
|
||||
leave_on=True,
|
||||
product_effect_profile={},
|
||||
)
|
||||
minoxidil_med = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="Minoxidil 5%",
|
||||
category="serum",
|
||||
is_medication=True,
|
||||
brand="Test",
|
||||
recommended_time="both",
|
||||
leave_on=True,
|
||||
product_effect_profile={},
|
||||
)
|
||||
am_product = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="AM SPF",
|
||||
category="spf",
|
||||
brand="Test",
|
||||
recommended_time="am",
|
||||
leave_on=True,
|
||||
product_effect_profile={},
|
||||
)
|
||||
pm_product = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="PM Cream",
|
||||
category="moisturizer",
|
||||
brand="Test",
|
||||
recommended_time="pm",
|
||||
leave_on=True,
|
||||
product_effect_profile={},
|
||||
)
|
||||
session.add_all([regular_med, minoxidil_med, am_product, pm_product])
|
||||
session.commit()
|
||||
|
||||
am_available = _get_available_products(session, time_filter="am")
|
||||
am_names = {p.name for p in am_available}
|
||||
assert "Tretinoin" not in am_names
|
||||
assert "Minoxidil 5%" in am_names
|
||||
assert "AM SPF" in am_names
|
||||
assert "PM Cream" not in am_names
|
||||
|
||||
|
||||
def test_build_inci_tool_handler_returns_only_available_ids(session: Session):
|
||||
available = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="Available",
|
||||
category="serum",
|
||||
brand="Test",
|
||||
recommended_time="both",
|
||||
leave_on=True,
|
||||
inci=["Water", "Niacinamide"],
|
||||
product_effect_profile={},
|
||||
)
|
||||
unavailable = Product(
|
||||
id=uuid.uuid4(),
|
||||
name="Unavailable",
|
||||
category="serum",
|
||||
brand="Test",
|
||||
recommended_time="both",
|
||||
leave_on=True,
|
||||
inci=["Water", "Retinol"],
|
||||
product_effect_profile={},
|
||||
)
|
||||
|
||||
handler = _build_inci_tool_handler([available])
|
||||
payload = handler(
|
||||
{
|
||||
"product_ids": [
|
||||
str(available.id),
|
||||
str(unavailable.id),
|
||||
str(available.id),
|
||||
123,
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
assert "products" in payload
|
||||
products = payload["products"]
|
||||
assert len(products) == 1
|
||||
assert products[0]["id"] == str(available.id)
|
||||
assert products[0]["name"] == "Available"
|
||||
assert products[0]["inci"] == ["Water", "Niacinamide"]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue