Skip to content

Commit bbbfcb6

Browse files
Chenlei Huchristian-byrne
andauthored
RFC: object_info (v2) (#1)
* object_info_v2 * Add old format for comparison * resolve comments * Update target version to TBD * Update rfc_01 proposal according to comments * Improve examples * Update status of unresolved questions * Add widget-level lazy initialization and memoization details * Remove cache key and invalidation signal fields --------- Co-authored-by: christian-byrne <[email protected]>
1 parent fd48830 commit bbbfcb6

File tree

1 file changed

+377
-0
lines changed

1 file changed

+377
-0
lines changed

rfcs/0001-object_info_v2.md

Lines changed: 377 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,377 @@
1+
# RFC: ComfyUI API Improvements
2+
3+
- Start Date: 2025-02-03
4+
- Target Major Version: TBD
5+
6+
## Summary
7+
8+
This RFC proposes three key improvements to the ComfyUI API:
9+
10+
1. Lazy loading for COMBO input options to reduce initial payload size
11+
2. Restructuring node output specifications for better maintainability
12+
3. Explicit COMBO type definition for clearer client-side handling
13+
14+
## Basic example
15+
16+
### 1\. Lazy Loading COMBO Options
17+
18+
```python
19+
# Before
20+
class CheckpointLoader:
21+
@classmethod
22+
def INPUT_TYPES(s):
23+
return {
24+
"required": {
25+
"config_name": (folder_paths.get_filename_list("configs"),),
26+
}
27+
}
28+
29+
# After
30+
class CheckpointLoader:
31+
@classmethod
32+
def INPUT_TYPES(s):
33+
return {
34+
"required": {
35+
"config_name": ("COMBO", {
36+
"type" : "remote",
37+
"route": "/internal/files",
38+
"response_key" : "files",
39+
"query_params" : {
40+
"folder_path" : "configs"
41+
}
42+
}),
43+
}
44+
}
45+
```
46+
47+
### 2\. Improved Output Specification
48+
49+
```python
50+
# Before
51+
RETURN_TYPES = ("CONDITIONING","CONDITIONING")
52+
RETURN_NAMES = ("positive", "negative")
53+
OUTPUT_IS_LIST = (False, False)
54+
OUTPUT_TOOLTIPS = ("positive-tooltip", "negative-tooltip")
55+
56+
# After
57+
RETURNS = (
58+
{"type": "CONDITIONING", "name": "positive", "is_list": False, "tooltip": "positive-tooltip"},
59+
{"type": "CONDITIONING", "name": "negative", "is_list": False, "tooltip": "negative-tooltip"},
60+
)
61+
```
62+
63+
### 3\. Explicit COMBO Type
64+
65+
```python
66+
# Before
67+
"combo input": [[1, 2, 3], { default: 2 }]
68+
69+
# After
70+
"combo input": ["COMBO", { options: [1, 2, 3], default: 2}]
71+
```
72+
73+
## Motivation
74+
75+
1. **Full recompute**: If the user wants to refresh the COMBO options for a single folder, they need to recompute the entire node definitions. This is a very slow process and not user friendly.
76+
77+
2. **Large Payload Issue**: The `/object_info` API currently returns several MB of JSON data, primarily due to eager loading of COMBO options. This impacts initial load times and overall performance.
78+
79+
3. **Output Specification Maintenance**: The current format for defining node outputs requires modifications in multiple lists, making it error-prone and difficult to maintain. Adding new features like tooltips would further complicate this.
80+
81+
4. **Implicit COMBO Type**: The current implementation requires client-side code to infer COMBO types by checking if the first parameter is a list, which is not intuitive and could lead to maintenance issues.
82+
83+
## Detailed design
84+
85+
The implementation will be split into two phases to minimize disruption:
86+
87+
### Phase 1: Combo Specification Changes
88+
89+
#### 1.1 New Combo Specification
90+
91+
Input types will be explicitly defined using tuples with configuration objects. A variant of the `COMBO` type will be added to support lazy loading options from the server.
92+
93+
```python
94+
@classmethod
95+
def INPUT_TYPES(s):
96+
return {
97+
"required": {
98+
# Remote combo
99+
"ckpt_name": ("COMBO", {
100+
"type": "remote",
101+
"route": "/internal/files",
102+
"response_key": "files",
103+
"refresh": 0, # TTL in ms. 0 = do not refresh after initial load.
104+
"query_params": {
105+
"folder_path": "checkpoints",
106+
"filter_ext": [".ckpt", ".safetensors"]
107+
}
108+
}),
109+
"mode": ("COMBO", {
110+
"options": ["balanced", "speed", "quality"],
111+
"default": "balanced",
112+
"tooltip": "Processing mode"
113+
})
114+
}
115+
}
116+
```
117+
118+
Use a Proxy on remote combo widgets' values property that doesn't compute/fetch until first access.
119+
120+
```typescript
121+
COMBO(node, inputName, inputData: InputSpec, app, widgetName) {
122+
123+
// ...
124+
125+
const res = {
126+
widget: node.addWidget('combo', inputName, defaultValue, () => {}, {
127+
// Support old and new combo input specs
128+
values: widgetStore.isComboInputV2(inputData)
129+
? inputData[1].options
130+
: inputType
131+
})
132+
}
133+
134+
if (type === 'remote') {
135+
const remoteWidget = useRemoteWidget(inputData)
136+
137+
const origOptions = res.widget.options
138+
res.widget.options = new Proxy(origOptions, {
139+
// Defer fetching until first access (node added to graph)
140+
get(target, prop: string | symbol) {
141+
if (prop !== 'values') return target[prop]
142+
143+
// Start non-blocking fetch
144+
remoteWidget.fetchOptions().then((data) => {})
145+
146+
const current = remoteWidget.getCacheEntry()
147+
return current?.data || widgetStore.getDefaultValue(inputData)
148+
}
149+
})
150+
}
151+
```
152+
153+
Backoff time will be determined by the number of failed attempts:
154+
155+
```typescript
156+
// Exponential backoff with max of 10 seconds
157+
const backoff = Math.min(1000 * 2 ** (failedAttempts - 1), 10000);
158+
159+
// Example backoff times:
160+
// Attempt 1: 1000ms (1s)
161+
// Attempt 2: 2000ms (2s)
162+
// Attempt 3: 4000ms (4s)
163+
// Attempt 4: 8000ms (8s)
164+
// Attempt 5+: 10000ms (10s)
165+
```
166+
167+
Share computation results between widgets using a key based on the route and query params:
168+
169+
```typescript
170+
// Global cache for memoizing fetches
171+
const dataCache = new Map<string, CacheEntry<any>>();
172+
173+
function getCacheKey(options: RemoteWidgetOptions): string {
174+
return JSON.stringify({ route: options.route, params: options.query_params });
175+
}
176+
```
177+
178+
The cache can be invalidated in two ways:
179+
180+
1. **TTL-based**: Using the `refresh` parameter to specify a time-to-live in milliseconds. When TTL expires, next access triggers a new fetch.
181+
2. **Manual**: Using the `forceUpdate` method of the widget, which deletes the cache entry and triggers a new fetch on next access.
182+
183+
Example TTL usage:
184+
185+
```python
186+
"ckpt_name": ("COMBO", {
187+
"type": "remote",
188+
"refresh": 60000, # Refresh every minute
189+
// ... other options
190+
})
191+
```
192+
193+
#### 1.2 New Endpoints
194+
195+
```python
196+
@routes.get("/internal/files/{folder_name}")
197+
async def list_folder_files(request):
198+
folder_name = request.match_info["folder_name"]
199+
filter_ext = request.query.get("filter_ext", "").split(",")
200+
filter_content_type = request.query.get("filter_content_type", "").split(",")
201+
202+
files = folder_paths.get_filename_list(folder_name)
203+
if filter_ext and filter_ext[0]:
204+
files = [f for f in files if any(f.endswith(ext) for ext in filter_ext)]
205+
if filter_content_type and filter_content_type[0]:
206+
files = folder_paths.filter_files_content_type(files, filter_content_type)
207+
208+
return web.json_response({
209+
"files": files,
210+
})
211+
```
212+
213+
#### 1.3 Gradual Change with Nodes
214+
215+
Nodes will be updated incrementally to use the new combo specification.
216+
217+
### Phase 2: Node Output Specification Changes
218+
219+
#### 2.1 New Output Format
220+
221+
Nodes will transition from multiple return definitions to a single `RETURNS` tuple:
222+
223+
```python
224+
# Current format will be supported during transition
225+
RETURN_TYPES = ("CONDITIONING", "CONDITIONING")
226+
RETURN_NAMES = ("positive", "negative")
227+
OUTPUT_IS_LIST = (False, False)
228+
OUTPUT_TOOLTIPS = ("positive-tooltip", "negative-tooltip")
229+
230+
# New format
231+
RETURNS = (
232+
{
233+
"type": "CONDITIONING",
234+
"name": "positive",
235+
"is_list": False,
236+
"tooltip": "positive-tooltip",
237+
"optional": False # New field for optional outputs
238+
},
239+
{
240+
"type": "CONDITIONING",
241+
"name": "negative",
242+
"is_list": False,
243+
"tooltip": "negative-tooltip"
244+
}
245+
)
246+
```
247+
248+
#### 2.2 New Response Format
249+
250+
Old format:
251+
252+
```javascript
253+
{
254+
"CheckpointLoader": {
255+
"input": {
256+
"required": {
257+
"ckpt_name": [[
258+
"file1",
259+
"file2",
260+
...
261+
"fileN",
262+
]],
263+
"combo_input": [[
264+
"option1",
265+
"option2",
266+
...
267+
"optionN",
268+
], {
269+
"default": "option1",
270+
"tooltip": "Processing mode"
271+
}],
272+
},
273+
"optional": {}
274+
},
275+
"output": ["MODEL"],
276+
"output_name": ["model"],
277+
"output_is_list": [false],
278+
"output_tooltip": ["The loaded model"],
279+
"output_node": false,
280+
"category": "loaders"
281+
}
282+
}
283+
```
284+
285+
New format:
286+
287+
```javascript
288+
{
289+
"CheckpointLoader": {
290+
"input": {
291+
"required": {
292+
"ckpt_name": [
293+
"COMBO",
294+
{
295+
"type" : "remote",
296+
"route": "/internal/files",
297+
"response_key" : "files",
298+
"query_params" : {
299+
"folder_path" : "checkpoints"
300+
}
301+
}
302+
],
303+
"combo_input": [
304+
"COMBO",
305+
{
306+
"options": ["option1", "option2", ... "optionN"],
307+
"default": "option1",
308+
"tooltip": "Processing mode"
309+
}
310+
],
311+
},
312+
"optional": {}
313+
},
314+
"output": [
315+
{
316+
"type": "MODEL",
317+
"name": "model",
318+
"is_list": false,
319+
"tooltip": "The loaded model"
320+
}
321+
],
322+
"output_node": false,
323+
"category": "loaders"
324+
}
325+
}
326+
```
327+
328+
#### 2.3 Compatibility Layer
329+
330+
Transformations will be applied on the frontend to convert the old format to the new format.
331+
332+
#### 2.4 Gradual Change with Nodes
333+
334+
Nodes will be updated incrementally to use the new output specification format.
335+
336+
### Migration Support
337+
338+
To support gradual migration, the API will:
339+
340+
1. **Dual Support**: Accept both old and new node definitions
341+
2. **Compatibility Layer**: Include a compatibility layer in the frontend that can type check and handle both old and new formats.
342+
343+
## Drawbacks
344+
345+
1. **Migration Effort**: Users and node developers will need to update their code to match the new formats.
346+
2. **Additional Complexity**: Lazy loading adds network requests, which could complicate error handling and state management.
347+
348+
## Adoption strategy
349+
350+
1. **Version Support**: Maintain backward compatibility for at least one major version.
351+
2. **Migration Guide**: Provide detailed documentation and migration scripts.
352+
3. **Gradual Rollout**: Implement changes in phases, starting with lazy loading.
353+
354+
## Unresolved questions
355+
356+
### Resolved
357+
358+
1. ~~Network failure handling~~ - Implemented with exponential backoff
359+
2. ~~Caching strategy~~ - Per-widget initialization with manual invalidation
360+
361+
### Implementation Details
362+
363+
3. Should we provide a migration utility for updating existing nodes?
364+
4. How do we handle custom node types that may not fit the new output specification format?
365+
366+
### Future Considerations
367+
368+
5. Should an option to set an invalidation signal be added to the remote COMBO type?
369+
6. Should an option for a custom cache key for the remote COMBO type be added?
370+
371+
### Security Concerns
372+
373+
7. Implementation details needed for:
374+
- Rate limiting strategy
375+
- Input validation approach
376+
- Cache poisoning prevention measures
377+
- Access control mechanisms

0 commit comments

Comments
 (0)