加了一堆模型和一堆功能
This commit is contained in:
139
art-agent/backend/app/services/image_prompt_strategy.py
Normal file
139
art-agent/backend/app/services/image_prompt_strategy.py
Normal file
@@ -0,0 +1,139 @@
|
||||
"""按生图模型预处理 prompt(与 provider 解耦)。
|
||||
|
||||
各策略在对应函数中注明依据:厂商文档、Replicate 模型 API 字段说明或社区通用写法。
|
||||
统一入口:prepare_image_prompt_for_model。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
# ─── SDXL(Stability / Replicate)────────────────────────────
|
||||
# Replicate stability-ai/sdxl 提供独立字段 negative_prompt(见模型 API 页)。
|
||||
# 下列为 CLIP 系文生图常见「质量/解剖」排除词,作未显式指定时的基线。
|
||||
DEFAULT_SDXL_NEGATIVE = (
|
||||
"low quality, worst quality, normal quality, lowres, blurry, jpeg artifacts, "
|
||||
"watermark, signature, text, logo, deformed, disfigured, bad anatomy, bad hands, "
|
||||
"extra fingers, mutated, cropped, poorly drawn face"
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PreparedImagePrompt:
|
||||
"""下游只读:prompt 必填;negative_prompt 仅部分后端使用(当前为 SDXL)。"""
|
||||
|
||||
prompt: str
|
||||
negative_prompt: str | None = None
|
||||
|
||||
|
||||
def prepare_image_prompt_for_model(
|
||||
model_config: dict[str, Any],
|
||||
raw_prompt: str,
|
||||
*,
|
||||
has_reference_images: bool = False,
|
||||
) -> PreparedImagePrompt:
|
||||
"""根据注册表 id 选择策略。未知 id 时原样透传。"""
|
||||
short_id = model_config.get("id", "")
|
||||
text = (raw_prompt or "").strip()
|
||||
if not text:
|
||||
return PreparedImagePrompt(prompt="")
|
||||
|
||||
dispatch: dict[str, Any] = {
|
||||
"gpt-image-1.5": _openai_gpt_image,
|
||||
"gemini-3.1-flash-image": _gemini_native_image,
|
||||
"flux-schnell": _flux_bfl,
|
||||
"flux-dev": _flux_bfl,
|
||||
"sdxl": _sdxl_replicate,
|
||||
"instant-style": _replicate_ip_adapter_scene,
|
||||
"kolors-ipadapter": _replicate_ip_adapter_scene,
|
||||
}
|
||||
fn = dispatch.get(short_id, _passthrough)
|
||||
return fn(text, model_config, has_reference_images)
|
||||
|
||||
|
||||
def _passthrough(text: str, _model_config: dict[str, Any], _has_ref: bool) -> PreparedImagePrompt:
|
||||
return PreparedImagePrompt(prompt=text)
|
||||
|
||||
|
||||
def _openai_gpt_image(text: str, _model_config: dict[str, Any], _has_ref: bool) -> PreparedImagePrompt:
|
||||
"""OpenAI GPT Image:自然语言指令遵循强,宜为完整、具体的场景描述。
|
||||
|
||||
参考:https://platform.openai.com/docs/guides/image-generation
|
||||
"""
|
||||
return PreparedImagePrompt(prompt=_collapse_ws(text))
|
||||
|
||||
|
||||
def _gemini_native_image(text: str, _model_config: dict[str, Any], _has_ref: bool) -> PreparedImagePrompt:
|
||||
"""Gemini 原生生图:多模态指令 + 文本;英文描述通常效果稳定。
|
||||
|
||||
参考:Google AI 文档 generateContent 与图像输出 modality 说明。
|
||||
"""
|
||||
return PreparedImagePrompt(prompt=_collapse_ws(text))
|
||||
|
||||
|
||||
def _flux_bfl(text: str, _model_config: dict[str, Any], _has_ref: bool) -> PreparedImagePrompt:
|
||||
"""Black Forest Labs FLUX:Subject + Action + Style + Context;靠前放置重点;无 negative API。
|
||||
|
||||
参考:https://docs.bfl.ai/guides/prompting_guide_t2i_fundamentals
|
||||
若用户从 SDXL 复制了 Negative 段,尽量剥掉以免干扰文意。
|
||||
"""
|
||||
cleaned = _strip_pasted_negative_block(text)
|
||||
return PreparedImagePrompt(prompt=_collapse_ws(cleaned))
|
||||
|
||||
|
||||
def _sdxl_replicate(text: str, model_config: dict[str, Any], _has_ref: bool) -> PreparedImagePrompt:
|
||||
"""Replicate SDXL:prompt + negative_prompt 双字段;支持显式拆分。
|
||||
|
||||
约定(可选):正提示与负提示用单独一行分隔符,便于 Agent/用户手写。
|
||||
- ---NEGATIVE--- 或 |||NEG|||
|
||||
未拆分时使用 default_params.negative_prompt 或模块默认 DEFAULT_SDXL_NEGATIVE。
|
||||
"""
|
||||
neg_fallback = model_config.get("default_params", {}).get(
|
||||
"negative_prompt", DEFAULT_SDXL_NEGATIVE
|
||||
)
|
||||
if "---NEGATIVE---" in text:
|
||||
pos, _, neg = text.partition("---NEGATIVE---")
|
||||
pos = pos.strip()
|
||||
neg = neg.strip()
|
||||
return PreparedImagePrompt(
|
||||
prompt=_collapse_ws(pos),
|
||||
negative_prompt=neg or neg_fallback,
|
||||
)
|
||||
if "|||NEG|||" in text:
|
||||
pos, _, neg = text.partition("|||NEG|||")
|
||||
pos = pos.strip()
|
||||
neg = neg.strip()
|
||||
return PreparedImagePrompt(
|
||||
prompt=_collapse_ws(pos),
|
||||
negative_prompt=neg or neg_fallback,
|
||||
)
|
||||
return PreparedImagePrompt(
|
||||
prompt=_collapse_ws(text),
|
||||
negative_prompt=neg_fallback,
|
||||
)
|
||||
|
||||
|
||||
def _replicate_ip_adapter_scene(
|
||||
text: str, _model_config: dict[str, Any], has_reference_images: bool
|
||||
) -> PreparedImagePrompt:
|
||||
"""IP-Adapter / InstantStyle / Kolors:参考图承担风格与纹理,prompt 侧重场景与内容语义。
|
||||
|
||||
Replicate 各模型 README 均强调 prompt + 参考图配合;无参考图时由上层拦截。
|
||||
有参考图时不额外堆叠长前缀,避免稀释主体描述。
|
||||
"""
|
||||
_ = has_reference_images
|
||||
return PreparedImagePrompt(prompt=_collapse_ws(text))
|
||||
|
||||
|
||||
def _collapse_ws(s: str) -> str:
|
||||
return " ".join(s.split())
|
||||
|
||||
|
||||
def _strip_pasted_negative_block(text: str) -> str:
|
||||
lower = text.lower()
|
||||
for sep in ("\n---negative---\n", "\nnegative prompt:", "\nnegative:"):
|
||||
idx = lower.find(sep)
|
||||
if idx != -1:
|
||||
return text[:idx].strip()
|
||||
return text
|
||||
Reference in New Issue
Block a user