diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 00000000..f7cbc91b --- /dev/null +++ b/playground/README.md @@ -0,0 +1,61 @@ +# RLM Playground + +The RLM Playground provides a web interface to interactively run RLM completions, visualize recursive calls, and debug execution across different environments. + +## Architecture + +- **Backend**: FastAPI server (`playground/server.py`) that executes RLM logic. +- **Frontend**: Next.js visualizer (`visualizer/`) with a dedicated playground page. + +## Getting Started + +### 1. Prerequisites + +Ensure you have installed the project dependencies with the `playground` extra: + +```bash +uv pip install -e ".[playground]" +``` + +### 2. Environment Setup + +The playground server uses a `.env` file for configuration. Create one in the root directory: + +```bash +# API Keys for LLM providers +OPENAI_API_KEY=your_key_here +ANTHROPIC_API_KEY=your_key_here + +# Optional: Directory for RLM logs +RLM_LOG_DIR=./logs +``` + +### 3. Running the Backend Server + +Start the FastAPI server. By default, it runs on `http://localhost:8000`. + +```bash +uv run playground/run.py +``` + +The server includes: +- **CORS Middleware**: Pre-configured to allow requests from the visualizer (port 3000). +- **Auto-reload**: Enabled for development. + +### 4. Running the Visualizer Frontend + +Navigate to the visualizer directory and start the Next.js development server: + +```bash +cd visualizer +bun install +bun run dev +``` + +Open [http://localhost:3000/playground](http://localhost:3000/playground) in your browser. + +## Features + +- **Configuration**: Toggle between different backends (OpenAI, Anthropic, Portkey, etc.) and environments (Local, Modal, Docker). +- **Iterative Execution**: Set `max_iterations` and `max_depth` to control the recursion. +- **Usage Tracking**: View real-time token usage and execution time for every model call. diff --git a/playground/__init__.py b/playground/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/playground/models.py b/playground/models.py new file mode 100644 index 00000000..4b6840b1 --- /dev/null +++ b/playground/models.py @@ -0,0 +1,58 @@ +"""Pydantic models for RLM playground API requests and responses.""" + +from typing import Any, Literal + +from pydantic import BaseModel, Field + +ClientBackend = Literal["openai", "portkey", "openrouter", "vllm", "litellm", "anthropic"] +EnvironmentType = Literal["local", "prime", "modal"] + + +class RunRequest(BaseModel): + """Request model for running RLM completion.""" + + prompt: str | dict[str, Any] = Field(..., description="Main prompt/context for the RLM") + root_prompt: str | None = Field(None, description="Optional root prompt visible to the root LM") + backend: ClientBackend = Field("openai", description="LM provider backend") + backend_kwargs: dict[str, Any] = Field( + default_factory=dict, + description="Backend-specific configuration (model_name, api_key, etc.)", + ) + environment: EnvironmentType = Field("local", description="Execution environment type") + environment_kwargs: dict[str, Any] = Field( + default_factory=dict, description="Environment-specific configuration" + ) + max_iterations: int = Field(30, ge=1, le=100, description="Maximum number of RLM iterations") + max_depth: int = Field( + 1, ge=0, le=1, description="Maximum recursion depth (currently only 0 or 1)" + ) + other_backends: list[ClientBackend] | None = Field( + None, description="Additional backends for sub-calls" + ) + other_backend_kwargs: list[dict[str, Any]] | None = Field( + None, description="Configuration for additional backends" + ) + custom_system_prompt: str | None = Field( + None, description="Custom system prompt to override the default" + ) + verbose: bool = Field(False, description="Enable verbose console output") + enable_logging: bool = Field(True, description="Whether to save logs to file") + + +class RunResponse(BaseModel): + """Response model for RLM completion result.""" + + success: bool = Field(..., description="Whether the completion succeeded") + response: str | None = Field(None, description="Final answer from RLM") + root_model: str | None = Field(None, description="Model name used") + execution_time: float | None = Field(None, description="Total execution time in seconds") + usage_summary: dict[str, Any] | None = Field(None, description="Token usage summary per model") + verbose_output: str | None = Field(None, description="Captured verbose console output") + error: str | None = Field(None, description="Error message if completion failed") + + +class StreamEvent(BaseModel): + """Base model for streaming events.""" + + event: str = Field(..., description="Event type: metadata, iteration, complete, error") + data: dict[str, Any] = Field(..., description="Event data payload") diff --git a/playground/run.py b/playground/run.py new file mode 100644 index 00000000..c8e7107a --- /dev/null +++ b/playground/run.py @@ -0,0 +1,20 @@ +"""CLI entry point for running the RLM playground FastAPI server.""" + +import sys +from pathlib import Path + +import uvicorn + +# Add project root to Python path +project_root = Path(__file__).parent.parent +if str(project_root) not in sys.path: + sys.path.insert(0, str(project_root)) + + +if __name__ == "__main__": + uvicorn.run( + "playground.server:app", + host="0.0.0.0", + port=8000, + reload=True, + ) diff --git a/playground/server.py b/playground/server.py new file mode 100644 index 00000000..999c2335 --- /dev/null +++ b/playground/server.py @@ -0,0 +1,256 @@ +"""FastAPI server for RLM playground.""" + +import asyncio +import io +import json +import os +import sys +from concurrent.futures import ThreadPoolExecutor + +from dotenv import load_dotenv +from fastapi import FastAPI, Request +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import StreamingResponse + +from playground.models import RunRequest, RunResponse +from rlm import RLM +from rlm.logger import RLMLogger + +# Load environment variables +load_dotenv() + +# Thread pool for running RLM in the background +executor = ThreadPoolExecutor(max_workers=10) + +# Create FastAPI app +app = FastAPI( + title="RLM Playground API", + description="API for running RLM completions via web interface", + version="0.1.0", +) + +# Configure CORS +app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:3000", # Next.js dev server + "http://127.0.0.1:3000", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + + +@app.get("/") +def root(): + """Health check endpoint.""" + return {"status": "ok", "message": "RLM Playground API"} + + +def serialize_result(result): + """Serialize RLMChatCompletion to a dict.""" + response_text = result.response + if isinstance(response_text, tuple): + answer_type, answer_content = response_text + if answer_type == "FINAL": + response_text = answer_content.strip().strip('"').strip("'") + elif answer_type == "FINAL_VAR": + variable_name = answer_content.strip().strip('"').strip("'") + response_text = f"[FINAL_VAR: {variable_name}]" + else: + response_text = ( + answer_content if isinstance(answer_content, str) else str(answer_content) + ) + + return { + "success": True, + "response": str(response_text) if response_text else None, + "root_model": result.root_model, + "execution_time": result.execution_time, + "usage_summary": result.usage_summary.to_dict(), + "error": None, + } + + +@app.post("/api/run/stream") +async def run_rlm_stream(request_body: RunRequest, request: Request): + """Stream RLM execution via Server-Sent Events.""" + + async def event_generator(): + queue: asyncio.Queue = asyncio.Queue() + loop = asyncio.get_event_loop() + + def on_log_callback(entry: dict): + loop.call_soon_threadsafe(queue.put_nowait, entry) + + def run_rlm_sync(): + try: + # Setup logger + log_dir = os.getenv("RLM_LOG_DIR", "./logs") + logger = RLMLogger(log_dir=log_dir, on_log=on_log_callback) + + # Create RLM instance + rlm = RLM( + backend=request_body.backend, # type: ignore + backend_kwargs=request_body.backend_kwargs or {}, + environment=request_body.environment, # type: ignore + environment_kwargs=request_body.environment_kwargs or {}, + max_depth=request_body.max_depth, + max_iterations=request_body.max_iterations, + other_backends=request_body.other_backends, # type: ignore + other_backend_kwargs=request_body.other_backend_kwargs, + custom_system_prompt=request_body.custom_system_prompt, + logger=logger, + verbose=False, # Verbose output is messy in streaming + ) + + # Run completion + result = rlm.completion( + prompt=request_body.prompt, + root_prompt=request_body.root_prompt, + ) + + # Signal completion + loop.call_soon_threadsafe(queue.put_nowait, {"type": "complete", "result": result}) + except Exception as e: + loop.call_soon_threadsafe(queue.put_nowait, {"type": "error", "error": str(e)}) + + # Start RLM in background thread + _ = executor.submit(run_rlm_sync) + + try: + while True: + if await request.is_disconnected(): + break + + try: + # Wait for an event with timeout to check for disconnect + entry = await asyncio.wait_for(queue.get(), timeout=1.0) + except TimeoutError: + continue + + if entry["type"] == "metadata": + yield f"event: metadata\ndata: {json.dumps(entry)}\n\n" + elif entry["type"] == "iteration": + yield f"event: iteration\ndata: {json.dumps(entry)}\n\n" + elif entry["type"] == "complete": + yield f"event: complete\ndata: {json.dumps(serialize_result(entry['result']))}\n\n" + break + elif entry["type"] == "error": + yield f"event: error\ndata: {json.dumps({'error': entry['error']})}\n\n" + break + finally: + # We don't explicitly cancel the thread as it's not easily possible in Python, + # but we stop yielding and let it finish or hit its own timeout. + pass + + return StreamingResponse( + event_generator(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "Connection": "keep-alive", + }, + ) + + +@app.post("/api/run", response_model=RunResponse) +def run_rlm(request: RunRequest) -> RunResponse: + """ + Run an RLM completion with the provided configuration. + + This endpoint creates an RLM instance, runs a completion, and returns the result. + """ + try: + # Setup logger if enabled + logger = None + if request.enable_logging: + log_dir = os.getenv("RLM_LOG_DIR", "./logs") + logger = RLMLogger(log_dir=log_dir) + + # Capture verbose output if enabled + verbose_output = None + if request.verbose: + stdout_capture = io.StringIO() + stderr_capture = io.StringIO() + old_stdout = sys.stdout + old_stderr = sys.stderr + + try: + if request.verbose: + sys.stdout = stdout_capture + sys.stderr = stderr_capture + + # Create RLM instance + rlm = RLM( + backend=request.backend, + backend_kwargs=request.backend_kwargs or {}, + environment=request.environment, + environment_kwargs=request.environment_kwargs or {}, + max_depth=request.max_depth, + max_iterations=request.max_iterations, + other_backends=request.other_backends, + other_backend_kwargs=request.other_backend_kwargs, + custom_system_prompt=request.custom_system_prompt, + logger=logger, + verbose=request.verbose, + ) + + # Run completion + result = rlm.completion( + prompt=request.prompt, + root_prompt=request.root_prompt, + ) + finally: + # Restore stdout/stderr and capture output + if request.verbose: + sys.stdout = old_stdout + sys.stderr = old_stderr + stdout_text = stdout_capture.getvalue() + stderr_text = stderr_capture.getvalue() + verbose_output = stdout_text + stderr_text if stderr_text else stdout_text + + # Extract response - handle both string and tuple (type, content) formats + response_text = result.response + if isinstance(response_text, tuple): + # If it's a tuple from find_final_answer, extract the content + # Format: (type, content) where type is "FINAL" or "FINAL_VAR" + answer_type, answer_content = response_text + if answer_type == "FINAL": + # For FINAL, use the content directly + response_text = answer_content.strip().strip('"').strip("'") + elif answer_type == "FINAL_VAR": + # For FINAL_VAR, the content is the variable name + # We can't look it up here since we don't have access to the environment + # So we'll return a message indicating the variable name + variable_name = answer_content.strip().strip('"').strip("'") + response_text = f"[FINAL_VAR: {variable_name}]" + else: + # Fallback: just use the content + response_text = ( + answer_content if isinstance(answer_content, str) else str(answer_content) + ) + + # Convert result to response + return RunResponse( + success=True, + response=str(response_text) if response_text else None, + root_model=result.root_model, + execution_time=result.execution_time, + usage_summary=result.usage_summary.to_dict(), + verbose_output=verbose_output, + error=None, + ) + + except Exception as e: + # Return error response + return RunResponse( + success=False, + response=None, + root_model=None, + execution_time=None, + usage_summary=None, + verbose_output=None, + error=str(e), + ) diff --git a/pyproject.toml b/pyproject.toml index d79b7022..5ce70141 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,13 +20,14 @@ dependencies = [ [project.optional-dependencies] modal = ["modal>=0.73.0", "dill>=0.3.7"] +playground = ["fastapi>=0.115.0", "uvicorn[standard]>=0.32.0", "pydantic>=2.0.0"] [build-system] requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] -include = ["rlm", "rlm.*"] +include = ["rlm", "rlm.*", "playground"] [dependency-groups] dev = [ diff --git a/rlm/logger/rlm_logger.py b/rlm/logger/rlm_logger.py index 25d57d30..6d2c6c79 100644 --- a/rlm/logger/rlm_logger.py +++ b/rlm/logger/rlm_logger.py @@ -7,6 +7,7 @@ import json import os import uuid +from collections.abc import Callable from datetime import datetime from rlm.core.types import RLMIteration, RLMMetadata @@ -15,8 +16,11 @@ class RLMLogger: """Logger that writes RLMIteration data to a JSON-lines file.""" - def __init__(self, log_dir: str, file_name: str = "rlm"): + def __init__( + self, log_dir: str, file_name: str = "rlm", on_log: Callable[[dict], None] | None = None + ): self.log_dir = log_dir + self.on_log = on_log os.makedirs(log_dir, exist_ok=True) timestamp = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") @@ -42,6 +46,8 @@ def log_metadata(self, metadata: RLMMetadata): f.write("\n") self._metadata_logged = True + if self.on_log: + self.on_log(entry) def log(self, iteration: RLMIteration): """Log an RLMIteration to the file.""" @@ -58,6 +64,9 @@ def log(self, iteration: RLMIteration): json.dump(entry, f) f.write("\n") + if self.on_log: + self.on_log(entry) + @property def iteration_count(self) -> int: return self._iteration_count diff --git a/uv.lock b/uv.lock index 095864fe..1582268c 100644 --- a/uv.lock +++ b/uv.lock @@ -13,7 +13,7 @@ wheels = [ [[package]] name = "aiohttp" -version = "3.13.2" +version = "3.13.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "aiohappyeyeballs" }, @@ -126,6 +126,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fb/76/641ae371508676492379f16e2fa48f4e2c11741bd63c48be4b12a6b09cba/aiosignal-1.4.0-py3-none-any.whl", hash = "sha256:053243f8b92b990551949e63930a839ff0cf0b0ebbe0597b0f3fb19e1a0fe82e", size = 7490 }, ] +[[package]] +name = "annotated-doc" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea27344560984e26a590f90bc7f4a75b06701f653222458922b558c/annotated_doc-0.0.4.tar.gz", hash = "sha256:fbcda96e87e9c92ad167c2e53839e57503ecfda18804ea28102353485033faa4", size = 7288, upload-time = "2025-11-10T22:07:42.062Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" }, +] + [[package]] name = "annotated-types" version = "0.7.0" @@ -233,7 +242,7 @@ wheels = [ [[package]] name = "certifi" -version = "2025.11.12" +version = "2026.1.4" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a2/8c/58f469717fa48465e4a50c014a0400602d3c437d7c0c468e17ada824da3a/certifi-2025.11.12.tar.gz", hash = "sha256:d8ab5478f2ecd78af242878415affce761ca6bc54a22a27e026d7c25357c3316", size = 160538 } wheels = [ @@ -471,9 +480,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/55/e2/2537ebcff11c1ee1ff17d8d0b6f4db75873e3b0fb32c2d4a2ee31ecb310a/docstring_parser-0.17.0-py3-none-any.whl", hash = "sha256:cf2569abd23dce8099b300f9b4fa8191e9582dda731fd533daf54c4551658708", size = 36896 }, ] +[[package]] +name = "fastapi" +version = "0.128.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "annotated-doc" }, + { name = "pydantic" }, + { name = "starlette" }, + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/52/08/8c8508db6c7b9aae8f7175046af41baad690771c9bcde676419965e338c7/fastapi-0.128.0.tar.gz", hash = "sha256:1cc179e1cef10a6be60ffe429f79b829dce99d8de32d7acb7e6c8dfdf7f2645a", size = 365682, upload-time = "2025-12-27T15:21:13.714Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5c/05/5cbb59154b093548acd0f4c7c474a118eda06da25aa75c616b72d8fcd92a/fastapi-0.128.0-py3-none-any.whl", hash = "sha256:aebd93f9716ee3b4f4fcfe13ffb7cf308d99c9f3ab5622d8877441072561582d", size = 103094, upload-time = "2025-12-27T15:21:12.154Z" }, +] + [[package]] name = "filelock" -version = "3.20.1" +version = "3.20.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a7/23/ce7a1126827cedeb958fc043d61745754464eb56c5937c35bbf2b8e26f34/filelock-3.20.1.tar.gz", hash = "sha256:b8360948b351b80f420878d8516519a2204b07aefcdcfd24912a5d33127f188c", size = 19476 } wheels = [ @@ -682,6 +706,42 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784 }, ] +[[package]] +name = "httptools" +version = "0.7.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/46/120a669232c7bdedb9d52d4aeae7e6c7dfe151e99dc70802e2fc7a5e1993/httptools-0.7.1.tar.gz", hash = "sha256:abd72556974f8e7c74a259655924a717a2365b236c882c3f6f8a45fe94703ac9", size = 258961, upload-time = "2025-10-10T03:55:08.559Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9c/08/17e07e8d89ab8f343c134616d72eebfe03798835058e2ab579dcc8353c06/httptools-0.7.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:474d3b7ab469fefcca3697a10d11a32ee2b9573250206ba1e50d5980910da657", size = 206521, upload-time = "2025-10-10T03:54:31.002Z" }, + { url = "https://files.pythonhosted.org/packages/aa/06/c9c1b41ff52f16aee526fd10fbda99fa4787938aa776858ddc4a1ea825ec/httptools-0.7.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3c3b7366bb6c7b96bd72d0dbe7f7d5eead261361f013be5f6d9590465ea1c70", size = 110375, upload-time = "2025-10-10T03:54:31.941Z" }, + { url = "https://files.pythonhosted.org/packages/cc/cc/10935db22fda0ee34c76f047590ca0a8bd9de531406a3ccb10a90e12ea21/httptools-0.7.1-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:379b479408b8747f47f3b253326183d7c009a3936518cdb70db58cffd369d9df", size = 456621, upload-time = "2025-10-10T03:54:33.176Z" }, + { url = "https://files.pythonhosted.org/packages/0e/84/875382b10d271b0c11aa5d414b44f92f8dd53e9b658aec338a79164fa548/httptools-0.7.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cad6b591a682dcc6cf1397c3900527f9affef1e55a06c4547264796bbd17cf5e", size = 454954, upload-time = "2025-10-10T03:54:34.226Z" }, + { url = "https://files.pythonhosted.org/packages/30/e1/44f89b280f7e46c0b1b2ccee5737d46b3bb13136383958f20b580a821ca0/httptools-0.7.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:eb844698d11433d2139bbeeb56499102143beb582bd6c194e3ba69c22f25c274", size = 440175, upload-time = "2025-10-10T03:54:35.942Z" }, + { url = "https://files.pythonhosted.org/packages/6f/7e/b9287763159e700e335028bc1824359dc736fa9b829dacedace91a39b37e/httptools-0.7.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f65744d7a8bdb4bda5e1fa23e4ba16832860606fcc09d674d56e425e991539ec", size = 440310, upload-time = "2025-10-10T03:54:37.1Z" }, + { url = "https://files.pythonhosted.org/packages/b3/07/5b614f592868e07f5c94b1f301b5e14a21df4e8076215a3bccb830a687d8/httptools-0.7.1-cp311-cp311-win_amd64.whl", hash = "sha256:135fbe974b3718eada677229312e97f3b31f8a9c8ffa3ae6f565bf808d5b6bcb", size = 86875, upload-time = "2025-10-10T03:54:38.421Z" }, + { url = "https://files.pythonhosted.org/packages/53/7f/403e5d787dc4942316e515e949b0c8a013d84078a915910e9f391ba9b3ed/httptools-0.7.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:38e0c83a2ea9746ebbd643bdfb521b9aa4a91703e2cd705c20443405d2fd16a5", size = 206280, upload-time = "2025-10-10T03:54:39.274Z" }, + { url = "https://files.pythonhosted.org/packages/2a/0d/7f3fd28e2ce311ccc998c388dd1c53b18120fda3b70ebb022b135dc9839b/httptools-0.7.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f25bbaf1235e27704f1a7b86cd3304eabc04f569c828101d94a0e605ef7205a5", size = 110004, upload-time = "2025-10-10T03:54:40.403Z" }, + { url = "https://files.pythonhosted.org/packages/84/a6/b3965e1e146ef5762870bbe76117876ceba51a201e18cc31f5703e454596/httptools-0.7.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:2c15f37ef679ab9ecc06bfc4e6e8628c32a8e4b305459de7cf6785acd57e4d03", size = 517655, upload-time = "2025-10-10T03:54:41.347Z" }, + { url = "https://files.pythonhosted.org/packages/11/7d/71fee6f1844e6fa378f2eddde6c3e41ce3a1fb4b2d81118dd544e3441ec0/httptools-0.7.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7fe6e96090df46b36ccfaf746f03034e5ab723162bc51b0a4cf58305324036f2", size = 511440, upload-time = "2025-10-10T03:54:42.452Z" }, + { url = "https://files.pythonhosted.org/packages/22/a5/079d216712a4f3ffa24af4a0381b108aa9c45b7a5cc6eb141f81726b1823/httptools-0.7.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f72fdbae2dbc6e68b8239defb48e6a5937b12218e6ffc2c7846cc37befa84362", size = 495186, upload-time = "2025-10-10T03:54:43.937Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/025ad7b65278745dee3bd0ebf9314934c4592560878308a6121f7f812084/httptools-0.7.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e99c7b90a29fd82fea9ef57943d501a16f3404d7b9ee81799d41639bdaae412c", size = 499192, upload-time = "2025-10-10T03:54:45.003Z" }, + { url = "https://files.pythonhosted.org/packages/6d/de/40a8f202b987d43afc4d54689600ff03ce65680ede2f31df348d7f368b8f/httptools-0.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:3e14f530fefa7499334a79b0cf7e7cd2992870eb893526fb097d51b4f2d0f321", size = 86694, upload-time = "2025-10-10T03:54:45.923Z" }, + { url = "https://files.pythonhosted.org/packages/09/8f/c77b1fcbfd262d422f12da02feb0d218fa228d52485b77b953832105bb90/httptools-0.7.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6babce6cfa2a99545c60bfef8bee0cc0545413cb0018f617c8059a30ad985de3", size = 202889, upload-time = "2025-10-10T03:54:47.089Z" }, + { url = "https://files.pythonhosted.org/packages/0a/1a/22887f53602feaa066354867bc49a68fc295c2293433177ee90870a7d517/httptools-0.7.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:601b7628de7504077dd3dcb3791c6b8694bbd967148a6d1f01806509254fb1ca", size = 108180, upload-time = "2025-10-10T03:54:48.052Z" }, + { url = "https://files.pythonhosted.org/packages/32/6a/6aaa91937f0010d288d3d124ca2946d48d60c3a5ee7ca62afe870e3ea011/httptools-0.7.1-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:04c6c0e6c5fb0739c5b8a9eb046d298650a0ff38cf42537fc372b28dc7e4472c", size = 478596, upload-time = "2025-10-10T03:54:48.919Z" }, + { url = "https://files.pythonhosted.org/packages/6d/70/023d7ce117993107be88d2cbca566a7c1323ccbaf0af7eabf2064fe356f6/httptools-0.7.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:69d4f9705c405ae3ee83d6a12283dc9feba8cc6aaec671b412917e644ab4fa66", size = 473268, upload-time = "2025-10-10T03:54:49.993Z" }, + { url = "https://files.pythonhosted.org/packages/32/4d/9dd616c38da088e3f436e9a616e1d0cc66544b8cdac405cc4e81c8679fc7/httptools-0.7.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:44c8f4347d4b31269c8a9205d8a5ee2df5322b09bbbd30f8f862185bb6b05346", size = 455517, upload-time = "2025-10-10T03:54:51.066Z" }, + { url = "https://files.pythonhosted.org/packages/1d/3a/a6c595c310b7df958e739aae88724e24f9246a514d909547778d776799be/httptools-0.7.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:465275d76db4d554918aba40bf1cbebe324670f3dfc979eaffaa5d108e2ed650", size = 458337, upload-time = "2025-10-10T03:54:52.196Z" }, + { url = "https://files.pythonhosted.org/packages/fd/82/88e8d6d2c51edc1cc391b6e044c6c435b6aebe97b1abc33db1b0b24cd582/httptools-0.7.1-cp313-cp313-win_amd64.whl", hash = "sha256:322d00c2068d125bd570f7bf78b2d367dad02b919d8581d7476d8b75b294e3e6", size = 85743, upload-time = "2025-10-10T03:54:53.448Z" }, + { url = "https://files.pythonhosted.org/packages/34/50/9d095fcbb6de2d523e027a2f304d4551855c2f46e0b82befd718b8b20056/httptools-0.7.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:c08fe65728b8d70b6923ce31e3956f859d5e1e8548e6f22ec520a962c6757270", size = 203619, upload-time = "2025-10-10T03:54:54.321Z" }, + { url = "https://files.pythonhosted.org/packages/07/f0/89720dc5139ae54b03f861b5e2c55a37dba9a5da7d51e1e824a1f343627f/httptools-0.7.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7aea2e3c3953521c3c51106ee11487a910d45586e351202474d45472db7d72d3", size = 108714, upload-time = "2025-10-10T03:54:55.163Z" }, + { url = "https://files.pythonhosted.org/packages/b3/cb/eea88506f191fb552c11787c23f9a405f4c7b0c5799bf73f2249cd4f5228/httptools-0.7.1-cp314-cp314-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0e68b8582f4ea9166be62926077a3334064d422cf08ab87d8b74664f8e9058e1", size = 472909, upload-time = "2025-10-10T03:54:56.056Z" }, + { url = "https://files.pythonhosted.org/packages/e0/4a/a548bdfae6369c0d078bab5769f7b66f17f1bfaa6fa28f81d6be6959066b/httptools-0.7.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df091cf961a3be783d6aebae963cc9b71e00d57fa6f149025075217bc6a55a7b", size = 470831, upload-time = "2025-10-10T03:54:57.219Z" }, + { url = "https://files.pythonhosted.org/packages/4d/31/14df99e1c43bd132eec921c2e7e11cda7852f65619bc0fc5bdc2d0cb126c/httptools-0.7.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f084813239e1eb403ddacd06a30de3d3e09a9b76e7894dcda2b22f8a726e9c60", size = 452631, upload-time = "2025-10-10T03:54:58.219Z" }, + { url = "https://files.pythonhosted.org/packages/22/d2/b7e131f7be8d854d48cb6d048113c30f9a46dca0c9a8b08fcb3fcd588cdc/httptools-0.7.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:7347714368fb2b335e9063bc2b96f2f87a9ceffcd9758ac295f8bbcd3ffbc0ca", size = 452910, upload-time = "2025-10-10T03:54:59.366Z" }, + { url = "https://files.pythonhosted.org/packages/53/cf/878f3b91e4e6e011eff6d1fa9ca39f7eb17d19c9d7971b04873734112f30/httptools-0.7.1-cp314-cp314-win_amd64.whl", hash = "sha256:cfabda2a5bb85aa2a904ce06d974a3f30fb36cc63d7feaddec05d2050acede96", size = 88205, upload-time = "2025-10-10T03:55:00.389Z" }, +] + [[package]] name = "httpx" version = "0.28.1" @@ -1468,6 +1528,11 @@ modal = [ { name = "dill" }, { name = "modal" }, ] +playground = [ + { name = "fastapi" }, + { name = "pydantic" }, + { name = "uvicorn", extra = ["standard"] }, +] [package.dev-dependencies] dev = [ @@ -1489,12 +1554,14 @@ requires-dist = [ { name = "modal", marker = "extra == 'modal'", specifier = ">=0.73.0" }, { name = "openai", specifier = ">=2.14.0" }, { name = "portkey-ai", specifier = ">=2.1.0" }, + { name = "pydantic", marker = "extra == 'playground'", specifier = ">=2.0.0" }, { name = "pytest", specifier = ">=9.0.2" }, { name = "python-dotenv", specifier = ">=1.2.1" }, { name = "requests", specifier = ">=2.32.5" }, { name = "rich", specifier = ">=13.0.0" }, + { name = "uvicorn", extras = ["standard"], marker = "extra == 'playground'", specifier = ">=0.32.0" }, ] -provides-extras = ["modal"] +provides-extras = ["modal", "playground"] [package.metadata.requires-dev] dev = [ @@ -1564,6 +1631,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 }, ] +[[package]] +name = "starlette" +version = "0.50.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "anyio" }, + { name = "typing-extensions", marker = "python_full_version < '3.13'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ba/b8/73a0e6a6e079a9d9cfa64113d771e421640b6f679a52eeb9b32f72d871a1/starlette-0.50.0.tar.gz", hash = "sha256:a2a17b22203254bcbc2e1f926d2d55f3f9497f769416b3190768befe598fa3ca", size = 2646985, upload-time = "2025-11-01T15:25:27.516Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/52/1064f510b141bd54025f9b55105e26d1fa970b9be67ad766380a3c9b74b0/starlette-0.50.0-py3-none-any.whl", hash = "sha256:9e5391843ec9b6e472eed1365a78c8098cfceb7a74bfd4d6b1c0c0095efb3bca", size = 74033, upload-time = "2025-11-01T15:25:25.461Z" }, +] + [[package]] name = "synchronicity" version = "0.11.1" @@ -1755,6 +1835,68 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6d/b9/4095b668ea3678bf6a0af005527f39de12fb026516fb3df17495a733b7f8/urllib3-2.6.2-py3-none-any.whl", hash = "sha256:ec21cddfe7724fc7cb4ba4bea7aa8e2ef36f607a4bab81aa6ce42a13dc3f03dd", size = 131182 }, ] +[[package]] +name = "uvicorn" +version = "0.40.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "h11" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c3/d1/8f3c683c9561a4e6689dd3b1d345c815f10f86acd044ee1fb9a4dcd0b8c5/uvicorn-0.40.0.tar.gz", hash = "sha256:839676675e87e73694518b5574fd0f24c9d97b46bea16df7b8c05ea1a51071ea", size = 81761, upload-time = "2025-12-21T14:16:22.45Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3d/d8/2083a1daa7439a66f3a48589a57d576aa117726762618f6bb09fe3798796/uvicorn-0.40.0-py3-none-any.whl", hash = "sha256:c6c8f55bc8bf13eb6fa9ff87ad62308bbbc33d0b67f84293151efe87e0d5f2ee", size = 68502, upload-time = "2025-12-21T14:16:21.041Z" }, +] + +[package.optional-dependencies] +standard = [ + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "httptools" }, + { name = "python-dotenv" }, + { name = "pyyaml" }, + { name = "uvloop", marker = "platform_python_implementation != 'PyPy' and sys_platform != 'cygwin' and sys_platform != 'win32'" }, + { name = "watchfiles" }, + { name = "websockets" }, +] + +[[package]] +name = "uvloop" +version = "0.22.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/06/f0/18d39dbd1971d6d62c4629cc7fa67f74821b0dc1f5a77af43719de7936a7/uvloop-0.22.1.tar.gz", hash = "sha256:6c84bae345b9147082b17371e3dd5d42775bddce91f885499017f4607fdaf39f", size = 2443250, upload-time = "2025-10-16T22:17:19.342Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c7/d5/69900f7883235562f1f50d8184bb7dd84a2fb61e9ec63f3782546fdbd057/uvloop-0.22.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c60ebcd36f7b240b30788554b6f0782454826a0ed765d8430652621b5de674b9", size = 1352420, upload-time = "2025-10-16T22:16:21.187Z" }, + { url = "https://files.pythonhosted.org/packages/a8/73/c4e271b3bce59724e291465cc936c37758886a4868787da0278b3b56b905/uvloop-0.22.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b7f102bf3cb1995cfeaee9321105e8f5da76fdb104cdad8986f85461a1b7b77", size = 748677, upload-time = "2025-10-16T22:16:22.558Z" }, + { url = "https://files.pythonhosted.org/packages/86/94/9fb7fad2f824d25f8ecac0d70b94d0d48107ad5ece03769a9c543444f78a/uvloop-0.22.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:53c85520781d84a4b8b230e24a5af5b0778efdb39142b424990ff1ef7c48ba21", size = 3753819, upload-time = "2025-10-16T22:16:23.903Z" }, + { url = "https://files.pythonhosted.org/packages/74/4f/256aca690709e9b008b7108bc85fba619a2bc37c6d80743d18abad16ee09/uvloop-0.22.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:56a2d1fae65fd82197cb8c53c367310b3eabe1bbb9fb5a04d28e3e3520e4f702", size = 3804529, upload-time = "2025-10-16T22:16:25.246Z" }, + { url = "https://files.pythonhosted.org/packages/7f/74/03c05ae4737e871923d21a76fe28b6aad57f5c03b6e6bfcfa5ad616013e4/uvloop-0.22.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:40631b049d5972c6755b06d0bfe8233b1bd9a8a6392d9d1c45c10b6f9e9b2733", size = 3621267, upload-time = "2025-10-16T22:16:26.819Z" }, + { url = "https://files.pythonhosted.org/packages/75/be/f8e590fe61d18b4a92070905497aec4c0e64ae1761498cad09023f3f4b3e/uvloop-0.22.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:535cc37b3a04f6cd2c1ef65fa1d370c9a35b6695df735fcff5427323f2cd5473", size = 3723105, upload-time = "2025-10-16T22:16:28.252Z" }, + { url = "https://files.pythonhosted.org/packages/3d/ff/7f72e8170be527b4977b033239a83a68d5c881cc4775fca255c677f7ac5d/uvloop-0.22.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:fe94b4564e865d968414598eea1a6de60adba0c040ba4ed05ac1300de402cd42", size = 1359936, upload-time = "2025-10-16T22:16:29.436Z" }, + { url = "https://files.pythonhosted.org/packages/c3/c6/e5d433f88fd54d81ef4be58b2b7b0cea13c442454a1db703a1eea0db1a59/uvloop-0.22.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:51eb9bd88391483410daad430813d982010f9c9c89512321f5b60e2cddbdddd6", size = 752769, upload-time = "2025-10-16T22:16:30.493Z" }, + { url = "https://files.pythonhosted.org/packages/24/68/a6ac446820273e71aa762fa21cdcc09861edd3536ff47c5cd3b7afb10eeb/uvloop-0.22.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:700e674a166ca5778255e0e1dc4e9d79ab2acc57b9171b79e65feba7184b3370", size = 4317413, upload-time = "2025-10-16T22:16:31.644Z" }, + { url = "https://files.pythonhosted.org/packages/5f/6f/e62b4dfc7ad6518e7eff2516f680d02a0f6eb62c0c212e152ca708a0085e/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5b1ac819a3f946d3b2ee07f09149578ae76066d70b44df3fa990add49a82e4", size = 4426307, upload-time = "2025-10-16T22:16:32.917Z" }, + { url = "https://files.pythonhosted.org/packages/90/60/97362554ac21e20e81bcef1150cb2a7e4ffdaf8ea1e5b2e8bf7a053caa18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e047cc068570bac9866237739607d1313b9253c3051ad84738cbb095be0537b2", size = 4131970, upload-time = "2025-10-16T22:16:34.015Z" }, + { url = "https://files.pythonhosted.org/packages/99/39/6b3f7d234ba3964c428a6e40006340f53ba37993f46ed6e111c6e9141d18/uvloop-0.22.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:512fec6815e2dd45161054592441ef76c830eddaad55c8aa30952e6fe1ed07c0", size = 4296343, upload-time = "2025-10-16T22:16:35.149Z" }, + { url = "https://files.pythonhosted.org/packages/89/8c/182a2a593195bfd39842ea68ebc084e20c850806117213f5a299dfc513d9/uvloop-0.22.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:561577354eb94200d75aca23fbde86ee11be36b00e52a4eaf8f50fb0c86b7705", size = 1358611, upload-time = "2025-10-16T22:16:36.833Z" }, + { url = "https://files.pythonhosted.org/packages/d2/14/e301ee96a6dc95224b6f1162cd3312f6d1217be3907b79173b06785f2fe7/uvloop-0.22.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1cdf5192ab3e674ca26da2eada35b288d2fa49fdd0f357a19f0e7c4e7d5077c8", size = 751811, upload-time = "2025-10-16T22:16:38.275Z" }, + { url = "https://files.pythonhosted.org/packages/b7/02/654426ce265ac19e2980bfd9ea6590ca96a56f10c76e63801a2df01c0486/uvloop-0.22.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e2ea3d6190a2968f4a14a23019d3b16870dd2190cd69c8180f7c632d21de68d", size = 4288562, upload-time = "2025-10-16T22:16:39.375Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/0be24758891ef825f2065cd5db8741aaddabe3e248ee6acc5e8a80f04005/uvloop-0.22.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0530a5fbad9c9e4ee3f2b33b148c6a64d47bbad8000ea63704fa8260f4cf728e", size = 4366890, upload-time = "2025-10-16T22:16:40.547Z" }, + { url = "https://files.pythonhosted.org/packages/d2/53/8369e5219a5855869bcee5f4d317f6da0e2c669aecf0ef7d371e3d084449/uvloop-0.22.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bc5ef13bbc10b5335792360623cc378d52d7e62c2de64660616478c32cd0598e", size = 4119472, upload-time = "2025-10-16T22:16:41.694Z" }, + { url = "https://files.pythonhosted.org/packages/f8/ba/d69adbe699b768f6b29a5eec7b47dd610bd17a69de51b251126a801369ea/uvloop-0.22.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1f38ec5e3f18c8a10ded09742f7fb8de0108796eb673f30ce7762ce1b8550cad", size = 4239051, upload-time = "2025-10-16T22:16:43.224Z" }, + { url = "https://files.pythonhosted.org/packages/90/cd/b62bdeaa429758aee8de8b00ac0dd26593a9de93d302bff3d21439e9791d/uvloop-0.22.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3879b88423ec7e97cd4eba2a443aa26ed4e59b45e6b76aabf13fe2f27023a142", size = 1362067, upload-time = "2025-10-16T22:16:44.503Z" }, + { url = "https://files.pythonhosted.org/packages/0d/f8/a132124dfda0777e489ca86732e85e69afcd1ff7686647000050ba670689/uvloop-0.22.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:4baa86acedf1d62115c1dc6ad1e17134476688f08c6efd8a2ab076e815665c74", size = 752423, upload-time = "2025-10-16T22:16:45.968Z" }, + { url = "https://files.pythonhosted.org/packages/a3/94/94af78c156f88da4b3a733773ad5ba0b164393e357cc4bd0ab2e2677a7d6/uvloop-0.22.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:297c27d8003520596236bdb2335e6b3f649480bd09e00d1e3a99144b691d2a35", size = 4272437, upload-time = "2025-10-16T22:16:47.451Z" }, + { url = "https://files.pythonhosted.org/packages/b5/35/60249e9fd07b32c665192cec7af29e06c7cd96fa1d08b84f012a56a0b38e/uvloop-0.22.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c1955d5a1dd43198244d47664a5858082a3239766a839b2102a269aaff7a4e25", size = 4292101, upload-time = "2025-10-16T22:16:49.318Z" }, + { url = "https://files.pythonhosted.org/packages/02/62/67d382dfcb25d0a98ce73c11ed1a6fba5037a1a1d533dcbb7cab033a2636/uvloop-0.22.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:b31dc2fccbd42adc73bc4e7cdbae4fc5086cf378979e53ca5d0301838c5682c6", size = 4114158, upload-time = "2025-10-16T22:16:50.517Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7a/f1171b4a882a5d13c8b7576f348acfe6074d72eaf52cccef752f748d4a9f/uvloop-0.22.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:93f617675b2d03af4e72a5333ef89450dfaa5321303ede6e67ba9c9d26878079", size = 4177360, upload-time = "2025-10-16T22:16:52.646Z" }, + { url = "https://files.pythonhosted.org/packages/79/7b/b01414f31546caf0919da80ad57cbfe24c56b151d12af68cee1b04922ca8/uvloop-0.22.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:37554f70528f60cad66945b885eb01f1bb514f132d92b6eeed1c90fd54ed6289", size = 1454790, upload-time = "2025-10-16T22:16:54.355Z" }, + { url = "https://files.pythonhosted.org/packages/d4/31/0bb232318dd838cad3fa8fb0c68c8b40e1145b32025581975e18b11fab40/uvloop-0.22.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:b76324e2dc033a0b2f435f33eb88ff9913c156ef78e153fb210e03c13da746b3", size = 796783, upload-time = "2025-10-16T22:16:55.906Z" }, + { url = "https://files.pythonhosted.org/packages/42/38/c9b09f3271a7a723a5de69f8e237ab8e7803183131bc57c890db0b6bb872/uvloop-0.22.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:badb4d8e58ee08dad957002027830d5c3b06aea446a6a3744483c2b3b745345c", size = 4647548, upload-time = "2025-10-16T22:16:57.008Z" }, + { url = "https://files.pythonhosted.org/packages/c1/37/945b4ca0ac27e3dc4952642d4c900edd030b3da6c9634875af6e13ae80e5/uvloop-0.22.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b91328c72635f6f9e0282e4a57da7470c7350ab1c9f48546c0f2866205349d21", size = 4467065, upload-time = "2025-10-16T22:16:58.206Z" }, + { url = "https://files.pythonhosted.org/packages/97/cc/48d232f33d60e2e2e0b42f4e73455b146b76ebe216487e862700457fbf3c/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:daf620c2995d193449393d6c62131b3fbd40a63bf7b307a1527856ace637fe88", size = 4328384, upload-time = "2025-10-16T22:16:59.36Z" }, + { url = "https://files.pythonhosted.org/packages/e4/16/c1fd27e9549f3c4baf1dc9c20c456cd2f822dbf8de9f463824b0c0357e06/uvloop-0.22.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:6cde23eeda1a25c75b2e07d39970f3374105d5eafbaab2a4482be82f272d5a5e", size = 4296730, upload-time = "2025-10-16T22:17:00.744Z" }, +] + [[package]] name = "virtualenv" version = "20.35.4" @@ -1898,6 +2040,48 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 }, ] +[[package]] +name = "websockets" +version = "15.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016, upload-time = "2025-03-05T20:03:41.606Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423, upload-time = "2025-03-05T20:01:56.276Z" }, + { url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082, upload-time = "2025-03-05T20:01:57.563Z" }, + { url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330, upload-time = "2025-03-05T20:01:59.063Z" }, + { url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878, upload-time = "2025-03-05T20:02:00.305Z" }, + { url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883, upload-time = "2025-03-05T20:02:03.148Z" }, + { url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252, upload-time = "2025-03-05T20:02:05.29Z" }, + { url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521, upload-time = "2025-03-05T20:02:07.458Z" }, + { url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958, upload-time = "2025-03-05T20:02:09.842Z" }, + { url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918, upload-time = "2025-03-05T20:02:11.968Z" }, + { url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388, upload-time = "2025-03-05T20:02:13.32Z" }, + { url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828, upload-time = "2025-03-05T20:02:14.585Z" }, + { url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437, upload-time = "2025-03-05T20:02:16.706Z" }, + { url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096, upload-time = "2025-03-05T20:02:18.832Z" }, + { url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332, upload-time = "2025-03-05T20:02:20.187Z" }, + { url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152, upload-time = "2025-03-05T20:02:22.286Z" }, + { url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096, upload-time = "2025-03-05T20:02:24.368Z" }, + { url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523, upload-time = "2025-03-05T20:02:25.669Z" }, + { url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790, upload-time = "2025-03-05T20:02:26.99Z" }, + { url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165, upload-time = "2025-03-05T20:02:30.291Z" }, + { url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160, upload-time = "2025-03-05T20:02:31.634Z" }, + { url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395, upload-time = "2025-03-05T20:02:33.017Z" }, + { url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841, upload-time = "2025-03-05T20:02:34.498Z" }, + { url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440, upload-time = "2025-03-05T20:02:36.695Z" }, + { url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098, upload-time = "2025-03-05T20:02:37.985Z" }, + { url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329, upload-time = "2025-03-05T20:02:39.298Z" }, + { url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111, upload-time = "2025-03-05T20:02:40.595Z" }, + { url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054, upload-time = "2025-03-05T20:02:41.926Z" }, + { url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496, upload-time = "2025-03-05T20:02:43.304Z" }, + { url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829, upload-time = "2025-03-05T20:02:48.812Z" }, + { url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217, upload-time = "2025-03-05T20:02:50.14Z" }, + { url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195, upload-time = "2025-03-05T20:02:51.561Z" }, + { url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393, upload-time = "2025-03-05T20:02:53.814Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837, upload-time = "2025-03-05T20:02:55.237Z" }, + { url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743, upload-time = "2025-03-05T20:03:39.41Z" }, +] + [[package]] name = "yarl" version = "1.22.0" diff --git a/visualizer/src/app/playground/page.tsx b/visualizer/src/app/playground/page.tsx new file mode 100644 index 00000000..cfc2bee9 --- /dev/null +++ b/visualizer/src/app/playground/page.tsx @@ -0,0 +1,110 @@ +'use client'; + +import { ThemeToggle } from '@/components/ThemeToggle'; +import { PlaygroundForm } from '@/components/PlaygroundForm'; +import { LiveLogViewer } from '@/components/LiveLogViewer'; +import { useLiveLog } from '@/hooks/useLiveLog'; +import Link from 'next/link'; + +export default function PlaygroundPage() { + const { state, startStream, cancel, reset } = useLiveLog(); + + const handleRun = async (config: any) => { + await startStream(config); + }; + + return ( +
+ {/* Background effects */} +
+
+
+ +
+ {/* Header */} +
+
+
+
+ +

+ RLM + Playground +

+ +

+ Run recursive language model completions interactively +

+
+
+ + VISUALIZER + + +
+ + + READY + +
+
+
+
+
+ + {/* Main Content */} +
+
+
+

+ 01 + Configuration +

+ +
+ +
+
+

+ 02 + Live Trace Viewer +

+ {state.status !== 'idle' && state.status !== 'streaming' && state.status !== 'connecting' && ( + + )} +
+ +
+
+
+ + {/* Footer */} +
+
+

+ RLM Playground • Recursive Language Models +

+

+ [LM ↔ REPL] +

+
+
+
+
+ ); +} + diff --git a/visualizer/src/components/Dashboard.tsx b/visualizer/src/components/Dashboard.tsx index 79a9e00e..8133fc6a 100644 --- a/visualizer/src/components/Dashboard.tsx +++ b/visualizer/src/components/Dashboard.tsx @@ -8,6 +8,7 @@ import { FileUploader } from './FileUploader'; import { LogViewer } from './LogViewer'; import { AsciiRLM } from './AsciiGlobe'; import { ThemeToggle } from './ThemeToggle'; +import Link from 'next/link'; import { parseLogFile, extractContextVariable } from '@/lib/parse-logs'; import { RLMLogFile } from '@/lib/types'; import { cn } from '@/lib/utils'; @@ -123,6 +124,12 @@ export function Dashboard() {
+ + PLAYGROUND +
diff --git a/visualizer/src/components/KeyValueEditor.tsx b/visualizer/src/components/KeyValueEditor.tsx new file mode 100644 index 00000000..99cf4242 --- /dev/null +++ b/visualizer/src/components/KeyValueEditor.tsx @@ -0,0 +1,84 @@ +'use client'; + +import { Button } from '@/components/ui/button'; + +interface KeyValuePair { + key: string; + value: string; +} + +interface KeyValueEditorProps { + pairs: KeyValuePair[]; + onChange: (pairs: KeyValuePair[]) => void; + disabled?: boolean; + keyPlaceholder?: string; + valuePlaceholder?: string; +} + +export function KeyValueEditor({ + pairs, + onChange, + disabled = false, + keyPlaceholder = 'key', + valuePlaceholder = 'value', +}: KeyValueEditorProps) { + const updatePair = (index: number, field: 'key' | 'value', newValue: string) => { + const newPairs = [...pairs]; + newPairs[index] = { ...newPairs[index], [field]: newValue }; + onChange(newPairs); + }; + + const addPair = () => { + onChange([...pairs, { key: '', value: '' }]); + }; + + const removePair = (index: number) => { + onChange(pairs.filter((_, i) => i !== index)); + }; + + return ( +
+ {pairs.map((pair, index) => ( +
+ updatePair(index, 'key', e.target.value)} + placeholder={keyPlaceholder} + className="flex-1 px-3 py-2 bg-background border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring" + disabled={disabled} + /> + updatePair(index, 'value', e.target.value)} + placeholder={valuePlaceholder} + className="flex-1 px-3 py-2 bg-background border border-input rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-ring" + disabled={disabled} + /> + +
+ ))} + +
+ ); +} + diff --git a/visualizer/src/components/LiveLogViewer.tsx b/visualizer/src/components/LiveLogViewer.tsx new file mode 100644 index 00000000..525c5a9c --- /dev/null +++ b/visualizer/src/components/LiveLogViewer.tsx @@ -0,0 +1,182 @@ +'use client'; + +import { useState, useEffect } from 'react'; +import { Card, CardContent } from '@/components/ui/card'; +import { Badge } from '@/components/ui/badge'; +import { Button } from '@/components/ui/button'; +import { ResizableHandle, ResizablePanel, ResizablePanelGroup } from '@/components/ui/resizable'; +import { StatsCard } from './StatsCard'; +import { TrajectoryPanel } from './TrajectoryPanel'; +import { ExecutionPanel } from './ExecutionPanel'; +import { IterationTimeline } from './IterationTimeline'; +import { LiveLogState } from '@/lib/types'; +import { computeMetadata } from '@/lib/parse-logs'; +import { cn } from '@/lib/utils'; + +interface LiveLogViewerProps { + state: LiveLogState; + onCancel: () => void; +} + +export function LiveLogViewer({ state, onCancel }: LiveLogViewerProps) { + const [selectedIteration, setSelectedIteration] = useState(0); + const { status, config, iterations, error, finalResult } = state; + + // Auto-select latest iteration as they come in + useEffect(() => { + if (status === 'streaming' && iterations.length > 0) { + setSelectedIteration(iterations.length - 1); + } + }, [iterations.length, status]); + + // Compute live metadata + const metadata = computeMetadata(iterations); + + if (status === 'idle') { + return ( + + +
+
+
+

+ Awaiting execution parameters... +

+
+
+
+
+ ); + } + + return ( +
+ {/* Streaming Overlay for connection phase */} + {status === 'connecting' && ( +
+
+
+
+
+
+

ESTABLISHING STREAM...

+
+
+ )} + + {/* Top Bar */} +
+
+
+
+ + {status} + +
+ + {config?.root_model ?? 'Initializing...'} + +
+ +
+ {status === 'streaming' && ( + + )} + {error && ( + + Error + + )} +
+
+
+ + {/* Stats Row */} +
+
+ + + + +
+
+ + {/* Iteration Timeline */} + + + {/* Main Content */} +
+ {iterations.length > 0 ? ( + + + + + + + + + + ) : ( +
+ Waiting for first iteration... +
+ )} +
+ + {/* Final Result / Error Banner */} + {(status === 'complete' || status === 'error' || status === 'cancelled') && ( +
+
+
+ {status === 'complete' ? ( + + DONE: {finalResult?.response?.slice(0, 100)}... + + ) : status === 'error' ? ( + + ERROR: {error} + + ) : ( + + CANCELLED by user + + )} +
+ {finalResult?.usage_summary && ( +
+ METRICS_SAVED +
+ )} +
+
+ )} +
+ ); +} diff --git a/visualizer/src/components/PlaygroundForm.tsx b/visualizer/src/components/PlaygroundForm.tsx new file mode 100644 index 00000000..23aa2f7b --- /dev/null +++ b/visualizer/src/components/PlaygroundForm.tsx @@ -0,0 +1,299 @@ +'use client'; + +import { useState } from 'react'; +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { KeyValueEditor } from './KeyValueEditor'; +import { cn } from '@/lib/utils'; + +interface PlaygroundFormProps { + onRun: (config: any) => void; + loading: boolean; +} + +interface KeyValuePair { + key: string; + value: string; +} + +export function PlaygroundForm({ onRun, loading }: PlaygroundFormProps) { + const [backend, setBackend] = useState('openai'); + const [backendKwargs, setBackendKwargs] = useState([ + { key: 'model_name', value: 'openai/gpt-4.1-mini' }, + ]); + const [environment, setEnvironment] = useState('local'); + const [environmentKwargs, setEnvironmentKwargs] = useState([]); + const [prompt, setPrompt] = useState('Print me the first 10 Fibonacci numbers.'); + const [rootPrompt, setRootPrompt] = useState(''); + const [maxIterations, setMaxIterations] = useState(30); + const [maxDepth, setMaxDepth] = useState(1); + const [customSystemPrompt, setCustomSystemPrompt] = useState(''); + const [otherBackends, setOtherBackends] = useState([]); + const [otherBackendKwargs, setOtherBackendKwargs] = useState([]); + const [verbose, setVerbose] = useState(false); + + // Convert key-value pairs to object + const pairsToObject = (pairs: KeyValuePair[]): Record => { + const obj: Record = {}; + pairs.forEach(({ key, value }) => { + if (key.trim()) { + // Try to parse as number or boolean, otherwise keep as string + let parsedValue: any = value; + if (value === 'true') parsedValue = true; + else if (value === 'false') parsedValue = false; + else if (value === 'null' || value === '') parsedValue = null; + else if (!isNaN(Number(value)) && value.trim() !== '') { + parsedValue = Number(value); + } + obj[key.trim()] = parsedValue; + } + }); + return obj; + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + const config: any = { + prompt, + root_prompt: rootPrompt || undefined, + backend, + backend_kwargs: pairsToObject(backendKwargs), + environment, + environment_kwargs: pairsToObject(environmentKwargs), + max_iterations: maxIterations, + max_depth: maxDepth, + verbose: verbose, + enable_logging: true, + }; + + if (customSystemPrompt.trim()) { + config.custom_system_prompt = customSystemPrompt; + } + + if (otherBackends.length > 0) { + config.other_backends = otherBackends; + if (otherBackendKwargs.length > 0) { + config.other_backend_kwargs = otherBackendKwargs.map(pairsToObject); + } + } + + onRun(config); + }; + + const addOtherBackend = () => { + setOtherBackends([...otherBackends, 'openai']); + setOtherBackendKwargs([...otherBackendKwargs, []]); + }; + + const removeOtherBackend = (index: number) => { + setOtherBackends(otherBackends.filter((_, i) => i !== index)); + setOtherBackendKwargs(otherBackendKwargs.filter((_, i) => i !== index)); + }; + + const updateOtherBackend = (index: number, backend: string) => { + const newBackends = [...otherBackends]; + newBackends[index] = backend; + setOtherBackends(newBackends); + }; + + return ( + + +
+
+
+ {/* Backend Selection */} +
+ + +
+ + {/* Backend Kwargs */} +
+ +

+ Key-value pairs for backend configuration (e.g., model_name, api_key) +

+
+ +
+
+ + {/* Environment */} +
+ + +
+ + {/* Environment Kwargs */} +
+ +
+ +
+
+ + {/* Custom System Prompt */} +
+ +