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.
Files changed (331) hide show
  1. agno/agent/agent.py +6009 -2874
  2. agno/api/api.py +2 -0
  3. agno/api/os.py +1 -1
  4. agno/culture/__init__.py +3 -0
  5. agno/culture/manager.py +956 -0
  6. agno/db/async_postgres/__init__.py +3 -0
  7. agno/db/base.py +385 -6
  8. agno/db/dynamo/dynamo.py +388 -81
  9. agno/db/dynamo/schemas.py +47 -10
  10. agno/db/dynamo/utils.py +63 -4
  11. agno/db/firestore/firestore.py +435 -64
  12. agno/db/firestore/schemas.py +11 -0
  13. agno/db/firestore/utils.py +102 -4
  14. agno/db/gcs_json/gcs_json_db.py +384 -42
  15. agno/db/gcs_json/utils.py +60 -26
  16. agno/db/in_memory/in_memory_db.py +351 -66
  17. agno/db/in_memory/utils.py +60 -2
  18. agno/db/json/json_db.py +339 -48
  19. agno/db/json/utils.py +60 -26
  20. agno/db/migrations/manager.py +199 -0
  21. agno/db/migrations/v1_to_v2.py +510 -37
  22. agno/db/migrations/versions/__init__.py +0 -0
  23. agno/db/migrations/versions/v2_3_0.py +938 -0
  24. agno/db/mongo/__init__.py +15 -1
  25. agno/db/mongo/async_mongo.py +2036 -0
  26. agno/db/mongo/mongo.py +653 -76
  27. agno/db/mongo/schemas.py +13 -0
  28. agno/db/mongo/utils.py +80 -8
  29. agno/db/mysql/mysql.py +687 -25
  30. agno/db/mysql/schemas.py +61 -37
  31. agno/db/mysql/utils.py +60 -2
  32. agno/db/postgres/__init__.py +2 -1
  33. agno/db/postgres/async_postgres.py +2001 -0
  34. agno/db/postgres/postgres.py +676 -57
  35. agno/db/postgres/schemas.py +43 -18
  36. agno/db/postgres/utils.py +164 -2
  37. agno/db/redis/redis.py +344 -38
  38. agno/db/redis/schemas.py +18 -0
  39. agno/db/redis/utils.py +60 -2
  40. agno/db/schemas/__init__.py +2 -1
  41. agno/db/schemas/culture.py +120 -0
  42. agno/db/schemas/memory.py +13 -0
  43. agno/db/singlestore/schemas.py +26 -1
  44. agno/db/singlestore/singlestore.py +687 -53
  45. agno/db/singlestore/utils.py +60 -2
  46. agno/db/sqlite/__init__.py +2 -1
  47. agno/db/sqlite/async_sqlite.py +2371 -0
  48. agno/db/sqlite/schemas.py +24 -0
  49. agno/db/sqlite/sqlite.py +774 -85
  50. agno/db/sqlite/utils.py +168 -5
  51. agno/db/surrealdb/__init__.py +3 -0
  52. agno/db/surrealdb/metrics.py +292 -0
  53. agno/db/surrealdb/models.py +309 -0
  54. agno/db/surrealdb/queries.py +71 -0
  55. agno/db/surrealdb/surrealdb.py +1361 -0
  56. agno/db/surrealdb/utils.py +147 -0
  57. agno/db/utils.py +50 -22
  58. agno/eval/accuracy.py +50 -43
  59. agno/eval/performance.py +6 -3
  60. agno/eval/reliability.py +6 -3
  61. agno/eval/utils.py +33 -16
  62. agno/exceptions.py +68 -1
  63. agno/filters.py +354 -0
  64. agno/guardrails/__init__.py +6 -0
  65. agno/guardrails/base.py +19 -0
  66. agno/guardrails/openai.py +144 -0
  67. agno/guardrails/pii.py +94 -0
  68. agno/guardrails/prompt_injection.py +52 -0
  69. agno/integrations/discord/client.py +1 -0
  70. agno/knowledge/chunking/agentic.py +13 -10
  71. agno/knowledge/chunking/fixed.py +1 -1
  72. agno/knowledge/chunking/semantic.py +40 -8
  73. agno/knowledge/chunking/strategy.py +59 -15
  74. agno/knowledge/embedder/aws_bedrock.py +9 -4
  75. agno/knowledge/embedder/azure_openai.py +54 -0
  76. agno/knowledge/embedder/base.py +2 -0
  77. agno/knowledge/embedder/cohere.py +184 -5
  78. agno/knowledge/embedder/fastembed.py +1 -1
  79. agno/knowledge/embedder/google.py +79 -1
  80. agno/knowledge/embedder/huggingface.py +9 -4
  81. agno/knowledge/embedder/jina.py +63 -0
  82. agno/knowledge/embedder/mistral.py +78 -11
  83. agno/knowledge/embedder/nebius.py +1 -1
  84. agno/knowledge/embedder/ollama.py +13 -0
  85. agno/knowledge/embedder/openai.py +37 -65
  86. agno/knowledge/embedder/sentence_transformer.py +8 -4
  87. agno/knowledge/embedder/vllm.py +262 -0
  88. agno/knowledge/embedder/voyageai.py +69 -16
  89. agno/knowledge/knowledge.py +595 -187
  90. agno/knowledge/reader/base.py +9 -2
  91. agno/knowledge/reader/csv_reader.py +8 -10
  92. agno/knowledge/reader/docx_reader.py +5 -6
  93. agno/knowledge/reader/field_labeled_csv_reader.py +290 -0
  94. agno/knowledge/reader/json_reader.py +6 -5
  95. agno/knowledge/reader/markdown_reader.py +13 -13
  96. agno/knowledge/reader/pdf_reader.py +43 -68
  97. agno/knowledge/reader/pptx_reader.py +101 -0
  98. agno/knowledge/reader/reader_factory.py +51 -6
  99. agno/knowledge/reader/s3_reader.py +3 -15
  100. agno/knowledge/reader/tavily_reader.py +194 -0
  101. agno/knowledge/reader/text_reader.py +13 -13
  102. agno/knowledge/reader/web_search_reader.py +2 -43
  103. agno/knowledge/reader/website_reader.py +43 -25
  104. agno/knowledge/reranker/__init__.py +3 -0
  105. agno/knowledge/types.py +9 -0
  106. agno/knowledge/utils.py +20 -0
  107. agno/media.py +339 -266
  108. agno/memory/manager.py +336 -82
  109. agno/models/aimlapi/aimlapi.py +2 -2
  110. agno/models/anthropic/claude.py +183 -37
  111. agno/models/aws/bedrock.py +52 -112
  112. agno/models/aws/claude.py +33 -1
  113. agno/models/azure/ai_foundry.py +33 -15
  114. agno/models/azure/openai_chat.py +25 -8
  115. agno/models/base.py +1011 -566
  116. agno/models/cerebras/cerebras.py +19 -13
  117. agno/models/cerebras/cerebras_openai.py +8 -5
  118. agno/models/cohere/chat.py +27 -1
  119. agno/models/cometapi/__init__.py +5 -0
  120. agno/models/cometapi/cometapi.py +57 -0
  121. agno/models/dashscope/dashscope.py +1 -0
  122. agno/models/deepinfra/deepinfra.py +2 -2
  123. agno/models/deepseek/deepseek.py +2 -2
  124. agno/models/fireworks/fireworks.py +2 -2
  125. agno/models/google/gemini.py +110 -37
  126. agno/models/groq/groq.py +28 -11
  127. agno/models/huggingface/huggingface.py +2 -1
  128. agno/models/internlm/internlm.py +2 -2
  129. agno/models/langdb/langdb.py +4 -4
  130. agno/models/litellm/chat.py +18 -1
  131. agno/models/litellm/litellm_openai.py +2 -2
  132. agno/models/llama_cpp/__init__.py +5 -0
  133. agno/models/llama_cpp/llama_cpp.py +22 -0
  134. agno/models/message.py +143 -4
  135. agno/models/meta/llama.py +27 -10
  136. agno/models/meta/llama_openai.py +5 -17
  137. agno/models/nebius/nebius.py +6 -6
  138. agno/models/nexus/__init__.py +3 -0
  139. agno/models/nexus/nexus.py +22 -0
  140. agno/models/nvidia/nvidia.py +2 -2
  141. agno/models/ollama/chat.py +60 -6
  142. agno/models/openai/chat.py +102 -43
  143. agno/models/openai/responses.py +103 -106
  144. agno/models/openrouter/openrouter.py +41 -3
  145. agno/models/perplexity/perplexity.py +4 -5
  146. agno/models/portkey/portkey.py +3 -3
  147. agno/models/requesty/__init__.py +5 -0
  148. agno/models/requesty/requesty.py +52 -0
  149. agno/models/response.py +81 -5
  150. agno/models/sambanova/sambanova.py +2 -2
  151. agno/models/siliconflow/__init__.py +5 -0
  152. agno/models/siliconflow/siliconflow.py +25 -0
  153. agno/models/together/together.py +2 -2
  154. agno/models/utils.py +254 -8
  155. agno/models/vercel/v0.py +2 -2
  156. agno/models/vertexai/__init__.py +0 -0
  157. agno/models/vertexai/claude.py +96 -0
  158. agno/models/vllm/vllm.py +1 -0
  159. agno/models/xai/xai.py +3 -2
  160. agno/os/app.py +543 -175
  161. agno/os/auth.py +24 -14
  162. agno/os/config.py +1 -0
  163. agno/os/interfaces/__init__.py +1 -0
  164. agno/os/interfaces/a2a/__init__.py +3 -0
  165. agno/os/interfaces/a2a/a2a.py +42 -0
  166. agno/os/interfaces/a2a/router.py +250 -0
  167. agno/os/interfaces/a2a/utils.py +924 -0
  168. agno/os/interfaces/agui/agui.py +23 -7
  169. agno/os/interfaces/agui/router.py +27 -3
  170. agno/os/interfaces/agui/utils.py +242 -142
  171. agno/os/interfaces/base.py +6 -2
  172. agno/os/interfaces/slack/router.py +81 -23
  173. agno/os/interfaces/slack/slack.py +29 -14
  174. agno/os/interfaces/whatsapp/router.py +11 -4
  175. agno/os/interfaces/whatsapp/whatsapp.py +14 -7
  176. agno/os/mcp.py +111 -54
  177. agno/os/middleware/__init__.py +7 -0
  178. agno/os/middleware/jwt.py +233 -0
  179. agno/os/router.py +556 -139
  180. agno/os/routers/evals/evals.py +71 -34
  181. agno/os/routers/evals/schemas.py +31 -31
  182. agno/os/routers/evals/utils.py +6 -5
  183. agno/os/routers/health.py +31 -0
  184. agno/os/routers/home.py +52 -0
  185. agno/os/routers/knowledge/knowledge.py +185 -38
  186. agno/os/routers/knowledge/schemas.py +82 -22
  187. agno/os/routers/memory/memory.py +158 -53
  188. agno/os/routers/memory/schemas.py +20 -16
  189. agno/os/routers/metrics/metrics.py +20 -8
  190. agno/os/routers/metrics/schemas.py +16 -16
  191. agno/os/routers/session/session.py +499 -38
  192. agno/os/schema.py +308 -198
  193. agno/os/utils.py +401 -41
  194. agno/reasoning/anthropic.py +80 -0
  195. agno/reasoning/azure_ai_foundry.py +2 -2
  196. agno/reasoning/deepseek.py +2 -2
  197. agno/reasoning/default.py +3 -1
  198. agno/reasoning/gemini.py +73 -0
  199. agno/reasoning/groq.py +2 -2
  200. agno/reasoning/ollama.py +2 -2
  201. agno/reasoning/openai.py +7 -2
  202. agno/reasoning/vertexai.py +76 -0
  203. agno/run/__init__.py +6 -0
  204. agno/run/agent.py +266 -112
  205. agno/run/base.py +53 -24
  206. agno/run/team.py +252 -111
  207. agno/run/workflow.py +156 -45
  208. agno/session/agent.py +105 -89
  209. agno/session/summary.py +65 -25
  210. agno/session/team.py +176 -96
  211. agno/session/workflow.py +406 -40
  212. agno/team/team.py +3854 -1692
  213. agno/tools/brightdata.py +3 -3
  214. agno/tools/cartesia.py +3 -5
  215. agno/tools/dalle.py +9 -8
  216. agno/tools/decorator.py +4 -2
  217. agno/tools/desi_vocal.py +2 -2
  218. agno/tools/duckduckgo.py +15 -11
  219. agno/tools/e2b.py +20 -13
  220. agno/tools/eleven_labs.py +26 -28
  221. agno/tools/exa.py +21 -16
  222. agno/tools/fal.py +4 -4
  223. agno/tools/file.py +153 -23
  224. agno/tools/file_generation.py +350 -0
  225. agno/tools/firecrawl.py +4 -4
  226. agno/tools/function.py +257 -37
  227. agno/tools/giphy.py +2 -2
  228. agno/tools/gmail.py +238 -14
  229. agno/tools/google_drive.py +270 -0
  230. agno/tools/googlecalendar.py +36 -8
  231. agno/tools/googlesheets.py +20 -5
  232. agno/tools/jira.py +20 -0
  233. agno/tools/knowledge.py +3 -3
  234. agno/tools/lumalab.py +3 -3
  235. agno/tools/mcp/__init__.py +10 -0
  236. agno/tools/mcp/mcp.py +331 -0
  237. agno/tools/mcp/multi_mcp.py +347 -0
  238. agno/tools/mcp/params.py +24 -0
  239. agno/tools/mcp_toolbox.py +284 -0
  240. agno/tools/mem0.py +11 -17
  241. agno/tools/memori.py +1 -53
  242. agno/tools/memory.py +419 -0
  243. agno/tools/models/azure_openai.py +2 -2
  244. agno/tools/models/gemini.py +3 -3
  245. agno/tools/models/groq.py +3 -5
  246. agno/tools/models/nebius.py +7 -7
  247. agno/tools/models_labs.py +25 -15
  248. agno/tools/notion.py +204 -0
  249. agno/tools/openai.py +4 -9
  250. agno/tools/opencv.py +3 -3
  251. agno/tools/parallel.py +314 -0
  252. agno/tools/replicate.py +7 -7
  253. agno/tools/scrapegraph.py +58 -31
  254. agno/tools/searxng.py +2 -2
  255. agno/tools/serper.py +2 -2
  256. agno/tools/slack.py +18 -3
  257. agno/tools/spider.py +2 -2
  258. agno/tools/tavily.py +146 -0
  259. agno/tools/whatsapp.py +1 -1
  260. agno/tools/workflow.py +278 -0
  261. agno/tools/yfinance.py +12 -11
  262. agno/utils/agent.py +820 -0
  263. agno/utils/audio.py +27 -0
  264. agno/utils/common.py +90 -1
  265. agno/utils/events.py +222 -7
  266. agno/utils/gemini.py +181 -23
  267. agno/utils/hooks.py +57 -0
  268. agno/utils/http.py +111 -0
  269. agno/utils/knowledge.py +12 -5
  270. agno/utils/log.py +1 -0
  271. agno/utils/mcp.py +95 -5
  272. agno/utils/media.py +188 -10
  273. agno/utils/merge_dict.py +22 -1
  274. agno/utils/message.py +60 -0
  275. agno/utils/models/claude.py +40 -11
  276. agno/utils/models/cohere.py +1 -1
  277. agno/utils/models/watsonx.py +1 -1
  278. agno/utils/openai.py +1 -1
  279. agno/utils/print_response/agent.py +105 -21
  280. agno/utils/print_response/team.py +103 -38
  281. agno/utils/print_response/workflow.py +251 -34
  282. agno/utils/reasoning.py +22 -1
  283. agno/utils/serialize.py +32 -0
  284. agno/utils/streamlit.py +16 -10
  285. agno/utils/string.py +41 -0
  286. agno/utils/team.py +98 -9
  287. agno/utils/tools.py +1 -1
  288. agno/vectordb/base.py +23 -4
  289. agno/vectordb/cassandra/cassandra.py +65 -9
  290. agno/vectordb/chroma/chromadb.py +182 -38
  291. agno/vectordb/clickhouse/clickhousedb.py +64 -11
  292. agno/vectordb/couchbase/couchbase.py +105 -10
  293. agno/vectordb/lancedb/lance_db.py +183 -135
  294. agno/vectordb/langchaindb/langchaindb.py +25 -7
  295. agno/vectordb/lightrag/lightrag.py +17 -3
  296. agno/vectordb/llamaindex/__init__.py +3 -0
  297. agno/vectordb/llamaindex/llamaindexdb.py +46 -7
  298. agno/vectordb/milvus/milvus.py +126 -9
  299. agno/vectordb/mongodb/__init__.py +7 -1
  300. agno/vectordb/mongodb/mongodb.py +112 -7
  301. agno/vectordb/pgvector/pgvector.py +142 -21
  302. agno/vectordb/pineconedb/pineconedb.py +80 -8
  303. agno/vectordb/qdrant/qdrant.py +125 -39
  304. agno/vectordb/redis/__init__.py +9 -0
  305. agno/vectordb/redis/redisdb.py +694 -0
  306. agno/vectordb/singlestore/singlestore.py +111 -25
  307. agno/vectordb/surrealdb/surrealdb.py +31 -5
  308. agno/vectordb/upstashdb/upstashdb.py +76 -8
  309. agno/vectordb/weaviate/weaviate.py +86 -15
  310. agno/workflow/__init__.py +2 -0
  311. agno/workflow/agent.py +299 -0
  312. agno/workflow/condition.py +112 -18
  313. agno/workflow/loop.py +69 -10
  314. agno/workflow/parallel.py +266 -118
  315. agno/workflow/router.py +110 -17
  316. agno/workflow/step.py +645 -136
  317. agno/workflow/steps.py +65 -6
  318. agno/workflow/types.py +71 -33
  319. agno/workflow/workflow.py +2113 -300
  320. agno-2.3.0.dist-info/METADATA +618 -0
  321. agno-2.3.0.dist-info/RECORD +577 -0
  322. agno-2.3.0.dist-info/licenses/LICENSE +201 -0
  323. agno/knowledge/reader/url_reader.py +0 -128
  324. agno/tools/googlesearch.py +0 -98
  325. agno/tools/mcp.py +0 -610
  326. agno/utils/models/aws_claude.py +0 -170
  327. agno-2.0.0rc2.dist-info/METADATA +0 -355
  328. agno-2.0.0rc2.dist-info/RECORD +0 -515
  329. agno-2.0.0rc2.dist-info/licenses/LICENSE +0 -375
  330. {agno-2.0.0rc2.dist-info → agno-2.3.0.dist-info}/WHEEL +0 -0
  331. {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 logger
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
- logger.error("`huggingface-hub` not installed, please run `pip install huggingface-hub`")
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
- logger.warning(f"Failed to process embeddings: {e}")
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
- logger.warning(f"Failed to process embeddings: {e}")
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]]:
@@ -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 logger
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
- logger.error("`mistralai` not installed")
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
- logger.warning(f"Error getting embedding: {e}")
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
- logger.warning(f"Error getting embedding and usage: {e}")
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
- logger.warning(f"Error getting embedding: {e}")
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
- logger.warning(f"Error getting embedding and usage: {e}")
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
@@ -10,4 +10,4 @@ class NebiusEmbedder(OpenAIEmbedder):
10
10
  id: str = "BAAI/bge-en-icl"
11
11
  dimensions: int = 1024
12
12
  api_key: Optional[str] = getenv("NEBIUS_API_KEY")
13
- base_url: str = "https://api.studio.nebius.com/v1/"
13
+ base_url: str = "https://api.tokenfactory.nebius.com/v1/"
@@ -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
- response: CreateEmbeddingResponse = self.response(text=text)
89
+ try:
90
+ response: CreateEmbeddingResponse = self.response(text=text)
90
91
 
91
- embedding = response.data[0].embedding
92
- usage = response.usage
93
- if usage:
94
- return embedding, usage.model_dump()
95
- return embedding, None
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
- response = await self.aclient.embeddings.create(**req)
131
- embedding = response.data[0].embedding
132
- usage = response.usage
133
- return embedding, usage.model_dump() if usage else None
134
-
135
- def get_embeddings_batch(self, texts: List[str], batch_size: int = 100) -> List[List[float]]:
136
- """
137
- Get embeddings for multiple texts in batches.
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 async_get_embeddings_batch(self, texts: List[str], batch_size: int = 100) -> List[List[float]]:
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 async calls for this batch
184
+ # Fallback to individual calls for this batch
215
185
  for text in batch_texts:
216
186
  try:
217
- embedding = await self.async_get_embedding(text)
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 not self.sentence_transformer_client:
30
- model = SentenceTransformer(model_name_or_path=self.id)
31
- else:
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):