Files
PlantAI-rag-new/sdk/app/schemas.py
T
jerryandClaude Fable 5 0875bb036c P0/M1: 四部分重构——frontend→ui、backend→sdk 改名 + 引用修复 + 骨架
- git mv 保留历史:ui/(Vue3+Vite)、sdk/(BFF 中间层)
- 路径语义修正:db_path/figures_cache 改相对 sdk/(BFF cwd),
  历史库 sdk/backend/data 迁移至 sdk/data/(会话/图片缓存无损)
- 引用修复 ~15 处:config.py、deploy/*.sh、vitest/playwright 配置、
  单测导入路径、docs、README、gitignore、.env.example
- 骨架:agent/{qa,methodology,legacy-rag,vendor}、knowledge/manifest.yaml、
  tooling/{compile,kb,crypto,assemble,audit,tools,scripts} + README
- 门禁验证:sdk pytest 125 通过、ui vitest 158 通过、
  生产 BFF 脚本重启四上游全绿 + 静态托管 + 会话库无损

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 12:56:47 +08:00

256 lines
6.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""schemas.py —— Pydantic v2 模型(API.md 的契约形态)。
覆盖:块联合(§2)、SourceItem(§8)、GraphData / GraphNode / GraphEdge /
GraphChain(§9)、SessionOut / MessageOut(§5)、错误信封(§1)。
约定:模型 `extra="ignore"` + 全字段默认值,落库/返参前先经模型验证归一。
"""
from __future__ import annotations
from datetime import datetime, timezone
from typing import Any, Literal, Union
from pydantic import BaseModel, ConfigDict, Field
def utcnow_iso() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
# ---------------------------------------------------------------------------
# §2 块联合(判别字段 type)
# ---------------------------------------------------------------------------
class TextBlock(BaseModel):
type: Literal["text"] = "text"
html: str
class TableBlock(BaseModel):
type: Literal["table"] = "table"
headers: list[str] = Field(default_factory=list)
rows: list[list[str]] = Field(default_factory=list)
class CodeBlock(BaseModel):
type: Literal["code"] = "code"
text: str
class StreamBlock(BaseModel):
"""瞬态块:只出现在流式过程中,绝不落库。"""
type: Literal["stream"] = "stream"
text: str
class PipelineStep(BaseModel):
name: str = ""
state: Literal["waiting", "running", "done"] = "waiting"
desc: str = ""
phase: str = "" # planning|retrieval|evidence|generation|done(API.md §4)
class PipelineBlock(BaseModel):
type: Literal["pipeline"] = "pipeline"
status: Literal["running", "done", "error"] = "running"
steps: list[PipelineStep] = Field(default_factory=list)
class ToolLog(BaseModel):
text: str = ""
cls: str = "log-ok" # log-ok|log-run
class ToolBlock(BaseModel):
type: Literal["tool"] = "tool"
icon: str = ""
name: str = ""
status: Literal["running", "done", "error"] = "running"
dur: str = ""
open: bool = True
params: str = ""
result: str = ""
logs: list[ToolLog] = Field(default_factory=list)
phase: str = "retrieval"
class SourcesBlock(BaseModel):
type: Literal["sources"] = "sources"
items: list["SourceItem"] = Field(default_factory=list)
class GraphBlock(BaseModel):
type: Literal["graph"] = "graph"
nodes: list["GraphNode"] = Field(default_factory=list)
edges: list["GraphEdge"] = Field(default_factory=list)
chains: list["GraphChain"] = Field(default_factory=list)
Block = Union[
TextBlock, TableBlock, CodeBlock, StreamBlock,
PipelineBlock, ToolBlock, SourcesBlock, GraphBlock,
]
# ---------------------------------------------------------------------------
# §8 SourceItem —— 所有字段键必须存在(缺失为 null)
# ---------------------------------------------------------------------------
class SourceItem(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str | None = None
title: str | None = None
paper_id: str | None = None
chunk_id: str | None = None
pmid: str | None = None
pmcid: str | None = None
doi: str | None = None
url: str | None = None
snippet: str | None = None
score: float | None = None
locator: str | None = None
section: str | None = None
support_count: int | None = None
type: str | None = None
metadata: dict[str, Any] = Field(default_factory=dict)
# ---------------------------------------------------------------------------
# §9 图谱结构
# ---------------------------------------------------------------------------
class GraphNode(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str
label: str | None = None
type: str | None = None
class GraphEdge(BaseModel):
model_config = ConfigDict(extra="ignore")
source: str | None = None
target: str | None = None
relation: str | None = None
label: str | None = None
chain_id: str | None = None
hop: int | None = None
paper_id: str | None = None
pmid: str | None = None
pmcid: str | None = None
doi: str | None = None
class GraphHop(BaseModel):
model_config = ConfigDict(extra="ignore")
source: str | None = None
target: str | None = None
relation: str | None = None
source_type: str | None = None
target_type: str | None = None
paper_id: str | None = None
pmid: str | None = None
pmcid: str | None = None
doi: str | None = None
class GraphChain(BaseModel):
model_config = ConfigDict(extra="ignore")
chain_id: str | None = None
hops: list[GraphHop] = Field(default_factory=list)
class GraphData(BaseModel):
"""消息内 graph 块与 /api/kg/neighborhood 共用的 {nodes,edges,chains} 形态。"""
model_config = ConfigDict(extra="ignore")
nodes: list[GraphNode] = Field(default_factory=list)
edges: list[GraphEdge] = Field(default_factory=list)
chains: list[GraphChain] = Field(default_factory=list)
# ---------------------------------------------------------------------------
# §5 会话与消息
# ---------------------------------------------------------------------------
class SessionMeta(BaseModel):
title_manual: bool = False
class SessionCreate(BaseModel):
title: str = "新会话"
def normalized_title(self) -> str:
return self.title.strip() or "新会话"
class SessionPatch(BaseModel):
title: str | None = None
status: Literal["running", "done", "error", "idle"] | None = None
class SessionOut(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str
title: str
status: str = "idle"
created_at: str = Field(default_factory=utcnow_iso)
updated_at: str = Field(default_factory=utcnow_iso)
message_count: int = 0
meta: SessionMeta = Field(default_factory=SessionMeta)
class SessionDetail(SessionOut):
messages: list["MessageOut"] = Field(default_factory=list)
class MessageCreate(BaseModel):
role: Literal["user", "assistant"]
text: str | None = None
kind: str = "qa"
status: str = "done"
blocks: list[Block] = Field(default_factory=list)
class MessagePatch(BaseModel):
"""流式检查点局部更新(PATCH /messages/{mid})。"""
status: str | None = None
blocks: list[Block] | None = None
raw: dict[str, Any] | None = None
class MessageOut(BaseModel):
model_config = ConfigDict(extra="ignore")
id: str
role: str
kind: str = "qa"
status: str = "done"
blocks: list[Block] = Field(default_factory=list)
raw: dict[str, Any] | None = None
text: str | None = None
created_at: str = Field(default_factory=utcnow_iso)
updated_at: str = Field(default_factory=utcnow_iso)
# ---------------------------------------------------------------------------
# §1 错误信封
# ---------------------------------------------------------------------------
class ErrorBody(BaseModel):
code: str
message: str
degraded: bool = False
class ErrorEnvelope(BaseModel):
ok: Literal[False] = False
error: ErrorBody