Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 28,837 Bytes
6c9a052 775d296 6c9a052 d98e7b3 6c9a052 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 d632079 54316b6 d632079 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 bcff293 6c9a052 570a57c 6c9a052 54316b6 6c9a052 570a57c 6c9a052 5bb3b2d d98e7b3 5bb3b2d d632079 5bb3b2d 6c9a052 54316b6 5bb3b2d bcff293 e1b4c00 bcff293 775d296 bcff293 775d296 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 | """
mcp_server.py
─────────────
MCP 服务层
挂载方式(在 ui_nicegui.py 中):
from mcp_server import mcp
app.mount('/mcp', mcp.streamable_http_app())
接入地址:
https://sakizuki-danboorusearch.hf.space/mcp/mcp
支持的工具:
search_tags 自然语言搜索标签
get_related_tags 基于共现表查关联推荐
get_anima_format 返回 Anima 模型 Hybrid 提示词格式规范
get_newbie_format 返回 NewBie 模型 XML 提示词格式规范
"""
import json
import asyncio
import logging
from anyio import BrokenResourceError, ClosedResourceError
from mcp.server.fastmcp import FastMCP
from mcp.server.transport_security import TransportSecuritySettings
from core.engine import DanbooruTagger
from core.models import SearchRequest
import core.counter as counter
import re
# ── 过滤客户端断连/超时产生的无害报错噪音 ──────────────────────────────
class _SuppressClientDisconnect(logging.Filter):
_SUPPRESSED: tuple = ()
_HAS_STARLETTE: bool = False
@classmethod
def _init_suppressed(cls):
if cls._SUPPRESSED:
return
types: list = [BrokenResourceError, ClosedResourceError, asyncio.CancelledError]
try:
from starlette.requests import ClientDisconnect
types.append(ClientDisconnect)
cls._HAS_STARLETTE = True
except ImportError:
pass
cls._SUPPRESSED = tuple(types)
def filter(self, record: logging.LogRecord) -> bool:
self._init_suppressed()
exc = record.exc_info[1] if record.exc_info else None
if isinstance(exc, self._SUPPRESSED):
return False
# 用类名字符串兜底(避免 starlette 版本差异导致 import 失败)
if exc is not None and not self._HAS_STARLETTE:
name = type(exc).__name__
if name in ('ClientDisconnect',):
return False
return True
_disconnect_filter = _SuppressClientDisconnect()
logging.getLogger("mcp.server.streamable_http").addFilter(_disconnect_filter)
logging.getLogger("mcp.server").addFilter(_disconnect_filter)
logging.getLogger("uvicorn.error").addFilter(_disconnect_filter)
mcp = FastMCP(
name="danbooru-searcher",
transport_security=TransportSecuritySettings(enable_dns_rebinding_protection=False),
)
@mcp.tool()
async def search_tags(
query: str,
search_mode: str = "full_scene",
category: str = "all",
show_nsfw: bool = True,
include_wiki: bool = False,
) -> str:
"""
Search Danbooru tags using natural language and return a ready-to-use prompt.
Only supported for general, copyright, and character tag searches; **artists and meta tags are not supported.**
## Args
- query: Natural language description (Chinese recommended).
- search_mode: Preset strategy. Pick the one that matches your intent.
"full_scene" — Full scene → prompt (e.g. "一个穿着白色水手服的少女在雨中奔跑")
"concept_explore" — Vague concept exploration, broad recall (e.g. "赛博朋克服装", "兔耳朵", "中国风汉服")
"subject_describe" — Describe **one** subject to find matching tags (e.g. "EVA中蓝发的驾驶员", "两侧有开口,前方有拉绳的运动短裤")
"precise_lookup" — Precise lookup / spell fix (e.g. "selafuku", "thighhigh")
- HINT: In subject_describe mode, the tokenizer is disabled, and you can only describe one thing at a time. To search for multiple things at once, use concept_explore or full_scene.
- category: Filter to a specific tag category. Default "all".
"all" — All (通用 + 版权 + 人物)
"general" — Visual attributes, clothing, pose, background, etc.
"character" — Named characters from any series
"copyright" — Specific anime/game/franchise titles
- show_nsfw: Include NSFW tags. Default True.
- include_wiki: Append wiki description to each result. Default False.
Set True when tags are unfamiliar and need disambiguation.
## Query writing guide
Use **spaces, newlines, Chinese commas (,), or Chinese dunhao (、)** to manually separate concepts.
Each delimiter-bounded segment ≤7 characters stays atomic — the engine respects your intent.
| Query style | Example |
|---|---|
| Concept list (spaces) | `运动社团 校队 比赛 运动会` |
| Concept list (dun hao) | `反乌托邦、赛博朋克、蒸汽朋克` |
| Natural sentence | `一个穿着白色水手服的少女在雨中奔跑` |
| Mixed | `运动社团 一个穿水手服的少女` |
## Workflow
After search_tags, pass selected tags to get_related_tags to discover complementary tags via co-occurrence.
Chain freely: search_tags → get_related_tags → get_related_tags → search_tags for multi-hop exploration.
## Returns
JSON with: prompt (comma-separated tags), keywords, results.
Each result: tag, cn_name, category, final_score, count[, wiki if include_wiki=True].
"""
_SEARCH_MODE_PRESETS: dict[str, dict] = {
"precise_lookup": {"top_k": 10, "limit": 10, "popularity_weight": 0.15, "use_segmentation": False, "group_mode": "off", "max_per_group": 2},
"concept_explore": {"top_k": 80, "limit": 80, "popularity_weight": 0.15, "use_segmentation": True, "group_mode": "expand", "max_per_group": 2},
"subject_describe": {"top_k": 20, "limit": 20, "popularity_weight": 0.15, "use_segmentation": False, "group_mode": "off", "max_per_group": 2},
"full_scene": {"top_k": 5, "limit": 80, "popularity_weight": 0.15, "use_segmentation": True, "group_mode": "diverse", "max_per_group": 2},
}
preset = _SEARCH_MODE_PRESETS.get(search_mode, _SEARCH_MODE_PRESETS["full_scene"])
_CATEGORY_MAP: dict[str, list[str]] = {
"all": ["General", "Character", "Copyright", "Artist", "Meta"],
"general": ["General"],
"character": ["Character"],
"copyright": ["Copyright"],
}
target_categories = _CATEGORY_MAP.get(
category,
_CATEGORY_MAP["all"],
)
tagger = await DanbooruTagger.get_instance()
request = SearchRequest(
query=query,
top_k=preset["top_k"],
limit=preset["limit"],
popularity_weight=preset["popularity_weight"],
show_nsfw=show_nsfw,
use_segmentation=preset["use_segmentation"],
target_categories=target_categories,
group_mode=preset["group_mode"],
max_per_group=preset["max_per_group"],
)
try:
response = await tagger.search_async(request)
except asyncio.TimeoutError:
return json.dumps({
"error": "搜索超时(120s),请简化查询或稍后重试",
}, ensure_ascii=False, indent=2)
# 计数:每次 MCP 搜索调用均计入搜索、成功、复制;访问不变
await counter.increment()
await counter.increment_success()
await counter.increment_copy()
await counter.increment_mcp()
results = []
for r in response.results:
if r.nsfw == '1' and not show_nsfw:
continue
item = {
"tag": r.tag,
"cn_name": r.cn_name,
"category": r.category,
"final_score": r.final_score,
"count": r.count,
}
if include_wiki:
item["wiki"] = r.wiki
results.append(item)
payload = {
"prompt": response.tags_sfw if not show_nsfw else response.tags_all,
"keywords": response.keywords,
"results": results,
}
han_chars = re.findall(r'[\u4e00-\u9fff]', query)
if len(query) > 0 and len(han_chars) / len(query) < 0.5:
payload["hint"] = (
"检测到英文查询,该搜索引擎对中文查询优化更好,如果搜索结果不合预期,推荐用中文重试"
)
return json.dumps(payload, ensure_ascii=False, indent=2)
@mcp.tool()
async def get_related_tags(
tags: list[str],
limit: int = 50,
show_nsfw: bool = True,
include_wiki: bool = False,
) -> str:
"""
Return co-occurrence-based tag recommendations for a given tag list (NPMI scoring).
Only supported for general, copyright, and character tag searches; **artists and meta tags are not supported.**
This tool surfaces tags that frequently appear alongside the seeds in
Danbooru, mixing categories (General / Character / Copyright) by design.
## Typical use cases
- Attribute → characters who have it
e.g. ["fingerless_gloves"] → tifa_lockhart, cammy_white, bridget_(guilty_gear), ...
- Work → characters in it
e.g. ["overlord_(maruyama)"] → shalltear_bloodfallen, ainz_ooal_gown, albedo_(overlord), ...
- Character → their visual attributes
e.g. ["amiya_(arknights)"] → outfits, expressions, accessories
- Theme exploration
e.g. ["fighter_jet"] → aircraft types, actions, backgrounds
- Multi-tag intersection
e.g. ["maid", "twintails"] → tags specific to the combination, scored by summed NPMI
For within-category exploration (e.g. "more clothing tags like X"), use search_tags
with the `category` parameter instead.
## Workflow
Chain freely: search_tags → get_related_tags → get_related_tags → search_tags.
Each hop along the co-occurrence graph reveals tags unreachable by semantic search alone.
## Args
- tags: List of canonical Danbooru tag names (underscores, no spaces).
e.g. ["white_serafuku", "sailor_collar"]
- limit: Max recommendations returned. Default 50.
- show_nsfw: Include NSFW tags. Default True.
- include_wiki: Append wiki description to each result. Default False.
Set True when result tags are unfamiliar and need disambiguation.
## Returns
JSON array sorted by aggregated NPMI score (descending). Each result:
- tag, cn_name, category, count (post_count), cooc_score (normalized to [0,1])
- sources: seed tags that contributed to this score
- wiki: only if include_wiki=True
"""
tagger = await DanbooruTagger.get_instance()
# ── 检查标签是否存在,不存在则尝试 search_tags 纠错 ──────────────────
valid_tags = []
invalid_tags = []
for t in tags:
if t in tagger._name_to_idx:
valid_tags.append(t)
else:
invalid_tags.append(t)
corrections = {}
if invalid_tags:
for bad_tag in invalid_tags:
try:
req = SearchRequest(
query=bad_tag,
top_k=5,
limit=5,
popularity_weight=0.15,
use_segmentation=False,
target_layers=['英文']
)
resp = await tagger.search_async(req)
if resp.results:
corrections[bad_tag] = resp.results[0].tag
except Exception:
pass
if not valid_tags and not corrections:
return json.dumps({
"error": "所有传入的标签均不存在于标签表中",
"invalid_tags": invalid_tags,
}, ensure_ascii=False, indent=2)
# 用纠错后的标签替换无效标签
corrected_tags = []
for t in tags:
if t in valid_tags:
corrected_tags.append(t)
elif t in corrections:
corrected_tags.append(corrections[t])
results = await tagger.get_related_async(
corrected_tags,
set(corrected_tags),
limit,
show_nsfw,
)
# 计数:每次 MCP related 调用均计入搜索、成功、复制;访问不变
await counter.increment()
await counter.increment_success()
await counter.increment_copy()
await counter.increment_mcp()
output = []
for r in results:
item = {
"tag": r.tag,
"cn_name": r.cn_name,
"category": r.category,
"count": r.post_count,
"cooc_score": r.cooc_score,
"sources": r.sources,
}
if include_wiki:
item["wiki"] = r.wiki
output.append(item)
payload = {"results": output}
if corrections:
correction_notes = [
f"{bad} → {good}" for bad, good in corrections.items()
]
payload = {
"correction_note": "标签拼写错误,已经纠错: " + ", ".join(correction_notes),
"corrections": corrections,
"results": output,
}
return json.dumps(payload, ensure_ascii=False, indent=2)
@mcp.tool()
async def get_artist_recommendations(
tags: list[str],
limit: int = 30,
min_cooc: int = 3,
show_nsfw: bool = True,
) -> str:
"""
Recommend artists who are skilled at drawing the given tags, based on NPMI co-occurrence data.
Given a list of Danbooru tags (e.g. character names, clothing, styles), this tool returns
artists whose works frequently co-occur with those tags on Danbooru, ranked by aggregated
NPMI score.
## Args
- tags: List of canonical Danbooru tag names (underscores, no spaces).
e.g. ["1girl", "blue_hair", "school_uniform"]
- limit: Max artists returned. Default 30.
- min_cooc: Minimum co-occurrence count per (tag, artist) pair to consider. Default 3.
- show_nsfw: Include NSFW artist data. Default True.
## Returns
JSON array sorted by NPMI score (descending). Each result:
- artist: Danbooru artist tag name
- score: Aggregated NPMI score (higher = stronger association)
- cooc_count: Total co-occurrence count across all input tags
- post_count: Artist's total post count on Danbooru
- sources: Input tags that matched this artist
- hit_count: Number of input tags that matched
"""
tagger = await DanbooruTagger.get_instance()
if not tags:
return json.dumps({"error": "tags 列表不能为空"}, ensure_ascii=False, indent=2)
# ── 检查标签是否存在,不存在则尝试 search_tags 纠错 ──────────────────
valid_tags = []
invalid_tags = []
for t in tags:
if t in tagger._name_to_idx:
valid_tags.append(t)
else:
invalid_tags.append(t)
corrections = {}
if invalid_tags:
for bad_tag in invalid_tags:
try:
req = SearchRequest(
query=bad_tag,
top_k=5,
limit=5,
popularity_weight=0.15,
use_segmentation=False,
target_layers=['英文']
)
resp = await tagger.search_async(req)
if resp.results:
corrections[bad_tag] = resp.results[0].tag
except Exception:
pass
if not valid_tags and not corrections:
return json.dumps({
"error": "所有传入的标签均不存在于标签表中",
"invalid_tags": invalid_tags,
}, ensure_ascii=False, indent=2)
# 用纠错后的标签替换无效标签
corrected_tags = []
for t in tags:
if t in valid_tags:
corrected_tags.append(t)
elif t in corrections:
corrected_tags.append(corrections[t])
results = await tagger.search_artists_by_tags_async(
corrected_tags, limit=limit, min_cooc=min_cooc,
)
output = []
for r in results:
item = {
"artist": r.artist,
"score": round(r.score, 4),
"cooc_count": r.cooc_count,
"post_count": r.post_count,
"sources": r.sources,
"hit_count": r.hit_count,
}
output.append(item)
# 计数
await counter.increment()
await counter.increment_success()
await counter.increment_copy()
await counter.increment_mcp()
payload = {"results": output}
if corrections:
correction_notes = [
f"{bad} → {good}" for bad, good in corrections.items()
]
payload = {
"correction_note": "标签拼写错误,已经纠错: " + ", ".join(correction_notes),
"corrections": corrections,
"results": output,
}
return json.dumps(payload, ensure_ascii=False, indent=2)
# ── Anima 提示词格式说明 ─────────────────────────────────────────────────
_ANIMA_FORMAT_INSTRUCTION = """请严格按以下 Anima 混合提示词(Hybrid Prompt)规范,基于提供的标签和用户描述,输出最终结果。
# Anima Prompt Composer
## Overview
将已有的 Danbooru 风格标签数据整合为 Anima 模型的最优 Hybrid 提示词。该 Skill 假定调用方已经拥有充足的标签信息(通过 Tagger、Captioner 或用户输入),仅负责按 Anima 的格式规范与社区验证的最佳实践进行结构化组装。
## 核心设计理念
Anima 是一个 2B 参数的文生图模型(CircleStone Labs × Comfy Org),基于 NVIDIA Cosmos-Predict2-2B,使用 Qwen 3 0.6B 文本编码器。它同时理解 Danbooru 标签和自然语言,但两者的行为有本质差异——标签掌控结构与精度,自然语言掌控氛围与构图。
社区的共识结论:
- **纯标签提示词**:线条锐利、色彩平整、几乎没有解剖错误,但画面扁平,缺乏光影、氛围、构图的精确控制。
- **纯自然语言提示词**:细节丰富、光影动态、气氛到位,但超过 2~3 段后结构崩塌,手部最先出问题。
- **Hybrid 混合模式**:标签主导主体结构,自然语言补充环境与氛围,获得约 80% 的主体控制力加完整的氛围控制力。
核心风险:自然语言的影响力 **远强于** 标签。当你用自然语言描述背景时,模型会忽略 `close-up`、`upper body` 等取景标签,生成广角镜头。解决方案是对取景标签使用权重语法。
## 输出格式
````markdown
## Prompt
```
[标签块:逗号分隔,单行]
[自然语言段落:2 到 3 句英文]
```
## 中文解释
[分点说明提示词设计逻辑,包含Prompt自然语言段落的完整翻译]
````
**绝对禁止**在任何部分之外添加开场白、寒暄或总结。
## 标签格式化规则
- 所有标签小写,下划线 `_` 替换为空格。**唯一例外**:`score_1` 到 `score_9` 保持下划线。
- 标签内括号用反斜杠转义:`momoko (momopoco)` → `momoko \\(momopoco\\)`
- 标签间用一个逗号加一个空格连接:`tag a, tag b, tag c`
- 不要编造不存在的标签。若不确定某标签是否存在,将该概念放入自然语言段落。
- Tag Dropout 机制意味着不需要塞入每一个相关标签——只保留最关键和区分性最强的。
## 标签块结构规则
### 官方推荐标签顺序
```
[quality/meta/year/safety] → [1girl/1boy/1other] → [character] → [series] → [@artist] → [general tags]
```
### 单人物详细结构
```
[quality/meta/safety], [1girl/1boy], [character name], [series], [@artist], [hair], [eyes], [clothing], [body/pose], [expression], [action], [background/atmosphere], [composition tags]
```
### 多人物详细结构(防串扰核心规则)
```
[quality/meta/safety], [2girls / 1girl 1boy],
[character_A name], [series_A], [A hair], [A eyes], [A clothing], [A body], [A expression],
[character_B name], [series_B], [B hair], [B eyes], [B clothing], [B body], [B expression],
[shared pose/action], [background], [atmosphere], [composition]
```
## 标签体系速查
### 质量标签(任选其一或混用)
- 人工评分系:`masterpiece`, `best quality`, `good quality`, `normal quality`, `low quality`, `worst quality`
- 美学评分系:`score_9`, `score_8`, `score_7`, `score_6` ... `score_1`(仅score标签保留下划线)
### 年代标签
- 具体年份:`year 2025`, `year 2024` ...
- 时期:`newest` (2022-2023), `recent` (2019-2021), `mid` (2015-2018), `early` (2011-2014), `old` (2005-2010)
### 元标签
`highres`, `absurdres`, `anime screenshot`, `jpeg artifacts`, `official art`
### 安全分级
`safe`, `sensitive`, `nsfw`, `explicit`
### 艺术家标签
**必须以 @ 开头**。没有 @ 前缀的风格几乎不生效。
格式:`@nnn yryr`, `@big chungus`
### 数据集标签(非动漫风格时的备选)
在提示词最开头另起一行使用,可大幅改变风格倾向:
- `ye-pop`:LAION-POP 数据集风格,偏抽象/油画/概念艺术
- `deviantart`:DeviantArt 数据集风格,偏数字绘画/插画
## 自然语言段落规则
自然语言段落严格 2 到 3 句英文,仅用于标签难以精确表达的内容:
1. **镜头取景**:angle、shot distance、framing (close-up, wide shot, dutch angle…)
2. **光线**:方向、质感、色温 (rim light, volumetric god rays, warm key light…)
3. **色彩调性**:palette、color grading (monochromatic indigo, vibrant cel-shaded…)
4. **天气与环境**:rain、fog、dappled sunlight、underwater…
5. **氛围**:somber、airy、tense、ethereal…
6. **多角色空间关系与动作**:谁在左边、谁在干什么、互动方式
**关键禁忌**:
- 不要在自然语言中重复标签已覆盖的内容(发型、瞳色、服装等)。
- 不要写超过 3 段的自然语言——超过 2~3 段后画面结构会崩溃,手部最先出问题。
- 自然语言中不要使用隐喻或情绪化修辞,应使用客观、具体、视觉化的描述。
## 默认前缀与默认值
**正向前缀**(无特殊要求时的默认值):
```
masterpiece, best quality, score_7, safe,
```
**取景默认**:若用户未指定,默认近景人物、人物面向观众。若用户有描述则以用户描述为准。
**模式默认**:采用 Hybrid 混合结构(标签 + 自然语言)。仅当用户明确要求纯标签或纯自然语言时才切换。
## 权重语法
Anima 支持 Prompt Weighting,但需要的权重值 **高于 SDXL**:
- 正常强调:`(tag:2)` 起步
- 强强调:`(tag:3)` 到 `(tag:5)`
- 权重取值范围:2 ~ 5
- 若用户提供 1.2 等较小权重,**必须放大至 2~5 区间**
- 多角色区分性特征(如一个蓝发一个红发)使用权重:`(blue hair:2)`, `(red hair:2)`
## Composition Tag 对抗自然语言漂移(关键规则)
当 Hybrid 提示词中自然语言段落包含环境描述时,模型倾向于拉远镜头,忽略 `close-up`、`upper body`、`portrait` 等取景标签。必须采取以下对抗措施:
1. **对取景标签使用强权重**:`(upper body:2)`, `(close-up:3)`
2. **在自然语言首句中明确取景**:`The composition is a tight close-up portrait...`
3. 如果仍然拉远,继续提高权重至 `(upper body:5)` 甚至 `(upper body:7)`
## 多人物特征分离规则(Anima 最高风险项)
Anima 在多人场景中极易发生特征混淆。必须严格遵守:
1. **角色属性按角色分组排列**。同一角色的发型、瞳色、服装、体型连续出现后再切换。严禁交叉排列(如 `blue hair, red hair, short hair, long hair`)。
2. **自然语言中为每个角色写一句"外观锚定短语"**。格式:`CharacterName with [key features]...` 明确指出视觉归属。这比仅靠标签的防串扰效果强得多。
3. **使用空间方位词分离角色**:left/right/foreground/background。
4. **为易混淆特征使用权重**:`(blue hair:2)`, `(red hair:2)`。
5. **角色外观在标签块中充分描述**。官方文档明确指出:先命名角色,再描述其外观。仅列出角色名而不描述外观会让模型困惑。
6. **自然语言中不重复标签内容**——自然语言补充空间关系、互动动作、光影氛围、构图取景。
## 安全标签使用规则
- 在提示 prefix 中始终包含安全分级标签(safe / sensitive / nsfw / explicit)。
- 描绘现有角色时,**禁止使用 score_8、score_9 等过强标签**,以免过拟合导致角色特征丢失。使用 `score_7` 作为上限。
## 中文解释撰写规则
- 采用分点结构,每点对应一个设计决策。
- 解释覆盖:为何选择当前提示词架构、关键标签的作用、自然语言各句的功能。
- 多人物时**必须**解释角色分组策略。
- 必须包含自然语言部分的完整中文翻译。
- 语言中立、客观、技术化。不使用感叹号、表情符号或情绪化措辞。
- 避免冗长背景介绍,只解释本次提示词中实际出现的元素。
"""
@mcp.tool()
async def get_anima_format() -> str:
"""
返回 Anima 文生图模型的 Hybrid 混合提示词格式规范。
当用户提到「Anima 提示词」「Anima 格式」「Anima Prompt」「Anima 模型」等关键词时,
应在搜索标签完成、最终输出前调用此工具,以获取完整的提示词组装规范。
## 适用场景
- 用户明确要求输出 Anima 模型的提示词
- 用户提到 anima、Anima 等关键词
- 需要将标签转换为 Anima 的 Hybrid 混合格式
## Returns
包含完整 Anima 提示词格式规范的 Markdown 文本,涵盖标签格式化规则、
自然语言段落规则、权重语法、多人物防串扰规则等。
"""
return _ANIMA_FORMAT_INSTRUCTION
# ── NewBie 提示词格式说明 ─────────────────────────────────────────────────
_NEWBIE_OUTPUT_FORMAT = """
## 输出格式要求
你的输出包括两部分:一个 XML 代码块和代码块外的中文翻译。
### 标签处理规则
- 标签内部的空格必须替换为下划线 `_`(如 `red eyes` → `red_eyes`)
- 标签名内的括号必须用反斜杠转义(如 `momoko (momopoco)` → `momoko_\\(momopoco\\)`)
- 权重括号(如 `(daito:1.2)`)保持原样,不转义
- 括号内包含多个独立标签时,拆解为独立标签
### XML 结构
```xml
<img>
<character_1>
<n>角色名</n>
<gender>性别标签 (如 1girl)</gender>
<appearance>外貌特征 (发色, 瞳色, 身体特征等)</appearance>
<clothing>衣着 (具体服饰)</clothing>
<expression>表情</expression>
<action>动作</action>
<position>位置</position>
</character_1>
<!-- 若有多个角色,按 character_2, character_3 顺延 -->
<general_tags>
<count>人数标签</count>
<style>画风标签(若用户未指定,默认 anime_style,realistic_shading)</style>
<background>背景标签</background>
<atmosphere>画面情绪、氛围标签</atmosphere>
<quality>very_aesthetic, masterpiece, no_text</quality>
<resolution>max_high_resolution</resolution>
<artist>画师标签</artist>
<objects>各种物品(包括武器、饰品等)</objects>
<other>其它标签</other>
</general_tags>
<caption>
将所有标签串联为一段流畅、详细的英文场景描述。包含光线、情绪、角色和背景。
不要在此处提及 style 或 quality 类词汇。
</caption>
</img>
```
在 XML 代码块结束后,输出 `<caption>` 内容的中文翻译。
### 多人物规则(防特征混淆)
如果用户提到了多个人物,必须严格遵循以下规则:
1. **角色分组**:每个 character_N 块内连续排列该角色的所有专属属性(发型、瞳色、服装、体型、表情、动作),然后再切换到下一角色。
2. **外观标签充分**:每个角色至少 5 个角色特征标签。可使用 get_related_tags 获得更多特征。
3. **属性不交叉**:禁止将不同角色的同类属性交叉排列。不同角色的特征混淆是多人场景最常见的失败模式。
4. **空间锚定**:在 `<position>` 和 `<caption>` 中明确每个角色的空间位置(如"左侧"、"右侧"、"前景"等)。
5. **caption 角色锚定**:在 `<caption>` 中为每个角色写一句外观锚定短语,使用"[角色名] with [关键特征]"的句式,明确指出视觉归属。
"""
@mcp.tool()
async def get_newbie_format() -> str:
"""
返回 NewBie 文生图模型的 XML 格式提示词规范。
当用户提到「NewBie 提示词」「NewBie 格式」「NewBie Prompt」「NewBie 模型」等关键词时,
应在搜索标签完成、最终输出前调用此工具,以获取完整的 XML 格式组装规范。
## 适用场景
- 用户明确要求输出 NewBie 模型的提示词
- 用户提到 newbie、NewBie 等关键词
- 需要将标签转换为 NewBie 的 XML 格式
## Returns
包含完整 NewBie 提示词格式规范的文本,涵盖 XML 结构、标签处理规则、多人物规则等。
"""
return _NEWBIE_OUTPUT_FORMAT |