10 Commits
Author SHA1 Message Date
Allan Saddi b834b3b1fd Bump version: 1.4.0 → 1.5.0 2026-10-14 18:17:35 -07:00
Allan Saddi 85423e6600 fix: Also explicitly request no streaming.
From experience elsewhere, there seem to be some providers that default
to streaming.

Also update dependencies in pyproject.toml to match requirements.txt.
2026-10-14 17:49:47 -07:00
Allan Saddi 033d1ac2a3 refactor: Drop openai package dependency.
Just use `requests` directly. May not be as robust, but I'll fix it as
problems come up.

Works for me.
2026-10-14 15:59:34 -07:00
Allan Saddi 6c23940081 Merge branch 'webbrain-one-webbrain/readme-improvement' 2026-08-11 18:10:25 -07:00
webbrain-one 2aefda956a docs: improve README 2026-08-12 01:53:18 +03:00
Allan Saddi cb8d164e71 Sync utils.py from private nodes to fix sub-graph issue. 2025-09-24 22:36:39 -07:00
Allan Saddi 6b8d59999e Sometimes remote APIs return no choices... 2025-08-09 15:45:10 -07:00
Allan Saddi 3a49fdade5 Merge branch 'ComfyNodePRs-update-publish-yaml' 2025-03-27 07:37:52 -07:00
snomiao de0c0d6b3f chore(publish): update workflow for node publishing
- Add permissions for issue writing
- Modify condition to check repository owner instead of fork status
- Update action version from main to v1 for stability and consistency
2025-01-25 07:36:15 +00:00
Allan Saddi b6dca3296c Add sample entries for LM Studio, Ollama.
I don't use these myself, feedback welcome.
2024-12-28 17:06:14 -08:00
8 changed files with 80 additions and 37 deletions
+5 -3
View File
@@ -8,17 +8,19 @@ on:
paths:
- "pyproject.toml"
permissions:
issues: write
jobs:
publish-node:
name: Publish Custom Node to registry
runs-on: ubuntu-latest
# if this is a forked repository. Skipping the workflow.
if: github.event.repository.fork == false
if: ${{ github.repository_owner == 'asaddi' }}
steps:
- name: Check out code
uses: actions/checkout@v4
- name: Publish Custom Node
uses: Comfy-Org/publish-node-action@main
uses: Comfy-Org/publish-node-action@v1
with:
## Add your own personal access token to your Github Repository secrets and reference it here.
personal_access_token: ${{ secrets.REGISTRY_ACCESS_TOKEN }}
+6
View File
@@ -1,3 +1,5 @@
# Yet Another LLM Node (for ComfyUI)
Yet another set of LLM nodes for [ComfyUI](https://github.com/comfyanonymous/ComfyUI). This one only supports OpenAI-like APIs, but of course can be used with local LLM providers such as [llama.cpp](https://github.com/ggerganov/llama.cpp) (and ollama, LM Studio, among many others).
@@ -69,6 +71,10 @@ The input is optional, meaning you can mute the node leading into it or even dis
The node will output whatever is currently being displayed in the textbox. The node text will also be saved in exported workflows and generated images.
## "Prepend/Append" Node
A simple utility node that takes a text input and optionally adds a prefix and/or suffix to it. This is useful for dynamically injecting instructions or context into prompts without altering the source text, as demonstrated in the `prompt-optimizer` example workflow.
## My Related Projects
* https://github.com/asaddi/YALLM-LlamaVision Basic ComfyUI node for Llama 3.2 Vision. Sampler/model nodes are cross-compatible with this project.
+10
View File
@@ -6,6 +6,16 @@ models:
api_key: none
model: whatever
- name: "[LOCAL] LM Studio"
base_url: http://localhost:1234/v1
api_key: none
model: changeme
- name: "[LOCAL] Ollama"
base_url: http://localhost:11434/v1
api_key: none
model: changeme
# Example of using a remote endpoint
# If prefixed with "os.environ/" (similar to LiteLLM), base_url and/or api_key
# will take their value from the named environment variable.
+26 -26
View File
@@ -6,9 +6,9 @@ from typing import Any, Optional
from aiohttp import web
from aiohttp.web_request import Request
import openai
from PIL.Image import Image
from pydantic import BaseModel
import requests
import torchvision.transforms.functional as F
import yaml
@@ -347,20 +347,19 @@ SamplerSetting = tuple[str, Any]
class LLMModel:
def __init__(self, prov_def: ProviderDefinition, model: str | None = None):
self._llm = openai.OpenAI(
base_url=prov_def.resolve_base_url(),
api_key=(
prov_def.resolve_api_key() or "none"
), # openai package requires something for API key
)
self._base_url = prov_def.resolve_base_url()
self._headers = {}
if (api_key := prov_def.resolve_api_key()) and api_key != "none":
self._headers["authorization"] = f"Bearer {api_key}"
if model is None:
self._model = getattr(prov_def, "model", None)
else:
self._model = model
def get_models(self):
models = list(self._llm.models.list())
return [m.id for m in models]
with requests.get(self._base_url + "/models", headers=self._headers) as r:
models_json = r.json()
return [m["id"] for m in models_json["data"]]
def chat_completion(
self,
@@ -404,34 +403,35 @@ class LLMModel:
sampler_order = []
extra_args = {}
extra_body = {}
for k, v in samplers:
sampler_order.append(k)
if k in (
"temperature",
"top_p",
): # The only ones supported directly by openai package
extra_args[k] = v
else:
# The rest have to go into "extra_body"
extra_body[k] = v
extra_body["samplers"] = (
extra_args[k] = v
extra_args["samplers"] = (
sampler_order # Hopefully won't cause an issue for non-llama.cpp endpoints
)
if seed is not None:
extra_args["seed"] = seed
# print(f'extra_args = {repr(extra_args)}')
# print(f'extra_body = {repr(extra_body)}')
# print(f"extra_args = {repr(extra_args)}")
output = self._llm.chat.completions.create(
messages=messages, model=self._model, extra_body=extra_body, **extra_args
)
req_json = {
"model": self._model,
"messages": messages,
"stream": False,
}
req_json.update(extra_args)
# TODO Since we're not streaming, we'll need some sort of timeout. How to specify that?
# TODO Since we're not streaming, we'll need some sort of timeout. TODO specify that
with requests.post(
self._base_url + "/chat/completions", json=req_json, headers=self._headers
) as r:
output = r.json()
return output.choices[0].message.content
if choices := output.get("choices", []):
return choices[0]["message"]["content"]
else:
return ""
class LLMProvider:
+8
View File
@@ -5,6 +5,14 @@ providers:
base_url: http://localhost:8080/v1
api_key: none
- name: "[LOCAL] LM Studio"
base_url: http://localhost:1234/v1
api_key: none
- name: "[LOCAL] Ollama"
base_url: http://localhost:11434/v1
api_key: none
# Example of using a remote endpoint
# If prefixed with "os.environ/" (similar to LiteLLM), base_url and/or api_key
# will take their value from the named environment variable.
+6 -4
View File
@@ -1,12 +1,14 @@
[project]
name = "comfyui-yallm-node"
description = "Yet another set of LLM nodes for ComfyUI (for local/remote OpenAI-like APIs, multi-modal models supported)"
version = "1.4.0"
version = "1.5.0"
license = {file = "LICENSE.txt"}
dependencies = [
"openai",
"pillow",
"pydantic",
"pyyaml"
"pyyaml",
"requests",
"torchvision"
]
[project.urls]
@@ -19,7 +21,7 @@ DisplayName = "ComfyUI-YALLM-node"
Icon = ""
[tool.bumpversion]
current_version = "1.4.0"
current_version = "1.5.0"
parse = "(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)"
serialize = ["{major}.{minor}.{patch}"]
search = "{current_version}"
+2 -3
View File
@@ -1,7 +1,6 @@
openai
pydantic
# These should already be installed by ComfyUI
pillow
pydantic
pyyaml
requests
torchvision
+17 -1
View File
@@ -1,3 +1,4 @@
# Copyright (c) 2024 Allan Saddi <allan@saddi.com>
class PromptUtils:
def __init__(self, prompt):
self.prompt = prompt
@@ -68,7 +69,7 @@ class WorkflowUtils:
and (workflow := self.extra_pnginfo.get("workflow")) is not None
and (nodes := workflow.get("nodes")) is not None
):
node_id = int(node_id)
node_id = str(node_id)
# TODO For the time being, we aren't caching anything.
# Concievably, if there are a large number of nodes, a lot of time
# could be wasted searching.
@@ -87,3 +88,18 @@ class WorkflowUtils:
# TODO When would there be a mismatch?
if slot < len(values):
values[slot] = value
class AnyType(str):
def __ne__(self, other):
if self == "*":
# If we're the wildcard, match anything
return False
return super().__ne__(other)
class ComboType(str):
def __ne__(self, other):
if self == "*" and isinstance(other, list):
return False
return True