Spaces:
Sleeping
Sleeping
| import base64 | |
| import datetime | |
| import os | |
| from io import BytesIO | |
| from huggingface_hub import InferenceClient | |
| from PIL import Image | |
| ADVISOR_MODEL_ID = os.getenv("ADVISOR_MODEL_ID", "Qwen/Qwen2.5-Coder-3B-Instruct") | |
| ADVISOR_PROVIDER = os.getenv("ADVISOR_PROVIDER", "nscale") | |
| _client = None | |
| def _get_client() -> InferenceClient: | |
| global _client | |
| if _client is None: | |
| _client = InferenceClient(model=ADVISOR_MODEL_ID, provider=ADVISOR_PROVIDER, token=os.getenv("HF_TOKEN")) | |
| return _client | |
| def _watering_status(last_watered: str | None) -> str: | |
| if not last_watered: | |
| return "Never watered yet (no record)." | |
| days_since = (datetime.date.today() - datetime.date.fromisoformat(last_watered)).days | |
| if days_since <= 0: | |
| return f"Watered today ({last_watered})." | |
| if days_since == 1: | |
| return f"Last watered yesterday ({last_watered})." | |
| return f"Last watered {days_since} days ago (on {last_watered})." | |
| def _build_system_prompt( | |
| plant_info: dict, | |
| plant_name: str | None = None, | |
| genus: str | None = None, | |
| last_watered: str | None = None, | |
| neighbors: list[dict] | None = None, | |
| ) -> str: | |
| name = plant_name or genus or "this plant" | |
| neighbor_line = "" | |
| if neighbors: | |
| names = ", ".join(f"{n['name']} ({n['genus']})" for n in neighbors) | |
| neighbor_line = f"- Plants growing right next to it in the garden: {names}\n" | |
| return ( | |
| "You are an expert gardening assistant with deep knowledge of houseplant " | |
| "and garden plant care. Be practical, encouraging, and specific.\n\n" | |
| f"The user is asking about their plant: {name} (genus: {genus}). " | |
| "Known care profile for this plant:\n" | |
| f"- Sunlight: {plant_info.get('sunlight')}\n" | |
| f"- Soil: {plant_info.get('soil')}\n" | |
| f"- Watering frequency: every {plant_info.get('watering_frequency_days')} days\n" | |
| f"- Fertilization: {plant_info.get('fertilization_type')}\n" | |
| f"- Watering status: {_watering_status(last_watered)}\n" | |
| f"{neighbor_line}\n" | |
| "Always factor the watering status into your answer: if it is overdue " | |
| "compared to the recommended frequency, say so and recommend watering; " | |
| "if it was watered recently, take that into account (don't tell the " | |
| "user to water again right away) and consider overwatering as a " | |
| "possible cause if the question describes a problem.\n\n" | |
| "Use this profile as context, but also draw on your general gardening " | |
| "knowledge for issues it doesn't cover (pests, diseases, yellowing " | |
| "leaves, repotting, etc.). If nearby plants are listed, factor in " | |
| "companion-planting effects (competition for light, water or nutrients, " | |
| "shared pests/diseases, or beneficial pairings) when relevant to the " | |
| "question. Give a precise, actionable, gardening-advice " | |
| "focused answer and never recommend dangerous or toxic substances. " | |
| "Answer in 2-4 sentences, in the same language as the question." | |
| ) | |
| def ask_about_plant( | |
| question: str, | |
| plant_info: dict, | |
| plant_name: str | None = None, | |
| genus: str | None = None, | |
| last_watered: str | None = None, | |
| neighbors: list[dict] | None = None, | |
| ) -> str: | |
| """Ask the advisor a question about a specific plant, grounded in its care data.""" | |
| try: | |
| completion = _get_client().chat_completion( | |
| messages=[ | |
| {"role": "system", "content": _build_system_prompt(plant_info, plant_name, genus, last_watered, neighbors)}, | |
| {"role": "user", "content": question}, | |
| ], | |
| max_tokens=300, | |
| ) | |
| return completion.choices[0].message.content | |
| except Exception as e: | |
| # TEMP debug: surface the real HF Inference error in the Space logs | |
| print(f"[advisor] HF Inference error: {e!r}") | |
| return "Sorry, the assistant is unavailable right now — please try again later." | |
| def diagnose_plant_health( | |
| image: Image.Image, | |
| plant_name: str | None = None, | |
| genus: str | None = None, | |
| ) -> str: | |
| """Ask a vision-language model to assess a plant's health from a photo.""" | |
| name = plant_name or genus or "this plant" | |
| buf = BytesIO() | |
| image.convert("RGB").save(buf, format="JPEG") | |
| b64 = base64.b64encode(buf.getvalue()).decode() | |
| prompt = ( | |
| f"This is a photo of a houseplant named '{name}' (genus: {genus}). " | |
| "Look closely at its leaves, stems and soil for signs of trouble: " | |
| "yellowing or browning leaves, wilting, spots, pests, mold, or " | |
| "dry/overwatered soil. Start your reply with exactly one status word " | |
| "on its own — 'Healthy', 'Needs attention', or 'Sick' — then a short " | |
| "explanation (1-3 sentences) of what you see and what to do about it." | |
| ) | |
| try: | |
| completion = _get_client().chat_completion( | |
| messages=[{ | |
| "role": "user", | |
| "content": [ | |
| {"type": "text", "text": prompt}, | |
| {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{b64}"}}, | |
| ], | |
| }], | |
| max_tokens=250, | |
| ) | |
| return completion.choices[0].message.content.strip() | |
| except Exception as e: | |
| print(f"[advisor] HF Inference health-check error: {e!r}") | |
| return "Sorry, the health check is unavailable right now — please try again later." | |