mirror of
https://github.com/comfyanonymous/ComfyUI.git
synced 2026-01-10 06:10:50 +08:00
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:
parent
03895dea7c
commit
195321d85c
252
api_schemas/README.md
Normal file
252
api_schemas/README.md
Normal 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
20
api_schemas/__init__.py
Normal 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'
|
||||
]
|
||||
205
api_schemas/prompt_format.json
Normal file
205
api_schemas/prompt_format.json
Normal 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
86
api_schemas/validation.py
Normal 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
|
||||
46
server.py
46
server.py
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user