mirror of
https://github.com/calesthio/OpenMontage.git
synced 2026-08-24 17:10:27 +08:00
fix: complete Hunyuan image provider integration
This commit is contained in:
@@ -44,6 +44,9 @@ DOUBAO_SPEECH_VOICE_TYPE= # Default Doubao speaker/voice type, e.g. zh_female
|
||||
DASHSCOPE_API_KEY= # Qwen image gen (qwen-image-2.0-pro), TTS (qwen3-tts-flash), ASR with word timestamps (qwen3-asr-flash-filetrans)
|
||||
# Get one at https://dashscope.aliyun.com/
|
||||
|
||||
# --- Tencent Hunyuan TokenHub ---
|
||||
TENCENT_TOKENHUB_API_KEY= # Hunyuan Image 3.0 via Tencent TokenHub
|
||||
|
||||
# --- Music ---
|
||||
SUNO_API_KEY= # Suno AI music generation (full songs, instrumentals, any genre)
|
||||
|
||||
|
||||
@@ -188,6 +188,24 @@ The ASR tool (`qwen3-asr-flash-filetrans`) uses an async submit-poll pattern. Au
|
||||
|
||||
---
|
||||
|
||||
### Tencent Hunyuan Cloud — Image Generation
|
||||
|
||||
> **Chinese-friendly first-party image generation.** `hunyuan_image` accesses
|
||||
> Hunyuan Image 3.0 through Tencent TokenHub with Bearer-token authentication.
|
||||
> It supports seeded text-to-image, up to three reference images, custom
|
||||
> resolutions, prompt rewriting, and watermark controls.
|
||||
|
||||
**Tool unlocked:** `hunyuan_image`
|
||||
|
||||
**Env var:** `TENCENT_TOKENHUB_API_KEY`
|
||||
|
||||
Generate an API key in the Tencent Cloud TokenHub console and add it to
|
||||
`.env`. The tool reports approximately $0.08 per generated image based on
|
||||
TokenHub's credit price. It is available through `image_selector`; shared
|
||||
reference-image inputs are normalized to the provider's `images` array.
|
||||
|
||||
---
|
||||
|
||||
### fal.ai — Multi-Model Gateway
|
||||
|
||||
> **Broad single-key coverage.** One API key unlocks image and video providers across multiple models.
|
||||
|
||||
@@ -46,6 +46,18 @@ def test_hunyuan_image_metadata():
|
||||
assert info["supports"]["reference_image"] is True
|
||||
assert info["supports"]["prompt_rewrite"] is True
|
||||
assert info["supports"]["negative_prompt"] is False
|
||||
assert "env:TENCENT_TOKENHUB_API_KEY" in info["dependencies"]
|
||||
assert "visual-style" in info["agent_skills"]
|
||||
|
||||
|
||||
def test_idempotency_includes_custom_watermark():
|
||||
from tools.graphics.hunyuan_image import HunyuanImage
|
||||
|
||||
tool = HunyuanImage()
|
||||
base = {"prompt": "x"}
|
||||
assert tool.idempotency_key(base) != tool.idempotency_key(
|
||||
{**base, "logo_param": {"logo_url": "https://example.com/logo.png"}}
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -355,6 +367,40 @@ def test_execute_returns_error_without_api_key(monkeypatch):
|
||||
assert "TENCENT_TOKENHUB_API_KEY" in result.error
|
||||
|
||||
|
||||
def test_image_selector_maps_shared_reference_input(monkeypatch, tmp_path):
|
||||
from tools.base_tool import ToolResult
|
||||
from tools.graphics.hunyuan_image import HunyuanImage
|
||||
from tools.graphics.image_selector import ImageSelector
|
||||
|
||||
monkeypatch.setenv("TENCENT_TOKENHUB_API_KEY", "test-key")
|
||||
tool = HunyuanImage()
|
||||
selector = ImageSelector()
|
||||
monkeypatch.setattr(selector, "_providers", lambda: [tool])
|
||||
monkeypatch.setattr(
|
||||
selector,
|
||||
"_select_best_tool",
|
||||
lambda _inputs, _candidates, _context: (tool, None),
|
||||
)
|
||||
observed = {}
|
||||
|
||||
def fake_execute(inputs):
|
||||
observed.update(inputs)
|
||||
return ToolResult(success=True, data={}, artifacts=[inputs["output_path"]])
|
||||
|
||||
monkeypatch.setattr(tool, "execute", fake_execute)
|
||||
result = selector.execute(
|
||||
{
|
||||
"prompt": "adapt this frame",
|
||||
"preferred_provider": "hunyuan_cloud",
|
||||
"image_path": str(tmp_path / "reference.png"),
|
||||
"output_path": str(tmp_path / "out.png"),
|
||||
}
|
||||
)
|
||||
assert result.success
|
||||
assert observed["images"] == [str(tmp_path / "reference.png")]
|
||||
assert result.data["selected_tool"] == "hunyuan_image"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dry run
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
@@ -52,12 +52,12 @@ class HunyuanImage(BaseTool):
|
||||
determinism = Determinism.SEEDED
|
||||
runtime = ToolRuntime.API
|
||||
|
||||
dependencies = []
|
||||
dependencies = ["env:TENCENT_TOKENHUB_API_KEY"]
|
||||
install_instructions = (
|
||||
"Set TENCENT_TOKENHUB_API_KEY to your Tencent Cloud TokenHub API key.\n"
|
||||
" Get it at https://console.cloud.tencent.com/tokenhub"
|
||||
)
|
||||
agent_skills = []
|
||||
agent_skills = ["visual-style"]
|
||||
|
||||
capabilities = ["generate_image", "text_to_image"]
|
||||
supports = {
|
||||
@@ -196,6 +196,7 @@ class HunyuanImage(BaseTool):
|
||||
"seed",
|
||||
"revise",
|
||||
"logo_add",
|
||||
"logo_param",
|
||||
]
|
||||
side_effects = [
|
||||
"writes image file to output_path",
|
||||
|
||||
@@ -242,6 +242,17 @@ class ImageSelector(BaseTool):
|
||||
props = tool.input_schema.get("properties", {})
|
||||
if "query" in props and "query" not in adapted:
|
||||
adapted["query"] = adapted.get("prompt", "")
|
||||
# Normalize the selector's shared reference-image inputs for
|
||||
# providers whose native contract accepts an ``images`` array.
|
||||
if "images" in props and "images" not in adapted:
|
||||
refs = (
|
||||
adapted.get("image_paths")
|
||||
or adapted.get("image_urls")
|
||||
or ([adapted["image_path"]] if adapted.get("image_path") else None)
|
||||
or ([adapted["image_url"]] if adapted.get("image_url") else None)
|
||||
)
|
||||
if refs:
|
||||
adapted["images"] = refs
|
||||
|
||||
# Strip selector-only keys that downstream tools don't understand
|
||||
adapted.pop("preferred_provider", None)
|
||||
|
||||
Reference in New Issue
Block a user