Files
my-v0-server/app/schemas/requirement.py
T
2026-09-30 06:49:32 -07:00

81 lines
3.7 KiB
Python

"""需求理解的输出协议,描述用户诉求,不包含前端执行动作。"""
from typing import Literal, Self
from pydantic import Field, field_validator, model_validator
from .base import ExtensibleModel, JsonObject, NonBlankString
from .operations import OPERATION_GUIDE, normalize_operation
Intent = Literal["create_screen", "modify_screen", "qa", "unclear"]
TargetScope = Literal["nodes", "canvas", "screen", "unresolved"]
class RequirementChange(ExtensibleModel):
"""一项需求变更。目标范围显式声明,避免空 ID 被理解成修改整个大屏。"""
target_scope: TargetScope = Field(
description="nodes=已有节点;canvas=画布属性;screen=整体或新增内容;unresolved=目标待确认"
)
target_ids: list[NonBlankString] = Field(
default_factory=list, description="已有节点的 ID;新增组件不能虚构 ID"
)
target_description: NonBlankString | None = Field(
default=None, description="目标的自然语言描述,例如右侧区域、用户所说的那个图"
)
operation: NonBlankString = Field(description=f"需求操作。{OPERATION_GUIDE}")
parameters: JsonObject = Field(
default_factory=dict,
description="需求参数,例如 delta_x=50;保留用户要求的单位与相对变化语义",
)
@field_validator("operation")
@classmethod
def normalize_operation_name(cls, value: str) -> str:
# 在协议层统一同义操作名,模型输出、测试构造和检查点恢复都经过这里
return normalize_operation(value)
@model_validator(mode="after")
def validate_target(self) -> Self:
if len(self.target_ids) != len(set(self.target_ids)):
raise ValueError("target_ids 不能包含重复 ID")
if self.target_scope == "nodes":
if not self.target_ids:
raise ValueError("nodes 范围必须提供 target_ids")
elif self.target_ids:
raise ValueError("只有 nodes 范围可以提供 target_ids")
if self.target_scope == "unresolved" and self.target_description is None:
raise ValueError("目标待确认时必须提供 target_description")
return self
class Requirement(ExtensibleModel):
intent: Intent
goal: NonBlankString = Field(
description="结合上下文改写后的独立完整需求,补全可确定的指代,保留目标、操作和约束,不扩大修改范围"
)
changes: list[RequirementChange] = Field(default_factory=list)
needs_clarification: bool
clarification_question: NonBlankString | None = None
assumptions: list[NonBlankString] = Field(
default_factory=list,
description="非阻塞信息采用的默认假设,不可用来猜测删除目标",
)
constraints: list[NonBlankString] = Field(
default_factory=list,
description="用户明确提出的约束,例如只修改右侧、保留其他组件",
)
@model_validator(mode="after")
def validate_consistency(self) -> Self:
if self.needs_clarification != (self.clarification_question is not None):
raise ValueError("需要澄清时必须提供问题,不需要澄清时问题应为 null")
if self.intent == "unclear" and not self.needs_clarification:
raise ValueError("意图不明确时必须澄清")
if any(change.target_scope == "unresolved" for change in self.changes):
if not self.needs_clarification:
raise ValueError("存在待确认目标时必须澄清")
if self.intent == "qa" and self.changes:
raise ValueError("纯问答需求不应包含大屏变更")
return self