Skip to content

Conversation

@psychedelicious
Copy link
Contributor

What type of PR is this? (check all applicable)

  • Refactor
  • Feature
  • Bug Fix
  • Optimization
  • Documentation Update
  • Community Node Submission

Have you discussed this change with the InvokeAI team?

  • Yes
  • No, because:

Description

feat(backend): allow/deny nodes

Allow denying and explicitly allowing nodes. When a not-allowed node is used, a pydantic ValidationError will be raised.

  • When collecting all invocations, check against the allowlist and denylist first. When pydantic constructs any unions related to nodes, the denied nodes will be omitted
  • Add allow_nodes and deny_nodes to InvokeAIAppConfig. These are Union[list[str], None], and may be populated with the type of invocations.
  • When allow_nodes is None, allow all nodes, else if it is list[str], only allow nodes in the list
  • When deny_nodes is None, deny no nodes, else if it is list[str], deny nodes in the list
  • deny_nodes overrides allow_nodes

feat(ui): add UI-level nodes denylist

This simply hides nodes from the workflow editor. The nodes will still work if an API request is made with them. For example, you could hide iterate nodes from the workflow editor, but if the Linear UI makes use of those nodes, they will still function.

  • Update AppConfig with optional property nodesDenylist: string[]
  • If provided, nodes are filtered out by type in the workflow editor

QA Instructions, Screenshots, Recordings

Testing the backend denylist handling

Test the existing behaviour:

  1. Open the node editor
  2. Reset it if you have anything in there currently
  3. Add a Float Primitive node
  4. Run the workflow with only this single node to confirm it works

Test allowing/denying nodes:

  1. Update your invokeai.yaml file, adding a new category for Nodes with keys for allow_nodes and deny_nodes. Suggest using this example stanza:
  Nodes:
    allow_nodes:
      - add
      - blank_image
      - boolean
      - boolean_collection
      - canny_image_processor
      - clip_skip
      - collect
      - color
      - color_correct
      - compel
      - conditioning
      - conditioning_collection
      - content_shuffle_image_processor
      - controlnet
      - create_denoise_mask
      - cv_inpaint
      - denoise_latents
      - div
      - dynamic_prompt
      - esrgan
      - float
      - float_collection
      - float_range
      - graph
      - hed_image_processor
      - i2l
      - image
      - image_collection
      - img_blur
      - img_chan
      - img_channel_multiply
      - img_channel_offset
      - img_conv
      - img_crop
      - img_hue_adjust
      - img_ilerp
      - img_lerp
      - img_mul
      - img_nsfw
      - img_paste
      - img_resize
      - img_scale
      - img_watermark
      - infill_cv2
      - infill_lama
      - infill_patchmatch
      - infill_rgba
      - infill_tile
      - integer
      - integer_collection
      - iterate
      - l2i
      - l2i_onnx
      - latents
      - latents_collection
      - lblend
      - leres_image_processor
      - lineart_anime_image_processor
      - lineart_image_processor
      - lora_loader
      - lresize
      - lscale
      - main_model_loader
      - mask_combine
      - mask_edge
      - mediapipe_face_processor
      - metadata_accumulator
      - midas_depth_image_processor
      - mlsd_image_processor
      - mul
      - noise
      - normalbae_image_processor
      - onnx_model_loader
      - openpose_image_processor
      - pidi_image_processor
      - prompt_onnx
      - rand_int
      - random_range
      - range
      - range_of_size
      - scheduler
      - sdxl_compel_prompt
      - sdxl_lora_loader
      - sdxl_model_loader
      - sdxl_refiner_compel_prompt
      - sdxl_refiner_model_loader
      - seamless
      - segment_anything_processor
      - step_param_easing
      - string
      - string_collection
      - sub
      - t2l_onnx
      - tile_image_processor
      - tomask
      - vae_loader
      - zoe_depth_image_processor
    deny_nodes:
      - image_processor
      - prompt_from_file
      - show_image
  1. Restart the server and refresh the web app
  2. You should still be able to execute the workflow with a single float node, as we haven't denied it yet
  3. But, you should not be able to find ShowImage in the add nodes search box, because it is denied

Add node to denylist that is already in-use in a workflow:

  1. In invokeai.yaml, add float to deny_nodes
  2. Restart the server
  3. ! Do not refresh the web app, else you'll need to start over !
  4. Click Invoke
  5. You should get an error about an invalid discriminant value, demonstrating that the backend doesn't care if the UI still thinks the node is valid

Slightly different case

  1. Refresh the web app
  2. The existing float node should now show an error.
  3. Click the workflow editor settings and disable validation
  4. Invoke
  5. You should get the same error about an invalid discriminant value.
  6. Delete the float node and try to add it again - you shouldn't be able to find it in the list

Testing the UI-level denylist handling

  1. Open invokeai/frontend/web/src/main.tsx
  2. Add this config with nodes denylist:
    image
  3. Refresh the web app
  4. Open workflow editor and you shouldn't be able to find these nodes
  5. Go to Linear UI, set image count to 2 and invoke. It should still work (internally it will use iterate nodes, but because this is only a UI-level denylist, the backend will still process everything as you'd expect)

Allow denying and explicitly allowing nodes. When a not-allowed node is used, a pydantic `ValidationError` will be raised.

- When collecting all invocations, check against the allowlist and denylist first. When pydantic constructs any unions related to nodes, the denied nodes will be omitted
- Add `allow_nodes` and `deny_nodes` to `InvokeAIAppConfig`. These are `Union[list[str], None]`, and may be populated with the `type` of invocations.
- When `allow_nodes` is `None`, allow all nodes, else if it is `list[str]`, only allow nodes in the list
- When `deny_nodes` is `None`, deny no nodes, else if it is `list[str]`, deny nodes in the list
- `deny_nodes` overrides `allow_nodes`
This simply hides nodes from the workflow editor. The nodes will still work if an API request is made with them. For example, you could hide `iterate` nodes from the workflow editor, but if the Linear UI makes use of those nodes, they will still function.

- Update `AppConfig` with optional property `nodesDenylist: string[]`
- If provided, nodes are filtered out by `type` in the workflow editor
@ebr
Copy link
Contributor

ebr commented Sep 6, 2023

Do we want to update this error toast (e.g. when the iterate node is disabled on the backend, but used by the linear UI):

Or should we rely (perhaps just temporarily) on whomever modifying this config to make sane choices?

@psychedelicious
Copy link
Contributor Author

@ebr it's unfortunately rather tricky to change this because almost all pydantic errors are the same type - ValidationError. There's no easy way to filter out this specific error because invalid discriminator can happen in many cases, and it is an error you'd want to see.

Could truncate the error messages when they exceed a limit though.

@psychedelicious
Copy link
Contributor Author

I've truncated the error messages to 128 characters, and added nodesAllowlist to the UI config. The behaviour of this and the backend allow/deny-lists is identical.

Do not throw when parsing unknown args, instead parse only known args print the unknown ones (supersedes #4216)
We need to parse the config before doing anything related to invocations to ensure that the invocations union picks up on denied nodes.

- Move that to the top of api_app and cli_app
- Wrap subsequent imports in `if True:`, as a hack to satisfy flake8 and not have to noqa every line or the whole file
- Add tests to ensure graph validation fails when using a denied node, and that the invocations union does not have denied nodes (this indirectly provides confidence that the generated OpenAPI schema will not include denied nodes)
@psychedelicious
Copy link
Contributor Author

I think this is ready for merge, with a caveat:

  • Resolved the issues with calling parse_args() inside baseinvocation.py
  • Added handling for unknown args for the config, superseding Ignore unknown command-line arguments #4216
  • Added test to verify that denied nodes are actually denied. Unfortunately, due to how pytest collects test modules before running, the test fails when run with the full app suite. It passes when run in isolation. Fixing this is beyond me.

Copy link
Collaborator

@lstein lstein left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any problem with the changes to base.py.

@hipsterusername hipsterusername merged commit d0a7832 into main Sep 8, 2023
@hipsterusername hipsterusername deleted the feat/nodes-denylist branch September 8, 2023 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants