typevet 0.1.0.dev1__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.
- typevet/__init__.py +67 -0
- typevet/_version.py +43 -0
- typevet/adapters/__init__.py +16 -0
- typevet/adapters/diagnostics/__init__.py +59 -0
- typevet/adapters/diagnostics/fields.py +108 -0
- typevet/adapters/diagnostics/generation_events.py +75 -0
- typevet/adapters/diagnostics/http_events.py +91 -0
- typevet/adapters/diagnostics/logs.py +116 -0
- typevet/adapters/diagnostics/redaction.py +94 -0
- typevet/adapters/diagnostics/settings.py +77 -0
- typevet/adapters/inbound/__init__.py +65 -0
- typevet/adapters/inbound/api.py +61 -0
- typevet/adapters/inbound/backend_settings.py +608 -0
- typevet/adapters/inbound/cord_semantic_acceptance_cli.py +135 -0
- typevet/adapters/inbound/helpers.py +67 -0
- typevet/adapters/inbound/settings.py +154 -0
- typevet/adapters/outbound/__init__.py +62 -0
- typevet/adapters/outbound/async_fake.py +123 -0
- typevet/adapters/outbound/async_llama_cpp.py +168 -0
- typevet/adapters/outbound/chat_completion.py +148 -0
- typevet/adapters/outbound/fake.py +119 -0
- typevet/adapters/outbound/gemma/__init__.py +84 -0
- typevet/adapters/outbound/gemma/answer_binding.py +290 -0
- typevet/adapters/outbound/gemma/scoring_prefix.py +102 -0
- typevet/adapters/outbound/gemma/served_template.py +131 -0
- typevet/adapters/outbound/gemma_native_vision_factory.py +286 -0
- typevet/adapters/outbound/generation_finite.py +50 -0
- typevet/adapters/outbound/http_errors.py +38 -0
- typevet/adapters/outbound/judgment_scoring.py +471 -0
- typevet/adapters/outbound/llama_cpp.py +187 -0
- typevet/adapters/outbound/llama_cpp_http.py +103 -0
- typevet/adapters/outbound/llama_cpp_multimodal.py +145 -0
- typevet/adapters/outbound/llama_cpp_scoring.py +333 -0
- typevet/adapters/outbound/vllm_content.py +63 -0
- typevet/adapters/outbound/vllm_generation.py +163 -0
- typevet/adapters/outbound/vllm_generation_async.py +257 -0
- typevet/adapters/outbound/vllm_http.py +121 -0
- typevet/adapters/outbound/vllm_judgment_factory.py +210 -0
- typevet/adapters/outbound/vllm_scoring.py +372 -0
- typevet/domain/__init__.py +193 -0
- typevet/domain/candidate_scoring_request.py +134 -0
- typevet/domain/candidate_scoring_response.py +96 -0
- typevet/domain/candidate_scoring_validate.py +197 -0
- typevet/domain/decision_compile.py +368 -0
- typevet/domain/decision_execute.py +229 -0
- typevet/domain/decisions.py +129 -0
- typevet/domain/errors.py +235 -0
- typevet/domain/field_instructions.py +134 -0
- typevet/domain/judgment_answers.py +198 -0
- typevet/domain/judgment_normalize.py +243 -0
- typevet/domain/judgment_questions.py +187 -0
- typevet/domain/judgment_response.py +102 -0
- typevet/domain/media.py +101 -0
- typevet/domain/models.py +115 -0
- typevet/domain/question_schema.py +254 -0
- typevet/domain/scoring_stage.py +34 -0
- typevet/evaluation/__init__.py +19 -0
- typevet/evaluation/consumer_http_accounting.py +135 -0
- typevet/evaluation/cord_expense_call_accounting.py +108 -0
- typevet/evaluation/cord_expense_live_harness.py +75 -0
- typevet/evaluation/cord_expense_receipt_requirement.py +212 -0
- typevet/evaluation/cord_expense_smoke.py +328 -0
- typevet/evaluation/cord_semantic_acceptance.py +463 -0
- typevet/evaluation/cord_semantic_acceptance_report.py +90 -0
- typevet/evaluation/cord_semantic_metrics.py +57 -0
- typevet/evaluation/datasets/__init__.py +30 -0
- typevet/evaluation/datasets/banking77.py +270 -0
- typevet/evaluation/datasets/boolq.py +550 -0
- typevet/evaluation/datasets/boolq_download.py +132 -0
- typevet/evaluation/datasets/civil_comments.py +450 -0
- typevet/evaluation/datasets/clinc.py +125 -0
- typevet/evaluation/datasets/clinc_domains.json +172 -0
- typevet/evaluation/datasets/clinc_download.py +103 -0
- typevet/evaluation/datasets/clinc_plus_intent_names.json +153 -0
- typevet/evaluation/datasets/clinc_rows.py +306 -0
- typevet/evaluation/datasets/clinc_shard.py +156 -0
- typevet/evaluation/datasets/cord_expense.py +381 -0
- typevet/evaluation/datasets/difraud.py +256 -0
- typevet/evaluation/datasets/go_emotions.py +368 -0
- typevet/evaluation/datasets/go_emotions_download.py +154 -0
- typevet/evaluation/datasets/hyperpartisan.py +562 -0
- typevet/evaluation/datasets/partner_guard.py +170 -0
- typevet/evaluation/datasets/psai.py +415 -0
- typevet/evaluation/datasets/psai_download.py +167 -0
- typevet/evaluation/datasets/psai_evidence_pilot.py +570 -0
- typevet/evaluation/datasets/psai_schema.py +157 -0
- typevet/evaluation/datasets/psai_stream.py +166 -0
- typevet/evaluation/datasets/psai_vision.py +534 -0
- typevet/evaluation/datasets/psai_vision_controls.py +502 -0
- typevet/evaluation/datasets/pubmedqa.py +420 -0
- typevet/evaluation/experiment_identity.py +677 -0
- typevet/evaluation/psai_vision_consumer_accounting.py +190 -0
- typevet/evaluation/psai_vision_consumer_dispatch.py +216 -0
- typevet/evaluation/psai_vision_consumer_harness.py +342 -0
- typevet/evaluation/psai_vision_consumer_live.py +332 -0
- typevet/evaluation/psai_vision_consumer_live_identity.py +156 -0
- typevet/evaluation/psai_vision_consumer_live_receipt.py +132 -0
- typevet/evaluation/psai_vision_consumer_live_router.py +162 -0
- typevet/evaluation/psai_vision_consumer_offline.py +418 -0
- typevet/evaluation/psai_vision_consumer_outcomes.py +224 -0
- typevet/evaluation/psai_vision_consumer_protocol.py +69 -0
- typevet/evaluation/psai_vision_consumer_receipt.py +161 -0
- typevet/evaluation/psai_vision_consumer_receipt_structure.py +263 -0
- typevet/evaluation/psai_vision_probability_evidence.py +336 -0
- typevet/evaluation/runner/__init__.py +56 -0
- typevet/evaluation/runner/core.py +104 -0
- typevet/evaluation/runner/datasets.py +186 -0
- typevet/evaluation/runner/live_gate.py +149 -0
- typevet/evaluation/runner/report.py +116 -0
- typevet/evaluation/tpjep/__init__.py +81 -0
- typevet/evaluation/tpjep/loader.py +237 -0
- typevet/evaluation/tpjep/outcome.py +101 -0
- typevet/evaluation/tpjep/records.py +428 -0
- typevet/evaluation/tpjep/runner.py +325 -0
- typevet/ports/__init__.py +39 -0
- typevet/ports/async_generation.py +57 -0
- typevet/ports/framing.py +59 -0
- typevet/ports/generation.py +58 -0
- typevet/ports/judgment.py +71 -0
- typevet/ports/scoring.py +64 -0
- typevet/py.typed +0 -0
- typevet/runtime/__init__.py +50 -0
- typevet/runtime/categorical.py +196 -0
- typevet/runtime/judgment.py +17 -0
- typevet/runtime/llama_cpp_gemma_vision.py +28 -0
- typevet/runtime/scoring_prefix.py +14 -0
- typevet/runtime/vllm_judgment.py +28 -0
- typevet/testing/__init__.py +22 -0
- typevet/testing/fakes.py +152 -0
- typevet-0.1.0.dev1.dist-info/METADATA +125 -0
- typevet-0.1.0.dev1.dist-info/RECORD +133 -0
- typevet-0.1.0.dev1.dist-info/WHEEL +4 -0
- typevet-0.1.0.dev1.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""Gemma native-turn vision judgment factory over llama.cpp ([#174][i174], [#196][i196]).
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet.adapters.inbound.settings import load_llama_settings
|
|
6
|
+
from typevet.adapters.outbound.gemma_native_vision_factory import (
|
|
7
|
+
open_gemma_native_vision_judgment,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
settings = load_llama_settings()
|
|
11
|
+
with open_gemma_native_vision_judgment(settings=settings) as session:
|
|
12
|
+
session.port.judge("Hello", questions, session.model)
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The factory pins ``session.model`` on ``session.port``; other model ids fail
|
|
16
|
+
before tokenization. Optional ``tokenize_content`` and ``scoring_port_wrapper``
|
|
17
|
+
hooks support consumer dispatch ledgers without importing ``typevet.evaluation``.
|
|
18
|
+
|
|
19
|
+
See Also:
|
|
20
|
+
- [typevet.adapters.outbound.judgment_scoring][]: ``ScoringJudgmentAdapter``
|
|
21
|
+
- [typevet.runtime][]: public re-exports for library callers
|
|
22
|
+
- [docs.how-to.connect-gemma4-native-vision-judgment][]: Composition how-to
|
|
23
|
+
|
|
24
|
+
[i174]: https://github.com/Alberto-Codes/typevet/issues/174
|
|
25
|
+
[i196]: https://github.com/Alberto-Codes/typevet/issues/196
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from collections.abc import Callable, Iterator, Sequence
|
|
31
|
+
from contextlib import contextmanager
|
|
32
|
+
from dataclasses import dataclass
|
|
33
|
+
from typing import Any, Protocol
|
|
34
|
+
|
|
35
|
+
import httpx
|
|
36
|
+
|
|
37
|
+
from typevet.adapters.outbound.gemma import (
|
|
38
|
+
ServedTemplateClass,
|
|
39
|
+
classify_served_template,
|
|
40
|
+
)
|
|
41
|
+
from typevet.adapters.outbound.judgment_scoring import ScoringJudgmentAdapter
|
|
42
|
+
from typevet.adapters.outbound.llama_cpp_multimodal import (
|
|
43
|
+
MediaCapability,
|
|
44
|
+
fetch_media_capability,
|
|
45
|
+
)
|
|
46
|
+
from typevet.adapters.outbound.llama_cpp_scoring import (
|
|
47
|
+
DEFAULT_N_VOCAB,
|
|
48
|
+
LlamaCppCandidateScoringAdapter,
|
|
49
|
+
)
|
|
50
|
+
from typevet.ports.judgment import JudgmentPort
|
|
51
|
+
from typevet.ports.scoring import CandidateScoringPort
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class GemmaVisionSettings(Protocol):
|
|
55
|
+
"""Router connection options the factory reads from the composition root.
|
|
56
|
+
|
|
57
|
+
Examples:
|
|
58
|
+
```python
|
|
59
|
+
from typevet.adapters.inbound.settings import load_llama_settings
|
|
60
|
+
|
|
61
|
+
settings = load_llama_settings()
|
|
62
|
+
assert settings.base_url
|
|
63
|
+
```
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def base_url(self) -> str:
|
|
68
|
+
"""Router base URL."""
|
|
69
|
+
...
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def timeout(self) -> float:
|
|
73
|
+
"""HTTP timeout in seconds."""
|
|
74
|
+
...
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def multimodal_model(self) -> str:
|
|
78
|
+
"""Default multimodal model id."""
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
_SUPPORTED_NATIVE = frozenset(
|
|
83
|
+
{
|
|
84
|
+
ServedTemplateClass.NATIVE_GEMMA3_TURN,
|
|
85
|
+
ServedTemplateClass.NATIVE_GEMMA4_TURN,
|
|
86
|
+
}
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass(frozen=True, slots=True)
|
|
91
|
+
class GemmaNativeVisionSession:
|
|
92
|
+
"""Live router session with a configured ``JudgmentPort``.
|
|
93
|
+
|
|
94
|
+
Attributes:
|
|
95
|
+
port (JudgmentPort): Scoring-backed judgment for native media turns.
|
|
96
|
+
client (httpx.Client): Shared HTTP client for the router base URL.
|
|
97
|
+
model (str): Model id probed for template and vision capability.
|
|
98
|
+
served (ServedTemplateClass): Classified native template family.
|
|
99
|
+
capability (MediaCapability): Router media capability for ``model``.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
```python
|
|
103
|
+
from typevet.adapters.outbound.gemma_native_vision_factory import (
|
|
104
|
+
GemmaNativeVisionSession,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
assert GemmaNativeVisionSession.__dataclass_fields__
|
|
108
|
+
```
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
port: JudgmentPort
|
|
112
|
+
client: httpx.Client
|
|
113
|
+
model: str
|
|
114
|
+
served: ServedTemplateClass
|
|
115
|
+
capability: MediaCapability
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _classify_native_template(
|
|
119
|
+
client: httpx.Client,
|
|
120
|
+
model: str,
|
|
121
|
+
*,
|
|
122
|
+
require_gemma4: bool,
|
|
123
|
+
) -> ServedTemplateClass:
|
|
124
|
+
rendered = (
|
|
125
|
+
client.post(
|
|
126
|
+
"/apply-template",
|
|
127
|
+
json={
|
|
128
|
+
"model": model,
|
|
129
|
+
"messages": [{"role": "user", "content": "hello"}],
|
|
130
|
+
"add_generation_prompt": True,
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
.raise_for_status()
|
|
134
|
+
.json()["prompt"]
|
|
135
|
+
)
|
|
136
|
+
family = classify_served_template(rendered)
|
|
137
|
+
if require_gemma4 and family is not ServedTemplateClass.NATIVE_GEMMA4_TURN:
|
|
138
|
+
msg = f"expected NATIVE_GEMMA4_TURN, got {family.value}"
|
|
139
|
+
raise ValueError(msg)
|
|
140
|
+
if family not in _SUPPORTED_NATIVE:
|
|
141
|
+
msg = f"unsupported served template for native vision: {family.value}"
|
|
142
|
+
raise ValueError(msg)
|
|
143
|
+
return family
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _tokenize_factory(
|
|
147
|
+
client: httpx.Client,
|
|
148
|
+
model: str,
|
|
149
|
+
) -> Callable[[str], tuple[int, ...]]:
|
|
150
|
+
def tokenize(text: str) -> tuple[int, ...]:
|
|
151
|
+
"""Tokenize ``text`` through the router ``/tokenize`` endpoint.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Token id tuple from the router JSON body.
|
|
155
|
+
"""
|
|
156
|
+
body = client.post(
|
|
157
|
+
"/tokenize",
|
|
158
|
+
json={"model": model, "content": text, "add_special": False},
|
|
159
|
+
)
|
|
160
|
+
return tuple(body.raise_for_status().json()["tokens"])
|
|
161
|
+
|
|
162
|
+
return tokenize
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
@contextmanager
|
|
166
|
+
def open_gemma_native_vision_judgment(
|
|
167
|
+
*,
|
|
168
|
+
settings: GemmaVisionSettings,
|
|
169
|
+
model: str | None = None,
|
|
170
|
+
require_gemma4: bool = True,
|
|
171
|
+
n_vocab: int = DEFAULT_N_VOCAB,
|
|
172
|
+
http_client: httpx.Client | None = None,
|
|
173
|
+
tokenize_content: Callable[[str], Sequence[int]] | None = None,
|
|
174
|
+
scoring_port_wrapper: Callable[[CandidateScoringPort], CandidateScoringPort]
|
|
175
|
+
| None = None,
|
|
176
|
+
) -> Iterator[GemmaNativeVisionSession]:
|
|
177
|
+
"""Open a judgment port for Gemma native-turn vision on a llama.cpp router.
|
|
178
|
+
|
|
179
|
+
Probes ``/apply-template`` and media capability before the first ``judge``
|
|
180
|
+
call. Unsupported template families or text-only models raise ``ValueError``
|
|
181
|
+
before scoring dispatch.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
settings: Router connection options from the composition root.
|
|
185
|
+
model: Model id; defaults to ``settings.multimodal_model``.
|
|
186
|
+
require_gemma4: When true, require ``NATIVE_GEMMA4_TURN``.
|
|
187
|
+
n_vocab: ``n_probs`` budget for Gemma 4 class vocab scoring.
|
|
188
|
+
http_client: Optional pre-built client (for tests); not closed on exit.
|
|
189
|
+
tokenize_content: Optional tokenizer hook; defaults to router ``/tokenize``.
|
|
190
|
+
scoring_port_wrapper: Optional wrapper applied before ``JudgmentPort`` wiring.
|
|
191
|
+
|
|
192
|
+
Yields:
|
|
193
|
+
A session holding the configured ``JudgmentPort`` and probe metadata.
|
|
194
|
+
|
|
195
|
+
Raises:
|
|
196
|
+
ValueError: When vision is unavailable or the template is unsupported.
|
|
197
|
+
"""
|
|
198
|
+
model_id = model or settings.multimodal_model
|
|
199
|
+
base = settings.base_url.rstrip("/")
|
|
200
|
+
|
|
201
|
+
def _session(client: httpx.Client) -> Iterator[GemmaNativeVisionSession]:
|
|
202
|
+
capability = fetch_media_capability(client, f"{base}/", model_id)
|
|
203
|
+
if not capability.vision:
|
|
204
|
+
msg = "model reports text-only input modalities"
|
|
205
|
+
raise ValueError(msg)
|
|
206
|
+
served = _classify_native_template(
|
|
207
|
+
client, model_id, require_gemma4=require_gemma4
|
|
208
|
+
)
|
|
209
|
+
tokenize = tokenize_content or _tokenize_factory(client, model_id)
|
|
210
|
+
scoring = LlamaCppCandidateScoringAdapter(
|
|
211
|
+
base_url=base,
|
|
212
|
+
timeout=settings.timeout,
|
|
213
|
+
client=client,
|
|
214
|
+
n_vocab=n_vocab,
|
|
215
|
+
media_capabilities={model_id: capability},
|
|
216
|
+
)
|
|
217
|
+
scoring_port: CandidateScoringPort = scoring
|
|
218
|
+
if scoring_port_wrapper is not None:
|
|
219
|
+
scoring_port = scoring_port_wrapper(scoring)
|
|
220
|
+
port: JudgmentPort = ScoringJudgmentAdapter(
|
|
221
|
+
scoring_port,
|
|
222
|
+
tokenize_content=tokenize,
|
|
223
|
+
served_template=served,
|
|
224
|
+
pinned_model=model_id,
|
|
225
|
+
)
|
|
226
|
+
try:
|
|
227
|
+
yield GemmaNativeVisionSession(
|
|
228
|
+
port=port,
|
|
229
|
+
client=client,
|
|
230
|
+
model=model_id,
|
|
231
|
+
served=served,
|
|
232
|
+
capability=capability,
|
|
233
|
+
)
|
|
234
|
+
finally:
|
|
235
|
+
scoring.close()
|
|
236
|
+
|
|
237
|
+
if http_client is not None:
|
|
238
|
+
yield from _session(http_client)
|
|
239
|
+
return
|
|
240
|
+
with httpx.Client(base_url=base, timeout=settings.timeout) as owned:
|
|
241
|
+
yield from _session(owned)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def probe_gemma_native_vision_support(
|
|
245
|
+
*,
|
|
246
|
+
settings: GemmaVisionSettings,
|
|
247
|
+
model: str | None = None,
|
|
248
|
+
require_gemma4: bool = True,
|
|
249
|
+
http_client: httpx.Client | None = None,
|
|
250
|
+
) -> dict[str, Any]:
|
|
251
|
+
"""Return probe metadata without constructing a long-lived judgment port.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
settings: Router connection options.
|
|
255
|
+
model: Model id; defaults to ``settings.multimodal_model``.
|
|
256
|
+
require_gemma4: When true, require ``NATIVE_GEMMA4_TURN``.
|
|
257
|
+
http_client: Optional pre-built client (for tests).
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Mapping with ``ok``, ``model``, ``served``, and ``vision`` keys.
|
|
261
|
+
|
|
262
|
+
Raises:
|
|
263
|
+
ValueError: When the router rejects the probe (same rules as open).
|
|
264
|
+
"""
|
|
265
|
+
model_id = model or settings.multimodal_model
|
|
266
|
+
base = settings.base_url.rstrip("/")
|
|
267
|
+
|
|
268
|
+
def _probe(client: httpx.Client) -> dict[str, Any]:
|
|
269
|
+
capability = fetch_media_capability(client, f"{base}/", model_id)
|
|
270
|
+
if not capability.vision:
|
|
271
|
+
msg = "model reports text-only input modalities"
|
|
272
|
+
raise ValueError(msg)
|
|
273
|
+
served = _classify_native_template(
|
|
274
|
+
client, model_id, require_gemma4=require_gemma4
|
|
275
|
+
)
|
|
276
|
+
return {
|
|
277
|
+
"ok": True,
|
|
278
|
+
"model": model_id,
|
|
279
|
+
"served": served.value,
|
|
280
|
+
"vision": capability.vision,
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if http_client is not None:
|
|
284
|
+
return _probe(http_client)
|
|
285
|
+
with httpx.Client(base_url=base, timeout=settings.timeout) as client:
|
|
286
|
+
return _probe(client)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Reject non-finite floats in parsed structured generation values.
|
|
2
|
+
|
|
3
|
+
Examples:
|
|
4
|
+
```python
|
|
5
|
+
from typevet.adapters.outbound.generation_finite import (
|
|
6
|
+
reject_non_finite_numbers,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
reject_non_finite_numbers({"score": 1.0})
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
See Also:
|
|
13
|
+
- [typevet.domain.errors][]: SchemaValidationError
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import math
|
|
19
|
+
from typing import Any
|
|
20
|
+
|
|
21
|
+
from typevet.domain.errors import SchemaValidationError
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def reject_non_finite_numbers(value: Any) -> None:
|
|
25
|
+
"""Raise when a non-finite float appears anywhere in a JSON-like tree.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
value: Parsed model output (objects, arrays, numbers, strings, etc.).
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
SchemaValidationError: When a float is NaN or positive/negative infinity.
|
|
32
|
+
"""
|
|
33
|
+
_reject_non_finite(value, root=value)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _reject_non_finite(node: Any, *, root: Any) -> None:
|
|
37
|
+
if isinstance(node, float):
|
|
38
|
+
if not math.isfinite(node):
|
|
39
|
+
raise SchemaValidationError(
|
|
40
|
+
"structured output contains a non-finite number",
|
|
41
|
+
payload=root,
|
|
42
|
+
)
|
|
43
|
+
return
|
|
44
|
+
if isinstance(node, dict):
|
|
45
|
+
for item in node.values():
|
|
46
|
+
_reject_non_finite(item, root=root)
|
|
47
|
+
return
|
|
48
|
+
if isinstance(node, list):
|
|
49
|
+
for item in node:
|
|
50
|
+
_reject_non_finite(item, root=root)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""HTTP error constants that every backend's error mapping shares.
|
|
2
|
+
|
|
3
|
+
A status at or above ``HTTP_ERROR_STATUS`` is a backend failure. The error
|
|
4
|
+
keeps at most ``BODY_SNIPPET_MAX`` characters of the response body.
|
|
5
|
+
|
|
6
|
+
Attributes:
|
|
7
|
+
HTTP_ERROR_STATUS (int): Lowest status that the adapters treat as failure.
|
|
8
|
+
BODY_SNIPPET_MAX (int): Maximum characters of body text in an error.
|
|
9
|
+
|
|
10
|
+
Examples:
|
|
11
|
+
```python
|
|
12
|
+
from typevet.adapters.outbound.http_errors import body_snippet
|
|
13
|
+
|
|
14
|
+
body_snippet("x" * 600) # first 500 characters
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
See Also:
|
|
18
|
+
- [typevet.adapters.outbound.llama_cpp_http][]: llama.cpp error mapping
|
|
19
|
+
- [typevet.adapters.outbound.vllm_http][]: vLLM error mapping
|
|
20
|
+
- [typevet.domain.errors][]: BackendHttpError
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
HTTP_ERROR_STATUS = 400
|
|
26
|
+
BODY_SNIPPET_MAX = 500
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def body_snippet(text: str) -> str:
|
|
30
|
+
"""Return a bounded snippet of response text for error metadata.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
text: Full HTTP response body text.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
At most ``BODY_SNIPPET_MAX`` characters from ``text``.
|
|
37
|
+
"""
|
|
38
|
+
return text[:BODY_SNIPPET_MAX]
|