embed-client-dev 4.0.78__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.
- embed_client/__init__.py +36 -0
- embed_client/__main__.py +15 -0
- embed_client/_version.py +3 -0
- embed_client/adapter_config_factory.py +213 -0
- embed_client/adapter_config_normalizer.py +135 -0
- embed_client/adapter_generator_bridge.py +166 -0
- embed_client/auth.py +189 -0
- embed_client/cli.py +483 -0
- embed_client/cli_core.py +427 -0
- embed_client/client_factory.py +515 -0
- embed_client/client_factory_examples.py +375 -0
- embed_client/client_factory_helpers.py +59 -0
- embed_client/config.py +408 -0
- embed_client/config_defaults.py +77 -0
- embed_client/config_examples.py +228 -0
- embed_client/config_generator.py +652 -0
- embed_client/config_generator_cli.py +151 -0
- embed_client/config_presets.py +245 -0
- embed_client/config_validator.py +246 -0
- embed_client/config_validator_cli.py +141 -0
- embed_client/constants.py +52 -0
- embed_client/demo_cli.py +78 -0
- embed_client/demo_security_cli.py +206 -0
- embed_client/embed_file_output_validate.py +348 -0
- embed_client/embed_file_status_store.py +80 -0
- embed_client/embedding_client.py +1929 -0
- embed_client/embedding_client_helpers.py +105 -0
- embed_client/example_async_usage.py +357 -0
- embed_client/example_async_usage_demo.py +282 -0
- embed_client/example_async_usage_ru.py +340 -0
- embed_client/example_async_usage_ru_demo.py +313 -0
- embed_client/exceptions.py +122 -0
- embed_client/identifier_types.py +56 -0
- embed_client/json_embed_file_preflight.py +128 -0
- embed_client/local_json_embed_file.py +73 -0
- embed_client/md_embed_file_prep.py +197 -0
- embed_client/response_normalizer.py +160 -0
- embed_client/response_parsers.py +219 -0
- embed_client/security_cli.py +200 -0
- embed_client/security_cli_core.py +227 -0
- embed_client/ssl_examples.py +278 -0
- embed_client/ssl_manager.py +141 -0
- embed_client_dev-4.0.78.dist-info/METADATA +155 -0
- embed_client_dev-4.0.78.dist-info/RECORD +48 -0
- embed_client_dev-4.0.78.dist-info/WHEEL +5 -0
- embed_client_dev-4.0.78.dist-info/entry_points.txt +4 -0
- embed_client_dev-4.0.78.dist-info/licenses/LICENSE +21 -0
- embed_client_dev-4.0.78.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Prepare Markdown (or plain text) files as UTF-8 ``json_array`` inputs for ``embed_file``.
|
|
3
|
+
|
|
4
|
+
Reads a ``.md`` file, optionally strips YAML front matter between the first pair of
|
|
5
|
+
``---`` lines, splits the body on blank lines (``\\n\\n``) into paragraph blocks,
|
|
6
|
+
enforces a per-block UTF-8 byte cap via
|
|
7
|
+
:func:`~embed_client.json_embed_file_preflight.resolve_max_json_block_bytes`,
|
|
8
|
+
and writes a top-level JSON array of strings suitable for
|
|
9
|
+
:func:`~embed_client.local_json_embed_file.embed_file_from_local_json_file`.
|
|
10
|
+
|
|
11
|
+
Author: Vasiliy Zdanovskiy
|
|
12
|
+
email: vasilyvz@gmail.com
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
from typing import Any, List, Optional
|
|
20
|
+
|
|
21
|
+
from embed_client.exceptions import EmbedClientError
|
|
22
|
+
from embed_client.json_embed_file_preflight import (
|
|
23
|
+
resolve_max_json_block_bytes,
|
|
24
|
+
validate_local_json_embed_file,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def strip_optional_yaml_front_matter(text: str) -> str:
|
|
29
|
+
"""
|
|
30
|
+
If *text* begins with a line ``---`` (after optional BOM, already decoded),
|
|
31
|
+
return the substring after the next line that is exactly ``---`` (stripped).
|
|
32
|
+
Otherwise return *text* unchanged.
|
|
33
|
+
|
|
34
|
+
If an opening ``---`` is present but no closing ``---`` line is found, returns
|
|
35
|
+
*text* unchanged (no strip).
|
|
36
|
+
"""
|
|
37
|
+
if not text:
|
|
38
|
+
return text
|
|
39
|
+
lines = text.splitlines(keepends=True)
|
|
40
|
+
if not lines:
|
|
41
|
+
return text
|
|
42
|
+
if lines[0].strip() != "---":
|
|
43
|
+
return text
|
|
44
|
+
for i in range(1, len(lines)):
|
|
45
|
+
if lines[i].strip() == "---":
|
|
46
|
+
return "".join(lines[i + 1 :])
|
|
47
|
+
return text
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def split_body_into_text_blocks(body: str, max_block_bytes: int) -> List[str]:
|
|
51
|
+
"""
|
|
52
|
+
Split *body* on double newlines (paragraph boundaries) only.
|
|
53
|
+
|
|
54
|
+
Line endings are normalized to ``\\n`` (``\\r\\n`` and lone ``\\r`` → ``\\n``)
|
|
55
|
+
before splitting on the two-character separator ``\\n\\n``. Each segment is
|
|
56
|
+
stripped of leading/trailing whitespace, and segments empty after strip are
|
|
57
|
+
dropped (runs of blank lines collapse to one boundary).
|
|
58
|
+
|
|
59
|
+
Each non-empty block must encode to at most *max_block_bytes* UTF-8 bytes.
|
|
60
|
+
If any block exceeds the cap, raises :class:`~embed_client.exceptions.EmbedClientError`
|
|
61
|
+
(no byte-level re-chunking).
|
|
62
|
+
"""
|
|
63
|
+
if max_block_bytes < 1:
|
|
64
|
+
raise EmbedClientError("max_block_bytes must be >= 1")
|
|
65
|
+
normalized = body.replace("\r\n", "\n").replace("\r", "\n")
|
|
66
|
+
paragraphs = normalized.split("\n\n")
|
|
67
|
+
out: List[str] = []
|
|
68
|
+
for raw in paragraphs:
|
|
69
|
+
block = raw.strip()
|
|
70
|
+
if not block:
|
|
71
|
+
continue
|
|
72
|
+
encoded = block.encode("utf-8")
|
|
73
|
+
if len(encoded) > max_block_bytes:
|
|
74
|
+
raise EmbedClientError(
|
|
75
|
+
"paragraph exceeds max_block_bytes after split on blank lines "
|
|
76
|
+
f"({len(encoded)} bytes > {max_block_bytes}); shorten the paragraph "
|
|
77
|
+
"or increase --max-block-bytes / EMBED_CLIENT_MAX_JSON_BLOCK_BYTES"
|
|
78
|
+
)
|
|
79
|
+
out.append(block)
|
|
80
|
+
return out
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def build_embed_file_json_array_from_markdown_text(
|
|
84
|
+
text: str,
|
|
85
|
+
*,
|
|
86
|
+
max_block_bytes: Optional[int] = None,
|
|
87
|
+
strip_front_matter: bool = True,
|
|
88
|
+
max_chunks: Optional[int] = None,
|
|
89
|
+
) -> List[str]:
|
|
90
|
+
"""
|
|
91
|
+
Return a non-empty list of strings (``json_array`` items) from Markdown/plain text.
|
|
92
|
+
|
|
93
|
+
Raises:
|
|
94
|
+
EmbedClientError: Empty output, or size/schema issues after splitting.
|
|
95
|
+
"""
|
|
96
|
+
max_b = resolve_max_json_block_bytes(max_block_bytes)
|
|
97
|
+
body = strip_optional_yaml_front_matter(text) if strip_front_matter else text
|
|
98
|
+
blocks = split_body_into_text_blocks(body, max_b)
|
|
99
|
+
if max_chunks is not None:
|
|
100
|
+
if max_chunks < 1:
|
|
101
|
+
raise EmbedClientError("max_chunks must be >= 1 when set")
|
|
102
|
+
blocks = blocks[:max_chunks]
|
|
103
|
+
if not blocks:
|
|
104
|
+
raise EmbedClientError(
|
|
105
|
+
"no non-empty text blocks after preparation (empty file or body?)"
|
|
106
|
+
)
|
|
107
|
+
return list(blocks)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def prepare_markdown_path_to_json_file(
|
|
111
|
+
md_path: str,
|
|
112
|
+
output_path: str,
|
|
113
|
+
*,
|
|
114
|
+
max_block_bytes: Optional[int] = None,
|
|
115
|
+
strip_front_matter: bool = True,
|
|
116
|
+
max_chunks: Optional[int] = None,
|
|
117
|
+
) -> str:
|
|
118
|
+
"""
|
|
119
|
+
Read UTF-8 (with optional BOM) from *md_path*, build ``json_array``, write *output_path*,
|
|
120
|
+
then run :func:`~embed_client.json_embed_file_preflight.validate_local_json_embed_file`
|
|
121
|
+
on the result.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Absolute path to *output_path*.
|
|
125
|
+
"""
|
|
126
|
+
if not md_path or not md_path.strip():
|
|
127
|
+
raise EmbedClientError("markdown input path is empty")
|
|
128
|
+
ap_in = os.path.abspath(md_path)
|
|
129
|
+
if not os.path.isfile(ap_in):
|
|
130
|
+
raise EmbedClientError(f"not a file or not found: {ap_in}")
|
|
131
|
+
if not os.access(ap_in, os.R_OK):
|
|
132
|
+
raise EmbedClientError(f"file not readable: {ap_in}")
|
|
133
|
+
try:
|
|
134
|
+
with open(ap_in, "rb") as f:
|
|
135
|
+
raw = f.read()
|
|
136
|
+
except OSError as e:
|
|
137
|
+
raise EmbedClientError(f"cannot read file: {ap_in}: {e}") from e
|
|
138
|
+
try:
|
|
139
|
+
decoded = raw.decode("utf-8-sig")
|
|
140
|
+
except UnicodeDecodeError as e:
|
|
141
|
+
raise EmbedClientError(
|
|
142
|
+
f"file must be UTF-8 (with optional BOM): {ap_in}"
|
|
143
|
+
) from e
|
|
144
|
+
|
|
145
|
+
data = build_embed_file_json_array_from_markdown_text(
|
|
146
|
+
decoded,
|
|
147
|
+
max_block_bytes=max_block_bytes,
|
|
148
|
+
strip_front_matter=strip_front_matter,
|
|
149
|
+
max_chunks=max_chunks,
|
|
150
|
+
)
|
|
151
|
+
ap_out = os.path.abspath(output_path)
|
|
152
|
+
_parent = os.path.dirname(ap_out)
|
|
153
|
+
if _parent:
|
|
154
|
+
os.makedirs(_parent, exist_ok=True)
|
|
155
|
+
try:
|
|
156
|
+
with open(ap_out, "w", encoding="utf-8") as f:
|
|
157
|
+
json.dump(data, f, ensure_ascii=False)
|
|
158
|
+
except OSError as e:
|
|
159
|
+
raise EmbedClientError(f"cannot write file: {ap_out}: {e}") from e
|
|
160
|
+
|
|
161
|
+
validate_local_json_embed_file(ap_out, max_block_bytes=max_block_bytes)
|
|
162
|
+
return ap_out
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def prepare_markdown_cli_main(args: Any) -> int:
|
|
166
|
+
"""
|
|
167
|
+
CLI entry for ``prepare-embed-file-json`` (expects argparse namespace attributes).
|
|
168
|
+
"""
|
|
169
|
+
import sys
|
|
170
|
+
from pathlib import Path
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
out_path = getattr(args, "output", None)
|
|
174
|
+
if not out_path:
|
|
175
|
+
base = Path(args.md_path).resolve()
|
|
176
|
+
out_path = str(base.parent / f"{base.stem}_embed_file.json")
|
|
177
|
+
out = prepare_markdown_path_to_json_file(
|
|
178
|
+
args.md_path,
|
|
179
|
+
out_path,
|
|
180
|
+
max_block_bytes=getattr(args, "max_block_bytes", None),
|
|
181
|
+
strip_front_matter=not bool(getattr(args, "no_strip_front_matter", False)),
|
|
182
|
+
max_chunks=getattr(args, "max_chunks", None),
|
|
183
|
+
)
|
|
184
|
+
print(out, file=sys.stdout)
|
|
185
|
+
return 0
|
|
186
|
+
except EmbedClientError as e:
|
|
187
|
+
print(f"❌ prepare-embed-file-json: {e}", file=sys.stderr)
|
|
188
|
+
return 1
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
__all__ = [
|
|
192
|
+
"build_embed_file_json_array_from_markdown_text",
|
|
193
|
+
"prepare_markdown_cli_main",
|
|
194
|
+
"prepare_markdown_path_to_json_file",
|
|
195
|
+
"split_body_into_text_blocks",
|
|
196
|
+
"strip_optional_yaml_front_matter",
|
|
197
|
+
]
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Response Normalizer
|
|
3
|
+
|
|
4
|
+
This module provides functions to normalize adapter responses to legacy format.
|
|
5
|
+
|
|
6
|
+
Author: Vasiliy Zdanovskiy
|
|
7
|
+
email: vasilyvz@gmail.com
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from typing import Any, Dict
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ResponseNormalizer:
|
|
14
|
+
"""
|
|
15
|
+
Normalizes adapter responses to legacy format expected by EmbeddingClient.
|
|
16
|
+
|
|
17
|
+
Adapter returns responses in format:
|
|
18
|
+
- Immediate: {"mode": "immediate", "command": "...", "result": {...}, "queued": False}
|
|
19
|
+
- Queued: {"mode": "queued", "job_id": "...", "status": "...", "result": {...}, "queued": True}
|
|
20
|
+
|
|
21
|
+
Legacy format expected:
|
|
22
|
+
- {"result": {"success": bool, "data": {...}}}
|
|
23
|
+
- {"error": {"code": int, "message": str, "details": ...}}
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
@staticmethod
|
|
27
|
+
def normalize_command_response(adapter_response: Dict[str, Any]) -> Dict[str, Any]:
|
|
28
|
+
"""
|
|
29
|
+
Normalize adapter command response to legacy format.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
adapter_response: Response from adapter execute_command_unified
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Normalized response in legacy format
|
|
36
|
+
"""
|
|
37
|
+
mode = adapter_response.get("mode", "immediate")
|
|
38
|
+
|
|
39
|
+
if mode == "queued":
|
|
40
|
+
return ResponseNormalizer._normalize_queued_response(adapter_response)
|
|
41
|
+
else:
|
|
42
|
+
return ResponseNormalizer._normalize_immediate_response(adapter_response)
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def _normalize_immediate_response(
|
|
46
|
+
adapter_response: Dict[str, Any],
|
|
47
|
+
) -> Dict[str, Any]:
|
|
48
|
+
"""Normalize immediate response."""
|
|
49
|
+
result = adapter_response.get("result", {})
|
|
50
|
+
|
|
51
|
+
# If result is already in legacy format, return as-is
|
|
52
|
+
if isinstance(result, dict) and ("success" in result or "data" in result):
|
|
53
|
+
return {"result": result}
|
|
54
|
+
|
|
55
|
+
# Wrap in legacy format
|
|
56
|
+
return {"result": {"success": True, "data": result}}
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def _normalize_queued_response(adapter_response: Dict[str, Any]) -> Dict[str, Any]:
|
|
60
|
+
"""Normalize queued response."""
|
|
61
|
+
result = adapter_response.get("result")
|
|
62
|
+
status = adapter_response.get("status", "unknown")
|
|
63
|
+
job_id = adapter_response.get("job_id")
|
|
64
|
+
|
|
65
|
+
# If result is already in legacy format, return as-is
|
|
66
|
+
if isinstance(result, dict) and ("success" in result or "data" in result):
|
|
67
|
+
# Add job metadata if available
|
|
68
|
+
if job_id and "data" in result:
|
|
69
|
+
result["data"]["job_id"] = job_id
|
|
70
|
+
result["data"]["status"] = status
|
|
71
|
+
return {"result": result}
|
|
72
|
+
|
|
73
|
+
# Wrap in legacy format with job metadata
|
|
74
|
+
data: Dict[str, Any]
|
|
75
|
+
if isinstance(result, dict):
|
|
76
|
+
data = result
|
|
77
|
+
else:
|
|
78
|
+
data = {}
|
|
79
|
+
|
|
80
|
+
normalized: Dict[str, Any] = {
|
|
81
|
+
"result": {
|
|
82
|
+
"success": status in ("completed", "success"),
|
|
83
|
+
"data": data,
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Add job metadata
|
|
88
|
+
if job_id:
|
|
89
|
+
data["job_id"] = job_id
|
|
90
|
+
data["status"] = status
|
|
91
|
+
|
|
92
|
+
return normalized
|
|
93
|
+
|
|
94
|
+
@staticmethod
|
|
95
|
+
def extract_error_from_adapter(adapter_error: Exception) -> Dict[str, Any]:
|
|
96
|
+
"""
|
|
97
|
+
Extract error information from adapter exception.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
adapter_error: Exception raised by adapter
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Error dictionary in legacy format
|
|
104
|
+
"""
|
|
105
|
+
error_msg = str(adapter_error)
|
|
106
|
+
|
|
107
|
+
# Try to extract structured error if available
|
|
108
|
+
if hasattr(adapter_error, "error"):
|
|
109
|
+
error_data = adapter_error.error
|
|
110
|
+
if isinstance(error_data, dict):
|
|
111
|
+
return {"error": error_data}
|
|
112
|
+
|
|
113
|
+
# Default error format
|
|
114
|
+
return {"error": {"code": -32000, "message": error_msg, "details": None}}
|
|
115
|
+
|
|
116
|
+
@staticmethod
|
|
117
|
+
def normalize_queue_status(queue_status: Dict[str, Any]) -> Dict[str, Any]:
|
|
118
|
+
"""
|
|
119
|
+
Normalize queue status response to legacy format.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
queue_status: Queue status from adapter (can be from queue_get_job_status or embed_job_status)
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
Normalized status in legacy format
|
|
126
|
+
"""
|
|
127
|
+
# Handle embed_job_status format
|
|
128
|
+
if "result" in queue_status and isinstance(queue_status["result"], dict):
|
|
129
|
+
result_data = queue_status["result"]
|
|
130
|
+
if result_data.get("success") and "data" in result_data:
|
|
131
|
+
data = result_data["data"]
|
|
132
|
+
status = data.get("status", "unknown")
|
|
133
|
+
# Check if done flag is set (embed_job_status specific)
|
|
134
|
+
if data.get("done", False) and status == "completed":
|
|
135
|
+
status = "completed"
|
|
136
|
+
result = data.get("result")
|
|
137
|
+
return {
|
|
138
|
+
"status": status,
|
|
139
|
+
"result": result,
|
|
140
|
+
"exists": data.get("exists", True),
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
# Extract status from nested structures (standard queue format)
|
|
144
|
+
status = queue_status.get("status")
|
|
145
|
+
if not status:
|
|
146
|
+
status = (
|
|
147
|
+
queue_status.get("data", {}).get("status")
|
|
148
|
+
or queue_status.get("result", {}).get("status")
|
|
149
|
+
or queue_status.get("result", {}).get("data", {}).get("status")
|
|
150
|
+
or "unknown"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# Extract result
|
|
154
|
+
result = queue_status.get("result")
|
|
155
|
+
if not result:
|
|
156
|
+
result = queue_status.get("data", {}).get("result") or queue_status.get(
|
|
157
|
+
"data", {}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return {"status": status, "result": result, "exists": status != "unknown"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Response Parsers
|
|
3
|
+
|
|
4
|
+
This module provides functions to extract data from API responses.
|
|
5
|
+
These functions were extracted from EmbeddingClient to reduce file size.
|
|
6
|
+
|
|
7
|
+
Author: Vasiliy Zdanovskiy
|
|
8
|
+
email: vasilyvz@gmail.com
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from typing import Any, Dict, List
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def extract_embeddings(result: Dict[str, Any]) -> List[List[float]]:
|
|
15
|
+
"""
|
|
16
|
+
Extract embeddings from API response, supporting both old and new formats.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
result: API response dictionary
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
List of embedding vectors (list of lists of floats)
|
|
23
|
+
|
|
24
|
+
Raises:
|
|
25
|
+
ValueError: If embeddings cannot be extracted from the response
|
|
26
|
+
"""
|
|
27
|
+
# Handle direct embeddings field (old format compatibility)
|
|
28
|
+
if "embeddings" in result:
|
|
29
|
+
return result["embeddings"]
|
|
30
|
+
|
|
31
|
+
# Handle result wrapper
|
|
32
|
+
if "result" in result:
|
|
33
|
+
res = result["result"]
|
|
34
|
+
|
|
35
|
+
# Handle direct list in result (old format)
|
|
36
|
+
if isinstance(res, list):
|
|
37
|
+
return res
|
|
38
|
+
|
|
39
|
+
if isinstance(res, dict):
|
|
40
|
+
# Handle old format: result.embeddings
|
|
41
|
+
if "embeddings" in res:
|
|
42
|
+
return res["embeddings"]
|
|
43
|
+
|
|
44
|
+
# Handle old format: result.data.embeddings
|
|
45
|
+
if (
|
|
46
|
+
"data" in res
|
|
47
|
+
and isinstance(res["data"], dict)
|
|
48
|
+
and "embeddings" in res["data"]
|
|
49
|
+
):
|
|
50
|
+
return res["data"]["embeddings"]
|
|
51
|
+
|
|
52
|
+
# Handle adapter format: result.data.results[].embedding
|
|
53
|
+
if (
|
|
54
|
+
"data" in res
|
|
55
|
+
and isinstance(res["data"], dict)
|
|
56
|
+
and "results" in res["data"]
|
|
57
|
+
):
|
|
58
|
+
results = res["data"]["results"]
|
|
59
|
+
if isinstance(results, list):
|
|
60
|
+
embeddings = []
|
|
61
|
+
for item in results:
|
|
62
|
+
if isinstance(item, dict) and "embedding" in item:
|
|
63
|
+
embeddings.append(item["embedding"])
|
|
64
|
+
else:
|
|
65
|
+
raise ValueError(
|
|
66
|
+
f"Invalid item format in adapter response: {item}"
|
|
67
|
+
)
|
|
68
|
+
return embeddings
|
|
69
|
+
|
|
70
|
+
# Handle new format: result.data[].embedding
|
|
71
|
+
if "data" in res and isinstance(res["data"], list):
|
|
72
|
+
embeddings = []
|
|
73
|
+
for item in res["data"]:
|
|
74
|
+
if isinstance(item, dict) and "embedding" in item:
|
|
75
|
+
embeddings.append(item["embedding"])
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError(
|
|
78
|
+
f"Invalid item format in new API response: {item}"
|
|
79
|
+
)
|
|
80
|
+
return embeddings
|
|
81
|
+
|
|
82
|
+
raise ValueError(f"Cannot extract embeddings from response: {result}")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def extract_embedding_data(result: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
86
|
+
"""
|
|
87
|
+
Extract full embedding data from API response (new format only).
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
result: API response dictionary
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
List of dictionaries with 'body', 'embedding', 'tokens', and 'bm25_tokens' fields
|
|
94
|
+
|
|
95
|
+
Raises:
|
|
96
|
+
ValueError: If data cannot be extracted or is in old format
|
|
97
|
+
"""
|
|
98
|
+
if "result" in result and isinstance(result["result"], dict):
|
|
99
|
+
res = result["result"]
|
|
100
|
+
if "data" in res and isinstance(res["data"], dict) and "results" in res["data"]:
|
|
101
|
+
# New format: result.data.results[]
|
|
102
|
+
results = res["data"]["results"]
|
|
103
|
+
if isinstance(results, list):
|
|
104
|
+
# Validate that all items have required fields
|
|
105
|
+
for i, item in enumerate(results):
|
|
106
|
+
if not isinstance(item, dict):
|
|
107
|
+
raise ValueError(f"Item {i} is not a dictionary: {item}")
|
|
108
|
+
if "body" not in item:
|
|
109
|
+
raise ValueError(f"Item {i} missing 'body' field: {item}")
|
|
110
|
+
if "embedding" not in item:
|
|
111
|
+
raise ValueError(f"Item {i} missing 'embedding' field: {item}")
|
|
112
|
+
if "tokens" not in item:
|
|
113
|
+
raise ValueError(f"Item {i} missing 'tokens' field: {item}")
|
|
114
|
+
if "bm25_tokens" not in item:
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"Item {i} missing 'bm25_tokens' field: {item}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return results
|
|
120
|
+
|
|
121
|
+
# Legacy support for old format: result.data[]
|
|
122
|
+
if "data" in res and isinstance(res["data"], list):
|
|
123
|
+
# Validate that all items have required fields
|
|
124
|
+
for i, item in enumerate(res["data"]):
|
|
125
|
+
if not isinstance(item, dict):
|
|
126
|
+
raise ValueError(f"Item {i} is not a dictionary: {item}")
|
|
127
|
+
if "body" not in item:
|
|
128
|
+
raise ValueError(f"Item {i} missing 'body' field: {item}")
|
|
129
|
+
if "embedding" not in item:
|
|
130
|
+
raise ValueError(f"Item {i} missing 'embedding' field: {item}")
|
|
131
|
+
# Old format had 'chunks' instead of 'tokens'
|
|
132
|
+
if "chunks" not in item and "tokens" not in item:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Item {i} missing 'chunks' or 'tokens' field: {item}"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return res["data"]
|
|
138
|
+
|
|
139
|
+
raise ValueError(
|
|
140
|
+
f"Cannot extract embedding data from response (new format required): {result}"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def extract_texts(result: Dict[str, Any]) -> List[str]:
|
|
145
|
+
"""
|
|
146
|
+
Extract original texts from API response (new format only).
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
result: API response dictionary
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
List of original text strings
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
ValueError: If texts cannot be extracted or is in old format
|
|
156
|
+
"""
|
|
157
|
+
data = extract_embedding_data(result)
|
|
158
|
+
return [item["body"] for item in data]
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def extract_chunks(result: Dict[str, Any]) -> List[List[str]]:
|
|
162
|
+
"""
|
|
163
|
+
Extract text chunks from API response (new format only).
|
|
164
|
+
Note: This method now extracts 'tokens' instead of 'chunks' for compatibility.
|
|
165
|
+
|
|
166
|
+
Args:
|
|
167
|
+
result: API response dictionary
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
List of token lists for each text
|
|
171
|
+
|
|
172
|
+
Raises:
|
|
173
|
+
ValueError: If chunks cannot be extracted or is in old format
|
|
174
|
+
"""
|
|
175
|
+
data = extract_embedding_data(result)
|
|
176
|
+
chunks = []
|
|
177
|
+
for item in data:
|
|
178
|
+
# New format uses 'tokens', old format used 'chunks'
|
|
179
|
+
if "tokens" in item:
|
|
180
|
+
chunks.append(item["tokens"])
|
|
181
|
+
elif "chunks" in item:
|
|
182
|
+
chunks.append(item["chunks"])
|
|
183
|
+
else:
|
|
184
|
+
raise ValueError(f"Item missing both 'tokens' and 'chunks' fields: {item}")
|
|
185
|
+
return chunks
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def extract_tokens(result: Dict[str, Any]) -> List[List[str]]:
|
|
189
|
+
"""
|
|
190
|
+
Extract tokens from API response (new format only).
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
result: API response dictionary
|
|
194
|
+
|
|
195
|
+
Returns:
|
|
196
|
+
List of token lists for each text
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValueError: If tokens cannot be extracted or is in old format
|
|
200
|
+
"""
|
|
201
|
+
data = extract_embedding_data(result)
|
|
202
|
+
return [item["tokens"] for item in data]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def extract_bm25_tokens(result: Dict[str, Any]) -> List[List[str]]:
|
|
206
|
+
"""
|
|
207
|
+
Extract BM25 tokens from API response (new format only).
|
|
208
|
+
|
|
209
|
+
Args:
|
|
210
|
+
result: API response dictionary
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
List of BM25 token lists for each text
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
ValueError: If BM25 tokens cannot be extracted or is in old format
|
|
217
|
+
"""
|
|
218
|
+
data = extract_embedding_data(result)
|
|
219
|
+
return [item["bm25_tokens"] for item in data]
|