JuanjoSG5
commited on
Commit
·
100ea5d
1
Parent(s):
e0b4034
feat: finally the gradio interface works
Browse files- gradio_interface/app.py +124 -33
gradio_interface/app.py
CHANGED
@@ -7,6 +7,7 @@ from dotenv import load_dotenv
|
|
7 |
import requests
|
8 |
import socket
|
9 |
import logging
|
|
|
10 |
|
11 |
from langchain_openai import ChatOpenAI
|
12 |
from langchain_core.messages import HumanMessage, AIMessage
|
@@ -35,6 +36,37 @@ def test_connectivity(url="https://openrouter.helicone.ai/api/v1"):
|
|
35 |
if not test_connectivity():
|
36 |
logger.warning("No network to OpenRouter; responses may fail.")
|
37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
# Initialize LLM with streaming and retry logic
|
39 |
def init_llm():
|
40 |
if not test_connectivity():
|
@@ -50,57 +82,116 @@ def init_llm():
|
|
50 |
},
|
51 |
)
|
52 |
|
53 |
-
|
|
|
|
|
|
|
|
|
|
|
54 |
|
55 |
# Helpers
|
56 |
def encode_image_to_base64(pil_image):
|
57 |
buffer = BytesIO()
|
58 |
pil_image.save(buffer, format="PNG")
|
59 |
-
return
|
60 |
|
61 |
# Core logic
|
62 |
def generate_response(message, chat_history, image):
|
63 |
-
|
|
|
64 |
for msg in chat_history:
|
65 |
role = msg.get('role')
|
66 |
content = msg.get('content')
|
67 |
if role == 'user':
|
68 |
-
|
69 |
else:
|
70 |
-
|
71 |
|
72 |
-
|
|
|
73 |
|
74 |
-
#
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
79 |
|
80 |
-
|
81 |
-
messages.append(HumanMessage(content=multimodal_content))
|
82 |
|
83 |
try:
|
84 |
-
|
85 |
-
if
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
except Exception as e:
|
102 |
-
|
103 |
-
|
|
|
|
|
|
|
104 |
|
105 |
# Gradio interface
|
106 |
def process_message(message, chat_history, image):
|
@@ -130,4 +221,4 @@ with gr.Blocks() as demo:
|
|
130 |
msg.submit(process_message, [msg, chatbot, image_input], [msg, chatbot])
|
131 |
image_input.change(lambda img: f"Tamaño: {img.size}" if img else "Sin imagen.", [image_input], [info])
|
132 |
|
133 |
-
demo.launch()
|
|
|
7 |
import requests
|
8 |
import socket
|
9 |
import logging
|
10 |
+
import json
|
11 |
|
12 |
from langchain_openai import ChatOpenAI
|
13 |
from langchain_core.messages import HumanMessage, AIMessage
|
|
|
36 |
if not test_connectivity():
|
37 |
logger.warning("No network to OpenRouter; responses may fail.")
|
38 |
|
39 |
+
# Helper to make direct API calls to OpenRouter when LangChain fails
|
40 |
+
def direct_api_call(messages, api_key, base_url):
|
41 |
+
headers = {
|
42 |
+
"Content-Type": "application/json",
|
43 |
+
"Authorization": f"Bearer {api_key}",
|
44 |
+
"HTTP-Referer": "https://your-app-domain.com", # Add your domain
|
45 |
+
"X-Title": "Image Analysis App"
|
46 |
+
}
|
47 |
+
|
48 |
+
if getenv("HELICONE_API_KEY"):
|
49 |
+
headers["Helicone-Auth"] = f"Bearer {getenv('HELICONE_API_KEY')}"
|
50 |
+
|
51 |
+
payload = {
|
52 |
+
"model": "google/gemini-flash-1.5",
|
53 |
+
"messages": messages,
|
54 |
+
"stream": False,
|
55 |
+
}
|
56 |
+
|
57 |
+
try:
|
58 |
+
response = requests.post(
|
59 |
+
f"{base_url}/chat/completions",
|
60 |
+
headers=headers,
|
61 |
+
json=payload,
|
62 |
+
timeout=30
|
63 |
+
)
|
64 |
+
response.raise_for_status()
|
65 |
+
return response.json()["choices"][0]["message"]["content"]
|
66 |
+
except Exception as e:
|
67 |
+
logger.error(f"Direct API call failed: {e}")
|
68 |
+
return f"Error: {str(e)}"
|
69 |
+
|
70 |
# Initialize LLM with streaming and retry logic
|
71 |
def init_llm():
|
72 |
if not test_connectivity():
|
|
|
82 |
},
|
83 |
)
|
84 |
|
85 |
+
# Try to initialize LLM but handle failures gracefully
|
86 |
+
try:
|
87 |
+
llm = init_llm()
|
88 |
+
except Exception as e:
|
89 |
+
logger.error(f"Failed to initialize LLM: {e}")
|
90 |
+
llm = None
|
91 |
|
92 |
# Helpers
|
93 |
def encode_image_to_base64(pil_image):
|
94 |
buffer = BytesIO()
|
95 |
pil_image.save(buffer, format="PNG")
|
96 |
+
return base64.b64encode(buffer.getvalue()).decode()
|
97 |
|
98 |
# Core logic
|
99 |
def generate_response(message, chat_history, image):
|
100 |
+
# Convert chat history to standard format
|
101 |
+
formatted_history = []
|
102 |
for msg in chat_history:
|
103 |
role = msg.get('role')
|
104 |
content = msg.get('content')
|
105 |
if role == 'user':
|
106 |
+
formatted_history.append({"role": "user", "content": content})
|
107 |
else:
|
108 |
+
formatted_history.append({"role": "assistant", "content": content})
|
109 |
|
110 |
+
# Prepare system message
|
111 |
+
system_msg = {"role": "system", "content": "You are an expert image analysis assistant. Answer succinctly."}
|
112 |
|
113 |
+
# Prepare the latest message with image if provided
|
114 |
+
if image:
|
115 |
+
base64_image = encode_image_to_base64(image)
|
116 |
+
|
117 |
+
# Format for direct API call (OpenRouter/OpenAI format)
|
118 |
+
api_messages = [system_msg] + formatted_history + [{
|
119 |
+
"role": "user",
|
120 |
+
"content": [
|
121 |
+
{"type": "text", "text": message},
|
122 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}}
|
123 |
+
]
|
124 |
+
}]
|
125 |
+
|
126 |
+
# For LangChain format
|
127 |
+
content_for_langchain = [
|
128 |
+
{"type": "text", "text": message},
|
129 |
+
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{base64_image}"}}
|
130 |
+
]
|
131 |
+
else:
|
132 |
+
api_messages = [system_msg] + formatted_history + [{"role": "user", "content": message}]
|
133 |
+
content_for_langchain = message
|
134 |
+
|
135 |
+
# Build LangChain messages
|
136 |
+
lc_messages = [HumanMessage(content="You are an expert image analysis assistant. Answer succinctly.")]
|
137 |
+
for msg in chat_history:
|
138 |
+
role = msg.get('role')
|
139 |
+
content = msg.get('content')
|
140 |
+
if role == 'user':
|
141 |
+
lc_messages.append(HumanMessage(content=content))
|
142 |
+
else:
|
143 |
+
lc_messages.append(AIMessage(content=content))
|
144 |
|
145 |
+
lc_messages.append(HumanMessage(content=content_for_langchain))
|
|
|
146 |
|
147 |
try:
|
148 |
+
# First try with LangChain
|
149 |
+
if llm:
|
150 |
+
try:
|
151 |
+
# Try streaming first
|
152 |
+
try:
|
153 |
+
stream_iter = llm.stream(lc_messages)
|
154 |
+
partial = ""
|
155 |
+
for chunk in stream_iter:
|
156 |
+
if chunk is None:
|
157 |
+
continue
|
158 |
+
content = getattr(chunk, 'content', None)
|
159 |
+
if content is None:
|
160 |
+
continue
|
161 |
+
partial += content
|
162 |
+
yield partial
|
163 |
+
|
164 |
+
# If we got this far, streaming worked
|
165 |
+
return
|
166 |
+
except Exception as e:
|
167 |
+
logger.warning(f"Streaming failed: {e}. Falling back to non-streaming mode")
|
168 |
+
|
169 |
+
# Try non-streaming
|
170 |
+
try:
|
171 |
+
response = llm.invoke(lc_messages)
|
172 |
+
yield response.content
|
173 |
+
return
|
174 |
+
except Exception as e:
|
175 |
+
logger.warning(f"Non-streaming LangChain invoke failed: {e}")
|
176 |
+
raise e
|
177 |
+
except Exception as e:
|
178 |
+
logger.warning(f"LangChain approach failed: {e}. Trying direct API call")
|
179 |
+
|
180 |
+
# Fallback to direct API call
|
181 |
+
logger.info("Using direct API call as fallback")
|
182 |
+
response_text = direct_api_call(
|
183 |
+
api_messages,
|
184 |
+
getenv("OPENROUTER_API_KEY"),
|
185 |
+
getenv("OPENROUTER_BASE_URL")
|
186 |
+
)
|
187 |
+
yield response_text
|
188 |
+
|
189 |
except Exception as e:
|
190 |
+
import traceback
|
191 |
+
error_trace = traceback.format_exc()
|
192 |
+
logger.exception(f"All approaches failed during response generation: {e}")
|
193 |
+
logger.error(f"Full traceback: {error_trace}")
|
194 |
+
yield f"⚠️ Error al generar respuesta: {str(e)}. Intenta más tarde."
|
195 |
|
196 |
# Gradio interface
|
197 |
def process_message(message, chat_history, image):
|
|
|
221 |
msg.submit(process_message, [msg, chatbot, image_input], [msg, chatbot])
|
222 |
image_input.change(lambda img: f"Tamaño: {img.size}" if img else "Sin imagen.", [image_input], [info])
|
223 |
|
224 |
+
demo.launch()
|