hammad-python 0.0.11__py3-none-any.whl → 0.0.13__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.
Files changed (80) hide show
  1. hammad/__init__.py +169 -56
  2. hammad/_core/__init__.py +1 -0
  3. hammad/_core/_utils/__init__.py +4 -0
  4. hammad/_core/_utils/_import_utils.py +182 -0
  5. hammad/ai/__init__.py +59 -0
  6. hammad/ai/_utils.py +142 -0
  7. hammad/ai/completions/__init__.py +44 -0
  8. hammad/ai/completions/client.py +729 -0
  9. hammad/ai/completions/create.py +686 -0
  10. hammad/ai/completions/types.py +711 -0
  11. hammad/ai/completions/utils.py +374 -0
  12. hammad/ai/embeddings/__init__.py +35 -0
  13. hammad/ai/embeddings/client/__init__.py +1 -0
  14. hammad/ai/embeddings/client/base_embeddings_client.py +26 -0
  15. hammad/ai/embeddings/client/fastembed_text_embeddings_client.py +200 -0
  16. hammad/ai/embeddings/client/litellm_embeddings_client.py +288 -0
  17. hammad/ai/embeddings/create.py +159 -0
  18. hammad/ai/embeddings/types.py +69 -0
  19. hammad/base/__init__.py +35 -0
  20. hammad/{based → base}/fields.py +23 -23
  21. hammad/{based → base}/model.py +124 -14
  22. hammad/base/utils.py +280 -0
  23. hammad/cache/__init__.py +30 -12
  24. hammad/cache/base_cache.py +181 -0
  25. hammad/cache/cache.py +169 -0
  26. hammad/cache/decorators.py +261 -0
  27. hammad/cache/file_cache.py +80 -0
  28. hammad/cache/ttl_cache.py +74 -0
  29. hammad/cli/__init__.py +10 -2
  30. hammad/cli/{styles/animations.py → animations.py} +79 -23
  31. hammad/cli/{plugins/__init__.py → plugins.py} +85 -90
  32. hammad/cli/styles/__init__.py +50 -0
  33. hammad/cli/styles/settings.py +4 -0
  34. hammad/configuration/__init__.py +35 -0
  35. hammad/{data/types/files → configuration}/configuration.py +96 -7
  36. hammad/data/__init__.py +14 -26
  37. hammad/data/collections/__init__.py +4 -2
  38. hammad/data/collections/collection.py +300 -75
  39. hammad/data/collections/vector_collection.py +118 -12
  40. hammad/data/databases/__init__.py +2 -2
  41. hammad/data/databases/database.py +383 -32
  42. hammad/json/__init__.py +2 -2
  43. hammad/logging/__init__.py +13 -5
  44. hammad/logging/decorators.py +404 -2
  45. hammad/logging/logger.py +442 -22
  46. hammad/multimodal/__init__.py +24 -0
  47. hammad/{data/types/files → multimodal}/audio.py +21 -6
  48. hammad/{data/types/files → multimodal}/image.py +5 -5
  49. hammad/multithreading/__init__.py +304 -0
  50. hammad/pydantic/__init__.py +2 -2
  51. hammad/pydantic/converters.py +1 -1
  52. hammad/pydantic/models/__init__.py +2 -2
  53. hammad/text/__init__.py +59 -14
  54. hammad/text/converters.py +723 -0
  55. hammad/text/{utils/markdown/formatting.py → markdown.py} +25 -23
  56. hammad/text/text.py +12 -14
  57. hammad/types/__init__.py +11 -0
  58. hammad/{data/types/files → types}/file.py +18 -18
  59. hammad/typing/__init__.py +138 -84
  60. hammad/web/__init__.py +3 -2
  61. hammad/web/models.py +245 -0
  62. hammad/web/search/client.py +75 -23
  63. hammad/web/utils.py +14 -5
  64. hammad/yaml/__init__.py +2 -2
  65. hammad/yaml/converters.py +1 -1
  66. {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/METADATA +4 -1
  67. hammad_python-0.0.13.dist-info/RECORD +85 -0
  68. hammad/based/__init__.py +0 -52
  69. hammad/based/utils.py +0 -455
  70. hammad/cache/_cache.py +0 -746
  71. hammad/data/types/__init__.py +0 -33
  72. hammad/data/types/files/__init__.py +0 -1
  73. hammad/data/types/files/document.py +0 -195
  74. hammad/text/utils/__init__.py +0 -1
  75. hammad/text/utils/converters.py +0 -229
  76. hammad/text/utils/markdown/__init__.py +0 -1
  77. hammad/text/utils/markdown/converters.py +0 -506
  78. hammad_python-0.0.11.dist-info/RECORD +0 -65
  79. {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/WHEEL +0 -0
  80. {hammad_python-0.0.11.dist-info → hammad_python-0.0.13.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,374 @@
1
+ """hammad.ai.completions.utils"""
2
+
3
+ import json
4
+ from typing import (
5
+ Optional,
6
+ List,
7
+ Iterator,
8
+ AsyncIterator,
9
+ TypeVar,
10
+ Type,
11
+ Any,
12
+ )
13
+
14
+ try:
15
+ from pydantic import BaseModel
16
+ except ImportError:
17
+ raise ImportError(
18
+ "Using completion stream parsing requires the `openai` and `instructor` packages."
19
+ "Please install with: pip install 'hammad-python[ai]'"
20
+ )
21
+
22
+ from ...cache import cached
23
+ from .types import (
24
+ CompletionsInputParam,
25
+ ChatCompletionMessageParam,
26
+ CompletionStream,
27
+ AsyncCompletionStream,
28
+ Completion,
29
+ )
30
+
31
+ T = TypeVar("T", bound=BaseModel)
32
+
33
+ __all__ = (
34
+ "parse_completions_input",
35
+ "create_completion_stream",
36
+ "create_async_completion_stream",
37
+ "format_tool_calls",
38
+ "convert_response_to_completion",
39
+ )
40
+
41
+
42
+ @cached
43
+ def parse_completions_input(
44
+ input: CompletionsInputParam,
45
+ instructions: Optional[str] = None,
46
+ ) -> List[ChatCompletionMessageParam]:
47
+ """Parse various input formats into a list of ChatCompletionMessageParam.
48
+
49
+ This function handles:
50
+ - Plain strings (converted to user messages)
51
+ - Strings with message blocks like [system], [user], [assistant]
52
+ - Single ChatCompletionMessageParam objects
53
+ - Lists of ChatCompletionMessageParam objects
54
+ - Objects with model_dump() method
55
+
56
+ Args:
57
+ input: The input to parse
58
+ instructions: Optional system instructions to prepend
59
+
60
+ Returns:
61
+ List of ChatCompletionMessageParam objects
62
+ """
63
+ messages: List[ChatCompletionMessageParam] = []
64
+
65
+ # Handle string inputs
66
+ if isinstance(input, str):
67
+ # Check if string contains message blocks like [system], [user], [assistant]
68
+ import re
69
+
70
+ # Pattern to match only allowed message blocks (system, user, assistant)
71
+ pattern = (
72
+ r"\[(system|user|assistant)\]\s*(.*?)(?=\[(?:system|user|assistant)\]|$)"
73
+ )
74
+ matches = re.findall(pattern, input, re.DOTALL | re.IGNORECASE)
75
+
76
+ if matches:
77
+ # Validate that we only have allowed roles
78
+ allowed_roles = {"system", "user", "assistant"}
79
+ found_roles = {role.lower() for role, _ in matches}
80
+
81
+ if not found_roles.issubset(allowed_roles):
82
+ invalid_roles = found_roles - allowed_roles
83
+ raise ValueError(
84
+ f"Invalid message roles found: {invalid_roles}. Only 'system', 'user', and 'assistant' are allowed."
85
+ )
86
+
87
+ # Parse message blocks
88
+ system_contents = []
89
+
90
+ for role, content in matches:
91
+ content = content.strip()
92
+ if content:
93
+ if role.lower() == "system":
94
+ system_contents.append(content)
95
+ else:
96
+ messages.append({"role": role.lower(), "content": content})
97
+
98
+ # Combine system contents if any exist
99
+ if system_contents:
100
+ combined_system = "\n\n".join(system_contents)
101
+ if instructions:
102
+ combined_system = f"{combined_system}\n\n{instructions}"
103
+ messages.insert(0, {"role": "system", "content": combined_system})
104
+ elif instructions:
105
+ messages.insert(0, {"role": "system", "content": instructions})
106
+ else:
107
+ # Plain string - create user message
108
+ if instructions:
109
+ messages.append({"role": "system", "content": instructions})
110
+ messages.append({"role": "user", "content": input})
111
+
112
+ # Handle single message object
113
+ elif hasattr(input, "model_dump"):
114
+ message_dict = input.model_dump()
115
+ if instructions:
116
+ messages.append({"role": "system", "content": instructions})
117
+ messages.append(message_dict)
118
+
119
+ # Handle list of messages
120
+ elif isinstance(input, list):
121
+ system_contents = []
122
+ other_messages = []
123
+
124
+ for item in input:
125
+ if hasattr(item, "model_dump"):
126
+ msg_dict = item.model_dump()
127
+ else:
128
+ msg_dict = item
129
+
130
+ if msg_dict.get("role") == "system":
131
+ system_contents.append(msg_dict.get("content", ""))
132
+ else:
133
+ other_messages.append(msg_dict)
134
+
135
+ # Combine system messages and instructions
136
+ if system_contents or instructions:
137
+ combined_system_parts = []
138
+ if system_contents:
139
+ combined_system_parts.extend(system_contents)
140
+ if instructions:
141
+ combined_system_parts.append(instructions)
142
+
143
+ messages.append(
144
+ {"role": "system", "content": "\n\n".join(combined_system_parts)}
145
+ )
146
+
147
+ messages.extend(other_messages)
148
+
149
+ # Handle single dictionary or other object
150
+ else:
151
+ if hasattr(input, "model_dump"):
152
+ message_dict = input.model_dump()
153
+ else:
154
+ message_dict = input
155
+
156
+ if instructions:
157
+ messages.append({"role": "system", "content": instructions})
158
+ messages.append(message_dict)
159
+
160
+ return messages
161
+
162
+
163
+ def create_completion_stream(
164
+ stream: Iterator[Any], output_type: Type[T] = str, model: str | None = None
165
+ ) -> CompletionStream[T]:
166
+ """Create a unified completion stream from a raw stream.
167
+
168
+ This function wraps raw streams from both LiteLLM and Instructor
169
+ into a unified CompletionStream interface. It automatically detects
170
+ the stream type based on the output_type parameter.
171
+
172
+ Args:
173
+ stream: The raw stream from LiteLLM or Instructor
174
+ output_type: The expected output type (str for LiteLLM, model class for Instructor)
175
+ model: The model name for metadata
176
+
177
+ Returns:
178
+ CompletionStream: Unified stream interface
179
+
180
+ Examples:
181
+ # For LiteLLM string completions
182
+ litellm_stream = litellm.completion(model="gpt-4", messages=messages, stream=True)
183
+ unified_stream = create_completion_stream(litellm_stream, str, "gpt-4")
184
+
185
+ # For Instructor structured outputs
186
+ instructor_stream = instructor_client.completion(response_model=User, messages=messages, stream=True)
187
+ unified_stream = create_completion_stream(instructor_stream, User, "gpt-4")
188
+ """
189
+ return CompletionStream(stream, output_type, model)
190
+
191
+
192
+ def create_async_completion_stream(
193
+ stream: AsyncIterator[Any], output_type: Type[T] = str, model: str | None = None
194
+ ) -> AsyncCompletionStream[T]:
195
+ """Create a unified async completion stream from a raw async stream.
196
+
197
+ This function wraps raw async streams from both LiteLLM and Instructor
198
+ into a unified AsyncCompletionStream interface. It automatically detects
199
+ the stream type based on the output_type parameter.
200
+
201
+ Args:
202
+ stream: The raw async stream from LiteLLM or Instructor
203
+ output_type: The expected output type (str for LiteLLM, model class for Instructor)
204
+ model: The model name for metadata
205
+
206
+ Returns:
207
+ AsyncCompletionStream: Unified async stream interface
208
+
209
+ Examples:
210
+ ```python
211
+ # For LiteLLM async string completions
212
+ litellm_stream = await litellm.acompletion(model="gpt-4", messages=messages, stream=True)
213
+ unified_stream = create_async_completion_stream(litellm_stream, str, "gpt-4")
214
+
215
+ # For Instructor async structured outputs
216
+ instructor_stream = await instructor_client.acompletion(response_model=User, messages=messages, stream=True)
217
+ unified_stream = create_async_completion_stream(instructor_stream, User, "gpt-4")
218
+ ```
219
+ """
220
+ return AsyncCompletionStream(stream, output_type, model)
221
+
222
+
223
+ def format_tool_calls(
224
+ messages: List[ChatCompletionMessageParam],
225
+ ) -> List[ChatCompletionMessageParam]:
226
+ """Format message thread by replacing tool call blocks with readable assistant messages.
227
+
228
+ This function processes a message thread and replaces sequences of:
229
+ assistant(with tool_calls) + tool + tool + ... with a single clean assistant message
230
+ that describes what tools were called and their results.
231
+
232
+ Args:
233
+ messages: List of messages in the conversation thread
234
+
235
+ Returns:
236
+ List[ChatCompletionMessageParam]: Cleaned message thread with tool calls formatted
237
+
238
+ Example:
239
+ ```python
240
+ messages = [
241
+ {"role": "user", "content": "What's the weather in NYC?"},
242
+ {"role": "assistant", "tool_calls": [...]},
243
+ {"role": "tool", "tool_call_id": "call_1", "content": "Sunny, 72°F"},
244
+ {"role": "user", "content": "Thanks!"}
245
+ ]
246
+
247
+ formatted = format_tool_calls(messages)
248
+ # Returns: [
249
+ # {"role": "user", "content": "What's the weather in NYC?"},
250
+ # {"role": "assistant", "content": "I called get_weather tool with parameters (city=NYC), and got result: Sunny, 72°F"},
251
+ # {"role": "user", "content": "Thanks!"}
252
+ # ]
253
+ ```
254
+ """
255
+ if not messages:
256
+ return messages
257
+
258
+ formatted_messages = []
259
+ i = 0
260
+
261
+ while i < len(messages):
262
+ current_msg = messages[i]
263
+
264
+ # Check if this is an assistant message with tool calls
265
+ if current_msg.get("role") == "assistant" and current_msg.get("tool_calls"):
266
+ # Collect all following tool messages
267
+ tool_results = {}
268
+ j = i + 1
269
+
270
+ # Gather tool results that follow this assistant message
271
+ while j < len(messages) and messages[j].get("role") == "tool":
272
+ tool_msg = messages[j]
273
+ tool_call_id = tool_msg.get("tool_call_id")
274
+ if tool_call_id:
275
+ tool_results[tool_call_id] = tool_msg.get("content", "No result")
276
+ j += 1
277
+
278
+ # Format the tool calls with their results
279
+ tool_calls = current_msg.get("tool_calls", [])
280
+ formatted_calls = []
281
+
282
+ for tool_call in tool_calls:
283
+ tool_name = tool_call.function.name
284
+ tool_args = tool_call.function.arguments
285
+ tool_id = tool_call.id
286
+
287
+ # Parse arguments for cleaner display
288
+ try:
289
+ args_dict = json.loads(tool_args) if tool_args else {}
290
+ args_str = ", ".join([f"{k}={v}" for k, v in args_dict.items()])
291
+ except json.JSONDecodeError:
292
+ args_str = tool_args or "no parameters"
293
+
294
+ # Get the result for this tool call
295
+ result = tool_results.get(tool_id, "No result available")
296
+
297
+ # Format the tool call description
298
+ call_description = f"I called {tool_name} tool with parameters ({args_str}), and got result: {result}"
299
+ formatted_calls.append(call_description)
300
+
301
+ # Create the formatted assistant message
302
+ if len(formatted_calls) == 1:
303
+ content = formatted_calls[0]
304
+ elif len(formatted_calls) > 1:
305
+ content = "I made the following tool calls:\n" + "\n".join(
306
+ [f"- {call}" for call in formatted_calls]
307
+ )
308
+ else:
309
+ content = "I made tool calls but no results were available."
310
+
311
+ # Add the formatted message
312
+ formatted_messages.append({"role": "assistant", "content": content})
313
+
314
+ # Skip past all the tool messages we processed
315
+ i = j
316
+ else:
317
+ # Regular message, add as-is
318
+ formatted_messages.append(current_msg)
319
+ i += 1
320
+
321
+ return formatted_messages
322
+
323
+
324
+ def convert_response_to_completion(response: Any) -> Completion[str]:
325
+ """Convert a LiteLLM ModelResponse to a Completion object.
326
+
327
+ This function converts LiteLLM's ModelResponse (which is based on OpenAI's
328
+ ChatCompletion format) into our unified Completion type for standard
329
+ string completions.
330
+
331
+ Args:
332
+ response: The ModelResponse from LiteLLM
333
+
334
+ Returns:
335
+ Completion[str]: Unified completion object with string output
336
+
337
+ Example:
338
+ ```python
339
+ # For LiteLLM completions
340
+ response = await litellm.acompletion(model="gpt-4", messages=messages)
341
+ completion = convert_response_to_completion(response)
342
+ ```
343
+ """
344
+ # Handle empty or invalid response
345
+ if not hasattr(response, "choices") or not response.choices:
346
+ return Completion(
347
+ output="",
348
+ model=getattr(response, "model", "unknown"),
349
+ content=None,
350
+ completion=response,
351
+ )
352
+
353
+ choice = response.choices[0]
354
+
355
+ # Extract message data
356
+ if hasattr(choice, "message"):
357
+ message = choice.message
358
+ content = getattr(message, "content", None)
359
+ tool_calls = getattr(message, "tool_calls", None)
360
+ refusal = getattr(message, "refusal", None)
361
+ else:
362
+ # Fallback for different response structures
363
+ content = None
364
+ tool_calls = None
365
+ refusal = None
366
+
367
+ return Completion(
368
+ output=content or "",
369
+ model=getattr(response, "model", "unknown"),
370
+ content=content,
371
+ tool_calls=tool_calls,
372
+ refusal=refusal,
373
+ completion=response,
374
+ )
@@ -0,0 +1,35 @@
1
+ """hammad.ai.embeddings"""
2
+
3
+ from typing import TYPE_CHECKING
4
+ from ..._core._utils._import_utils import _auto_create_getattr_loader
5
+
6
+ if TYPE_CHECKING:
7
+ from .client.base_embeddings_client import BaseEmbeddingsClient
8
+ from .client.fastembed_text_embeddings_client import FastEmbedTextEmbeddingsClient
9
+ from .client.litellm_embeddings_client import LiteLlmEmbeddingsClient
10
+ from .types import Embedding, EmbeddingResponse, EmbeddingUsage
11
+ from .create import create_embeddings, async_create_embeddings
12
+
13
+
14
+ __all__ = (
15
+ # hammad.ai.embeddings.client.base_embeddings_client
16
+ "BaseEmbeddingsClient",
17
+ # hammad.ai.embeddings.client.fastembed_text_embeddings_client
18
+ "FastEmbedTextEmbeddingsClient",
19
+ # hammad.ai.embeddings.client.litellm_embeddings_client
20
+ "LiteLlmEmbeddingsClient",
21
+ # hammad.ai.embeddings.types
22
+ "Embedding",
23
+ "EmbeddingResponse",
24
+ "EmbeddingUsage",
25
+ # hammad.ai.embeddings.create
26
+ "create_embeddings",
27
+ "async_create_embeddings",
28
+ )
29
+
30
+
31
+ __getattr__ = _auto_create_getattr_loader(__all__)
32
+
33
+
34
+ def __dir__() -> list[str]:
35
+ return list(__all__)
@@ -0,0 +1 @@
1
+ """hammad.ai.embeddings.client"""
@@ -0,0 +1,26 @@
1
+ """hammad.ai.embeddings.client.base_embeddings_client"""
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from ..types import (
6
+ EmbeddingResponse,
7
+ )
8
+
9
+ __all__ = ("BaseEmbeddingsClient",)
10
+
11
+
12
+ class BaseEmbeddingsClient(ABC):
13
+ """Base class for the various supported embeddings clients within
14
+ the `hammad.ai` extension."""
15
+
16
+ @staticmethod
17
+ @abstractmethod
18
+ def async_embed(input: list, model: str, **kwargs) -> EmbeddingResponse:
19
+ """"""
20
+ pass
21
+
22
+ @staticmethod
23
+ @abstractmethod
24
+ def embed(input: list, model: str, **kwargs) -> EmbeddingResponse:
25
+ """"""
26
+ pass
@@ -0,0 +1,200 @@
1
+ """hammad.ai.embeddings.client.fastembed_text_embeddings_client"""
2
+
3
+ from typing import Any, List, Optional, Union, Literal
4
+ import sys
5
+
6
+ if sys.version_info >= (3, 12):
7
+ from typing import TypedDict
8
+ else:
9
+ from typing_extensions import TypedDict
10
+
11
+ from .base_embeddings_client import BaseEmbeddingsClient
12
+ from ..types import (
13
+ Embedding,
14
+ EmbeddingUsage,
15
+ EmbeddingResponse,
16
+ )
17
+ from ....text.converters import convert_to_text
18
+ from ..._utils import (
19
+ get_fastembed_text_embedding_model,
20
+ )
21
+
22
+
23
+ __all__ = (
24
+ "FastEmbedTextEmbeddingsClient",
25
+ "FastEmbedTextEmbeddingModel",
26
+ "FastEmbedTextEmbeddingModelSettings",
27
+ )
28
+
29
+
30
+ FastEmbedTextEmbeddingModel = Literal[
31
+ "BAAI/bge-small-en-v1.5",
32
+ "BAAI/bge-small-zh-v1.5",
33
+ "snowflake/snowflake-arctic-embed-xs",
34
+ "sentence-transformers/all-MiniLM-L6-v2",
35
+ "jinaai/jina-embeddings-v2-small-en",
36
+ "BAAI/bge-small-en",
37
+ "snowflake/snowflake-arctic-embed-s",
38
+ "nomic-ai/nomic-embed-text-v1.5-Q",
39
+ "BAAI/bge-base-en-v1.5",
40
+ "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
41
+ "Qdrant/clip-ViT-B-32-text",
42
+ "jinaai/jina-embeddings-v2-base-de",
43
+ "BAAI/bge-base-en",
44
+ "snowflake/snowflake-arctic-embed-m",
45
+ "nomic-ai/nomic-embed-text-v1.5",
46
+ "jinaai/jina-embeddings-v2-base-en",
47
+ "nomic-ai/nomic-embed-text-v1",
48
+ "snowflake/snowflake-arctic-embed-m-long",
49
+ "mixedbread-ai/mxbai-embed-large-v1",
50
+ "jinaai/jina-embeddings-v2-base-code",
51
+ "sentence-transformers/paraphrase-multilingual-mpnet-base-v2",
52
+ "snowflake/snowflake-arctic-embed-l",
53
+ "thenlper/gte-large",
54
+ "BAAI/bge-large-en-v1.5",
55
+ "intfloat/multilingual-e5-large",
56
+ ]
57
+ """All supported text embedding models supported by `fastembed`."""
58
+
59
+
60
+ class FastEmbedTextEmbeddingModelSettings(TypedDict):
61
+ """Valid settings for the `fastembed` text embedding models."""
62
+
63
+ model: FastEmbedTextEmbeddingModel | str
64
+ parallel: Optional[int]
65
+ batch_size: Optional[int]
66
+ format: bool
67
+
68
+
69
+ class FastEmbedTextEmbeddingError(Exception):
70
+ """Exception raised when an error occurs while generating embeddings
71
+ using `fastembed` text embedding models."""
72
+
73
+ def __init__(self, message: str, response: Any):
74
+ self.message = message
75
+ self.response = response
76
+
77
+
78
+ def _parse_fastembed_response_to_embedding_response(
79
+ response: Any,
80
+ model: FastEmbedTextEmbeddingModel | str,
81
+ ) -> EmbeddingResponse:
82
+ """Parse the response from the `fastembed` text embedding model to an `EmbeddingResponse` object."""
83
+ try:
84
+ embedding_data: List[Embedding] = []
85
+
86
+ # Convert generator to list if needed
87
+ if hasattr(response, "__iter__") and not isinstance(response, (list, tuple)):
88
+ response = list(response)
89
+
90
+ for i, item in enumerate(response):
91
+ # Ensure item is a list of floats
92
+ if hasattr(item, "tolist"):
93
+ item = item.tolist()
94
+ elif not isinstance(item, list):
95
+ item = list(item)
96
+
97
+ embedding_data.append(
98
+ Embedding(embedding=item, index=i, object="embedding")
99
+ )
100
+
101
+ return EmbeddingResponse(
102
+ data=embedding_data,
103
+ model=str(model),
104
+ object="list",
105
+ usage=EmbeddingUsage(prompt_tokens=0, total_tokens=0),
106
+ )
107
+ except Exception as e:
108
+ raise FastEmbedTextEmbeddingError(
109
+ f"Failed to parse fastembed response to embedding response: {e}",
110
+ response,
111
+ )
112
+
113
+
114
+ class FastEmbedTextEmbeddingsClient(BaseEmbeddingsClient):
115
+ """Client for the `fastembed` text embedding models."""
116
+
117
+ @staticmethod
118
+ def embed(
119
+ input: List[Any] | Any,
120
+ model: FastEmbedTextEmbeddingModel | str,
121
+ parallel: Optional[int] = None,
122
+ batch_size: Optional[int] = None,
123
+ format: bool = False,
124
+ **kwargs: Any,
125
+ ) -> EmbeddingResponse:
126
+ """Generate embeddings for the given input using
127
+ a valid `fastembed` text embedding model.
128
+
129
+ Args:
130
+ input (List[Any] | Any) : The input text / content to generate embeddings for.
131
+ model (FastEmbedTextEmbeddingModel | str) : The model to use for generating embeddings.
132
+ parallel (Optional[int]) : The number of parallel processes to use for the embedding.
133
+ batch_size (Optional[int]) : The batch size to use for the embedding.
134
+ format (bool) : Whether to format each non-string input as a markdown string.
135
+ **kwargs : Any : Additional keyword arguments to pass to the `fastembed` text embedding model.
136
+
137
+ Returns:
138
+ EmbeddingResponse : The embedding response from the `fastembed` text embedding model.
139
+ """
140
+ if not isinstance(input, list):
141
+ input = [input]
142
+
143
+ if format:
144
+ for i in input:
145
+ try:
146
+ i = convert_to_text(i)
147
+ except Exception as e:
148
+ raise FastEmbedTextEmbeddingError(
149
+ f"Failed to format input to text: {e}",
150
+ i,
151
+ )
152
+
153
+ model = get_fastembed_text_embedding_model(model)
154
+
155
+ try:
156
+ response = model.embed(
157
+ documents=input,
158
+ parallel=parallel,
159
+ batch_size=batch_size,
160
+ **kwargs,
161
+ )
162
+ except Exception as e:
163
+ raise FastEmbedTextEmbeddingError(
164
+ f"Failed to generate embeddings: {e}",
165
+ input,
166
+ )
167
+
168
+ return _parse_fastembed_response_to_embedding_response(response, str(model))
169
+
170
+ @staticmethod
171
+ async def async_embed(
172
+ input: List[Any] | Any,
173
+ model: FastEmbedTextEmbeddingModel | str,
174
+ parallel: Optional[int] = None,
175
+ batch_size: Optional[int] = None,
176
+ format: bool = False,
177
+ **kwargs: Any,
178
+ ) -> EmbeddingResponse:
179
+ """Async generate embeddings for the given input using
180
+ a valid `fastembed` text embedding model.
181
+
182
+ Args:
183
+ input (List[Any] | Any) : The input text / content to generate embeddings for.
184
+ model (FastEmbedTextEmbeddingModel | str) : The model to use for generating embeddings.
185
+ parallel (Optional[int]) : The number of parallel processes to use for the embedding.
186
+ batch_size (Optional[int]) : The batch size to use for the embedding.
187
+ format (bool) : Whether to format each non-string input as a markdown string.
188
+ **kwargs : Any : Additional keyword arguments to pass to the `fastembed` text embedding model.
189
+
190
+ Returns:
191
+ EmbeddingResponse : The embedding response from the `fastembed` text embedding model.
192
+ """
193
+ return FastEmbedTextEmbeddingsClient.embed(
194
+ input=input,
195
+ model=model,
196
+ parallel=parallel,
197
+ batch_size=batch_size,
198
+ format=format,
199
+ **kwargs,
200
+ )