-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrok_api_node.py
More file actions
132 lines (114 loc) · 4.8 KB
/
Copy pathgrok_api_node.py
File metadata and controls
132 lines (114 loc) · 4.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import base64
import os
from io import BytesIO
import requests
from PIL import Image
from comfy_api.latest import io
XAI_RESPONSES_URL = "https://api.x.ai/v1/responses"
GROK_MODELS = [
"grok-4.5",
"grok-4.3",
"grok-4.20-reasoning",
"grok-4.20-non-reasoning",
"grok-4.20-multi-agent",
]
GROK_IMAGE_INPUTS = ["image", *[f"image_{index}" for index in range(2, 10)]]
def _image_to_data_url(image):
pixels = image[0].detach().cpu().clamp(0, 1).mul(255).byte().numpy()
image_pil = Image.fromarray(pixels)
buffer = BytesIO()
image_pil.save(buffer, format="PNG")
encoded = base64.b64encode(buffer.getvalue()).decode("ascii")
return f"data:image/png;base64,{encoded}"
def _get_response_text(data):
parts = []
for output in data.get("output", []):
if output.get("type") != "message":
continue
for content in output.get("content", []):
if content.get("type") == "output_text" and content.get("text"):
parts.append(content["text"])
return "\n".join(parts)
class SnJakeGrokApi(io.ComfyNode):
@classmethod
def define_schema(cls):
return io.Schema(
node_id="SnJakeGrokApi",
display_name="😎 Grok API",
category="😎 SnJake/API",
inputs=[
io.Combo.Input("model", options=GROK_MODELS),
io.String.Input("prompt", default="", multiline=True),
io.String.Input("api_key", default="", placeholder="xAI API key or XAI_API_KEY environment variable"),
io.Combo.Input("reasoning_effort", options=["default", "low", "medium", "high"]),
io.Int.Input("max_output_tokens", default=4096, min=1, max=32768),
io.Autogrow.Input(
"images",
template=io.Autogrow.TemplateNames(
io.Image.Input("image"),
names=GROK_IMAGE_INPUTS,
min=0,
),
tooltip="Optional images for analysis. Up to 9 images; the first image from each connected batch is used.",
),
],
outputs=[io.String.Output(display_name="text")],
)
@classmethod
def execute(cls, model, prompt, api_key, reasoning_effort, max_output_tokens, images: io.Autogrow.Type = None):
image_tensors = [images[name] for name in GROK_IMAGE_INPUTS if images and images.get(name) is not None]
return io.NodeOutput(cls._generate_text(model, prompt, api_key, reasoning_effort, max_output_tokens, image_tensors))
@staticmethod
def _generate_text(model, prompt, api_key, reasoning_effort, max_output_tokens, images):
api_key = api_key.strip() or os.getenv("XAI_API_KEY", "").strip()
if not api_key:
return "xAI API error: API key is required."
if not images:
input_data = prompt
else:
content = [
{"type": "input_image", "image_url": _image_to_data_url(image), "detail": "high"}
for image in images
]
content.append({"type": "input_text", "text": prompt})
input_data = [
{
"role": "user",
"content": content,
}
]
payload = {
"model": model,
"input": input_data,
"max_output_tokens": max_output_tokens,
"store": False,
}
if reasoning_effort != "default":
payload["reasoning"] = {"effort": reasoning_effort}
try:
response = requests.post(
XAI_RESPONSES_URL,
headers={
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json",
},
json=payload,
timeout=3600,
)
except requests.exceptions.RequestException as error:
return f"xAI API request failed: {error}"
try:
data = response.json()
except requests.exceptions.JSONDecodeError:
return f"xAI API returned HTTP {response.status_code} with an invalid JSON response."
if not response.ok:
error = data.get("error", {})
message = error.get("message") if isinstance(error, dict) else str(error)
return f"xAI API error ({response.status_code}): {message or 'Unknown error'}"
text = _get_response_text(data)
if not text:
return "xAI API error: response did not contain text."
return text
def generate(self, model, prompt, api_key, reasoning_effort, max_output_tokens, image=None):
images = [] if image is None else [image]
return (self._generate_text(model, prompt, api_key, reasoning_effort, max_output_tokens, images),)