agent-comfy-ui-api-workflow 0.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agent_comfy_ui_api_workflow-0.0.1/.gitignore +10 -0
- agent_comfy_ui_api_workflow-0.0.1/LICENSE +0 -0
- agent_comfy_ui_api_workflow-0.0.1/PKG-INFO +191 -0
- agent_comfy_ui_api_workflow-0.0.1/README.md +168 -0
- agent_comfy_ui_api_workflow-0.0.1/pyproject.toml +53 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/__about__.py +1 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/__init__.py +39 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/__init__.py +0 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/agent.py +276 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/catalog/__init__.py +348 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/catalog/essential.py +98 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/catalog/new_object_info.json +58779 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/env.py +44 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/errors.py +54 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/families.py +314 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/files.py +152 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/inventory.py +168 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/middleware.py +39 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/model.py +32 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/pipelines/__init__.py +70 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/pipelines/base.py +87 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/pipelines/generation.py +1249 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/pipelines/media.py +370 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/prompts.py +101 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/runpod.py +209 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/session.py +357 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/__init__.py +25 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/common.py +56 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/editing.py +249 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/nodes.py +98 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/pipelines.py +92 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/tools/questions.py +42 -0
- agent_comfy_ui_api_workflow-0.0.1/src/agent_comfy_ui_api_workflow/_impl/validation.py +142 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/builders.py +34 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/conftest.py +102 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/main.py +23 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/media.py +51 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/scripted.py +46 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_agent.py +249 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_agent_claude.py +172 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_agent_runpod.py +167 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_catalog.py +123 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_env.py +34 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_families.py +116 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_files.py +101 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_inventory.py +160 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_pipelines_generation.py +414 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_pipelines_media.py +216 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_runpod_client.py +266 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_runpod_jobs.py +98 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_runpod_pipelines.py +1269 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_runpod_server.py +167 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_session.py +137 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_tools.py +375 -0
- agent_comfy_ui_api_workflow-0.0.1/tests/test_validation.py +103 -0
|
File without changes
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agent-comfy-ui-api-workflow
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Async LangChain agent on Claude that builds, edits and validates ComfyUI API workflows.
|
|
5
|
+
Author-email: Fabio Antonio Astore <astore.a.fabio@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Requires-Dist: comfy-ui-api-workflow==0.0.9
|
|
12
|
+
Requires-Dist: httpx==0.28.1
|
|
13
|
+
Requires-Dist: langchain-anthropic==1.7.6
|
|
14
|
+
Requires-Dist: langchain==1.4.7
|
|
15
|
+
Requires-Dist: langgraph==1.2.16
|
|
16
|
+
Requires-Dist: pillow==13.0.0
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
19
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest-cov>=7.1; extra == 'dev'
|
|
21
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# agent-comfy-ui-api-workflow
|
|
25
|
+
|
|
26
|
+
An async agent on Claude (through LangChain) that builds, edits and validates
|
|
27
|
+
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) workflows in the API format, with
|
|
28
|
+
[comfy-ui-api-workflow](https://github.com/fabioantonioastore/comfy-ui-api-workflow).
|
|
29
|
+
|
|
30
|
+
- **Pipelines first.** The agent adds tested chains of nodes in one call:
|
|
31
|
+
`text_to_image`, `image_to_image`, `inpaint`, `outpaint`, `text_to_video`,
|
|
32
|
+
`image_to_video` (also between a start and an end image), `sound_to_video` (a photo
|
|
33
|
+
that talks or sings an audio), `text_to_audio` (music), `upscale_image_with_model`,
|
|
34
|
+
and video, audio and image editing. Single nodes come from a short list of essential
|
|
35
|
+
ones, only for steps no pipeline covers.
|
|
36
|
+
- **Your server's models only.** Generation pipelines are offered for the model
|
|
37
|
+
families found on the server, and every model name is checked against the server's
|
|
38
|
+
files. The families it knows:
|
|
39
|
+
|
|
40
|
+
| Family | Pipelines |
|
|
41
|
+
|---|---|
|
|
42
|
+
| SDXL | images: text to image, image to image, inpaint, outpaint |
|
|
43
|
+
| Wan 2.2 TI2V 5B | video from text or an image, 24 fps |
|
|
44
|
+
| Wan 2.2 A14B T2V / I2V, with the LightX2V 4-step LoRAs | video from text, an image, or a start and an end image, 16 fps |
|
|
45
|
+
| MiniMax H3 (with its turbo LoRA) | video with its own sound: speech, effects, music; 5 to 15 s |
|
|
46
|
+
| Wan 2.2 S2V | a photo animated in sync with an audio |
|
|
47
|
+
| ACE-Step 1.5 XL turbo | music, sung or instrumental |
|
|
48
|
+
| Upscale models (Real-ESRGAN...) | image upscaling |
|
|
49
|
+
|
|
50
|
+
MiniMax H3 has its own community license, which restricts where it may be used:
|
|
51
|
+
read it before serving that model.
|
|
52
|
+
- **It asks when it must.** A request that needs a file the user did not attach (an
|
|
53
|
+
image, a video, an audio, a mask), something the server cannot do, or a choice that
|
|
54
|
+
changes the result, comes back as a question instead of a guess.
|
|
55
|
+
- **Checked before it is delivered.** When the agent finishes, the workflow is
|
|
56
|
+
validated: an output node, every node connected, model files the server has, files
|
|
57
|
+
the user attached, and a JSON that loads back. Problems go back to the agent to fix.
|
|
58
|
+
- **It never runs the workflow.** You get the JSON for `POST /prompt` and the list of
|
|
59
|
+
files to upload with it.
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install git+https://github.com/fabioantonioastore/agent-comfy-ui-api-workflow
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The agent reads `ANTHROPIC_API_KEY`, and `RunPodClient` reads `RUNPOD_API_KEY` and
|
|
68
|
+
`RUNPOD_ENDPOINT_ID`, from the environment or from a `.env` file (`NAME=value` or
|
|
69
|
+
`NAME: value` lines).
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
import asyncio
|
|
75
|
+
from pathlib import Path
|
|
76
|
+
|
|
77
|
+
from agent_comfy_ui_api_workflow import RunPodClient, ServerInventory, WorkflowAgent
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
async def main():
|
|
81
|
+
# Once: ask the server which model files it has, and keep the answer. Workers that
|
|
82
|
+
# answer {"object_info": true} jobs give every loader's files; the official worker
|
|
83
|
+
# gets a probe instead, which misses the loaders its handler does not check.
|
|
84
|
+
async with RunPodClient.from_env() as runpod:
|
|
85
|
+
inventory = await runpod.discover_inventory()
|
|
86
|
+
inventory.save("inventory.json") # later: ServerInventory.load("inventory.json")
|
|
87
|
+
|
|
88
|
+
agent = WorkflowAgent(inventory)
|
|
89
|
+
reply = await agent.send("make an elephant with my face")
|
|
90
|
+
if reply.status == "needs_input":
|
|
91
|
+
print(reply.question.text, reply.question.needs) # ... ('image',)
|
|
92
|
+
photo = Path("me.jpg").read_bytes()
|
|
93
|
+
reply = await agent.send("here it is", files={"me.jpg": photo})
|
|
94
|
+
|
|
95
|
+
print(reply.status, reply.message)
|
|
96
|
+
print(reply.workflow) # the API format, ready for POST /prompt
|
|
97
|
+
print(reply.input_files) # ('me.jpg',): upload it with the workflow
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
asyncio.run(main())
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Each `send` is one turn of the same conversation, so the next request can change what
|
|
104
|
+
the agent built: "now make it 16:9 with 35 steps".
|
|
105
|
+
|
|
106
|
+
More in [examples/](examples/): questions, editing, conversations, running on RunPod,
|
|
107
|
+
a chat in the terminal and a server described by hand.
|
|
108
|
+
|
|
109
|
+
## Editing an exported workflow
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
import json
|
|
113
|
+
|
|
114
|
+
workflow = json.loads(Path("workflow_api.json").read_text()) # ComfyUI's Export (API)
|
|
115
|
+
agent = WorkflowAgent.from_workflow(workflow, inventory)
|
|
116
|
+
reply = await agent.send("change the prompt to a lighthouse at dusk and use 30 steps")
|
|
117
|
+
print(reply.changes.text()) # Changed: 6 (text), 3 (steps).
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The agent reads the workflow before changing it, changes only what was asked and keeps
|
|
121
|
+
the node ids.
|
|
122
|
+
|
|
123
|
+
## Replies
|
|
124
|
+
|
|
125
|
+
`send` returns an `AgentReply`:
|
|
126
|
+
|
|
127
|
+
| Field | What it holds |
|
|
128
|
+
|---|---|
|
|
129
|
+
| `status` | `completed`, `needs_input` (a question), `invalid` (problems the agent could not fix), `refused` or `error` |
|
|
130
|
+
| `message` | The agent's answer, in the user's language |
|
|
131
|
+
| `workflow` | The workflow in the API format, as plain JSON |
|
|
132
|
+
| `question` | For `needs_input`: `text` and `needs` (`image`, `video`, `audio`, `mask`, `answer`) |
|
|
133
|
+
| `changes` | Nodes added, removed and changed in this turn |
|
|
134
|
+
| `validation` | The checks above: `ok`, `problems`, `text()` |
|
|
135
|
+
| `input_files` | The user files the workflow loads |
|
|
136
|
+
| `usage` | Tokens of the turn: input, output, cache reads and writes, model calls |
|
|
137
|
+
|
|
138
|
+
`agent.workflow`, `agent.files` (the attached files the workflow loads),
|
|
139
|
+
`agent.previous_workflows`, `agent.view()` and `agent.validate()` give the same
|
|
140
|
+
information between turns.
|
|
141
|
+
|
|
142
|
+
## Running a workflow
|
|
143
|
+
|
|
144
|
+
The agent only builds workflows. To run one on a RunPod `worker-comfyui` endpoint:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
async with RunPodClient.from_env() as runpod:
|
|
148
|
+
job = await runpod.run(reply.workflow, files=agent.files)
|
|
149
|
+
if not job.ok:
|
|
150
|
+
raise RuntimeError(job.error)
|
|
151
|
+
for file in job.files:
|
|
152
|
+
if file.filename not in agent.files: # the worker also returns some of the inputs
|
|
153
|
+
Path(file.filename).write_bytes(file.data)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
`job.ok` is false when a node failed, even when RunPod reports the job `COMPLETED`;
|
|
157
|
+
`job.error` then names the node and the reason. RunPod takes requests of up to 10 MiB,
|
|
158
|
+
and the files go in them base64-encoded: about 7.5 MB of files per job.
|
|
159
|
+
|
|
160
|
+
`run` waits up to `timeout` seconds (600 by default) and then cancels the job. When
|
|
161
|
+
RunPod's API fails for a moment while the job runs, it asks for the status again.
|
|
162
|
+
|
|
163
|
+
## Configuration
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
WorkflowAgent(
|
|
167
|
+
inventory,
|
|
168
|
+
model="claude-opus-5-5", # any Claude model id
|
|
169
|
+
effort="high", # low, medium, high, xhigh or max
|
|
170
|
+
api_key=None, # default: ANTHROPIC_API_KEY from the environment or env_file
|
|
171
|
+
env_file=".env",
|
|
172
|
+
vision=True, # show attached images to the model
|
|
173
|
+
refusal_fallback=True, # let the API retry a declined request on another model
|
|
174
|
+
max_model_calls=40, # per turn
|
|
175
|
+
)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pip install -e ".[dev]"
|
|
182
|
+
pytest # offline tests
|
|
183
|
+
pytest --anthropic # the agent on the Claude API (paid tokens)
|
|
184
|
+
pytest --runpod # every pipeline on the RunPod endpoint (paid GPU time)
|
|
185
|
+
pytest --runpod --anthropic # everything, plus the agent's workflows run on the endpoint
|
|
186
|
+
pytest --cov=agent_comfy_ui_api_workflow --cov-report=term-missing # coverage
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# agent-comfy-ui-api-workflow
|
|
2
|
+
|
|
3
|
+
An async agent on Claude (through LangChain) that builds, edits and validates
|
|
4
|
+
[ComfyUI](https://github.com/comfyanonymous/ComfyUI) workflows in the API format, with
|
|
5
|
+
[comfy-ui-api-workflow](https://github.com/fabioantonioastore/comfy-ui-api-workflow).
|
|
6
|
+
|
|
7
|
+
- **Pipelines first.** The agent adds tested chains of nodes in one call:
|
|
8
|
+
`text_to_image`, `image_to_image`, `inpaint`, `outpaint`, `text_to_video`,
|
|
9
|
+
`image_to_video` (also between a start and an end image), `sound_to_video` (a photo
|
|
10
|
+
that talks or sings an audio), `text_to_audio` (music), `upscale_image_with_model`,
|
|
11
|
+
and video, audio and image editing. Single nodes come from a short list of essential
|
|
12
|
+
ones, only for steps no pipeline covers.
|
|
13
|
+
- **Your server's models only.** Generation pipelines are offered for the model
|
|
14
|
+
families found on the server, and every model name is checked against the server's
|
|
15
|
+
files. The families it knows:
|
|
16
|
+
|
|
17
|
+
| Family | Pipelines |
|
|
18
|
+
|---|---|
|
|
19
|
+
| SDXL | images: text to image, image to image, inpaint, outpaint |
|
|
20
|
+
| Wan 2.2 TI2V 5B | video from text or an image, 24 fps |
|
|
21
|
+
| Wan 2.2 A14B T2V / I2V, with the LightX2V 4-step LoRAs | video from text, an image, or a start and an end image, 16 fps |
|
|
22
|
+
| MiniMax H3 (with its turbo LoRA) | video with its own sound: speech, effects, music; 5 to 15 s |
|
|
23
|
+
| Wan 2.2 S2V | a photo animated in sync with an audio |
|
|
24
|
+
| ACE-Step 1.5 XL turbo | music, sung or instrumental |
|
|
25
|
+
| Upscale models (Real-ESRGAN...) | image upscaling |
|
|
26
|
+
|
|
27
|
+
MiniMax H3 has its own community license, which restricts where it may be used:
|
|
28
|
+
read it before serving that model.
|
|
29
|
+
- **It asks when it must.** A request that needs a file the user did not attach (an
|
|
30
|
+
image, a video, an audio, a mask), something the server cannot do, or a choice that
|
|
31
|
+
changes the result, comes back as a question instead of a guess.
|
|
32
|
+
- **Checked before it is delivered.** When the agent finishes, the workflow is
|
|
33
|
+
validated: an output node, every node connected, model files the server has, files
|
|
34
|
+
the user attached, and a JSON that loads back. Problems go back to the agent to fix.
|
|
35
|
+
- **It never runs the workflow.** You get the JSON for `POST /prompt` and the list of
|
|
36
|
+
files to upload with it.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install git+https://github.com/fabioantonioastore/agent-comfy-ui-api-workflow
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The agent reads `ANTHROPIC_API_KEY`, and `RunPodClient` reads `RUNPOD_API_KEY` and
|
|
45
|
+
`RUNPOD_ENDPOINT_ID`, from the environment or from a `.env` file (`NAME=value` or
|
|
46
|
+
`NAME: value` lines).
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from pathlib import Path
|
|
53
|
+
|
|
54
|
+
from agent_comfy_ui_api_workflow import RunPodClient, ServerInventory, WorkflowAgent
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
async def main():
|
|
58
|
+
# Once: ask the server which model files it has, and keep the answer. Workers that
|
|
59
|
+
# answer {"object_info": true} jobs give every loader's files; the official worker
|
|
60
|
+
# gets a probe instead, which misses the loaders its handler does not check.
|
|
61
|
+
async with RunPodClient.from_env() as runpod:
|
|
62
|
+
inventory = await runpod.discover_inventory()
|
|
63
|
+
inventory.save("inventory.json") # later: ServerInventory.load("inventory.json")
|
|
64
|
+
|
|
65
|
+
agent = WorkflowAgent(inventory)
|
|
66
|
+
reply = await agent.send("make an elephant with my face")
|
|
67
|
+
if reply.status == "needs_input":
|
|
68
|
+
print(reply.question.text, reply.question.needs) # ... ('image',)
|
|
69
|
+
photo = Path("me.jpg").read_bytes()
|
|
70
|
+
reply = await agent.send("here it is", files={"me.jpg": photo})
|
|
71
|
+
|
|
72
|
+
print(reply.status, reply.message)
|
|
73
|
+
print(reply.workflow) # the API format, ready for POST /prompt
|
|
74
|
+
print(reply.input_files) # ('me.jpg',): upload it with the workflow
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
asyncio.run(main())
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Each `send` is one turn of the same conversation, so the next request can change what
|
|
81
|
+
the agent built: "now make it 16:9 with 35 steps".
|
|
82
|
+
|
|
83
|
+
More in [examples/](examples/): questions, editing, conversations, running on RunPod,
|
|
84
|
+
a chat in the terminal and a server described by hand.
|
|
85
|
+
|
|
86
|
+
## Editing an exported workflow
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
import json
|
|
90
|
+
|
|
91
|
+
workflow = json.loads(Path("workflow_api.json").read_text()) # ComfyUI's Export (API)
|
|
92
|
+
agent = WorkflowAgent.from_workflow(workflow, inventory)
|
|
93
|
+
reply = await agent.send("change the prompt to a lighthouse at dusk and use 30 steps")
|
|
94
|
+
print(reply.changes.text()) # Changed: 6 (text), 3 (steps).
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
The agent reads the workflow before changing it, changes only what was asked and keeps
|
|
98
|
+
the node ids.
|
|
99
|
+
|
|
100
|
+
## Replies
|
|
101
|
+
|
|
102
|
+
`send` returns an `AgentReply`:
|
|
103
|
+
|
|
104
|
+
| Field | What it holds |
|
|
105
|
+
|---|---|
|
|
106
|
+
| `status` | `completed`, `needs_input` (a question), `invalid` (problems the agent could not fix), `refused` or `error` |
|
|
107
|
+
| `message` | The agent's answer, in the user's language |
|
|
108
|
+
| `workflow` | The workflow in the API format, as plain JSON |
|
|
109
|
+
| `question` | For `needs_input`: `text` and `needs` (`image`, `video`, `audio`, `mask`, `answer`) |
|
|
110
|
+
| `changes` | Nodes added, removed and changed in this turn |
|
|
111
|
+
| `validation` | The checks above: `ok`, `problems`, `text()` |
|
|
112
|
+
| `input_files` | The user files the workflow loads |
|
|
113
|
+
| `usage` | Tokens of the turn: input, output, cache reads and writes, model calls |
|
|
114
|
+
|
|
115
|
+
`agent.workflow`, `agent.files` (the attached files the workflow loads),
|
|
116
|
+
`agent.previous_workflows`, `agent.view()` and `agent.validate()` give the same
|
|
117
|
+
information between turns.
|
|
118
|
+
|
|
119
|
+
## Running a workflow
|
|
120
|
+
|
|
121
|
+
The agent only builds workflows. To run one on a RunPod `worker-comfyui` endpoint:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
async with RunPodClient.from_env() as runpod:
|
|
125
|
+
job = await runpod.run(reply.workflow, files=agent.files)
|
|
126
|
+
if not job.ok:
|
|
127
|
+
raise RuntimeError(job.error)
|
|
128
|
+
for file in job.files:
|
|
129
|
+
if file.filename not in agent.files: # the worker also returns some of the inputs
|
|
130
|
+
Path(file.filename).write_bytes(file.data)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
`job.ok` is false when a node failed, even when RunPod reports the job `COMPLETED`;
|
|
134
|
+
`job.error` then names the node and the reason. RunPod takes requests of up to 10 MiB,
|
|
135
|
+
and the files go in them base64-encoded: about 7.5 MB of files per job.
|
|
136
|
+
|
|
137
|
+
`run` waits up to `timeout` seconds (600 by default) and then cancels the job. When
|
|
138
|
+
RunPod's API fails for a moment while the job runs, it asks for the status again.
|
|
139
|
+
|
|
140
|
+
## Configuration
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
WorkflowAgent(
|
|
144
|
+
inventory,
|
|
145
|
+
model="claude-opus-5-5", # any Claude model id
|
|
146
|
+
effort="high", # low, medium, high, xhigh or max
|
|
147
|
+
api_key=None, # default: ANTHROPIC_API_KEY from the environment or env_file
|
|
148
|
+
env_file=".env",
|
|
149
|
+
vision=True, # show attached images to the model
|
|
150
|
+
refusal_fallback=True, # let the API retry a declined request on another model
|
|
151
|
+
max_model_calls=40, # per turn
|
|
152
|
+
)
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
pip install -e ".[dev]"
|
|
159
|
+
pytest # offline tests
|
|
160
|
+
pytest --anthropic # the agent on the Claude API (paid tokens)
|
|
161
|
+
pytest --runpod # every pipeline on the RunPod endpoint (paid GPU time)
|
|
162
|
+
pytest --runpod --anthropic # everything, plus the agent's workflows run on the endpoint
|
|
163
|
+
pytest --cov=agent_comfy_ui_api_workflow --cov-report=term-missing # coverage
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "agent-comfy-ui-api-workflow"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Async LangChain agent on Claude that builds, edits and validates ComfyUI API workflows."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Fabio Antonio Astore", email = "astore.a.fabio@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
]
|
|
19
|
+
dependencies = [
|
|
20
|
+
"comfy-ui-api-workflow==0.0.9",
|
|
21
|
+
"langchain==1.4.7",
|
|
22
|
+
"langchain-anthropic==1.7.6",
|
|
23
|
+
"langgraph==1.2.16",
|
|
24
|
+
"httpx==0.28.1",
|
|
25
|
+
"pillow==13.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=8.0",
|
|
31
|
+
"pytest-asyncio>=1.4.0",
|
|
32
|
+
"pytest-cov>=7.1",
|
|
33
|
+
"build>=1.2",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.hatch.version]
|
|
37
|
+
path = "src/agent_comfy_ui_api_workflow/__about__.py"
|
|
38
|
+
|
|
39
|
+
[tool.hatch.build.targets.wheel]
|
|
40
|
+
packages = ["src/agent_comfy_ui_api_workflow"]
|
|
41
|
+
|
|
42
|
+
# only-include keeps the src/ path, so the wheel built from the sdist finds the package.
|
|
43
|
+
[tool.hatch.build.targets.sdist]
|
|
44
|
+
only-include = ["src/agent_comfy_ui_api_workflow", "tests"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
testpaths = ["tests"]
|
|
48
|
+
asyncio_mode = "auto"
|
|
49
|
+
asyncio_default_fixture_loop_scope = "function"
|
|
50
|
+
markers = [
|
|
51
|
+
"runpod: sends real jobs to the RunPod ComfyUI endpoint in .env; needs --runpod",
|
|
52
|
+
"anthropic: calls the Claude API with ANTHROPIC_API_KEY; needs --anthropic",
|
|
53
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
version = "0.0.1"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from .__about__ import version as __version__
|
|
2
|
+
from ._impl.agent import AgentReply, WorkflowAgent
|
|
3
|
+
from ._impl.errors import (
|
|
4
|
+
AgentComfyUIError,
|
|
5
|
+
InputFileError,
|
|
6
|
+
InventoryError,
|
|
7
|
+
JobTimeoutError,
|
|
8
|
+
RunPodError,
|
|
9
|
+
SettingsError,
|
|
10
|
+
)
|
|
11
|
+
from ._impl.files import InputFile
|
|
12
|
+
from ._impl.inventory import ServerInventory
|
|
13
|
+
from ._impl.pipelines import MEDIA_PIPELINES, AgentPipeline
|
|
14
|
+
from ._impl.runpod import JobResult, OutputFile, RunPodClient
|
|
15
|
+
from ._impl.session import Question, WorkflowDiff
|
|
16
|
+
from ._impl.validation import Problem, ValidationReport
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"MEDIA_PIPELINES",
|
|
20
|
+
"AgentComfyUIError",
|
|
21
|
+
"AgentPipeline",
|
|
22
|
+
"AgentReply",
|
|
23
|
+
"InputFile",
|
|
24
|
+
"InputFileError",
|
|
25
|
+
"InventoryError",
|
|
26
|
+
"JobResult",
|
|
27
|
+
"JobTimeoutError",
|
|
28
|
+
"OutputFile",
|
|
29
|
+
"Problem",
|
|
30
|
+
"Question",
|
|
31
|
+
"RunPodClient",
|
|
32
|
+
"RunPodError",
|
|
33
|
+
"ServerInventory",
|
|
34
|
+
"SettingsError",
|
|
35
|
+
"ValidationReport",
|
|
36
|
+
"WorkflowAgent",
|
|
37
|
+
"WorkflowDiff",
|
|
38
|
+
"__version__",
|
|
39
|
+
]
|
|
File without changes
|