Remara / app.py
axcoder
parenthesis fix
b0126d5
Raw
History Blame Contribute Delete
5.19 kB
import os
import json
import httpx
import gradio as gr
# ---- Configure via Hugging Face Spaces → Settings → Repository secrets ----
# REQUIRED:
# LETTA_BASE_URL e.g. https://cloud.letta.ai/api/v1 (or your tenant base that ends with /v1 or /api/v1)
# LETTA_API_KEY your Letta Cloud API key
# OPTIONAL:
# LETTA_AGENT_ID default agent id (defaults to "default")
# LETTA_MODEL e.g. "openai/gpt-4-nano" or "openai/gpt-4o-mini" (optional; agent may decide if omitted)
# LETTA_SESSION_ID a stable id for memory (optional; you can also expose a textbox)
LETTA_BASE_URL = (os.getenv("LETTA_BASE_URL"))
LETTA_API_KEY = (os.getenv("LETTA_API_KEY"))
LETTA_AGENT_ID = (os.getenv("LETTA_AGENT_ID", "default"))
# LETTA_MODEL = (os.getenv("LETTA_MODEL")
DEFAULT_SESSION = (os.getenv("LETTA_SESSION_ID", ""))
def _config_error() -> str | None:
if not LETTA_BASE_URL:
return "Missing LETTA_BASE_URL (e.g., https://cloud.letta.ai/api/v1)"
if not LETTA_BASE_URL.startswith("https://"):
return "LETTA_BASE_URL must start with https://"
if not LETTA_API_KEY:
return "Missing LETTA_API_KEY"
return None
def _stream_letta(messages, max_tokens, temperature, top_p, session_id: str):
"""
Streams tokens from Letta Cloud (OpenAI-compatible SSE).
"""
headers = {
"Authorization": f"Bearer {LETTA_API_KEY}",
"Content-Type": "application/json",
}
# Minimal OpenAI-compatible payload with Letta agent hints
payload = {
"messages": messages, # [{"role": "...", "content": "..."}]
"stream": True,
"max_tokens": int(max_tokens),
"temperature": float(temperature),
"top_p": float(top_p),
# Letta-specific context (pass via 'extra' to avoid strict OpenAI schema issues on some stacks)
"extra": {
"agent_id": LETTA_AGENT_ID,
"session_id": session_id or DEFAULT_SESSION
}
}
# If you want to pin a specific OpenAI-backed model, set LETTA_MODEL
if LETTA_MODEL:
payload["model"] = LETTA_MODEL
endpoint = f"{LETTA_BASE_URL}/chat/completions" # base must already include /v1 or /api/v1
# Stream Server-Sent Events (SSE) lines
with httpx.stream("POST", endpoint, headers=headers, json=payload, timeout=120) as resp:
if resp.status_code >= 400:
# Surface backend error in the UI
yield f"Letta error {resp.status_code}: {resp.text}"
return
partial = ""
for raw in resp.iter_lines():
if not raw:
continue
line = raw.decode("utf-8", errors="ignore")
if not line.startswith("data: "):
continue
data = line[6:].strip()
if data == "[DONE]":
break
try:
chunk = json.loads(data)
# OpenAI delta shape
delta = chunk["choices"][0]["delta"]
token = delta.get("content", "")
if token:
partial += token
yield partial
except Exception:
# ignore keepalives or malformed lines
continue
def respond(
message,
history: list[dict[str, str]], # ChatInterface type="messages": [{"role","content"}, ...]
system_message,
max_tokens,
temperature,
top_p,
_hf_token: gr.OAuthToken, # unused; kept to match the default template signature
session_id="",
):
# Validate configuration once per call
err = _config_error()
if err:
yield f"⚠️ Configuration error: {err}"
return
# Build messages for OpenAI-compatible API
msgs = []
if system_message:
msgs.append({"role": "system", "content": system_message})
if history:
msgs.extend(history) # already in [{"role","content"}] format when type="messages"
msgs.append({"role": "user", "content": message or ""})
try:
for chunk in _stream_letta(msgs, max_tokens, temperature, top_p, session_id=session_id or DEFAULT_SESSION):
yield chunk
except httpx.ConnectError as e:
yield f"❌ Network error reaching Letta: {e}"
except Exception as e:
yield f"❌ Unexpected error: {e}"
# ----------------- UI -----------------
chat = gr.ChatInterface(
respond,
type="messages", # history comes in [{"role","content"}] which we forward as-is
additional_inputs=[
gr.Textbox(value="You are a concise, helpful assistant.", label="System message"),
gr.Slider(1, 4096, value=512, step=1, label="Max new tokens"),
gr.Slider(0.0, 2.0, value=0.7, step=0.1, label="Temperature"),
gr.Slider(0.1, 1.0, value=0.95, step=0.05, label="Top-p"),
gr.Textbox(value=DEFAULT_SESSION, label="Session ID (memory)"),
],
)
with gr.Blocks(theme=gr.themes.Soft()) as demo:
with gr.Sidebar():
gr.Markdown("### Letta Cloud Chat")
gr.Markdown("Uses OpenAI-compatible streaming via Letta Cloud.")
chat.render()
if __name__ == "__main__":
demo.queue().launch(server_name="0.0.0.0", server_port=7860)