Skip to content

Commit 1bceae4

Browse files
committed
Integrate Gemini for LinkedIn prompt cache miss
When a LinkedIn URL is not found in cache, call Google Gemini to generate a profile analysis and store the result. Adds GEMINI_API_KEY env check, a default prompt if none supplied, and a model fallback loop that tries several Gemini models until one returns text. Inserts the completion, model, duration and metadata into the prompt table (with search_vector when available), closes DB cursors/connections earlier, and returns the generated completion and record id in the response. Improves error handling and updates response meta messages accordingly.
1 parent c5da6b1 commit 1bceae4

1 file changed

Lines changed: 108 additions & 5 deletions

File tree

app/api/prompt/linkedin.py

Lines changed: 108 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import os
2+
13
from fastapi import APIRouter, Depends, HTTPException
24

35
from app.utils.api_key_auth import get_api_key
@@ -9,11 +11,23 @@
911

1012
@router.post("/prompt/linkedin")
1113
def linkedin_prompt_success(payload: dict, api_key: str = Depends(get_api_key)) -> dict:
12-
"""POST /prompt/linkedin: return cached completion for linkedin_url when available."""
14+
"""POST /prompt/linkedin: return cached completion or create a new Gemini analysis."""
1315
linkedin_url = (payload.get("linkedin_url") or payload.get("linkedinUrl") or "").strip()
1416
if not linkedin_url:
1517
raise HTTPException(status_code=400, detail="Missing 'linkedin_url' in request body.")
1618

19+
prompt = (payload.get("prompt") or "").strip()
20+
if not prompt:
21+
prompt = (
22+
"Analyse this LinkedIn profile URL and provide a concise summary of the person, "
23+
"their role, company, seniority, likely responsibilities, and notable signals. "
24+
f"LinkedIn URL: {linkedin_url}"
25+
)
26+
27+
gemini_api_key = os.getenv("GEMINI_API_KEY")
28+
if not gemini_api_key:
29+
raise HTTPException(status_code=500, detail="Gemini API key not configured.")
30+
1731
conn = None
1832
cur = None
1933
try:
@@ -62,6 +76,10 @@ def linkedin_prompt_success(payload: dict, api_key: str = Depends(get_api_key))
6276
row = cur.fetchone()
6377

6478
if row:
79+
cur.close()
80+
conn.close()
81+
cur = None
82+
conn = None
6583
return {
6684
"meta": make_meta("success", "LinkedIn URL already analysed"),
6785
"data": {
@@ -76,20 +94,105 @@ def linkedin_prompt_success(payload: dict, api_key: str = Depends(get_api_key))
7694
},
7795
}
7896

97+
cur.close()
98+
conn.close()
99+
cur = None
100+
conn = None
101+
102+
import json
103+
import logging
104+
import time as time_mod
105+
from app import __version__
106+
from google import genai
107+
108+
client = genai.Client(api_key=gemini_api_key)
109+
model_names = [
110+
"models/gemini-flash-latest",
111+
"models/gemini-1.5-pro",
112+
"models/gemini-1.5-flash",
113+
"models/gemini-1.0-pro",
114+
"models/gemini-pro",
115+
"models/gemini-pro-vision",
116+
]
117+
response = None
118+
completion = None
119+
used_model = None
120+
errors = {}
121+
start_time = time_mod.time()
122+
for model_name in model_names:
123+
try:
124+
response = client.models.generate_content(model=model_name, contents=prompt)
125+
completion = getattr(response, "text", None)
126+
if completion:
127+
used_model = model_name
128+
break
129+
except Exception as model_exc:
130+
errors[model_name] = str(model_exc)
131+
continue
132+
133+
duration = time_mod.time() - start_time
134+
if not completion:
135+
error_details = " | ".join([f"{name}: {message}" for name, message in errors.items()])
136+
raise Exception(
137+
"No available Gemini model succeeded for generate_content with your API key. "
138+
f"Details: {error_details}"
139+
)
140+
141+
record_id = None
142+
record_data = {
143+
"version": __version__,
144+
"linkedin_url": linkedin_url,
145+
}
146+
try:
147+
conn = get_db_connection_direct()
148+
cur = conn.cursor()
149+
data_blob = json.dumps(record_data)
150+
if has_search_vector:
151+
cur.execute(
152+
"""
153+
INSERT INTO prompt (prompt, completion, duration, model, data, search_vector)
154+
VALUES (%s, %s, %s, %s, %s, to_tsvector('english', %s || ' ' || %s))
155+
RETURNING id;
156+
""",
157+
(prompt, completion, duration, used_model, data_blob, prompt, completion)
158+
)
159+
else:
160+
cur.execute(
161+
"""
162+
INSERT INTO prompt (prompt, completion, duration, model, data)
163+
VALUES (%s, %s, %s, %s, %s)
164+
RETURNING id;
165+
""",
166+
(prompt, completion, duration, used_model, data_blob)
167+
)
168+
record_id_row = cur.fetchone()
169+
record_id = record_id_row[0] if record_id_row else None
170+
conn.commit()
171+
cur.close()
172+
conn.close()
173+
cur = None
174+
conn = None
175+
except Exception as db_exc:
176+
logging.error(f"Failed to insert prompt record: {db_exc}")
177+
79178
return {
80-
"meta": make_meta("warning", "LinkedIn URL not analysed yet"),
179+
"meta": make_meta("success", f"Gemini completion received from {used_model}"),
81180
"data": {
82181
"cached": False,
182+
"id": record_id,
83183
"linkedin_url": linkedin_url,
84-
"prompt": None,
85-
"completion": None,
184+
"prompt": prompt,
185+
"completion": completion,
186+
"duration": duration,
187+
"model": used_model,
188+
"record_data": record_data,
86189
},
87190
}
88191
except HTTPException:
89192
raise
90193
except Exception as e:
91194
return {
92-
"meta": make_meta("error", f"DB error: {str(e)}"),
195+
"meta": make_meta("error", f"Gemini API error: {str(e)}"),
93196
"data": {},
94197
}
95198
finally:

0 commit comments

Comments
 (0)