Z User
feat: add DuckDuckGo free fallback for web_search (no API key needed)
4036608
"""Pollinations.ai image generation backend — free, no API key required.
Generates images via https://image.pollinations.ai/prompt/{prompt}.
Downloads the result and saves to $HERMES_HOME/cache/images/.
Supports landscape/square/portrait aspect ratios.
"""
from __future__ import annotations
import logging
import os
import urllib.parse
import urllib.request
from typing import Any, Dict, List
from agent.image_gen_provider import (
DEFAULT_ASPECT_RATIO,
ImageGenProvider,
error_response,
resolve_aspect_ratio,
success_response,
)
logger = logging.getLogger(__name__)
_API_BASE = "https://image.pollinations.ai/prompt"
_SIZES = {
"landscape": "1344x768",
"square": "1024x1024",
"portrait": "768x1344",
}
DEFAULT_MODEL = "flux"
class PollinationsImageGenProvider(ImageGenProvider):
"""Pollinations.ai free image generation backend."""
@property
def name(self) -> str:
return "pollinations"
@property
def display_name(self) -> str:
return "Pollinations (Free)"
def is_available(self) -> bool:
return True # no API key needed
def list_models(self) -> List[Dict[str, Any]]:
return [
{
"id": "flux",
"display": "Flux (Free)",
"speed": "~10-20s",
"strengths": "Free, no API key, good quality",
"price": "Free",
}
]
def get_setup_schema(self) -> Dict[str, Any]:
return {
"name": "Pollinations.ai",
"badge": "free",
"tag": "Free image generation — no API key required",
"env_vars": [],
}
def default_model(self) -> str:
return DEFAULT_MODEL
def generate(
self,
prompt: str,
aspect_ratio: str = DEFAULT_ASPECT_RATIO,
**kwargs: Any,
) -> Dict[str, Any]:
prompt = (prompt or "").strip()
aspect = resolve_aspect_ratio(aspect_ratio)
if not prompt:
return error_response(
error="Prompt is required",
error_type="invalid_argument",
provider="pollinations",
aspect_ratio=aspect,
)
width, height = _SIZES.get(aspect, _SIZES["square"]).split("x")
seed = os.urandom(4).hex()
url = f"{_API_BASE}/{urllib.parse.quote(prompt)}?width={width}&height={height}&seed={seed}&nologo=true&model=flux"
# Download image to cache
try:
from hermes_constants import get_hermes_home
import datetime
import uuid
cache_dir = get_hermes_home() / "cache" / "images"
cache_dir.mkdir(parents=True, exist_ok=True)
ts = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
short = uuid.uuid4().hex[:8]
save_path = cache_dir / f"pollinations_{ts}_{short}.png"
req = urllib.request.Request(url, headers={"User-Agent": "Hermes/1.0"})
with urllib.request.urlopen(req, timeout=60) as resp:
data = resp.read()
save_path.write_bytes(data)
image_ref = str(save_path)
except Exception as exc:
logger.warning("Pollinations image download failed: %s", exc)
# Fallback: return URL directly
image_ref = url
return success_response(
image=image_ref,
model=DEFAULT_MODEL,
prompt=prompt,
aspect_ratio=aspect,
provider="pollinations",
extra={"url": url},
)
def register(ctx) -> None:
ctx.register_image_gen_provider(PollinationsImageGenProvider())