| import os |
| from dotenv import load_dotenv |
| from langchain_anthropic import ChatAnthropic |
| from langchain_core.prompts import ChatPromptTemplate |
| from langchain_core.tools import tool |
| from tools import get_weather, get_air_quality, get_uv_index, search_skincare_evidence |
|
|
| load_dotenv() |
|
|
|
|
| @tool |
| def weather_tool(city: str) -> dict: |
| """Get current weather for a city including temperature, humidity, wind speed, and conditions.""" |
| return get_weather(city) |
|
|
|
|
| @tool |
| def air_quality_tool(lat: float, lon: float) -> dict: |
| """Get air quality data for a location using latitude and longitude. Returns AQI index and PM2.5.""" |
| return get_air_quality(lat, lon) |
|
|
|
|
| @tool |
| def uv_tool(lat: float, lon: float) -> dict: |
| """Get the current UV index for a location using latitude and longitude.""" |
| return get_uv_index(lat, lon) |
|
|
| @tool |
| def evidence_search_tool(query: str) -> list: |
| """Search the web for scientific evidence to answer a skincare myth, claim, or science question. Returns sources with titles, URLs, and snippets to cite.""" |
| return search_skincare_evidence(query) |
|
|
| tools = [weather_tool, air_quality_tool, uv_tool, evidence_search_tool] |
|
|
| llm = ChatAnthropic( |
| model="claude-sonnet-4-6", |
| api_key=os.getenv("ANTHROPIC_API_KEY"), |
| ).bind_tools(tools) |
|
|
| SYSTEM_PROMPT = """You are Sērēnum, a skincare advisor that translates weather data into personalised skin advice. |
| |
| When a user gives you a location: |
| 1. Call the weather tool to get current conditions |
| 2. Use the latitude and longitude from weather to call the air quality tool |
| 3. Use the same coordinates to call the UV index tool |
| 4. Combine all three data points into a concise skin brief |
| |
| Start your response directly with the skin brief — no preamble like "Here's your brief" or "I have everything I need". Just begin with the formatted output. |
| |
| Your skin brief should cover: |
| - SPF recommendation based on UV index |
| - Hydration advice based on humidity and temperature |
| - Barrier protection advice based on AQI and PM2.5 |
| - Any active ingredients to avoid or embrace today |
| |
| Keep advice practical, warm, and under 150 words. Never claim to diagnose skin conditions. |
| |
| If the user asks a skincare myth or science question, you MUST call the evidence_search_tool BEFORE answering — even if you already know the answer. This is required, not optional: the tool provides current sources you will cite. After calling it, answer based on what you find, and ALWAYS end your response with a "Sources:" line listing the URLs returned by the tool.""" |
|
|
|
|
| def run_agent(user_input: str) -> str: |
| """Run the Sērēnum agent with a user message and return the skin brief.""" |
| messages = [ |
| {"role": "system", "content": SYSTEM_PROMPT}, |
| {"role": "user", "content": user_input}, |
| ] |
|
|
| |
| while True: |
| response = llm.invoke(messages) |
| messages.append(response) |
| |
| |
| if not response.tool_calls: |
| return response.content |
|
|
| |
| for tool_call in response.tool_calls: |
| tool_name = tool_call["name"] |
| tool_args = tool_call["args"] |
|
|
| |
| tool_fn = next(t for t in tools if t.name == tool_name) |
| tool_result = tool_fn.invoke(tool_args) |
|
|
| messages.append({ |
| "role": "tool", |
| "tool_call_id": tool_call["id"], |
| "content": str(tool_result), |
| }) |
|
|
|
|
| if __name__ == "__main__": |
| result = run_agent("Does vitamin C cancel out when used with niacinamide?") |
| print("\n--- SKIN BRIEF ---") |
| print(result) |
|
|