Add JSON Schema for Prompt API format

Resolves #8899

This implementation adds a comprehensive JSON Schema for the ComfyUI Prompt API format to improve developer experience and API integration.

Features:
- JSON Schema definition for prompt format validation
- Optional schema validation via ?validate_schema=true parameter
- New /schema/prompt endpoint to serve the schema
- Comprehensive documentation and examples
- Backward compatible - no breaking changes

Implementation:
- api_schemas/prompt_format.json: Complete JSON Schema definition
- api_schemas/validation.py: Validation utilities with graceful fallback
- api_schemas/README.md: Detailed documentation and usage examples
- server.py: Added schema endpoint and optional validation

The schema validates:
- Prompt structure and required fields
- Node definitions with class_type and inputs
- Connection format [node_id, output_slot]
- Optional metadata like prompt_id, client_id, etc.

Schema validation is opt-in via query parameter to ensure no breaking changes.
If jsonschema package is not installed, validation is skipped with a warning.
This commit is contained in:
vivek 2025-08-05 05:19:02 +05:30
parent 03895dea7c
commit 195321d85c
5 changed files with 608 additions and 1 deletions

252
api_schemas/README.md Normal file
View File

@ -0,0 +1,252 @@
# ComfyUI Prompt API JSON Schema
This directory contains JSON Schema definitions for the ComfyUI API, providing formal specification, validation, and IDE support for API integrations.
## 📁 Files
- **`prompt_format.json`** - JSON Schema for the `/prompt` endpoint request format
- **`validation.py`** - Python utilities for schema validation
- **`README.md`** - This documentation file
## 🚀 Quick Start
### 1. Get the Schema
The JSON Schema is available at: `GET /schema/prompt` or `GET /api/schema/prompt`
```bash
curl http://localhost:8188/schema/prompt
```
### 2. Enable Validation (Optional)
Add `?validate_schema=true` to your POST requests for server-side validation:
```bash
curl -X POST http://localhost:8188/prompt?validate_schema=true \
-H "Content-Type: application/json" \
-d @your_prompt.json
```
### 3. IDE Setup
Most modern IDEs support JSON Schema for autocomplete and validation:
**VS Code:**
```json
{
"json.schemas": [
{
"fileMatch": ["**/comfyui_prompt*.json"],
"url": "http://localhost:8188/schema/prompt"
}
]
}
```
**IntelliJ/PyCharm:**
Settings → Languages & Frameworks → Schemas and DTDs → JSON Schema Mappings
## 📋 Schema Overview
The prompt format schema defines the structure for ComfyUI workflow execution requests:
```json
{
"prompt": {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "model.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a beautiful landscape",
"clip": ["1", 1]
}
}
},
"prompt_id": "optional-uuid",
"client_id": "optional-client-id"
}
```
### Key Properties
- **`prompt`** (required) - The node graph defining the workflow
- **`prompt_id`** (optional) - Unique identifier for tracking execution
- **`number`** (optional) - Execution priority (lower = higher priority)
- **`front`** (optional) - If true, prioritize this execution
- **`extra_data`** (optional) - Additional metadata
- **`client_id`** (optional) - WebSocket client identifier
- **`partial_execution_targets`** (optional) - Array of specific nodes to execute
### Node Structure
Each node in the prompt is keyed by a numeric string ID and contains:
- **`class_type`** (required) - The ComfyUI node class name
- **`inputs`** (required) - Input values or connections to other nodes
- **`_meta`** (optional) - Metadata not used in execution
### Input Types
Node inputs can be:
1. **Direct values:** `"text": "hello world"`
2. **Node connections:** `"clip": ["1", 0]` (node_id, output_slot)
## 🛠️ Validation
### Python Integration
```python
from api_schemas.validation import validate_prompt_format
data = {"prompt": {...}}
is_valid, error_msg = validate_prompt_format(data)
if not is_valid:
print(f"Validation failed: {error_msg}")
```
### Server-Side Validation
Enable validation with query parameter:
```
POST /prompt?validate_schema=true
```
Returns `400 Bad Request` with detailed error information if validation fails.
## 🔧 Development
### Requirements
For validation features:
```bash
pip install jsonschema
```
### Schema Updates
When updating the schema:
1. Modify `prompt_format.json`
2. Test with real ComfyUI workflows
3. Update examples and documentation
4. Verify backward compatibility
### Testing
```python
# Test schema loading
from api_schemas.validation import load_prompt_schema
schema = load_prompt_schema()
assert schema is not None
# Test validation
from api_schemas.validation import validate_prompt_format
valid_prompt = {"prompt": {"1": {"class_type": "TestNode", "inputs": {}}}}
is_valid, error = validate_prompt_format(valid_prompt)
assert is_valid
```
## 📚 Examples
### Basic Text-to-Image Workflow
```json
{
"prompt": {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "model.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a beautiful landscape",
"clip": ["1", 1]
}
},
"3": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "blurry, low quality",
"clip": ["1", 1]
}
},
"4": {
"class_type": "KSampler",
"inputs": {
"seed": 12345,
"steps": 20,
"cfg": 7.0,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1.0,
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["3", 0],
"latent_image": ["5", 0]
}
},
"5": {
"class_type": "EmptyLatentImage",
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
}
},
"6": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["4", 0],
"vae": ["1", 2]
}
},
"7": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI",
"images": ["6", 0]
}
}
}
}
```
### Partial Execution
```json
{
"prompt": {
"1": {"class_type": "LoadImage", "inputs": {"image": "input.png"}},
"2": {"class_type": "SaveImage", "inputs": {"images": ["1", 0]}}
},
"partial_execution_targets": ["2"],
"prompt_id": "550e8400-e29b-41d4-a716-446655440000"
}
```
## 🤝 Contributing
This schema implementation addresses GitHub issue [#8899](https://github.com/comfyanonymous/ComfyUI/issues/8899).
To contribute:
1. Test with real ComfyUI workflows
2. Report issues or inaccuracies
3. Suggest improvements for better IDE support
4. Help with documentation and examples
## 📄 License
This follows the same license as ComfyUI main repository.

20
api_schemas/__init__.py Normal file
View File

@ -0,0 +1,20 @@
"""
ComfyUI API Schema validation package
This package provides JSON Schema definitions and validation utilities
for the ComfyUI API endpoints.
"""
from .validation import (
validate_prompt_format,
load_prompt_schema,
get_schema_info,
JSONSCHEMA_AVAILABLE
)
__all__ = [
'validate_prompt_format',
'load_prompt_schema',
'get_schema_info',
'JSONSCHEMA_AVAILABLE'
]

View File

@ -0,0 +1,205 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://github.com/comfyanonymous/ComfyUI/schemas/prompt_format.json",
"title": "ComfyUI Prompt API Format",
"description": "JSON Schema for the ComfyUI Prompt API format used with the /prompt endpoint",
"type": "object",
"properties": {
"prompt": {
"type": "object",
"description": "The node graph defining the workflow to execute",
"patternProperties": {
"^[0-9]+$": {
"type": "object",
"description": "Node definition with numeric ID as key",
"properties": {
"class_type": {
"type": "string",
"description": "The class name of the node (e.g., 'CheckpointLoaderSimple', 'KSampler')",
"minLength": 1
},
"inputs": {
"type": "object",
"description": "Input values or connections for this node",
"patternProperties": {
".*": {
"oneOf": [
{
"type": "array",
"description": "Connection to another node [node_id, output_slot]",
"prefixItems": [
{
"type": "string",
"description": "ID of the source node",
"pattern": "^[0-9]+$"
},
{
"type": "integer",
"description": "Output slot index of the source node",
"minimum": 0
}
],
"items": false,
"minItems": 2,
"maxItems": 2
},
{
"not": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"prefixItems": [
{"type": "string", "pattern": "^[0-9]+$"},
{"type": "integer", "minimum": 0}
]
},
"description": "Direct input value (string, number, boolean, object, or array)"
}
]
}
},
"additionalProperties": true
},
"_meta": {
"type": "object",
"description": "Optional metadata for the node (not used in execution)",
"properties": {
"title": {
"type": "string",
"description": "Custom title for the node"
}
},
"additionalProperties": true
}
},
"required": ["class_type", "inputs"],
"additionalProperties": false
}
},
"additionalProperties": false,
"minProperties": 1
},
"prompt_id": {
"type": "string",
"description": "Optional unique identifier for this prompt execution. If not provided, a UUID will be generated.",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$|^[a-fA-F0-9]{32}$"
},
"number": {
"type": "number",
"description": "Optional execution priority number. Lower numbers execute first."
},
"front": {
"type": "boolean",
"description": "If true, this prompt will be prioritized (given a negative number)."
},
"extra_data": {
"type": "object",
"description": "Additional metadata and configuration for the execution",
"properties": {
"client_id": {
"type": "string",
"description": "Client identifier for WebSocket communication"
}
},
"additionalProperties": true
},
"client_id": {
"type": "string",
"description": "Client identifier for WebSocket communication (alternative to extra_data.client_id)"
},
"partial_execution_targets": {
"type": "array",
"description": "Optional array of node IDs to execute selectively. If provided, only these nodes (and their dependencies) will be executed.",
"items": {
"type": "string",
"pattern": "^[0-9]+$",
"description": "Node ID to execute"
},
"uniqueItems": true
}
},
"required": ["prompt"],
"additionalProperties": false,
"examples": [
{
"prompt": {
"1": {
"class_type": "CheckpointLoaderSimple",
"inputs": {
"ckpt_name": "model.safetensors"
}
},
"2": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "a beautiful landscape",
"clip": ["1", 1]
}
},
"3": {
"class_type": "CLIPTextEncode",
"inputs": {
"text": "blurry, low quality",
"clip": ["1", 1]
}
},
"4": {
"class_type": "KSampler",
"inputs": {
"seed": 12345,
"steps": 20,
"cfg": 7.0,
"sampler_name": "euler",
"scheduler": "normal",
"denoise": 1.0,
"model": ["1", 0],
"positive": ["2", 0],
"negative": ["3", 0],
"latent_image": ["5", 0]
}
},
"5": {
"class_type": "EmptyLatentImage",
"inputs": {
"width": 512,
"height": 512,
"batch_size": 1
}
},
"6": {
"class_type": "VAEDecode",
"inputs": {
"samples": ["4", 0],
"vae": ["1", 2]
}
},
"7": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "ComfyUI",
"images": ["6", 0]
}
}
}
},
{
"prompt": {
"1": {
"class_type": "LoadImage",
"inputs": {
"image": "input.png"
}
},
"2": {
"class_type": "SaveImage",
"inputs": {
"filename_prefix": "output",
"images": ["1", 0]
}
}
},
"prompt_id": "550e8400-e29b-41d4-a716-446655440000",
"client_id": "client_123"
}
]
}

86
api_schemas/validation.py Normal file
View File

@ -0,0 +1,86 @@
"""
JSON Schema validation utilities for ComfyUI API
"""
import json
import os
import logging
from typing import Dict, Any, Tuple, Optional
try:
import jsonschema
from jsonschema import validate, ValidationError
JSONSCHEMA_AVAILABLE = True
except ImportError:
JSONSCHEMA_AVAILABLE = False
ValidationError = Exception # Fallback for type hints
def load_prompt_schema() -> Optional[Dict[str, Any]]:
"""
Load the prompt format JSON schema from file.
Returns:
Dict containing the schema, or None if not found/invalid
"""
schema_path = os.path.join(os.path.dirname(__file__), "prompt_format.json")
try:
with open(schema_path, 'r', encoding='utf-8') as f:
return json.load(f)
except (FileNotFoundError, json.JSONDecodeError) as e:
logging.warning(f"Could not load prompt schema: {e}")
return None
def validate_prompt_format(data: Dict[str, Any], warn_only: bool = True) -> Tuple[bool, Optional[str]]:
"""
Validate prompt data against the JSON schema.
Args:
data: The prompt data to validate
warn_only: If True, log warnings instead of raising errors
Returns:
Tuple of (is_valid, error_message)
"""
if not JSONSCHEMA_AVAILABLE:
if warn_only:
logging.debug("jsonschema not available, skipping schema validation")
return True, None
schema = load_prompt_schema()
if schema is None:
if warn_only:
logging.debug("Could not load schema, skipping validation")
return True, None
try:
validate(instance=data, schema=schema)
return True, None
except ValidationError as e:
error_msg = f"Prompt format validation failed: {e.message}"
if e.path:
error_msg += f" at path: {'.'.join(str(p) for p in e.path)}"
if warn_only:
logging.warning(f"Schema validation warning: {error_msg}")
return True, error_msg # Still return True for warnings
else:
return False, error_msg
def get_schema_info() -> Dict[str, Any]:
"""
Get information about the schema validation capability.
Returns:
Dict containing schema validation status and info
"""
info = {
"jsonschema_available": JSONSCHEMA_AVAILABLE,
"schema_loaded": load_prompt_schema() is not None,
}
if JSONSCHEMA_AVAILABLE:
info["jsonschema_version"] = getattr(jsonschema, "__version__", "unknown")
return info

View File

@ -584,7 +584,31 @@ class PromptServer():
@routes.get("/features")
async def get_features(request):
return web.json_response(feature_flags.get_server_features())
features = feature_flags.get_server_features()
try:
from api_schemas.validation import get_schema_info
features["schema_validation"] = get_schema_info()
except ImportError:
features["schema_validation"] = {
"jsonschema_available": False,
"schema_loaded": False
}
return web.json_response(features)
@routes.get("/schema/prompt")
async def get_prompt_schema(request):
"""Serve the JSON Schema for the prompt API format"""
schema_path = os.path.join(os.path.dirname(__file__), "api_schemas", "prompt_format.json")
try:
with open(schema_path, 'r', encoding='utf-8') as f:
schema = json.load(f)
return web.json_response(schema)
except FileNotFoundError:
return web.json_response({"error": "Schema file not found"}, status=404)
except json.JSONDecodeError as e:
return web.json_response({"error": f"Invalid schema file: {str(e)}"}, status=500)
@routes.get("/prompt")
async def get_prompt(request):
@ -669,6 +693,26 @@ class PromptServer():
async def post_prompt(request):
logging.info("got prompt")
json_data = await request.json()
# Optional JSON Schema validation
validate_schema = request.rel_url.query.get('validate_schema', 'false').lower() == 'true'
if validate_schema:
try:
from api_schemas.validation import validate_prompt_format
is_valid, error_msg = validate_prompt_format(json_data, warn_only=False)
if not is_valid:
error = {
"type": "schema_validation_failed",
"message": "Request does not conform to prompt API schema",
"details": error_msg,
"extra_info": {
"schema_url": "/schema/prompt"
}
}
return web.json_response({"error": error, "node_errors": {}}, status=400)
except ImportError:
logging.warning("Schema validation requested but validation module not available")
json_data = self.trigger_on_prompt(json_data)
if "number" in json_data: