agno 2.0.6__py3-none-any.whl → 2.0.8__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 +94 -48
- agno/db/migrations/v1_to_v2.py +140 -11
- agno/knowledge/chunking/semantic.py +33 -6
- agno/knowledge/embedder/sentence_transformer.py +3 -3
- agno/knowledge/knowledge.py +152 -31
- agno/knowledge/types.py +8 -0
- agno/media.py +2 -0
- agno/models/base.py +38 -9
- agno/models/cometapi/__init__.py +5 -0
- agno/models/cometapi/cometapi.py +57 -0
- agno/models/google/gemini.py +4 -8
- agno/models/llama_cpp/__init__.py +5 -0
- agno/models/llama_cpp/llama_cpp.py +22 -0
- agno/models/nexus/__init__.py +1 -1
- agno/models/nexus/nexus.py +2 -5
- agno/models/ollama/chat.py +24 -1
- agno/models/openai/chat.py +2 -7
- agno/models/openai/responses.py +21 -17
- agno/os/app.py +4 -10
- agno/os/interfaces/agui/agui.py +2 -2
- agno/os/interfaces/agui/utils.py +81 -18
- agno/os/interfaces/slack/slack.py +2 -2
- agno/os/interfaces/whatsapp/whatsapp.py +2 -2
- agno/os/router.py +3 -4
- agno/os/routers/evals/evals.py +1 -1
- agno/os/routers/memory/memory.py +1 -1
- agno/os/schema.py +3 -4
- agno/os/utils.py +55 -12
- agno/reasoning/default.py +3 -1
- agno/run/agent.py +4 -0
- agno/run/team.py +3 -1
- agno/session/agent.py +8 -5
- agno/session/team.py +14 -10
- agno/team/team.py +239 -115
- agno/tools/decorator.py +4 -2
- agno/tools/function.py +43 -4
- agno/tools/mcp.py +61 -38
- agno/tools/memori.py +1 -53
- agno/utils/events.py +7 -1
- agno/utils/gemini.py +147 -19
- agno/utils/models/claude.py +9 -0
- agno/utils/print_response/agent.py +16 -0
- agno/utils/print_response/team.py +16 -0
- agno/vectordb/base.py +2 -2
- agno/vectordb/langchaindb/langchaindb.py +5 -7
- agno/vectordb/llamaindex/llamaindexdb.py +25 -6
- agno/workflow/workflow.py +59 -15
- {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/METADATA +1 -1
- {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/RECORD +52 -48
- {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/WHEEL +0 -0
- {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/licenses/LICENSE +0 -0
- {agno-2.0.6.dist-info → agno-2.0.8.dist-info}/top_level.txt +0 -0
agno/agent/agent.py
CHANGED
|
@@ -30,6 +30,7 @@ from pydantic import BaseModel
|
|
|
30
30
|
from agno.db.base import BaseDb, SessionType, UserMemory
|
|
31
31
|
from agno.exceptions import ModelProviderError, RunCancelledException, StopAgentRun
|
|
32
32
|
from agno.knowledge.knowledge import Knowledge
|
|
33
|
+
from agno.knowledge.types import KnowledgeFilter
|
|
33
34
|
from agno.media import Audio, File, Image, Video
|
|
34
35
|
from agno.memory import MemoryManager
|
|
35
36
|
from agno.models.base import Model
|
|
@@ -455,6 +456,11 @@ class Agent:
|
|
|
455
456
|
self.add_history_to_context = add_history_to_context
|
|
456
457
|
self.num_history_runs = num_history_runs
|
|
457
458
|
|
|
459
|
+
if add_history_to_context and not db:
|
|
460
|
+
log_warning(
|
|
461
|
+
"add_history_to_context is True, but no database has been assigned to the agent. History will not be added to the context."
|
|
462
|
+
)
|
|
463
|
+
|
|
458
464
|
self.store_media = store_media
|
|
459
465
|
|
|
460
466
|
self.knowledge = knowledge
|
|
@@ -711,6 +717,14 @@ class Agent:
|
|
|
711
717
|
# Determine the session_state
|
|
712
718
|
if session_state is None:
|
|
713
719
|
session_state = self.session_state or {}
|
|
720
|
+
else:
|
|
721
|
+
# If run session_state is provided, merge agent defaults under it
|
|
722
|
+
# This ensures run state takes precedence over agent defaults
|
|
723
|
+
if self.session_state:
|
|
724
|
+
base_state = self.session_state.copy()
|
|
725
|
+
merge_dictionaries(base_state, session_state)
|
|
726
|
+
session_state.clear()
|
|
727
|
+
session_state.update(base_state)
|
|
714
728
|
|
|
715
729
|
if user_id is not None:
|
|
716
730
|
session_state["current_user_id"] = user_id
|
|
@@ -762,6 +776,7 @@ class Agent:
|
|
|
762
776
|
tool_call_limit=self.tool_call_limit,
|
|
763
777
|
response_format=response_format,
|
|
764
778
|
run_response=run_response,
|
|
779
|
+
send_media_to_model=self.send_media_to_model,
|
|
765
780
|
)
|
|
766
781
|
|
|
767
782
|
# Check for cancellation after model call
|
|
@@ -1176,6 +1191,7 @@ class Agent:
|
|
|
1176
1191
|
run_response=run_response,
|
|
1177
1192
|
session=agent_session,
|
|
1178
1193
|
session_state=session_state,
|
|
1194
|
+
dependencies=run_dependencies,
|
|
1179
1195
|
user_id=user_id,
|
|
1180
1196
|
async_mode=False,
|
|
1181
1197
|
knowledge_filters=effective_filters,
|
|
@@ -1359,6 +1375,7 @@ class Agent:
|
|
|
1359
1375
|
tool_choice=self.tool_choice,
|
|
1360
1376
|
tool_call_limit=self.tool_call_limit,
|
|
1361
1377
|
response_format=response_format,
|
|
1378
|
+
send_media_to_model=self.send_media_to_model,
|
|
1362
1379
|
)
|
|
1363
1380
|
|
|
1364
1381
|
# Check for cancellation after model call
|
|
@@ -1807,6 +1824,7 @@ class Agent:
|
|
|
1807
1824
|
run_response=run_response,
|
|
1808
1825
|
session=agent_session,
|
|
1809
1826
|
session_state=session_state,
|
|
1827
|
+
dependencies=run_dependencies,
|
|
1810
1828
|
user_id=user_id,
|
|
1811
1829
|
async_mode=True,
|
|
1812
1830
|
knowledge_filters=effective_filters,
|
|
@@ -3100,6 +3118,8 @@ class Agent:
|
|
|
3100
3118
|
# Update the run_response citations with the model response citations
|
|
3101
3119
|
if model_response.citations is not None:
|
|
3102
3120
|
run_response.citations = model_response.citations
|
|
3121
|
+
if model_response.provider_data is not None:
|
|
3122
|
+
run_response.model_provider_data = model_response.provider_data
|
|
3103
3123
|
|
|
3104
3124
|
# Update the run_response tools with the model response tool_executions
|
|
3105
3125
|
if model_response.tool_executions is not None:
|
|
@@ -3174,6 +3194,7 @@ class Agent:
|
|
|
3174
3194
|
tool_call_limit=self.tool_call_limit,
|
|
3175
3195
|
stream_model_response=stream_model_response,
|
|
3176
3196
|
run_response=run_response,
|
|
3197
|
+
send_media_to_model=self.send_media_to_model,
|
|
3177
3198
|
):
|
|
3178
3199
|
yield from self._handle_model_response_chunk(
|
|
3179
3200
|
session=session,
|
|
@@ -3250,6 +3271,7 @@ class Agent:
|
|
|
3250
3271
|
tool_call_limit=self.tool_call_limit,
|
|
3251
3272
|
stream_model_response=stream_model_response,
|
|
3252
3273
|
run_response=run_response,
|
|
3274
|
+
send_media_to_model=self.send_media_to_model,
|
|
3253
3275
|
) # type: ignore
|
|
3254
3276
|
|
|
3255
3277
|
async for model_response_event in model_response_stream: # type: ignore
|
|
@@ -3339,6 +3361,7 @@ class Agent:
|
|
|
3339
3361
|
run_response.content = model_response.content
|
|
3340
3362
|
run_response.content_type = "str"
|
|
3341
3363
|
|
|
3364
|
+
# Process reasoning content
|
|
3342
3365
|
if model_response_event.reasoning_content is not None:
|
|
3343
3366
|
model_response.reasoning_content = (
|
|
3344
3367
|
model_response.reasoning_content or ""
|
|
@@ -3352,8 +3375,12 @@ class Agent:
|
|
|
3352
3375
|
model_response.reasoning_content += model_response_event.redacted_reasoning_content
|
|
3353
3376
|
run_response.reasoning_content = model_response.reasoning_content
|
|
3354
3377
|
|
|
3378
|
+
# Handle provider data (one chunk)
|
|
3379
|
+
if model_response_event.provider_data is not None:
|
|
3380
|
+
run_response.model_provider_data = model_response_event.provider_data
|
|
3381
|
+
|
|
3382
|
+
# Handle citations (one chunk)
|
|
3355
3383
|
if model_response_event.citations is not None:
|
|
3356
|
-
# We get citations in one chunk
|
|
3357
3384
|
run_response.citations = model_response_event.citations
|
|
3358
3385
|
|
|
3359
3386
|
# Only yield if we have content to show
|
|
@@ -3372,6 +3399,7 @@ class Agent:
|
|
|
3372
3399
|
or model_response_event.reasoning_content is not None
|
|
3373
3400
|
or model_response_event.redacted_reasoning_content is not None
|
|
3374
3401
|
or model_response_event.citations is not None
|
|
3402
|
+
or model_response_event.provider_data is not None
|
|
3375
3403
|
):
|
|
3376
3404
|
yield self._handle_event(
|
|
3377
3405
|
create_run_output_content_event(
|
|
@@ -3380,6 +3408,7 @@ class Agent:
|
|
|
3380
3408
|
reasoning_content=model_response_event.reasoning_content,
|
|
3381
3409
|
redacted_reasoning_content=model_response_event.redacted_reasoning_content,
|
|
3382
3410
|
citations=model_response_event.citations,
|
|
3411
|
+
model_provider_data=model_response_event.provider_data,
|
|
3383
3412
|
),
|
|
3384
3413
|
run_response,
|
|
3385
3414
|
workflow_context=workflow_context,
|
|
@@ -3778,29 +3807,21 @@ class Agent:
|
|
|
3778
3807
|
|
|
3779
3808
|
# If any of the tools has "agent" as parameter, set _rebuild_tools to True
|
|
3780
3809
|
for tool in agent_tools:
|
|
3810
|
+
param_names = {"agent", "session_state", "team", "images", "videos", "audios", "files"}
|
|
3811
|
+
|
|
3781
3812
|
if isinstance(tool, Function):
|
|
3782
|
-
if
|
|
3783
|
-
self._rebuild_tools = True
|
|
3784
|
-
break
|
|
3785
|
-
if "team" in tool.parameters:
|
|
3813
|
+
if param_names & set(tool.parameters):
|
|
3786
3814
|
self._rebuild_tools = True
|
|
3787
3815
|
break
|
|
3788
|
-
|
|
3816
|
+
elif isinstance(tool, Toolkit):
|
|
3789
3817
|
for func in tool.functions.values():
|
|
3790
|
-
if
|
|
3791
|
-
self._rebuild_tools = True
|
|
3792
|
-
break
|
|
3793
|
-
if "team" in func.parameters:
|
|
3818
|
+
if param_names & set(func.parameters):
|
|
3794
3819
|
self._rebuild_tools = True
|
|
3795
3820
|
break
|
|
3796
|
-
|
|
3821
|
+
elif callable(tool):
|
|
3797
3822
|
from inspect import signature
|
|
3798
3823
|
|
|
3799
|
-
|
|
3800
|
-
if "agent" in sig.parameters:
|
|
3801
|
-
self._rebuild_tools = True
|
|
3802
|
-
break
|
|
3803
|
-
if "team" in sig.parameters:
|
|
3824
|
+
if param_names & set(signature(tool).parameters):
|
|
3804
3825
|
self._rebuild_tools = True
|
|
3805
3826
|
break
|
|
3806
3827
|
|
|
@@ -3813,7 +3834,9 @@ class Agent:
|
|
|
3813
3834
|
self._rebuild_tools = True
|
|
3814
3835
|
if self.search_session_history:
|
|
3815
3836
|
agent_tools.append(
|
|
3816
|
-
self._get_previous_sessions_messages_function(
|
|
3837
|
+
self._get_previous_sessions_messages_function(
|
|
3838
|
+
num_history_sessions=self.num_history_sessions, user_id=user_id
|
|
3839
|
+
)
|
|
3817
3840
|
)
|
|
3818
3841
|
self._rebuild_tools = True
|
|
3819
3842
|
|
|
@@ -3993,6 +4016,7 @@ class Agent:
|
|
|
3993
4016
|
run_response: RunOutput,
|
|
3994
4017
|
session: AgentSession,
|
|
3995
4018
|
session_state: Optional[Dict[str, Any]] = None,
|
|
4019
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
3996
4020
|
user_id: Optional[str] = None,
|
|
3997
4021
|
async_mode: bool = False,
|
|
3998
4022
|
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
@@ -4102,6 +4126,7 @@ class Agent:
|
|
|
4102
4126
|
|
|
4103
4127
|
for func in self._functions_for_model.values():
|
|
4104
4128
|
func._session_state = session_state
|
|
4129
|
+
func._dependencies = dependencies
|
|
4105
4130
|
func._images = joint_images
|
|
4106
4131
|
func._files = joint_files
|
|
4107
4132
|
func._audios = joint_audios
|
|
@@ -4230,7 +4255,8 @@ class Agent:
|
|
|
4230
4255
|
def _update_session_state(self, session: AgentSession, session_state: Dict[str, Any]):
|
|
4231
4256
|
"""Load the existing Agent from an AgentSession (from the database)"""
|
|
4232
4257
|
|
|
4233
|
-
# Get the session_state from the database and
|
|
4258
|
+
# Get the session_state from the database and merge with proper precedence
|
|
4259
|
+
# At this point session_state contains: agent_defaults + run_params
|
|
4234
4260
|
if session.session_data is not None and "session_state" in session.session_data:
|
|
4235
4261
|
session_state_from_db = session.session_data.get("session_state")
|
|
4236
4262
|
|
|
@@ -4239,10 +4265,11 @@ class Agent:
|
|
|
4239
4265
|
and isinstance(session_state_from_db, dict)
|
|
4240
4266
|
and len(session_state_from_db) > 0
|
|
4241
4267
|
):
|
|
4242
|
-
# This
|
|
4243
|
-
|
|
4244
|
-
merge_dictionaries(
|
|
4245
|
-
session_state
|
|
4268
|
+
# This preserves precedence: run_params > db_state > agent_defaults
|
|
4269
|
+
merged_state = session_state_from_db.copy()
|
|
4270
|
+
merge_dictionaries(merged_state, session_state)
|
|
4271
|
+
session_state.clear()
|
|
4272
|
+
session_state.update(merged_state)
|
|
4246
4273
|
|
|
4247
4274
|
# Update the session_state in the session
|
|
4248
4275
|
if session.session_data is not None:
|
|
@@ -4919,7 +4946,7 @@ class Agent:
|
|
|
4919
4946
|
system_message_content += f"{get_response_model_format_prompt(self.output_schema)}"
|
|
4920
4947
|
|
|
4921
4948
|
# 3.3.15 Add the session state to the system message
|
|
4922
|
-
if
|
|
4949
|
+
if add_session_state_to_context and session_state is not None:
|
|
4923
4950
|
system_message_content += self._get_formatted_session_state_for_system_message(session_state)
|
|
4924
4951
|
|
|
4925
4952
|
# Return the system message
|
|
@@ -5190,9 +5217,16 @@ class Agent:
|
|
|
5190
5217
|
if add_history_to_context:
|
|
5191
5218
|
from copy import deepcopy
|
|
5192
5219
|
|
|
5220
|
+
# Only skip messages from history when system_message_role is NOT a standard conversation role.
|
|
5221
|
+
# Standard conversation roles ("user", "assistant", "tool") should never be filtered
|
|
5222
|
+
# to preserve conversation continuity.
|
|
5223
|
+
skip_role = (
|
|
5224
|
+
self.system_message_role if self.system_message_role not in ["user", "assistant", "tool"] else None
|
|
5225
|
+
)
|
|
5226
|
+
|
|
5193
5227
|
history: List[Message] = session.get_messages_from_last_n_runs(
|
|
5194
5228
|
last_n=self.num_history_runs,
|
|
5195
|
-
skip_role=
|
|
5229
|
+
skip_role=skip_role,
|
|
5196
5230
|
agent_id=self.id if self.team_id is not None else None,
|
|
5197
5231
|
)
|
|
5198
5232
|
|
|
@@ -5910,6 +5944,7 @@ class Agent:
|
|
|
5910
5944
|
min_steps=self.reasoning_min_steps,
|
|
5911
5945
|
max_steps=self.reasoning_max_steps,
|
|
5912
5946
|
tools=self.tools,
|
|
5947
|
+
tool_call_limit=self.tool_call_limit,
|
|
5913
5948
|
use_json_mode=self.use_json_mode,
|
|
5914
5949
|
telemetry=self.telemetry,
|
|
5915
5950
|
debug_mode=self.debug_mode,
|
|
@@ -6135,6 +6170,7 @@ class Agent:
|
|
|
6135
6170
|
min_steps=self.reasoning_min_steps,
|
|
6136
6171
|
max_steps=self.reasoning_max_steps,
|
|
6137
6172
|
tools=self.tools,
|
|
6173
|
+
tool_call_limit=self.tool_call_limit,
|
|
6138
6174
|
use_json_mode=self.use_json_mode,
|
|
6139
6175
|
telemetry=self.telemetry,
|
|
6140
6176
|
debug_mode=self.debug_mode,
|
|
@@ -6706,17 +6742,18 @@ class Agent:
|
|
|
6706
6742
|
) -> Function:
|
|
6707
6743
|
"""Factory function to create a search_knowledge_base function with filters."""
|
|
6708
6744
|
|
|
6709
|
-
def search_knowledge_base(query: str, filters: Optional[
|
|
6745
|
+
def search_knowledge_base(query: str, filters: Optional[List[KnowledgeFilter]] = None) -> str:
|
|
6710
6746
|
"""Use this function to search the knowledge base for information about a query.
|
|
6711
6747
|
|
|
6712
6748
|
Args:
|
|
6713
6749
|
query: The query to search for.
|
|
6714
|
-
filters: The filters to apply to the search. This is a
|
|
6750
|
+
filters (optional): The filters to apply to the search. This is a list of KnowledgeFilter objects.
|
|
6715
6751
|
|
|
6716
6752
|
Returns:
|
|
6717
6753
|
str: A string containing the response from the knowledge base.
|
|
6718
6754
|
"""
|
|
6719
|
-
|
|
6755
|
+
filters_dict = {filt.key: filt.value for filt in filters} if filters else None
|
|
6756
|
+
search_filters = get_agentic_or_user_search_filters(filters_dict, knowledge_filters)
|
|
6720
6757
|
|
|
6721
6758
|
# Get the relevant documents from the knowledge base, passing filters
|
|
6722
6759
|
retrieval_timer = Timer()
|
|
@@ -6739,17 +6776,18 @@ class Agent:
|
|
|
6739
6776
|
return "No documents found"
|
|
6740
6777
|
return self._convert_documents_to_string(docs_from_knowledge)
|
|
6741
6778
|
|
|
6742
|
-
async def asearch_knowledge_base(query: str, filters: Optional[
|
|
6779
|
+
async def asearch_knowledge_base(query: str, filters: Optional[List[KnowledgeFilter]] = None) -> str:
|
|
6743
6780
|
"""Use this function to search the knowledge base for information about a query asynchronously.
|
|
6744
6781
|
|
|
6745
6782
|
Args:
|
|
6746
6783
|
query: The query to search for.
|
|
6747
|
-
filters: The filters to apply to the search. This is a
|
|
6784
|
+
filters (optional): The filters to apply to the search. This is a list of KnowledgeFilter objects.
|
|
6748
6785
|
|
|
6749
6786
|
Returns:
|
|
6750
6787
|
str: A string containing the response from the knowledge base.
|
|
6751
6788
|
"""
|
|
6752
|
-
|
|
6789
|
+
filters_dict = {filt.key: filt.value for filt in filters} if filters else None
|
|
6790
|
+
search_filters = get_agentic_or_user_search_filters(filters_dict, knowledge_filters)
|
|
6753
6791
|
|
|
6754
6792
|
retrieval_timer = Timer()
|
|
6755
6793
|
retrieval_timer.start()
|
|
@@ -6880,21 +6918,21 @@ class Agent:
|
|
|
6880
6918
|
stream: Optional[bool] = None,
|
|
6881
6919
|
stream_intermediate_steps: Optional[bool] = None,
|
|
6882
6920
|
markdown: Optional[bool] = None,
|
|
6921
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
6922
|
+
add_history_to_context: Optional[bool] = None,
|
|
6923
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
6924
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
6925
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
6926
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
6927
|
+
debug_mode: Optional[bool] = None,
|
|
6883
6928
|
show_message: bool = True,
|
|
6884
6929
|
show_reasoning: bool = True,
|
|
6885
6930
|
show_full_reasoning: bool = False,
|
|
6886
6931
|
console: Optional[Any] = None,
|
|
6887
6932
|
# Add tags to include in markdown content
|
|
6888
6933
|
tags_to_include_in_markdown: Optional[Set[str]] = None,
|
|
6889
|
-
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
6890
|
-
add_history_to_context: Optional[bool] = None,
|
|
6891
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
6892
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
6893
|
-
debug_mode: Optional[bool] = None,
|
|
6894
6934
|
**kwargs: Any,
|
|
6895
6935
|
) -> None:
|
|
6896
|
-
add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
|
|
6897
|
-
|
|
6898
6936
|
if not tags_to_include_in_markdown:
|
|
6899
6937
|
tags_to_include_in_markdown = {"think", "thinking"}
|
|
6900
6938
|
|
|
@@ -6931,8 +6969,10 @@ class Agent:
|
|
|
6931
6969
|
show_full_reasoning=show_full_reasoning,
|
|
6932
6970
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
6933
6971
|
console=console,
|
|
6934
|
-
add_history_to_context=
|
|
6972
|
+
add_history_to_context=add_history_to_context,
|
|
6935
6973
|
dependencies=dependencies,
|
|
6974
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
6975
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
6936
6976
|
metadata=metadata,
|
|
6937
6977
|
**kwargs,
|
|
6938
6978
|
)
|
|
@@ -6957,8 +6997,10 @@ class Agent:
|
|
|
6957
6997
|
show_full_reasoning=show_full_reasoning,
|
|
6958
6998
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
6959
6999
|
console=console,
|
|
6960
|
-
add_history_to_context=
|
|
7000
|
+
add_history_to_context=add_history_to_context,
|
|
6961
7001
|
dependencies=dependencies,
|
|
7002
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
7003
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
6962
7004
|
metadata=metadata,
|
|
6963
7005
|
**kwargs,
|
|
6964
7006
|
)
|
|
@@ -6977,21 +7019,21 @@ class Agent:
|
|
|
6977
7019
|
stream: Optional[bool] = None,
|
|
6978
7020
|
stream_intermediate_steps: Optional[bool] = None,
|
|
6979
7021
|
markdown: Optional[bool] = None,
|
|
7022
|
+
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
7023
|
+
add_history_to_context: Optional[bool] = None,
|
|
7024
|
+
dependencies: Optional[Dict[str, Any]] = None,
|
|
7025
|
+
add_dependencies_to_context: Optional[bool] = None,
|
|
7026
|
+
add_session_state_to_context: Optional[bool] = None,
|
|
7027
|
+
metadata: Optional[Dict[str, Any]] = None,
|
|
7028
|
+
debug_mode: Optional[bool] = None,
|
|
6980
7029
|
show_message: bool = True,
|
|
6981
7030
|
show_reasoning: bool = True,
|
|
6982
7031
|
show_full_reasoning: bool = False,
|
|
6983
7032
|
console: Optional[Any] = None,
|
|
6984
7033
|
# Add tags to include in markdown content
|
|
6985
7034
|
tags_to_include_in_markdown: Optional[Set[str]] = None,
|
|
6986
|
-
knowledge_filters: Optional[Dict[str, Any]] = None,
|
|
6987
|
-
add_history_to_context: Optional[bool] = None,
|
|
6988
|
-
dependencies: Optional[Dict[str, Any]] = None,
|
|
6989
|
-
metadata: Optional[Dict[str, Any]] = None,
|
|
6990
|
-
debug_mode: Optional[bool] = None,
|
|
6991
7035
|
**kwargs: Any,
|
|
6992
7036
|
) -> None:
|
|
6993
|
-
add_history = add_history_to_context if add_history_to_context is not None else self.add_history_to_context
|
|
6994
|
-
|
|
6995
7037
|
if not tags_to_include_in_markdown:
|
|
6996
7038
|
tags_to_include_in_markdown = {"think", "thinking"}
|
|
6997
7039
|
|
|
@@ -7027,8 +7069,10 @@ class Agent:
|
|
|
7027
7069
|
show_full_reasoning=show_full_reasoning,
|
|
7028
7070
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
7029
7071
|
console=console,
|
|
7030
|
-
add_history_to_context=
|
|
7072
|
+
add_history_to_context=add_history_to_context,
|
|
7031
7073
|
dependencies=dependencies,
|
|
7074
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
7075
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
7032
7076
|
metadata=metadata,
|
|
7033
7077
|
**kwargs,
|
|
7034
7078
|
)
|
|
@@ -7052,8 +7096,10 @@ class Agent:
|
|
|
7052
7096
|
show_full_reasoning=show_full_reasoning,
|
|
7053
7097
|
tags_to_include_in_markdown=tags_to_include_in_markdown,
|
|
7054
7098
|
console=console,
|
|
7055
|
-
add_history_to_context=
|
|
7099
|
+
add_history_to_context=add_history_to_context,
|
|
7056
7100
|
dependencies=dependencies,
|
|
7101
|
+
add_dependencies_to_context=add_dependencies_to_context,
|
|
7102
|
+
add_session_state_to_context=add_session_state_to_context,
|
|
7057
7103
|
metadata=metadata,
|
|
7058
7104
|
**kwargs,
|
|
7059
7105
|
)
|
agno/db/migrations/v1_to_v2.py
CHANGED
|
@@ -47,10 +47,10 @@ def convert_v1_metrics_to_v2(metrics_dict: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
47
47
|
|
|
48
48
|
|
|
49
49
|
def convert_any_metrics_in_data(data: Any) -> Any:
|
|
50
|
-
"""Recursively find and convert any metrics dictionaries
|
|
50
|
+
"""Recursively find and convert any metrics dictionaries and handle v1 to v2 field conversion."""
|
|
51
51
|
if isinstance(data, dict):
|
|
52
|
-
# First
|
|
53
|
-
data =
|
|
52
|
+
# First apply v1 to v2 field conversion (handles extra_data extraction, thinking/reasoning_content consolidation, etc.)
|
|
53
|
+
data = convert_v1_fields_to_v2(data)
|
|
54
54
|
|
|
55
55
|
# Check if this looks like a metrics dictionary
|
|
56
56
|
if _is_metrics_dict(data):
|
|
@@ -114,11 +114,11 @@ def _is_metrics_dict(data: Dict[str, Any]) -> bool:
|
|
|
114
114
|
|
|
115
115
|
|
|
116
116
|
def convert_session_data_comprehensively(session_data: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
|
117
|
-
"""Comprehensively convert
|
|
117
|
+
"""Comprehensively convert session data from v1 to v2 format, including metrics conversion and field mapping."""
|
|
118
118
|
if not session_data:
|
|
119
119
|
return session_data
|
|
120
120
|
|
|
121
|
-
# Use the recursive converter to
|
|
121
|
+
# Use the recursive converter to handle all v1 to v2 conversions (metrics, field mapping, extra_data extraction, etc.)
|
|
122
122
|
return convert_any_metrics_in_data(session_data)
|
|
123
123
|
|
|
124
124
|
|
|
@@ -145,21 +145,150 @@ def safe_get_runs_from_memory(memory_data: Any) -> Any:
|
|
|
145
145
|
return None
|
|
146
146
|
|
|
147
147
|
|
|
148
|
-
def
|
|
149
|
-
"""
|
|
148
|
+
def convert_v1_media_to_v2(media_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
149
|
+
"""Convert v1 media objects to v2 format."""
|
|
150
|
+
if not isinstance(media_data, dict):
|
|
151
|
+
return media_data
|
|
152
|
+
|
|
153
|
+
# Create a copy to avoid modifying the original
|
|
154
|
+
v2_media = media_data.copy()
|
|
155
|
+
|
|
156
|
+
# Add id if missing (required in v2)
|
|
157
|
+
if "id" not in v2_media or v2_media["id"] is None:
|
|
158
|
+
from uuid import uuid4
|
|
159
|
+
|
|
160
|
+
v2_media["id"] = str(uuid4())
|
|
161
|
+
|
|
162
|
+
# Handle VideoArtifact → Video conversion
|
|
163
|
+
if "eta" in v2_media or "length" in v2_media:
|
|
164
|
+
# Convert length to duration if it's numeric
|
|
165
|
+
length = v2_media.pop("length", None)
|
|
166
|
+
if length and isinstance(length, (int, float)):
|
|
167
|
+
v2_media["duration"] = length
|
|
168
|
+
elif length and isinstance(length, str):
|
|
169
|
+
try:
|
|
170
|
+
v2_media["duration"] = float(length)
|
|
171
|
+
except ValueError:
|
|
172
|
+
pass # Keep as is if not convertible
|
|
173
|
+
|
|
174
|
+
# Handle AudioArtifact → Audio conversion
|
|
175
|
+
if "base64_audio" in v2_media:
|
|
176
|
+
# Map base64_audio to content
|
|
177
|
+
base64_audio = v2_media.pop("base64_audio", None)
|
|
178
|
+
if base64_audio:
|
|
179
|
+
v2_media["content"] = base64_audio
|
|
180
|
+
|
|
181
|
+
# Handle AudioResponse content conversion (base64 string to bytes if needed)
|
|
182
|
+
if "transcript" in v2_media and "content" in v2_media:
|
|
183
|
+
content = v2_media.get("content")
|
|
184
|
+
if content and isinstance(content, str):
|
|
185
|
+
# Try to decode base64 content to bytes for v2
|
|
186
|
+
try:
|
|
187
|
+
import base64
|
|
188
|
+
|
|
189
|
+
v2_media["content"] = base64.b64decode(content)
|
|
190
|
+
except Exception:
|
|
191
|
+
# If not valid base64, keep as string
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
# Ensure format and mime_type are set appropriately
|
|
195
|
+
if "format" in v2_media and "mime_type" not in v2_media:
|
|
196
|
+
format_val = v2_media["format"]
|
|
197
|
+
if format_val:
|
|
198
|
+
# Set mime_type based on format for common types
|
|
199
|
+
mime_type_map = {
|
|
200
|
+
"mp4": "video/mp4",
|
|
201
|
+
"mov": "video/quicktime",
|
|
202
|
+
"avi": "video/x-msvideo",
|
|
203
|
+
"webm": "video/webm",
|
|
204
|
+
"mp3": "audio/mpeg",
|
|
205
|
+
"wav": "audio/wav",
|
|
206
|
+
"ogg": "audio/ogg",
|
|
207
|
+
"png": "image/png",
|
|
208
|
+
"jpg": "image/jpeg",
|
|
209
|
+
"jpeg": "image/jpeg",
|
|
210
|
+
"gif": "image/gif",
|
|
211
|
+
"webp": "image/webp",
|
|
212
|
+
}
|
|
213
|
+
if format_val.lower() in mime_type_map:
|
|
214
|
+
v2_media["mime_type"] = mime_type_map[format_val.lower()]
|
|
215
|
+
|
|
216
|
+
return v2_media
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def convert_v1_fields_to_v2(data: Dict[str, Any]) -> Dict[str, Any]:
|
|
220
|
+
"""Convert v1 fields to v2 format with proper field mapping and extraction."""
|
|
150
221
|
if not isinstance(data, dict):
|
|
151
222
|
return data
|
|
152
223
|
|
|
153
|
-
#
|
|
224
|
+
# Create a copy to avoid modifying the original
|
|
225
|
+
v2_data = data.copy()
|
|
226
|
+
|
|
227
|
+
# Fields that should be completely ignored/removed in v2
|
|
154
228
|
deprecated_fields = {
|
|
155
229
|
"team_session_id", # RunOutput v1 field, removed in v2
|
|
156
230
|
"formatted_tool_calls", # RunOutput v1 field, removed in v2
|
|
231
|
+
"event", # Remove event field
|
|
232
|
+
"events", # Remove events field
|
|
157
233
|
# Add other deprecated fields here as needed
|
|
158
234
|
}
|
|
159
235
|
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
236
|
+
# Extract and map fields from extra_data before removing it
|
|
237
|
+
extra_data = v2_data.get("extra_data")
|
|
238
|
+
if extra_data and isinstance(extra_data, dict):
|
|
239
|
+
# Map extra_data fields to their v2 locations
|
|
240
|
+
if "add_messages" in extra_data:
|
|
241
|
+
v2_data["additional_input"] = extra_data["add_messages"]
|
|
242
|
+
if "references" in extra_data:
|
|
243
|
+
v2_data["references"] = extra_data["references"]
|
|
244
|
+
if "reasoning_steps" in extra_data:
|
|
245
|
+
v2_data["reasoning_steps"] = extra_data["reasoning_steps"]
|
|
246
|
+
if "reasoning_content" in extra_data:
|
|
247
|
+
# reasoning_content from extra_data also goes to reasoning_content
|
|
248
|
+
v2_data["reasoning_content"] = extra_data["reasoning_content"]
|
|
249
|
+
if "reasoning_messages" in extra_data:
|
|
250
|
+
v2_data["reasoning_messages"] = extra_data["reasoning_messages"]
|
|
251
|
+
|
|
252
|
+
# Handle thinking and reasoning_content consolidation
|
|
253
|
+
# Both thinking and reasoning_content from v1 should become reasoning_content in v2
|
|
254
|
+
thinking = v2_data.get("thinking")
|
|
255
|
+
reasoning_content = v2_data.get("reasoning_content")
|
|
256
|
+
|
|
257
|
+
# Consolidate thinking and reasoning_content into reasoning_content
|
|
258
|
+
if thinking and reasoning_content:
|
|
259
|
+
# Both exist, combine them (thinking first, then reasoning_content)
|
|
260
|
+
v2_data["reasoning_content"] = f"{thinking}\n{reasoning_content}"
|
|
261
|
+
elif thinking and not reasoning_content:
|
|
262
|
+
# Only thinking exists, move it to reasoning_content
|
|
263
|
+
v2_data["reasoning_content"] = thinking
|
|
264
|
+
# If only reasoning_content exists, keep it as is
|
|
265
|
+
|
|
266
|
+
# Remove thinking field since it's now consolidated into reasoning_content
|
|
267
|
+
if "thinking" in v2_data:
|
|
268
|
+
del v2_data["thinking"]
|
|
269
|
+
|
|
270
|
+
# Handle media object conversions
|
|
271
|
+
media_fields = ["images", "videos", "audio", "response_audio"]
|
|
272
|
+
for field in media_fields:
|
|
273
|
+
if field in v2_data and v2_data[field]:
|
|
274
|
+
if isinstance(v2_data[field], list):
|
|
275
|
+
# Handle list of media objects
|
|
276
|
+
v2_data[field] = [
|
|
277
|
+
convert_v1_media_to_v2(item) if isinstance(item, dict) else item for item in v2_data[field]
|
|
278
|
+
]
|
|
279
|
+
elif isinstance(v2_data[field], dict):
|
|
280
|
+
# Handle single media object
|
|
281
|
+
v2_data[field] = convert_v1_media_to_v2(v2_data[field])
|
|
282
|
+
|
|
283
|
+
# Remove extra_data after extraction
|
|
284
|
+
if "extra_data" in v2_data:
|
|
285
|
+
del v2_data["extra_data"]
|
|
286
|
+
|
|
287
|
+
# Remove other deprecated fields
|
|
288
|
+
for field in deprecated_fields:
|
|
289
|
+
v2_data.pop(field, None)
|
|
290
|
+
|
|
291
|
+
return v2_data
|
|
163
292
|
|
|
164
293
|
|
|
165
294
|
def migrate(
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
|
|
1
|
+
import inspect
|
|
2
|
+
from typing import Any, Dict, List, Optional
|
|
2
3
|
|
|
3
4
|
from agno.knowledge.chunking.strategy import ChunkingStrategy
|
|
4
5
|
from agno.knowledge.document.base import Document
|
|
@@ -26,11 +27,37 @@ class SemanticChunking(ChunkingStrategy):
|
|
|
26
27
|
"Please install it using `pip install chonkie` to use SemanticChunking."
|
|
27
28
|
)
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
chunk_size
|
|
32
|
-
threshold
|
|
33
|
-
|
|
30
|
+
# Build arguments dynamically based on chonkie's supported signature
|
|
31
|
+
params: Dict[str, Any] = {
|
|
32
|
+
"chunk_size": self.chunk_size,
|
|
33
|
+
"threshold": self.similarity_threshold,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
sig = inspect.signature(SemanticChunker)
|
|
38
|
+
param_names = set(sig.parameters.keys())
|
|
39
|
+
|
|
40
|
+
# Prefer passing a callable to avoid Chonkie initializing its own client
|
|
41
|
+
if "embedding_fn" in param_names:
|
|
42
|
+
params["embedding_fn"] = self.embedder.get_embedding # type: ignore[attr-defined]
|
|
43
|
+
# If chonkie allows specifying dimensions, provide them
|
|
44
|
+
if "embedding_dimensions" in param_names and getattr(self.embedder, "dimensions", None):
|
|
45
|
+
params["embedding_dimensions"] = self.embedder.dimensions # type: ignore[attr-defined]
|
|
46
|
+
elif "embedder" in param_names:
|
|
47
|
+
# Some versions may accept an embedder object directly
|
|
48
|
+
params["embedder"] = self.embedder
|
|
49
|
+
else:
|
|
50
|
+
# Fallback to model id
|
|
51
|
+
params["embedding_model"] = getattr(self.embedder, "id", None) or "text-embedding-3-small"
|
|
52
|
+
|
|
53
|
+
self.chunker = SemanticChunker(**params)
|
|
54
|
+
except Exception:
|
|
55
|
+
# As a final fallback, use the original behavior
|
|
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
|
+
)
|
|
34
61
|
|
|
35
62
|
def chunk(self, document: Document) -> List[Document]:
|
|
36
63
|
"""Split document into semantic chunks using chonkie"""
|
|
@@ -27,9 +27,9 @@ class SentenceTransformerEmbedder(Embedder):
|
|
|
27
27
|
|
|
28
28
|
def get_embedding(self, text: Union[str, List[str]]) -> List[float]:
|
|
29
29
|
if not self.sentence_transformer_client:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
self.sentence_transformer_client = SentenceTransformer(model_name_or_path=self.id)
|
|
31
|
+
|
|
32
|
+
model = self.sentence_transformer_client
|
|
33
33
|
embedding = model.encode(text, prompt=self.prompt, normalize_embeddings=self.normalize_embeddings)
|
|
34
34
|
try:
|
|
35
35
|
if isinstance(embedding, np.ndarray):
|