File size: 2,913 Bytes
938949f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
Shared utilities for working with the Google Gemini (genai) client.

Centralises:
- GOOGLE_API_KEY resolution (Streamlit secrets → environment variable).
- genai.Client construction.
- Robust JSON object extraction from model responses.
"""

from __future__ import annotations

import json
import os
import re
from typing import Optional


def get_google_api_key(explicit: Optional[str] = None) -> str:
    """
    Resolve the Google API key used for Gemini.

    Resolution order:
    1. Explicit argument (if non-empty).
    2. Streamlit secrets["GOOGLE_API_KEY"] (if available and non-empty).
    3. Environment variable GOOGLE_API_KEY.

    Raises
    ------
    ValueError
        If no key can be found.
    """
    if explicit:
        return explicit

    # Try Streamlit secrets if available
    try:
        import streamlit as st  # type: ignore

        key = st.secrets.get("GOOGLE_API_KEY", "")
        if key:
            return str(key)
    except Exception:
        pass

    key = os.environ.get("GOOGLE_API_KEY", "").strip()
    if not key:
        raise ValueError(
            "GOOGLE_API_KEY not found. Set it as an environment variable or in "
            "Streamlit secrets."
        )
    return key


def get_genai_client(api_key: Optional[str] = None):
    """
    Construct and return a google.genai.Client using the resolved API key.

    Parameters
    ----------
    api_key : str, optional
        Explicit key to use; falls back to get_google_api_key() when None or empty.
    """
    try:
        from google import genai  # type: ignore
    except ImportError as e:
        raise ImportError(
            "Could not import 'google.genai'. Install the Gemini SDK with:\n"
            "  pip install google-genai\n"
            "Then run Streamlit using the same Python environment (e.g. activate "
            "your venv or conda env before 'streamlit run app.py')."
        ) from e

    key = get_google_API_key_safe(api_key)
    return genai.Client(api_key=key)


def get_google_API_key_safe(explicit: Optional[str] = None) -> str:
    """
    Wrapper for get_google_api_key used internally to avoid circular imports.

    Kept separate so that callers can patch or override in tests if needed.
    """
    return get_google_api_key(explicit)


def extract_json_object(text: str) -> dict:
    """
    Extract a JSON object from raw model text.

    Strips optional markdown ``` fences and returns the first {...} block.

    Raises
    ------
    ValueError
        If no JSON object can be found or parsed.
    """
    # Strip markdown fences like ```json ... ```
    cleaned = re.sub(r"```(?:json)?\s*", "", text).strip().rstrip("`").strip()
    start = cleaned.find("{")
    end = cleaned.rfind("}") + 1
    if start == -1 or end <= start:
        raise ValueError(f"No JSON object found in LLM response:\n{cleaned[:300]}")
    return json.loads(cleaned[start:end])