Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 45,874 Bytes
6c9a052 775d296 e3630d8 775d296 6c9a052 d98e7b3 6c9a052 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 6c9a052 e3630d8 6c9a052 54316b6 6c9a052 e3630d8 57c49db e3630d8 7cb2215 e3630d8 54316b6 e3630d8 6c9a052 e3630d8 6c9a052 e3630d8 6c9a052 e3630d8 54316b6 e3630d8 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 bcff293 6c9a052 570a57c 6c9a052 e3630d8 6c9a052 e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 570a57c e3630d8 2047809 e3630d8 6c9a052 5bb3b2d e3630d8 5bb3b2d e3630d8 5bb3b2d d632079 5bb3b2d 6c9a052 54316b6 5bb3b2d bcff293 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 6164549 e1b4c00 6164549 e1b4c00 e3630d8 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 57c49db e9af2b7 57c49db e9af2b7 57c49db e9af2b7 57c49db e9af2b7 57c49db e9af2b7 725431e e9af2b7 725431e e9af2b7 725431e e9af2b7 725431e e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 57c49db e9af2b7 2dab0ab e9af2b7 2dab0ab e9af2b7 2dab0ab e9af2b7 bcff293 2ba8b5f 6164549 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 2dab0ab bcff293 71cc343 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 71cc343 bcff293 6164549 bcff293 e9af2b7 2dab0ab bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 57c49db e9af2b7 bcff293 2dab0ab bcff293 775d296 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 2dab0ab e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 e9af2b7 bcff293 775d296 e9af2b7 775d296 e9af2b7 775d296 2ba8b5f 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 e3630d8 | 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 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 | """
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_artist_profile 查询单个画师常见共现标签
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),
)
def _resolve_canonical_tags(tagger: DanbooruTagger, tags: list[str]) -> tuple[list[str], list[str], dict[str, str], dict[str, list[str]]]:
"""轻量解析 canonical tag 名,不调用语义搜索。"""
resolved_tags: list[str] = []
invalid_tags: list[str] = []
corrections: dict[str, str] = {}
candidates: dict[str, list[str]] = {}
for raw_tag in tags:
resolved = tagger.resolve_tag_name(raw_tag)
tag = resolved.get("tag")
if tag:
resolved_tags.append(tag)
if tag != raw_tag:
corrections[raw_tag] = tag
continue
invalid_tags.append(raw_tag)
if resolved.get("candidates"):
candidates[raw_tag] = resolved["candidates"]
return resolved_tags, invalid_tags, corrections, candidates
@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:
"""
使用自然语言搜索 Danbooru 视觉标签、角色标签、作品标签,并返回可直接用于提示词的 tag 列表。
本工具适合搜索可见画面内容:主体、服装、姿势、动作、表情、背景、构图、角色名、作品名等。
不要用本工具搜索画师名、画师风格、creator/artist lookup,也不要用它验证某个画师标签是否存在。
遇到 "Mika Pikazo style"、"画师 mika_pikazo"、"by redjuice"、"这个画师常画什么" 这类请求时,
应改用 get_artist_profile。若用户同时给出画师/风格参考和可见画面描述,只把可见画面描述交给
search_tags,不要把画师名放进 query。
## 参数
- query: 自然语言画面描述。推荐使用中文。
- search_mode: 搜索策略。**默认是 "full_scene";除非用户明确想探索多种候选,否则保持默认。**
"full_scene" — **默认。** 用户给出具体画面描述时使用:场景、主体、服装、姿势、动作、
背景等,不管描述多长、元素多少。用户想要的是一张图的一组连贯提示词。
(e.g. "一个穿着白色水手服的少女在雨中奔跑", "金发双马尾女孩坐在教室窗边看书,夕阳",
"芙兰朵露 金发 辫子 发带 连衣裙 围裙 灯笼裤")
"concept_explore" — **只用于开放式概念浏览。** 当用户想看某个模糊/单一概念有哪些类型、
想从大量候选中挑选时使用。会返回最多 80 个候选,token 成本较高。
不要因为描述元素多就使用此模式;详细场景仍然属于 "full_scene"。
(e.g. "各种各样的汉服", "兔耳朵都有哪些", "赛博朋克服装有什么风格")
"subject_describe" — **只用于描述一个单一视觉概念。** 此模式关闭分词,不能解析多元素 query。
如果 query 包含角色名 + 属性、多个服装物件、或任何组合场景,应使用
"full_scene"。
适合:"EVA中蓝发的驾驶员"(单一角色概念)、"灯笼裤"(单一物件)、
"两侧有开口,前方有拉绳的运动短裤"(带细节的单一物件)。
"precise_lookup" — 精确查词 / 拼写纠错,例如 "selafuku"、"thighhigh"。
- 判断规则:用户是想得到一张具体图的提示词(→ full_scene),还是想浏览某个概念的多种候选
(→ concept_explore)?元素数量不是判断依据,探索意图才是。
- 重要:只要 query 是具体场景、多元素组合、角色 + 属性,就用 "full_scene"。拿不准时也用
"full_scene",它能处理具体画面描述。
- category: 限定搜索类别。默认 "all"。
"all" — 全部(通用 + 作品 + 角色)
"general" — 可见属性、服装、姿势、背景等通用标签
"character" — 角色标签
"copyright" — 动画/游戏/作品名等版权标签
- show_nsfw: 是否包含 NSFW 标签。默认 True。
- include_wiki: 是否在结果中附带 wiki 说明。默认 False。
当标签含义不熟悉、需要消歧时设为 True。
## query 写法建议
可以使用**空格、换行、中文逗号(,)、顿号(、)**手动分隔概念。
被分隔符包围且长度不超过 7 个汉字的片段会尽量保持原子性,搜索引擎会尊重你的拆分意图。
| 写法 | 示例 |
|---|---|
| 空格分隔概念 | `运动社团 校队 比赛 运动会` |
| 顿号分隔概念 | `反乌托邦、赛博朋克、蒸汽朋克` |
| 自然句子 | `一个穿着白色水手服的少女在雨中奔跑` |
| 混合写法 | `运动社团 一个穿水手服的少女` |
## 工作流
调用 search_tags 后,可以把选中的标签传给 get_related_tags,通过共现关系发现互补标签。
可按 search_tags → get_related_tags → get_related_tags → search_tags 多跳探索。
## 返回
JSON 对象,包含 prompt(逗号分隔 tag)、keywords、results。
每个 result 包含 tag、cn_name;当 include_wiki=True 时额外包含 wiki。
"""
_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,
}
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:
"""
根据已给定的 Danbooru 标签列表,返回基于 NPMI 共现评分的关联标签推荐。
本工具只支持通用标签、作品标签、角色标签;**不支持画师标签和 meta 标签。**
不要用本工具搜索画师名、画师风格、creator/artist lookup,也不要用它验证某个画师标签是否存在。
如果用户询问某个具体画师常画什么,或询问画师风格参考,应使用 get_artist_profile。
本工具会找出在 Danbooru 中经常与种子标签共同出现的标签。结果会按设计混合
General / Character / Copyright 类别。
## 典型用法
- 属性 → 拥有该属性的角色
例如 ["fingerless_gloves"] → tifa_lockhart, cammy_white, bridget_(guilty_gear), ...
- 作品 → 作品中的角色
例如 ["overlord_(maruyama)"] → shalltear_bloodfallen, ainz_ooal_gown, albedo_(overlord), ...
- 角色 → 该角色常见视觉属性
例如 ["amiya_(arknights)"] → 服装、表情、配饰等
- 主题探索
例如 ["fighter_jet"] → 飞机类型、动作、背景等
- 多标签交集
例如 ["maid", "twintails"] → 与该组合强相关的标签,按聚合 NPMI 评分排序
如果要做同类别内部探索,例如“更多类似 X 的服装标签”,请使用 search_tags 并设置 category。
## 工作流
可按 search_tags → get_related_tags → get_related_tags → search_tags 链式调用。
沿共现图多跳探索时,可以发现单纯语义搜索不容易召回的标签。
## 参数
- tags: canonical Danbooru tag 名列表,使用下划线,不使用空格。
例如 ["white_serafuku", "sailor_collar"]
- limit: 最多返回的推荐数量。默认 50。
- show_nsfw: 是否包含 NSFW 标签。默认 True。
- include_wiki: 是否在结果中附带 wiki 说明。默认 False。
当结果标签不熟悉、需要消歧时设为 True。
## 返回
JSON 对象,results 按聚合 NPMI 分数降序排序。每个结果包含:
- tag, cn_name
- sources: 对该推荐有贡献的种子标签
- wiki: 仅当 include_wiki=True 时返回
"""
tagger = await DanbooruTagger.get_instance()
corrected_tags, invalid_tags, corrections, candidates = _resolve_canonical_tags(tagger, tags)
if not corrected_tags:
payload = {
"error": "所有传入的标签均不存在于标签表中",
"invalid_tags": invalid_tags,
}
if candidates:
payload["candidates"] = candidates
return json.dumps(payload, ensure_ascii=False, indent=2)
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,
"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:
"""
根据标签-画师 NPMI 共现数据,推荐擅长绘制给定标签的画师。
输入一组 canonical Danbooru 标签(例如角色名、服装、主题、视觉元素),本工具会返回作品中
经常与这些标签共同出现的画师,并按聚合 NPMI 分数排序。
本工具用于 tag → artist 推荐。输入必须是 canonical Danbooru tag 名,不是画师名。
不要用本工具查询某个具体画师;画师 → 常见标签应使用 get_artist_profile。
## 参数
- tags: canonical Danbooru tag 名列表,使用下划线,不使用空格。
例如 ["1girl", "blue_hair", "school_uniform"]
- limit: 最多返回的画师数量。默认 30。
- min_cooc: 单个 (tag, artist) 组合进入计算所需的最小共现次数。默认 3。
- show_nsfw: 是否包含 NSFW 画师数据。默认 True。
## 返回
JSON 对象,results 按 NPMI 分数降序排序。每个结果包含:
- artist: Danbooru 画师 tag 名
- cooc_count: 所有输入标签上的累计共现次数
- post_count: 该画师在 Danbooru 的作品数
- sources: 命中该画师的输入标签
- top_tags: 该画师最常画的前 10 个标签(带中文名)
"""
tagger = await DanbooruTagger.get_instance()
if not tags:
return json.dumps({"error": "tags 列表不能为空"}, ensure_ascii=False, indent=2)
corrected_tags, invalid_tags, corrections, candidates = _resolve_canonical_tags(tagger, tags)
if not corrected_tags:
payload = {
"error": "所有传入的标签均不存在于标签表中",
"invalid_tags": invalid_tags,
}
if candidates:
payload["candidates"] = candidates
return json.dumps(payload, ensure_ascii=False, indent=2)
results = await tagger.search_artists_by_tags_async(
corrected_tags, limit=limit, min_cooc=min_cooc,
)
# 获取每个画师最常画的标签
artist_names = [r.artist for r in results]
top_tags_map = tagger.get_artist_top_tags(artist_names, show_nsfw=show_nsfw)
output = []
for r in results:
item = {
"artist": r.artist,
"cooc_count": r.cooc_count,
"post_count": r.post_count,
"sources": r.sources,
"top_tags": top_tags_map.get(r.artist, []),
}
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)
@mcp.tool()
async def get_artist_profile(
artist_name: str,
top_n: int = 20,
show_nsfw: bool = True,
) -> str:
"""
在画师-标签共现数据库中查询单个 Danbooru 画师,并返回该画师常见共现标签。
当用户询问某个具体画师或画师风格参考时使用本工具,例如:
"Mika Pikazo style"、"画师 mika_pikazo"、"by redjuice"、"这个画师常画什么"。
本工具查询的是画师数据库,不是普通视觉 tag 搜索索引。
画师名会在查询前自动规范化。因此,当数据库中存在 "mika_pikazo" 时,
"Mika Pikazo"、"mika pikazo"、"mika_pikazo"、"MikaPikazo" 都可以解析到它。
## 参数
- artist_name: 画师名或 Danbooru 画师 tag。允许大小写差异和空格。
- top_n: 最多返回的常见标签数量。默认 20。
- show_nsfw: 是否包含 NSFW 常见标签。默认 True。
## 返回
JSON 对象,包含:
- artist: 解析后的 canonical Danbooru 画师 tag
- input: 原始输入
- matched_by: 匹配方式,可能是 exact / normalized_exact / compact_exact / fuzzy
- post_count: 该画师在共现数据库中的作品数
- top_tags: 常见共现标签列表,每项只包含 tag 和 cn_name
- note: 说明这些常见标签只能作为风格参考,不等于完整画风语义描述
如果没有找到唯一画师,会返回 artist_not_found 和候选画师名。这不代表该画师 tag 在 Danbooru
不存在,也不要改用 search_tags 验证画师名。
"""
tagger = await DanbooruTagger.get_instance()
profile = tagger.get_artist_profile(
artist_name,
top_n=max(1, min(int(top_n), 100)),
show_nsfw=show_nsfw,
)
await counter.increment()
await counter.increment_mcp()
if "error" not in profile:
await counter.increment_success()
await counter.increment_copy()
return json.dumps(profile, ensure_ascii=False, indent=2)
# ── Anima 提示词格式说明 ─────────────────────────────────────────────────
_ANIMA_FORMAT_INSTRUCTION = """
# Anima Hybrid Prompt Format Specification
请严格按以下 Anima 混合提示词(Hybrid Prompt)规范,基于提供的标签和用户描述,输出最终结果。
## 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` 等取景标签,生成广角镜头。解决方案是对取景标签使用权重语法。
---
## 情境因果锁(组装前必做)
组装 prompt 前,先建立情境因果链,再拆解为两层内容:
```
发生了什么 → 角色的情感/欲望/冲突 → 具体反应(表情+肢体) → 环境如何参与 → 最抓人眼球的画面瞬间
```
- 先定情境,再选 hard tags、soft phrases、nltags。
- 情境必须包含因果链:事件起因 → 角色反应 → 可见后果。
- 即使是单人图,也要有内在张力(例:偷穿大衣的体温升高 → 颤抖+脸红+抓衣服)。
- 只选一个最有张力的瞬间,不描述连续剧情。
### 因果可见性
- 每个关键动作必须产生至少一个可见后果。
- 环境事件必须影响角色、道具、服装、头发、表情或构图层次。
- 角色情绪必须落到表情、视线、手势、身体重心或距离变化。
- 手部动作必须明确接触对象、接触位置和结果。
- 天气/季节不能只写 tag,必须落到可见物理效果。
- 看不见后果的动作不写;无法明确归属的动作改写成 nltags。
---
## 两层 Prompt 结构
prompt 内部分两层组装,同一语义不跨层重复:
### 第一层:硬锚点(Hard Tags)
经 Danbooru 检索确认的离散标签,负责主体结构与精度。
**包含:**
- 质量/年代/安全:`masterpiece, best quality, very aesthetic, score_7, safe, newest, year 2025`
- 人数/性别:`1girl, 1boy, 2girls, solo`
- 角色/作品:经确认的 character 和 series 标签
- 画师:`@artist name`(必须带 @)
- 确认的外观:发色、瞳色、发型、体型(经检索确认或热门角色已知)
- 确认的服装/道具:经检索确认的关键服装和道具
- 确认的姿势/表情/场景单标签:`sitting, smile, classroom`
**不包含:**
- 未经确认的模糊描述
- 完整英文句子
- 构图、光影、氛围(这些交给下层)
### 第二层:空间叙事(NL Tags Block)
有语法结构的连续描述,负责 hard tags 和 soft phrases 难以精确表达的内容。
特别提示:画面的逻辑需要由空间叙事描述。例如:如果场景有大风,那么画面各处的风向应当一致。如果场景是室内,那么室内桌椅板凳的布局和位置必须合理。
这些画面逻辑应由自然语言部分负责描述。
**包含:**
- 镜头取景:angle, shot distance, framing (close-up, wide shot, dutch angle…)
- 光线:方向、质感、色温 (rim light, volumetric god rays, warm key light…)
- 色彩调性:palette, color grading (monochromatic indigo, vibrant cel-shaded…)
- 空间布局:谁在左边、谁在右边、前后层次
- 空间逻辑合理性叙述:场景光照方向、风向一致,室内布局合理,角色与物品互动合理
- 多角色空间关系与动作归属
- 手和道具的精确接触关系
- 视线引导与构图层级
- 因果链的可见后果
- 景深、虚化、清晰区域
**规则:**
- 严格 2 到 3 句英文。严禁过长,否则会严重破坏模型性能。
- 不重复已在 hard tags 中出现的外观/服装。
- 不写离散 tag 列表、不写文学比喻、复杂修辞、高阶词汇、世界观解释。语言应尽量简明扼要。
- 使用客观、具体、视觉化的描述。
---
## 输出格式
````markdown
## Prompt
```
[硬锚点层:逗号分隔,单行]
[空间叙事层:2 到 3 句英文]
```
## 中文解释
[分点说明提示词设计逻辑,包含空间叙事层的完整翻译]
````
**绝对禁止**在任何部分之外添加开场白、寒暄或总结。
---
## 八维补全检查(输出前必做)
两层组装完成后,自查以下 8 个维度,**至少触发 3 维以上**。缺失的维度用空间叙事层补全,不硬塞更多 Danbooru 标签。
| 维度 | 检查问题 | 缺失表现 | 补全方向 |
|------|----------|----------|----------|
| **互动** | 元素之间有无行为联系? | 各自独立摆 pose,零交集 | 对视、触碰、动作呼应、人与环境互动 |
| **情感** | 表情+肢体传递了什么情绪? | generic smile / 面无表情 | 微表情、身体语言(前倾/缩肩/攥拳) |
| **视线** | 目光或引导线指向哪里? | 所有人看镜头或闭眼 | 角色间对视、偷瞄、看向画外某物 |
| **联动** | 环境是否影响主体? | 环境是纯背景装饰 | 风雨→反应、光线→塑型、材质受环境影响 |
| **动势** | 冻结画面暗示了运动吗? | 像摆拍立绘,重心正中 | 重心偏移、布料飞扬、头发飘动、失衡感 |
| **空间** | 有前后层次和呼吸感吗? | 平铺直叙,贴脸输出 | 前景遮挡、景深虚化、正负空间、引导线 |
| **质感** | 材质有真实细节吗? | 塑料感/卡通化 | 湿润反光、粗糙纹理、丝滑垂坠、水珠凝结 |
| **因果** | 观众能看出前因后果吗? | 不知道在发生什么 | 行为起因→当前姿态→暗示后续 |
**规则:**
- 补全内容必须服务于已有情境因果链,不能凭空插入无关元素。
- 单人图:互动维转为「主体与环境的互动」(风吹头发、踩水溅起、光影打在脸侧)。
- 空间叙事层是补全八维的主要载体,hard tags 维持硬锚点干净。
---
## 标签质量检查(输出前必做)
### 冲突消解
组装前必须消解以下冲突,逐项通过后才输出:
#### 视角互斥示例
| 标签A | 标签B | 原因 |
|---|---|---|
| `from front` | `from behind` | 物理矛盾 |
| `from above` | `from below` | 物理矛盾 |
| `looking at viewer` | `facing away` | 视线矛盾 |
| `pov` | `full body` | POV 不可能看到自己全身 |
| `close-up` | `full body` | 景别矛盾 |
#### 身份互斥示例
| 标签A | 标签B | 原因 |
|---|---|---|
| `solo` | `hetero` / `1boy` / `yuri` | 单人不存在互动 |
| `femdom` | `male-on-female rape` | 逻辑矛盾(主导方冲突) |
| `sleeping` / `unconscious` | `looking at viewer` | 无意识不可能直视 |
| `blindfold` | `heart-shaped pupils` / `rolling eyes` | 看不到眼睛 |
#### 服装互斥示例
| 标签A | 标签B | 原因 |
|---|---|---|
| `completely nude` | 任何具体服装标签 | 全裸不穿衣 |
| `pantyhose` | `barefoot` | 穿了丝袜不可能光脚(除非 `torn pantyhose`) |
| `blindfold` | `glasses` | 物理冲突 |
| 内衣套装 (`cat lingerie`, `lace lingerie`, `babydoll`, `negligee`, `chemise` 等) | `no panties` / `bottomless` | 内衣套装隐含包含内裤,模型优先解析套装忽略暴露标签;需暴露时拆为单件(`cat bra` + `no panties`) |
> **不互斥**:外衣/制服(`maid outfit`、`school uniform`、`bunny suit`、`sailor uniform` 等)与 `no panties` / `bottomless` 完全兼容——穿制服不穿内裤 = 合理场景。
#### 动作互斥示例
| 标签A | 标签B | 原因 |
|---|---|---|
| `standing sex` | `lying` / `on back` | 体位矛盾 |
| `missionary` | `doggystyle` | 不可能同时两个体位 |
| `cowgirl position` | `prone bone` | 体位矛盾 |
#### 细节过多互斥示例
同一身体部位同时堆叠多个细节标签会导致模型过度渲染,产生畸形。**每部位细节标签 ≤2 个,且不能互斥。**
| 部位 | 矛盾组合 | 原因 |
|---|---|---|
| 脚趾 | `spread toes` + `toe scrunch` / `toes curling` | 舒展 vs 蜷缩,物理矛盾 |
| 脚趾 | `spread toes` + `feet together` | 分趾需要空间,合拢则压缩 |
| 手指 | `spread fingers` + `clenched fist` / `gripping` | 张开 vs 握拳 |
| 胸部 | `bouncing breasts` + `breasts squeeze together` | 弹跳 vs 挤压,动态矛盾 |
| 嘴巴 | `open mouth` + `clenched teeth` / `closed mouth` | 张嘴 vs 闭嘴 |
| 眼睛 | `rolling eyes` + `looking at viewer` | 翻白眼 vs 直视 |
| 腿部 | `spread legs` + `legs together` | 分开 vs 并拢 |
| 足部整体 | 3 个以上足部标签(如 `foot focus` + `footjob` + `toe scrunch` + `spread toes`) | 过度细化导致脚趾/脚掌畸形 |
### 视线保护规则
**单人场景下**,除非用户明确要求「背影/背对/转身离开/侧脸/profile/from behind」等具体视线限制,否则必须注入 `direct eye contact, facing viewer`。
**两人及以上场景**:不强制注入 `direct eye contact`。根据角色间互动关系选择合适的视线标签(如 `looking at another`),或由用户明确指定。
### 标签数量
组装前按照下面的表格检查标签数量,严禁输出过多标签。过多标签会破坏模型的注意力。
| 场景复杂度 | 总标签数 |
|---|---|
| 简单 | 16-30 |
| 标准 | 22-38 |
| 复杂(多人/特殊主题/剧情主视觉) | 30-48 |
---
## 标签格式化规则
- 所有标签小写,下划线 `_` 替换为空格。**唯一例外**:`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],[多人互动标签,例如:duo, holding each other's hands...]
[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], [@artist]
```
---
## 标签体系速查
### 质量标签(任选其一或混用)
- 人工评分系:`masterpiece`, `best quality`, `good quality`,`very aesthetic`, `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)
### 安全分级
`safe`, `sensitive`, `nsfw`, `explicit`
### 艺术家标签
**必须以 @ 开头**。没有 @ 前缀的风格几乎不生效。格式:`@nnn yryr`, `@big chungus`
一段提示词中最多包含3个艺术家标签。
### 数据集标签(非动漫风格时的备选)
当且仅当用户明确要求抽象、油画、概念艺术、数字绘画、插画风格,且 **明确要求排除动漫风格** 时才可用。
如果用户仅要求油画风格,但没有明确说明排除动漫风格,仍然不能使用。
在提示词最开头另起一行使用,可大幅改变风格倾向:
- `ye-pop`:LAION-POP 数据集风格,偏抽象/油画/概念艺术
- `deviantart`:DeviantArt 数据集风格,偏数字绘画/插画
---
## 默认前缀与默认值
**正向前缀**(无特殊要求时的默认值):
```
masterpiece, best quality, very aesthetic, score_7, safe,
```
**取景默认**:若用户未指定,默认近景人物、人物面向观众。若用户有描述则以用户描述为准。
**模式默认**:采用 Hybrid 混合结构(硬锚点 + 空间叙事)。仅当用户明确要求纯标签或纯自然语言时才切换。
---
## 权重语法
Anima 支持 Prompt Weighting,但需要的权重值 **高于 SDXL**:
- 慎用权重:一段提示词中最多用权重强调4个标签,少而精,只强调最重要的部分
- 正常强调:`(tag:2)` 起步
- 强强调:`(tag:3)` 到 `(tag:5)`
- 权重取值范围:2 ~ 5
- 若用户提供 1.2 等较小权重,**必须放大至 2~5 区间**
- 多角色区分性特征(如一个蓝发一个红发)使用权重:`(blue hair:2)`, `(red hair:2)`
---
## Composition Tag 对抗自然语言漂移(关键规则)
当空间叙事层包含环境描述时,模型倾向于拉远镜头,忽略 `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. **互动词必须紧跟在人数后**。如果画中有多个人物,必须在人数声明完毕后,**立即** 写下他们的互动行为。推荐写法:2girls, duo, holding each other's hands,,然后开始分开描述每位美少女的容貌和衣服。
3. **空间叙事层中为每个角色写一句"外观锚定短语"**。格式:`CharacterName with [key features]... do something...` 明确指出视觉归属。这比仅靠标签的防串扰效果强得多。
4. **使用空间方位词分离角色**:left/right/foreground/background。
5. **为易混淆特征使用权重**:`(blue hair:2)`, `(red hair:2)`。
6. **角色外观在硬锚点层中充分描述**。官方文档明确指出:先命名角色,再描述其外观。仅列出角色名而不描述外观会让模型困惑。
7. **空间叙事层中不重复标签内容**——空间叙事层补充空间关系、互动动作、光影氛围、构图取景。
---
## 安全标签使用规则
- 在提示 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 = """
# NewBie XML Prompt Format Specification
## 输出格式要求
你的输出包括两部分:一个 XML 代码块和代码块外的中文翻译。
---
## 情境因果锁(组装前必做)
组装 prompt 前,先建立情境因果链,再拆解为 XML 各字段内容:
```
发生了什么 → 角色的情感/欲望/冲突 → 具体反应(表情+肢体) → 环境如何参与 → 最抓人眼球的画面瞬间
```
- 先定情境,再填充各 XML 字段。
- 情境必须包含因果链:事件起因 → 角色反应 → 可见后果。
- 即使是单人图,也要有内在张力(例:偷穿大衣的体温升高 → 颤抖+脸红+抓衣服)。
- 只选一个最有张力的瞬间,不描述连续剧情。
### 因果可见性
- 每个关键动作必须产生至少一个可见后果。
- 环境事件必须影响角色、道具、服装、头发、表情或构图层次。
- 角色情绪必须落到表情、视线、手势、身体重心或距离变化。
- 手部动作必须明确接触对象、接触位置和结果。
- 天气/季节不能只写 tag,必须落到可见物理效果。
- 看不见后果的动作不写;无法明确归属的动作改写进 `<caption>`。
---
## 标签处理规则
- 标签内部的空格必须替换为下划线 `_`(如 `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>` 内容的中文翻译。
---
## XML 字段职责划分
### character_N 块(离散标签层)
负责角色的结构化属性,使用 Danbooru 标签格式:
- `<n>`:角色名(经检索确认的 canonical name)
- `<gender>`:人数/性别标签
- `<appearance>`:发色、瞳色、发型、体型等外观特征(经检索确认)
- `<clothing>`:服装、配饰(经检索确认)
- `<expression>`:表情标签
- `<action>`:动作/姿势标签
- `<position>`:空间位置(left/right/foreground/background)
### general_tags 块(画面全局标签)
负责画面整体的结构化属性:
- `<count>`:人数标签
- `<style>`:画风标签
- `<background>`:场景/背景标签
- `<atmosphere>`:氛围/情绪标签
- `<quality>`:质量标签
- `<resolution>`:分辨率标签
- `<artist>`:画师标签
- `<objects>`:道具/物品标签
- `<other>`:其他标签
### caption 块(空间叙事层)
负责 hard tags 难以精确表达的内容,使用自然语言:
**包含:**
- 镜头取景:angle, shot distance, framing
- 光线:方向、质感、色温
- 色彩调性:palette, color grading
- 空间布局:角色间的位置关系、前后层次
- 多角色动作归属与互动
- 手和道具的精确接触关系
- 因果链的可见后果
- 景深、虚化、清晰区域
**规则:**
- 流畅的英文段落,不是标签列表。
- 不重复 character_N 和 general_tags 中已出现的标签内容。
- 不写 style 或 quality 类词汇。
- 使用客观、具体、视觉化的描述。
---
## 八维补全检查(输出前必做)
组装完成后,自查以下 8 个维度,**至少触发 3 维以上**。缺失的维度用 `<caption>` 补全,不硬塞更多标签。
| 维度 | 检查问题 | 缺失表现 | 补全方向 |
|------|----------|----------|----------|
| **互动** | 元素之间有无行为联系? | 各自独立摆 pose,零交集 | 对视、触碰、动作呼应、人与环境互动 |
| **情感** | 表情+肢体传递了什么情绪? | generic smile / 面无表情 | 微表情、身体语言(前倾/缩肩/攥拳) |
| **视线** | 目光或引导线指向哪里? | 所有人看镜头或闭眼 | 角色间对视、偷瞄、看向画外某物 |
| **联动** | 环境是否影响主体? | 环境是纯背景装饰 | 风雨→反应、光线→塑型、材质受环境影响 |
| **动势** | 冻结画面暗示了运动吗? | 像摆拍立绘,重心正中 | 重心偏移、布料飞扬、头发飘动、失衡感 |
| **空间** | 有前后层次和呼吸感吗? | 平铺直叙,贴脸输出 | 前景遮挡、景深虚化、正负空间、引导线 |
| **质感** | 材质有真实细节吗? | 塑料感/卡通化 | 湿润反光、粗糙纹理、丝滑垂坠、水珠凝结 |
| **因果** | 观众能看出前因后果吗? | 不知道在发生什么 | 行为起因→当前姿态→暗示后续 |
**规则:**
- 补全内容必须服务于已有情境因果链,不能凭空插入无关元素。
- 单人图:互动维转为「主体与环境的互动」(风吹头发、踩水溅起、光影打在脸侧)。
- `<caption>` 是补全八维的主要载体,character_N 和 general_tags 维持结构化标签干净。
---
## 冲突检查(输出前必做)
组装前必须消解以下冲突,逐项通过后才输出:
| 冲突对 | 规则 |
|--------|------|
| `solo` vs 多人 | 选一个,不共存 |
| `close-up` vs `full body` | 选一个景别 |
| `from above` vs `from below` | 选一个视角 |
| `from front` vs `from behind` | 选一个朝向 |
| `closed eyes` vs `looking at viewer` | 选一个视线 |
| 裸体 vs 服装 | 选一个着装状态 |
| 多角色属性归属 | 发色/服装必须绑定具体角色,不串 |
| 室内光源 vs 室外背景 | 光源和背景必须同空间 |
| 背光 | 必须补脸部补光或轮廓保护 |
单人正面默认保护脸部:保留 `looking at viewer` 或 `facing viewer`,`<caption>` 补一句脸部清晰。
多人必须在 `<position>` 和 `<caption>` 中明确空间方位。
---
## 多人物规则(防特征混淆)
如果用户提到了多个人物,必须严格遵循以下规则:
1. **角色分组**:每个 character_N 块内连续排列该角色的所有专属属性(发型、瞳色、服装、体型、表情、动作),然后再切换到下一角色。
2. **外观标签充分**:每个角色至少 5 个角色特征标签。可使用 `get_related_tags` 获得更多特征。
3. **属性不交叉**:禁止将不同角色的同类属性交叉排列。不同角色的特征混淆是多人场景最常见的失败模式。
4. **空间锚定**:在 `<position>` 和 `<caption>` 中明确每个角色的空间位置(如"左侧"、"右侧"、"前景"等)。
5. **caption 角色锚定**:在 `<caption>` 中为每个角色写一句外观锚定短语,使用"[角色名] with [关键特征]"的句式,明确指出视觉归属。
6. **caption 中不重复标签内容**——`<caption>` 补充空间关系、互动动作、光影氛围、构图取景。
---
## 默认值
**质量标签**(无特殊要求时的默认值):
```xml
<quality>very_aesthetic, masterpiece, no_text</quality>
<resolution>max_high_resolution</resolution>
```
**画风标签**(用户未指定时的默认值):
```xml
<style>anime_style, realistic_shading</style>
```
**取景默认**:若用户未指定,默认近景人物、人物面向观众。若用户有描述则以用户描述为准。
---
## 中文翻译规则
在 XML 代码块结束后,输出 `<caption>` 内容的完整中文翻译。
"""
@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
|