feat: add OCR document types and ports
OcrDocumentElement extends the layout element with text; OcrDocumentResult stays independent because the parent's defaulted call_stats forbids a required markdown field. OcrDocumentTransport is a separate Protocol so existing OcrTransport implementers keep passing isinstance.
This commit is contained in:
@@ -44,6 +44,8 @@ from polygateway.types import (
|
||||
Effort,
|
||||
EmbeddingResponse,
|
||||
LLMResponse,
|
||||
OcrDocumentElement,
|
||||
OcrDocumentResult,
|
||||
OcrLayoutElement,
|
||||
OcrLayoutResult,
|
||||
OcrTextResult,
|
||||
@@ -78,6 +80,8 @@ __all__ = [
|
||||
"OpenAIChatCompletion",
|
||||
"ModelPrice",
|
||||
"OcrClient",
|
||||
"OcrDocumentElement",
|
||||
"OcrDocumentResult",
|
||||
"OcrLayoutElement",
|
||||
"OcrLayoutResult",
|
||||
"OcrSettings",
|
||||
|
||||
@@ -16,6 +16,8 @@ from .types import (
|
||||
Effort,
|
||||
EmbeddingTransportResult,
|
||||
LLMResponse,
|
||||
OcrDocumentResult,
|
||||
OcrDocumentTransportResult,
|
||||
OcrLayoutResult,
|
||||
OcrLayoutTransportResult,
|
||||
OcrTextResult,
|
||||
@@ -102,6 +104,26 @@ class OcrLayoutPort(Protocol):
|
||||
async def parse_layout(self, image: bytes) -> OcrLayoutResult: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class OcrDocumentPort(Protocol):
|
||||
"""OCR 整份文档解析端口(#34;对应 MonkeyOCR POST /parse → GET ZIP,PDF 或图片)。"""
|
||||
|
||||
async def parse_document(self, document: bytes) -> OcrDocumentResult: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class OcrDocumentTransport(Protocol):
|
||||
"""整份文档解析的协议细节(#34 设计 §4.2);不含任何治理。
|
||||
|
||||
**独立于 `OcrTransport`**: 往那个 `runtime_checkable` Protocol 加成员,会让所有
|
||||
现有实现(测试替身、下游自带 fake)在 `isinstance` 上一夜之间不合格。
|
||||
"""
|
||||
|
||||
async def parse_document(
|
||||
self, *, document: bytes, source: SourceConfig, call_id: str
|
||||
) -> OcrDocumentTransportResult: ...
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class OcrTransport(Protocol):
|
||||
"""一次原始 OCR 调用的协议细节(M3 设计 §3.2);不含任何治理。
|
||||
|
||||
@@ -276,13 +276,19 @@ class ThinkingObservation(StrEnum):
|
||||
UNKNOWN = "unknown"
|
||||
|
||||
|
||||
CallOperation = Literal["chat", "embed", "recognize_text", "parse_layout"]
|
||||
"""遥测 `operation` 列的值域: **公开方法**四值,由调用点给定。
|
||||
CallOperation = Literal["chat", "embed", "recognize_text", "parse_layout", "parse_document"]
|
||||
"""遥测 `operation` 列的值域: **公开方法**五值,由调用点给定。
|
||||
|
||||
与 `PolyGatewayError.operation`(HTTP 子操作,如 `download_result`)是**两个语义**,
|
||||
不做自动转换;链路上任何位置都不得读 `exc.operation` 来填本列(设计 §5 I1/I2)。"""
|
||||
|
||||
CALL_OPERATIONS: tuple[CallOperation, ...] = ("chat", "embed", "recognize_text", "parse_layout")
|
||||
CALL_OPERATIONS: tuple[CallOperation, ...] = (
|
||||
"chat",
|
||||
"embed",
|
||||
"recognize_text",
|
||||
"parse_layout",
|
||||
"parse_document",
|
||||
)
|
||||
|
||||
EventKind = Literal["attempt", "cache_hit", "terminal_failure"]
|
||||
"""一行遥测描述的事件形态;旧行 NULL,不回填。
|
||||
@@ -1189,6 +1195,10 @@ class OcrLayoutElement:
|
||||
|
||||
type 为开放字符串(实测 table/image/text,不枚举锁死——零业务假设);
|
||||
bbox 为 OCR 原生页面坐标 (x1, y1, x2, y2),几何映射留业务侧(D9)。
|
||||
|
||||
注意: `OcrDocumentElement` 继承本类并追加**无默认值**的 `text`。dataclass 要求
|
||||
无默认字段不得排在带默认字段之后,故日后若给本类追加带默认值的字段,必须先评估
|
||||
子类——否则 `OcrDocumentElement` 会在导入期抛 `TypeError`(#34 设计 §4.1)。
|
||||
"""
|
||||
|
||||
type: str
|
||||
@@ -1196,6 +1206,22 @@ class OcrLayoutElement:
|
||||
page_index: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OcrDocumentElement(OcrLayoutElement):
|
||||
"""文档解析的逐块结果(#34 设计 §4.1): 版面元素 + 该块的文字内容。
|
||||
|
||||
`text` 的含义随 `type`,由 `parse_document` 的解析规则固定:
|
||||
`text`/`title` 为纯文本(行内公式保留 `$…$`);`interline_equation` 为裸 LaTeX
|
||||
(无 `$$` 定界);`table` 为 HTML 表体;`image` 为空串。服务将来若给出带 caption
|
||||
/footnote 的嵌套子块,它们作为独立元素紧随父块输出,`type` 取子块类型。
|
||||
|
||||
继承而非组合: 文档元素"是"版面元素加内容,按 bbox 裁剪的下游函数可同时处理两者。
|
||||
代价是 dataclass 的 `__eq__` 要求同类,`OcrDocumentElement != OcrLayoutElement`。
|
||||
"""
|
||||
|
||||
text: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OcrTextResult:
|
||||
"""一次治理 OCR 文本转录的统一响应(/ocr/text;M3 设计 §3.1)。
|
||||
@@ -1232,6 +1258,31 @@ class OcrLayoutResult:
|
||||
"""本次逻辑调用的统计快照(1.3.5);`None` = 未知。"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OcrDocumentResult:
|
||||
"""一次治理整份文档解析的统一响应(/parse → ZIP;#34 设计 §4.1)。
|
||||
|
||||
**独立定义,不继承 `OcrLayoutResult`**: 父类末尾的 `call_stats` 带默认值,
|
||||
子类追加无默认字段 `markdown` 会在导入期 `TypeError`。
|
||||
|
||||
elements 按服务阅读序排列,页内按页序;page_sizes 按 page_index 索引(零基)。
|
||||
`markdown` 是服务生成的整篇 Markdown 原文,**含悬空的 ``
|
||||
图片引用**——库不外传图片资源。服务端会丢弃脚注、页眉页脚与页码,这些内容
|
||||
既不在 elements 也不在 markdown 里,库无从补偿。
|
||||
"""
|
||||
|
||||
elements: list[OcrDocumentElement]
|
||||
page_sizes: list[tuple[float, float]]
|
||||
markdown: str
|
||||
source_name: str
|
||||
usage: Usage
|
||||
latency_ms: int
|
||||
call_id: str
|
||||
raw: dict[str, Any]
|
||||
call_stats: CallStats | None = None
|
||||
"""本次逻辑调用的统计快照(1.3.5);`None` = 未知。"""
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OcrTextTransportResult:
|
||||
"""transport 单次 /ocr/text 调用产物;治理字段由 OcrClient 补齐。"""
|
||||
@@ -1249,6 +1300,16 @@ class OcrLayoutTransportResult:
|
||||
raw: dict[str, Any]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OcrDocumentTransportResult:
|
||||
"""transport 单次整份文档两段调用的产物;治理字段由 OcrClient 补齐。"""
|
||||
|
||||
elements: list[OcrDocumentElement]
|
||||
page_sizes: list[tuple[float, float]]
|
||||
markdown: str
|
||||
raw: dict[str, Any]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class EmbeddingTransportResult:
|
||||
"""一次原始 embedding 调用的解析结果(M2 设计 §7.2;transport → client)。"""
|
||||
|
||||
Reference in New Issue
Block a user