Skip to content

Commit ebc047f

Browse files
Merge pull request #19 from EnragedAntelope/v2.0.0-audit
v2.0.0: remove two no-op widgets, add streaming/interrupt, structured output, honest diagnostics
2 parents 569c122 + b6c0a94 commit ebc047f

18 files changed

Lines changed: 1853 additions & 191 deletions

.github/workflows/test.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: Tests
2+
3+
on:
4+
push:
5+
branches: [main, master]
6+
pull_request:
7+
workflow_dispatch:
8+
9+
jobs:
10+
pytest:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
# Floor matches requires-python; the newest release guards against
16+
# deprecations landing in a future ComfyUI runtime.
17+
python-version: ["3.10", "3.13"]
18+
steps:
19+
- uses: actions/checkout@v4
20+
- uses: actions/setup-python@v5
21+
with:
22+
python-version: ${{ matrix.python-version }}
23+
cache: pip
24+
- name: Install test dependencies
25+
# requirements-dev.txt pulls in requirements.txt (requests, Pillow,
26+
# numpy, lmstudio). The suite stubs lmstudio and comfy when they are
27+
# missing, so it also has to pass without a running LM Studio - the
28+
# startup model fetch simply fails to connect and is handled.
29+
run: pip install -r requirements-dev.txt
30+
- name: Run tests
31+
run: pytest -q

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
# User configuration (survives updates)
22
lms_config/user_config.json
33

4+
# Local development worklogs / scratch notes (never published)
5+
worklogs/
6+
47
# Byte-compiled / optimized / DLL files
58
__pycache__/
69
*.py[cod]

AGENTS.md

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# EA_LMStudio — agent notes
2+
3+
A single ComfyUI custom node (`EA_LMStudio`, displayed as **EA LM Studio**) that
4+
runs text and vision generation against a local [LM Studio](https://lmstudio.ai/)
5+
server through the official `lmstudio` Python SDK. It returns three STRING
6+
outputs — `response`, `reasoning`, `troubleshooting` — and renders the response
7+
inside the node.
8+
9+
## Current state
10+
11+
_Last verified: 2026-08-09_
12+
13+
- **Status:** v2.0.0, published to the Comfy Registry from `main` on every
14+
`pyproject.toml` version change.
15+
- **Works:** model discovery + refresh (startup fetch and a live API route),
16+
text generation, multi-image VLM input, reasoning extraction (LM Studio's own
17+
split, with tag-regex fallback), structured JSON output, stop strings,
18+
context-overflow policy, speculative decoding with acceptance stats,
19+
cancellable streaming with a queue progress bar, VRAM unload of both the LLM
20+
and ComfyUI's own models, and migration of workflows saved by 1.x.
21+
- **In progress:** nothing outstanding.
22+
- **Known gaps:** LM Studio's `ttl`, tool/function calling (`.act()`), GBNF
23+
grammars, and load-time `seed` (real determinism, but it only applies when the
24+
node actually loads the model) are all supported by the SDK and not exposed.
25+
There is no automated frontend test — `web/ea_lmstudio.js` is verified by hand
26+
in a browser.
27+
- **Deep docs:** user-facing behaviour lives in `README.md`; nothing is
28+
duplicated here.
29+
30+
## Build / test / run
31+
32+
```bash
33+
pip install -r requirements-dev.txt # pulls requirements.txt too
34+
pytest -q # 100+ tests, no LM Studio needed
35+
```
36+
37+
The suite is dependency-light by design. `tests/conftest.py` stubs `lmstudio`
38+
and `comfy.model_management` **only when they are genuinely missing**, so a real
39+
install is never shadowed, and registers the repo root as a synthetic package so
40+
`LMStudio.py` (which uses relative imports) is importable without the checkout
41+
having to be named `EA_LMStudio`.
42+
43+
CI runs the same command on Python 3.10 and 3.13 (`.github/workflows/test.yml`).
44+
45+
To exercise the node for real, point a scratch script at a running LM Studio and
46+
stub `comfy.model_management` / `comfy.utils` — the node only needs
47+
`unload_all_models`, `soft_empty_cache`, `processing_interrupted`,
48+
`throw_exception_if_processing_interrupted`, `InterruptProcessingException` and
49+
`ProgressBar`.
50+
51+
## Layout
52+
53+
| File | Responsibility |
54+
|------|----------------|
55+
| `LMStudio.py` | The node: INPUT_TYPES, streaming, diagnostics, reasoning split |
56+
| `lms_params.py` | Pure widget-string → SDK-value helpers (stop strings, schema, fences) |
57+
| `lms_reasoning.py` | Tag/harmony reasoning regexes (fallback path) |
58+
| `lms_image.py` | ComfyUI IMAGE tensor → JPEG-safe PIL |
59+
| `model_fetcher.py` | `/v1/models` discovery, validation, cache |
60+
| `lms_config/` | `default_config.json` + gitignored `user_config.json` |
61+
| `web/ea_lmstudio.js` | Refresh toggle, in-node preview, 1.x workflow migration |
62+
| `example_workflows/` | Shipped examples, loadable by drag-and-drop |
63+
64+
Everything except `LMStudio.py` is deliberately free of the `lmstudio` SDK and
65+
`comfy` imports so it stays unit-testable.
66+
67+
## Things that will bite you
68+
69+
**The SDK silently discards unknown prediction-config keys.** `LlmPredictionConfig`
70+
is built with msgspec and drops anything not in `LlmPredictionConfigDict` rather
71+
than raising, so a wrong or wished-for key becomes a no-op that looks like it
72+
worked. v1.x shipped `presencePenalty` and `enableThinking` this way for
73+
releases. Before adding a parameter, check it exists in `LlmPredictionConfigDict`
74+
in the installed SDK, then confirm it round-trips in
75+
`PredictionResult.prediction_config`. `generate()` performs that diff on every
76+
run and warns — do not remove it.
77+
78+
**LM Studio has no presence penalty, no frequency penalty, no inference-time
79+
seed, and no thinking on/off flag.** `seed` exists only in the *load* config.
80+
The `seed` widget is a ComfyUI cache-buster and nothing more.
81+
82+
**Removing or reordering a widget corrupts saved workflows.** ComfyUI serialises
83+
`widgets_values` positionally, so a removal or a regroup shifts every later
84+
value. The migration in `web/ea_lmstudio.js` keys the old array by the v1.5.x
85+
widget-name order (two variants, with and without the `control_after_generate`
86+
widget ComfyUI inserts after an INT named `seed`) and writes values onto current
87+
widgets **by name**, which is why v2.0.0 could both drop two widgets and regroup
88+
the rest. Any future removal or reorder needs the same treatment, and the legacy
89+
order table must be kept.
90+
91+
**A stored node size is restored verbatim and is not re-checked against the
92+
widgets.** Adding a widget therefore leaves every previously saved workflow too
93+
short, and the overflow draws outside the node frame. `growToFitWidgets` in the
94+
frontend extension grows (never shrinks) the node on configure and after
95+
execution. Note that a stock ComfyUI `Note` node's textarea overhangs its own
96+
frame by ~13 units at any size — that is upstream behaviour, not a symptom of
97+
this, so don't chase it.
98+
99+
**A streamed prediction must be drained, not broken out of.** Breaking the `for`
100+
loop closes the generator and `stream.result()` then raises `GeneratorExit`.
101+
Call `stream.cancel()` and keep iterating; it ends promptly with
102+
`stop_reason == "userStopped"`.
103+
104+
**Every `LlmPredictionStats` field except `stop_reason` is Optional.** Formatting
105+
one with `:.2f` without a None check raised `TypeError` *after* a successful
106+
generation, which the outer handler then reported as a failure — throwing away
107+
text the model had already produced.
108+
109+
**`{"type": "json"}` without a schema does not constrain decoding.** Models
110+
routinely answer with a ```` ```json ```` fence. Only `jsonSchema` constrains the
111+
sampler.
112+
113+
## Conventions
114+
115+
- Registry publishing is driven by the `version` in `pyproject.toml`; the
116+
workflow compares it against `HEAD^` and skips when unchanged. Bump it in the
117+
same commit as any change worth shipping.
118+
- `lms_config/user_config.json` and `worklogs/` are gitignored. Never commit
119+
either, and never put a server address or token in a tracked file.
120+
- Keep the `CUSTOM_MODEL_OPTION` literal in `web/ea_lmstudio.js` in sync with
121+
`model_fetcher.py`, which is the source of truth.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@AGENTS.md

0 commit comments

Comments
 (0)