NujacsaintS commited on
Commit
76dad5b
·
verified ·
1 Parent(s): 31decff

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +183 -1
app.py CHANGED
@@ -83,4 +83,186 @@ async def call_llm(message: str, context: Dict[str, str] | None = None) -> tuple
83
  text-generation endpoint. If LLM_URL is not set, it falls back to a local echo.
84
  """
85
 
86
- # Fallback so the b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  text-generation endpoint. If LLM_URL is not set, it falls back to a local echo.
84
  """
85
 
86
+ # Fallback so the bridge still works even if you haven't configured the model yet
87
+ if not LLM_URL:
88
+ reply = f"[stub] Echo: {message}"
89
+ tokens_used = len(message)
90
+ return reply, tokens_used
91
+
92
+ # Basic prompt construction. You can get fancier later.
93
+ prompt = message
94
+ if context:
95
+ # Simple way to inject context: a header line then the message.
96
+ ctx_str = "; ".join(f"{k}={v}" for k, v in context.items())
97
+ prompt = f"[context: {ctx_str}]\n\n{message}"
98
+
99
+ headers = {}
100
+ if LLM_KEY:
101
+ # Hugging Face style: "Bearer <token>"
102
+ headers["Authorization"] = f"Bearer {LLM_KEY}"
103
+
104
+ payload = {
105
+ "inputs": prompt,
106
+ # Optional: adjust parameters to your model
107
+ "parameters": {
108
+ "max_new_tokens": 256,
109
+ "temperature": 0.3,
110
+ "do_sample": False,
111
+ }
112
+ }
113
+
114
+ async with httpx.AsyncClient(timeout=60.0) as client:
115
+ resp = await client.post(LLM_URL, headers=headers, json=payload)
116
+ resp.raise_for_status()
117
+ data = resp.json()
118
+
119
+ # Hugging Face text-generation outputs are usually a list of dicts with "generated_text"
120
+ # Adjust here if your model returns a different shape.
121
+ if isinstance(data, list) and data and "generated_text" in data[0]:
122
+ full_text = data[0]["generated_text"]
123
+ # Heuristic: reply is the part after the original prompt
124
+ reply = full_text[len(prompt) :].strip() or full_text.strip()
125
+ else:
126
+ # Fallback: just stringify whatever we got
127
+ reply = str(data)
128
+
129
+ # Approx token count: you can replace this with a real tokenizer later
130
+ tokens_used = len(reply.split())
131
+
132
+ return reply, tokens_used
133
+
134
+
135
+ # -----------------------------
136
+ # API endpoints
137
+ # -----------------------------
138
+
139
+
140
+ @app.post("/v1/chat", response_model=ChatResponse)
141
+ async def chat_endpoint(payload: ChatRequest):
142
+ reply, tokens_used = await call_llm(payload.message, payload.context)
143
+ record_request(payload.session_id, tokens_used)
144
+
145
+ return ChatResponse(
146
+ session_id=payload.session_id,
147
+ reply=reply,
148
+ tokens_used=tokens_used,
149
+ )
150
+
151
+
152
+ @app.get("/health")
153
+ async def health():
154
+ return JSONResponse(
155
+ {
156
+ "status": "ok",
157
+ "total_requests": metrics["total_requests"],
158
+ "total_tokens": metrics["total_tokens"],
159
+ "active_sessions_5m": count_active_sessions(300),
160
+ }
161
+ )
162
+
163
+
164
+ @app.get("/admin", response_class=HTMLResponse)
165
+ async def admin_dashboard():
166
+ active_5m = count_active_sessions(300)
167
+ rows = []
168
+
169
+ for sid, s in metrics["sessions"].items():
170
+ last_seen = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(s["last_seen"]))
171
+ rows.append(
172
+ f"<tr>"
173
+ f"<td>{sid}</td>"
174
+ f"<td>{s['requests']}</td>"
175
+ f"<td>{s['tokens']}</td>"
176
+ f"<td>{last_seen}</td>"
177
+ f"</tr>"
178
+ )
179
+
180
+ rows_html = "\n".join(rows) if rows else "<tr><td colspan='4'>No sessions yet</td></tr>"
181
+
182
+ html = f"""
183
+ <!doctype html>
184
+ <html>
185
+ <head>
186
+ <title>Tessai LLM Admin</title>
187
+ <meta charset="utf-8" />
188
+ <style>
189
+ body {{
190
+ font-family: system-ui, sans-serif;
191
+ margin: 20px;
192
+ }}
193
+ h1, h2 {{
194
+ margin-bottom: 0.2rem;
195
+ }}
196
+ .metrics {{
197
+ display: flex;
198
+ gap: 1.5rem;
199
+ margin-bottom: 1.5rem;
200
+ }}
201
+ .metric-card {{
202
+ padding: 1rem 1.5rem;
203
+ border-radius: 8px;
204
+ border: 1px solid #ddd;
205
+ box-shadow: 0 1px 3px rgba(0,0,0,0.05);
206
+ }}
207
+ table {{
208
+ border-collapse: collapse;
209
+ width: 100%;
210
+ }}
211
+ th, td {{
212
+ border: 1px solid #ddd;
213
+ padding: 8px;
214
+ font-size: 0.9rem;
215
+ }}
216
+ th {{
217
+ background: #f4f4f4;
218
+ text-align: left;
219
+ }}
220
+ </style>
221
+ </head>
222
+ <body>
223
+ <h1>Tessai LLM Bridge</h1>
224
+ <p>Simple meter board for current traffic.</p>
225
+
226
+ <div class="metrics">
227
+ <div class="metric-card">
228
+ <h2>Total requests</h2>
229
+ <p>{metrics["total_requests"]}</p>
230
+ </div>
231
+ <div class="metric-card">
232
+ <h2>Total tokens (approx)</h2>
233
+ <p>{metrics["total_tokens"]}</p>
234
+ </div>
235
+ <div class="metric-card">
236
+ <h2>Active sessions (last 5 min)</h2>
237
+ <p>{active_5m}</p>
238
+ </div>
239
+ </div>
240
+
241
+ <h2>Sessions</h2>
242
+ <table>
243
+ <thead>
244
+ <tr>
245
+ <th>Session ID</th>
246
+ <th>Requests</th>
247
+ <th>Tokens</th>
248
+ <th>Last seen</th>
249
+ </tr>
250
+ </thead>
251
+ <tbody>
252
+ {rows_html}
253
+ </tbody>
254
+ </table>
255
+ </body>
256
+ </html>
257
+ """
258
+ return HTMLResponse(content=html)
259
+
260
+
261
+ # -----------------------------
262
+ # Local dev entrypoint
263
+ # -----------------------------
264
+
265
+ if __name__ == "__main__":
266
+ import uvicorn
267
+
268
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)