Spaces:
Running on CPU Upgrade
Running on CPU Upgrade
File size: 52,928 Bytes
6c9a052 775d296 e3630d8 775d296 6c9a052 d98e7b3 6c9a052 928b8a2 6c9a052 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 bcff293 d98e7b3 f6fee5c 6c9a052 e3630d8 6c9a052 54316b6 6c9a052 e3630d8 57c49db e3630d8 7cb2215 e3630d8 65b0b93 e3630d8 54316b6 e3630d8 6c9a052 e3630d8 6c9a052 e3630d8 6c9a052 e3630d8 54316b6 e3630d8 65b0b93 6c9a052 928b8a2 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 54316b6 6c9a052 bcff293 928b8a2 6c9a052 65b0b93 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 928b8a2 6c9a052 5bb3b2d e3630d8 5bb3b2d e3630d8 5bb3b2d d632079 5bb3b2d 6c9a052 928b8a2 6c9a052 54316b6 5bb3b2d bcff293 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 928b8a2 e1b4c00 e3630d8 e1b4c00 e3630d8 e1b4c00 6164549 e1b4c00 6164549 e1b4c00 e3630d8 928b8a2 e3630d8 bcff293 e9af2b7 bcff293 5973046 bcff293 5973046 bcff293 5973046 bcff293 5973046 70bed47 5973046 bcff293 5973046 bcff293 e9af2b7 5973046 e9af2b7 5973046 e9af2b7 70bed47 e9af2b7 5973046 70bed47 e9af2b7 57c49db e9af2b7 5973046 e9af2b7 65b0b93 e9af2b7 5973046 e9af2b7 5973046 bcff293 5973046 bcff293 5973046 e9af2b7 70bed47 e9af2b7 5973046 e9af2b7 70bed47 e9af2b7 65b0b93 70bed47 a12eb9b e9af2b7 5973046 e9af2b7 70bed47 e9af2b7 70bed47 2dab0ab 5973046 70bed47 65b0b93 2dab0ab 5973046 2dab0ab 70bed47 5973046 70bed47 2dab0ab 5973046 2dab0ab 5973046 70bed47 2dab0ab 5973046 2dab0ab 5973046 70bed47 5973046 22f3e17 70bed47 2dab0ab 70bed47 2dab0ab 70bed47 2dab0ab 5973046 2dab0ab 5973046 70bed47 5973046 e9af2b7 5973046 bcff293 5973046 bcff293 5973046 bcff293 5973046 e9af2b7 5973046 bcff293 70bed47 5973046 e9af2b7 5973046 bcff293 70bed47 e9af2b7 70bed47 5973046 bcff293 5973046 e9af2b7 5973046 bcff293 5973046 6164549 5973046 e9af2b7 a12eb9b 5973046 2dab0ab 65b0b93 5973046 70bed47 5973046 65b0b93 5973046 bcff293 e9af2b7 bcff293 5973046 bcff293 5973046 e9af2b7 5973046 bcff293 5973046 bcff293 5973046 e9af2b7 5973046 bcff293 5973046 bcff293 5973046 bcff293 e9af2b7 5973046 bcff293 5973046 bcff293 5973046 bcff293 e9af2b7 5973046 bcff293 a12eb9b 65b0b93 22f3e17 65b0b93 22f3e17 65b0b93 a12eb9b bcff293 a12eb9b 65b0b93 a12eb9b 65b0b93 a12eb9b e9af2b7 bcff293 5973046 70bed47 5973046 bcff293 5973046 bcff293 e9af2b7 5973046 70bed47 bcff293 5973046 bcff293 5973046 70bed47 5973046 bcff293 e9af2b7 bcff293 928b8a2 775d296 e9af2b7 775d296 e9af2b7 775d296 2ba8b5f 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 e9af2b7 775d296 928b8a2 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 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 | """
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 core.telemetry as telemetry
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)
# ── 过滤 Trae 非标准会话结束通知产生的校验噪音 ─────────────────────────────
class _SuppressTraeSessionStopNoise(logging.Filter):
_MARKERS = (
"Failed to validate notification",
"notifications/trae/session_stop",
)
def filter(self, record: logging.LogRecord) -> bool:
message = record.getMessage()
return not all(marker in message for marker in self._MARKERS)
# MCP 依赖在 shared.session 中直接使用根 logger 记录此校验警告。
logging.getLogger().addFilter(_SuppressTraeSessionStopNoise())
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"。仍使用语义搜索;官方 Tag Alias 仅用于
将召回的废弃标签规范化为当前标签。
- 判断规则:用户是想得到一张具体图的提示词(→ 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;搜索结果经官方 Alias 规范化时包含 alias_from;
当 include_wiki=True 时额外包含 wiki。
"""
await telemetry.increment("mcp_search_tags")
_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 成功不再冒充真实 UI 复制。
await counter.increment()
await counter.increment_success()
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 r.alias_from:
item["alias_from"] = r.alias_from
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 时返回
"""
await telemetry.increment("mcp_get_related_tags")
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 成功不再冒充真实 UI 复制。
await counter.increment()
await counter.increment_success()
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 个标签(带中文名)
"""
await telemetry.increment("mcp_get_artist_recommendations")
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_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 验证画师名。
"""
await telemetry.increment("mcp_get_artist_profile")
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()
return json.dumps(profile, ensure_ascii=False, indent=2)
# ── Anima 提示词格式说明 ─────────────────────────────────────────────────
_ANIMA_FORMAT_INSTRUCTION = """
# Anima Hybrid Prompt Format Specification
请严格按照以下规范,将已确认的 Danbooru 标签和用户描述整理为 Anima Hybrid 提示词。
## 核心原则
Anima 同时理解 Danbooru 标签和自然语言:
- **Hard Tags** 负责人物身份、外观、服装、动作、道具和场景锚点。
- **Natural Language** 负责第一视觉主体、构图模式、主体占比、画面分区、空间关系、光照、曝光和色彩。
- 第一视觉主体可以是人物、建筑、环境或道具;人物也可以只承担尺度标尺、色彩焦点或情绪入口。
- 当人物需要清晰可读时,复杂环境描述容易拉远镜头,因此必须同时明确人物大小、人物功能和环境层级。
- `full body` 只表示身体完整可见,不代表人物会占据主要画面。
- 权重只能强化标签,不能代替构图、主体占比和曝光描述。
不得为了增加画面感,擅自加入用户没有要求的前景、天气、道具、人物、戏剧冲突或复杂光效。
---
## 组装前确认
先确定以下内容:
第一视觉主体是什么 → 构图模式是什么 → 主要视觉锚点位于哪里 → 人物承担什么功能 → 前中远景如何组织 → 主光与色彩如何建立主次
构图模式只选择一种:
- **人物主导**:人物是第一视觉主体,环境辅助人物。
- **人景平衡**:人物与主要环境锚点都清楚可读,彼此不吞没。
- **环境/建筑主导**:银河、巨构、城市或其他环境对象是第一视觉主体,人物可以退居次要位置。
- **小人物尺度锚点**:人物主要用于证明环境尺度或提供叙事入口,只需轮廓、位置、朝向和关键识别点可读。
- 有剧情时,只选择一个可见瞬间,不描述连续事件。
- 安静日常场景不强制添加内在冲突、动势或戏剧性光影。
- 动作、视线、道具接触和环境关系必须符合物理逻辑。
- 用户已要求的风、雨、雾、夕阳、霓虹或植物等环境因素,应至少产生一个与画面逻辑一致的可见影响,例如衣物飘动、倒影、光晕、轮廓光或表面覆盖;不得为满足本规则擅自添加新环境因素。
---
## 两层 Prompt 结构
### 第一层:Hard Tags
使用用户已提供或经检索确认的逗号分隔标签。
**包含:**
- 质量、年代和安全分级
- 人数、性别、角色、作品
- `@artist`
- 发色、瞳色、发型、体型
- 服装、道具
- 姿势、表情、动作
- 简洁场景锚点,如 `library, bookshelf, window`
**不包含:**
- 完整英文句子
- 未确认或编造的标签
- 主体占比、画面布局和前景限制
- 光源方向、主体曝光和色彩主次
- `warm colors`、`dim lighting` 等自由形式的审美短语
### 第二层:Natural Language
简单人物主导场景通常使用2~3句简洁、具体、可视化的英文;复杂环境主视觉通常使用3~5句;多人物场景优先为每个角色单独写一句,通常使用3~6句。句数以职责清楚为准,禁止为了满足句数限制制造嵌套长句或删除必要的空间信息。
推荐职责:
1. 第一句:景别、第一视觉主体、构图模式、主要视觉锚点和整体布局。
2. 使用画面左/右/中央/上/下与前景/中景/远景建立构图地图,说明主要分区、框景元素、引导线、呼吸空间和有意出框。
3. 单人物时,说明人物大小、画面位置、承担的功能、动作、视线、道具接触和环境的可见影响;多人物时,先按角色分别说明画面位置、身份,以及按可见类别选出的代表性特征。
4. 多人物的角色锚定完成后,再用独立句子说明互动,明确写出动作发起者、承受者、接触对象和视线方向。
5. 最后一句:主光源、主体曝光、色彩角色和景深。
单人物不要机械复述 Hard Tags 中已经明确的外观和服装。多人物为了明确属性归属,每个角色的锚定句必须复述“多人物特征分离规则”要求的各类代表性特征,但不要继续抄写同类别的其余次要 Hard Tags。
环境描述不要只写 `behind the character`、`recedes into the background` 或整体 `softly blurred`;应说明主要环境锚点占据画面的哪一侧、哪个深度层级和多大区域,并仅让最远处细节轻微虚化。
多人物场景必须使用“画面方位 + 角色名/身份 + 关键辨识特征”的独立短句建立角色锚点;详细规则见“多人物特征分离规则”。
---
## 构图规则
### 景别、主视觉与主体大小
第一视觉主体、景别和人物占比必须分别确定:
- `close-up`、`upper body`、`cowboy_shot`、`full body` 等决定可见范围。
- `dominates the frame`、`occupies most of the frame` 等描述决定人物、建筑或环境锚点的视觉优先级和大小。
- Hard Tags 必须与最终景别一致。用户已有标签即使本身正确,只要对应内容不会出现在当前取景范围内,也应删除,不能为了“保留已有标签”继续放入提示词。
- `close-up`:优先保留脸部、发型、头部饰品和表情,删除画面外的身体、下装、腿部、鞋袜及全身姿势标签。
- `upper body`:保留上半身服装、手臂和画面内可见动作;删除裙子、裤子等下装细节,以及腿部姿势、袜子、长筒袜和鞋子标签。
- `cowboy shot` 或大腿以上取景:可以保留可见的下装,通常删除袜子、鞋子、脚部动作及依赖完整身体的姿势标签。
- `full body`:身体、下装、腿部和鞋袜均实际可见时,才保留对应标签。
参考范围:
| 人物功能 | 人物占画面高度 |
|---|---:|
| 人物主导 | 65%~85% |
| 人景平衡 | 40%~65% |
| 环境中的可读人物 | 20%~40% |
| 纯尺度/叙事锚点 | 8%~25% |
不必机械输出百分比,可以写:
- `the character dominates the frame`
- `the seated figure occupies about two-thirds of the frame height`
- `the full body remains large and clearly readable`
- `the megastructure dominates the central skyline`
- `the entire left side opens into a luminous star field`
- `a small figure in the lower-right foreground provides a clear sense of scale`
### 默认构图
- 用户没有要求环境主视觉时,默认采用人物主导构图。
- 人物主导或人景平衡的全身图必须完整显示身体、鞋子和必要的承载物,但不得默认缩成远景小人物;用户明确要求小人物尺度锚点时除外。
- 坐姿全身图可以显示完整椅子,但人物仍应是主要视觉焦点。
- 已知画幅比例时,构图必须与画幅匹配。横向全身图不得用大量空白、墙壁或黑暗物体填充两侧。
- 宽画幅中人物位于左侧或右侧时,必须使用用户已有的环境元素明确填充另一侧及中间区域;除非用户要求留白或极简构图,不得留下未定义的大面积纯色空间。
- 巨构、银河、高架等第一视觉主体可以有意突破画框来表达尺度;人物裁切仍必须服从既定景别,不得把意外裁切解释成尺度设计。
### 主视觉与环境层级
- 只确定一个主要视觉锚点,其余人物、环境和道具按构图模式承担辅助、尺度或叙事作用。
- 人物主导时,窗户、太阳、极光、霓虹等高亮环境不得压过人物;人景平衡时,两者都要清楚可读。
- 环境/建筑主导或小人物尺度锚点模式中,主要环境可以拥有最高视觉优先级,但人物仍须满足其既定功能所需的可读性。
- 环境复杂时,必须同时说明主要视觉锚点、人物占比、画面分区和各层级主次,不能只把所有元素列为并列背景。
- 用户明确要求的主要环境必须清晰可辨,不能变成纯色或被整体严重虚化。
- 环境元素不得无故延伸到镜头前方形成狭窄通道;用户明确要求且用于框景、引导线、尺度或世界观表达时,可以进入前景。
### 前景限制
- 不得为了增加空间感自动添加前景遮挡。
- 除非用户明确要求,不得生成大片黑暗前景、严重遮挡、门框式夹景或隧道式构图。
- 用户明确要求的起重机、植物、高架、门框等前景,以及大片暗部或物体出框,只有在承担框景、引导线、尺度、遮光或世界观表达时才可保留。
- 前景不得无故遮挡第一视觉主体;人物主导时不能遮挡脸、手、主要动作或大部分身体,小人物尺度锚点模式中不能破坏人物轮廓和朝向的可读性。
- 需要空间层次时,优先使用中景、背景、引导线和景深。
---
## 光照、曝光与色彩
光照描述应明确:
```text
主光源 → 光线方向 → 照亮的主体部位 → 背景光作用 → 暗部细节
```
### 主体曝光
- 需要正常可见的人物图,应明确脸、眼睛、手或关键服装被主光照亮。
- 窗户、夕阳、极光、霓虹位于人物背后时,必须说明它是背景光或轮廓光。
- 强背景光场景如果要求人物清晰,应增加正面或侧前方主光、柔和补光。
- 除非用户明确要求剪影,否则人物不能完全落入死黑暗部。
- 必要时使用:
- `well-exposed subject`
- `visible facial features`
- `clear details in the shadows`
- `no silhouette`
### 有意剪影
- 用户明确要求剪影时,先确定是完全黑色剪影,还是保留少量服装、发型或暗部细节的可读暗色人物。
- 剪影轮廓必须与背景分离,发型、姿势、朝向和承担叙事作用的关键外形应可辨认。
- 人物可以作为第一视觉主体,也可以只作为尺度锚点;若剪影本身就是目标,不强制增加会破坏剪影意图的正面补光。
- 权重只能强化 `silhouette`,不能代替轮廓分离、人物功能和背光关系的描述。
### `dim lighting`
- `dim lighting` 表示低照度,不等于温馨或柔和。
- 只有用户明确要求昏暗、低调光、压抑氛围或剪影时才使用。
- 温馨室内场景优先使用:
- `soft warm interior light`
- `gentle warm key light`
- `cozy ambient lighting`
- 夜景中需要人物清晰时,必须同时指定照亮人物的光源。
### 色彩角色
- 确定一个主色倾向和最多两个辅助色,并说明每种颜色绑定的对象或画面区域。
- 明确大面积环境色、过渡色带、小面积强调色和中性色骨架分别由什么承担;这些角色不要求各自使用不同色相。
- 冷暖对比必须说明哪一方占主导,以及强调色出现在哪个具体物体上。
- 人物主导时,高饱和环境色不得压过人物肤色、眼睛和关键服装;环境主导时,高饱和环境可以成为第一视觉焦点,但人物应通过轮廓、位置或小面积强调色保持其既定功能。
- 避免同时堆叠多个互相竞争的调色词,却不说明主次。
- 用户要求明亮画面时,应明确使用 `bright`、`luminous`、`well-exposed` 或 `clear midtones`。
---
## 标签规则
### 标签顺序
```text
[quality/meta/year/safety] → [人数] → [character] → [series] → [@artist] → [外观/服装/动作/场景标签]
```
多人时,先完整列出人数,再按角色连续排列身份和作品锚点;同一角色的专属外观、服装和道具标签必须保持相邻,不与其他角色的同类属性交叉排列。
### 标签数量
以下数量是推荐的**目标范围**,不是必须满足的硬性上下限:
| 场景复杂度 | 总标签数 |
|---|---:|
| 简单 | 16~30 |
| 标准 | 22~38 |
| 复杂环境、多人或剧情主视觉 | 30~48 |
用户已有标签视为可信标签,通常不需要重新检索,但不代表必须全部保留。应尽量保留与最终画面一致、对人物识别或用户意图有帮助的标签;当已有标签过多时,可以按需删除一部分。
裁剪已有标签时按以下顺序处理:
1. 先删除与最终景别不一致、实际不会出现在画面中的标签。
2. 再删除与人数、动作、视线、姿势、服装状态或安全分级冲突的标签。
3. 再删除宽泛标签、近义重复标签,以及被更准确标签覆盖的标签。
4. 最后删除对人物区分和主要画面贡献较低的次要装饰、身体细节或场景细节。
应优先保留用户强调的内容、人物身份与作品、关键发型和瞳色、主要服装、核心道具、主要表情和动作。不要为了达到目标范围的下限而补充无关标签;如果完成必要裁剪后仍略高于目标范围,可以保留真正重要的标签,不要为了机械计数继续删除。
只保留关键且有区分度的标签:
- 宽泛标签与更准确的具体标签重复时,保留更准确的一项。
- `holding book, open book, reading` 等近义动作按实际需要精简。
- 同一身体部位的细节标签不超过两个。
- 同一概念不在 Hard Tags 和 Natural Language 中机械重复。
- Tag Dropout 意味着不需要列出所有相关标签。
### 格式
- 标签使用小写,空格替换下划线。
- `score_1` 到 `score_9` 保留下划线。
- 标签内括号使用反斜线转义。
- 画师标签必须带 `@`,最多使用三个画师。
- 标签之间使用一个逗号和一个空格。
- 不确定是否存在的标签不得编造,应改写到 Natural Language。
- 必须包含一个安全分级:`safe`、`sensitive`、`nsfw` 或 `explicit`。
### 冲突检查
输出前消解明显冲突:
- `close-up` 与 `full body`
- `from front` 与 `from behind`
- `looking at viewer` 与 `facing away`
- `solo` 与多人互动标签
- `open mouth` 与 `closed mouth`
- `spread fingers` 与 `clenched fist`
- `spread legs` 与 `legs together`
- 完全裸露与具体服装
- 同一动作的多个互斥姿态
### 视线
- 肖像或无明确动作对象时,可以默认看向观众。
- 阅读、工作、睡眠、观察道具等场景,视线必须服从当前动作。
- 用户要求背影、侧脸或看向画外时,以用户要求为准。
- 多人物场景根据互动关系确定视线,不强制看向观众。
---
## 默认前缀
### Anima Base
```text
masterpiece, best quality, score_7, safe,
```
### Anima Aesthetic
```text
masterpiece, best quality, safe,
```
Anima Aesthetic 默认不使用 `score_*`,避免对已完成美学微调的模型施加过强偏置。
### 其他规则
- 模型版本未知时使用 Anima Base 默认前缀。
- `very aesthetic`、`newest`、`year 2025` 不作为强制默认标签。
- 年代标签只在用户要求特定时期或画风时加入。
- Anima Turbo 的 CFG、步数等参数由外部工作流控制,不写入提示词。
- `ye-pop`、`deviantart` 只在用户明确要求对应的非动漫数据集风格时使用。
---
## 权重规则
Anima 支持 Prompt Weighting,但权重只是辅助控制:
- 从 `(tag:2)` 开始。
- 必要时提高到 `(tag:3)`~`(tag:5)`。
- 不得超过5。
- 一段提示词最多强调四个标签。
- 不得使用权重代替主体占比、背景层级和曝光关系。
- `(full body:2)` 只能强化全身可见,不能保证人物足够大。
- 背景导致景别漂移时,应先补全构图描述,再考虑提高景别权重。
---
## 多人物特征分离规则
Anima 的多人生成可以通过清晰的角色边界减少特征混淆,但提示词不能保证彻底消除串色。必须严格遵守:
1. **先声明准确人数与性别构成**:使用 `2girls`、`1girl, 1boy`、`3girls` 等与画面一致的标签;不得同时保留 `solo`,也不得用 `multiple girls` 代替已知的精确人数。
2. **先建立角色身份,再描述互动**:人数之后先写角色名及各自作品名。不要把互动标签插在角色身份之间,也不要只列角色名后立刻进入复杂动作。
3. **Hard Tags 按角色分组**:同一角色的专属发型、瞳色、服装、体型和道具连续出现后再切换到下一角色。严禁把不同角色的同类属性交叉排列,例如 `blue hair, red hair, short hair, long hair`。
4. **每个角色使用独立的 Natural Language 锚定句**:推荐结构为 `On the left side of the image is Character A from Series A, with [按规则5覆盖各可见类别的代表性特征].`;下一角色另起一句。不得把多名角色的外观塞进同一个嵌套长句。
5. **各类别至少一个代表性特征**:每个角色在设定中存在且在当前景别中可见的发型、上衣、下装、鞋袜、道具、姿势类别,各至少写一个代表性特征。某类别本来不存在,或因 `close-up`、`upper body` 等景别不会出现在画面中时,不得为了凑齐类别而编造或保留画面外特征。
6. **空间位置以画面/观众视角为基准并保持稳定**:使用 `on the left side of the image`、`on the right side of the image`、`in the center`、`in the foreground`、`in the background`。不要混用画面左右与角色自身左右;后文不得交换已分配的位置。
7. **互动句必须明确主语和宾语**:完成所有角色锚定后,再写 `Character A holds Character B's right hand`、`Character B looks at Character A` 等。避免连续使用含义不明的 `she`、`he`、`they`,避免笼统的 `interacting`、`together` 代替可见动作。
8. **区分专属属性与共享属性**:专属外观、服装、表情和道具必须放进对应角色的分组或锚定句;两人共有的服装、姿势或环境状态使用 `Both characters...` 单独说明,不得复制成含混的全局属性。
9. **每类只保留少量高区分度特征**:在满足规则5的前提下,每个可见类别通常只选择一个最有辨识度的代表性特征;只有人物识别确实需要时,同一类别才增加第二个。角色相似、人数达到3人以上或互动复杂时,应删除同类别的次要细节和不必要的同时动作,但不得删掉某个实际存在且可见类别的唯一代表性特征。
10. **权重不能代替属性归属**:只有在角色分组和自然语言锚定已经清楚时,才可谨慎强化关键特征。不得仅靠 `(blue hair:2)`、`(red hair:2)` 分离角色,也不得同时堆叠大量高权重属性。
11. **Natural Language 负责明确归属而非完整抄写**:角色锚定句必须复述规则5要求的各类别代表性特征,并补充空间位置、互动动作、光影对象和构图取景;同类别的其余次要 Hard Tags 不再重复。
推荐示意:
```text
2girls, character a, series a, short black hair, blue eyes, white jacket, blue skirt, black boots, shoulder bag, standing, character b, series b, long blonde hair, red eyes, black blouse, red skirt, white boots, suitcase, standing, holding hands, railway station
The image is divided into a left side and a right side, with both characters shown at the same readable scale. On the left side of the image is Character A from Series A, standing with short black hair, a white jacket, a blue skirt, black boots, and a shoulder bag. On the right side of the image is Character B from Series B, standing with long blonde hair, a black blouse, a red skirt, white boots, and a suitcase held in her left hand. Character A holds Character B's right hand while Character B looks at Character A. A soft side light keeps both faces clearly visible while the railway platform remains secondary in the background.
```
---
## 输出格式
````markdown
## Prompt
```
[Hard Tags:逗号分隔,单行]
[Natural Language:简单人物主导通常2~3句;复杂环境主视觉通常3~5句;多人物按角色拆句,通常3~6句]
```
## 中文解释
[分点解释实际使用的标签、构图、空间和光照设计,并完整翻译 Natural Language]
````
禁止在规定部分之外添加开场白、寒暄或总结。
---
## 最终自检
输出前确认:
1. 是否只有一个明确的第一视觉主体,并选择了人物主导、人景平衡、环境/建筑主导或小人物尺度锚点中的一种构图模式?
2. 是否只有一个明确景别;人物大小、画面位置和承担的主体、尺度、色彩或叙事功能是否一致?
3. 主要视觉锚点、人物和重要环境是否都有明确的画面方位、占据区域和前景/中景/远景归属?
4. 是否只确定一个主要环境锚点,其余环境、人物和道具均按构图模式承担清楚的辅助作用?
5. 前景、框景、大片暗部和物体出框是否具有明确作用,并避免意外遮挡第一视觉主体或破坏人物轮廓、脸、手和主要动作?
6. 是否明确主光源、光线方向、被照亮或形成剪影的对象,以及暗部需要保留的细节?
7. 夜景或背光场景中的剪影是用户要求的有意设计,还是应当通过主光或补光避免的意外结果?
8. 大面积环境色、过渡色带、小面积强调色和中性色骨架是否绑定了具体区域或物体,并符合当前构图模式的视觉优先级?
9. 用户已要求的风、雨、雾、夕阳、霓虹或植物是否产生了符合画面逻辑的可见影响,且没有为补全效果擅自添加新元素?
10. 标签是否去重并尽量落入目标范围;超出目标范围时,是否只保留了与取景、人物识别和用户重点真正相关的标签?
11. 动作、视线、姿势和道具关系是否一致;多人物时,人数、角色位置、独立锚定、动作归属和各可见类别的代表性特征是否清楚?
12. Natural Language 的句数是否与场景职责匹配,没有嵌套长句;是否只加入用户要求或画面逻辑真正需要的内容?
## 中文解释规则
- 只解释本次提示词中实际采用的设计。
- 说明关键标签、第一视觉主体、构图模式、人物功能与占比、构图地图、前景或出框作用、光照和色彩角色。
- 多人物时说明角色分组和动作归属。
- 必须完整翻译 Natural Language。
- 使用中立、简洁、技术化的语言。
"""
@mcp.tool()
async def get_anima_format() -> str:
"""
返回 Anima 文生图模型的 Hybrid 混合提示词格式规范。
当用户提到「Anima 提示词」「Anima 格式」「Anima Prompt」「Anima 模型」等关键词时,
应调用此工具,以获取完整的提示词组装规范。
## 适用场景
- 用户明确要求输出 Anima 模型的提示词
- 用户提到 anima、Anima 等关键词
- 需要将标签转换为 Anima 的 Hybrid 混合格式
## Returns
包含完整 Anima 提示词格式规范的 Markdown 文本,涵盖标签格式化规则、
自然语言段落规则、权重语法、多人物防串扰规则等。
"""
await telemetry.increment("mcp_get_anima_format")
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 结构、标签处理规则、多人物规则等。
"""
await telemetry.increment("mcp_get_newbie_format")
return _NEWBIE_OUTPUT_FORMAT
|