Add LICENSE, pyproject.toml, workflow screenshots, and update script headers
- LICENSE.txt: dual license (CC BY-NC 4.0 / commercial) with full attribution for models (Gen-Searcher-8B, Qwen3-VL-8B), runtime (SGLang), services (Serper, Jina), tools (gallery-dl), and libraries (PyTorch, Pillow, NumPy) - pyproject.toml: project metadata with publisher id 'ericrollei' - All .py files: expanded license/attribution headers per-file - README.md: added Example Workflow section with workflow.png and four annotated screenshots (prompting, results, generation, sharpening) - workflows/: workflow.png + Gen_Search_Prompting, Results, Generation_example, Sharpening screenshots
This commit is contained in:
+174
@@ -0,0 +1,174 @@
|
||||
# Dual License
|
||||
|
||||
This software is available under a dual licensing model.
|
||||
|
||||
## For Non-Commercial Use
|
||||
|
||||
This work is licensed under a modified Creative Commons Attribution-NonCommercial 4.0
|
||||
International License, with additional requirements:
|
||||
|
||||
### Attribution Requirements
|
||||
|
||||
1. **Mandatory Credit**: You must give appropriate credit to Eric Hiss as the original
|
||||
author by:
|
||||
- Including a link to the original repository
|
||||
(https://github.com/EricRollei/Visual_Research) in any documentation
|
||||
- Maintaining the original copyright and license notices in all copies or substantial
|
||||
portions of the software
|
||||
- Including attribution comments in your code that clearly state
|
||||
"Based on work by Eric Hiss (https://github.com/EricRollei/Visual_Research)"
|
||||
|
||||
2. **Modification Notice**: If you modify the software, you must clearly indicate what
|
||||
changes were made and attribute the original work to Eric Hiss.
|
||||
|
||||
3. **No Endorsement**: You may not use the name of the author or contributors to endorse
|
||||
or promote products derived from this software without specific prior written permission.
|
||||
|
||||
### Additional Restrictions
|
||||
|
||||
- **NonCommercial** — You may not use the material for commercial purposes.
|
||||
- **No Redistribution Without Attribution** — You may not redistribute this code without
|
||||
the required attribution as specified above.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
PURPOSE AND NONINFRINGEMENT.
|
||||
|
||||
### For the full text of the CC BY-NC 4.0 License
|
||||
|
||||
Visit http://creativecommons.org/licenses/by-nc/4.0/ or send a letter to Creative Commons,
|
||||
PO Box 1866, Mountain View, CA 94042, USA.
|
||||
|
||||
---
|
||||
|
||||
## For Commercial Use
|
||||
|
||||
For commercial use of this software, a separate commercial license is required.
|
||||
|
||||
Please contact Eric Hiss at eric@rollei.us or eric@historic.camera for licensing options
|
||||
and terms.
|
||||
|
||||
### Commercial License Terms (General Overview)
|
||||
|
||||
1. **Definition of Commercial Use**: Any use of the software that is primarily intended
|
||||
for or directed toward commercial advantage or monetary compensation.
|
||||
|
||||
2. **License Grant**: Subject to payment of applicable fees and compliance with the terms
|
||||
of the commercial license agreement, licensee will be granted a non-exclusive,
|
||||
non-transferable license to use the software for commercial purposes.
|
||||
|
||||
3. **Mandatory Attribution**: All commercial uses must include appropriate attribution to
|
||||
Eric Hiss and a link to the original repository in a manner appropriate to the medium,
|
||||
visible to end users.
|
||||
|
||||
4. **Restrictions**: The commercial license does not allow redistribution, modification,
|
||||
or creation of derivative works without explicit permission and additional licensing.
|
||||
|
||||
5. **Support**: Commercial licenses may include support options depending on the specific
|
||||
agreement.
|
||||
|
||||
6. **Liability**: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
|
||||
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
|
||||
THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For detailed commercial license terms and pricing, please contact the author directly.
|
||||
|
||||
---
|
||||
|
||||
## Models
|
||||
|
||||
This project is designed to work with the following AI models:
|
||||
|
||||
### Gen-Searcher-8B
|
||||
A fine-tuned vision-language model trained to perform multi-hop web research for
|
||||
image generation. Fine-tuned from Qwen3-VL-8B by Eric Hiss.
|
||||
- Base model: Qwen3-VL-8B (see Qwen3-VL below)
|
||||
- Fine-tune author: Eric Hiss
|
||||
- Intended use: agentic web research and visual reference gathering inside ComfyUI
|
||||
|
||||
### Qwen3-VL-8B
|
||||
Vision-language model by Alibaba Cloud / Qwen Team.
|
||||
- License: Apache 2.0
|
||||
- Repository: https://github.com/QwenLM/Qwen2.5-VL
|
||||
- Model card: https://huggingface.co/Qwen/Qwen2.5-VL-7B-Instruct
|
||||
- Paper: https://arxiv.org/abs/2502.13923
|
||||
- Copyright: Copyright (c) Alibaba Cloud. All rights reserved.
|
||||
|
||||
---
|
||||
|
||||
## Inference Runtime
|
||||
|
||||
### SGLang
|
||||
Fast inference and serving framework for LLMs and VLMs.
|
||||
- License: Apache 2.0
|
||||
- Repository: https://github.com/sgl-project/sglang
|
||||
- Copyright: Copyright (c) 2023-2024 SGLang Team
|
||||
|
||||
---
|
||||
|
||||
## Third-Party Services
|
||||
|
||||
This project optionally integrates with external web services. Each service is subject to
|
||||
its own terms of service and privacy policy, which users are responsible for reviewing and
|
||||
accepting before use.
|
||||
|
||||
### Serper (Google Search API)
|
||||
Web search service used for research queries.
|
||||
- Website: https://serper.dev
|
||||
- Terms of Service: https://serper.dev/terms
|
||||
- Free tier: 2,500 queries/month; paid plans available
|
||||
- Users must supply their own API key.
|
||||
|
||||
### Jina AI Reader
|
||||
Web page content extraction service.
|
||||
- Website: https://jina.ai/reader/
|
||||
- Terms of Service: https://jina.ai/legal/terms
|
||||
- Free tier available; paid plans available
|
||||
- Users must supply their own API key (or use anonymously within rate limits).
|
||||
|
||||
---
|
||||
|
||||
## Third-Party Tools
|
||||
|
||||
### gallery-dl
|
||||
Command-line program to download image galleries from various websites.
|
||||
- License: GNU General Public License v2.0 (GPL-2.0)
|
||||
- Repository: https://github.com/mikf/gallery-dl
|
||||
- Copyright: Copyright (c) 2014-2024 Mike Fährmann
|
||||
- Note: gallery-dl is an optional external binary invoked as a subprocess.
|
||||
It is not bundled with this package. The GPL-2.0 license applies to gallery-dl
|
||||
itself; this project does not incorporate gallery-dl source code.
|
||||
|
||||
---
|
||||
|
||||
## Third-Party Libraries
|
||||
|
||||
This project depends on the following third-party Python libraries:
|
||||
|
||||
### PyTorch
|
||||
Open source machine learning framework.
|
||||
- License: BSD 3-Clause ("New BSD License")
|
||||
- Repository: https://github.com/pytorch/pytorch
|
||||
- Copyright: Copyright (c) 2016-present, Facebook Inc. All rights reserved.
|
||||
- Full license text: https://github.com/pytorch/pytorch/blob/main/LICENSE
|
||||
|
||||
### Pillow (PIL Fork)
|
||||
Python Imaging Library fork for image processing.
|
||||
- License: Historical Permission Notice and Disclaimer (HPND)
|
||||
- Repository: https://github.com/python-pillow/Pillow
|
||||
- Copyright: Copyright (c) 2010-present by Jeffrey A. Clark and contributors
|
||||
- Full license text: https://github.com/python-pillow/Pillow/blob/main/LICENSE
|
||||
|
||||
### NumPy
|
||||
Fundamental package for scientific computing with Python.
|
||||
- License: BSD 3-Clause
|
||||
- Repository: https://github.com/numpy/numpy
|
||||
- Copyright: Copyright (c) 2005-present NumPy Developers. All rights reserved.
|
||||
- Full license text: https://github.com/numpy/numpy/blob/main/LICENSE.txt
|
||||
|
||||
---
|
||||
|
||||
Copyright (c) 2026 Eric Hiss. All rights reserved.
|
||||
@@ -32,6 +32,48 @@ when using web thumbnails with edit-mode models.
|
||||
|
||||
---
|
||||
|
||||
## Example Workflow
|
||||
|
||||
An example ComfyUI workflow is included in the `workflows/` folder.
|
||||
Open `workflows/workflow.png` in ComfyUI to load it directly (the PNG embeds the full
|
||||
workflow JSON).
|
||||
|
||||
### Full workflow overview
|
||||
|
||||

|
||||
|
||||
### Prompting — Gen-Searcher research and grounded prompt
|
||||
|
||||
The Gen-Searcher node takes your brief subject description, performs multi-hop web research
|
||||
(text search, image search, optional page browsing), and outputs a rich grounded prompt
|
||||
along with up to four reference images.
|
||||
|
||||

|
||||
|
||||
### Visual search results
|
||||
|
||||
Reference images retrieved from the web and returned as individual IMAGE outputs and a
|
||||
combined batch tensor.
|
||||
|
||||

|
||||
|
||||
### Generation
|
||||
|
||||
The grounded prompt and (optionally) the combined reference description feed into the
|
||||
generation node of your choice.
|
||||
|
||||

|
||||
|
||||
### Post-processing — sharpening and segmentation
|
||||
|
||||
Optional post-processing stage using Richardson-Lucy deconvolution, Smart Sharpening,
|
||||
subject segmentation masking (SAM + GroundingDINO), depth estimation, and metadata
|
||||
embedding before saving.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Typical Workflows
|
||||
|
||||
### With any text-to-image model
|
||||
|
||||
+13
-2
@@ -1,6 +1,17 @@
|
||||
# Eric Visual Research - ComfyUI Node Package
|
||||
# Copyright (c) 2026 Eric Hiss. All rights reserved.
|
||||
# Licensed under the terms in LICENSE.txt (CC BY-NC 4.0 / Commercial dual license).
|
||||
# https://github.com/EricRollei/Eric_Visual_Research
|
||||
# License: CC BY-NC 4.0 (non-commercial) / Commercial license — see LICENSE.txt
|
||||
# Repository: https://github.com/EricRollei/Visual_Research
|
||||
#
|
||||
# Third-party acknowledgements:
|
||||
# Gen-Searcher-8B fine-tuned Qwen3-VL-8B by Eric Hiss
|
||||
# Qwen3-VL-8B Alibaba Cloud / Qwen Team (Apache 2.0)
|
||||
# SGLang LMSYS / SGLang Team (Apache 2.0)
|
||||
# Serper Search API https://serper.dev (service)
|
||||
# Jina AI Reader https://jina.ai/reader/ (service)
|
||||
# gallery-dl Mike Fährmann (GPL-2.0, external binary)
|
||||
# PyTorch Meta / PyTorch contributors (BSD 3-Clause)
|
||||
# Pillow Jeffrey A. Clark et al. (HPND)
|
||||
"""
|
||||
Eric Visual Research - ComfyUI Node Package
|
||||
|
||||
|
||||
+3
-1
@@ -1,5 +1,7 @@
|
||||
# Eric Visual Research - Node Package
|
||||
# Eric Visual Research - Node Registry
|
||||
# Copyright (c) 2026 Eric Hiss. All rights reserved.
|
||||
# License: CC BY-NC 4.0 (non-commercial) / Commercial license — see LICENSE.txt
|
||||
# Repository: https://github.com/EricRollei/Visual_Research
|
||||
|
||||
from .eric_gen_searcher_node import EricGenSearcherNode
|
||||
from .eric_reference_describer import EricReferenceDescriber
|
||||
|
||||
+170
-49
@@ -1,6 +1,17 @@
|
||||
# Eric Visual Research - Eric Gen-Searcher Node
|
||||
# Copyright (c) 2026 Eric Hiss. All rights reserved.
|
||||
# Licensed under the terms in LICENSE.txt (CC BY-NC 4.0 / Commercial dual license).
|
||||
# https://github.com/EricRollei/Eric_Visual_Research
|
||||
# License: CC BY-NC 4.0 (non-commercial) / Commercial license - see LICENSE.txt
|
||||
# Repository: https://github.com/EricRollei/Visual_Research
|
||||
#
|
||||
# Third-party acknowledgements:
|
||||
# Gen-Searcher-8B Fine-tuned Qwen3-VL-8B; served via SGLang
|
||||
# Qwen3-VL-8B Alibaba Cloud / Qwen Team (Apache 2.0)
|
||||
# SGLang LMSYS / SGLang Team (Apache 2.0)
|
||||
# Serper Search API https://serper.dev
|
||||
# Jina AI Reader https://jina.ai/reader/
|
||||
# gallery-dl Mike Fährmann (GPL-2.0, external binary)
|
||||
# PyTorch Meta / PyTorch contributors (BSD 3-Clause)
|
||||
# Pillow Jeffrey A. Clark et al. (HPND)
|
||||
"""
|
||||
Eric Gen-Searcher Node
|
||||
Agentic web research for grounded image generation using Gen-Searcher-8B.
|
||||
@@ -8,6 +19,13 @@ Agentic web research for grounded image generation using Gen-Searcher-8B.
|
||||
Platform-agnostic: outputs STRING (grounded prompt) and IMAGE (reference images).
|
||||
Works with any downstream generation model - Flux, SDXL, Qwen-Image, etc.
|
||||
|
||||
Two-field mode (recommended):
|
||||
base_prompt - creative/compositional direction ("dramatic low-angle portrait...")
|
||||
search_query - specific subject to research visually ("Zendaya Challengers outfit")
|
||||
|
||||
The agent uses search_query for targeted web/image searches, then writes a final
|
||||
prompt that fulfills base_prompt enriched with the visual details it discovered.
|
||||
|
||||
Author: Eric Hiss (GitHub: EricRollei)
|
||||
"""
|
||||
|
||||
@@ -38,13 +56,11 @@ logger = logging.getLogger(__name__)
|
||||
_PACKAGE_ROOT = Path(__file__).resolve().parent.parent
|
||||
_CONFIG_PATH = _PACKAGE_ROOT / "api_keys.ini"
|
||||
_REF_MAX_DIM = 768
|
||||
_CANDIDATE_MULTIPLIER = 3 # collect 3x slots so we have fallbacks when downloads fail
|
||||
_CANDIDATE_MULTIPLIER = 3
|
||||
|
||||
|
||||
# gallery-dl paths - auto-detected from common locations, or set explicitly
|
||||
# in api_keys.ini under [gallery_dl] section
|
||||
def _find_gallery_dl() -> Tuple[str, str]:
|
||||
"""Find gallery-dl executable and config from common install locations."""
|
||||
# Check ini config first
|
||||
if _CONFIG_PATH.exists():
|
||||
cfg = configparser.ConfigParser()
|
||||
cfg.read(str(_CONFIG_PATH), encoding="utf-8")
|
||||
@@ -53,16 +69,12 @@ def _find_gallery_dl() -> Tuple[str, str]:
|
||||
if exe and os.path.exists(exe):
|
||||
return exe, conf
|
||||
|
||||
# Common Windows locations
|
||||
candidates_exe = [
|
||||
# ComfyUI embedded python
|
||||
r"A:\Comfy25\ComfyUI_windows_portable\python_embeded\Scripts\gallery-dl.exe",
|
||||
r"A:\Comfy_Dec\python_embeded\Scripts\gallery-dl.exe",
|
||||
# Standard Python installs
|
||||
os.path.join(os.environ.get("APPDATA", ""), "Python", "Scripts", "gallery-dl.exe"),
|
||||
os.path.join(os.environ.get("LOCALAPPDATA", ""), "Programs", "Python",
|
||||
"Python312", "Scripts", "gallery-dl.exe"),
|
||||
# PATH lookup
|
||||
shutil.which("gallery-dl") or "",
|
||||
]
|
||||
candidates_conf = [
|
||||
@@ -83,7 +95,7 @@ _GALLERY_DL_EXE, _GALLERY_DL_CONF = _find_gallery_dl()
|
||||
|
||||
_BLOCKED_CDN_DOMAINS = {
|
||||
"lookaside.instagram.com", "lookaside.fbsbx.com", "lookaside.facebook.com",
|
||||
"scontent.fbcdn.net", "scontent.instagram.com",
|
||||
"scontent.fbcdn.net", "scontent.instagram.com",
|
||||
"external.fwfw1-1.fna.fbcdn.net", "external.fwfw1-2.fna.fbcdn.net",
|
||||
"t.co",
|
||||
}
|
||||
@@ -132,16 +144,36 @@ def _is_gallery_dl_page(url: str) -> bool:
|
||||
# System prompt
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_GEN_SEARCHER_SYSTEM = """You are Gen-Searcher, an expert AI assistant for knowledge-grounded image generation. Your task is to research the user's image generation prompt, gather visual references, and then write a final detailed image generation prompt.
|
||||
_GEN_SEARCHER_SYSTEM = """You are Gen-Searcher, an expert AI assistant for knowledge-grounded image generation. Your task is to research visual references and write a final detailed image generation prompt.
|
||||
|
||||
You have access to three tools:
|
||||
- search(query): Search the web for text information
|
||||
- image_search(query): Search for reference images of a subject
|
||||
- browse(url): Read the full content of a web page
|
||||
|
||||
## Input Formats
|
||||
|
||||
You may receive ONE of two formats:
|
||||
|
||||
FORMAT A - Single prompt (research AND generate from the same text):
|
||||
The user's full prompt. Extract searchable visual subjects and research them.
|
||||
|
||||
FORMAT B - Separated fields (best results):
|
||||
SEARCH QUERY: <specific subject/terms to research visually>
|
||||
BASE PROMPT: <the creative/compositional direction for the final image>
|
||||
|
||||
When FORMAT B is used:
|
||||
- Use the SEARCH QUERY terms for ALL your web and image searches
|
||||
- The SEARCH QUERY is specific and visual - trust it for search terms
|
||||
- The BASE PROMPT describes the creative intent: lighting, mood, composition,
|
||||
style, camera angle, narrative context
|
||||
- Your final prompt must FULFILL the BASE PROMPT, ENRICHED with the
|
||||
accurate visual details you discovered by researching the SEARCH QUERY
|
||||
|
||||
## Research Strategy
|
||||
- Aim for 3-6 searches - enough to be thorough, not exhaustive
|
||||
- Prioritize visual details: colors, textures, shapes, proportions, distinctive features
|
||||
- For FORMAT B: focus searches tightly on the SEARCH QUERY subject
|
||||
- Stop when you have enough visual information to write a complete description
|
||||
|
||||
## CRITICAL - Final Output Format
|
||||
@@ -151,6 +183,8 @@ When you have enough information, output ONLY a single paragraph of descriptive
|
||||
- Just one flowing paragraph describing exactly what to generate
|
||||
- Must be 150-300 words of vivid visual description
|
||||
- Start directly with the subject, style, lighting, etc.
|
||||
- For FORMAT B: weave BASE PROMPT's creative direction with the visual
|
||||
details you discovered through the SEARCH QUERY research
|
||||
|
||||
Example of correct output:
|
||||
"A cinematic portrait of a young woman with long dark braided hair, wearing an oversized magenta Adidas hoodie with white logo detailing on the hood panels, standing on a red clay tennis court. The hoodie features drawstring closures and a relaxed athletic fit. She wears white tennis shorts and clean white sneakers. Soft afternoon light falls from the left, casting gentle shadows. The background shows blurred court lines and a net. Shot at eye level with a medium telephoto perspective, shallow depth of field. Photorealistic, high detail."
|
||||
@@ -307,7 +341,6 @@ def _download_direct(url: str, timeout: int = 10) -> Optional[Image.Image]:
|
||||
|
||||
def _download_with_gallery_dl(page_url: str, timeout: int = 45) -> Optional[Image.Image]:
|
||||
if not _GALLERY_DL_EXE or not os.path.exists(_GALLERY_DL_EXE):
|
||||
print(f"[EricGenSearcher] gallery-dl not found")
|
||||
return None
|
||||
tmp_dir = tempfile.mkdtemp(prefix="gensearch_")
|
||||
try:
|
||||
@@ -499,23 +532,71 @@ def _execute_tool(fn_name, fn_args, serper_key, jina_key,
|
||||
return f"[unknown tool: {fn_name}]"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build user message - handles single-prompt and two-field formats
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_user_message(base_prompt: str, search_query: str) -> str:
|
||||
"""Construct the user message for the agent.
|
||||
|
||||
Two-field format (FORMAT B) when search_query is provided:
|
||||
Separates the visual search subject from the creative direction,
|
||||
giving the model clear guidance on what to search for vs what to
|
||||
generate. Produces better-targeted image searches and more accurate
|
||||
visual descriptions than prompting with a combined creative prompt.
|
||||
|
||||
Single-prompt format (FORMAT A) when search_query is blank:
|
||||
Original behaviour - agent extracts its own search terms from the prompt.
|
||||
"""
|
||||
sq = search_query.strip()
|
||||
bp = base_prompt.strip()
|
||||
|
||||
if sq:
|
||||
return (
|
||||
f"SEARCH QUERY (use these specific terms for your web and image searches):\n"
|
||||
f"{sq}\n\n"
|
||||
f"BASE PROMPT (the creative/compositional direction for the final image):\n"
|
||||
f"{bp}\n\n"
|
||||
f"Research the SEARCH QUERY to discover accurate visual details and find "
|
||||
f"reference images. Then write a final image generation prompt that fulfills "
|
||||
f"the BASE PROMPT, enriched with the visual details you discovered."
|
||||
)
|
||||
else:
|
||||
return bp
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Agent loop
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _run_agent_loop(
|
||||
prompt, api_url, model, serper_key, jina_key,
|
||||
max_hops=8, enable_browse=True, max_ref_images=4,
|
||||
base_prompt: str,
|
||||
search_query: str,
|
||||
api_url: str,
|
||||
model: str,
|
||||
serper_key: str,
|
||||
jina_key: str,
|
||||
max_hops: int = 8,
|
||||
enable_browse: bool = True,
|
||||
max_ref_images: int = 4,
|
||||
) -> Tuple[str, List[Dict], str]:
|
||||
"""Multi-hop research loop.
|
||||
|
||||
Returns (grounded_prompt, image_entries, search_log).
|
||||
Uses search_query for targeted searches when provided.
|
||||
"""
|
||||
max_candidates = max_ref_images * _CANDIDATE_MULTIPLIER
|
||||
|
||||
user_message = _build_user_message(base_prompt, search_query)
|
||||
|
||||
messages = [
|
||||
{"role": "system", "content": _GEN_SEARCHER_SYSTEM},
|
||||
{"role": "user", "content": prompt},
|
||||
{"role": "user", "content": user_message},
|
||||
]
|
||||
image_entries: List[Dict] = []
|
||||
log_lines: List[str] = []
|
||||
hops = 0
|
||||
grounded = prompt
|
||||
grounded = base_prompt # safe fallback
|
||||
|
||||
while hops < max_hops:
|
||||
response = _chat_completion(api_url, messages, _TOOLS, model, max_tokens=2048)
|
||||
@@ -530,7 +611,7 @@ def _run_agent_loop(
|
||||
grounded = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip()
|
||||
grounded = re.sub(r"<tool_call>.*", "", grounded, flags=re.DOTALL).strip()
|
||||
if not grounded:
|
||||
grounded = prompt
|
||||
grounded = base_prompt
|
||||
if _is_analysis_output(grounded):
|
||||
grounded = _cleanup_prompt(grounded, api_url, model, messages + [message])
|
||||
break
|
||||
@@ -562,9 +643,9 @@ def _run_agent_loop(
|
||||
"no explanations. Start with the subject."
|
||||
),
|
||||
})
|
||||
resp = _chat_completion(api_url, messages, None, model, max_tokens=1024)
|
||||
resp = _chat_completion(api_url, messages, None, model, max_tokens=1024)
|
||||
content = resp["choices"][0]["message"].get("content", "").strip()
|
||||
grounded = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip() or prompt
|
||||
grounded = re.sub(r"<think>.*?</think>", "", content, flags=re.DOTALL).strip() or base_prompt
|
||||
if _is_analysis_output(grounded):
|
||||
grounded = _cleanup_prompt(grounded, api_url, model, messages)
|
||||
|
||||
@@ -624,17 +705,24 @@ class EricGenSearcherNode:
|
||||
"""
|
||||
Agentic web research for grounded image generation.
|
||||
|
||||
Sends your prompt to a running Gen-Searcher-8B model (via SGLang),
|
||||
which autonomously searches the web for text information and visual
|
||||
references, then returns a grounded prompt + reference images.
|
||||
TWO-FIELD MODE (recommended):
|
||||
search_query - specific subject to research visually
|
||||
e.g. "Zendaya Challengers movie tennis outfit"
|
||||
base_prompt - creative/compositional direction
|
||||
e.g. "dramatic low-angle portrait, golden hour, fashion photography"
|
||||
|
||||
The agent searches for search_query to get accurate visual details,
|
||||
then writes a prompt that fulfills base_prompt enriched with those details.
|
||||
Better image searches + better final prompt than single-field mode.
|
||||
|
||||
SINGLE-FIELD MODE (backward compatible):
|
||||
Leave search_query blank. The agent extracts its own search terms
|
||||
from base_prompt. Same as previous behaviour.
|
||||
|
||||
PLATFORM AGNOSTIC - outputs wire into any generation node:
|
||||
grounded_prompt → any STRING prompt input (Flux, SDXL, Qwen-Image, etc.)
|
||||
ref_image_1..4 → any IMAGE input or Eric Reference Describer
|
||||
reference_images → batch IMAGE for multi-image nodes
|
||||
|
||||
See docs/SERVER_SETUP.md for SGLang server setup instructions.
|
||||
See api_keys.ini.example for API key configuration.
|
||||
reference_images → batch IMAGE tensor
|
||||
"""
|
||||
|
||||
CATEGORY = "Eric/VisualResearch"
|
||||
@@ -647,24 +735,45 @@ class EricGenSearcherNode:
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"prompt": ("STRING", {
|
||||
"base_prompt": ("STRING", {
|
||||
"multiline": True,
|
||||
"default": "a portrait of ...",
|
||||
"tooltip": "Your image generation concept. Gen-Searcher researches it and writes a detailed grounded prompt.",
|
||||
"default": "",
|
||||
"tooltip": (
|
||||
"The creative/compositional direction for the final image.\n"
|
||||
"Describe mood, lighting, style, camera angle, narrative.\n"
|
||||
"Examples:\n"
|
||||
" 'dramatic low-angle portrait, golden hour, shallow depth of field'\n"
|
||||
" 'fashion editorial, studio lighting, minimalist background'\n\n"
|
||||
"If search_query is blank, this is also used as the search prompt\n"
|
||||
"(original single-field behaviour)."
|
||||
),
|
||||
}),
|
||||
"agent_api_url": ("STRING", {
|
||||
"default": "http://localhost:30000",
|
||||
"tooltip": (
|
||||
"URL of the running SGLang server.\n"
|
||||
"See docs/SERVER_SETUP.md for setup instructions.\n"
|
||||
"Default: http://localhost:30000"
|
||||
"See docs/SERVER_SETUP.md for setup instructions."
|
||||
),
|
||||
}),
|
||||
},
|
||||
"optional": {
|
||||
"search_query": ("STRING", {
|
||||
"multiline": False,
|
||||
"default": "",
|
||||
"tooltip": (
|
||||
"Specific subject/terms to search for visually (recommended).\n"
|
||||
"Keep this focused and visual - it drives the web and image searches.\n"
|
||||
"Examples:\n"
|
||||
" 'Zendaya Challengers movie tennis outfit'\n"
|
||||
" 'Stephen Miller politician appearance'\n"
|
||||
" 'Ferrari SF-23 F1 car cockpit interior'\n"
|
||||
" 'Art Deco architecture New York Chrysler building'\n\n"
|
||||
"Leave blank to use base_prompt for searches (original behaviour)."
|
||||
),
|
||||
}),
|
||||
"model_name": ("STRING", {
|
||||
"default": "",
|
||||
"tooltip": "Model name override. Leave blank to auto-detect from server.",
|
||||
"tooltip": "Model name override. Leave blank to auto-detect.",
|
||||
}),
|
||||
"max_hops": ("INT", {
|
||||
"default": 8, "min": 1, "max": 20, "step": 1,
|
||||
@@ -676,18 +785,17 @@ class EricGenSearcherNode:
|
||||
}),
|
||||
"enable_browse": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Allow reading full web pages via Jina for deeper detail. Disable for faster runs.",
|
||||
"tooltip": "Allow reading full web pages via Jina. Disable for faster runs.",
|
||||
}),
|
||||
"fallback_on_error": ("BOOLEAN", {
|
||||
"default": True,
|
||||
"tooltip": "Return original prompt if server is unreachable rather than failing.",
|
||||
"tooltip": "Return original prompt if server unreachable, rather than failing.",
|
||||
}),
|
||||
"use_instagram": ("BOOLEAN", {
|
||||
"default": False,
|
||||
"tooltip": (
|
||||
"Allow gallery-dl to download from Instagram.\n"
|
||||
"WARNING: Use only with a dedicated dummy account.\n"
|
||||
"Instagram flags automated access aggressively."
|
||||
"WARNING: Use only with a dedicated dummy account."
|
||||
),
|
||||
}),
|
||||
"use_facebook": ("BOOLEAN", {
|
||||
@@ -701,13 +809,14 @@ class EricGenSearcherNode:
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def IS_CHANGED(cls, prompt, **kwargs):
|
||||
def IS_CHANGED(cls, base_prompt, search_query="", **kwargs):
|
||||
return float("nan") # always re-run
|
||||
|
||||
def research(
|
||||
self,
|
||||
prompt: str,
|
||||
base_prompt: str,
|
||||
agent_api_url: str = "http://localhost:30000",
|
||||
search_query: str = "",
|
||||
model_name: str = "",
|
||||
max_hops: int = 8,
|
||||
ref_image_count: int = 4,
|
||||
@@ -721,34 +830,46 @@ class EricGenSearcherNode:
|
||||
if not serper_key:
|
||||
print(f"[EricGenSearcher] WARNING: No Serper key found.\n"
|
||||
f" Add to {_CONFIG_PATH}: serper = your-key\n"
|
||||
f" Or set env var SERPER_API_KEY\n"
|
||||
f" Get a free key at https://serper.dev")
|
||||
f" Or set env var SERPER_API_KEY")
|
||||
|
||||
model = model_name.strip() if model_name.strip() else _detect_model(agent_api_url)
|
||||
|
||||
# Log which mode we're using
|
||||
sq = search_query.strip()
|
||||
if sq:
|
||||
print(f"[EricGenSearcher] TWO-FIELD MODE")
|
||||
print(f"[EricGenSearcher] search_query: {sq[:100]}")
|
||||
print(f"[EricGenSearcher] base_prompt: {base_prompt[:100]}{'...' if len(base_prompt) > 100 else ''}")
|
||||
else:
|
||||
print(f"[EricGenSearcher] SINGLE-FIELD MODE (no search_query)")
|
||||
print(f"[EricGenSearcher] base_prompt: {base_prompt[:100]}{'...' if len(base_prompt) > 100 else ''}")
|
||||
|
||||
print(f"[EricGenSearcher] model={model} url={agent_api_url}")
|
||||
print(f"[EricGenSearcher] prompt: {prompt[:100]}{'...' if len(prompt) > 100 else ''}")
|
||||
|
||||
if _GALLERY_DL_EXE:
|
||||
platforms = ["Pinterest/Flickr/500px/Reddit/Twitter"]
|
||||
if use_instagram: platforms.append("Instagram ⚠")
|
||||
if use_facebook: platforms.append("Facebook ⚠")
|
||||
print(f"[EricGenSearcher] gallery-dl: {', '.join(platforms)}")
|
||||
else:
|
||||
print(f"[EricGenSearcher] gallery-dl not found - direct download only")
|
||||
|
||||
placeholder = torch.ones(1, _REF_MAX_DIM, _REF_MAX_DIM, 3) * 0.5
|
||||
|
||||
try:
|
||||
grounded, image_entries, search_log = _run_agent_loop(
|
||||
prompt=prompt, api_url=agent_api_url, model=model,
|
||||
serper_key=serper_key, jina_key=jina_key,
|
||||
max_hops=max_hops, enable_browse=enable_browse,
|
||||
max_ref_images=ref_image_count,
|
||||
base_prompt = base_prompt,
|
||||
search_query = sq,
|
||||
api_url = agent_api_url,
|
||||
model = model,
|
||||
serper_key = serper_key,
|
||||
jina_key = jina_key,
|
||||
max_hops = max_hops,
|
||||
enable_browse = enable_browse,
|
||||
max_ref_images = ref_image_count,
|
||||
)
|
||||
except RuntimeError as e:
|
||||
if fallback_on_error:
|
||||
print(f"[EricGenSearcher] ERROR (fallback): {e}")
|
||||
return (prompt, placeholder, f"ERROR: {e}",
|
||||
return (base_prompt, placeholder, f"ERROR: {e}",
|
||||
placeholder, placeholder, placeholder, placeholder)
|
||||
raise
|
||||
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
# Eric Visual Research - Eric Reference Describer Node
|
||||
# Copyright (c) 2026 Eric Hiss. All rights reserved.
|
||||
# Licensed under the terms in LICENSE.txt (CC BY-NC 4.0 / Commercial dual license).
|
||||
# https://github.com/EricRollei/Eric_Visual_Research
|
||||
# License: CC BY-NC 4.0 (non-commercial) / Commercial license — see LICENSE.txt
|
||||
# Repository: https://github.com/EricRollei/Visual_Research
|
||||
#
|
||||
# Third-party acknowledgements:
|
||||
# Qwen3-VL-8B Alibaba Cloud / Qwen Team (Apache 2.0)
|
||||
# https://github.com/QwenLM/Qwen2.5-VL
|
||||
# SGLang LMSYS / SGLang Team (Apache 2.0)
|
||||
# https://github.com/sgl-project/sglang
|
||||
# PyTorch Meta / PyTorch contributors (BSD 3-Clause)
|
||||
# Pillow Jeffrey A. Clark et al. (HPND)
|
||||
# NumPy NumPy Developers (BSD 3-Clause)
|
||||
"""
|
||||
Eric Reference Describer Node
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
[project]
|
||||
name = "comfyui-eric-visual-research"
|
||||
version = "1.0.1"
|
||||
description = "Agentic web research nodes for ComfyUI — grounded prompt generation and VL reference description"
|
||||
readme = "README.md"
|
||||
license = { file = "LICENSE.txt" }
|
||||
requires-python = ">=3.10"
|
||||
authors = [{ name = "Eric Hiss", email = "eric@rollei.us" }]
|
||||
keywords = [
|
||||
"comfyui",
|
||||
"image generation",
|
||||
"web research",
|
||||
"grounded prompts",
|
||||
"visual language model",
|
||||
"agentic",
|
||||
]
|
||||
classifiers = [
|
||||
"Programming Language :: Python :: 3",
|
||||
"License :: Other/Proprietary License",
|
||||
"Operating System :: OS Independent",
|
||||
"Topic :: Multimedia :: Graphics",
|
||||
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
||||
]
|
||||
dependencies = [
|
||||
"Pillow",
|
||||
"torch",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/EricRollei/Visual_Research"
|
||||
Repository = "https://github.com/EricRollei/Visual_Research"
|
||||
"Bug Tracker" = "https://github.com/EricRollei/Visual_Research/issues"
|
||||
|
||||
[tool.comfy]
|
||||
PublisherId = "ericrollei"
|
||||
DisplayName = "Eric Visual Research"
|
||||
Icon = ""
|
||||
Tags = ["research", "web search", "grounded prompts", "vision-language", "image generation"]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 476 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1013 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 3.2 MiB |
Reference in New Issue
Block a user