"""The shapes a model may send when it writes. These are tool-argument models, not domain models: they exist so the JSON Schema a client receives is precise enough that the model gets the shape right the first time. Each one is converted into the project's own payload (:class:`~app.schemas.paper.SentenceInput`, :class:`~app.schemas.template.TemplateFieldInput`) before it reaches the CRUD layer, so the validation rules stay in one place — a citation with a blank quote is refused here for the same reason the REST API refuses it. The union types are deliberate. A model writing sentences naturally produces either plain strings or objects, and refusing one of the two shapes would produce a retry that costs a whole round trip: "sentences": ["First sentence.", "Second sentence."] "sentences": [{"content": "Cited sentence.", "citations": [{"quote": "…"}]}] "sentences": [{"text": "One long block to be cut.", "split": "sentence"}] """ from __future__ import annotations from typing import Annotated, Literal from pydantic import BaseModel, Field #: How a block of text becomes sentences when the caller sent ``text`` instead #: of an explicit list. See :func:`app.mcp.support.split_text`. SplitMode = Literal["line", "sentence", "paragraph"] class CitationSpec(BaseModel): """One citation of one sentence.""" quote: Annotated[ str, Field(description="引用内容,必填;空引用会被拒绝"), ] reference_id: Annotated[ int | None, Field(description="参考文献库的编号,暂无库时可省略"), ] = None class SentenceSpec(BaseModel): """One sentence, optionally carrying citations.""" content: Annotated[str, Field(description="句子正文;空白会被折叠成一行")] citations: Annotated[ list[CitationSpec], Field(description="这一段引用了哪些文献"), ] = [] class TextSpec(BaseModel): """A block of text to be cut into sentences by the server.""" text: Annotated[str, Field(description="要写入的整段文本")] split: Annotated[ SplitMode, Field(description="切句方式:line 一行一句(默认)/ sentence 按句号切 / paragraph 整段一句"), ] = "line" citations: Annotated[ list[CitationSpec], Field(description="整段共用的引用"), ] = [] #: One entry of a paragraph's sentence list — a string, a sentence object, or a #: block to be cut. SentenceItem = str | SentenceSpec | TextSpec class ParagraphSpec(BaseModel): """One paragraph to write, addressed by position or by heading.""" position: Annotated[ int | None, Field(description="段落位置(模板字段的 sort);与 heading 二选一"), ] = None heading: Annotated[ str | None, Field(description="段落标题,如 “1. Introduction”;会按模板解析成位置"), ] = None sentences: Annotated[ list[SentenceItem] | None, Field(description="句子列表;字符串或对象都可以"), ] = None text: Annotated[ str | None, Field(description="整段文本,等价于 sentences 里放一个 {text, split} 对象"), ] = None split: Annotated[ SplitMode, Field(description="text 的切句方式;默认 line 一行一句"), ] = "line" citations: Annotated[ list[CitationSpec], Field(description="整段共用的引用"), ] = [] class FieldSpec(BaseModel): """One placement of a library field inside a template.""" field_id: Annotated[int, Field(description="字段库里的字段 id")] sort: Annotated[ int, Field(description="渲染顺序,升序整数;唯一决定段落顺序的就是它"), ] __all__ = [ "CitationSpec", "FieldSpec", "ParagraphSpec", "SentenceItem", "SentenceSpec", "SplitMode", "TextSpec", ]