| name | tools |
| description | Agentic tools system for ComfyUI interaction. Use when implementing tools, function calling, or enabling LLM-driven actions in ComfyUI workflows. |
| version | 0.0.1 |
| license | MIT |
Agentic Tools System
Enable AI assistant to interact with ComfyUI workflows through tools/function calling.
References
Overview
The agentic tools system allows the AI assistant to:
- Add, remove, and modify ComfyUI nodes
- Connect nodes and manage workflows
- Query workflow information
- Execute actions based on user requests
Architecture
Frontend (TypeScript) Backend (Python)
┌─────────────────────┐ ┌──────────────────┐
│ useLocalRuntime │ │ OpenAI-compatible provider API │
│ ┌───────────────┐ │ │ ┌────────────┐ │
│ │ createTools() │ │ │ │ tools=TOOLS│ │
│ └───────────────┘ │ │ └────────────┘ │
│ ↓ │ │ ↓ │
│ ┌───────────────┐ │ │ ┌────────────┐ │
│ │ Executes │◄─┼───────┼──│ Declares │ │
│ │ Actions │ │ │ │ Tools │ │
│ └───────────────┘ │ │ └────────────┘ │
│ ↓ │ │ │
│ ┌───────────────┐ │ │ │
│ │ window.app │ │ │ │
│ │ (ComfyUI) │ │ │ │
│ └───────────────┘ │ │ │
└─────────────────────┘ └──────────────────┘
Quick Start
1. Frontend Setup
import { useLocalRuntime } from "@assistant-ui/react";
import { AssistantChatTransport } from "@assistant-ui/react-ai-sdk";
import { createTools } from "@/tools";
function App() {
const runtime = useLocalRuntime({
adapter: new AssistantChatTransport({ api: "/api/chat" }),
tools: window.app ? createTools({ app: window.app }) : {},
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Thread />
</AssistantRuntimeProvider>
);
}
2. Backend Setup
from tools_definitions import TOOLS
stream = await client.chat.completions.create(
model=OPENAI_MODEL,
messages=openai_messages,
tools=TOOLS,
stream=True,
)
Available Tools
| Tool | Description | Status |
|---|
addNode | Add node to workflow | ✅ Implemented |
removeNode | Remove node from workflow | ✅ Implemented |
connectNodes | Connect two nodes | ✅ Implemented |
getWorkflowInfo | Get workflow information (with widget data) | ✅ Implemented |
setNodeWidgetValue | Set any widget value on a node | ✅ Implemented |
fillPromptNode | Set prompt text on CLIPTextEncode | ✅ Implemented |
refreshEnvironment | Rescan installed nodes, packages, models | ✅ Implemented |
searchInstalledNodes | Search installed node types | ✅ Implemented |
readDocumentation | Fetch documentation for a topic | ✅ Implemented |
createSkill | Create a persistent user skill | ✅ Implemented |
deleteSkill | Delete a user skill by slug | ✅ Implemented |
updateSkill | Update a user skill by slug (name, description, instructions) | ✅ Implemented |
Folder Structure
ui/src/tools/
├── types.ts # Shared types
├── index.ts # Tool registry
├── definitions/ # Zod schemas & metadata
│ ├── add-node.ts
│ ├── remove-node.ts
│ ├── connect-nodes.ts
│ ├── get-workflow-info.ts
│ ├── set-node-widget-value.ts
│ ├── fill-prompt-node.ts
│ ├── create-skill.ts
│ ├── delete-skill.ts
│ ├── update-skill.ts
│ ├── refresh-environment.ts
│ ├── search-installed-nodes.ts
│ └── read-documentation.ts
└── implementations/ # Execution logic
├── add-node.ts
├── remove-node.ts
├── connect-nodes.ts
├── get-workflow-info.ts
├── set-node-widget-value.ts
├── fill-prompt-node.ts
├── create-skill.ts
├── delete-skill.ts
├── update-skill.ts
├── refresh-environment.ts
├── search-installed-nodes.ts
└── read-documentation.ts
tools_definitions.py # Backend tool declarations (single source of truth)
Key Concepts
Tool Context
All tools receive a ToolContext with access to ComfyUI:
interface ToolContext {
app: ComfyApp;
}
Tool Result
Tools return a standardized result:
interface ToolResult<T = unknown> {
success: boolean;
data?: T;
error?: string;
}
Execution Flow
- User Request → "Add a KSampler node"
- LLM Decision → Calls
addNode tool
- Frontend Execution → Creates node in ComfyUI
- Result → Returns node ID and position
- LLM Response → "I've added a KSampler node at position..."
Adding a New Tool
1. Create Definition
import { z } from 'zod';
export const myToolSchema = z.object({
param: z.string().describe("Parameter description")
});
export const myToolDefinition = {
name: "myTool",
description: "What the tool does",
parameters: myToolSchema,
};
2. Create Implementation
export async function executeMyTool(
params: MyToolParams,
context: ToolContext
): Promise<ToolResult> {
const { app } = context;
try {
return { success: true, data: { } };
} catch (error) {
return {
success: false,
error: error instanceof Error ? error.message : "Unknown error"
};
}
}
3. Register Tool
export function createTools(context: ToolContext) {
return {
[myToolDefinition.name]: {
description: myToolDefinition.description,
parameters: myToolDefinition.parameters,
execute: async (params) => executeMyTool(params, context)
}
};
}
4. Add to Backend
TOOLS.append({
"type": "function",
"function": {
"name": "myTool",
"description": "What the tool does",
"parameters": {
"type": "object",
"properties": {
"param": {"type": "string"}
}
}
}
})
Best Practices
- Always validate
app and app.graph exist
- Use try-catch and return proper
ToolResult
- Update canvas with
app.graph.setDirtyCanvas(true, true)
- Write clear descriptions for LLM understanding
- Keep tools focused - one action per tool
- Type safety - leverage TypeScript and Zod
Troubleshooting
Tools not executing
- Check
window.app is available
- Verify
useLocalRuntime is used (not useChatRuntime)
- Ensure tools are registered in runtime
LLM not using tools
- Add
tools=TOOLS to backend API call
- Use a model that supports function calling
- Add system message explaining available tools
Backend integration issues
- Verify tool definitions match frontend
- Check SSE event format for tool calls
- Test with simple tool first (e.g.,
getWorkflowInfo)
Next Steps
- Review architecture.md for design patterns
- Follow implementation.md for setup
- Check examples.md for usage patterns
- Read backend-integration.md for streaming setup
Resources