feat(web): 角色群像按定位配比生成(role_mix 缩减版)

- 契约: CharacterGenerateRequest 加 role_mix{定位:数量},model_validator 归一
  count=各值之和(每项≥1、总和≤12),缺省退回单一 role+count
- @llm: build_character_gen_context/run_character_gen 加 role_mix 参并注入配比块
  (确定性、无时间戳,守 assemble 纯函数);router 穿参
- character-gen.md 加"按配比严格分配 role"纪律 + regen prompt_hashes 金标准
- 前端: sanitizeRoleMix/roleMixTotal + buildCharacterGenerateRequest 第4参 +
  useCharacterGen 穿参 + RoleMixEditor + CharacterGenerator 单一/配比双模
This commit is contained in:
Yaojia Wang
2026-07-06 17:24:16 +02:00
parent 72b3c81146
commit 7babb4854b
16 changed files with 428 additions and 50 deletions

View File

@@ -0,0 +1,51 @@
"""CharacterGenerateRequest.role_mix 校验单测(⑦ 群像按定位配比)。
纯 pydantic 校验,无 DB/网关。契约role_mix 在场时 count 取各值之和,
每项数量须 ≥ 1、总和须 ≤ MAX_GENERATE_COUNT。
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from ww_api.schemas.generation import MAX_GENERATE_COUNT, CharacterGenerateRequest
def test_role_mix_absent_keeps_explicit_count() -> None:
req = CharacterGenerateRequest(brief="剑修", count=3)
assert req.role_mix is None
assert req.count == 3
def test_role_mix_sets_count_to_sum() -> None:
req = CharacterGenerateRequest(brief="群像", role_mix={"主角": 1, "对手": 2, "配角": 3})
assert req.count == 6
def test_role_mix_overrides_provided_count() -> None:
# role_mix 在场时 count 取各值之和,忽略客户端显式传入的 count
req = CharacterGenerateRequest(brief="群像", count=1, role_mix={"主角": 2, "对手": 2})
assert req.count == 4
def test_role_mix_total_at_max_allowed() -> None:
req = CharacterGenerateRequest(brief="群像", role_mix={"主角": MAX_GENERATE_COUNT})
assert req.count == MAX_GENERATE_COUNT
def test_role_mix_sum_over_max_rejected() -> None:
with pytest.raises(ValidationError):
# 6 + 7 = 13 > 12
CharacterGenerateRequest(brief="群像", role_mix={"主角": 6, "对手": 7})
def test_role_mix_empty_dict_rejected() -> None:
with pytest.raises(ValidationError):
CharacterGenerateRequest(brief="群像", role_mix={})
def test_role_mix_nonpositive_value_rejected() -> None:
with pytest.raises(ValidationError):
CharacterGenerateRequest(brief="群像", role_mix={"主角": 0})
with pytest.raises(ValidationError):
CharacterGenerateRequest(brief="群像", role_mix={"主角": -1})