agno 2.2.13__py3-none-any.whl → 2.4.3__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/__init__.py +6 -0
- agno/agent/agent.py +5252 -3145
- agno/agent/remote.py +525 -0
- agno/api/api.py +2 -0
- agno/client/__init__.py +3 -0
- agno/client/a2a/__init__.py +10 -0
- agno/client/a2a/client.py +554 -0
- agno/client/a2a/schemas.py +112 -0
- agno/client/a2a/utils.py +369 -0
- agno/client/os.py +2669 -0
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/manager.py +2 -2
- agno/db/base.py +927 -6
- agno/db/dynamo/dynamo.py +788 -2
- agno/db/dynamo/schemas.py +128 -0
- agno/db/dynamo/utils.py +26 -3
- agno/db/firestore/firestore.py +674 -50
- agno/db/firestore/schemas.py +41 -0
- agno/db/firestore/utils.py +25 -10
- agno/db/gcs_json/gcs_json_db.py +506 -3
- agno/db/gcs_json/utils.py +14 -2
- agno/db/in_memory/in_memory_db.py +203 -4
- agno/db/in_memory/utils.py +14 -2
- agno/db/json/json_db.py +498 -2
- agno/db/json/utils.py +14 -2
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/utils.py +19 -0
- agno/db/migrations/v1_to_v2.py +54 -16
- agno/db/migrations/versions/__init__.py +0 -0
- agno/db/migrations/versions/v2_3_0.py +977 -0
- agno/db/mongo/async_mongo.py +1013 -39
- agno/db/mongo/mongo.py +684 -4
- agno/db/mongo/schemas.py +48 -0
- agno/db/mongo/utils.py +17 -0
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2958 -0
- agno/db/mysql/mysql.py +722 -53
- agno/db/mysql/schemas.py +77 -11
- agno/db/mysql/utils.py +151 -8
- agno/db/postgres/async_postgres.py +1254 -137
- agno/db/postgres/postgres.py +2316 -93
- agno/db/postgres/schemas.py +153 -21
- agno/db/postgres/utils.py +22 -7
- agno/db/redis/redis.py +531 -3
- agno/db/redis/schemas.py +36 -0
- agno/db/redis/utils.py +31 -15
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +20 -9
- agno/db/singlestore/schemas.py +70 -1
- agno/db/singlestore/singlestore.py +737 -74
- agno/db/singlestore/utils.py +13 -3
- agno/db/sqlite/async_sqlite.py +1069 -89
- agno/db/sqlite/schemas.py +133 -1
- agno/db/sqlite/sqlite.py +2203 -165
- agno/db/sqlite/utils.py +21 -11
- agno/db/surrealdb/models.py +25 -0
- agno/db/surrealdb/surrealdb.py +603 -1
- agno/db/utils.py +60 -0
- agno/eval/__init__.py +26 -3
- agno/eval/accuracy.py +25 -12
- agno/eval/agent_as_judge.py +871 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +10 -4
- agno/eval/reliability.py +22 -13
- agno/eval/utils.py +2 -1
- agno/exceptions.py +42 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +13 -2
- agno/knowledge/__init__.py +4 -0
- agno/knowledge/chunking/code.py +90 -0
- agno/knowledge/chunking/document.py +65 -4
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/markdown.py +102 -11
- agno/knowledge/chunking/recursive.py +2 -2
- agno/knowledge/chunking/semantic.py +130 -48
- agno/knowledge/chunking/strategy.py +18 -0
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/knowledge/embedder/mistral.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/openai.py +16 -12
- agno/knowledge/filesystem.py +412 -0
- agno/knowledge/knowledge.py +4261 -1199
- agno/knowledge/protocol.py +134 -0
- agno/knowledge/reader/arxiv_reader.py +3 -2
- agno/knowledge/reader/base.py +9 -7
- agno/knowledge/reader/csv_reader.py +91 -42
- agno/knowledge/reader/docx_reader.py +9 -10
- agno/knowledge/reader/excel_reader.py +225 -0
- agno/knowledge/reader/field_labeled_csv_reader.py +38 -48
- agno/knowledge/reader/firecrawl_reader.py +3 -2
- agno/knowledge/reader/json_reader.py +16 -22
- agno/knowledge/reader/markdown_reader.py +15 -14
- agno/knowledge/reader/pdf_reader.py +33 -28
- agno/knowledge/reader/pptx_reader.py +9 -10
- agno/knowledge/reader/reader_factory.py +135 -1
- agno/knowledge/reader/s3_reader.py +8 -16
- agno/knowledge/reader/tavily_reader.py +3 -3
- agno/knowledge/reader/text_reader.py +15 -14
- agno/knowledge/reader/utils/__init__.py +17 -0
- agno/knowledge/reader/utils/spreadsheet.py +114 -0
- agno/knowledge/reader/web_search_reader.py +8 -65
- agno/knowledge/reader/website_reader.py +16 -13
- agno/knowledge/reader/wikipedia_reader.py +36 -3
- agno/knowledge/reader/youtube_reader.py +3 -2
- agno/knowledge/remote_content/__init__.py +33 -0
- agno/knowledge/remote_content/config.py +266 -0
- agno/knowledge/remote_content/remote_content.py +105 -17
- agno/knowledge/utils.py +76 -22
- agno/learn/__init__.py +71 -0
- agno/learn/config.py +463 -0
- agno/learn/curate.py +185 -0
- agno/learn/machine.py +725 -0
- agno/learn/schemas.py +1114 -0
- agno/learn/stores/__init__.py +38 -0
- agno/learn/stores/decision_log.py +1156 -0
- agno/learn/stores/entity_memory.py +3275 -0
- agno/learn/stores/learned_knowledge.py +1583 -0
- agno/learn/stores/protocol.py +117 -0
- agno/learn/stores/session_context.py +1217 -0
- agno/learn/stores/user_memory.py +1495 -0
- agno/learn/stores/user_profile.py +1220 -0
- agno/learn/utils.py +209 -0
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +66 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/aimlapi/aimlapi.py +17 -0
- agno/models/anthropic/claude.py +434 -59
- agno/models/aws/bedrock.py +121 -20
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +10 -6
- agno/models/azure/openai_chat.py +33 -10
- agno/models/base.py +1162 -561
- agno/models/cerebras/cerebras.py +120 -24
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +65 -6
- agno/models/cometapi/cometapi.py +18 -1
- agno/models/dashscope/dashscope.py +2 -3
- agno/models/deepinfra/deepinfra.py +18 -1
- agno/models/deepseek/deepseek.py +69 -3
- agno/models/fireworks/fireworks.py +18 -1
- agno/models/google/gemini.py +959 -89
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +48 -18
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/internlm/internlm.py +18 -1
- agno/models/langdb/langdb.py +13 -1
- agno/models/litellm/chat.py +88 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +24 -5
- agno/models/meta/llama.py +40 -13
- agno/models/meta/llama_openai.py +22 -21
- agno/models/metrics.py +12 -0
- agno/models/mistral/mistral.py +8 -4
- agno/models/n1n/__init__.py +3 -0
- agno/models/n1n/n1n.py +57 -0
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/__init__.py +2 -0
- agno/models/ollama/chat.py +17 -6
- agno/models/ollama/responses.py +100 -0
- agno/models/openai/__init__.py +2 -0
- agno/models/openai/chat.py +117 -26
- agno/models/openai/open_responses.py +46 -0
- agno/models/openai/responses.py +110 -32
- agno/models/openrouter/__init__.py +2 -0
- agno/models/openrouter/openrouter.py +67 -2
- agno/models/openrouter/responses.py +146 -0
- agno/models/perplexity/perplexity.py +19 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +19 -2
- agno/models/response.py +20 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/claude.py +124 -4
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +467 -137
- agno/os/auth.py +253 -5
- agno/os/config.py +22 -0
- agno/os/interfaces/a2a/a2a.py +7 -6
- agno/os/interfaces/a2a/router.py +635 -26
- agno/os/interfaces/a2a/utils.py +32 -33
- agno/os/interfaces/agui/agui.py +5 -3
- agno/os/interfaces/agui/router.py +26 -16
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/interfaces/base.py +7 -7
- agno/os/interfaces/slack/router.py +16 -7
- agno/os/interfaces/slack/slack.py +7 -7
- agno/os/interfaces/whatsapp/router.py +35 -7
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/interfaces/whatsapp/whatsapp.py +11 -8
- agno/os/managers.py +326 -0
- agno/os/mcp.py +652 -79
- agno/os/middleware/__init__.py +4 -0
- agno/os/middleware/jwt.py +718 -115
- agno/os/middleware/trailing_slash.py +27 -0
- agno/os/router.py +105 -1558
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +655 -0
- agno/os/routers/agents/schema.py +288 -0
- agno/os/routers/components/__init__.py +3 -0
- agno/os/routers/components/components.py +475 -0
- agno/os/routers/database.py +155 -0
- agno/os/routers/evals/evals.py +111 -18
- agno/os/routers/evals/schemas.py +38 -5
- agno/os/routers/evals/utils.py +80 -11
- agno/os/routers/health.py +3 -3
- agno/os/routers/knowledge/knowledge.py +284 -35
- agno/os/routers/knowledge/schemas.py +14 -2
- agno/os/routers/memory/memory.py +274 -11
- agno/os/routers/memory/schemas.py +44 -3
- agno/os/routers/metrics/metrics.py +30 -15
- agno/os/routers/metrics/schemas.py +10 -6
- agno/os/routers/registry/__init__.py +3 -0
- agno/os/routers/registry/registry.py +337 -0
- agno/os/routers/session/session.py +143 -14
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +550 -0
- agno/os/routers/teams/schema.py +280 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +549 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +757 -0
- agno/os/routers/workflows/schema.py +139 -0
- agno/os/schema.py +157 -584
- agno/os/scopes.py +469 -0
- agno/os/settings.py +3 -0
- agno/os/utils.py +574 -185
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +102 -2
- agno/reasoning/default.py +6 -7
- agno/reasoning/gemini.py +87 -3
- agno/reasoning/groq.py +109 -2
- agno/reasoning/helpers.py +6 -7
- agno/reasoning/manager.py +1238 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +115 -1
- agno/reasoning/vertexai.py +85 -1
- agno/registry/__init__.py +3 -0
- agno/registry/registry.py +68 -0
- agno/remote/__init__.py +3 -0
- agno/remote/base.py +581 -0
- agno/run/__init__.py +2 -4
- agno/run/agent.py +134 -19
- agno/run/base.py +49 -1
- agno/run/cancel.py +65 -52
- agno/run/cancellation_management/__init__.py +9 -0
- agno/run/cancellation_management/base.py +78 -0
- agno/run/cancellation_management/in_memory_cancellation_manager.py +100 -0
- agno/run/cancellation_management/redis_cancellation_manager.py +236 -0
- agno/run/requirement.py +181 -0
- agno/run/team.py +111 -19
- agno/run/workflow.py +2 -1
- agno/session/agent.py +57 -92
- agno/session/summary.py +1 -1
- agno/session/team.py +62 -115
- agno/session/workflow.py +353 -57
- agno/skills/__init__.py +17 -0
- agno/skills/agent_skills.py +377 -0
- agno/skills/errors.py +32 -0
- agno/skills/loaders/__init__.py +4 -0
- agno/skills/loaders/base.py +27 -0
- agno/skills/loaders/local.py +216 -0
- agno/skills/skill.py +65 -0
- agno/skills/utils.py +107 -0
- agno/skills/validator.py +277 -0
- agno/table.py +10 -0
- agno/team/__init__.py +5 -1
- agno/team/remote.py +447 -0
- agno/team/team.py +3769 -2202
- agno/tools/brandfetch.py +27 -18
- agno/tools/browserbase.py +225 -16
- agno/tools/crawl4ai.py +3 -0
- agno/tools/duckduckgo.py +25 -71
- agno/tools/exa.py +0 -21
- agno/tools/file.py +14 -13
- agno/tools/file_generation.py +12 -6
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +94 -113
- agno/tools/google_bigquery.py +11 -2
- agno/tools/google_drive.py +4 -3
- agno/tools/knowledge.py +9 -4
- agno/tools/mcp/mcp.py +301 -18
- agno/tools/mcp/multi_mcp.py +269 -14
- agno/tools/mem0.py +11 -10
- agno/tools/memory.py +47 -46
- agno/tools/mlx_transcribe.py +10 -7
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/parallel.py +0 -7
- agno/tools/postgres.py +76 -36
- agno/tools/python.py +14 -6
- agno/tools/reasoning.py +30 -23
- agno/tools/redshift.py +406 -0
- agno/tools/shopify.py +1519 -0
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +4 -1
- agno/tools/toolkit.py +253 -18
- agno/tools/websearch.py +93 -0
- agno/tools/website.py +1 -1
- agno/tools/wikipedia.py +1 -1
- agno/tools/workflow.py +56 -48
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +161 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +112 -0
- agno/utils/agent.py +251 -10
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +264 -7
- agno/utils/hooks.py +111 -3
- agno/utils/http.py +161 -2
- agno/utils/mcp.py +49 -8
- agno/utils/media.py +22 -1
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +20 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/os.py +0 -0
- agno/utils/print_response/agent.py +99 -16
- agno/utils/print_response/team.py +223 -24
- agno/utils/print_response/workflow.py +0 -2
- agno/utils/prompts.py +8 -6
- agno/utils/remote.py +23 -0
- agno/utils/response.py +1 -13
- agno/utils/string.py +91 -2
- agno/utils/team.py +62 -12
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +15 -2
- agno/vectordb/cassandra/cassandra.py +1 -1
- agno/vectordb/chroma/__init__.py +2 -1
- agno/vectordb/chroma/chromadb.py +468 -23
- agno/vectordb/clickhouse/clickhousedb.py +1 -1
- agno/vectordb/couchbase/couchbase.py +6 -2
- agno/vectordb/lancedb/lance_db.py +7 -38
- agno/vectordb/lightrag/lightrag.py +7 -6
- agno/vectordb/milvus/milvus.py +118 -84
- agno/vectordb/mongodb/__init__.py +2 -1
- agno/vectordb/mongodb/mongodb.py +14 -31
- agno/vectordb/pgvector/pgvector.py +120 -66
- agno/vectordb/pineconedb/pineconedb.py +2 -19
- agno/vectordb/qdrant/__init__.py +2 -1
- agno/vectordb/qdrant/qdrant.py +33 -56
- agno/vectordb/redis/__init__.py +2 -1
- agno/vectordb/redis/redisdb.py +19 -31
- agno/vectordb/singlestore/singlestore.py +17 -9
- agno/vectordb/surrealdb/surrealdb.py +2 -38
- agno/vectordb/weaviate/__init__.py +2 -1
- agno/vectordb/weaviate/weaviate.py +7 -3
- agno/workflow/__init__.py +5 -1
- agno/workflow/agent.py +2 -2
- agno/workflow/condition.py +12 -10
- agno/workflow/loop.py +28 -9
- agno/workflow/parallel.py +21 -13
- agno/workflow/remote.py +362 -0
- agno/workflow/router.py +12 -9
- agno/workflow/step.py +261 -36
- agno/workflow/steps.py +12 -8
- agno/workflow/types.py +40 -77
- agno/workflow/workflow.py +939 -213
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/METADATA +134 -181
- agno-2.4.3.dist-info/RECORD +677 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/WHEEL +1 -1
- agno/tools/googlesearch.py +0 -98
- agno/tools/memori.py +0 -339
- agno-2.2.13.dist-info/RECORD +0 -575
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.2.13.dist-info → agno-2.4.3.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
|
+
import re
|
|
2
3
|
import tempfile
|
|
3
|
-
from typing import List
|
|
4
|
+
from typing import List, Union
|
|
4
5
|
|
|
5
6
|
try:
|
|
6
7
|
from unstructured.chunking.title import chunk_by_title # type: ignore
|
|
@@ -13,17 +14,83 @@ from agno.knowledge.document.base import Document
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class MarkdownChunking(ChunkingStrategy):
|
|
16
|
-
"""A chunking strategy that splits markdown based on structure like headers, paragraphs and sections
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
"""A chunking strategy that splits markdown based on structure like headers, paragraphs and sections
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
chunk_size: Maximum size of each chunk in characters
|
|
21
|
+
overlap: Number of characters to overlap between chunks
|
|
22
|
+
split_on_headings: Controls heading-based splitting behavior:
|
|
23
|
+
- False: Use size-based chunking (default)
|
|
24
|
+
- True: Split on all headings (H1-H6)
|
|
25
|
+
- int: Split on headings at or above this level (1-6)
|
|
26
|
+
e.g., 2 splits on H1 and H2, keeping H3-H6 content together
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, chunk_size: int = 5000, overlap: int = 0, split_on_headings: Union[bool, int] = False):
|
|
19
30
|
self.chunk_size = chunk_size
|
|
20
31
|
self.overlap = overlap
|
|
32
|
+
self.split_on_headings = split_on_headings
|
|
33
|
+
|
|
34
|
+
# Validate split_on_headings parameter
|
|
35
|
+
# Note: In Python, isinstance(False, int) is True, so we exclude booleans explicitly
|
|
36
|
+
if isinstance(split_on_headings, int) and not isinstance(split_on_headings, bool):
|
|
37
|
+
if not (1 <= split_on_headings <= 6):
|
|
38
|
+
raise ValueError("split_on_headings must be between 1 and 6 when using integer value")
|
|
39
|
+
|
|
40
|
+
def _split_by_headings(self, content: str) -> List[str]:
|
|
41
|
+
"""
|
|
42
|
+
Split markdown content by headings, keeping each heading with its content.
|
|
43
|
+
Returns a list of sections where each section starts with a heading.
|
|
44
|
+
|
|
45
|
+
When split_on_headings is an int, only splits on headings at or above that level.
|
|
46
|
+
For example, split_on_headings=2 splits on H1 and H2, keeping H3-H6 content together.
|
|
47
|
+
"""
|
|
48
|
+
# Determine which heading levels to split on
|
|
49
|
+
if isinstance(self.split_on_headings, int) and not isinstance(self.split_on_headings, bool):
|
|
50
|
+
# Split on headings at or above this level (1 to split_on_headings)
|
|
51
|
+
max_heading_level = self.split_on_headings
|
|
52
|
+
heading_pattern = rf"^#{{{1},{max_heading_level}}}\s+.+$"
|
|
53
|
+
else:
|
|
54
|
+
# split_on_headings is True: split on all headings (# to ######)
|
|
55
|
+
heading_pattern = r"^#{1,6}\s+.+$"
|
|
56
|
+
|
|
57
|
+
# Split content while keeping the delimiter (heading)
|
|
58
|
+
# Use non-capturing group for the pattern to avoid extra capture groups
|
|
59
|
+
parts = re.split(f"({heading_pattern})", content, flags=re.MULTILINE)
|
|
60
|
+
|
|
61
|
+
sections = []
|
|
62
|
+
current_section = ""
|
|
63
|
+
|
|
64
|
+
for part in parts:
|
|
65
|
+
if not part or not part.strip():
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Check if this part is a heading
|
|
69
|
+
if re.match(heading_pattern, part.strip(), re.MULTILINE):
|
|
70
|
+
# Save previous section if exists
|
|
71
|
+
if current_section.strip():
|
|
72
|
+
sections.append(current_section.strip())
|
|
73
|
+
# Start new section with this heading
|
|
74
|
+
current_section = part
|
|
75
|
+
else:
|
|
76
|
+
# Add content to current section
|
|
77
|
+
current_section += "\n\n" + part if current_section else part
|
|
78
|
+
|
|
79
|
+
# Don't forget the last section
|
|
80
|
+
if current_section.strip():
|
|
81
|
+
sections.append(current_section.strip())
|
|
82
|
+
|
|
83
|
+
return sections if sections else [content]
|
|
21
84
|
|
|
22
85
|
def _partition_markdown_content(self, content: str) -> List[str]:
|
|
23
86
|
"""
|
|
24
87
|
Partition markdown content and return a list of text chunks.
|
|
25
88
|
Falls back to paragraph splitting if the markdown chunking fails.
|
|
26
89
|
"""
|
|
90
|
+
# When split_on_headings is True or an int, use regex-based splitting to preserve headings
|
|
91
|
+
if self.split_on_headings:
|
|
92
|
+
return self._split_by_headings(content)
|
|
93
|
+
|
|
27
94
|
try:
|
|
28
95
|
# Create a temporary file with the markdown content.
|
|
29
96
|
# This is the recommended usage of the unstructured library.
|
|
@@ -35,9 +102,9 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
35
102
|
elements = partition_md(filename=temp_file_path)
|
|
36
103
|
|
|
37
104
|
if not elements:
|
|
38
|
-
|
|
105
|
+
raw_paragraphs = content.split("\n\n")
|
|
106
|
+
return [self.clean_text(para) for para in raw_paragraphs]
|
|
39
107
|
|
|
40
|
-
# Chunk by title with some default values
|
|
41
108
|
chunked_elements = chunk_by_title(
|
|
42
109
|
elements=elements,
|
|
43
110
|
max_characters=self.chunk_size,
|
|
@@ -57,7 +124,10 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
57
124
|
if chunk_text.strip():
|
|
58
125
|
text_chunks.append(chunk_text.strip())
|
|
59
126
|
|
|
60
|
-
|
|
127
|
+
if text_chunks:
|
|
128
|
+
return text_chunks
|
|
129
|
+
raw_paragraphs = content.split("\n\n")
|
|
130
|
+
return [self.clean_text(para) for para in raw_paragraphs]
|
|
61
131
|
|
|
62
132
|
# Always clean up the temporary file
|
|
63
133
|
finally:
|
|
@@ -65,11 +135,18 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
65
135
|
|
|
66
136
|
# Fallback to simple paragraph splitting if the markdown chunking fails
|
|
67
137
|
except Exception:
|
|
68
|
-
|
|
138
|
+
raw_paragraphs = content.split("\n\n")
|
|
139
|
+
return [self.clean_text(para) for para in raw_paragraphs]
|
|
69
140
|
|
|
70
141
|
def chunk(self, document: Document) -> List[Document]:
|
|
71
142
|
"""Split markdown document into chunks based on markdown structure"""
|
|
72
|
-
|
|
143
|
+
# If content is empty, return as-is
|
|
144
|
+
if not document.content:
|
|
145
|
+
return [document]
|
|
146
|
+
|
|
147
|
+
# When split_on_headings is enabled, always split by headings regardless of size
|
|
148
|
+
# Only skip chunking for small content when using size-based chunking
|
|
149
|
+
if not self.split_on_headings and len(document.content) <= self.chunk_size:
|
|
73
150
|
return [document]
|
|
74
151
|
|
|
75
152
|
# Split using markdown chunking logic, or fallback to paragraphs
|
|
@@ -85,7 +162,20 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
85
162
|
section = section.strip()
|
|
86
163
|
section_size = len(section)
|
|
87
164
|
|
|
88
|
-
|
|
165
|
+
# When split_on_headings is True or an int, each section becomes its own chunk
|
|
166
|
+
if self.split_on_headings:
|
|
167
|
+
meta_data = chunk_meta_data.copy()
|
|
168
|
+
meta_data["chunk"] = chunk_number
|
|
169
|
+
chunk_id = None
|
|
170
|
+
if document.id:
|
|
171
|
+
chunk_id = f"{document.id}_{chunk_number}"
|
|
172
|
+
elif document.name:
|
|
173
|
+
chunk_id = f"{document.name}_{chunk_number}"
|
|
174
|
+
meta_data["chunk_size"] = section_size
|
|
175
|
+
|
|
176
|
+
chunks.append(Document(id=chunk_id, name=document.name, meta_data=meta_data, content=section))
|
|
177
|
+
chunk_number += 1
|
|
178
|
+
elif current_size + section_size <= self.chunk_size:
|
|
89
179
|
current_chunk.append(section)
|
|
90
180
|
current_size += section_size
|
|
91
181
|
else:
|
|
@@ -109,7 +199,8 @@ class MarkdownChunking(ChunkingStrategy):
|
|
|
109
199
|
current_chunk = [section]
|
|
110
200
|
current_size = section_size
|
|
111
201
|
|
|
112
|
-
|
|
202
|
+
# Handle remaining content (only when not split_on_headings)
|
|
203
|
+
if current_chunk and not self.split_on_headings:
|
|
113
204
|
meta_data = chunk_meta_data.copy()
|
|
114
205
|
meta_data["chunk"] = chunk_number
|
|
115
206
|
chunk_id = None
|
|
@@ -31,7 +31,7 @@ class RecursiveChunking(ChunkingStrategy):
|
|
|
31
31
|
start = 0
|
|
32
32
|
chunk_meta_data = document.meta_data
|
|
33
33
|
chunk_number = 1
|
|
34
|
-
content =
|
|
34
|
+
content = document.content
|
|
35
35
|
|
|
36
36
|
while start < len(content):
|
|
37
37
|
end = min(start + self.chunk_size, len(content))
|
|
@@ -43,7 +43,7 @@ class RecursiveChunking(ChunkingStrategy):
|
|
|
43
43
|
end = start + last_sep + 1
|
|
44
44
|
break
|
|
45
45
|
|
|
46
|
-
chunk = content[start:end]
|
|
46
|
+
chunk = self.clean_text(content[start:end])
|
|
47
47
|
meta_data = chunk_meta_data.copy()
|
|
48
48
|
meta_data["chunk"] = chunk_number
|
|
49
49
|
chunk_id = None
|
|
@@ -1,63 +1,145 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from typing import Any, Dict, List, Literal, Optional, Union
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
import numpy as np
|
|
5
|
+
except ImportError:
|
|
6
|
+
raise ImportError("`numpy` not installed. Please install using `pip install numpy`")
|
|
7
|
+
|
|
8
|
+
try:
|
|
9
|
+
from chonkie import SemanticChunker
|
|
10
|
+
from chonkie.embeddings.base import BaseEmbeddings
|
|
11
|
+
except ImportError:
|
|
12
|
+
raise ImportError(
|
|
13
|
+
"`chonkie` is required for semantic chunking. "
|
|
14
|
+
'Please install it using `pip install "chonkie[semantic]"` to use SemanticChunking.'
|
|
15
|
+
)
|
|
3
16
|
|
|
4
17
|
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
5
18
|
from agno.knowledge.document.base import Document
|
|
6
19
|
from agno.knowledge.embedder.base import Embedder
|
|
7
|
-
from agno.
|
|
20
|
+
from agno.utils.log import log_debug
|
|
8
21
|
|
|
9
22
|
|
|
10
|
-
|
|
11
|
-
"""
|
|
23
|
+
def _get_chonkie_embedder_wrapper(embedder: Embedder):
|
|
24
|
+
"""Create a wrapper that adapts Agno Embedder to chonkie's BaseEmbeddings interface."""
|
|
25
|
+
|
|
26
|
+
class _ChonkieEmbedderWrapper(BaseEmbeddings):
|
|
27
|
+
"""Wrapper to make Agno Embedders compatible with chonkie."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, agno_embedder: Embedder):
|
|
30
|
+
super().__init__()
|
|
31
|
+
self._embedder = agno_embedder
|
|
32
|
+
|
|
33
|
+
def embed(self, text: str):
|
|
34
|
+
embedding = self._embedder.get_embedding(text) # type: ignore[attr-defined]
|
|
35
|
+
return np.array(embedding, dtype=np.float32)
|
|
36
|
+
|
|
37
|
+
def get_tokenizer(self):
|
|
38
|
+
"""Return a simple token counter function."""
|
|
39
|
+
return lambda text: len(text.split())
|
|
12
40
|
|
|
13
|
-
|
|
14
|
-
|
|
41
|
+
@property
|
|
42
|
+
def dimension(self) -> int:
|
|
43
|
+
return getattr(self._embedder, "dimensions")
|
|
44
|
+
|
|
45
|
+
return _ChonkieEmbedderWrapper(embedder)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class SemanticChunking(ChunkingStrategy):
|
|
49
|
+
"""Chunking strategy that splits text into semantic chunks using chonkie.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
embedder: The embedder to use for generating embeddings. Can be:
|
|
53
|
+
- A string model identifier (e.g., "minishlab/potion-base-32M") for chonkie's built-in models
|
|
54
|
+
- A chonkie BaseEmbeddings instance (used directly)
|
|
55
|
+
- An Agno Embedder (wrapped for chonkie compatibility)
|
|
56
|
+
chunk_size: Maximum tokens allowed per chunk.
|
|
57
|
+
similarity_threshold: Threshold for semantic similarity (0-1).
|
|
58
|
+
similarity_window: Number of sentences to consider for similarity calculation.
|
|
59
|
+
min_sentences_per_chunk: Minimum number of sentences per chunk.
|
|
60
|
+
min_characters_per_sentence: Minimum number of characters per sentence.
|
|
61
|
+
delimiters: Delimiters to use for sentence splitting.
|
|
62
|
+
include_delimiters: Whether to include delimiter in prev/next sentence or None.
|
|
63
|
+
skip_window: Number of groups to skip when merging (0=disabled).
|
|
64
|
+
filter_window: Window length for the Savitzky-Golay filter.
|
|
65
|
+
filter_polyorder: Polynomial order for the Savitzky-Golay filter.
|
|
66
|
+
filter_tolerance: Tolerance for the Savitzky-Golay filter.
|
|
67
|
+
chunker_params: Additional parameters to pass to chonkie's SemanticChunker.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
embedder: Optional[Union[str, Embedder, BaseEmbeddings]] = None,
|
|
73
|
+
chunk_size: int = 5000,
|
|
74
|
+
similarity_threshold: float = 0.5,
|
|
75
|
+
similarity_window: int = 3,
|
|
76
|
+
min_sentences_per_chunk: int = 1,
|
|
77
|
+
min_characters_per_sentence: int = 24,
|
|
78
|
+
delimiters: Optional[List[str]] = None,
|
|
79
|
+
include_delimiters: Literal["prev", "next", None] = "prev",
|
|
80
|
+
skip_window: int = 0,
|
|
81
|
+
filter_window: int = 5,
|
|
82
|
+
filter_polyorder: int = 3,
|
|
83
|
+
filter_tolerance: float = 0.2,
|
|
84
|
+
chunker_params: Optional[Dict[str, Any]] = None,
|
|
85
|
+
):
|
|
86
|
+
if embedder is None:
|
|
87
|
+
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
88
|
+
|
|
89
|
+
embedder = OpenAIEmbedder() # type: ignore
|
|
90
|
+
log_debug("Embedder not provided, using OpenAIEmbedder as default.")
|
|
91
|
+
self.embedder = embedder
|
|
15
92
|
self.chunk_size = chunk_size
|
|
16
93
|
self.similarity_threshold = similarity_threshold
|
|
17
|
-
self.
|
|
94
|
+
self.similarity_window = similarity_window
|
|
95
|
+
self.min_sentences_per_chunk = min_sentences_per_chunk
|
|
96
|
+
self.min_characters_per_sentence = min_characters_per_sentence
|
|
97
|
+
self.delimiters = delimiters if delimiters is not None else [". ", "! ", "? ", "\n"]
|
|
98
|
+
self.include_delimiters = include_delimiters
|
|
99
|
+
self.skip_window = skip_window
|
|
100
|
+
self.filter_window = filter_window
|
|
101
|
+
self.filter_polyorder = filter_polyorder
|
|
102
|
+
self.filter_tolerance = filter_tolerance
|
|
103
|
+
self.chunker_params = chunker_params
|
|
104
|
+
self.chunker: Optional[SemanticChunker] = None
|
|
18
105
|
|
|
19
106
|
def _initialize_chunker(self):
|
|
20
107
|
"""Lazily initialize the chunker with chonkie dependency."""
|
|
21
|
-
if self.chunker is None:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
self.chunker = SemanticChunker(
|
|
57
|
-
embedding_model=getattr(self.embedder, "id", None) or "text-embedding-3-small",
|
|
58
|
-
chunk_size=self.chunk_size,
|
|
59
|
-
threshold=self.similarity_threshold,
|
|
60
|
-
)
|
|
108
|
+
if self.chunker is not None:
|
|
109
|
+
return
|
|
110
|
+
|
|
111
|
+
# Determine embedding model based on type:
|
|
112
|
+
# - str: pass directly to chonkie (uses chonkie's built-in models)
|
|
113
|
+
# - BaseEmbeddings: pass directly to chonkie
|
|
114
|
+
# - Agno Embedder: wrap for chonkie compatibility
|
|
115
|
+
embedding_model: Union[str, BaseEmbeddings]
|
|
116
|
+
if isinstance(self.embedder, str):
|
|
117
|
+
embedding_model = self.embedder
|
|
118
|
+
elif isinstance(self.embedder, BaseEmbeddings):
|
|
119
|
+
embedding_model = self.embedder
|
|
120
|
+
elif isinstance(self.embedder, Embedder):
|
|
121
|
+
embedding_model = _get_chonkie_embedder_wrapper(self.embedder)
|
|
122
|
+
else:
|
|
123
|
+
raise ValueError("Invalid embedder type. Must be a string, BaseEmbeddings, or Embedder instance.")
|
|
124
|
+
|
|
125
|
+
_chunker_params: Dict[str, Any] = {
|
|
126
|
+
"embedding_model": embedding_model,
|
|
127
|
+
"chunk_size": self.chunk_size,
|
|
128
|
+
"threshold": self.similarity_threshold,
|
|
129
|
+
"similarity_window": self.similarity_window,
|
|
130
|
+
"min_sentences_per_chunk": self.min_sentences_per_chunk,
|
|
131
|
+
"min_characters_per_sentence": self.min_characters_per_sentence,
|
|
132
|
+
"delim": self.delimiters,
|
|
133
|
+
"include_delim": self.include_delimiters,
|
|
134
|
+
"skip_window": self.skip_window,
|
|
135
|
+
"filter_window": self.filter_window,
|
|
136
|
+
"filter_polyorder": self.filter_polyorder,
|
|
137
|
+
"filter_tolerance": self.filter_tolerance,
|
|
138
|
+
}
|
|
139
|
+
if self.chunker_params:
|
|
140
|
+
_chunker_params.update(self.chunker_params)
|
|
141
|
+
|
|
142
|
+
self.chunker = SemanticChunker(**_chunker_params)
|
|
61
143
|
|
|
62
144
|
def chunk(self, document: Document) -> List[Document]:
|
|
63
145
|
"""Split document into semantic chunks using chonkie"""
|
|
@@ -12,6 +12,10 @@ class ChunkingStrategy(ABC):
|
|
|
12
12
|
def chunk(self, document: Document) -> List[Document]:
|
|
13
13
|
raise NotImplementedError
|
|
14
14
|
|
|
15
|
+
async def achunk(self, document: Document) -> List[Document]:
|
|
16
|
+
"""Async version of chunk. Override for truly async implementations."""
|
|
17
|
+
return self.chunk(document)
|
|
18
|
+
|
|
15
19
|
def clean_text(self, text: str) -> str:
|
|
16
20
|
"""Clean the text by replacing multiple newlines with a single newline"""
|
|
17
21
|
import re
|
|
@@ -36,6 +40,7 @@ class ChunkingStrategyType(str, Enum):
|
|
|
36
40
|
"""Enumeration of available chunking strategies."""
|
|
37
41
|
|
|
38
42
|
AGENTIC_CHUNKER = "AgenticChunker"
|
|
43
|
+
CODE_CHUNKER = "CodeChunker"
|
|
39
44
|
DOCUMENT_CHUNKER = "DocumentChunker"
|
|
40
45
|
RECURSIVE_CHUNKER = "RecursiveChunker"
|
|
41
46
|
SEMANTIC_CHUNKER = "SemanticChunker"
|
|
@@ -70,6 +75,7 @@ class ChunkingStrategyFactory:
|
|
|
70
75
|
"""Create an instance of the chunking strategy with the given parameters."""
|
|
71
76
|
strategy_map = {
|
|
72
77
|
ChunkingStrategyType.AGENTIC_CHUNKER: cls._create_agentic_chunking,
|
|
78
|
+
ChunkingStrategyType.CODE_CHUNKER: cls._create_code_chunking,
|
|
73
79
|
ChunkingStrategyType.DOCUMENT_CHUNKER: cls._create_document_chunking,
|
|
74
80
|
ChunkingStrategyType.RECURSIVE_CHUNKER: cls._create_recursive_chunking,
|
|
75
81
|
ChunkingStrategyType.SEMANTIC_CHUNKER: cls._create_semantic_chunking,
|
|
@@ -91,6 +97,18 @@ class ChunkingStrategyFactory:
|
|
|
91
97
|
# Remove overlap since AgenticChunking doesn't support it
|
|
92
98
|
return AgenticChunking(**kwargs)
|
|
93
99
|
|
|
100
|
+
@classmethod
|
|
101
|
+
def _create_code_chunking(
|
|
102
|
+
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
103
|
+
) -> ChunkingStrategy:
|
|
104
|
+
from agno.knowledge.chunking.code import CodeChunking
|
|
105
|
+
|
|
106
|
+
# CodeChunking accepts chunk_size but not overlap
|
|
107
|
+
if chunk_size is not None:
|
|
108
|
+
kwargs["chunk_size"] = chunk_size
|
|
109
|
+
# Remove overlap since CodeChunking doesn't support it
|
|
110
|
+
return CodeChunking(**kwargs)
|
|
111
|
+
|
|
94
112
|
@classmethod
|
|
95
113
|
def _create_document_chunking(
|
|
96
114
|
cls, chunk_size: Optional[int] = None, overlap: Optional[int] = None, **kwargs
|
|
@@ -18,7 +18,6 @@ except ImportError:
|
|
|
18
18
|
@dataclass
|
|
19
19
|
class AzureOpenAIEmbedder(Embedder):
|
|
20
20
|
id: str = "text-embedding-3-small" # This has to match the model that you deployed at the provided URL
|
|
21
|
-
|
|
22
21
|
dimensions: int = 1536
|
|
23
22
|
encoding_format: Literal["float", "base64"] = "float"
|
|
24
23
|
user: Optional[str] = None
|
|
@@ -37,7 +37,7 @@ class MistralEmbedder(Embedder):
|
|
|
37
37
|
"api_key": self.api_key,
|
|
38
38
|
"endpoint": self.endpoint,
|
|
39
39
|
"max_retries": self.max_retries,
|
|
40
|
-
"
|
|
40
|
+
"timeout_ms": self.timeout * 1000 if self.timeout else None,
|
|
41
41
|
}
|
|
42
42
|
_client_params = {k: v for k, v in _client_params.items() if v is not None}
|
|
43
43
|
|
|
@@ -4,7 +4,7 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
|
4
4
|
from typing_extensions import Literal
|
|
5
5
|
|
|
6
6
|
from agno.knowledge.embedder.base import Embedder
|
|
7
|
-
from agno.utils.log import
|
|
7
|
+
from agno.utils.log import log_info, log_warning
|
|
8
8
|
|
|
9
9
|
try:
|
|
10
10
|
from openai import AsyncOpenAI
|
|
@@ -71,7 +71,8 @@ class OpenAIEmbedder(Embedder):
|
|
|
71
71
|
}
|
|
72
72
|
if self.user is not None:
|
|
73
73
|
_request_params["user"] = self.user
|
|
74
|
-
|
|
74
|
+
# Pass dimensions for text-embedding-3 models or when using custom base_url (third-party APIs)
|
|
75
|
+
if self.id.startswith("text-embedding-3") or self.base_url is not None:
|
|
75
76
|
_request_params["dimensions"] = self.dimensions
|
|
76
77
|
if self.request_params:
|
|
77
78
|
_request_params.update(self.request_params)
|
|
@@ -82,7 +83,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
82
83
|
response: CreateEmbeddingResponse = self.response(text=text)
|
|
83
84
|
return response.data[0].embedding
|
|
84
85
|
except Exception as e:
|
|
85
|
-
|
|
86
|
+
log_warning(e)
|
|
86
87
|
return []
|
|
87
88
|
|
|
88
89
|
def get_embedding_and_usage(self, text: str) -> Tuple[List[float], Optional[Dict]]:
|
|
@@ -95,7 +96,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
95
96
|
return embedding, usage.model_dump()
|
|
96
97
|
return embedding, None
|
|
97
98
|
except Exception as e:
|
|
98
|
-
|
|
99
|
+
log_warning(e)
|
|
99
100
|
return [], None
|
|
100
101
|
|
|
101
102
|
async def async_get_embedding(self, text: str) -> List[float]:
|
|
@@ -106,7 +107,8 @@ class OpenAIEmbedder(Embedder):
|
|
|
106
107
|
}
|
|
107
108
|
if self.user is not None:
|
|
108
109
|
req["user"] = self.user
|
|
109
|
-
|
|
110
|
+
# Pass dimensions for text-embedding-3 models or when using custom base_url (third-party APIs)
|
|
111
|
+
if self.id.startswith("text-embedding-3") or self.base_url is not None:
|
|
110
112
|
req["dimensions"] = self.dimensions
|
|
111
113
|
if self.request_params:
|
|
112
114
|
req.update(self.request_params)
|
|
@@ -115,7 +117,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
115
117
|
response: CreateEmbeddingResponse = await self.aclient.embeddings.create(**req)
|
|
116
118
|
return response.data[0].embedding
|
|
117
119
|
except Exception as e:
|
|
118
|
-
|
|
120
|
+
log_warning(e)
|
|
119
121
|
return []
|
|
120
122
|
|
|
121
123
|
async def async_get_embedding_and_usage(self, text: str):
|
|
@@ -126,7 +128,8 @@ class OpenAIEmbedder(Embedder):
|
|
|
126
128
|
}
|
|
127
129
|
if self.user is not None:
|
|
128
130
|
req["user"] = self.user
|
|
129
|
-
|
|
131
|
+
# Pass dimensions for text-embedding-3 models or when using custom base_url (third-party APIs)
|
|
132
|
+
if self.id.startswith("text-embedding-3") or self.base_url is not None:
|
|
130
133
|
req["dimensions"] = self.dimensions
|
|
131
134
|
if self.request_params:
|
|
132
135
|
req.update(self.request_params)
|
|
@@ -137,7 +140,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
137
140
|
usage = response.usage
|
|
138
141
|
return embedding, usage.model_dump() if usage else None
|
|
139
142
|
except Exception as e:
|
|
140
|
-
|
|
143
|
+
log_warning(f"Error getting embedding: {e}")
|
|
141
144
|
return [], None
|
|
142
145
|
|
|
143
146
|
async def async_get_embeddings_batch_and_usage(
|
|
@@ -154,7 +157,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
154
157
|
"""
|
|
155
158
|
all_embeddings = []
|
|
156
159
|
all_usage = []
|
|
157
|
-
|
|
160
|
+
log_info(f"Getting embeddings and usage for {len(texts)} texts in batches of {self.batch_size} (async)")
|
|
158
161
|
|
|
159
162
|
for i in range(0, len(texts), self.batch_size):
|
|
160
163
|
batch_texts = texts[i : i + self.batch_size]
|
|
@@ -166,7 +169,8 @@ class OpenAIEmbedder(Embedder):
|
|
|
166
169
|
}
|
|
167
170
|
if self.user is not None:
|
|
168
171
|
req["user"] = self.user
|
|
169
|
-
|
|
172
|
+
# Pass dimensions for text-embedding-3 models or when using custom base_url (third-party APIs)
|
|
173
|
+
if self.id.startswith("text-embedding-3") or self.base_url is not None:
|
|
170
174
|
req["dimensions"] = self.dimensions
|
|
171
175
|
if self.request_params:
|
|
172
176
|
req.update(self.request_params)
|
|
@@ -180,7 +184,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
180
184
|
usage_dict = response.usage.model_dump() if response.usage else None
|
|
181
185
|
all_usage.extend([usage_dict] * len(batch_embeddings))
|
|
182
186
|
except Exception as e:
|
|
183
|
-
|
|
187
|
+
log_warning(f"Error in async batch embedding: {e}")
|
|
184
188
|
# Fallback to individual calls for this batch
|
|
185
189
|
for text in batch_texts:
|
|
186
190
|
try:
|
|
@@ -188,7 +192,7 @@ class OpenAIEmbedder(Embedder):
|
|
|
188
192
|
all_embeddings.append(embedding)
|
|
189
193
|
all_usage.append(usage)
|
|
190
194
|
except Exception as e2:
|
|
191
|
-
|
|
195
|
+
log_warning(f"Error in individual async embedding fallback: {e2}")
|
|
192
196
|
all_embeddings.append([])
|
|
193
197
|
all_usage.append(None)
|
|
194
198
|
|