feat:接入mcp,为模型提供图表信息

This commit is contained in:
2026-10-01 06:33:44 -07:00
parent 618290d852
commit f1b2a70ad1
44 changed files with 6531 additions and 207 deletions
+69 -17
View File
@@ -5,6 +5,7 @@ from typing import Literal, Self
from pydantic import BaseModel, ConfigDict, Field, model_validator
from app.schemas.base import ExtensibleModel, JsonObject, NonBlankString
from app.schemas.material_field import MISSING, FieldGroup, lookup
from app.schemas.screen import Layout, PositiveNumber
@@ -26,15 +27,6 @@ class MaterialTemplate(ExtensibleModel):
events: list[JsonObject] = Field(default_factory=list)
class MaterialSetter(ExtensibleModel):
type: NonBlankString = Field(description="前端属性控件类型,不是完整的值类型约束")
label: NonBlankString
key: NonBlankString = Field(
description="前端原始点路径,例如 props.option.series.0.itemStyle.color"
)
props: JsonObject = Field(default_factory=dict)
class ComponentDefinition(ExtensibleModel):
model_config = ConfigDict(serialize_by_alias=True)
key: NonBlankString = Field(description="目录中的唯一编号,不是 Screen 节点 ID")
@@ -52,22 +44,26 @@ class ComponentDefinition(ExtensibleModel):
schema_: MaterialTemplate = Field(
alias="schema", description="完整默认物料配置,不包含实例 ID"
)
setters: list[MaterialSetter] = Field(default_factory=list)
field_groups: list[NonBlankString | FieldGroup] = Field(
default_factory=list,
description="字段分组:字符串引用目录的共享分组,对象为本物料独有的分组;"
"前端属性面板的 setters 由它派生,不单独保存",
)
eventOptions: list[JsonObject] = Field(default_factory=list)
sample_data: bool = Field(
default=False, description="默认配置是否含示例数据,不能视为真实业务数据"
)
props_schema: JsonObject | None = Field(
default=None,
description="可选的 props JSON Schema,未提供表示未知,不表示无限制",
)
style_schema: JsonObject | None = Field(
default=None, description="可选的 style JSON Schema,仅保存定义,当前不执行校验"
)
usage_notes: list[NonBlankString] = Field(
default_factory=list, description="配置、数据绑定及使用限制说明"
)
def template(self) -> JsonObject:
"""默认配置的 JSON;style 为 null 表示未设置,字段默认值按路径不存在处理"""
data = self.schema_.model_dump(mode="json")
if data.get("style") is None:
data.pop("style", None)
return data
@model_validator(mode="after")
def consistent_type(self) -> Self:
if self.type != self.schema_.type:
@@ -77,17 +73,73 @@ class ComponentDefinition(ExtensibleModel):
return self
def _default_problem(field, template: JsonObject) -> str | None:
"""默认值只有一个来源:默认配置有该路径时取它,没有时字段必须声明 default"""
try:
current = lookup(template, field.segments)
except ValueError:
return "的路径与默认配置结构冲突"
if current is MISSING:
if field.default is None:
return "在默认配置中不存在,需要声明 default"
error = field.value_error(field.default)
return f"的 default {error}" if error else None
if field.default is not None:
return "在默认配置中已有值,不能再声明 default"
error = field.value_error(current)
return f"的默认配置值 {error}" if error else None
class ComponentCatalog(ExtensibleModel):
version: NonBlankString = Field(
description="前端组件目录版本,后续用于检查是否与前端匹配"
)
components: list[ComponentDefinition]
field_groups: list[FieldGroup] = Field(
default_factory=list, description="多个物料共用的字段分组,物料按 key 引用"
)
@model_validator(mode="after")
def unique_keys(self) -> Self:
keys = [component.key for component in self.components]
if len(keys) != len(set(keys)):
raise ValueError("组件目录 key 不能重复")
shared = [group.key for group in self.field_groups]
if len(shared) != len(set(shared)):
raise ValueError("共享字段分组 key 不能重复")
for component in self.components:
groups = [
group.key if isinstance(group, FieldGroup) else group
for group in component.field_groups
]
if len(groups) != len(set(groups)):
raise ValueError(f"物料 {component.key} 的字段分组 key 重复")
if missing := [
group
for group in component.field_groups
if isinstance(group, str) and group not in shared
]:
raise ValueError(f"物料 {component.key} 引用了不存在的共享分组: {missing}")
if shadowed := [
group.key
for group in component.field_groups
if isinstance(group, FieldGroup) and group.key in shared
]:
raise ValueError(f"物料 {component.key} 的独有分组与共享分组重名: {shadowed}")
by_key = {group.key: group for group in self.field_groups}
fields = [
field
for group in component.field_groups
for field in (by_key[group] if isinstance(group, str) else group).fields
]
paths = [field.path for field in fields]
if duplicated := sorted({path for path in paths if paths.count(path) > 1}):
raise ValueError(f"物料 {component.key} 的字段路径重复: {duplicated}")
template = component.template()
for field in fields:
problem = _default_problem(field, template)
if problem:
raise ValueError(f"物料 {component.key} 字段 {field.path} {problem}")
return self