agno 2.0.0rc2__py3-none-any.whl → 2.3.0__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.
- agno/agent/agent.py +6009 -2874
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +385 -6
- agno/db/dynamo/dynamo.py +388 -81
- agno/db/dynamo/schemas.py +47 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +435 -64
- agno/db/firestore/schemas.py +11 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +384 -42
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +351 -66
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +339 -48
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +510 -37
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +938 -0
- agno/db/mongo/__init__.py +15 -1
- agno/db/mongo/async_mongo.py +2036 -0
- agno/db/mongo/mongo.py +653 -76
- agno/db/mongo/schemas.py +13 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/mysql.py +687 -25
- agno/db/mysql/schemas.py +61 -37
- agno/db/mysql/utils.py +60 -2
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2001 -0
- agno/db/postgres/postgres.py +676 -57
- agno/db/postgres/schemas.py +43 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +344 -38
- agno/db/redis/schemas.py +18 -0
- agno/db/redis/utils.py +60 -2
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/memory.py +13 -0
- agno/db/singlestore/schemas.py +26 -1
- agno/db/singlestore/singlestore.py +687 -53
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2371 -0
- agno/db/sqlite/schemas.py +24 -0
- agno/db/sqlite/sqlite.py +774 -85
- agno/db/sqlite/utils.py +168 -5
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +309 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1361 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +50 -22
- agno/eval/accuracy.py +50 -43
- agno/eval/performance.py +6 -3
- agno/eval/reliability.py +6 -3
- agno/eval/utils.py +33 -16
- agno/exceptions.py +68 -1
- agno/filters.py +354 -0
- agno/guardrails/__init__.py +6 -0
- agno/guardrails/base.py +19 -0
- agno/guardrails/openai.py +144 -0
- agno/guardrails/pii.py +94 -0
- agno/guardrails/prompt_injection.py +52 -0
- agno/integrations/discord/client.py +1 -0
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +1 -1
- agno/knowledge/chunking/semantic.py +40 -8
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/aws_bedrock.py +9 -4
- agno/knowledge/embedder/azure_openai.py +54 -0
- agno/knowledge/embedder/base.py +2 -0
- agno/knowledge/embedder/cohere.py +184 -5
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/google.py +79 -1
- agno/knowledge/embedder/huggingface.py +9 -4
- agno/knowledge/embedder/jina.py +63 -0
- agno/knowledge/embedder/mistral.py +78 -11
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +13 -0
- agno/knowledge/embedder/openai.py +37 -65
- agno/knowledge/embedder/sentence_transformer.py +8 -4
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/embedder/voyageai.py +69 -16
- agno/knowledge/knowledge.py +595 -187
- agno/knowledge/reader/base.py +9 -2
- agno/knowledge/reader/csv_reader.py +8 -10
- agno/knowledge/reader/docx_reader.py +5 -6
- agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
- agno/knowledge/reader/json_reader.py +6 -5
- agno/knowledge/reader/markdown_reader.py +13 -13
- agno/knowledge/reader/pdf_reader.py +43 -68
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +51 -6
- agno/knowledge/reader/s3_reader.py +3 -15
- agno/knowledge/reader/tavily_reader.py +194 -0
- agno/knowledge/reader/text_reader.py +13 -13
- agno/knowledge/reader/web_search_reader.py +2 -43
- agno/knowledge/reader/website_reader.py +43 -25
- agno/knowledge/reranker/__init__.py +3 -0
- agno/knowledge/types.py +9 -0
- agno/knowledge/utils.py +20 -0
- agno/media.py +339 -266
- agno/memory/manager.py +336 -82
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/anthropic/claude.py +183 -37
- agno/models/aws/bedrock.py +52 -112
- agno/models/aws/claude.py +33 -1
- agno/models/azure/ai_foundry.py +33 -15
- agno/models/azure/openai_chat.py +25 -8
- agno/models/base.py +1011 -566
- agno/models/cerebras/cerebras.py +19 -13
- agno/models/cerebras/cerebras_openai.py +8 -5
- agno/models/cohere/chat.py +27 -1
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/dashscope/dashscope.py +1 -0
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/google/gemini.py +110 -37
- agno/models/groq/groq.py +28 -11
- agno/models/huggingface/huggingface.py +2 -1
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/chat.py +18 -1
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/message.py +143 -4
- agno/models/meta/llama.py +27 -10
- agno/models/meta/llama_openai.py +5 -17
- agno/models/nebius/nebius.py +6 -6
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +22 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/ollama/chat.py +60 -6
- agno/models/openai/chat.py +102 -43
- agno/models/openai/responses.py +103 -106
- agno/models/openrouter/openrouter.py +41 -3
- agno/models/perplexity/perplexity.py +4 -5
- agno/models/portkey/portkey.py +3 -3
- agno/models/requesty/__init__.py +5 -0
- agno/models/requesty/requesty.py +52 -0
- agno/models/response.py +81 -5
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/siliconflow/__init__.py +5 -0
- agno/models/siliconflow/siliconflow.py +25 -0
- agno/models/together/together.py +2 -2
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +2 -2
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +96 -0
- agno/models/vllm/vllm.py +1 -0
- agno/models/xai/xai.py +3 -2
- agno/os/app.py +543 -175
- agno/os/auth.py +24 -14
- agno/os/config.py +1 -0
- agno/os/interfaces/__init__.py +1 -0
- agno/os/interfaces/a2a/__init__.py +3 -0
- agno/os/interfaces/a2a/a2a.py +42 -0
- agno/os/interfaces/a2a/router.py +250 -0
- agno/os/interfaces/a2a/utils.py +924 -0
- agno/os/interfaces/agui/agui.py +23 -7
- agno/os/interfaces/agui/router.py +27 -3
- agno/os/interfaces/agui/utils.py +242 -142
- agno/os/interfaces/base.py +6 -2
- agno/os/interfaces/slack/router.py +81 -23
- agno/os/interfaces/slack/slack.py +29 -14
- agno/os/interfaces/whatsapp/router.py +11 -4
- agno/os/interfaces/whatsapp/whatsapp.py +14 -7
- agno/os/mcp.py +111 -54
- agno/os/middleware/__init__.py +7 -0
- agno/os/middleware/jwt.py +233 -0
- agno/os/router.py +556 -139
- agno/os/routers/evals/evals.py +71 -34
- agno/os/routers/evals/schemas.py +31 -31
- agno/os/routers/evals/utils.py +6 -5
- agno/os/routers/health.py +31 -0
- agno/os/routers/home.py +52 -0
- agno/os/routers/knowledge/knowledge.py +185 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +158 -53
- agno/os/routers/memory/schemas.py +20 -16
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +499 -38
- agno/os/schema.py +308 -198
- agno/os/utils.py +401 -41
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/azure_ai_foundry.py +2 -2
- agno/reasoning/deepseek.py +2 -2
- agno/reasoning/default.py +3 -1
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/groq.py +2 -2
- agno/reasoning/ollama.py +2 -2
- agno/reasoning/openai.py +7 -2
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +266 -112
- agno/run/base.py +53 -24
- agno/run/team.py +252 -111
- agno/run/workflow.py +156 -45
- agno/session/agent.py +105 -89
- agno/session/summary.py +65 -25
- agno/session/team.py +176 -96
- agno/session/workflow.py +406 -40
- agno/team/team.py +3854 -1692
- agno/tools/brightdata.py +3 -3
- agno/tools/cartesia.py +3 -5
- agno/tools/dalle.py +9 -8
- agno/tools/decorator.py +4 -2
- agno/tools/desi_vocal.py +2 -2
- agno/tools/duckduckgo.py +15 -11
- agno/tools/e2b.py +20 -13
- agno/tools/eleven_labs.py +26 -28
- agno/tools/exa.py +21 -16
- agno/tools/fal.py +4 -4
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +350 -0
- agno/tools/firecrawl.py +4 -4
- agno/tools/function.py +257 -37
- agno/tools/giphy.py +2 -2
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +270 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- agno/tools/knowledge.py +3 -3
- agno/tools/lumalab.py +3 -3
- agno/tools/mcp/__init__.py +10 -0
- agno/tools/mcp/mcp.py +331 -0
- agno/tools/mcp/multi_mcp.py +347 -0
- agno/tools/mcp/params.py +24 -0
- agno/tools/mcp_toolbox.py +284 -0
- agno/tools/mem0.py +11 -17
- agno/tools/memori.py +1 -53
- agno/tools/memory.py +419 -0
- agno/tools/models/azure_openai.py +2 -2
- agno/tools/models/gemini.py +3 -3
- agno/tools/models/groq.py +3 -5
- agno/tools/models/nebius.py +7 -7
- agno/tools/models_labs.py +25 -15
- agno/tools/notion.py +204 -0
- agno/tools/openai.py +4 -9
- agno/tools/opencv.py +3 -3
- agno/tools/parallel.py +314 -0
- agno/tools/replicate.py +7 -7
- agno/tools/scrapegraph.py +58 -31
- agno/tools/searxng.py +2 -2
- agno/tools/serper.py +2 -2
- agno/tools/slack.py +18 -3
- agno/tools/spider.py +2 -2
- agno/tools/tavily.py +146 -0
- agno/tools/whatsapp.py +1 -1
- agno/tools/workflow.py +278 -0
- agno/tools/yfinance.py +12 -11
- agno/utils/agent.py +820 -0
- agno/utils/audio.py +27 -0
- agno/utils/common.py +90 -1
- agno/utils/events.py +222 -7
- agno/utils/gemini.py +181 -23
- agno/utils/hooks.py +57 -0
- agno/utils/http.py +111 -0
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +95 -5
- agno/utils/media.py +188 -10
- agno/utils/merge_dict.py +22 -1
- agno/utils/message.py +60 -0
- agno/utils/models/claude.py +40 -11
- agno/utils/models/cohere.py +1 -1
- agno/utils/models/watsonx.py +1 -1
- agno/utils/openai.py +1 -1
- agno/utils/print_response/agent.py +105 -21
- agno/utils/print_response/team.py +103 -38
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/reasoning.py +22 -1
- agno/utils/serialize.py +32 -0
- agno/utils/streamlit.py +16 -10
- agno/utils/string.py +41 -0
- agno/utils/team.py +98 -9
- agno/utils/tools.py +1 -1
- agno/vectordb/base.py +23 -4
- agno/vectordb/cassandra/cassandra.py +65 -9
- agno/vectordb/chroma/chromadb.py +182 -38
- agno/vectordb/clickhouse/clickhousedb.py +64 -11
- agno/vectordb/couchbase/couchbase.py +105 -10
- agno/vectordb/lancedb/lance_db.py +183 -135
- agno/vectordb/langchaindb/langchaindb.py +25 -7
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/__init__.py +3 -0
- agno/vectordb/llamaindex/llamaindexdb.py +46 -7
- agno/vectordb/milvus/milvus.py +126 -9
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +112 -7
- agno/vectordb/pgvector/pgvector.py +142 -21
- agno/vectordb/pineconedb/pineconedb.py +80 -8
- agno/vectordb/qdrant/qdrant.py +125 -39
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +694 -0
- agno/vectordb/singlestore/singlestore.py +111 -25
- agno/vectordb/surrealdb/surrealdb.py +31 -5
- agno/vectordb/upstashdb/upstashdb.py +76 -8
- agno/vectordb/weaviate/weaviate.py +86 -15
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +112 -18
- agno/workflow/loop.py +69 -10
- agno/workflow/parallel.py +266 -118
- agno/workflow/router.py +110 -17
- agno/workflow/step.py +645 -136
- agno/workflow/steps.py +65 -6
- agno/workflow/types.py +71 -33
- agno/workflow/workflow.py +2113 -300
- agno-2.3.0.dist-info/METADATA +618 -0
- agno-2.3.0.dist-info/RECORD +577 -0
- agno-2.3.0.dist-info/licenses/LICENSE +201 -0
- agno/knowledge/reader/url_reader.py +0 -128
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -610
- agno/utils/models/aws_claude.py +0 -170
- agno-2.0.0rc2.dist-info/METADATA +0 -355
- agno-2.0.0rc2.dist-info/RECORD +0 -515
- agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
- {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@ from os import getenv
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.knowledge.embedder.base import Embedder
|
|
6
|
-
from agno.utils.log import log_error, log_info
|
|
6
|
+
from agno.utils.log import log_error, log_info, log_warning
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
from google import genai
|
|
@@ -178,3 +178,81 @@ class GeminiEmbedder(Embedder):
|
|
|
178
178
|
except Exception as e:
|
|
179
179
|
log_error(f"Error extracting embeddings: {e}")
|
|
180
180
|
return [], usage
|
|
181
|
+
|
|
182
|
+
async def async_get_embeddings_batch_and_usage(
|
|
183
|
+
self, texts: List[str]
|
|
184
|
+
) -> Tuple[List[List[float]], List[Optional[Dict[str, Any]]]]:
|
|
185
|
+
"""
|
|
186
|
+
Get embeddings and usage for multiple texts in batches.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
texts: List of text strings to embed
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
193
|
+
"""
|
|
194
|
+
all_embeddings: List[List[float]] = []
|
|
195
|
+
all_usage: List[Optional[Dict[str, Any]]] = []
|
|
196
|
+
log_info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size}")
|
|
197
|
+
|
|
198
|
+
for i in range(0, len(texts), self.batch_size):
|
|
199
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
200
|
+
|
|
201
|
+
# If a user provides a model id with the `models/` prefix, we need to remove it
|
|
202
|
+
_id = self.id
|
|
203
|
+
if _id.startswith("models/"):
|
|
204
|
+
_id = _id.split("/")[-1]
|
|
205
|
+
|
|
206
|
+
_request_params: Dict[str, Any] = {"contents": batch_texts, "model": _id, "config": {}}
|
|
207
|
+
if self.dimensions:
|
|
208
|
+
_request_params["config"]["output_dimensionality"] = self.dimensions
|
|
209
|
+
if self.task_type:
|
|
210
|
+
_request_params["config"]["task_type"] = self.task_type
|
|
211
|
+
if self.title:
|
|
212
|
+
_request_params["config"]["title"] = self.title
|
|
213
|
+
if not _request_params["config"]:
|
|
214
|
+
del _request_params["config"]
|
|
215
|
+
|
|
216
|
+
if self.request_params:
|
|
217
|
+
_request_params.update(self.request_params)
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
response = await self.aclient.aio.models.embed_content(**_request_params)
|
|
221
|
+
|
|
222
|
+
# Extract embeddings from batch response
|
|
223
|
+
if response.embeddings:
|
|
224
|
+
batch_embeddings = []
|
|
225
|
+
for embedding in response.embeddings:
|
|
226
|
+
if embedding.values is not None:
|
|
227
|
+
batch_embeddings.append(embedding.values)
|
|
228
|
+
else:
|
|
229
|
+
batch_embeddings.append([])
|
|
230
|
+
all_embeddings.extend(batch_embeddings)
|
|
231
|
+
else:
|
|
232
|
+
# If no embeddings, add empty lists for each text in batch
|
|
233
|
+
all_embeddings.extend([[] for _ in batch_texts])
|
|
234
|
+
|
|
235
|
+
# Extract usage information
|
|
236
|
+
usage_dict = None
|
|
237
|
+
if response.metadata and hasattr(response.metadata, "billable_character_count"):
|
|
238
|
+
usage_dict = {"billable_character_count": response.metadata.billable_character_count}
|
|
239
|
+
|
|
240
|
+
# Add same usage info for each embedding in the batch
|
|
241
|
+
all_usage.extend([usage_dict] * len(batch_texts))
|
|
242
|
+
|
|
243
|
+
except Exception as e:
|
|
244
|
+
log_warning(f"Error in async batch embedding: {e}")
|
|
245
|
+
# Fallback to individual calls for this batch
|
|
246
|
+
for text in batch_texts:
|
|
247
|
+
try:
|
|
248
|
+
text_embedding: List[float]
|
|
249
|
+
text_usage: Optional[Dict[str, Any]]
|
|
250
|
+
text_embedding, text_usage = await self.async_get_embedding_and_usage(text)
|
|
251
|
+
all_embeddings.append(text_embedding)
|
|
252
|
+
all_usage.append(text_usage)
|
|
253
|
+
except Exception as e2:
|
|
254
|
+
log_warning(f"Error in individual async embedding fallback: {e2}")
|
|
255
|
+
all_embeddings.append([])
|
|
256
|
+
all_usage.append(None)
|
|
257
|
+
|
|
258
|
+
return all_embeddings, all_usage
|
|
@@ -3,12 +3,12 @@ from os import getenv
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.knowledge.embedder.base import Embedder
|
|
6
|
-
from agno.utils.log import
|
|
6
|
+
from agno.utils.log import log_error, log_warning
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
from huggingface_hub import AsyncInferenceClient, InferenceClient
|
|
10
10
|
except ImportError:
|
|
11
|
-
|
|
11
|
+
log_error("`huggingface-hub` not installed, please run `pip install huggingface-hub`")
|
|
12
12
|
raise
|
|
13
13
|
|
|
14
14
|
|
|
@@ -22,6 +22,11 @@ class HuggingfaceCustomEmbedder(Embedder):
|
|
|
22
22
|
huggingface_client: Optional[InferenceClient] = None
|
|
23
23
|
async_client: Optional[AsyncInferenceClient] = None
|
|
24
24
|
|
|
25
|
+
def __post_init__(self):
|
|
26
|
+
if self.enable_batch:
|
|
27
|
+
log_warning("HuggingfaceEmbedder does not support batch embeddings, setting enable_batch to False")
|
|
28
|
+
self.enable_batch = False
|
|
29
|
+
|
|
25
30
|
@property
|
|
26
31
|
def client(self) -> InferenceClient:
|
|
27
32
|
if self.huggingface_client:
|
|
@@ -61,7 +66,7 @@ class HuggingfaceCustomEmbedder(Embedder):
|
|
|
61
66
|
else:
|
|
62
67
|
return list(response)
|
|
63
68
|
except Exception as e:
|
|
64
|
-
|
|
69
|
+
log_warning(f"Failed to process embeddings: {e}")
|
|
65
70
|
return []
|
|
66
71
|
|
|
67
72
|
def get_embedding_and_usage(self, text: str) -> Tuple[List[float], Optional[Dict]]:
|
|
@@ -80,7 +85,7 @@ class HuggingfaceCustomEmbedder(Embedder):
|
|
|
80
85
|
else:
|
|
81
86
|
return list(response)
|
|
82
87
|
except Exception as e:
|
|
83
|
-
|
|
88
|
+
log_warning(f"Failed to process embeddings: {e}")
|
|
84
89
|
return []
|
|
85
90
|
|
|
86
91
|
async def async_get_embedding_and_usage(self, text: str) -> Tuple[List[float], Optional[Dict]]:
|
agno/knowledge/embedder/jina.py
CHANGED
|
@@ -117,3 +117,66 @@ class JinaEmbedder(Embedder):
|
|
|
117
117
|
except Exception as e:
|
|
118
118
|
logger.warning(f"Failed to get embedding and usage: {e}")
|
|
119
119
|
return [], None
|
|
120
|
+
|
|
121
|
+
async def _async_batch_response(self, texts: List[str]) -> Dict[str, Any]:
|
|
122
|
+
"""Async batch version of _response using aiohttp."""
|
|
123
|
+
data = {
|
|
124
|
+
"model": self.id,
|
|
125
|
+
"late_chunking": self.late_chunking,
|
|
126
|
+
"dimensions": self.dimensions,
|
|
127
|
+
"embedding_type": self.embedding_type,
|
|
128
|
+
"input": texts, # Jina API expects a list of texts for batch processing
|
|
129
|
+
}
|
|
130
|
+
if self.user is not None:
|
|
131
|
+
data["user"] = self.user
|
|
132
|
+
if self.request_params:
|
|
133
|
+
data.update(self.request_params)
|
|
134
|
+
|
|
135
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout) if self.timeout else None
|
|
136
|
+
|
|
137
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
138
|
+
async with session.post(self.base_url, headers=self._get_headers(), json=data) as response:
|
|
139
|
+
response.raise_for_status()
|
|
140
|
+
return await response.json()
|
|
141
|
+
|
|
142
|
+
async def async_get_embeddings_batch_and_usage(
|
|
143
|
+
self, texts: List[str]
|
|
144
|
+
) -> Tuple[List[List[float]], List[Optional[Dict]]]:
|
|
145
|
+
"""
|
|
146
|
+
Get embeddings and usage for multiple texts in batches.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
texts: List of text strings to embed
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
153
|
+
"""
|
|
154
|
+
all_embeddings = []
|
|
155
|
+
all_usage = []
|
|
156
|
+
logger.info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size}")
|
|
157
|
+
|
|
158
|
+
for i in range(0, len(texts), self.batch_size):
|
|
159
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
160
|
+
|
|
161
|
+
try:
|
|
162
|
+
result = await self._async_batch_response(batch_texts)
|
|
163
|
+
batch_embeddings = [data["embedding"] for data in result["data"]]
|
|
164
|
+
all_embeddings.extend(batch_embeddings)
|
|
165
|
+
|
|
166
|
+
# For each embedding in the batch, add the same usage information
|
|
167
|
+
usage_dict = result.get("usage")
|
|
168
|
+
all_usage.extend([usage_dict] * len(batch_embeddings))
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.warning(f"Error in async batch embedding: {e}")
|
|
171
|
+
# Fallback to individual calls for this batch
|
|
172
|
+
for text in batch_texts:
|
|
173
|
+
try:
|
|
174
|
+
embedding, usage = await self.async_get_embedding_and_usage(text)
|
|
175
|
+
all_embeddings.append(embedding)
|
|
176
|
+
all_usage.append(usage)
|
|
177
|
+
except Exception as e2:
|
|
178
|
+
logger.warning(f"Error in individual async embedding fallback: {e2}")
|
|
179
|
+
all_embeddings.append([])
|
|
180
|
+
all_usage.append(None)
|
|
181
|
+
|
|
182
|
+
return all_embeddings, all_usage
|
|
@@ -3,13 +3,13 @@ from os import getenv
|
|
|
3
3
|
from typing import Any, Dict, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.knowledge.embedder.base import Embedder
|
|
6
|
-
from agno.utils.log import
|
|
6
|
+
from agno.utils.log import log_error, log_info, log_warning
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
9
|
from mistralai import Mistral # type: ignore
|
|
10
10
|
from mistralai.models.embeddingresponse import EmbeddingResponse # type: ignore
|
|
11
11
|
except ImportError:
|
|
12
|
-
|
|
12
|
+
log_error("`mistralai` not installed")
|
|
13
13
|
raise
|
|
14
14
|
|
|
15
15
|
|
|
@@ -50,7 +50,7 @@ class MistralEmbedder(Embedder):
|
|
|
50
50
|
|
|
51
51
|
def _response(self, text: str) -> EmbeddingResponse:
|
|
52
52
|
_request_params: Dict[str, Any] = {
|
|
53
|
-
"inputs": text,
|
|
53
|
+
"inputs": [text], # Mistral API expects a list
|
|
54
54
|
"model": self.id,
|
|
55
55
|
}
|
|
56
56
|
if self.request_params:
|
|
@@ -67,7 +67,7 @@ class MistralEmbedder(Embedder):
|
|
|
67
67
|
return response.data[0].embedding
|
|
68
68
|
return []
|
|
69
69
|
except Exception as e:
|
|
70
|
-
|
|
70
|
+
log_warning(f"Error getting embedding: {e}")
|
|
71
71
|
return []
|
|
72
72
|
|
|
73
73
|
def get_embedding_and_usage(self, text: str) -> Tuple[List[float], Dict[str, Any]]:
|
|
@@ -79,7 +79,7 @@ class MistralEmbedder(Embedder):
|
|
|
79
79
|
usage: Dict[str, Any] = response.usage.model_dump() if response.usage else {}
|
|
80
80
|
return embedding, usage
|
|
81
81
|
except Exception as e:
|
|
82
|
-
|
|
82
|
+
log_warning(f"Error getting embedding and usage: {e}")
|
|
83
83
|
return [], {}
|
|
84
84
|
|
|
85
85
|
async def async_get_embedding(self, text: str) -> List[float]:
|
|
@@ -88,7 +88,7 @@ class MistralEmbedder(Embedder):
|
|
|
88
88
|
# Check if the client has an async version of embeddings.create
|
|
89
89
|
if hasattr(self.client.embeddings, "create_async"):
|
|
90
90
|
response: EmbeddingResponse = await self.client.embeddings.create_async(
|
|
91
|
-
inputs=text, model=self.id, **self.request_params if self.request_params else {}
|
|
91
|
+
inputs=[text], model=self.id, **self.request_params if self.request_params else {}
|
|
92
92
|
)
|
|
93
93
|
else:
|
|
94
94
|
# Fallback to running sync method in thread executor
|
|
@@ -98,7 +98,7 @@ class MistralEmbedder(Embedder):
|
|
|
98
98
|
response: EmbeddingResponse = await loop.run_in_executor( # type: ignore
|
|
99
99
|
None,
|
|
100
100
|
lambda: self.client.embeddings.create(
|
|
101
|
-
inputs=text, model=self.id, **self.request_params if self.request_params else {}
|
|
101
|
+
inputs=[text], model=self.id, **self.request_params if self.request_params else {}
|
|
102
102
|
),
|
|
103
103
|
)
|
|
104
104
|
|
|
@@ -106,7 +106,7 @@ class MistralEmbedder(Embedder):
|
|
|
106
106
|
return response.data[0].embedding
|
|
107
107
|
return []
|
|
108
108
|
except Exception as e:
|
|
109
|
-
|
|
109
|
+
log_warning(f"Error getting embedding: {e}")
|
|
110
110
|
return []
|
|
111
111
|
|
|
112
112
|
async def async_get_embedding_and_usage(self, text: str) -> Tuple[List[float], Dict[str, Any]]:
|
|
@@ -115,7 +115,7 @@ class MistralEmbedder(Embedder):
|
|
|
115
115
|
# Check if the client has an async version of embeddings.create
|
|
116
116
|
if hasattr(self.client.embeddings, "create_async"):
|
|
117
117
|
response: EmbeddingResponse = await self.client.embeddings.create_async(
|
|
118
|
-
inputs=text, model=self.id, **self.request_params if self.request_params else {}
|
|
118
|
+
inputs=[text], model=self.id, **self.request_params if self.request_params else {}
|
|
119
119
|
)
|
|
120
120
|
else:
|
|
121
121
|
# Fallback to running sync method in thread executor
|
|
@@ -125,7 +125,7 @@ class MistralEmbedder(Embedder):
|
|
|
125
125
|
response: EmbeddingResponse = await loop.run_in_executor( # type: ignore
|
|
126
126
|
None,
|
|
127
127
|
lambda: self.client.embeddings.create(
|
|
128
|
-
inputs=text, model=self.id, **self.request_params if self.request_params else {}
|
|
128
|
+
inputs=[text], model=self.id, **self.request_params if self.request_params else {}
|
|
129
129
|
),
|
|
130
130
|
)
|
|
131
131
|
|
|
@@ -135,5 +135,72 @@ class MistralEmbedder(Embedder):
|
|
|
135
135
|
usage: Dict[str, Any] = response.usage.model_dump() if response.usage else {}
|
|
136
136
|
return embedding, usage
|
|
137
137
|
except Exception as e:
|
|
138
|
-
|
|
138
|
+
log_warning(f"Error getting embedding and usage: {e}")
|
|
139
139
|
return [], {}
|
|
140
|
+
|
|
141
|
+
async def async_get_embeddings_batch_and_usage(
|
|
142
|
+
self, texts: List[str]
|
|
143
|
+
) -> Tuple[List[List[float]], List[Optional[Dict[str, Any]]]]:
|
|
144
|
+
"""
|
|
145
|
+
Get embeddings and usage for multiple texts in batches.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
texts: List of text strings to embed
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
152
|
+
"""
|
|
153
|
+
all_embeddings = []
|
|
154
|
+
all_usage = []
|
|
155
|
+
log_info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size}")
|
|
156
|
+
|
|
157
|
+
for i in range(0, len(texts), self.batch_size):
|
|
158
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
159
|
+
|
|
160
|
+
_request_params: Dict[str, Any] = {
|
|
161
|
+
"inputs": batch_texts, # Mistral API expects a list for batch processing
|
|
162
|
+
"model": self.id,
|
|
163
|
+
}
|
|
164
|
+
if self.request_params:
|
|
165
|
+
_request_params.update(self.request_params)
|
|
166
|
+
|
|
167
|
+
try:
|
|
168
|
+
# Check if the client has an async version of embeddings.create
|
|
169
|
+
if hasattr(self.client.embeddings, "create_async"):
|
|
170
|
+
response: EmbeddingResponse = await self.client.embeddings.create_async(**_request_params)
|
|
171
|
+
else:
|
|
172
|
+
# Fallback to running sync method in thread executor
|
|
173
|
+
import asyncio
|
|
174
|
+
|
|
175
|
+
loop = asyncio.get_running_loop()
|
|
176
|
+
response: EmbeddingResponse = await loop.run_in_executor( # type: ignore
|
|
177
|
+
None, lambda: self.client.embeddings.create(**_request_params)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
# Extract embeddings from batch response
|
|
181
|
+
if response.data:
|
|
182
|
+
batch_embeddings = [data.embedding for data in response.data if data.embedding]
|
|
183
|
+
all_embeddings.extend(batch_embeddings)
|
|
184
|
+
else:
|
|
185
|
+
# If no embeddings, add empty lists for each text in batch
|
|
186
|
+
all_embeddings.extend([[] for _ in batch_texts])
|
|
187
|
+
|
|
188
|
+
# Extract usage information
|
|
189
|
+
usage_dict = response.usage.model_dump() if response.usage else None
|
|
190
|
+
# Add same usage info for each embedding in the batch
|
|
191
|
+
all_usage.extend([usage_dict] * len(batch_texts))
|
|
192
|
+
|
|
193
|
+
except Exception as e:
|
|
194
|
+
log_warning(f"Error in async batch embedding: {e}")
|
|
195
|
+
# Fallback to individual calls for this batch
|
|
196
|
+
for text in batch_texts:
|
|
197
|
+
try:
|
|
198
|
+
embedding, usage = await self.async_get_embedding_and_usage(text)
|
|
199
|
+
all_embeddings.append(embedding)
|
|
200
|
+
all_usage.append(usage)
|
|
201
|
+
except Exception as e2:
|
|
202
|
+
log_warning(f"Error in individual async embedding fallback: {e2}")
|
|
203
|
+
all_embeddings.append([])
|
|
204
|
+
all_usage.append(None)
|
|
205
|
+
|
|
206
|
+
return all_embeddings, all_usage
|
|
@@ -45,6 +45,11 @@ class OllamaEmbedder(Embedder):
|
|
|
45
45
|
ollama_client: Optional[OllamaClient] = None
|
|
46
46
|
async_client: Optional[AsyncOllamaClient] = None
|
|
47
47
|
|
|
48
|
+
def __post_init__(self):
|
|
49
|
+
if self.enable_batch:
|
|
50
|
+
logger.warning("OllamaEmbedder does not support batch embeddings, setting enable_batch to False")
|
|
51
|
+
self.enable_batch = False
|
|
52
|
+
|
|
48
53
|
@property
|
|
49
54
|
def client(self) -> OllamaClient:
|
|
50
55
|
if self.ollama_client:
|
|
@@ -80,6 +85,10 @@ class OllamaEmbedder(Embedder):
|
|
|
80
85
|
if self.options is not None:
|
|
81
86
|
kwargs["options"] = self.options
|
|
82
87
|
|
|
88
|
+
# Add dimensions parameter for models that support it
|
|
89
|
+
if self.dimensions is not None:
|
|
90
|
+
kwargs["dimensions"] = self.dimensions
|
|
91
|
+
|
|
83
92
|
response = self.client.embed(input=text, model=self.id, **kwargs)
|
|
84
93
|
if response and "embeddings" in response:
|
|
85
94
|
embeddings = response["embeddings"]
|
|
@@ -112,6 +121,10 @@ class OllamaEmbedder(Embedder):
|
|
|
112
121
|
if self.options is not None:
|
|
113
122
|
kwargs["options"] = self.options
|
|
114
123
|
|
|
124
|
+
# Add dimensions parameter for models that support it
|
|
125
|
+
if self.dimensions is not None:
|
|
126
|
+
kwargs["dimensions"] = self.dimensions
|
|
127
|
+
|
|
115
128
|
response = await self.aclient.embed(input=text, model=self.id, **kwargs)
|
|
116
129
|
if response and "embeddings" in response:
|
|
117
130
|
embeddings = response["embeddings"]
|
|
@@ -78,21 +78,25 @@ class OpenAIEmbedder(Embedder):
|
|
|
78
78
|
return self.client.embeddings.create(**_request_params)
|
|
79
79
|
|
|
80
80
|
def get_embedding(self, text: str) -> List[float]:
|
|
81
|
-
response: CreateEmbeddingResponse = self.response(text=text)
|
|
82
81
|
try:
|
|
82
|
+
response: CreateEmbeddingResponse = self.response(text=text)
|
|
83
83
|
return response.data[0].embedding
|
|
84
84
|
except Exception as e:
|
|
85
85
|
logger.warning(e)
|
|
86
86
|
return []
|
|
87
87
|
|
|
88
88
|
def get_embedding_and_usage(self, text: str) -> Tuple[List[float], Optional[Dict]]:
|
|
89
|
-
|
|
89
|
+
try:
|
|
90
|
+
response: CreateEmbeddingResponse = self.response(text=text)
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
embedding = response.data[0].embedding
|
|
93
|
+
usage = response.usage
|
|
94
|
+
if usage:
|
|
95
|
+
return embedding, usage.model_dump()
|
|
96
|
+
return embedding, None
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.warning(e)
|
|
99
|
+
return [], None
|
|
96
100
|
|
|
97
101
|
async def async_get_embedding(self, text: str) -> List[float]:
|
|
98
102
|
req: Dict[str, Any] = {
|
|
@@ -127,71 +131,33 @@ class OpenAIEmbedder(Embedder):
|
|
|
127
131
|
if self.request_params:
|
|
128
132
|
req.update(self.request_params)
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
Args:
|
|
140
|
-
texts: List of text strings to embed
|
|
141
|
-
batch_size: Number of texts to process in each API call (max ~2048)
|
|
142
|
-
|
|
143
|
-
Returns:
|
|
144
|
-
List of embedding vectors
|
|
145
|
-
"""
|
|
146
|
-
all_embeddings = []
|
|
147
|
-
|
|
148
|
-
for i in range(0, len(texts), batch_size):
|
|
149
|
-
batch_texts = texts[i : i + batch_size]
|
|
150
|
-
|
|
151
|
-
req: Dict[str, Any] = {
|
|
152
|
-
"input": batch_texts,
|
|
153
|
-
"model": self.id,
|
|
154
|
-
"encoding_format": self.encoding_format,
|
|
155
|
-
}
|
|
156
|
-
if self.user is not None:
|
|
157
|
-
req["user"] = self.user
|
|
158
|
-
if self.id.startswith("text-embedding-3"):
|
|
159
|
-
req["dimensions"] = self.dimensions
|
|
160
|
-
if self.request_params:
|
|
161
|
-
req.update(self.request_params)
|
|
162
|
-
|
|
163
|
-
try:
|
|
164
|
-
response: CreateEmbeddingResponse = self.client.embeddings.create(**req)
|
|
165
|
-
batch_embeddings = [data.embedding for data in response.data]
|
|
166
|
-
all_embeddings.extend(batch_embeddings)
|
|
167
|
-
except Exception as e:
|
|
168
|
-
logger.warning(f"Error in batch embedding: {e}")
|
|
169
|
-
# Fallback to individual calls for this batch
|
|
170
|
-
for text in batch_texts:
|
|
171
|
-
try:
|
|
172
|
-
embedding = self.get_embedding(text)
|
|
173
|
-
all_embeddings.append(embedding)
|
|
174
|
-
except Exception as e2:
|
|
175
|
-
logger.warning(f"Error in individual embedding fallback: {e2}")
|
|
176
|
-
all_embeddings.append([])
|
|
177
|
-
|
|
178
|
-
return all_embeddings
|
|
134
|
+
try:
|
|
135
|
+
response = await self.aclient.embeddings.create(**req)
|
|
136
|
+
embedding = response.data[0].embedding
|
|
137
|
+
usage = response.usage
|
|
138
|
+
return embedding, usage.model_dump() if usage else None
|
|
139
|
+
except Exception as e:
|
|
140
|
+
logger.warning(e)
|
|
141
|
+
return [], None
|
|
179
142
|
|
|
180
|
-
async def
|
|
143
|
+
async def async_get_embeddings_batch_and_usage(
|
|
144
|
+
self, texts: List[str]
|
|
145
|
+
) -> Tuple[List[List[float]], List[Optional[Dict]]]:
|
|
181
146
|
"""
|
|
182
|
-
Get embeddings for multiple texts in batches (async version).
|
|
147
|
+
Get embeddings and usage for multiple texts in batches (async version).
|
|
183
148
|
|
|
184
149
|
Args:
|
|
185
150
|
texts: List of text strings to embed
|
|
186
|
-
batch_size: Number of texts to process in each API call (max ~2048)
|
|
187
151
|
|
|
188
152
|
Returns:
|
|
189
|
-
List of embedding vectors
|
|
153
|
+
Tuple of (List of embedding vectors, List of usage dictionaries)
|
|
190
154
|
"""
|
|
191
155
|
all_embeddings = []
|
|
156
|
+
all_usage = []
|
|
157
|
+
logger.info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size} (async)")
|
|
192
158
|
|
|
193
|
-
for i in range(0, len(texts), batch_size):
|
|
194
|
-
batch_texts = texts[i : i + batch_size]
|
|
159
|
+
for i in range(0, len(texts), self.batch_size):
|
|
160
|
+
batch_texts = texts[i : i + self.batch_size]
|
|
195
161
|
|
|
196
162
|
req: Dict[str, Any] = {
|
|
197
163
|
"input": batch_texts,
|
|
@@ -209,15 +175,21 @@ class OpenAIEmbedder(Embedder):
|
|
|
209
175
|
response: CreateEmbeddingResponse = await self.aclient.embeddings.create(**req)
|
|
210
176
|
batch_embeddings = [data.embedding for data in response.data]
|
|
211
177
|
all_embeddings.extend(batch_embeddings)
|
|
178
|
+
|
|
179
|
+
# For each embedding in the batch, add the same usage information
|
|
180
|
+
usage_dict = response.usage.model_dump() if response.usage else None
|
|
181
|
+
all_usage.extend([usage_dict] * len(batch_embeddings))
|
|
212
182
|
except Exception as e:
|
|
213
183
|
logger.warning(f"Error in async batch embedding: {e}")
|
|
214
|
-
# Fallback to individual
|
|
184
|
+
# Fallback to individual calls for this batch
|
|
215
185
|
for text in batch_texts:
|
|
216
186
|
try:
|
|
217
|
-
embedding = await self.
|
|
187
|
+
embedding, usage = await self.async_get_embedding_and_usage(text)
|
|
218
188
|
all_embeddings.append(embedding)
|
|
189
|
+
all_usage.append(usage)
|
|
219
190
|
except Exception as e2:
|
|
220
191
|
logger.warning(f"Error in individual async embedding fallback: {e2}")
|
|
221
192
|
all_embeddings.append([])
|
|
193
|
+
all_usage.append(None)
|
|
222
194
|
|
|
223
|
-
return all_embeddings
|
|
195
|
+
return all_embeddings, all_usage
|
|
@@ -25,11 +25,15 @@ class SentenceTransformerEmbedder(Embedder):
|
|
|
25
25
|
prompt: Optional[str] = None
|
|
26
26
|
normalize_embeddings: bool = False
|
|
27
27
|
|
|
28
|
+
def __post_init__(self):
|
|
29
|
+
# Initialize the SentenceTransformer model eagerly to avoid race conditions in async contexts
|
|
30
|
+
if self.sentence_transformer_client is None:
|
|
31
|
+
self.sentence_transformer_client = SentenceTransformer(model_name_or_path=self.id)
|
|
32
|
+
|
|
28
33
|
def get_embedding(self, text: Union[str, List[str]]) -> List[float]:
|
|
29
|
-
if
|
|
30
|
-
model
|
|
31
|
-
|
|
32
|
-
model = self.sentence_transformer_client
|
|
34
|
+
if self.sentence_transformer_client is None:
|
|
35
|
+
raise RuntimeError("SentenceTransformer model not initialized")
|
|
36
|
+
model = self.sentence_transformer_client
|
|
33
37
|
embedding = model.encode(text, prompt=self.prompt, normalize_embeddings=self.normalize_embeddings)
|
|
34
38
|
try:
|
|
35
39
|
if isinstance(embedding, np.ndarray):
|