File size: 6,017 Bytes
4b7573c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
"""
models/gemini_client.py
-----------------------
Gemini API client for Notiflow.

Serves two distinct roles:

  1. FALLBACK REASONER β€” used by ModelRouter when Nova is unavailable.
     generate(prompt) returns a plain-text response that must match
     Nova's JSON output schema exactly (enforced by the prompt).

  2. NOTIFICATION GENERATOR β€” used by notification_generator.py to
     produce realistic Hinglish business notifications for demo mode.
     generate_notifications(n) returns a list of notification dicts.

Configuration
-------------
  GEMINI_API_KEY β€” from .env / environment variable

Dependencies
------------
  pip install google-generativeai
"""

from __future__ import annotations

import json
import logging
import re
from typing import Any

from app.config import GEMINI_API_KEY

logger = logging.getLogger(__name__)

# Gemini model to use β€” flash is fast and sufficient for this use case
_GEMINI_MODEL = "gemini-2.5-flash"

_client = None  # lazy singleton


def _get_client():
    """Return a cached Gemini GenerativeModel instance."""
    global _client
    if _client is not None:
        return _client

    if not GEMINI_API_KEY:
        raise RuntimeError(
            "GEMINI_API_KEY is not set. "
            "Add it to your .env file: GEMINI_API_KEY=your_key_here"
        )

    try:
        import google.generativeai as genai
        genai.configure(api_key=GEMINI_API_KEY)
        _client = genai.GenerativeModel(_GEMINI_MODEL)
        logger.info("Gemini client initialised (model=%s)", _GEMINI_MODEL)
        return _client
    except ImportError:
        raise RuntimeError(
            "google-generativeai is not installed. "
            "Run: pip install google-generativeai"
        )
    except Exception as exc:
        raise RuntimeError(f"Failed to initialise Gemini client: {exc}") from exc


def _strip_fences(text: str) -> str:
    """Strip markdown code fences from model output."""
    return re.sub(r"```(?:json)?|```", "", text).strip()


# ---------------------------------------------------------------------------
# Public API β€” Role 1: Fallback reasoner for ModelRouter
# ---------------------------------------------------------------------------

def generate(prompt: str) -> str:
    """
    Send a prompt to Gemini and return the raw text response.

    Used by ModelRouter as a Nova fallback. The prompt already instructs
    the model to return JSON β€” this function returns the raw string and
    lets the caller parse it.

    Args:
        prompt: Fully rendered prompt (same prompt sent to Nova).

    Returns:
        Raw text response from Gemini (may contain JSON).

    Raises:
        RuntimeError: If the API call fails or client cannot be initialised.
    """
    client = _get_client()
    try:
        response = client.generate_content(prompt)
        raw = response.text or ""
        logger.debug("Gemini raw response: %r", raw[:200])
        return _strip_fences(raw)
    except Exception as exc:
        logger.error("Gemini generate() failed: %s", exc)
        raise RuntimeError(f"Gemini API error: {exc}") from exc


# ---------------------------------------------------------------------------
# Public API β€” Role 2: Notification generator
# ---------------------------------------------------------------------------

_NOTIFICATION_PROMPT = """
You are simulating incoming business notifications for a small business in India.

Generate {n} realistic business notifications in Hinglish (Hindi + English mix).

Each notification must come from one of these sources:
- whatsapp   (informal text from customers or suppliers)
- payment    (UPI payment confirmation message)
- amazon     (marketplace order or return notification)
- return     (customer return or exchange request)

Each notification must represent ONE of these business events:
- An order for a product (item name + quantity)
- A payment received (person name + amount)
- A credit/udhar request
- A return or exchange request
- A preparation/packing request

Rules:
- Use natural Hinglish phrasing. Not too formal.
- Vary sources and event types.
- Include real-sounding names (Rahul, Priya, Suresh, Amit, etc.)
- Include real product names (kurti, aata, daal, saree, etc.)
- Do NOT include any explanation or preamble.
- Return ONLY a valid JSON array, nothing else.

Output format (JSON array only, no markdown):
[
  {{"source": "whatsapp", "message": "bhaiya 3 kurti bhej dena"}},
  {{"source": "payment", "message": "Rahul ne 15000 bheja UPI se"}}
]
"""


def generate_notifications(n: int = 5) -> list[dict[str, str]]:
    """
    Generate n realistic Hinglish business notifications via Gemini.

    Args:
        n: Number of notifications to generate (default 5).

    Returns:
        List of dicts with keys "source" and "message".
        Returns an empty list if generation fails (non-fatal).

    Example:
        >>> generate_notifications(3)
        [
            {"source": "whatsapp", "message": "bhaiya 3 kurti bhej dena"},
            {"source": "payment",  "message": "Rahul ne 15000 bheja"},
            {"source": "return",   "message": "size chota hai exchange karna hai"},
        ]
    """
    prompt = _NOTIFICATION_PROMPT.format(n=n)
    try:
        raw  = generate(prompt)
        data = json.loads(raw)
        if not isinstance(data, list):
            logger.warning("Gemini notification response was not a list: %s", type(data))
            return []
        # Validate each entry has required keys
        valid = [
            item for item in data
            if isinstance(item, dict)
            and "source"  in item
            and "message" in item
        ]
        logger.info("Generated %d notifications via Gemini", len(valid))
        return valid
    except json.JSONDecodeError as exc:
        logger.warning("Could not parse Gemini notification output as JSON: %s", exc)
        return []
    except Exception as exc:
        logger.warning("generate_notifications failed: %s", exc)
        return []