11import os
2+ import hashlib
23from fastapi import APIRouter , HTTPException , Depends
34from app .utils .make_meta import make_meta
45from app .utils .db import get_db_connection_direct
@@ -41,15 +42,84 @@ def get_prompt_table_metadata(api_key: str = Depends(get_api_key)) -> dict:
4142
4243@router .post ("/prompt" )
4344def llm_post (payload : dict ) -> dict :
44- """POST /prompt: send prompt to Gemini, returns completion google-genai SDK ."""
45- prompt = payload .get ("prompt" )
45+ """POST /prompt: send prompt to Gemini with DB-backed caching ."""
46+ prompt = ( payload .get ("prompt" ) or "" ). strip ( )
4647 if not prompt :
4748 raise HTTPException (status_code = 400 , detail = "Missing 'prompt' in request body." )
49+
4850 api_key = os .getenv ("GEMINI_API_KEY" )
4951 if not api_key :
5052 raise HTTPException (status_code = 500 , detail = "Gemini API key not configured." )
53+
54+ prompt_hash = hashlib .sha256 (prompt .encode ("utf-8" )).hexdigest ()
55+ conn = None
56+ cur = None
5157 import logging
5258 try :
59+ conn = get_db_connection_direct ()
60+ cur = conn .cursor ()
61+ cur .execute (
62+ """
63+ SELECT EXISTS (
64+ SELECT 1
65+ FROM information_schema.columns
66+ WHERE table_schema = 'public'
67+ AND table_name = 'prompt'
68+ AND column_name = 'search_vector'
69+ );
70+ """
71+ )
72+ exists_row = cur .fetchone ()
73+ has_search_vector = bool (exists_row and exists_row [0 ])
74+
75+ # Fast/safe cache hit: exact prompt hash or exact prompt text.
76+ cur .execute (
77+ """
78+ SELECT id, prompt, completion, time, model
79+ FROM prompt
80+ WHERE COALESCE(data->>'prompt_hash', '') = %s OR prompt = %s
81+ ORDER BY id DESC
82+ LIMIT 1;
83+ """ ,
84+ (prompt_hash , prompt ),
85+ )
86+ row = cur .fetchone ()
87+
88+ # Fallback cache hit when tsvector exists and query terms match strongly.
89+ if not row and has_search_vector :
90+ cur .execute (
91+ """
92+ SELECT id, prompt, completion, time, model,
93+ ts_rank_cd(search_vector, plainto_tsquery('english', %s)) AS rank
94+ FROM prompt
95+ WHERE search_vector @@ plainto_tsquery('english', %s)
96+ ORDER BY rank DESC, id DESC
97+ LIMIT 1;
98+ """ ,
99+ (prompt , prompt ),
100+ )
101+ rank_row = cur .fetchone ()
102+ if rank_row and rank_row [5 ] is not None and float (rank_row [5 ]) >= 0.35 :
103+ row = rank_row [:5 ]
104+
105+ cur .close ()
106+ conn .close ()
107+ cur = None
108+ conn = None
109+
110+ if row :
111+ return {
112+ "meta" : make_meta ("success" , "Prompt returned from cache" ),
113+ "data" : {
114+ "cached" : True ,
115+ "prompt_id" : row [0 ],
116+ "prompt" : row [1 ],
117+ "completion" : row [2 ],
118+ "time" : row [3 ].isoformat () if row [3 ] else None ,
119+ "model" : row [4 ],
120+ },
121+ }
122+
53123 from google import genai
54124 import time as time_mod
55125 client = genai .Client (api_key = api_key )
@@ -85,17 +155,31 @@ def llm_post(payload: dict) -> dict:
85155 try :
86156 import json
87157 from app import __version__
88- data_blob = json .dumps ({"version" : __version__ })
158+ record_data = {
159+ "version" : __version__ ,
160+ "prompt_hash" : prompt_hash ,
161+ }
162+ data_blob = json .dumps (record_data )
89163 conn = get_db_connection_direct ()
90164 cur = conn .cursor ()
91- cur .execute (
92- """
93- INSERT INTO prompt (prompt, completion, duration, data, model)
94- VALUES (%s, %s, %s, %s, %s)
95- RETURNING id;
96- """ ,
97- (prompt , completion , duration , data_blob , used_model )
98- )
165+ if has_search_vector :
166+ cur .execute (
167+ """
168+ INSERT INTO prompt (prompt, completion, duration, data, model, search_vector)
169+ VALUES (%s, %s, %s, %s, %s, to_tsvector('english', %s || ' ' || %s))
170+ RETURNING id;
171+ """ ,
172+ (prompt , completion , duration , data_blob , used_model , prompt , completion )
173+ )
174+ else :
175+ cur .execute (
176+ """
177+ INSERT INTO prompt (prompt, completion, duration, data, model)
178+ VALUES (%s, %s, %s, %s, %s)
179+ RETURNING id;
180+ """ ,
181+ (prompt , completion , duration , data_blob , used_model )
182+ )
99183 record_id_row = cur .fetchone ()
100184 record_id = record_id_row [0 ] if record_id_row else None
101185 conn .commit ()
@@ -105,8 +189,23 @@ def llm_post(payload: dict) -> dict:
105189 # Log DB error but do not fail the API response
106190 logging .error (f"Failed to insert prompt record: { db_exc } " )
107191 meta = make_meta ("success" , f"Gemini completion received from { used_model } " )
108- return {"meta" : meta , "data" : {"id" : record_id , "prompt" : prompt , "completion" : completion }}
192+ return {
193+ "meta" : meta ,
194+ "data" : {
195+ "cached" : False ,
196+ "id" : record_id ,
197+ "prompt" : prompt ,
198+ "completion" : completion ,
199+ "duration" : duration ,
200+ "model" : used_model ,
201+ },
202+ }
109203 except Exception as e :
110204 meta = make_meta ("error" , f"Gemini API error: { str (e )} " )
111205 return {"meta" : meta , "data" : {}}
206+ finally :
207+ if cur :
208+ cur .close ()
209+ if conn :
210+ conn .close ()
112211
0 commit comments