remdb 0.3.242__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.

Potentially problematic release.


This version of remdb might be problematic. Click here for more details.

Files changed (235) hide show
  1. rem/__init__.py +129 -0
  2. rem/agentic/README.md +760 -0
  3. rem/agentic/__init__.py +54 -0
  4. rem/agentic/agents/README.md +155 -0
  5. rem/agentic/agents/__init__.py +38 -0
  6. rem/agentic/agents/agent_manager.py +311 -0
  7. rem/agentic/agents/sse_simulator.py +502 -0
  8. rem/agentic/context.py +425 -0
  9. rem/agentic/context_builder.py +360 -0
  10. rem/agentic/llm_provider_models.py +301 -0
  11. rem/agentic/mcp/__init__.py +0 -0
  12. rem/agentic/mcp/tool_wrapper.py +273 -0
  13. rem/agentic/otel/__init__.py +5 -0
  14. rem/agentic/otel/setup.py +240 -0
  15. rem/agentic/providers/phoenix.py +926 -0
  16. rem/agentic/providers/pydantic_ai.py +854 -0
  17. rem/agentic/query.py +117 -0
  18. rem/agentic/query_helper.py +89 -0
  19. rem/agentic/schema.py +737 -0
  20. rem/agentic/serialization.py +245 -0
  21. rem/agentic/tools/__init__.py +5 -0
  22. rem/agentic/tools/rem_tools.py +242 -0
  23. rem/api/README.md +657 -0
  24. rem/api/deps.py +253 -0
  25. rem/api/main.py +460 -0
  26. rem/api/mcp_router/prompts.py +182 -0
  27. rem/api/mcp_router/resources.py +820 -0
  28. rem/api/mcp_router/server.py +243 -0
  29. rem/api/mcp_router/tools.py +1605 -0
  30. rem/api/middleware/tracking.py +172 -0
  31. rem/api/routers/admin.py +520 -0
  32. rem/api/routers/auth.py +898 -0
  33. rem/api/routers/chat/__init__.py +5 -0
  34. rem/api/routers/chat/child_streaming.py +394 -0
  35. rem/api/routers/chat/completions.py +702 -0
  36. rem/api/routers/chat/json_utils.py +76 -0
  37. rem/api/routers/chat/models.py +202 -0
  38. rem/api/routers/chat/otel_utils.py +33 -0
  39. rem/api/routers/chat/sse_events.py +546 -0
  40. rem/api/routers/chat/streaming.py +950 -0
  41. rem/api/routers/chat/streaming_utils.py +327 -0
  42. rem/api/routers/common.py +18 -0
  43. rem/api/routers/dev.py +87 -0
  44. rem/api/routers/feedback.py +276 -0
  45. rem/api/routers/messages.py +620 -0
  46. rem/api/routers/models.py +86 -0
  47. rem/api/routers/query.py +362 -0
  48. rem/api/routers/shared_sessions.py +422 -0
  49. rem/auth/README.md +258 -0
  50. rem/auth/__init__.py +36 -0
  51. rem/auth/jwt.py +367 -0
  52. rem/auth/middleware.py +318 -0
  53. rem/auth/providers/__init__.py +16 -0
  54. rem/auth/providers/base.py +376 -0
  55. rem/auth/providers/email.py +215 -0
  56. rem/auth/providers/google.py +163 -0
  57. rem/auth/providers/microsoft.py +237 -0
  58. rem/cli/README.md +517 -0
  59. rem/cli/__init__.py +8 -0
  60. rem/cli/commands/README.md +299 -0
  61. rem/cli/commands/__init__.py +3 -0
  62. rem/cli/commands/ask.py +549 -0
  63. rem/cli/commands/cluster.py +1808 -0
  64. rem/cli/commands/configure.py +495 -0
  65. rem/cli/commands/db.py +828 -0
  66. rem/cli/commands/dreaming.py +324 -0
  67. rem/cli/commands/experiments.py +1698 -0
  68. rem/cli/commands/mcp.py +66 -0
  69. rem/cli/commands/process.py +388 -0
  70. rem/cli/commands/query.py +109 -0
  71. rem/cli/commands/scaffold.py +47 -0
  72. rem/cli/commands/schema.py +230 -0
  73. rem/cli/commands/serve.py +106 -0
  74. rem/cli/commands/session.py +453 -0
  75. rem/cli/dreaming.py +363 -0
  76. rem/cli/main.py +123 -0
  77. rem/config.py +244 -0
  78. rem/mcp_server.py +41 -0
  79. rem/models/core/__init__.py +49 -0
  80. rem/models/core/core_model.py +70 -0
  81. rem/models/core/engram.py +333 -0
  82. rem/models/core/experiment.py +672 -0
  83. rem/models/core/inline_edge.py +132 -0
  84. rem/models/core/rem_query.py +246 -0
  85. rem/models/entities/__init__.py +68 -0
  86. rem/models/entities/domain_resource.py +38 -0
  87. rem/models/entities/feedback.py +123 -0
  88. rem/models/entities/file.py +57 -0
  89. rem/models/entities/image_resource.py +88 -0
  90. rem/models/entities/message.py +64 -0
  91. rem/models/entities/moment.py +123 -0
  92. rem/models/entities/ontology.py +181 -0
  93. rem/models/entities/ontology_config.py +131 -0
  94. rem/models/entities/resource.py +95 -0
  95. rem/models/entities/schema.py +87 -0
  96. rem/models/entities/session.py +84 -0
  97. rem/models/entities/shared_session.py +180 -0
  98. rem/models/entities/subscriber.py +175 -0
  99. rem/models/entities/user.py +93 -0
  100. rem/py.typed +0 -0
  101. rem/registry.py +373 -0
  102. rem/schemas/README.md +507 -0
  103. rem/schemas/__init__.py +6 -0
  104. rem/schemas/agents/README.md +92 -0
  105. rem/schemas/agents/core/agent-builder.yaml +235 -0
  106. rem/schemas/agents/core/moment-builder.yaml +178 -0
  107. rem/schemas/agents/core/rem-query-agent.yaml +226 -0
  108. rem/schemas/agents/core/resource-affinity-assessor.yaml +99 -0
  109. rem/schemas/agents/core/simple-assistant.yaml +19 -0
  110. rem/schemas/agents/core/user-profile-builder.yaml +163 -0
  111. rem/schemas/agents/examples/contract-analyzer.yaml +317 -0
  112. rem/schemas/agents/examples/contract-extractor.yaml +134 -0
  113. rem/schemas/agents/examples/cv-parser.yaml +263 -0
  114. rem/schemas/agents/examples/hello-world.yaml +37 -0
  115. rem/schemas/agents/examples/query.yaml +54 -0
  116. rem/schemas/agents/examples/simple.yaml +21 -0
  117. rem/schemas/agents/examples/test.yaml +29 -0
  118. rem/schemas/agents/rem.yaml +132 -0
  119. rem/schemas/evaluators/hello-world/default.yaml +77 -0
  120. rem/schemas/evaluators/rem/faithfulness.yaml +219 -0
  121. rem/schemas/evaluators/rem/lookup-correctness.yaml +182 -0
  122. rem/schemas/evaluators/rem/retrieval-precision.yaml +199 -0
  123. rem/schemas/evaluators/rem/retrieval-recall.yaml +211 -0
  124. rem/schemas/evaluators/rem/search-correctness.yaml +192 -0
  125. rem/services/__init__.py +18 -0
  126. rem/services/audio/INTEGRATION.md +308 -0
  127. rem/services/audio/README.md +376 -0
  128. rem/services/audio/__init__.py +15 -0
  129. rem/services/audio/chunker.py +354 -0
  130. rem/services/audio/transcriber.py +259 -0
  131. rem/services/content/README.md +1269 -0
  132. rem/services/content/__init__.py +5 -0
  133. rem/services/content/providers.py +760 -0
  134. rem/services/content/service.py +762 -0
  135. rem/services/dreaming/README.md +230 -0
  136. rem/services/dreaming/__init__.py +53 -0
  137. rem/services/dreaming/affinity_service.py +322 -0
  138. rem/services/dreaming/moment_service.py +251 -0
  139. rem/services/dreaming/ontology_service.py +54 -0
  140. rem/services/dreaming/user_model_service.py +297 -0
  141. rem/services/dreaming/utils.py +39 -0
  142. rem/services/email/__init__.py +10 -0
  143. rem/services/email/service.py +522 -0
  144. rem/services/email/templates.py +360 -0
  145. rem/services/embeddings/__init__.py +11 -0
  146. rem/services/embeddings/api.py +127 -0
  147. rem/services/embeddings/worker.py +435 -0
  148. rem/services/fs/README.md +662 -0
  149. rem/services/fs/__init__.py +62 -0
  150. rem/services/fs/examples.py +206 -0
  151. rem/services/fs/examples_paths.py +204 -0
  152. rem/services/fs/git_provider.py +935 -0
  153. rem/services/fs/local_provider.py +760 -0
  154. rem/services/fs/parsing-hooks-examples.md +172 -0
  155. rem/services/fs/paths.py +276 -0
  156. rem/services/fs/provider.py +460 -0
  157. rem/services/fs/s3_provider.py +1042 -0
  158. rem/services/fs/service.py +186 -0
  159. rem/services/git/README.md +1075 -0
  160. rem/services/git/__init__.py +17 -0
  161. rem/services/git/service.py +469 -0
  162. rem/services/phoenix/EXPERIMENT_DESIGN.md +1146 -0
  163. rem/services/phoenix/README.md +453 -0
  164. rem/services/phoenix/__init__.py +46 -0
  165. rem/services/phoenix/client.py +960 -0
  166. rem/services/phoenix/config.py +88 -0
  167. rem/services/phoenix/prompt_labels.py +477 -0
  168. rem/services/postgres/README.md +757 -0
  169. rem/services/postgres/__init__.py +49 -0
  170. rem/services/postgres/diff_service.py +599 -0
  171. rem/services/postgres/migration_service.py +427 -0
  172. rem/services/postgres/programmable_diff_service.py +635 -0
  173. rem/services/postgres/pydantic_to_sqlalchemy.py +562 -0
  174. rem/services/postgres/register_type.py +353 -0
  175. rem/services/postgres/repository.py +481 -0
  176. rem/services/postgres/schema_generator.py +661 -0
  177. rem/services/postgres/service.py +802 -0
  178. rem/services/postgres/sql_builder.py +355 -0
  179. rem/services/rate_limit.py +113 -0
  180. rem/services/rem/README.md +318 -0
  181. rem/services/rem/__init__.py +23 -0
  182. rem/services/rem/exceptions.py +71 -0
  183. rem/services/rem/executor.py +293 -0
  184. rem/services/rem/parser.py +180 -0
  185. rem/services/rem/queries.py +196 -0
  186. rem/services/rem/query.py +371 -0
  187. rem/services/rem/service.py +608 -0
  188. rem/services/session/README.md +374 -0
  189. rem/services/session/__init__.py +13 -0
  190. rem/services/session/compression.py +488 -0
  191. rem/services/session/pydantic_messages.py +310 -0
  192. rem/services/session/reload.py +85 -0
  193. rem/services/user_service.py +130 -0
  194. rem/settings.py +1877 -0
  195. rem/sql/background_indexes.sql +52 -0
  196. rem/sql/migrations/001_install.sql +983 -0
  197. rem/sql/migrations/002_install_models.sql +3157 -0
  198. rem/sql/migrations/003_optional_extensions.sql +326 -0
  199. rem/sql/migrations/004_cache_system.sql +282 -0
  200. rem/sql/migrations/005_schema_update.sql +145 -0
  201. rem/sql/migrations/migrate_session_id_to_uuid.sql +45 -0
  202. rem/utils/AGENTIC_CHUNKING.md +597 -0
  203. rem/utils/README.md +628 -0
  204. rem/utils/__init__.py +61 -0
  205. rem/utils/agentic_chunking.py +622 -0
  206. rem/utils/batch_ops.py +343 -0
  207. rem/utils/chunking.py +108 -0
  208. rem/utils/clip_embeddings.py +276 -0
  209. rem/utils/constants.py +97 -0
  210. rem/utils/date_utils.py +228 -0
  211. rem/utils/dict_utils.py +98 -0
  212. rem/utils/embeddings.py +436 -0
  213. rem/utils/examples/embeddings_example.py +305 -0
  214. rem/utils/examples/sql_types_example.py +202 -0
  215. rem/utils/files.py +323 -0
  216. rem/utils/markdown.py +16 -0
  217. rem/utils/mime_types.py +158 -0
  218. rem/utils/model_helpers.py +492 -0
  219. rem/utils/schema_loader.py +649 -0
  220. rem/utils/sql_paths.py +146 -0
  221. rem/utils/sql_types.py +350 -0
  222. rem/utils/user_id.py +81 -0
  223. rem/utils/vision.py +325 -0
  224. rem/workers/README.md +506 -0
  225. rem/workers/__init__.py +7 -0
  226. rem/workers/db_listener.py +579 -0
  227. rem/workers/db_maintainer.py +74 -0
  228. rem/workers/dreaming.py +502 -0
  229. rem/workers/engram_processor.py +312 -0
  230. rem/workers/sqs_file_processor.py +193 -0
  231. rem/workers/unlogged_maintainer.py +463 -0
  232. remdb-0.3.242.dist-info/METADATA +1632 -0
  233. remdb-0.3.242.dist-info/RECORD +235 -0
  234. remdb-0.3.242.dist-info/WHEEL +4 -0
  235. remdb-0.3.242.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,436 @@
1
+ """
2
+ Embeddings utility for generating vector embeddings using various providers.
3
+
4
+ Uses requests library for HTTP calls (no provider SDKs required).
5
+ Supports batch processing to optimize API usage and respect rate limits.
6
+ Uses tenacity for automatic retry with exponential backoff.
7
+
8
+ Supported Providers:
9
+ - OpenAI: text-embedding-3-small, text-embedding-3-large, text-embedding-ada-002
10
+ - Anthropic: voyage-2 (via Voyage AI)
11
+
12
+ Usage:
13
+ from rem.utils.embeddings import generate_embeddings
14
+
15
+ # Single text
16
+ embedding = generate_embeddings("openai:text-embedding-3-small", "Hello world")
17
+
18
+ # Batch processing
19
+ texts = ["Hello world", "How are you?", "Good morning"]
20
+ embeddings = generate_embeddings("openai:text-embedding-3-small", texts)
21
+ """
22
+
23
+ from typing import Any, cast
24
+
25
+ import requests
26
+ from tenacity import (
27
+ retry,
28
+ retry_if_exception_type,
29
+ stop_after_attempt,
30
+ wait_exponential,
31
+ )
32
+
33
+ from rem.utils.constants import (
34
+ HTTP_TIMEOUT_LONG,
35
+ OPENAI_EMBEDDING_DIMS_SMALL,
36
+ OPENAI_EMBEDDING_DIMS_LARGE,
37
+ VOYAGE_EMBEDDING_DIMS,
38
+ RETRY_BACKOFF_MULTIPLIER,
39
+ RETRY_BACKOFF_MIN,
40
+ RETRY_BACKOFF_MAX,
41
+ )
42
+
43
+
44
+ class EmbeddingError(Exception):
45
+ """Base exception for embedding generation errors."""
46
+
47
+ pass
48
+
49
+
50
+ class RateLimitError(EmbeddingError):
51
+ """Raised when rate limit is exceeded."""
52
+
53
+ pass
54
+
55
+
56
+ def generate_embeddings(
57
+ embedding_provider: str,
58
+ texts: str | list[str],
59
+ api_key: str | None = None,
60
+ max_retries: int = 1,
61
+ ) -> list[float] | list[list[float]]:
62
+ """
63
+ Generate embeddings for text(s) using specified provider.
64
+
65
+ Uses tenacity for automatic retry with exponential backoff on rate limits.
66
+
67
+ Args:
68
+ embedding_provider: Provider and model in format "provider:model_name"
69
+ (e.g., "openai:text-embedding-3-small")
70
+ texts: Single text string or list of texts to embed
71
+ api_key: API key for the provider. If None, reads from environment variables:
72
+ - OpenAI: OPENAI_API_KEY or LLM__OPENAI_API_KEY
73
+ - Anthropic: ANTHROPIC_API_KEY or LLM__ANTHROPIC_API_KEY
74
+ max_retries: Maximum number of retry attempts for rate limits (default: 1)
75
+
76
+ Returns:
77
+ - If single text: list[float] (single embedding vector)
78
+ - If list of texts: list[list[float]] (list of embedding vectors)
79
+
80
+ Raises:
81
+ EmbeddingError: If embedding generation fails
82
+ RateLimitError: If rate limit exceeded after retries
83
+ ValueError: If provider format is invalid
84
+
85
+ Examples:
86
+ >>> embedding = generate_embeddings("openai:text-embedding-3-small", "Hello")
87
+ >>> len(embedding)
88
+ 1536
89
+
90
+ >>> embeddings = generate_embeddings(
91
+ ... "openai:text-embedding-3-small",
92
+ ... ["Hello", "World"]
93
+ ... )
94
+ >>> len(embeddings)
95
+ 2
96
+ """
97
+ # Parse provider format
98
+ if ":" not in embedding_provider:
99
+ raise ValueError(
100
+ f"Invalid embedding_provider format: {embedding_provider}. "
101
+ f"Expected format: 'provider:model_name'"
102
+ )
103
+
104
+ provider, model_name = embedding_provider.split(":", 1)
105
+ provider = provider.lower()
106
+
107
+ # Normalize input to list
108
+ if isinstance(texts, str):
109
+ text_list: list[str] = [texts]
110
+ is_single = True
111
+ else:
112
+ text_list = texts
113
+ is_single = False
114
+
115
+ # Validate input
116
+ if not text_list:
117
+ raise ValueError("texts cannot be empty")
118
+
119
+ # Get API key from environment if not provided
120
+ if api_key is None:
121
+ api_key = _get_api_key(provider)
122
+
123
+ # Generate embeddings (tenacity handles retries)
124
+ if provider == "openai":
125
+ embeddings = _generate_openai_embeddings_with_retry(
126
+ model_name, text_list, api_key, max_retries
127
+ )
128
+ elif provider == "anthropic":
129
+ # Anthropic uses Voyage AI for embeddings
130
+ embeddings = _generate_voyage_embeddings_with_retry(
131
+ model_name, text_list, api_key, max_retries
132
+ )
133
+ else:
134
+ raise ValueError(f"Unsupported embedding provider: {provider}")
135
+
136
+ # Return single embedding or list based on input
137
+ return embeddings[0] if is_single else embeddings
138
+
139
+
140
+ def _get_api_key(provider: str) -> str:
141
+ """
142
+ Get API key from environment variables.
143
+
144
+ Args:
145
+ provider: Provider name (openai, anthropic)
146
+
147
+ Returns:
148
+ API key string
149
+
150
+ Raises:
151
+ ValueError: If API key not found in environment
152
+ """
153
+ from ..settings import settings
154
+
155
+ if provider == "openai":
156
+ api_key = settings.llm.openai_api_key
157
+ if not api_key:
158
+ raise ValueError(
159
+ "OpenAI API key not found. Set LLM__OPENAI_API_KEY environment variable."
160
+ )
161
+ return api_key
162
+ elif provider == "anthropic":
163
+ api_key = settings.llm.anthropic_api_key
164
+ if not api_key:
165
+ raise ValueError(
166
+ "Anthropic API key not found. Set LLM__ANTHROPIC_API_KEY environment variable."
167
+ )
168
+ return api_key
169
+ else:
170
+ raise ValueError(f"Unknown provider: {provider}")
171
+
172
+
173
+ def _create_retry_decorator(max_retries: int):
174
+ """Create a retry decorator with exponential backoff."""
175
+ return retry(
176
+ retry=retry_if_exception_type(RateLimitError),
177
+ stop=stop_after_attempt(max_retries),
178
+ wait=wait_exponential(
179
+ multiplier=RETRY_BACKOFF_MULTIPLIER,
180
+ min=RETRY_BACKOFF_MIN,
181
+ max=RETRY_BACKOFF_MAX,
182
+ ),
183
+ reraise=True,
184
+ )
185
+
186
+
187
+ def _generate_openai_embeddings_with_retry(
188
+ model: str, texts: list[str], api_key: str, max_retries: int
189
+ ) -> list[list[float]]:
190
+ """
191
+ Generate embeddings using OpenAI API with automatic retry.
192
+
193
+ Uses tenacity for exponential backoff on rate limits.
194
+
195
+ Args:
196
+ model: OpenAI model name (e.g., "text-embedding-3-small")
197
+ texts: List of texts to embed
198
+ api_key: OpenAI API key
199
+ max_retries: Maximum number of retry attempts
200
+
201
+ Returns:
202
+ List of embedding vectors
203
+
204
+ Raises:
205
+ EmbeddingError: If API request fails
206
+ RateLimitError: If rate limit exceeded after retries
207
+ """
208
+ # Create retry decorator dynamically based on max_retries
209
+ retry_decorator = _create_retry_decorator(max_retries)
210
+
211
+ @retry_decorator
212
+ def _call_api():
213
+ return _generate_openai_embeddings(model, texts, api_key)
214
+
215
+ return cast(list[list[float]], _call_api())
216
+
217
+
218
+ def _generate_openai_embeddings(
219
+ model: str, texts: list[str], api_key: str
220
+ ) -> list[list[float]]:
221
+ """
222
+ Generate embeddings using OpenAI API (internal, no retry).
223
+
224
+ API Docs: https://platform.openai.com/docs/api-reference/embeddings
225
+
226
+ Args:
227
+ model: OpenAI model name (e.g., "text-embedding-3-small")
228
+ texts: List of texts to embed
229
+ api_key: OpenAI API key
230
+
231
+ Returns:
232
+ List of embedding vectors
233
+
234
+ Raises:
235
+ EmbeddingError: If API request fails
236
+ RateLimitError: If rate limit exceeded
237
+ """
238
+ url = "https://api.openai.com/v1/embeddings"
239
+ headers = {
240
+ "Authorization": f"Bearer {api_key}",
241
+ "Content-Type": "application/json",
242
+ }
243
+
244
+ payload = {
245
+ "model": model,
246
+ "input": texts,
247
+ }
248
+
249
+ try:
250
+ response = requests.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT_LONG)
251
+
252
+ # Handle rate limits
253
+ if response.status_code == 429:
254
+ raise RateLimitError(
255
+ f"OpenAI rate limit exceeded: {response.json().get('error', {}).get('message', 'Unknown error')}"
256
+ )
257
+
258
+ # Handle other errors
259
+ if response.status_code != 200:
260
+ error_msg = response.json().get("error", {}).get("message", "Unknown error")
261
+ raise EmbeddingError(
262
+ f"OpenAI API error (status {response.status_code}): {error_msg}"
263
+ )
264
+
265
+ # Extract embeddings from response
266
+ data = response.json()
267
+ embeddings_data = data.get("data", [])
268
+
269
+ # Sort by index to maintain order (API may return out of order)
270
+ embeddings_data.sort(key=lambda x: x.get("index", 0))
271
+
272
+ embeddings = [item["embedding"] for item in embeddings_data]
273
+
274
+ if len(embeddings) != len(texts):
275
+ raise EmbeddingError(
276
+ f"Expected {len(texts)} embeddings, got {len(embeddings)}"
277
+ )
278
+
279
+ return embeddings
280
+
281
+ except requests.exceptions.Timeout:
282
+ raise EmbeddingError("OpenAI API request timed out")
283
+ except requests.exceptions.RequestException as e:
284
+ raise EmbeddingError(f"OpenAI API request failed: {str(e)}")
285
+
286
+
287
+ def _generate_voyage_embeddings_with_retry(
288
+ model: str, texts: list[str], api_key: str, max_retries: int
289
+ ) -> list[list[float]]:
290
+ """
291
+ Generate embeddings using Voyage AI API with automatic retry.
292
+
293
+ Uses tenacity for exponential backoff on rate limits.
294
+
295
+ Args:
296
+ model: Voyage model name (e.g., "voyage-2")
297
+ texts: List of texts to embed
298
+ api_key: Voyage AI API key
299
+ max_retries: Maximum number of retry attempts
300
+
301
+ Returns:
302
+ List of embedding vectors
303
+
304
+ Raises:
305
+ EmbeddingError: If API request fails
306
+ RateLimitError: If rate limit exceeded after retries
307
+ """
308
+ # Create retry decorator dynamically based on max_retries
309
+ retry_decorator = _create_retry_decorator(max_retries)
310
+
311
+ @retry_decorator
312
+ def _call_api():
313
+ return _generate_voyage_embeddings(model, texts, api_key)
314
+
315
+ return cast(list[list[float]], _call_api())
316
+
317
+
318
+ def _generate_voyage_embeddings(
319
+ model: str, texts: list[str], api_key: str
320
+ ) -> list[list[float]]:
321
+ """
322
+ Generate embeddings using Voyage AI API (internal, no retry).
323
+
324
+ API Docs: https://docs.voyageai.com/docs/embeddings
325
+
326
+ Args:
327
+ model: Voyage model name (e.g., "voyage-2")
328
+ texts: List of texts to embed
329
+ api_key: Voyage AI API key
330
+
331
+ Returns:
332
+ List of embedding vectors
333
+
334
+ Raises:
335
+ EmbeddingError: If API request fails
336
+ RateLimitError: If rate limit exceeded
337
+ """
338
+ url = "https://api.voyageai.com/v1/embeddings"
339
+ headers = {
340
+ "Authorization": f"Bearer {api_key}",
341
+ "Content-Type": "application/json",
342
+ }
343
+
344
+ payload = {
345
+ "model": model,
346
+ "input": texts,
347
+ }
348
+
349
+ try:
350
+ response = requests.post(url, json=payload, headers=headers, timeout=HTTP_TIMEOUT_LONG)
351
+
352
+ # Handle rate limits
353
+ if response.status_code == 429:
354
+ raise RateLimitError(
355
+ f"Voyage AI rate limit exceeded: {response.json().get('error', {}).get('message', 'Unknown error')}"
356
+ )
357
+
358
+ # Handle other errors
359
+ if response.status_code != 200:
360
+ error_msg = response.json().get("error", {}).get("message", "Unknown error")
361
+ raise EmbeddingError(
362
+ f"Voyage AI API error (status {response.status_code}): {error_msg}"
363
+ )
364
+
365
+ # Extract embeddings from response
366
+ data = response.json()
367
+ embeddings_data = data.get("data", [])
368
+
369
+ embeddings = [item["embedding"] for item in embeddings_data]
370
+
371
+ if len(embeddings) != len(texts):
372
+ raise EmbeddingError(
373
+ f"Expected {len(texts)} embeddings, got {len(embeddings)}"
374
+ )
375
+
376
+ return embeddings
377
+
378
+ except requests.exceptions.Timeout:
379
+ raise EmbeddingError("Voyage AI API request timed out")
380
+ except requests.exceptions.RequestException as e:
381
+ raise EmbeddingError(f"Voyage AI API request failed: {str(e)}")
382
+
383
+
384
+ def get_embedding_dimension(embedding_provider: str) -> int:
385
+ """
386
+ Get embedding dimension for a given provider and model.
387
+
388
+ Args:
389
+ embedding_provider: Provider and model in format "provider:model_name"
390
+
391
+ Returns:
392
+ Embedding dimension (vector length)
393
+
394
+ Raises:
395
+ ValueError: If provider/model is unknown
396
+
397
+ Examples:
398
+ >>> get_embedding_dimension("openai:text-embedding-3-small")
399
+ 1536
400
+ >>> get_embedding_dimension("openai:text-embedding-3-large")
401
+ 3072
402
+ """
403
+ if ":" not in embedding_provider:
404
+ raise ValueError(
405
+ f"Invalid embedding_provider format: {embedding_provider}. "
406
+ f"Expected format: 'provider:model_name'"
407
+ )
408
+
409
+ provider, model_name = embedding_provider.split(":", 1)
410
+ provider = provider.lower()
411
+
412
+ # OpenAI dimensions
413
+ openai_dimensions = {
414
+ "text-embedding-3-small": 1536,
415
+ "text-embedding-3-large": 3072,
416
+ "text-embedding-ada-002": 1536,
417
+ }
418
+
419
+ # Voyage AI dimensions
420
+ voyage_dimensions = {
421
+ "voyage-2": 1024,
422
+ "voyage-large-2": 1536,
423
+ "voyage-code-2": 1536,
424
+ }
425
+
426
+ if provider == "openai":
427
+ if model_name in openai_dimensions:
428
+ return openai_dimensions[model_name]
429
+ raise ValueError(f"Unknown OpenAI model: {model_name}")
430
+ elif provider == "anthropic":
431
+ # Anthropic uses Voyage AI
432
+ if model_name in voyage_dimensions:
433
+ return voyage_dimensions[model_name]
434
+ raise ValueError(f"Unknown Voyage AI model: {model_name}")
435
+ else:
436
+ raise ValueError(f"Unknown provider: {provider}")