|
19 | 19 | from __future__ import annotations |
20 | 20 |
|
21 | 21 | from collections.abc import Mapping |
22 | | -from typing import Any, cast |
| 22 | +from typing import Any |
23 | 23 |
|
24 | 24 | from openarmature.graph.events import ( |
25 | 25 | EmbeddingEvent, |
26 | 26 | EmbeddingFailedEvent, |
27 | 27 | RerankEvent, |
28 | 28 | RerankFailedEvent, |
29 | 29 | ) |
30 | | -from openarmature.llm.errors import LlmProviderError, ProviderInvalidResponse |
| 30 | +from openarmature.llm.errors import LlmProviderError |
31 | 31 | from openarmature.observability.correlation import ( |
32 | 32 | current_attempt_index, |
33 | 33 | current_branch_name, |
|
41 | 41 | from .response import EmbeddingResponse, RerankResponse |
42 | 42 |
|
43 | 43 |
|
44 | | -def normalize_base_url(base_url: str, *, guard_prefix: str) -> str: |
45 | | - """Strip a trailing slash from ``base_url`` and reject a doubled version prefix. |
46 | | -
|
47 | | - Returns the trailing-slash-stripped host root. Raises :class:`ValueError` |
48 | | - when the stripped value already ends in ``guard_prefix`` (e.g. ``"/v1"`` / |
49 | | - ``"/v2"``). |
50 | | - """ |
51 | | - # base_url is the host root; the provider appends the versioned routes |
52 | | - # itself (e.g. /v1/embeddings, /v2/rerank), so a base_url that already ends |
53 | | - # in the version prefix would produce a doubled /v1/v1 (or /v2/v2) path that |
54 | | - # 404s. Guard the footgun at construction; trailing slashes are stripped. |
55 | | - normalized = base_url.rstrip("/") |
56 | | - if normalized.endswith(guard_prefix): |
57 | | - raise ValueError( |
58 | | - f"base_url should be the host root; the provider appends the " |
59 | | - f"{guard_prefix} routes itself, so a trailing {guard_prefix} would " |
60 | | - f"produce a doubled {guard_prefix}{guard_prefix} path. Got {base_url!r}." |
61 | | - ) |
62 | | - return normalized |
63 | | - |
64 | | - |
65 | | -# retrieval-provider §8.1 client-side prefix rule, reused by §8.3: input_type |
66 | | -# "query" selects query_prefix, "document" selects document_prefix; any other |
67 | | -# value (including None / absent) selects no prefix -- the symmetric default. |
68 | | -def client_side_prefix( |
69 | | - input_type: str | None, |
70 | | - *, |
71 | | - query_prefix: str | None, |
72 | | - document_prefix: str | None, |
73 | | -) -> str | None: |
74 | | - """Return the client-side prefix for ``input_type``, or ``None``. |
75 | | -
|
76 | | - ``"query"`` maps to ``query_prefix``, ``"document"`` to ``document_prefix``; |
77 | | - any other value (including ``None``) maps to ``None`` (no prefix). |
78 | | - """ |
79 | | - if input_type == "query": |
80 | | - return query_prefix |
81 | | - if input_type == "document": |
82 | | - return document_prefix |
83 | | - return None |
84 | | - |
85 | | - |
86 | | -# Apply the §8.1 client-side prefix to an input list for input_type: prepend the |
87 | | -# resolved prefix to every string, or return the list unchanged when input_type |
88 | | -# resolves to no prefix. The single source of the "prefix the inputs" step |
89 | | -# shared by the OpenAI (§8.3) and TEI (§8.1) embed mappings -- keeps the copy / |
90 | | -# no-copy semantics from drifting between providers. |
91 | | -def apply_client_side_prefix( |
92 | | - input_strings: list[str], |
93 | | - input_type: str | None, |
94 | | - *, |
95 | | - query_prefix: str | None, |
96 | | - document_prefix: str | None, |
97 | | -) -> list[str]: |
98 | | - """Return ``input_strings`` prefixed per ``input_type``, or unchanged. |
99 | | -
|
100 | | - Resolves the prefix via :func:`client_side_prefix`; when it is ``None`` the |
101 | | - list is returned as-is (no defensive copy -- the result is only serialized |
102 | | - onto the wire), otherwise a new list with the prefix prepended to each |
103 | | - string. |
104 | | - """ |
105 | | - prefix = client_side_prefix(input_type, query_prefix=query_prefix, document_prefix=document_prefix) |
106 | | - if prefix is None: |
107 | | - return input_strings |
108 | | - return [prefix + s for s in input_strings] |
109 | | - |
110 | | - |
111 | | -# §6 (0097): the rerank document-echo shape rule, shared by every RerankProvider |
112 | | -# (Cohere / TEI / Jina). ScoredDocument.document carries the provider's string |
113 | | -# echo verbatim when present, null otherwise -- never fabricated from the input |
114 | | -# documents. Vendors echo it in several shapes: a bare string, a TextDoc object |
115 | | -# {"text": "..."} (Jina's return_documents form), a text-less object (an |
116 | | -# ImageDoc), or absent/null. Unwrap the string from a string or a TextDoc; a |
117 | | -# text-less object or absent/null yields null (an empty string is present -- |
118 | | -# surfaced as "", not folded to null). A NON-object scalar (number / array / |
119 | | -# bool -- outside the documented anyOf[string, object, null]) is wire corruption |
120 | | -# and raises provider_invalid_response (§7), NOT folded to null. The verbatim |
121 | | -# echo is preserved on RerankResponse.raw regardless. |
122 | | -def document_echo(value: Any) -> str | None: |
123 | | - """Extract the string document echo from a rerank result's documented shapes.""" |
124 | | - if value is None: |
125 | | - return None |
126 | | - if isinstance(value, str): |
127 | | - return value |
128 | | - if isinstance(value, dict): |
129 | | - text = cast("dict[str, Any]", value).get("text") |
130 | | - return text if isinstance(text, str) else None |
131 | | - raise ProviderInvalidResponse( |
132 | | - f"rerank document echo must be a string, object, or null (got {type(value).__name__})" |
133 | | - ) |
134 | | - |
135 | | - |
136 | 44 | def build_embedding_event( |
137 | 45 | response: EmbeddingResponse, |
138 | 46 | latency_ms: float, |
@@ -353,12 +261,8 @@ def build_rerank_failed_event( |
353 | 261 |
|
354 | 262 |
|
355 | 263 | __all__ = [ |
356 | | - "apply_client_side_prefix", |
357 | 264 | "build_embedding_event", |
358 | 265 | "build_embedding_failed_event", |
359 | 266 | "build_rerank_event", |
360 | 267 | "build_rerank_failed_event", |
361 | | - "client_side_prefix", |
362 | | - "document_echo", |
363 | | - "normalize_base_url", |
364 | 268 | ] |
0 commit comments