agno 2.1.2__py3-none-any.whl → 2.3.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- agno/agent/agent.py +5540 -2273
- agno/api/api.py +2 -0
- agno/api/os.py +1 -1
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +247 -0
- agno/culture/__init__.py +3 -0
- agno/culture/manager.py +956 -0
- agno/db/async_postgres/__init__.py +3 -0
- agno/db/base.py +689 -6
- agno/db/dynamo/dynamo.py +933 -37
- agno/db/dynamo/schemas.py +174 -10
- agno/db/dynamo/utils.py +63 -4
- agno/db/firestore/firestore.py +831 -9
- agno/db/firestore/schemas.py +51 -0
- agno/db/firestore/utils.py +102 -4
- agno/db/gcs_json/gcs_json_db.py +660 -12
- agno/db/gcs_json/utils.py +60 -26
- agno/db/in_memory/in_memory_db.py +287 -14
- agno/db/in_memory/utils.py +60 -2
- agno/db/json/json_db.py +590 -14
- agno/db/json/utils.py +60 -26
- agno/db/migrations/manager.py +199 -0
- agno/db/migrations/v1_to_v2.py +43 -13
- 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 +2760 -0
- agno/db/mongo/mongo.py +879 -11
- agno/db/mongo/schemas.py +42 -0
- agno/db/mongo/utils.py +80 -8
- agno/db/mysql/__init__.py +2 -1
- agno/db/mysql/async_mysql.py +2912 -0
- agno/db/mysql/mysql.py +946 -68
- agno/db/mysql/schemas.py +72 -10
- agno/db/mysql/utils.py +198 -7
- agno/db/postgres/__init__.py +2 -1
- agno/db/postgres/async_postgres.py +2579 -0
- agno/db/postgres/postgres.py +942 -57
- agno/db/postgres/schemas.py +81 -18
- agno/db/postgres/utils.py +164 -2
- agno/db/redis/redis.py +671 -7
- agno/db/redis/schemas.py +50 -0
- agno/db/redis/utils.py +65 -7
- agno/db/schemas/__init__.py +2 -1
- agno/db/schemas/culture.py +120 -0
- agno/db/schemas/evals.py +1 -0
- agno/db/schemas/memory.py +17 -2
- agno/db/singlestore/schemas.py +63 -0
- agno/db/singlestore/singlestore.py +949 -83
- agno/db/singlestore/utils.py +60 -2
- agno/db/sqlite/__init__.py +2 -1
- agno/db/sqlite/async_sqlite.py +2911 -0
- agno/db/sqlite/schemas.py +62 -0
- agno/db/sqlite/sqlite.py +965 -46
- agno/db/sqlite/utils.py +169 -8
- agno/db/surrealdb/__init__.py +3 -0
- agno/db/surrealdb/metrics.py +292 -0
- agno/db/surrealdb/models.py +334 -0
- agno/db/surrealdb/queries.py +71 -0
- agno/db/surrealdb/surrealdb.py +1908 -0
- agno/db/surrealdb/utils.py +147 -0
- agno/db/utils.py +2 -0
- agno/eval/__init__.py +10 -0
- agno/eval/accuracy.py +75 -55
- agno/eval/agent_as_judge.py +861 -0
- agno/eval/base.py +29 -0
- agno/eval/performance.py +16 -7
- agno/eval/reliability.py +28 -16
- agno/eval/utils.py +35 -17
- agno/exceptions.py +27 -2
- agno/filters.py +354 -0
- agno/guardrails/prompt_injection.py +1 -0
- agno/hooks/__init__.py +3 -0
- agno/hooks/decorator.py +164 -0
- agno/integrations/discord/client.py +1 -1
- agno/knowledge/chunking/agentic.py +13 -10
- agno/knowledge/chunking/fixed.py +4 -1
- agno/knowledge/chunking/semantic.py +9 -4
- agno/knowledge/chunking/strategy.py +59 -15
- agno/knowledge/embedder/fastembed.py +1 -1
- agno/knowledge/embedder/nebius.py +1 -1
- agno/knowledge/embedder/ollama.py +8 -0
- agno/knowledge/embedder/openai.py +8 -8
- agno/knowledge/embedder/sentence_transformer.py +6 -2
- agno/knowledge/embedder/vllm.py +262 -0
- agno/knowledge/knowledge.py +1618 -318
- agno/knowledge/reader/base.py +6 -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 +16 -20
- agno/knowledge/reader/json_reader.py +5 -4
- agno/knowledge/reader/markdown_reader.py +8 -8
- agno/knowledge/reader/pdf_reader.py +17 -19
- agno/knowledge/reader/pptx_reader.py +101 -0
- agno/knowledge/reader/reader_factory.py +32 -3
- agno/knowledge/reader/s3_reader.py +3 -3
- agno/knowledge/reader/tavily_reader.py +193 -0
- agno/knowledge/reader/text_reader.py +22 -10
- agno/knowledge/reader/web_search_reader.py +1 -48
- agno/knowledge/reader/website_reader.py +10 -10
- agno/knowledge/reader/wikipedia_reader.py +33 -1
- agno/knowledge/types.py +1 -0
- agno/knowledge/utils.py +72 -7
- agno/media.py +22 -6
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +544 -83
- 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 +515 -40
- agno/models/aws/bedrock.py +102 -21
- agno/models/aws/claude.py +131 -274
- agno/models/azure/ai_foundry.py +41 -19
- agno/models/azure/openai_chat.py +39 -8
- agno/models/base.py +1249 -525
- agno/models/cerebras/cerebras.py +91 -21
- agno/models/cerebras/cerebras_openai.py +21 -2
- agno/models/cohere/chat.py +40 -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 +877 -80
- agno/models/google/utils.py +22 -0
- agno/models/groq/groq.py +51 -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 +44 -9
- agno/models/litellm/litellm_openai.py +18 -1
- agno/models/message.py +28 -5
- agno/models/meta/llama.py +47 -14
- agno/models/meta/llama_openai.py +22 -17
- agno/models/mistral/mistral.py +8 -4
- agno/models/nebius/nebius.py +6 -7
- agno/models/nvidia/nvidia.py +20 -3
- agno/models/ollama/chat.py +24 -8
- agno/models/openai/chat.py +104 -29
- agno/models/openai/responses.py +101 -81
- agno/models/openrouter/openrouter.py +60 -3
- agno/models/perplexity/perplexity.py +17 -1
- agno/models/portkey/portkey.py +7 -6
- agno/models/requesty/requesty.py +24 -4
- agno/models/response.py +73 -2
- agno/models/sambanova/sambanova.py +20 -3
- agno/models/siliconflow/siliconflow.py +19 -2
- agno/models/together/together.py +20 -3
- agno/models/utils.py +254 -8
- agno/models/vercel/v0.py +20 -3
- agno/models/vertexai/__init__.py +0 -0
- agno/models/vertexai/claude.py +190 -0
- agno/models/vllm/vllm.py +19 -14
- agno/models/xai/xai.py +19 -2
- agno/os/app.py +549 -152
- agno/os/auth.py +190 -3
- agno/os/config.py +23 -0
- agno/os/interfaces/a2a/router.py +8 -11
- agno/os/interfaces/a2a/utils.py +1 -1
- agno/os/interfaces/agui/router.py +18 -3
- agno/os/interfaces/agui/utils.py +152 -39
- agno/os/interfaces/slack/router.py +55 -37
- agno/os/interfaces/slack/slack.py +9 -1
- agno/os/interfaces/whatsapp/router.py +0 -1
- agno/os/interfaces/whatsapp/security.py +3 -1
- agno/os/mcp.py +110 -52
- agno/os/middleware/__init__.py +2 -0
- agno/os/middleware/jwt.py +676 -112
- agno/os/router.py +40 -1478
- agno/os/routers/agents/__init__.py +3 -0
- agno/os/routers/agents/router.py +599 -0
- agno/os/routers/agents/schema.py +261 -0
- agno/os/routers/evals/evals.py +96 -39
- agno/os/routers/evals/schemas.py +65 -33
- agno/os/routers/evals/utils.py +80 -10
- agno/os/routers/health.py +10 -4
- agno/os/routers/knowledge/knowledge.py +196 -38
- agno/os/routers/knowledge/schemas.py +82 -22
- agno/os/routers/memory/memory.py +279 -52
- agno/os/routers/memory/schemas.py +46 -17
- agno/os/routers/metrics/metrics.py +20 -8
- agno/os/routers/metrics/schemas.py +16 -16
- agno/os/routers/session/session.py +462 -34
- agno/os/routers/teams/__init__.py +3 -0
- agno/os/routers/teams/router.py +512 -0
- agno/os/routers/teams/schema.py +257 -0
- agno/os/routers/traces/__init__.py +3 -0
- agno/os/routers/traces/schemas.py +414 -0
- agno/os/routers/traces/traces.py +499 -0
- agno/os/routers/workflows/__init__.py +3 -0
- agno/os/routers/workflows/router.py +624 -0
- agno/os/routers/workflows/schema.py +75 -0
- agno/os/schema.py +256 -693
- agno/os/scopes.py +469 -0
- agno/os/utils.py +514 -36
- agno/reasoning/anthropic.py +80 -0
- agno/reasoning/gemini.py +73 -0
- agno/reasoning/openai.py +5 -0
- agno/reasoning/vertexai.py +76 -0
- agno/run/__init__.py +6 -0
- agno/run/agent.py +155 -32
- agno/run/base.py +55 -3
- agno/run/requirement.py +181 -0
- agno/run/team.py +125 -38
- agno/run/workflow.py +72 -18
- agno/session/agent.py +102 -89
- agno/session/summary.py +56 -15
- agno/session/team.py +164 -90
- agno/session/workflow.py +405 -40
- agno/table.py +10 -0
- agno/team/team.py +3974 -1903
- agno/tools/dalle.py +2 -4
- agno/tools/eleven_labs.py +23 -25
- agno/tools/exa.py +21 -16
- agno/tools/file.py +153 -23
- agno/tools/file_generation.py +16 -10
- agno/tools/firecrawl.py +15 -7
- agno/tools/function.py +193 -38
- agno/tools/gmail.py +238 -14
- agno/tools/google_drive.py +271 -0
- agno/tools/googlecalendar.py +36 -8
- agno/tools/googlesheets.py +20 -5
- agno/tools/jira.py +20 -0
- 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 +3 -3
- agno/tools/models/nebius.py +5 -5
- agno/tools/models_labs.py +20 -10
- agno/tools/nano_banana.py +151 -0
- agno/tools/notion.py +204 -0
- agno/tools/parallel.py +314 -0
- agno/tools/postgres.py +76 -36
- agno/tools/redshift.py +406 -0
- agno/tools/scrapegraph.py +1 -1
- agno/tools/shopify.py +1519 -0
- agno/tools/slack.py +18 -3
- agno/tools/spotify.py +919 -0
- agno/tools/tavily.py +146 -0
- agno/tools/toolkit.py +25 -0
- agno/tools/workflow.py +8 -1
- agno/tools/yfinance.py +12 -11
- agno/tracing/__init__.py +12 -0
- agno/tracing/exporter.py +157 -0
- agno/tracing/schemas.py +276 -0
- agno/tracing/setup.py +111 -0
- agno/utils/agent.py +938 -0
- agno/utils/cryptography.py +22 -0
- agno/utils/dttm.py +33 -0
- agno/utils/events.py +151 -3
- agno/utils/gemini.py +15 -5
- agno/utils/hooks.py +118 -4
- agno/utils/http.py +113 -2
- agno/utils/knowledge.py +12 -5
- agno/utils/log.py +1 -0
- agno/utils/mcp.py +92 -2
- agno/utils/media.py +187 -1
- agno/utils/merge_dict.py +3 -3
- agno/utils/message.py +60 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +49 -14
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +109 -16
- agno/utils/print_response/team.py +223 -30
- agno/utils/print_response/workflow.py +251 -34
- agno/utils/streamlit.py +1 -1
- agno/utils/team.py +98 -9
- agno/utils/tokens.py +657 -0
- agno/vectordb/base.py +39 -7
- agno/vectordb/cassandra/cassandra.py +21 -5
- agno/vectordb/chroma/chromadb.py +43 -12
- agno/vectordb/clickhouse/clickhousedb.py +21 -5
- agno/vectordb/couchbase/couchbase.py +29 -5
- agno/vectordb/lancedb/lance_db.py +92 -181
- agno/vectordb/langchaindb/langchaindb.py +24 -4
- agno/vectordb/lightrag/lightrag.py +17 -3
- agno/vectordb/llamaindex/llamaindexdb.py +25 -5
- agno/vectordb/milvus/milvus.py +50 -37
- agno/vectordb/mongodb/__init__.py +7 -1
- agno/vectordb/mongodb/mongodb.py +36 -30
- agno/vectordb/pgvector/pgvector.py +201 -77
- agno/vectordb/pineconedb/pineconedb.py +41 -23
- agno/vectordb/qdrant/qdrant.py +67 -54
- agno/vectordb/redis/__init__.py +9 -0
- agno/vectordb/redis/redisdb.py +682 -0
- agno/vectordb/singlestore/singlestore.py +50 -29
- agno/vectordb/surrealdb/surrealdb.py +31 -41
- agno/vectordb/upstashdb/upstashdb.py +34 -6
- agno/vectordb/weaviate/weaviate.py +53 -14
- agno/workflow/__init__.py +2 -0
- agno/workflow/agent.py +299 -0
- agno/workflow/condition.py +120 -18
- agno/workflow/loop.py +77 -10
- agno/workflow/parallel.py +231 -143
- agno/workflow/router.py +118 -17
- agno/workflow/step.py +609 -170
- agno/workflow/steps.py +73 -6
- agno/workflow/types.py +96 -21
- agno/workflow/workflow.py +2039 -262
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/METADATA +201 -66
- agno-2.3.13.dist-info/RECORD +613 -0
- agno/tools/googlesearch.py +0 -98
- agno/tools/mcp.py +0 -679
- agno/tools/memori.py +0 -339
- agno-2.1.2.dist-info/RECORD +0 -543
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/WHEEL +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/licenses/LICENSE +0 -0
- {agno-2.1.2.dist-info → agno-2.3.13.dist-info}/top_level.txt +0 -0
agno/filters.py
ADDED
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
"""Search filter expressions for filtering knowledge base documents and search results.
|
|
2
|
+
|
|
3
|
+
This module provides a set of filter operators for constructing complex search queries
|
|
4
|
+
that can be applied to knowledge bases, vector databases, and other searchable content.
|
|
5
|
+
|
|
6
|
+
Filter Types:
|
|
7
|
+
- Comparison: EQ (equals), GT (greater than), LT (less than)
|
|
8
|
+
- Inclusion: IN (value in list)
|
|
9
|
+
- Logical: AND, OR, NOT
|
|
10
|
+
|
|
11
|
+
Example:
|
|
12
|
+
>>> from agno.filters import EQ, GT, IN, AND, OR, NOT
|
|
13
|
+
>>>
|
|
14
|
+
>>> # Simple equality filter
|
|
15
|
+
>>> filter = EQ("category", "technology")
|
|
16
|
+
>>>
|
|
17
|
+
>>> # Complex filter with multiple conditions
|
|
18
|
+
>>> filter = AND(
|
|
19
|
+
... EQ("status", "published"),
|
|
20
|
+
... GT("views", 1000),
|
|
21
|
+
... IN("category", ["tech", "science"])
|
|
22
|
+
... )
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Using OR logic
|
|
25
|
+
>>> filter = OR(EQ("priority", "high"), EQ("urgent", True))
|
|
26
|
+
>>>
|
|
27
|
+
>>> # Negating conditions
|
|
28
|
+
>>> filter = NOT(EQ("status", "archived"))
|
|
29
|
+
>>>
|
|
30
|
+
>>> # Complex nested logic
|
|
31
|
+
>>> filter = OR(
|
|
32
|
+
... AND(EQ("type", "article"), GT("word_count", 500)),
|
|
33
|
+
... AND(EQ("type", "tutorial"), NOT(EQ("difficulty", "beginner")))
|
|
34
|
+
... )
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
from typing import Any, List
|
|
40
|
+
|
|
41
|
+
# ============================================================
|
|
42
|
+
# Base Expression
|
|
43
|
+
# ============================================================
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class FilterExpr:
|
|
47
|
+
"""Base class for all filter expressions.
|
|
48
|
+
|
|
49
|
+
Filters can be combined using AND, OR, and NOT classes:
|
|
50
|
+
- AND: Combine filters where both expressions must be true
|
|
51
|
+
- OR: Combine filters where either expression can be true
|
|
52
|
+
- NOT: Negate a filter expression
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
>>> # Create complex filters using AND, OR, NOT
|
|
56
|
+
>>> filter = OR(AND(EQ("status", "active"), GT("age", 18)), EQ("role", "admin"))
|
|
57
|
+
>>> # Equivalent to: (status == "active" AND age > 18) OR role == "admin"
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
# Logical operator overloads
|
|
61
|
+
def __or__(self, other: FilterExpr) -> OR:
|
|
62
|
+
"""Combine two filters with OR logic."""
|
|
63
|
+
return OR(self, other)
|
|
64
|
+
|
|
65
|
+
def __and__(self, other: FilterExpr) -> AND:
|
|
66
|
+
"""Combine two filters with AND logic."""
|
|
67
|
+
return AND(self, other)
|
|
68
|
+
|
|
69
|
+
def __invert__(self) -> NOT:
|
|
70
|
+
"""Negate a filter."""
|
|
71
|
+
return NOT(self)
|
|
72
|
+
|
|
73
|
+
def to_dict(self) -> dict:
|
|
74
|
+
"""Convert the filter expression to a dictionary representation."""
|
|
75
|
+
raise NotImplementedError("Subclasses must implement to_dict()")
|
|
76
|
+
|
|
77
|
+
def __repr__(self) -> str:
|
|
78
|
+
return f"{self.__class__.__name__}({self.__dict__})"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ============================================================
|
|
82
|
+
# Comparison & Inclusion Filters
|
|
83
|
+
# ============================================================
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class EQ(FilterExpr):
|
|
87
|
+
"""Equality filter - matches documents where a field equals a specific value.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
key: The field name to compare
|
|
91
|
+
value: The value to match against
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
>>> # Match documents where status is "published"
|
|
95
|
+
>>> filter = EQ("status", "published")
|
|
96
|
+
>>>
|
|
97
|
+
>>> # Match documents where author_id is 123
|
|
98
|
+
>>> filter = EQ("author_id", 123)
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def __init__(self, key: str, value: Any):
|
|
102
|
+
self.key = key
|
|
103
|
+
self.value = value
|
|
104
|
+
|
|
105
|
+
def to_dict(self) -> dict:
|
|
106
|
+
return {"op": "EQ", "key": self.key, "value": self.value}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class IN(FilterExpr):
|
|
110
|
+
"""Inclusion filter - matches documents where a field's value is in a list of values.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
key: The field name to check
|
|
114
|
+
values: List of acceptable values
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
>>> # Match documents where category is either "tech", "science", or "engineering"
|
|
118
|
+
>>> filter = IN("category", ["tech", "science", "engineering"])
|
|
119
|
+
>>>
|
|
120
|
+
>>> # Match documents where status is either "draft" or "published"
|
|
121
|
+
>>> filter = IN("status", ["draft", "published"])
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
def __init__(self, key: str, values: List[Any]):
|
|
125
|
+
self.key = key
|
|
126
|
+
self.values = values
|
|
127
|
+
|
|
128
|
+
def to_dict(self) -> dict:
|
|
129
|
+
return {"op": "IN", "key": self.key, "values": self.values}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class GT(FilterExpr):
|
|
133
|
+
"""Greater than filter - matches documents where a field's value is greater than a threshold.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
key: The field name to compare
|
|
137
|
+
value: The threshold value
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
>>> # Match documents where age is greater than 18
|
|
141
|
+
>>> filter = GT("age", 18)
|
|
142
|
+
>>>
|
|
143
|
+
>>> # Match documents where price is greater than 100.0
|
|
144
|
+
>>> filter = GT("price", 100.0)
|
|
145
|
+
>>>
|
|
146
|
+
>>> # Match documents created after a certain timestamp
|
|
147
|
+
>>> filter = GT("created_at", 1234567890)
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
def __init__(self, key: str, value: Any):
|
|
151
|
+
self.key = key
|
|
152
|
+
self.value = value
|
|
153
|
+
|
|
154
|
+
def to_dict(self) -> dict:
|
|
155
|
+
return {"op": "GT", "key": self.key, "value": self.value}
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class LT(FilterExpr):
|
|
159
|
+
"""Less than filter - matches documents where a field's value is less than a threshold.
|
|
160
|
+
|
|
161
|
+
Args:
|
|
162
|
+
key: The field name to compare
|
|
163
|
+
value: The threshold value
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> # Match documents where age is less than 65
|
|
167
|
+
>>> filter = LT("age", 65)
|
|
168
|
+
>>>
|
|
169
|
+
>>> # Match documents where price is less than 50.0
|
|
170
|
+
>>> filter = LT("price", 50.0)
|
|
171
|
+
>>>
|
|
172
|
+
>>> # Match documents created before a certain timestamp
|
|
173
|
+
>>> filter = LT("created_at", 1234567890)
|
|
174
|
+
"""
|
|
175
|
+
|
|
176
|
+
def __init__(self, key: str, value: Any):
|
|
177
|
+
self.key = key
|
|
178
|
+
self.value = value
|
|
179
|
+
|
|
180
|
+
def to_dict(self) -> dict:
|
|
181
|
+
return {"op": "LT", "key": self.key, "value": self.value}
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
# ============================================================
|
|
185
|
+
# Logical Operators
|
|
186
|
+
# ============================================================
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class AND(FilterExpr):
|
|
190
|
+
"""Logical AND operator - matches documents where ALL expressions are true.
|
|
191
|
+
|
|
192
|
+
Combines multiple filter expressions where every expression must be satisfied
|
|
193
|
+
for a document to match.
|
|
194
|
+
|
|
195
|
+
Args:
|
|
196
|
+
*expressions: Variable number of FilterExpr expressions to combine with AND logic
|
|
197
|
+
|
|
198
|
+
Example:
|
|
199
|
+
>>> # Match documents where status is "published" AND age > 18
|
|
200
|
+
>>> filter = AND(EQ("status", "published"), GT("age", 18))
|
|
201
|
+
>>>
|
|
202
|
+
>>> # Multiple expressions
|
|
203
|
+
>>> filter = AND(
|
|
204
|
+
... EQ("status", "active"),
|
|
205
|
+
... GT("score", 80),
|
|
206
|
+
... IN("category", ["tech", "science"])
|
|
207
|
+
... )
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
def __init__(self, *expressions: FilterExpr):
|
|
211
|
+
self.expressions = list(expressions)
|
|
212
|
+
|
|
213
|
+
def to_dict(self) -> dict:
|
|
214
|
+
return {"op": "AND", "conditions": [e.to_dict() for e in self.expressions]}
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class OR(FilterExpr):
|
|
218
|
+
"""Logical OR operator - matches documents where ANY expression is true.
|
|
219
|
+
|
|
220
|
+
Combines multiple filter expressions where at least one expression must be satisfied
|
|
221
|
+
for a document to match.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
*expressions: Variable number of FilterExpr expressions to combine with OR logic
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
>>> # Match documents where status is "published" OR status is "archived"
|
|
228
|
+
>>> filter = OR(EQ("status", "published"), EQ("status", "archived"))
|
|
229
|
+
>>>
|
|
230
|
+
>>> # Complex: Match VIP users OR users with high score
|
|
231
|
+
>>> filter = OR(
|
|
232
|
+
... EQ("membership", "VIP"),
|
|
233
|
+
... GT("score", 1000)
|
|
234
|
+
... )
|
|
235
|
+
"""
|
|
236
|
+
|
|
237
|
+
def __init__(self, *expressions: FilterExpr):
|
|
238
|
+
self.expressions = list(expressions)
|
|
239
|
+
|
|
240
|
+
def to_dict(self) -> dict:
|
|
241
|
+
return {"op": "OR", "conditions": [e.to_dict() for e in self.expressions]}
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
class NOT(FilterExpr):
|
|
245
|
+
"""Logical NOT operator - matches documents where the expression is NOT true.
|
|
246
|
+
|
|
247
|
+
Negates a filter expression, matching documents that don't satisfy the expression.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
expression: The FilterExpr expression to negate
|
|
251
|
+
|
|
252
|
+
Example:
|
|
253
|
+
>>> # Match documents where status is NOT "draft"
|
|
254
|
+
>>> filter = NOT(EQ("status", "draft"))
|
|
255
|
+
>>>
|
|
256
|
+
>>> # Exclude inactive users with low scores
|
|
257
|
+
>>> filter = NOT(AND(EQ("status", "inactive"), LT("score", 10)))
|
|
258
|
+
>>>
|
|
259
|
+
>>> # Match users who are NOT in the blocked list
|
|
260
|
+
>>> filter = NOT(IN("user_id", [101, 102, 103]))
|
|
261
|
+
"""
|
|
262
|
+
|
|
263
|
+
def __init__(self, expression: FilterExpr):
|
|
264
|
+
self.expression = expression
|
|
265
|
+
|
|
266
|
+
def to_dict(self) -> dict:
|
|
267
|
+
return {"op": "NOT", "condition": self.expression.to_dict()}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
# ============================================================
|
|
271
|
+
# Deserialization
|
|
272
|
+
# ============================================================
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def from_dict(filter_dict: dict) -> FilterExpr:
|
|
276
|
+
"""Reconstruct a FilterExpr object from its dictionary representation.
|
|
277
|
+
|
|
278
|
+
This function deserializes filter expressions that were serialized using the
|
|
279
|
+
to_dict() method, enabling filters to be passed through JSON APIs and reconstructed
|
|
280
|
+
on the server side.
|
|
281
|
+
|
|
282
|
+
Args:
|
|
283
|
+
filter_dict: Dictionary representation of a filter expression with an "op" key
|
|
284
|
+
|
|
285
|
+
Returns:
|
|
286
|
+
FilterExpr: The reconstructed filter expression object
|
|
287
|
+
|
|
288
|
+
Raises:
|
|
289
|
+
ValueError: If the filter dictionary has an invalid structure or unknown operator
|
|
290
|
+
|
|
291
|
+
Example:
|
|
292
|
+
>>> # Serialize and deserialize a simple filter
|
|
293
|
+
>>> original = EQ("status", "published")
|
|
294
|
+
>>> serialized = original.to_dict()
|
|
295
|
+
>>> # {"op": "EQ", "key": "status", "value": "published"}
|
|
296
|
+
>>> reconstructed = from_dict(serialized)
|
|
297
|
+
>>>
|
|
298
|
+
>>> # Complex filter with nested expressions
|
|
299
|
+
>>> complex_filter = OR(AND(EQ("type", "article"), GT("views", 1000)), IN("priority", ["high", "urgent"]))
|
|
300
|
+
>>> serialized = complex_filter.to_dict()
|
|
301
|
+
>>> reconstructed = from_dict(serialized)
|
|
302
|
+
>>>
|
|
303
|
+
>>> # From JSON API
|
|
304
|
+
>>> import json
|
|
305
|
+
>>> json_str = '{"op": "AND", "conditions": [{"op": "EQ", "key": "status", "value": "active"}, {"op": "GT", "key": "age", "value": 18}]}'
|
|
306
|
+
>>> filter_dict = json.loads(json_str)
|
|
307
|
+
>>> filter_expr = from_dict(filter_dict)
|
|
308
|
+
"""
|
|
309
|
+
if not isinstance(filter_dict, dict) or "op" not in filter_dict:
|
|
310
|
+
raise ValueError(f"Invalid filter dictionary: must contain 'op' key. Got: {filter_dict}")
|
|
311
|
+
|
|
312
|
+
op = filter_dict["op"]
|
|
313
|
+
|
|
314
|
+
# Comparison and inclusion operators
|
|
315
|
+
if op == "EQ":
|
|
316
|
+
if "key" not in filter_dict or "value" not in filter_dict:
|
|
317
|
+
raise ValueError(f"EQ filter requires 'key' and 'value' fields. Got: {filter_dict}")
|
|
318
|
+
return EQ(filter_dict["key"], filter_dict["value"])
|
|
319
|
+
|
|
320
|
+
elif op == "IN":
|
|
321
|
+
if "key" not in filter_dict or "values" not in filter_dict:
|
|
322
|
+
raise ValueError(f"IN filter requires 'key' and 'values' fields. Got: {filter_dict}")
|
|
323
|
+
return IN(filter_dict["key"], filter_dict["values"])
|
|
324
|
+
|
|
325
|
+
elif op == "GT":
|
|
326
|
+
if "key" not in filter_dict or "value" not in filter_dict:
|
|
327
|
+
raise ValueError(f"GT filter requires 'key' and 'value' fields. Got: {filter_dict}")
|
|
328
|
+
return GT(filter_dict["key"], filter_dict["value"])
|
|
329
|
+
|
|
330
|
+
elif op == "LT":
|
|
331
|
+
if "key" not in filter_dict or "value" not in filter_dict:
|
|
332
|
+
raise ValueError(f"LT filter requires 'key' and 'value' fields. Got: {filter_dict}")
|
|
333
|
+
return LT(filter_dict["key"], filter_dict["value"])
|
|
334
|
+
|
|
335
|
+
# Logical operators
|
|
336
|
+
elif op == "AND":
|
|
337
|
+
if "conditions" not in filter_dict:
|
|
338
|
+
raise ValueError(f"AND filter requires 'conditions' field. Got: {filter_dict}")
|
|
339
|
+
conditions = [from_dict(cond) for cond in filter_dict["conditions"]]
|
|
340
|
+
return AND(*conditions)
|
|
341
|
+
|
|
342
|
+
elif op == "OR":
|
|
343
|
+
if "conditions" not in filter_dict:
|
|
344
|
+
raise ValueError(f"OR filter requires 'conditions' field. Got: {filter_dict}")
|
|
345
|
+
conditions = [from_dict(cond) for cond in filter_dict["conditions"]]
|
|
346
|
+
return OR(*conditions)
|
|
347
|
+
|
|
348
|
+
elif op == "NOT":
|
|
349
|
+
if "condition" not in filter_dict:
|
|
350
|
+
raise ValueError(f"NOT filter requires 'condition' field. Got: {filter_dict}")
|
|
351
|
+
return NOT(from_dict(filter_dict["condition"]))
|
|
352
|
+
|
|
353
|
+
else:
|
|
354
|
+
raise ValueError(f"Unknown filter operator: {op}")
|
agno/hooks/__init__.py
ADDED
agno/hooks/decorator.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from functools import wraps
|
|
2
|
+
from typing import Any, Callable, TypeVar, Union, overload
|
|
3
|
+
|
|
4
|
+
# Type variable for better type hints
|
|
5
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
6
|
+
|
|
7
|
+
# Attribute name used to mark hooks for background execution
|
|
8
|
+
HOOK_RUN_IN_BACKGROUND_ATTR = "_agno_run_in_background"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _is_async_function(func: Callable) -> bool:
|
|
12
|
+
"""
|
|
13
|
+
Check if a function is async, even when wrapped by decorators like @staticmethod.
|
|
14
|
+
Traverses the full wrapper chain to find the original function.
|
|
15
|
+
"""
|
|
16
|
+
from inspect import iscoroutinefunction, unwrap
|
|
17
|
+
|
|
18
|
+
# First, try the standard inspect function on the wrapper
|
|
19
|
+
if iscoroutinefunction(func):
|
|
20
|
+
return True
|
|
21
|
+
|
|
22
|
+
# Use unwrap to traverse the full __wrapped__ chain to the original function
|
|
23
|
+
try:
|
|
24
|
+
original_func = unwrap(func)
|
|
25
|
+
if original_func is not func and iscoroutinefunction(original_func):
|
|
26
|
+
return True
|
|
27
|
+
except ValueError:
|
|
28
|
+
# unwrap raises ValueError if it hits a cycle
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
# Check if the function has CO_COROUTINE flag in its code object
|
|
32
|
+
try:
|
|
33
|
+
if hasattr(func, "__code__") and func.__code__.co_flags & 0x80: # CO_COROUTINE flag
|
|
34
|
+
return True
|
|
35
|
+
except (AttributeError, TypeError):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
return False
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@overload
|
|
42
|
+
def hook() -> Callable[[F], F]: ...
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@overload
|
|
46
|
+
def hook(
|
|
47
|
+
*,
|
|
48
|
+
run_in_background: bool = False,
|
|
49
|
+
) -> Callable[[F], F]: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@overload
|
|
53
|
+
def hook(func: F) -> F: ...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def hook(*args, **kwargs) -> Union[F, Callable[[F], F]]:
|
|
57
|
+
"""Decorator to configure hook behavior.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
run_in_background: If True, this hook will be scheduled as a FastAPI background task
|
|
61
|
+
when background_tasks is available, regardless of the agent/team's
|
|
62
|
+
run_hooks_in_background setting. This allows per-hook control over
|
|
63
|
+
background execution. This is only use-able when running with AgentOS.
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Union[F, Callable[[F], F]]: Decorated function or decorator
|
|
67
|
+
|
|
68
|
+
Examples:
|
|
69
|
+
@hook
|
|
70
|
+
def my_hook(run_output, agent):
|
|
71
|
+
# This runs normally (blocking)
|
|
72
|
+
process_output(run_output.content)
|
|
73
|
+
|
|
74
|
+
@hook()
|
|
75
|
+
def another_hook(run_output, agent):
|
|
76
|
+
# Same as above - runs normally
|
|
77
|
+
process_output(run_output.content)
|
|
78
|
+
|
|
79
|
+
@hook(run_in_background=True)
|
|
80
|
+
def my_background_hook(run_output, agent):
|
|
81
|
+
# This will run in the background when background_tasks is available
|
|
82
|
+
send_notification(run_output.content)
|
|
83
|
+
|
|
84
|
+
@hook(run_in_background=True)
|
|
85
|
+
async def my_async_background_hook(run_output, agent):
|
|
86
|
+
# Async hooks also supported
|
|
87
|
+
await send_async_notification(run_output.content)
|
|
88
|
+
|
|
89
|
+
agent = Agent(
|
|
90
|
+
model=OpenAIChat(id="gpt-4o"),
|
|
91
|
+
post_hooks=[my_hook, my_background_hook],
|
|
92
|
+
)
|
|
93
|
+
"""
|
|
94
|
+
# Valid kwargs for the hook decorator
|
|
95
|
+
VALID_KWARGS = frozenset({"run_in_background"})
|
|
96
|
+
|
|
97
|
+
# Validate kwargs
|
|
98
|
+
invalid_kwargs = set(kwargs.keys()) - VALID_KWARGS
|
|
99
|
+
if invalid_kwargs:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
f"Invalid hook configuration arguments: {invalid_kwargs}. Valid arguments are: {sorted(VALID_KWARGS)}"
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def decorator(func: F) -> F:
|
|
105
|
+
run_in_background = kwargs.get("run_in_background", False)
|
|
106
|
+
|
|
107
|
+
# Preserve existing hook attributes from previously applied decorators
|
|
108
|
+
# Use OR logic: if any decorator sets run_in_background=True, it stays True
|
|
109
|
+
existing_run_in_background = should_run_in_background(func)
|
|
110
|
+
final_run_in_background = run_in_background or existing_run_in_background
|
|
111
|
+
|
|
112
|
+
@wraps(func)
|
|
113
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
114
|
+
return func(*args, **kwargs)
|
|
115
|
+
|
|
116
|
+
@wraps(func)
|
|
117
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
118
|
+
return await func(*args, **kwargs)
|
|
119
|
+
|
|
120
|
+
# Choose appropriate wrapper based on function type
|
|
121
|
+
if _is_async_function(func):
|
|
122
|
+
wrapper = async_wrapper
|
|
123
|
+
else:
|
|
124
|
+
wrapper = sync_wrapper
|
|
125
|
+
|
|
126
|
+
# Set the background execution attribute (combined from all decorators)
|
|
127
|
+
setattr(wrapper, HOOK_RUN_IN_BACKGROUND_ATTR, final_run_in_background)
|
|
128
|
+
|
|
129
|
+
return wrapper # type: ignore
|
|
130
|
+
|
|
131
|
+
# Handle both @hook and @hook() cases
|
|
132
|
+
if len(args) == 1 and callable(args[0]) and not kwargs:
|
|
133
|
+
return decorator(args[0])
|
|
134
|
+
|
|
135
|
+
return decorator
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def should_run_in_background(hook_func: Callable) -> bool:
|
|
139
|
+
"""
|
|
140
|
+
Check if a hook function is marked to run in background.
|
|
141
|
+
Traverses the wrapper chain to find the attribute when multiple decorators are stacked.
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
hook_func: The hook function to check
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
True if the hook is decorated with @hook(run_in_background=True)
|
|
148
|
+
"""
|
|
149
|
+
# Check the function directly first
|
|
150
|
+
if hasattr(hook_func, HOOK_RUN_IN_BACKGROUND_ATTR):
|
|
151
|
+
return getattr(hook_func, HOOK_RUN_IN_BACKGROUND_ATTR)
|
|
152
|
+
|
|
153
|
+
# Traverse the wrapper chain to find the attribute
|
|
154
|
+
current = hook_func
|
|
155
|
+
seen: set[int] = set()
|
|
156
|
+
while hasattr(current, "__wrapped__"):
|
|
157
|
+
if id(current) in seen:
|
|
158
|
+
break
|
|
159
|
+
seen.add(id(current))
|
|
160
|
+
current = current.__wrapped__
|
|
161
|
+
if hasattr(current, HOOK_RUN_IN_BACKGROUND_ATTR):
|
|
162
|
+
return getattr(current, HOOK_RUN_IN_BACKGROUND_ATTR)
|
|
163
|
+
|
|
164
|
+
return False
|
|
@@ -14,7 +14,7 @@ try:
|
|
|
14
14
|
import discord
|
|
15
15
|
|
|
16
16
|
except (ImportError, ModuleNotFoundError):
|
|
17
|
-
|
|
17
|
+
raise ImportError("`discord.py` not installed. Please install using `pip install discord.py`")
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class RequiresConfirmationView(discord.ui.View):
|
|
@@ -1,28 +1,31 @@
|
|
|
1
|
-
from typing import List, Optional
|
|
1
|
+
from typing import List, Optional, Union
|
|
2
2
|
|
|
3
3
|
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
4
4
|
from agno.knowledge.document.base import Document
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.defaults import DEFAULT_OPENAI_MODEL_ID
|
|
7
7
|
from agno.models.message import Message
|
|
8
|
+
from agno.models.utils import get_model
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class AgenticChunking(ChunkingStrategy):
|
|
11
12
|
"""Chunking strategy that uses an LLM to determine natural breakpoints in the text"""
|
|
12
13
|
|
|
13
|
-
def __init__(self, model: Optional[Model] = None, max_chunk_size: int = 5000):
|
|
14
|
+
def __init__(self, model: Optional[Union[Model, str]] = None, max_chunk_size: int = 5000):
|
|
15
|
+
# Convert model string to Model instance
|
|
16
|
+
model = get_model(model)
|
|
14
17
|
if model is None:
|
|
15
18
|
try:
|
|
16
19
|
from agno.models.openai import OpenAIChat
|
|
17
20
|
except Exception:
|
|
18
21
|
raise ValueError("`openai` isn't installed. Please install it with `pip install openai`")
|
|
19
22
|
model = OpenAIChat(DEFAULT_OPENAI_MODEL_ID)
|
|
20
|
-
self.
|
|
23
|
+
self.chunk_size = max_chunk_size
|
|
21
24
|
self.model = model
|
|
22
25
|
|
|
23
26
|
def chunk(self, document: Document) -> List[Document]:
|
|
24
27
|
"""Split text into chunks using LLM to determine natural breakpoints based on context"""
|
|
25
|
-
if len(document.content) <= self.
|
|
28
|
+
if len(document.content) <= self.chunk_size:
|
|
26
29
|
return [document]
|
|
27
30
|
|
|
28
31
|
chunks: List[Document] = []
|
|
@@ -31,22 +34,22 @@ class AgenticChunking(ChunkingStrategy):
|
|
|
31
34
|
chunk_number = 1
|
|
32
35
|
|
|
33
36
|
while remaining_text:
|
|
34
|
-
# Ask model to find a good breakpoint within
|
|
35
|
-
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.
|
|
37
|
+
# Ask model to find a good breakpoint within chunk_size
|
|
38
|
+
prompt = f"""Analyze this text and determine a natural breakpoint within the first {self.chunk_size} characters.
|
|
36
39
|
Consider semantic completeness, paragraph boundaries, and topic transitions.
|
|
37
40
|
Return only the character position number of where to break the text:
|
|
38
41
|
|
|
39
|
-
{remaining_text[: self.
|
|
42
|
+
{remaining_text[: self.chunk_size]}"""
|
|
40
43
|
|
|
41
44
|
try:
|
|
42
45
|
response = self.model.response([Message(role="user", content=prompt)])
|
|
43
46
|
if response and response.content:
|
|
44
|
-
break_point = min(int(response.content.strip()), self.
|
|
47
|
+
break_point = min(int(response.content.strip()), self.chunk_size)
|
|
45
48
|
else:
|
|
46
|
-
break_point = self.
|
|
49
|
+
break_point = self.chunk_size
|
|
47
50
|
except Exception:
|
|
48
51
|
# Fallback to max size if model fails
|
|
49
|
-
break_point = self.
|
|
52
|
+
break_point = self.chunk_size
|
|
50
53
|
|
|
51
54
|
# Extract chunk and update remaining text
|
|
52
55
|
chunk = remaining_text[:break_point].strip()
|
agno/knowledge/chunking/fixed.py
CHANGED
|
@@ -53,5 +53,8 @@ class FixedSizeChunking(ChunkingStrategy):
|
|
|
53
53
|
)
|
|
54
54
|
)
|
|
55
55
|
chunk_number += 1
|
|
56
|
-
start
|
|
56
|
+
# Ensure start always advances by at least 1 to prevent infinite loops
|
|
57
|
+
# when overlap is large relative to chunk_size
|
|
58
|
+
new_start = max(start + 1, end - self.overlap)
|
|
59
|
+
start = new_start
|
|
57
60
|
return chunked_documents
|
|
@@ -4,14 +4,19 @@ from typing import Any, Dict, List, Optional
|
|
|
4
4
|
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
5
5
|
from agno.knowledge.document.base import Document
|
|
6
6
|
from agno.knowledge.embedder.base import Embedder
|
|
7
|
-
from agno.
|
|
7
|
+
from agno.utils.log import log_info
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class SemanticChunking(ChunkingStrategy):
|
|
11
11
|
"""Chunking strategy that splits text into semantic chunks using chonkie"""
|
|
12
12
|
|
|
13
13
|
def __init__(self, embedder: Optional[Embedder] = None, chunk_size: int = 5000, similarity_threshold: float = 0.5):
|
|
14
|
-
|
|
14
|
+
if embedder is None:
|
|
15
|
+
from agno.knowledge.embedder.openai import OpenAIEmbedder
|
|
16
|
+
|
|
17
|
+
embedder = OpenAIEmbedder() # type: ignore
|
|
18
|
+
log_info("Embedder not provided, using OpenAIEmbedder as default.")
|
|
19
|
+
self.embedder = embedder
|
|
15
20
|
self.chunk_size = chunk_size
|
|
16
21
|
self.similarity_threshold = similarity_threshold
|
|
17
22
|
self.chunker = None # Will be initialized lazily when needed
|
|
@@ -50,10 +55,10 @@ class SemanticChunking(ChunkingStrategy):
|
|
|
50
55
|
# Fallback to model id
|
|
51
56
|
params["embedding_model"] = getattr(self.embedder, "id", None) or "text-embedding-3-small"
|
|
52
57
|
|
|
53
|
-
self.chunker = SemanticChunker(**params)
|
|
58
|
+
self.chunker = SemanticChunker(**params) # type: ignore
|
|
54
59
|
except Exception:
|
|
55
60
|
# As a final fallback, use the original behavior
|
|
56
|
-
self.chunker = SemanticChunker(
|
|
61
|
+
self.chunker = SemanticChunker( # type: ignore
|
|
57
62
|
embedding_model=getattr(self.embedder, "id", None) or "text-embedding-3-small",
|
|
58
63
|
chunk_size=self.chunk_size,
|
|
59
64
|
threshold=self.similarity_threshold,
|