langparse 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- langparse/__init__.py +55 -0
- langparse/autoparser.py +25 -0
- langparse/chunkers/__init__.py +12 -0
- langparse/chunkers/blocks.py +151 -0
- langparse/chunkers/profiles.py +53 -0
- langparse/chunkers/registry.py +38 -0
- langparse/chunkers/semantic.py +242 -0
- langparse/chunkers/text.py +96 -0
- langparse/chunkers/workbook.py +942 -0
- langparse/cli.py +329 -0
- langparse/config.py +169 -0
- langparse/core/__init__.py +0 -0
- langparse/core/chunker.py +16 -0
- langparse/core/engine.py +37 -0
- langparse/core/parser.py +35 -0
- langparse/core/rendering.py +49 -0
- langparse/engines/__init__.py +1 -0
- langparse/engines/pdf/__init__.py +1 -0
- langparse/engines/pdf/deepdoc/__init__.py +55 -0
- langparse/engines/pdf/deepdoc/layout_recognizer.py +235 -0
- langparse/engines/pdf/deepdoc/model_loader.py +101 -0
- langparse/engines/pdf/deepdoc/ocr.py +641 -0
- langparse/engines/pdf/deepdoc/operators.py +684 -0
- langparse/engines/pdf/deepdoc/pdf_parser.py +1894 -0
- langparse/engines/pdf/deepdoc/postprocess.py +339 -0
- langparse/engines/pdf/deepdoc/recognizer.py +418 -0
- langparse/engines/pdf/deepdoc/rendering.py +210 -0
- langparse/engines/pdf/deepdoc/table_structure_recognizer.py +559 -0
- langparse/engines/pdf/deepdoc/tokenizer.py +30 -0
- langparse/engines/pdf/deepdoc/utils.py +36 -0
- langparse/engines/pdf/deepdoc_engine.py +164 -0
- langparse/engines/pdf/mineru.py +259 -0
- langparse/engines/pdf/mineru_client.py +318 -0
- langparse/engines/pdf/mineru_service.py +225 -0
- langparse/engines/pdf/ocr.py +101 -0
- langparse/engines/pdf/other.py +20 -0
- langparse/engines/pdf/simple.py +134 -0
- langparse/engines/pdf/vision_llm.py +27 -0
- langparse/errors.py +70 -0
- langparse/logging.py +27 -0
- langparse/metrics.py +129 -0
- langparse/parsers/__init__.py +0 -0
- langparse/parsers/docx_parser.py +114 -0
- langparse/parsers/excel_parser.py +220 -0
- langparse/parsers/markdown_parser.py +34 -0
- langparse/parsers/pdf_parser.py +31 -0
- langparse/parsers/registry.py +48 -0
- langparse/parsers/sniff.py +72 -0
- langparse/progress.py +77 -0
- langparse/py.typed +0 -0
- langparse/services/__init__.py +11 -0
- langparse/services/batch_service.py +339 -0
- langparse/services/benchmark_service.py +202 -0
- langparse/services/fidelity.py +154 -0
- langparse/services/output_paths.py +86 -0
- langparse/services/parse_service.py +523 -0
- langparse/services/quality.py +65 -0
- langparse/services/workbook_ambiguity_benchmark.py +563 -0
- langparse/services/workbook_quality_benchmark.py +230 -0
- langparse/types.py +97 -0
- langparse/workbooks/__init__.py +103 -0
- langparse/workbooks/adapters.py +474 -0
- langparse/workbooks/assembly.py +993 -0
- langparse/workbooks/blocks.py +209 -0
- langparse/workbooks/bundle-v1.schema.json +71 -0
- langparse/workbooks/bundle.py +341 -0
- langparse/workbooks/classification.py +393 -0
- langparse/workbooks/continuation.py +577 -0
- langparse/workbooks/evaluation/__init__.py +45 -0
- langparse/workbooks/evaluation/evaluator.py +381 -0
- langparse/workbooks/evaluation/schema.py +419 -0
- langparse/workbooks/labels.py +14 -0
- langparse/workbooks/lineage.py +117 -0
- langparse/workbooks/modeling/__init__.py +52 -0
- langparse/workbooks/modeling/cache.py +20 -0
- langparse/workbooks/modeling/config.py +87 -0
- langparse/workbooks/modeling/contract.py +628 -0
- langparse/workbooks/modeling/disambiguation.py +800 -0
- langparse/workbooks/modeling/openai_adapter.py +192 -0
- langparse/workbooks/modeling/policy.py +79 -0
- langparse/workbooks/modeling/ports.py +44 -0
- langparse/workbooks/modeling/pricing.py +17 -0
- langparse/workbooks/modeling/types.py +251 -0
- langparse/workbooks/objects.py +229 -0
- langparse/workbooks/quality/__init__.py +23 -0
- langparse/workbooks/quality/bundle.py +53 -0
- langparse/workbooks/quality/evaluator.py +266 -0
- langparse/workbooks/quality/facts.py +142 -0
- langparse/workbooks/quality/schema.py +462 -0
- langparse/workbooks/reference_types.py +73 -0
- langparse/workbooks/references.py +178 -0
- langparse/workbooks/regions.py +932 -0
- langparse/workbooks/rendering.py +222 -0
- langparse/workbooks/tables.py +477 -0
- langparse/workbooks/types.py +257 -0
- langparse-0.1.0.dist-info/METADATA +790 -0
- langparse-0.1.0.dist-info/RECORD +101 -0
- langparse-0.1.0.dist-info/WHEEL +5 -0
- langparse-0.1.0.dist-info/entry_points.txt +2 -0
- langparse-0.1.0.dist-info/licenses/LICENSE +192 -0
- langparse-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .config import WorkbookModelConfig, resolve_workbook_model_config
|
|
4
|
+
from .ports import WorkbookModelResponseError, WorkbookStructureModelAdapter
|
|
5
|
+
from .types import ModelIdentity, ProviderReply, WorkbookModelRequest
|
|
6
|
+
|
|
7
|
+
_DECISION_COMMON_PROPERTIES = {
|
|
8
|
+
"case_id": {"type": "string"},
|
|
9
|
+
"status": {"type": "string"},
|
|
10
|
+
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
|
11
|
+
"reason_codes": {"type": "array", "items": {"type": "string"}},
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
_WORKBOOK_RESPONSE_SCHEMA = {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": False,
|
|
17
|
+
"required": ["schema_version", "request_checksum", "decisions"],
|
|
18
|
+
"properties": {
|
|
19
|
+
"schema_version": {"type": "integer", "const": 1},
|
|
20
|
+
"request_checksum": {"type": "string"},
|
|
21
|
+
"decisions": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"minItems": 1,
|
|
24
|
+
"maxItems": 1,
|
|
25
|
+
"items": {
|
|
26
|
+
"anyOf": [
|
|
27
|
+
{
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": False,
|
|
30
|
+
"required": [
|
|
31
|
+
"case_id",
|
|
32
|
+
"status",
|
|
33
|
+
"choice_id",
|
|
34
|
+
"confidence",
|
|
35
|
+
"reason_codes",
|
|
36
|
+
],
|
|
37
|
+
"properties": {
|
|
38
|
+
**_DECISION_COMMON_PROPERTIES,
|
|
39
|
+
"status": {"type": "string", "const": "selected"},
|
|
40
|
+
"choice_id": {"type": "string"},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"type": "object",
|
|
45
|
+
"additionalProperties": False,
|
|
46
|
+
"required": ["case_id", "status", "confidence", "reason_codes"],
|
|
47
|
+
"properties": {
|
|
48
|
+
**_DECISION_COMMON_PROPERTIES,
|
|
49
|
+
"status": {"type": "string", "const": "abstained"},
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class OpenAIWorkbookStructureAdapter(WorkbookStructureModelAdapter):
|
|
60
|
+
"""Adapter invoking OpenAI Chat Completions API for workbook structure disambiguation."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, config: WorkbookModelConfig) -> None:
|
|
63
|
+
self._config = config
|
|
64
|
+
self._identity = ModelIdentity(
|
|
65
|
+
provider="openai",
|
|
66
|
+
model=config.model,
|
|
67
|
+
revision=None,
|
|
68
|
+
)
|
|
69
|
+
self._client = None
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def identity(self) -> ModelIdentity:
|
|
73
|
+
return self._identity
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def config(self) -> WorkbookModelConfig:
|
|
77
|
+
return self._config
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_env(
|
|
81
|
+
cls,
|
|
82
|
+
*,
|
|
83
|
+
cli_model: str | None = None,
|
|
84
|
+
cli_api_key: str | None = None,
|
|
85
|
+
cli_base_url: str | None = None,
|
|
86
|
+
cli_timeout_seconds: float | None = None,
|
|
87
|
+
) -> OpenAIWorkbookStructureAdapter:
|
|
88
|
+
config = resolve_workbook_model_config(
|
|
89
|
+
cli_model=cli_model,
|
|
90
|
+
cli_api_key=cli_api_key,
|
|
91
|
+
cli_base_url=cli_base_url,
|
|
92
|
+
cli_timeout_seconds=cli_timeout_seconds,
|
|
93
|
+
from_env=True,
|
|
94
|
+
)
|
|
95
|
+
return cls(config)
|
|
96
|
+
|
|
97
|
+
def _get_client(self, timeout_seconds: float):
|
|
98
|
+
import openai
|
|
99
|
+
|
|
100
|
+
if self._client is None:
|
|
101
|
+
self._client = openai.OpenAI(
|
|
102
|
+
api_key=self._config.api_key,
|
|
103
|
+
base_url=self._config.base_url,
|
|
104
|
+
timeout=timeout_seconds,
|
|
105
|
+
)
|
|
106
|
+
return self._client
|
|
107
|
+
|
|
108
|
+
def complete(
|
|
109
|
+
self,
|
|
110
|
+
request: WorkbookModelRequest,
|
|
111
|
+
*,
|
|
112
|
+
timeout_seconds: float,
|
|
113
|
+
) -> ProviderReply:
|
|
114
|
+
try:
|
|
115
|
+
import openai
|
|
116
|
+
|
|
117
|
+
if openai is None:
|
|
118
|
+
raise ImportError("openai module is None")
|
|
119
|
+
except (ImportError, TypeError) as exc:
|
|
120
|
+
raise ImportError(
|
|
121
|
+
"The 'openai' package is required for model disambiguation. "
|
|
122
|
+
"Install it with 'pip install \"langparse[model]\"'."
|
|
123
|
+
) from exc
|
|
124
|
+
|
|
125
|
+
effective_timeout = min(timeout_seconds, self._config.timeout_seconds)
|
|
126
|
+
try:
|
|
127
|
+
client = self._get_client(effective_timeout)
|
|
128
|
+
response = client.chat.completions.create(
|
|
129
|
+
model=self._config.model,
|
|
130
|
+
messages=[
|
|
131
|
+
{
|
|
132
|
+
"role": "system",
|
|
133
|
+
"content": (
|
|
134
|
+
"You are an Excel layout disambiguation adjudicator. "
|
|
135
|
+
"Analyze the cell cues, structural feature summary, and registered choice reasons, "
|
|
136
|
+
"then select the registered choice that best explains the region. "
|
|
137
|
+
"Use 'unclassified' only when no registered typed choice is supported by the facts. "
|
|
138
|
+
"Return only one JSON object matching the supplied response contract. "
|
|
139
|
+
"If you choose a registered choice_id, status MUST be exactly 'selected'. "
|
|
140
|
+
"If you do not choose one, status MUST be exactly 'abstained' and choice_id "
|
|
141
|
+
"MUST be omitted. Never use 'resolved', 'chosen', or any other status. "
|
|
142
|
+
"Copy schema_version, request_checksum, and case_id exactly from the request. "
|
|
143
|
+
"choice_id MUST be copied exactly from that case's registered choices."
|
|
144
|
+
),
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"role": "user",
|
|
148
|
+
"content": request.body.decode("utf-8"),
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
response_format={
|
|
152
|
+
"type": "json_schema",
|
|
153
|
+
"json_schema": {
|
|
154
|
+
"name": "workbook_region_decision",
|
|
155
|
+
"strict": True,
|
|
156
|
+
"schema": _WORKBOOK_RESPONSE_SCHEMA,
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
seed=0,
|
|
160
|
+
temperature=0,
|
|
161
|
+
timeout=effective_timeout,
|
|
162
|
+
)
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
if isinstance(exc, ImportError):
|
|
165
|
+
raise
|
|
166
|
+
raise WorkbookModelResponseError(
|
|
167
|
+
f"OpenAI completion failed ({type(exc).__name__})"
|
|
168
|
+
) from exc
|
|
169
|
+
|
|
170
|
+
if not response.choices:
|
|
171
|
+
raise WorkbookModelResponseError("OpenAI returned an empty choices list")
|
|
172
|
+
|
|
173
|
+
message_content = response.choices[0].message.content or ""
|
|
174
|
+
reply_body = message_content.encode("utf-8")
|
|
175
|
+
|
|
176
|
+
usage_dict: dict[str, int] = {}
|
|
177
|
+
if getattr(response, "usage", None) is not None:
|
|
178
|
+
usage = response.usage
|
|
179
|
+
if getattr(usage, "prompt_tokens", None) is not None:
|
|
180
|
+
usage_dict["prompt_tokens"] = usage.prompt_tokens
|
|
181
|
+
if getattr(usage, "completion_tokens", None) is not None:
|
|
182
|
+
usage_dict["completion_tokens"] = usage.completion_tokens
|
|
183
|
+
if getattr(usage, "total_tokens", None) is not None:
|
|
184
|
+
usage_dict["total_tokens"] = usage.total_tokens
|
|
185
|
+
|
|
186
|
+
provider_request_id = getattr(response, "id", None)
|
|
187
|
+
|
|
188
|
+
return ProviderReply(
|
|
189
|
+
body=reply_body,
|
|
190
|
+
provider_request_id=provider_request_id,
|
|
191
|
+
usage=usage_dict,
|
|
192
|
+
)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from .ports import WorkbookModelConfigurationError, WorkbookStructureModelAdapter
|
|
7
|
+
from .types import WorkbookModelMode, WorkbookModelPolicy
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .disambiguation import WorkbookRegionDisambiguator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class WorkbookDisambiguation:
|
|
15
|
+
mode: WorkbookModelMode = WorkbookModelMode.OFF
|
|
16
|
+
adapter: WorkbookStructureModelAdapter | None = None
|
|
17
|
+
policy: WorkbookModelPolicy = WorkbookModelPolicy()
|
|
18
|
+
_runtime: WorkbookRegionDisambiguator | None = field(
|
|
19
|
+
init=False,
|
|
20
|
+
default=None,
|
|
21
|
+
repr=False,
|
|
22
|
+
compare=False,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def __post_init__(self) -> None:
|
|
26
|
+
mode = WorkbookModelMode(self.mode)
|
|
27
|
+
object.__setattr__(self, "mode", mode)
|
|
28
|
+
|
|
29
|
+
if self.policy is None:
|
|
30
|
+
object.__setattr__(self, "policy", WorkbookModelPolicy())
|
|
31
|
+
elif not isinstance(self.policy, WorkbookModelPolicy):
|
|
32
|
+
raise WorkbookModelConfigurationError("policy must be a WorkbookModelPolicy")
|
|
33
|
+
|
|
34
|
+
if mode is WorkbookModelMode.OFF and self.adapter is not None:
|
|
35
|
+
raise WorkbookModelConfigurationError(
|
|
36
|
+
"off workbook disambiguation cannot carry an adapter"
|
|
37
|
+
)
|
|
38
|
+
if mode is WorkbookModelMode.AUTO and self.adapter is None:
|
|
39
|
+
raise WorkbookModelConfigurationError(
|
|
40
|
+
"auto workbook disambiguation requires an adapter"
|
|
41
|
+
)
|
|
42
|
+
if mode is WorkbookModelMode.REQUIRED and self.adapter is None:
|
|
43
|
+
raise WorkbookModelConfigurationError(
|
|
44
|
+
"required workbook disambiguation requires an adapter"
|
|
45
|
+
)
|
|
46
|
+
if mode is not WorkbookModelMode.OFF:
|
|
47
|
+
from .disambiguation import WorkbookRegionDisambiguator
|
|
48
|
+
|
|
49
|
+
object.__setattr__(self, "_runtime", WorkbookRegionDisambiguator())
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def off(cls) -> WorkbookDisambiguation:
|
|
53
|
+
return cls(mode=WorkbookModelMode.OFF)
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def auto(
|
|
57
|
+
cls,
|
|
58
|
+
adapter: WorkbookStructureModelAdapter,
|
|
59
|
+
*,
|
|
60
|
+
policy: WorkbookModelPolicy | None = None,
|
|
61
|
+
) -> WorkbookDisambiguation:
|
|
62
|
+
return cls(
|
|
63
|
+
mode=WorkbookModelMode.AUTO,
|
|
64
|
+
adapter=adapter,
|
|
65
|
+
policy=WorkbookModelPolicy() if policy is None else policy,
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
@classmethod
|
|
69
|
+
def required(
|
|
70
|
+
cls,
|
|
71
|
+
adapter: WorkbookStructureModelAdapter,
|
|
72
|
+
*,
|
|
73
|
+
policy: WorkbookModelPolicy | None = None,
|
|
74
|
+
) -> WorkbookDisambiguation:
|
|
75
|
+
return cls(
|
|
76
|
+
mode=WorkbookModelMode.REQUIRED,
|
|
77
|
+
adapter=adapter,
|
|
78
|
+
policy=WorkbookModelPolicy() if policy is None else policy,
|
|
79
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Protocol
|
|
4
|
+
|
|
5
|
+
from langparse.types import ParseDiagnostics
|
|
6
|
+
|
|
7
|
+
from .types import ModelIdentity, ProviderReply, WorkbookModelRequest
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WorkbookModelError(Exception):
|
|
11
|
+
"""Base error for workbook model disambiguation."""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WorkbookModelConfigurationError(WorkbookModelError, ValueError):
|
|
15
|
+
"""Raised when model disambiguation is configured inconsistently."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class InvalidRegionAmbiguityCaseError(WorkbookModelError, ValueError):
|
|
19
|
+
"""Raised when an ambiguity case does not satisfy its contract."""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class WorkbookModelResponseError(WorkbookModelError, ValueError):
|
|
23
|
+
"""Raised when a provider response cannot be accepted."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RequiredWorkbookDisambiguationError(WorkbookModelError):
|
|
27
|
+
"""Raised when required model disambiguation leaves cases unresolved."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, case_ids: tuple[str, ...], diagnostics: ParseDiagnostics):
|
|
30
|
+
super().__init__(f"Workbook model disambiguation unresolved: {', '.join(case_ids)}")
|
|
31
|
+
self.case_ids = case_ids
|
|
32
|
+
self.diagnostics = diagnostics
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class WorkbookStructureModelAdapter(Protocol):
|
|
36
|
+
@property
|
|
37
|
+
def identity(self) -> ModelIdentity: ...
|
|
38
|
+
|
|
39
|
+
def complete(
|
|
40
|
+
self,
|
|
41
|
+
request: WorkbookModelRequest,
|
|
42
|
+
*,
|
|
43
|
+
timeout_seconds: float,
|
|
44
|
+
) -> ProviderReply: ...
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def calculate_cost_usd(
|
|
5
|
+
prompt_tokens: int,
|
|
6
|
+
completion_tokens: int,
|
|
7
|
+
*,
|
|
8
|
+
input_cost_usd_per_million: float,
|
|
9
|
+
output_cost_usd_per_million: float,
|
|
10
|
+
) -> float:
|
|
11
|
+
"""Calculate observed cost using deployment-supplied, versioned policy rates."""
|
|
12
|
+
if prompt_tokens <= 0 and completion_tokens <= 0:
|
|
13
|
+
return 0.0
|
|
14
|
+
|
|
15
|
+
prompt_cost = (prompt_tokens / 1_000_000.0) * input_cost_usd_per_million
|
|
16
|
+
completion_cost = (completion_tokens / 1_000_000.0) * output_cost_usd_per_million
|
|
17
|
+
return prompt_cost + completion_cost
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from collections.abc import Mapping
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from numbers import Real
|
|
8
|
+
from types import MappingProxyType
|
|
9
|
+
from typing import Literal, TypeAlias
|
|
10
|
+
|
|
11
|
+
REGION_SCHEMA_VERSION = 1
|
|
12
|
+
REGION_PROMPT_VERSION = "region-choice-v2"
|
|
13
|
+
REGION_RULE_VERSION = "region-rules-v1"
|
|
14
|
+
REGION_VALIDATOR_VERSION = "region-validator-v1"
|
|
15
|
+
REGION_PRIVACY_VERSION = "region-privacy-v1"
|
|
16
|
+
|
|
17
|
+
RegionFeatureScalar: TypeAlias = str | int | float | bool | None
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _is_region_feature_scalar(value: object) -> bool:
|
|
21
|
+
return value is None or type(value) in (str, int, float, bool)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class WorkbookModelMode(str, Enum):
|
|
25
|
+
OFF = "off"
|
|
26
|
+
AUTO = "auto"
|
|
27
|
+
REQUIRED = "required"
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class WorkbookModelPolicy:
|
|
32
|
+
timeout_seconds: float = 20.0
|
|
33
|
+
workbook_timeout_seconds: float = 60.0
|
|
34
|
+
max_attempts: int = 2
|
|
35
|
+
max_cases: int = 8
|
|
36
|
+
max_calls: int = 4
|
|
37
|
+
max_cells_per_case: int = 500
|
|
38
|
+
max_request_bytes: int = 256_000
|
|
39
|
+
max_response_bytes: int = 128_000
|
|
40
|
+
kill_switch: bool = False
|
|
41
|
+
max_tokens_per_workbook: int | None = None
|
|
42
|
+
max_cost_usd_per_workbook: float | None = None
|
|
43
|
+
input_cost_usd_per_million: float | None = None
|
|
44
|
+
output_cost_usd_per_million: float | None = None
|
|
45
|
+
cost_pricing_version: str | None = None
|
|
46
|
+
|
|
47
|
+
def __post_init__(self) -> None:
|
|
48
|
+
if not isinstance(self.kill_switch, bool):
|
|
49
|
+
raise ValueError("kill_switch must be a bool")
|
|
50
|
+
for name in ("timeout_seconds", "workbook_timeout_seconds"):
|
|
51
|
+
value = getattr(self, name)
|
|
52
|
+
if (
|
|
53
|
+
isinstance(value, bool)
|
|
54
|
+
or not isinstance(value, Real)
|
|
55
|
+
or not math.isfinite(value)
|
|
56
|
+
or value <= 0
|
|
57
|
+
):
|
|
58
|
+
raise ValueError(f"{name} must be a finite positive real")
|
|
59
|
+
for name in (
|
|
60
|
+
"max_attempts",
|
|
61
|
+
"max_cases",
|
|
62
|
+
"max_calls",
|
|
63
|
+
"max_cells_per_case",
|
|
64
|
+
"max_request_bytes",
|
|
65
|
+
"max_response_bytes",
|
|
66
|
+
):
|
|
67
|
+
value = getattr(self, name)
|
|
68
|
+
if type(value) is not int or value <= 0:
|
|
69
|
+
raise ValueError(f"{name} must be an exact positive integer")
|
|
70
|
+
if self.max_tokens_per_workbook is not None:
|
|
71
|
+
if type(self.max_tokens_per_workbook) is not int or self.max_tokens_per_workbook <= 0:
|
|
72
|
+
raise ValueError("max_tokens_per_workbook must be an exact positive integer")
|
|
73
|
+
if self.max_cost_usd_per_workbook is not None:
|
|
74
|
+
if (
|
|
75
|
+
isinstance(self.max_cost_usd_per_workbook, bool)
|
|
76
|
+
or not isinstance(self.max_cost_usd_per_workbook, Real)
|
|
77
|
+
or not math.isfinite(self.max_cost_usd_per_workbook)
|
|
78
|
+
or self.max_cost_usd_per_workbook <= 0
|
|
79
|
+
):
|
|
80
|
+
raise ValueError("max_cost_usd_per_workbook must be a finite positive real")
|
|
81
|
+
for name in (
|
|
82
|
+
"input_cost_usd_per_million",
|
|
83
|
+
"output_cost_usd_per_million",
|
|
84
|
+
):
|
|
85
|
+
value = getattr(self, name)
|
|
86
|
+
if (
|
|
87
|
+
isinstance(value, bool)
|
|
88
|
+
or not isinstance(value, Real)
|
|
89
|
+
or not math.isfinite(value)
|
|
90
|
+
or value < 0
|
|
91
|
+
):
|
|
92
|
+
raise ValueError(
|
|
93
|
+
"cost pricing requires finite non-negative input and output rates"
|
|
94
|
+
)
|
|
95
|
+
version = self.cost_pricing_version
|
|
96
|
+
if (
|
|
97
|
+
not isinstance(version, str)
|
|
98
|
+
or not 0 < len(version.strip()) <= 64
|
|
99
|
+
or not version.isascii()
|
|
100
|
+
or not all(character.isalnum() or character in "._-" for character in version)
|
|
101
|
+
):
|
|
102
|
+
raise ValueError("cost_pricing_version must be a non-empty safe ASCII identifier")
|
|
103
|
+
object.__setattr__(self, "cost_pricing_version", version.strip())
|
|
104
|
+
elif any(
|
|
105
|
+
value is not None
|
|
106
|
+
for value in (
|
|
107
|
+
self.input_cost_usd_per_million,
|
|
108
|
+
self.output_cost_usd_per_million,
|
|
109
|
+
self.cost_pricing_version,
|
|
110
|
+
)
|
|
111
|
+
):
|
|
112
|
+
raise ValueError("cost pricing fields require max_cost_usd_per_workbook")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@dataclass(frozen=True)
|
|
116
|
+
class ModelIdentity:
|
|
117
|
+
provider: str
|
|
118
|
+
model: str
|
|
119
|
+
revision: str | None = None
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@dataclass(frozen=True)
|
|
123
|
+
class RegionChoice:
|
|
124
|
+
choice_id: str
|
|
125
|
+
kind: Literal["logical_table", "form", "matrix", "text", "unclassified"]
|
|
126
|
+
local_score: float
|
|
127
|
+
reason_codes: tuple[str, ...] = ()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@dataclass(frozen=True)
|
|
131
|
+
class RegionCellCue:
|
|
132
|
+
coordinate: str
|
|
133
|
+
display_text: str
|
|
134
|
+
value_type: str
|
|
135
|
+
style_fingerprint: str
|
|
136
|
+
merge_anchor: str | None
|
|
137
|
+
rowspan: int
|
|
138
|
+
colspan: int
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@dataclass(frozen=True)
|
|
142
|
+
class RegionAmbiguityCase:
|
|
143
|
+
case_id: str
|
|
144
|
+
sheet_name: str
|
|
145
|
+
sheet_visibility: str
|
|
146
|
+
source_range: str
|
|
147
|
+
fact_digest: str
|
|
148
|
+
cells: tuple[RegionCellCue, ...]
|
|
149
|
+
feature_summary: tuple[tuple[str, RegionFeatureScalar], ...]
|
|
150
|
+
choices: tuple[RegionChoice, ...]
|
|
151
|
+
fallback_choice_id: str
|
|
152
|
+
ambiguity_codes: tuple[str, ...]
|
|
153
|
+
|
|
154
|
+
def __post_init__(self) -> None:
|
|
155
|
+
if not isinstance(self.feature_summary, tuple):
|
|
156
|
+
raise TypeError("feature_summary must be a tuple of (str, scalar) entries")
|
|
157
|
+
for entry in self.feature_summary:
|
|
158
|
+
if not isinstance(entry, tuple) or len(entry) != 2:
|
|
159
|
+
raise TypeError("feature_summary must contain (str, scalar) tuples")
|
|
160
|
+
key, value = entry
|
|
161
|
+
if type(key) is not str or not _is_region_feature_scalar(value):
|
|
162
|
+
raise TypeError("feature_summary entries must contain a string and scalar value")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@dataclass(frozen=True)
|
|
166
|
+
class WorkbookModelRequest:
|
|
167
|
+
schema_version: int
|
|
168
|
+
prompt_version: str
|
|
169
|
+
privacy_version: str
|
|
170
|
+
request_checksum: str
|
|
171
|
+
body: bytes
|
|
172
|
+
case_ids: tuple[str, ...]
|
|
173
|
+
choice_ids_by_case: tuple[tuple[str, tuple[str, ...]], ...]
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
@dataclass(frozen=True)
|
|
177
|
+
class ProviderReply:
|
|
178
|
+
body: bytes
|
|
179
|
+
provider_request_id: str | None
|
|
180
|
+
usage: Mapping[str, int] = field(default_factory=dict)
|
|
181
|
+
|
|
182
|
+
def __post_init__(self) -> None:
|
|
183
|
+
if not isinstance(self.body, (bytes, bytearray, memoryview)):
|
|
184
|
+
raise TypeError("body must be bytes-like")
|
|
185
|
+
object.__setattr__(self, "body", bytes(self.body))
|
|
186
|
+
if self.provider_request_id is not None and type(self.provider_request_id) is not str:
|
|
187
|
+
raise TypeError("provider_request_id must be a string or None")
|
|
188
|
+
if not isinstance(self.usage, Mapping):
|
|
189
|
+
raise TypeError("usage must be a mapping of string keys to integer values")
|
|
190
|
+
copied_usage: dict[str, int] = {}
|
|
191
|
+
for key, value in self.usage.items():
|
|
192
|
+
if not isinstance(key, str):
|
|
193
|
+
raise TypeError("usage keys must be strings")
|
|
194
|
+
if not isinstance(value, int) or isinstance(value, bool):
|
|
195
|
+
raise TypeError("usage values must be non-bool integers")
|
|
196
|
+
if value < 0:
|
|
197
|
+
raise TypeError("usage values must be non-negative integers")
|
|
198
|
+
copied_usage[key] = value
|
|
199
|
+
object.__setattr__(self, "usage", MappingProxyType(copied_usage))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@dataclass(frozen=True)
|
|
203
|
+
class RegionModelDecision:
|
|
204
|
+
case_id: str
|
|
205
|
+
status: Literal["selected", "abstained"]
|
|
206
|
+
choice_id: str | None
|
|
207
|
+
reported_confidence: float | None
|
|
208
|
+
reason_codes: tuple[str, ...] = ()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
@dataclass(frozen=True)
|
|
212
|
+
class ModelCallAudit:
|
|
213
|
+
case_id: str
|
|
214
|
+
source_range: str
|
|
215
|
+
mode: str
|
|
216
|
+
schema_version: int
|
|
217
|
+
prompt_version: str
|
|
218
|
+
rule_version: str
|
|
219
|
+
validator_version: str
|
|
220
|
+
privacy_version: str
|
|
221
|
+
rule_confidence: float
|
|
222
|
+
provider: str | None
|
|
223
|
+
model: str | None
|
|
224
|
+
model_revision: str | None
|
|
225
|
+
request_checksum: str | None
|
|
226
|
+
response_checksum: str | None
|
|
227
|
+
cache_status: str
|
|
228
|
+
attempts: int
|
|
229
|
+
elapsed_ms: int
|
|
230
|
+
request_bytes: int
|
|
231
|
+
response_bytes: int
|
|
232
|
+
outcome: str
|
|
233
|
+
selected_choice_id: str | None
|
|
234
|
+
reported_confidence: float | None
|
|
235
|
+
validation_codes: tuple[str, ...] = ()
|
|
236
|
+
reason_codes: tuple[str, ...] = ()
|
|
237
|
+
error_type: str | None = None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True)
|
|
241
|
+
class RegionResolution:
|
|
242
|
+
case_id: str
|
|
243
|
+
choice_id: str
|
|
244
|
+
status: Literal["local_fallback", "model_selected", "cache_selected"]
|
|
245
|
+
audit: ModelCallAudit | None = None
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
@dataclass(frozen=True)
|
|
249
|
+
class RegionResolutionBatch:
|
|
250
|
+
resolutions: tuple[RegionResolution, ...]
|
|
251
|
+
unresolved_case_ids: tuple[str, ...] = ()
|