agno 2.0.5__py3-none-any.whl → 2.0.6__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 +53 -17
- agno/db/dynamo/dynamo.py +7 -5
- agno/db/firestore/firestore.py +4 -2
- agno/db/gcs_json/gcs_json_db.py +4 -2
- agno/db/json/json_db.py +8 -4
- agno/db/mongo/mongo.py +6 -4
- agno/db/mysql/mysql.py +2 -1
- agno/db/postgres/postgres.py +2 -1
- agno/db/redis/redis.py +1 -1
- agno/db/singlestore/singlestore.py +2 -2
- agno/db/sqlite/sqlite.py +1 -1
- agno/knowledge/embedder/openai.py +19 -11
- agno/knowledge/knowledge.py +4 -3
- agno/knowledge/reader/website_reader.py +33 -16
- agno/media.py +70 -0
- agno/models/aimlapi/aimlapi.py +2 -2
- agno/models/base.py +31 -4
- agno/models/cerebras/cerebras_openai.py +2 -2
- agno/models/deepinfra/deepinfra.py +2 -2
- agno/models/deepseek/deepseek.py +2 -2
- agno/models/fireworks/fireworks.py +2 -2
- agno/models/internlm/internlm.py +2 -2
- agno/models/langdb/langdb.py +4 -4
- agno/models/litellm/litellm_openai.py +2 -2
- agno/models/message.py +26 -0
- agno/models/meta/llama_openai.py +2 -2
- agno/models/nebius/nebius.py +2 -2
- agno/models/nexus/__init__.py +3 -0
- agno/models/nexus/nexus.py +25 -0
- agno/models/nvidia/nvidia.py +2 -2
- agno/models/openrouter/openrouter.py +2 -2
- agno/models/perplexity/perplexity.py +2 -2
- agno/models/portkey/portkey.py +3 -3
- agno/models/response.py +2 -1
- agno/models/sambanova/sambanova.py +2 -2
- agno/models/together/together.py +2 -2
- agno/models/vercel/v0.py +2 -2
- agno/models/xai/xai.py +2 -2
- agno/os/router.py +3 -1
- agno/os/utils.py +1 -1
- agno/run/agent.py +16 -0
- agno/run/team.py +15 -0
- agno/run/workflow.py +10 -0
- agno/team/team.py +37 -7
- agno/tools/e2b.py +14 -7
- agno/tools/file_generation.py +350 -0
- agno/tools/function.py +2 -0
- agno/utils/gemini.py +24 -4
- agno/vectordb/chroma/chromadb.py +66 -25
- agno/vectordb/lancedb/lance_db.py +15 -4
- agno/vectordb/milvus/milvus.py +6 -0
- agno/workflow/workflow.py +4 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/METADATA +4 -1
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/RECORD +57 -54
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/WHEEL +0 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.5.dist-info → agno-2.0.6.dist-info}/top_level.txt +0 -0
agno/agent/agent.py
CHANGED
|
@@ -371,7 +371,7 @@ class Agent:
|
|
|
371
371
|
knowledge_retriever: Optional[Callable[..., Optional[List[Union[Dict, str]]]]] = None,
|
|
372
372
|
references_format: Literal["json", "yaml"] = "json",
|
|
373
373
|
metadata: Optional[Dict[str, Any]] = None,
|
|
374
|
-
tools: Optional[
|
|
374
|
+
tools: Optional[Sequence[Union[Toolkit, Callable, Function, Dict]]] = None,
|
|
375
375
|
tool_call_limit: Optional[int] = None,
|
|
376
376
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
377
377
|
tool_hooks: Optional[List[Callable]] = None,
|
|
@@ -466,7 +466,7 @@ class Agent:
|
|
|
466
466
|
|
|
467
467
|
self.metadata = metadata
|
|
468
468
|
|
|
469
|
-
self.tools = tools
|
|
469
|
+
self.tools = list(tools) if tools else []
|
|
470
470
|
self.tool_call_limit = tool_call_limit
|
|
471
471
|
self.tool_choice = tool_choice
|
|
472
472
|
self.tool_hooks = tool_hooks
|
|
@@ -681,8 +681,8 @@ class Agent:
|
|
|
681
681
|
self.tools.append(tool)
|
|
682
682
|
self._rebuild_tools = True
|
|
683
683
|
|
|
684
|
-
def set_tools(self, tools:
|
|
685
|
-
self.tools = tools
|
|
684
|
+
def set_tools(self, tools: Sequence[Union[Toolkit, Callable, Function, Dict]]):
|
|
685
|
+
self.tools = list(tools) if tools else []
|
|
686
686
|
self._rebuild_tools = True
|
|
687
687
|
|
|
688
688
|
def _initialize_session(
|
|
@@ -1080,13 +1080,17 @@ class Agent:
|
|
|
1080
1080
|
# Initialize the Agent
|
|
1081
1081
|
self.initialize_agent(debug_mode=debug_mode)
|
|
1082
1082
|
|
|
1083
|
-
image_artifacts, video_artifacts, audio_artifacts = self._validate_media_object_id(
|
|
1084
|
-
images=images, videos=videos, audios=audio
|
|
1083
|
+
image_artifacts, video_artifacts, audio_artifacts, file_artifacts = self._validate_media_object_id(
|
|
1084
|
+
images=images, videos=videos, audios=audio, files=files
|
|
1085
1085
|
)
|
|
1086
1086
|
|
|
1087
1087
|
# Create RunInput to capture the original user input
|
|
1088
1088
|
run_input = RunInput(
|
|
1089
|
-
input_content=input,
|
|
1089
|
+
input_content=input,
|
|
1090
|
+
images=image_artifacts,
|
|
1091
|
+
videos=video_artifacts,
|
|
1092
|
+
audios=audio_artifacts,
|
|
1093
|
+
files=file_artifacts,
|
|
1090
1094
|
)
|
|
1091
1095
|
|
|
1092
1096
|
# Read existing session from database
|
|
@@ -1713,13 +1717,17 @@ class Agent:
|
|
|
1713
1717
|
# Initialize the Agent
|
|
1714
1718
|
self.initialize_agent(debug_mode=debug_mode)
|
|
1715
1719
|
|
|
1716
|
-
image_artifacts, video_artifacts, audio_artifacts = self._validate_media_object_id(
|
|
1717
|
-
images=images, videos=videos, audios=audio
|
|
1720
|
+
image_artifacts, video_artifacts, audio_artifacts, file_artifacts = self._validate_media_object_id(
|
|
1721
|
+
images=images, videos=videos, audios=audio, files=files
|
|
1718
1722
|
)
|
|
1719
1723
|
|
|
1720
1724
|
# Create RunInput to capture the original user input
|
|
1721
1725
|
run_input = RunInput(
|
|
1722
|
-
input_content=input,
|
|
1726
|
+
input_content=input,
|
|
1727
|
+
images=image_artifacts,
|
|
1728
|
+
videos=video_artifacts,
|
|
1729
|
+
audios=audio_artifacts,
|
|
1730
|
+
files=file_artifacts,
|
|
1723
1731
|
)
|
|
1724
1732
|
|
|
1725
1733
|
# Read existing session from storage
|
|
@@ -3063,6 +3071,10 @@ class Agent:
|
|
|
3063
3071
|
for audio in model_response.audios:
|
|
3064
3072
|
self._add_audio(audio, run_response) # Generated audio go to run_response.audio
|
|
3065
3073
|
|
|
3074
|
+
if model_response.files is not None:
|
|
3075
|
+
for file in model_response.files:
|
|
3076
|
+
self._add_file(file, run_response) # Generated files go to run_response.files
|
|
3077
|
+
|
|
3066
3078
|
def _update_run_response(self, model_response: ModelResponse, run_response: RunOutput, run_messages: RunMessages):
|
|
3067
3079
|
# Handle structured outputs
|
|
3068
3080
|
if self.output_schema is not None and model_response.parsed is not None:
|
|
@@ -3125,7 +3137,8 @@ class Agent:
|
|
|
3125
3137
|
"""Calculate session metrics"""
|
|
3126
3138
|
session_metrics = self._get_session_metrics(session=session)
|
|
3127
3139
|
# Add the metrics for the current run to the session metrics
|
|
3128
|
-
|
|
3140
|
+
if run_response.metrics is not None:
|
|
3141
|
+
session_metrics += run_response.metrics
|
|
3129
3142
|
session_metrics.time_to_first_token = None
|
|
3130
3143
|
if session.session_data is not None:
|
|
3131
3144
|
session.session_data["session_metrics"] = session_metrics
|
|
@@ -3800,7 +3813,7 @@ class Agent:
|
|
|
3800
3813
|
self._rebuild_tools = True
|
|
3801
3814
|
if self.search_session_history:
|
|
3802
3815
|
agent_tools.append(
|
|
3803
|
-
self._get_previous_sessions_messages_function(num_history_sessions=self.num_history_sessions)
|
|
3816
|
+
self._get_previous_sessions_messages_function(num_history_sessions=self.num_history_sessions, user_id=user_id)
|
|
3804
3817
|
)
|
|
3805
3818
|
self._rebuild_tools = True
|
|
3806
3819
|
|
|
@@ -4948,7 +4961,7 @@ class Agent:
|
|
|
4948
4961
|
# 1. If build_user_context is False or message is a list, return the message as is.
|
|
4949
4962
|
if not self.build_user_context:
|
|
4950
4963
|
return Message(
|
|
4951
|
-
role=self.user_message_role,
|
|
4964
|
+
role=self.user_message_role or "user",
|
|
4952
4965
|
content=input,
|
|
4953
4966
|
images=None if not self.send_media_to_model else images,
|
|
4954
4967
|
audio=None if not self.send_media_to_model else audio,
|
|
@@ -4961,7 +4974,7 @@ class Agent:
|
|
|
4961
4974
|
# If we have any media, return a message with empty content
|
|
4962
4975
|
if images is not None or audio is not None or videos is not None or files is not None:
|
|
4963
4976
|
return Message(
|
|
4964
|
-
role=self.user_message_role,
|
|
4977
|
+
role=self.user_message_role or "user",
|
|
4965
4978
|
content="",
|
|
4966
4979
|
images=None if not self.send_media_to_model else images,
|
|
4967
4980
|
audio=None if not self.send_media_to_model else audio,
|
|
@@ -5716,6 +5729,13 @@ class Agent:
|
|
|
5716
5729
|
run_response.audio = []
|
|
5717
5730
|
run_response.audio.append(audio)
|
|
5718
5731
|
|
|
5732
|
+
def _add_file(self, file: File, run_response: RunOutput) -> None:
|
|
5733
|
+
"""Add file to both the agent's stateful storage and the current run response"""
|
|
5734
|
+
# Add to run response
|
|
5735
|
+
if run_response.files is None:
|
|
5736
|
+
run_response.files = []
|
|
5737
|
+
run_response.files.append(file)
|
|
5738
|
+
|
|
5719
5739
|
###########################################################################
|
|
5720
5740
|
# Reasoning
|
|
5721
5741
|
###########################################################################
|
|
@@ -6779,11 +6799,14 @@ class Agent:
|
|
|
6779
6799
|
)
|
|
6780
6800
|
return "Successfully added to knowledge base"
|
|
6781
6801
|
|
|
6782
|
-
def _get_previous_sessions_messages_function(
|
|
6802
|
+
def _get_previous_sessions_messages_function(
|
|
6803
|
+
self, num_history_sessions: Optional[int] = 2, user_id: Optional[str] = None
|
|
6804
|
+
) -> Callable:
|
|
6783
6805
|
"""Factory function to create a get_previous_session_messages function.
|
|
6784
6806
|
|
|
6785
6807
|
Args:
|
|
6786
6808
|
num_history_sessions: The last n sessions to be taken from db
|
|
6809
|
+
user_id: The user ID to filter sessions by
|
|
6787
6810
|
|
|
6788
6811
|
Returns:
|
|
6789
6812
|
Callable: A function that retrieves messages from previous sessions
|
|
@@ -6802,7 +6825,9 @@ class Agent:
|
|
|
6802
6825
|
if self.db is None:
|
|
6803
6826
|
return "Previous session messages not available"
|
|
6804
6827
|
|
|
6805
|
-
selected_sessions = self.db.get_sessions(
|
|
6828
|
+
selected_sessions = self.db.get_sessions(
|
|
6829
|
+
session_type=SessionType.AGENT, limit=num_history_sessions, user_id=user_id
|
|
6830
|
+
)
|
|
6806
6831
|
|
|
6807
6832
|
all_messages = []
|
|
6808
6833
|
seen_message_pairs = set()
|
|
@@ -7199,6 +7224,7 @@ class Agent:
|
|
|
7199
7224
|
images: Optional[Sequence[Image]] = None,
|
|
7200
7225
|
videos: Optional[Sequence[Video]] = None,
|
|
7201
7226
|
audios: Optional[Sequence[Audio]] = None,
|
|
7227
|
+
files: Optional[Sequence[File]] = None,
|
|
7202
7228
|
) -> tuple:
|
|
7203
7229
|
"""Convert raw Image/Video/Audio objects - now unified, so just return as-is."""
|
|
7204
7230
|
# With unified classes, no conversion needed - just ensure IDs are set
|
|
@@ -7233,7 +7259,17 @@ class Agent:
|
|
|
7233
7259
|
aud.id = str(uuid4())
|
|
7234
7260
|
audio_list.append(aud)
|
|
7235
7261
|
|
|
7236
|
-
|
|
7262
|
+
file_list = None
|
|
7263
|
+
if files:
|
|
7264
|
+
file_list = []
|
|
7265
|
+
for file in files:
|
|
7266
|
+
if not file.id:
|
|
7267
|
+
from uuid import uuid4
|
|
7268
|
+
|
|
7269
|
+
file.id = str(uuid4())
|
|
7270
|
+
file_list.append(file)
|
|
7271
|
+
|
|
7272
|
+
return image_list, video_list, audio_list, file_list
|
|
7237
7273
|
|
|
7238
7274
|
def cli_app(
|
|
7239
7275
|
self,
|
agno/db/dynamo/dynamo.py
CHANGED
|
@@ -181,7 +181,7 @@ class DynamoDb(BaseDb):
|
|
|
181
181
|
|
|
182
182
|
# --- Sessions ---
|
|
183
183
|
|
|
184
|
-
def delete_session(self, session_id: Optional[str] = None
|
|
184
|
+
def delete_session(self, session_id: Optional[str] = None) -> bool:
|
|
185
185
|
"""
|
|
186
186
|
Delete a session from the database.
|
|
187
187
|
|
|
@@ -236,7 +236,7 @@ class DynamoDb(BaseDb):
|
|
|
236
236
|
def get_session(
|
|
237
237
|
self,
|
|
238
238
|
session_id: str,
|
|
239
|
-
session_type:
|
|
239
|
+
session_type: SessionType,
|
|
240
240
|
user_id: Optional[str] = None,
|
|
241
241
|
deserialize: Optional[bool] = True,
|
|
242
242
|
) -> Optional[Union[Session, Dict[str, Any]]]:
|
|
@@ -245,7 +245,7 @@ class DynamoDb(BaseDb):
|
|
|
245
245
|
|
|
246
246
|
Args:
|
|
247
247
|
session_id (str): The ID of the session to get.
|
|
248
|
-
session_type (
|
|
248
|
+
session_type (SessionType): The type of session to get.
|
|
249
249
|
user_id (Optional[str]): The ID of the user to get the session for.
|
|
250
250
|
deserialize (Optional[bool]): Whether to deserialize the session.
|
|
251
251
|
|
|
@@ -268,7 +268,7 @@ class DynamoDb(BaseDb):
|
|
|
268
268
|
|
|
269
269
|
session = deserialize_from_dynamodb_item(item)
|
|
270
270
|
|
|
271
|
-
if
|
|
271
|
+
if session.get("session_type") != session_type.value:
|
|
272
272
|
return None
|
|
273
273
|
if user_id and session.get("user_id") != user_id:
|
|
274
274
|
return None
|
|
@@ -283,8 +283,10 @@ class DynamoDb(BaseDb):
|
|
|
283
283
|
return AgentSession.from_dict(session)
|
|
284
284
|
elif session_type == SessionType.TEAM:
|
|
285
285
|
return TeamSession.from_dict(session)
|
|
286
|
-
|
|
286
|
+
elif session_type == SessionType.WORKFLOW:
|
|
287
287
|
return WorkflowSession.from_dict(session)
|
|
288
|
+
else:
|
|
289
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
288
290
|
|
|
289
291
|
except Exception as e:
|
|
290
292
|
log_error(f"Failed to get session {session_id}: {e}")
|
agno/db/firestore/firestore.py
CHANGED
|
@@ -242,8 +242,8 @@ class FirestoreDb(BaseDb):
|
|
|
242
242
|
|
|
243
243
|
Args:
|
|
244
244
|
session_id (str): The ID of the session to get.
|
|
245
|
+
session_type (SessionType): The type of session to get.
|
|
245
246
|
user_id (Optional[str]): The ID of the user to get the session for.
|
|
246
|
-
session_type (Optional[SessionType]): The type of session to get.
|
|
247
247
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
248
248
|
|
|
249
249
|
Returns:
|
|
@@ -281,8 +281,10 @@ class FirestoreDb(BaseDb):
|
|
|
281
281
|
return AgentSession.from_dict(session)
|
|
282
282
|
elif session_type == SessionType.TEAM:
|
|
283
283
|
return TeamSession.from_dict(session)
|
|
284
|
-
|
|
284
|
+
elif session_type == SessionType.WORKFLOW:
|
|
285
285
|
return WorkflowSession.from_dict(session)
|
|
286
|
+
else:
|
|
287
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
286
288
|
|
|
287
289
|
except Exception as e:
|
|
288
290
|
log_error(f"Exception reading session: {e}")
|
agno/db/gcs_json/gcs_json_db.py
CHANGED
|
@@ -185,7 +185,7 @@ class GcsJsonDb(BaseDb):
|
|
|
185
185
|
def get_session(
|
|
186
186
|
self,
|
|
187
187
|
session_id: str,
|
|
188
|
-
session_type:
|
|
188
|
+
session_type: SessionType,
|
|
189
189
|
user_id: Optional[str] = None,
|
|
190
190
|
deserialize: Optional[bool] = True,
|
|
191
191
|
) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
|
|
@@ -193,7 +193,7 @@ class GcsJsonDb(BaseDb):
|
|
|
193
193
|
|
|
194
194
|
Args:
|
|
195
195
|
session_id (str): The ID of the session to read.
|
|
196
|
-
session_type (
|
|
196
|
+
session_type (SessionType): The type of the session to read.
|
|
197
197
|
user_id (Optional[str]): The ID of the user to read the session for.
|
|
198
198
|
deserialize (Optional[bool]): Whether to deserialize the session.
|
|
199
199
|
|
|
@@ -226,6 +226,8 @@ class GcsJsonDb(BaseDb):
|
|
|
226
226
|
return TeamSession.from_dict(session_data)
|
|
227
227
|
elif session_type == SessionType.WORKFLOW:
|
|
228
228
|
return WorkflowSession.from_dict(session_data)
|
|
229
|
+
else:
|
|
230
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
229
231
|
|
|
230
232
|
return None
|
|
231
233
|
|
agno/db/json/json_db.py
CHANGED
|
@@ -168,7 +168,7 @@ class JsonDb(BaseDb):
|
|
|
168
168
|
def get_session(
|
|
169
169
|
self,
|
|
170
170
|
session_id: str,
|
|
171
|
-
session_type:
|
|
171
|
+
session_type: SessionType,
|
|
172
172
|
user_id: Optional[str] = None,
|
|
173
173
|
deserialize: Optional[bool] = True,
|
|
174
174
|
) -> Optional[Union[AgentSession, TeamSession, WorkflowSession, Dict[str, Any]]]:
|
|
@@ -176,7 +176,7 @@ class JsonDb(BaseDb):
|
|
|
176
176
|
|
|
177
177
|
Args:
|
|
178
178
|
session_id (str): The ID of the session to read.
|
|
179
|
-
session_type (
|
|
179
|
+
session_type (SessionType): The type of the session to read.
|
|
180
180
|
user_id (Optional[str]): The ID of the user to read the session for.
|
|
181
181
|
deserialize (Optional[bool]): Whether to deserialize the session.
|
|
182
182
|
|
|
@@ -208,8 +208,10 @@ class JsonDb(BaseDb):
|
|
|
208
208
|
return AgentSession.from_dict(session)
|
|
209
209
|
elif session_type == SessionType.TEAM:
|
|
210
210
|
return TeamSession.from_dict(session)
|
|
211
|
-
|
|
211
|
+
elif session_type == SessionType.WORKFLOW:
|
|
212
212
|
return WorkflowSession.from_dict(session)
|
|
213
|
+
else:
|
|
214
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
213
215
|
|
|
214
216
|
return None
|
|
215
217
|
|
|
@@ -338,8 +340,10 @@ class JsonDb(BaseDb):
|
|
|
338
340
|
return AgentSession.from_dict(session)
|
|
339
341
|
elif session_type == SessionType.TEAM:
|
|
340
342
|
return TeamSession.from_dict(session)
|
|
341
|
-
|
|
343
|
+
elif session_type == SessionType.WORKFLOW:
|
|
342
344
|
return WorkflowSession.from_dict(session)
|
|
345
|
+
else:
|
|
346
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
343
347
|
|
|
344
348
|
return None
|
|
345
349
|
|
agno/db/mongo/mongo.py
CHANGED
|
@@ -253,8 +253,8 @@ class MongoDb(BaseDb):
|
|
|
253
253
|
|
|
254
254
|
Args:
|
|
255
255
|
session_id (str): The ID of the session to get.
|
|
256
|
+
session_type (SessionType): The type of session to get.
|
|
256
257
|
user_id (Optional[str]): The ID of the user to get the session for.
|
|
257
|
-
session_type (Optional[SessionType]): The type of session to get.
|
|
258
258
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
259
259
|
|
|
260
260
|
Returns:
|
|
@@ -285,12 +285,14 @@ class MongoDb(BaseDb):
|
|
|
285
285
|
if not deserialize:
|
|
286
286
|
return session
|
|
287
287
|
|
|
288
|
-
if session_type == SessionType.AGENT
|
|
288
|
+
if session_type == SessionType.AGENT:
|
|
289
289
|
return AgentSession.from_dict(session)
|
|
290
|
-
elif session_type == SessionType.TEAM
|
|
290
|
+
elif session_type == SessionType.TEAM:
|
|
291
291
|
return TeamSession.from_dict(session)
|
|
292
|
-
|
|
292
|
+
elif session_type == SessionType.WORKFLOW:
|
|
293
293
|
return WorkflowSession.from_dict(session)
|
|
294
|
+
else:
|
|
295
|
+
raise ValueError(f"Invalid session type: {session_type}")
|
|
294
296
|
|
|
295
297
|
except Exception as e:
|
|
296
298
|
log_error(f"Exception reading session: {e}")
|
agno/db/mysql/mysql.py
CHANGED
|
@@ -350,8 +350,8 @@ class MySQLDb(BaseDb):
|
|
|
350
350
|
|
|
351
351
|
Args:
|
|
352
352
|
session_id (str): ID of the session to read.
|
|
353
|
+
session_type (SessionType): Type of session to get.
|
|
353
354
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
354
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
355
355
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
356
356
|
|
|
357
357
|
Returns:
|
|
@@ -415,6 +415,7 @@ class MySQLDb(BaseDb):
|
|
|
415
415
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
416
416
|
|
|
417
417
|
Args:
|
|
418
|
+
session_type (Optional[SessionType]): The type of sessions to get.
|
|
418
419
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
419
420
|
entity_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
420
421
|
start_timestamp (Optional[int]): The start timestamp to filter by.
|
agno/db/postgres/postgres.py
CHANGED
|
@@ -345,8 +345,8 @@ class PostgresDb(BaseDb):
|
|
|
345
345
|
|
|
346
346
|
Args:
|
|
347
347
|
session_id (str): ID of the session to read.
|
|
348
|
+
session_type (SessionType): Type of session to get.
|
|
348
349
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
349
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
350
350
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
351
351
|
|
|
352
352
|
Returns:
|
|
@@ -410,6 +410,7 @@ class PostgresDb(BaseDb):
|
|
|
410
410
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
411
411
|
|
|
412
412
|
Args:
|
|
413
|
+
session_type (Optional[SessionType]): The type of session to get.
|
|
413
414
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
414
415
|
entity_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
415
416
|
start_timestamp (Optional[int]): The start timestamp to filter by.
|
agno/db/redis/redis.py
CHANGED
|
@@ -300,8 +300,8 @@ class RedisDb(BaseDb):
|
|
|
300
300
|
|
|
301
301
|
Args:
|
|
302
302
|
session_id (str): The ID of the session to get.
|
|
303
|
+
session_type (SessionType): The type of session to get.
|
|
303
304
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
304
|
-
session_type (Optional[SessionType]): The type of session to filter by.
|
|
305
305
|
|
|
306
306
|
Returns:
|
|
307
307
|
Optional[Union[AgentSession, TeamSession, WorkflowSession]]: The session if found, None otherwise.
|
|
@@ -431,8 +431,8 @@ class SingleStoreDb(BaseDb):
|
|
|
431
431
|
|
|
432
432
|
Args:
|
|
433
433
|
session_id (str): ID of the session to read.
|
|
434
|
+
session_type (SessionType): Type of session to get.
|
|
434
435
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
435
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
436
436
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
437
437
|
|
|
438
438
|
Returns:
|
|
@@ -496,7 +496,7 @@ class SingleStoreDb(BaseDb):
|
|
|
496
496
|
Get all sessions in the given table. Can filter by user_id and entity_id.
|
|
497
497
|
|
|
498
498
|
Args:
|
|
499
|
-
session_type (Optional[SessionType]): The type of session to filter by.
|
|
499
|
+
session_type (Optional[SessionType]): The type of session to filter by.
|
|
500
500
|
user_id (Optional[str]): The ID of the user to filter by.
|
|
501
501
|
component_id (Optional[str]): The ID of the agent / workflow to filter by.
|
|
502
502
|
session_name (Optional[str]): The name of the session to filter by.
|
agno/db/sqlite/sqlite.py
CHANGED
|
@@ -333,8 +333,8 @@ class SqliteDb(BaseDb):
|
|
|
333
333
|
|
|
334
334
|
Args:
|
|
335
335
|
session_id (str): ID of the session to read.
|
|
336
|
+
session_type (SessionType): Type of session to get.
|
|
336
337
|
user_id (Optional[str]): User ID to filter by. Defaults to None.
|
|
337
|
-
session_type (Optional[SessionType]): Type of session to read. Defaults to None.
|
|
338
338
|
deserialize (Optional[bool]): Whether to serialize the session. Defaults to True.
|
|
339
339
|
|
|
340
340
|
Returns:
|
|
@@ -78,21 +78,25 @@ class OpenAIEmbedder(Embedder):
|
|
|
78
78
|
return self.client.embeddings.create(**_request_params)
|
|
79
79
|
|
|
80
80
|
def get_embedding(self, text: str) -> List[float]:
|
|
81
|
-
response: CreateEmbeddingResponse = self.response(text=text)
|
|
82
81
|
try:
|
|
82
|
+
response: CreateEmbeddingResponse = self.response(text=text)
|
|
83
83
|
return response.data[0].embedding
|
|
84
84
|
except Exception as e:
|
|
85
85
|
logger.warning(e)
|
|
86
86
|
return []
|
|
87
87
|
|
|
88
88
|
def get_embedding_and_usage(self, text: str) -> Tuple[List[float], Optional[Dict]]:
|
|
89
|
-
|
|
89
|
+
try:
|
|
90
|
+
response: CreateEmbeddingResponse = self.response(text=text)
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
92
|
+
embedding = response.data[0].embedding
|
|
93
|
+
usage = response.usage
|
|
94
|
+
if usage:
|
|
95
|
+
return embedding, usage.model_dump()
|
|
96
|
+
return embedding, None
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.warning(e)
|
|
99
|
+
return [], None
|
|
96
100
|
|
|
97
101
|
async def async_get_embedding(self, text: str) -> List[float]:
|
|
98
102
|
req: Dict[str, Any] = {
|
|
@@ -127,10 +131,14 @@ class OpenAIEmbedder(Embedder):
|
|
|
127
131
|
if self.request_params:
|
|
128
132
|
req.update(self.request_params)
|
|
129
133
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
134
142
|
|
|
135
143
|
def get_embeddings_batch(self, texts: List[str], batch_size: int = 100) -> List[List[float]]:
|
|
136
144
|
"""
|
agno/knowledge/knowledge.py
CHANGED
|
@@ -74,6 +74,8 @@ class Knowledge:
|
|
|
74
74
|
async def add_contents_async(self, *args, **kwargs) -> None:
|
|
75
75
|
if args and isinstance(args[0], list):
|
|
76
76
|
arguments = args[0]
|
|
77
|
+
upsert = kwargs.get("upsert", False)
|
|
78
|
+
skip_if_exists = kwargs.get("skip_if_exists", False)
|
|
77
79
|
for argument in arguments:
|
|
78
80
|
await self.add_content_async(
|
|
79
81
|
name=argument.get("name"),
|
|
@@ -85,8 +87,8 @@ class Knowledge:
|
|
|
85
87
|
reader=argument.get("reader"),
|
|
86
88
|
include=argument.get("include"),
|
|
87
89
|
exclude=argument.get("exclude"),
|
|
88
|
-
upsert=argument.get("upsert",
|
|
89
|
-
skip_if_exists=argument.get("skip_if_exists",
|
|
90
|
+
upsert=argument.get("upsert", upsert),
|
|
91
|
+
skip_if_exists=argument.get("skip_if_exists", skip_if_exists),
|
|
90
92
|
remote_content=argument.get("remote_content", None),
|
|
91
93
|
)
|
|
92
94
|
|
|
@@ -102,7 +104,6 @@ class Knowledge:
|
|
|
102
104
|
upsert = kwargs.get("upsert", False)
|
|
103
105
|
skip_if_exists = kwargs.get("skip_if_exists", False)
|
|
104
106
|
remote_content = kwargs.get("remote_content", None)
|
|
105
|
-
|
|
106
107
|
for path in paths:
|
|
107
108
|
await self.add_content_async(
|
|
108
109
|
name=name,
|
|
@@ -106,23 +106,35 @@ class WebsiteReader(Reader):
|
|
|
106
106
|
"""
|
|
107
107
|
Check if the tag matches any of the relevant tags or class names
|
|
108
108
|
"""
|
|
109
|
-
if tag
|
|
109
|
+
if not isinstance(tag, Tag):
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
if tag.name in ["article", "main", "section"]:
|
|
113
|
+
return True
|
|
114
|
+
|
|
115
|
+
classes = tag.get("class", [])
|
|
116
|
+
content_classes = ["content", "main-content", "post-content", "entry-content", "article-body"]
|
|
117
|
+
if any(cls in content_classes for cls in classes):
|
|
110
118
|
return True
|
|
111
|
-
|
|
119
|
+
|
|
120
|
+
# Check for common content IDs
|
|
121
|
+
tag_id = tag.get("id", "")
|
|
122
|
+
if tag_id in ["content", "main", "article"]:
|
|
112
123
|
return True
|
|
124
|
+
|
|
113
125
|
return False
|
|
114
126
|
|
|
115
|
-
#
|
|
127
|
+
# Try to find main content element
|
|
116
128
|
element = soup.find(match)
|
|
117
129
|
if element:
|
|
130
|
+
# Remove common unwanted elements from the found content
|
|
131
|
+
for unwanted in element.find_all(["script", "style", "nav", "header", "footer"]):
|
|
132
|
+
unwanted.decompose()
|
|
118
133
|
return element.get_text(strip=True, separator=" ")
|
|
119
134
|
|
|
120
|
-
#
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
):
|
|
124
|
-
return ""
|
|
125
|
-
|
|
135
|
+
# Fallback: get full page content
|
|
136
|
+
for unwanted in soup.find_all(["script", "style", "nav", "header", "footer"]):
|
|
137
|
+
unwanted.decompose()
|
|
126
138
|
return soup.get_text(strip=True, separator=" ")
|
|
127
139
|
|
|
128
140
|
def crawl(self, url: str, starting_depth: int = 1) -> Dict[str, str]:
|
|
@@ -164,7 +176,7 @@ class WebsiteReader(Reader):
|
|
|
164
176
|
if (
|
|
165
177
|
current_url in self._visited
|
|
166
178
|
or not urlparse(current_url).netloc.endswith(primary_domain)
|
|
167
|
-
or current_depth > self.max_depth
|
|
179
|
+
or (current_depth > self.max_depth and current_url != url)
|
|
168
180
|
or num_links >= self.max_links
|
|
169
181
|
):
|
|
170
182
|
continue
|
|
@@ -174,13 +186,14 @@ class WebsiteReader(Reader):
|
|
|
174
186
|
|
|
175
187
|
try:
|
|
176
188
|
log_debug(f"Crawling: {current_url}")
|
|
189
|
+
|
|
177
190
|
response = (
|
|
178
|
-
httpx.get(current_url, timeout=self.timeout, proxy=self.proxy)
|
|
191
|
+
httpx.get(current_url, timeout=self.timeout, proxy=self.proxy, follow_redirects=True)
|
|
179
192
|
if self.proxy
|
|
180
|
-
else httpx.get(current_url, timeout=self.timeout)
|
|
193
|
+
else httpx.get(current_url, timeout=self.timeout, follow_redirects=True)
|
|
181
194
|
)
|
|
182
|
-
|
|
183
195
|
response.raise_for_status()
|
|
196
|
+
|
|
184
197
|
soup = BeautifulSoup(response.content, "html.parser")
|
|
185
198
|
|
|
186
199
|
# Extract main content
|
|
@@ -213,9 +226,13 @@ class WebsiteReader(Reader):
|
|
|
213
226
|
|
|
214
227
|
except httpx.HTTPStatusError as e:
|
|
215
228
|
# Log HTTP status errors but continue crawling other pages
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
229
|
+
# Skip redirect errors (3xx) as they should be handled by follow_redirects
|
|
230
|
+
if e.response.status_code >= 300 and e.response.status_code < 400:
|
|
231
|
+
logger.debug(f"Redirect encountered for {current_url}, skipping: {e}")
|
|
232
|
+
else:
|
|
233
|
+
logger.warning(f"HTTP status error while crawling {current_url}: {e}")
|
|
234
|
+
# For the initial URL, we should raise the error only if it's not a redirect
|
|
235
|
+
if current_url == url and not crawler_result and not (300 <= e.response.status_code < 400):
|
|
219
236
|
raise
|
|
220
237
|
except httpx.RequestError as e:
|
|
221
238
|
# Log request errors but continue crawling other pages
|