Skip to content

Commit 97793f0

Browse files
committed
Enhance RLM Playground with FastAPI integration and new dependencies
- Added FastAPI server for RLM playground, enabling web-based completions. - Introduced Pydantic models for API requests and responses. - Updated `pyproject.toml` to include new dependencies: FastAPI, Uvicorn, and others. - Modified example quickstart to use the new FastAPI environment. - Created a user interface for the playground with React components for form submission and result display. - Implemented CORS middleware for API accessibility from different origins.
1 parent 40f6a92 commit 97793f0

10 files changed

Lines changed: 818 additions & 4 deletions

File tree

‎examples/quickstart.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@
1212
rlm = RLM(
1313
backend="openai", # or "portkey", etc.
1414
backend_kwargs={
15-
"model_name": "gpt-5-nano",
15+
"model_name": "openai/gpt-4.1-mini",
1616
"api_key": os.getenv("OPENAI_API_KEY"),
1717
},
18-
environment="local",
18+
environment="modal",
1919
environment_kwargs={},
2020
max_depth=1,
2121
logger=logger,

‎playground/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
"""RLM Playground - FastAPI server for running RLM completions via web interface."""
2+

‎playground/models.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""Pydantic models for RLM playground API requests and responses."""
2+
3+
from typing import Any, Literal
4+
5+
from pydantic import BaseModel, Field
6+
7+
ClientBackend = Literal["openai", "portkey", "openrouter", "vllm", "litellm", "anthropic"]
8+
EnvironmentType = Literal["local", "prime", "modal"]
9+
10+
11+
class RunRequest(BaseModel):
12+
"""Request model for running RLM completion."""
13+
14+
prompt: str | dict[str, Any] = Field(..., description="Main prompt/context for the RLM")
15+
root_prompt: str | None = Field(None, description="Optional root prompt visible to the root LM")
16+
backend: ClientBackend = Field("openai", description="LM provider backend")
17+
backend_kwargs: dict[str, Any] = Field(
18+
default_factory=dict,
19+
description="Backend-specific configuration (model_name, api_key, etc.)",
20+
)
21+
environment: EnvironmentType = Field("local", description="Execution environment type")
22+
environment_kwargs: dict[str, Any] = Field(
23+
default_factory=dict, description="Environment-specific configuration"
24+
)
25+
max_iterations: int = Field(30, ge=1, le=100, description="Maximum number of RLM iterations")
26+
max_depth: int = Field(
27+
1, ge=0, le=1, description="Maximum recursion depth (currently only 0 or 1)"
28+
)
29+
other_backends: list[ClientBackend] | None = Field(
30+
None, description="Additional backends for sub-calls"
31+
)
32+
other_backend_kwargs: list[dict[str, Any]] | None = Field(
33+
None, description="Configuration for additional backends"
34+
)
35+
enable_logging: bool = Field(False, description="Whether to save logs to file")
36+
37+
38+
class RunResponse(BaseModel):
39+
"""Response model for RLM completion result."""
40+
41+
success: bool = Field(..., description="Whether the completion succeeded")
42+
response: str | None = Field(None, description="Final answer from RLM")
43+
root_model: str | None = Field(None, description="Model name used")
44+
execution_time: float | None = Field(None, description="Total execution time in seconds")
45+
usage_summary: dict[str, Any] | None = Field(None, description="Token usage summary per model")
46+
error: str | None = Field(None, description="Error message if completion failed")

‎playground/run.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
"""CLI entry point for running the RLM playground FastAPI server."""
2+
3+
import sys
4+
from pathlib import Path
5+
6+
# Add project root to Python path
7+
project_root = Path(__file__).parent.parent
8+
if str(project_root) not in sys.path:
9+
sys.path.insert(0, str(project_root))
10+
11+
import uvicorn
12+
13+
if __name__ == "__main__":
14+
uvicorn.run(
15+
"playground.server:app",
16+
host="0.0.0.0",
17+
port=8000,
18+
reload=True,
19+
)

‎playground/server.py‎

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""FastAPI server for RLM playground."""
2+
3+
import os
4+
5+
from dotenv import load_dotenv
6+
from fastapi import FastAPI
7+
from fastapi.middleware.cors import CORSMiddleware
8+
9+
from rlm import RLM
10+
from rlm.logger import RLMLogger
11+
12+
from playground.models import RunRequest, RunResponse
13+
14+
# Load environment variables
15+
load_dotenv()
16+
17+
# Create FastAPI app
18+
app = FastAPI(
19+
title="RLM Playground API",
20+
description="API for running RLM completions via web interface",
21+
version="0.1.0",
22+
)
23+
24+
# Configure CORS
25+
app.add_middleware(
26+
CORSMiddleware,
27+
allow_origins=[
28+
"http://localhost:3000", # Next.js dev server
29+
"http://localhost:3001", # Alternative Next.js port
30+
"http://127.0.0.1:3000",
31+
"http://127.0.0.1:3001",
32+
],
33+
allow_credentials=True,
34+
allow_methods=["*"],
35+
allow_headers=["*"],
36+
)
37+
38+
39+
@app.get("/")
40+
def root():
41+
"""Health check endpoint."""
42+
return {"status": "ok", "message": "RLM Playground API"}
43+
44+
45+
@app.post("/api/run", response_model=RunResponse)
46+
def run_rlm(request: RunRequest) -> RunResponse:
47+
"""
48+
Run an RLM completion with the provided configuration.
49+
50+
This endpoint creates an RLM instance, runs a completion, and returns the result.
51+
"""
52+
try:
53+
# Setup logger if enabled
54+
logger = None
55+
if request.enable_logging:
56+
log_dir = os.getenv("RLM_LOG_DIR", "./logs")
57+
logger = RLMLogger(log_dir=log_dir)
58+
59+
# Create RLM instance
60+
rlm = RLM(
61+
backend=request.backend,
62+
backend_kwargs=request.backend_kwargs or {},
63+
environment=request.environment,
64+
environment_kwargs=request.environment_kwargs or {},
65+
max_depth=request.max_depth,
66+
max_iterations=request.max_iterations,
67+
other_backends=request.other_backends,
68+
other_backend_kwargs=request.other_backend_kwargs,
69+
logger=logger,
70+
verbose=False, # Disable console output in API
71+
)
72+
73+
# Run completion
74+
result = rlm.completion(
75+
prompt=request.prompt,
76+
root_prompt=request.root_prompt,
77+
)
78+
79+
# Extract response - handle both string and tuple (type, content) formats
80+
response_text = result.response
81+
if isinstance(response_text, tuple):
82+
# If it's a tuple from find_final_answer, extract the content
83+
# Format: (type, content) where type is "FINAL" or "FINAL_VAR"
84+
answer_type, answer_content = response_text
85+
if answer_type == "FINAL":
86+
# For FINAL, use the content directly
87+
response_text = answer_content.strip().strip('"').strip("'")
88+
elif answer_type == "FINAL_VAR":
89+
# For FINAL_VAR, the content is the variable name
90+
# We can't look it up here since we don't have access to the environment
91+
# So we'll return a message indicating the variable name
92+
variable_name = answer_content.strip().strip('"').strip("'")
93+
response_text = f"[FINAL_VAR: {variable_name}]"
94+
else:
95+
# Fallback: just use the content
96+
response_text = (
97+
answer_content if isinstance(answer_content, str) else str(answer_content)
98+
)
99+
100+
# Convert result to response
101+
return RunResponse(
102+
success=True,
103+
response=str(response_text) if response_text else None,
104+
root_model=result.root_model,
105+
execution_time=result.execution_time,
106+
usage_summary=result.usage_summary.to_dict(),
107+
error=None,
108+
)
109+
110+
except Exception as e:
111+
# Return error response
112+
return RunResponse(
113+
success=False,
114+
response=None,
115+
root_model=None,
116+
execution_time=None,
117+
usage_summary=None,
118+
error=str(e),
119+
)

‎pyproject.toml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ dependencies = [
1919

2020
[project.optional-dependencies]
2121
modal = ["modal>=0.73.0", "dill>=0.3.7"]
22+
playground = ["fastapi>=0.115.0", "uvicorn[standard]>=0.32.0", "pydantic>=2.0.0"]
2223

2324
[build-system]
2425
requires = ["setuptools>=61.0"]
2526
build-backend = "setuptools.build_meta"
2627

2728
[tool.setuptools.packages.find]
28-
include = ["rlm", "rlm.*"]
29+
include = ["rlm", "rlm.*", "playground"]
2930

3031
[dependency-groups]
3132
dev = [

0 commit comments

Comments
 (0)