Skip to content

Commit 194d9b0

Browse files
authored
chore: Release 1.7.3 (#3846)
# Changelog ## New Features: - **Session State on Run**: You can now pass `session_state` when running an agent or team. See [docs](https://docs.agno.com/agents/state) for more information. - **GCS Support for PDF Knowledge Base:** Added `GCSPDFKnowledgeBase` to support PDFs on Google Cloud Storage. ## Bug Fixes: - **Workflows Async + Storage**: Fixed issues where sessions was not correctly stored with async workflow executions. - **Session State Management**: Fixed issues around session state management in teams and agents. Session state and session name will now correctly reset and load from storage if sessions are switched. - **Metadata Support for Document Knowledge Base:** Adds metadata support for `DocumentKnowledgeBase` - **Session Metrics with History**: Fixed bug with session metrics on `Agent` where history is enabled.
1 parent f402ee7 commit 194d9b0

File tree

12 files changed

+140
-28
lines changed

12 files changed

+140
-28
lines changed

cookbook/agent_concepts/knowledge/gcs_pdf_kb.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
1. Install required libraries: agno, google-cloud-storage, psycopg2-binary (for PostgreSQL vector DB).
66
2. Set up your GCS bucket and upload your PDF file.
77
3. For public GCS buckets: No authentication needed, just set the bucket and PDF path.
8-
4. For private GCS buckets:
8+
4. For private GCS buckets:
99
- Grant the service account Storage Object Viewer access to the bucket via Google Cloud Console
1010
- Export GOOGLE_APPLICATION_CREDENTIALS with the path to your service account JSON before running the script
1111
5. Update 'bucket_name' and 'blob_name' in the script to your PDF's location.

cookbook/agent_concepts/knowledge/gcs_pdf_kb_async.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,15 @@
55
1. Install required libraries: agno, google-cloud-storage, psycopg2-binary (for PostgreSQL vector DB).
66
2. Set up your GCS bucket and upload your PDF file.
77
3. For public GCS buckets: No authentication needed, just set the bucket and PDF path.
8-
4. For private GCS buckets:
8+
4. For private GCS buckets:
99
- Grant the service account Storage Object Viewer access to the bucket via Google Cloud Console
1010
- Export GOOGLE_APPLICATION_CREDENTIALS with the path to your service account JSON before running the script
1111
5. Update 'bucket_name' and 'blob_name' in the script to your PDF's location.
1212
6. Run the script to load the knowledge base and ask questions.
1313
"""
1414

1515
import asyncio
16+
1617
from agno.agent import Agent
1718
from agno.knowledge.gcs.pdf import GCSPDFKnowledgeBase
1819
from agno.vectordb.pgvector import PgVector
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
from agno.agent import Agent
22

3+
34
def get_instructions(agent: Agent):
4-
return f"Make the story about {agent.session_state.get("current_user_id")}."
5+
return f"Make the story about {agent.session_state.get('current_user_id')}."
6+
57

68
agent = Agent(instructions=get_instructions)
79
agent.print_response("Write a 2 sentence story", user_id="john.doe")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from agno.agent import Agent
2+
from agno.models.openai import OpenAIChat
3+
4+
agent = Agent(
5+
model=OpenAIChat(id="gpt-4o-mini"),
6+
add_state_in_messages=True,
7+
instructions="Users name is {user_name} and age is {age}",
8+
)
9+
10+
# Sets the session state for the session with the id "user_1_session_1"
11+
agent.print_response(
12+
"What is my name?",
13+
session_id="user_1_session_1",
14+
user_id="user_1",
15+
session_state={"user_name": "John", "age": 30},
16+
)
17+
18+
# Will load the session state from the session with the id "user_1_session_1"
19+
agent.print_response("How old am I?", session_id="user_1_session_1", user_id="user_1")
20+
21+
# Sets the session state for the session with the id "user_2_session_1"
22+
agent.print_response(
23+
"What is my name?",
24+
session_id="user_2_session_1",
25+
user_id="user_2",
26+
session_state={"user_name": "Jane", "age": 25},
27+
)
28+
29+
# Will load the session state from the session with the id "user_2_session_1"
30+
agent.print_response("How old am I?", session_id="user_2_session_1", user_id="user_2")

libs/agno/agno/agent/agent.py

Lines changed: 71 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,7 @@ def _run(
758758
session_id: str,
759759
user_id: Optional[str] = None,
760760
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
761+
refresh_session_before_write: Optional[bool] = False,
761762
) -> RunResponse:
762763
"""Run the Agent and return the RunResponse.
763764
@@ -825,7 +826,7 @@ def _run(
825826
self._convert_response_to_structured_format(run_response)
826827

827828
# 6. Save session to storage
828-
self.write_to_storage(user_id=user_id, session_id=session_id)
829+
self.write_to_storage(user_id=user_id, session_id=session_id, refresh_session=refresh_session_before_write)
829830

830831
# 7. Save output to file if save_response_to_file is set
831832
self.save_run_response_to_file(message=run_messages.user_message, session_id=session_id)
@@ -845,6 +846,7 @@ def _run_stream(
845846
user_id: Optional[str] = None,
846847
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
847848
stream_intermediate_steps: bool = False,
849+
refresh_session_before_write: Optional[bool] = False,
848850
) -> Iterator[RunResponseEvent]:
849851
"""Run the Agent and yield the RunResponse.
850852
@@ -917,7 +919,7 @@ def _run_stream(
917919
yield self._handle_event(create_run_response_completed_event(from_run_response=run_response), run_response)
918920

919921
# 7. Save session to storage
920-
self.write_to_storage(user_id=user_id, session_id=session_id)
922+
self.write_to_storage(user_id=user_id, session_id=session_id, refresh_session=refresh_session_before_write)
921923

922924
# Log Agent Run
923925
self._log_agent_run(user_id=user_id, session_id=session_id)
@@ -941,6 +943,7 @@ def run(
941943
messages: Optional[Sequence[Union[Dict, Message]]] = None,
942944
retries: Optional[int] = None,
943945
knowledge_filters: Optional[Dict[str, Any]] = None,
946+
refresh_session_before_write: Optional[bool] = False,
944947
**kwargs: Any,
945948
) -> RunResponse: ...
946949

@@ -961,6 +964,7 @@ def run(
961964
messages: Optional[Sequence[Union[Dict, Message]]] = None,
962965
retries: Optional[int] = None,
963966
knowledge_filters: Optional[Dict[str, Any]] = None,
967+
refresh_session_before_write: Optional[bool] = False,
964968
**kwargs: Any,
965969
) -> Iterator[RunResponseEvent]: ...
966970

@@ -980,6 +984,7 @@ def run(
980984
messages: Optional[Sequence[Union[Dict, Message]]] = None,
981985
retries: Optional[int] = None,
982986
knowledge_filters: Optional[Dict[str, Any]] = None,
987+
refresh_session_before_write: Optional[bool] = False,
983988
**kwargs: Any,
984989
) -> Union[RunResponse, Iterator[RunResponseEvent]]:
985990
"""Run the Agent and return the response."""
@@ -1109,6 +1114,7 @@ def run(
11091114
session_id=session_id,
11101115
response_format=response_format,
11111116
stream_intermediate_steps=stream_intermediate_steps,
1117+
refresh_session_before_write=refresh_session_before_write,
11121118
)
11131119
return response_iterator
11141120
else:
@@ -1118,6 +1124,7 @@ def run(
11181124
user_id=user_id,
11191125
session_id=session_id,
11201126
response_format=response_format,
1127+
refresh_session_before_write=refresh_session_before_write,
11211128
)
11221129
return response
11231130
except ModelProviderError as e:
@@ -1167,6 +1174,7 @@ async def _arun(
11671174
session_id: str,
11681175
user_id: Optional[str] = None,
11691176
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
1177+
refresh_session_before_write: Optional[bool] = False,
11701178
) -> RunResponse:
11711179
"""Run the Agent and yield the RunResponse.
11721180
@@ -1233,7 +1241,7 @@ async def _arun(
12331241
self._convert_response_to_structured_format(run_response)
12341242

12351243
# 6. Save session to storage
1236-
self.write_to_storage(user_id=user_id, session_id=session_id)
1244+
self.write_to_storage(user_id=user_id, session_id=session_id, refresh_session=refresh_session_before_write)
12371245

12381246
# 7. Save output to file if save_response_to_file is set
12391247
self.save_run_response_to_file(message=run_messages.user_message, session_id=session_id)
@@ -1253,6 +1261,7 @@ async def _arun_stream(
12531261
user_id: Optional[str] = None,
12541262
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
12551263
stream_intermediate_steps: bool = False,
1264+
refresh_session_before_write: Optional[bool] = False,
12561265
) -> AsyncIterator[RunResponseEvent]:
12571266
"""Run the Agent and yield the RunResponse.
12581267
@@ -1328,7 +1337,7 @@ async def _arun_stream(
13281337
yield self._handle_event(create_run_response_completed_event(from_run_response=run_response), run_response)
13291338

13301339
# 7. Save session to storage
1331-
self.write_to_storage(user_id=user_id, session_id=session_id)
1340+
self.write_to_storage(user_id=user_id, session_id=session_id, refresh_session=refresh_session_before_write)
13321341

13331342
# Log Agent Run
13341343
await self._alog_agent_run(user_id=user_id, session_id=session_id)
@@ -1351,6 +1360,7 @@ async def arun(
13511360
stream_intermediate_steps: Optional[bool] = None,
13521361
retries: Optional[int] = None,
13531362
knowledge_filters: Optional[Dict[str, Any]] = None,
1363+
refresh_session_before_write: Optional[bool] = False,
13541364
**kwargs: Any,
13551365
) -> Any:
13561366
"""Async Run the Agent and return the response."""
@@ -1361,6 +1371,9 @@ async def arun(
13611371

13621372
log_debug(f"Session ID: {session_id}", center=True)
13631373

1374+
# Initialize the Agent
1375+
self.initialize_agent()
1376+
13641377
effective_filters = knowledge_filters
13651378
# When filters are passed manually
13661379
if self.knowledge_filters or knowledge_filters:
@@ -1476,6 +1489,7 @@ async def arun(
14761489
session_id=session_id,
14771490
response_format=response_format,
14781491
stream_intermediate_steps=stream_intermediate_steps,
1492+
refresh_session_before_write=refresh_session_before_write,
14791493
) # type: ignore[assignment]
14801494
return response_iterator
14811495
else:
@@ -1485,6 +1499,7 @@ async def arun(
14851499
user_id=user_id,
14861500
session_id=session_id,
14871501
response_format=response_format,
1502+
refresh_session_before_write=refresh_session_before_write,
14881503
)
14891504
return response
14901505
except ModelProviderError as e:
@@ -4123,13 +4138,56 @@ def read_from_storage(
41234138
self.load_agent_session(session=self.agent_session)
41244139
return self.agent_session
41254140

4126-
def write_to_storage(self, session_id: str, user_id: Optional[str] = None) -> Optional[AgentSession]:
4141+
def refresh_from_storage(self, session_id: str) -> None:
4142+
"""Refresh the AgentSession from storage
4143+
4144+
Args:
4145+
session_id: The session_id to refresh from storage.
4146+
"""
4147+
if not self.storage:
4148+
return
4149+
4150+
agent_session_from_db = self.storage.read(session_id=session_id)
4151+
if (
4152+
agent_session_from_db is not None
4153+
and agent_session_from_db.memory is not None
4154+
and "runs" in agent_session_from_db.memory # type: ignore
4155+
):
4156+
if isinstance(self.memory, AgentMemory):
4157+
return
4158+
try:
4159+
if self.memory.runs is None: # type: ignore
4160+
self.memory.runs = {} # type: ignore
4161+
if session_id not in self.memory.runs: # type: ignore
4162+
self.memory.runs[session_id] = [] # type: ignore
4163+
for run in agent_session_from_db.memory["runs"]: # type: ignore
4164+
run_session_id = run["session_id"]
4165+
skip = False
4166+
for existing_run in self.memory.runs[run_session_id]: # type: ignore
4167+
if existing_run.run_id == run["run_id"]:
4168+
skip = True
4169+
break
4170+
if skip:
4171+
continue
4172+
if "team_id" in run:
4173+
self.memory.runs[run_session_id].append(TeamRunResponse.from_dict(run)) # type: ignore
4174+
else:
4175+
self.memory.runs[run_session_id].append(RunResponse.from_dict(run)) # type: ignore
4176+
except Exception as e:
4177+
log_warning(f"Failed to load runs from memory: {e}")
4178+
4179+
def write_to_storage(
4180+
self, session_id: str, user_id: Optional[str] = None, refresh_session: Optional[bool] = False
4181+
) -> Optional[AgentSession]:
41274182
"""Save the AgentSession to storage
41284183
41294184
Returns:
41304185
Optional[AgentSession]: The saved AgentSession or None if not saved.
41314186
"""
41324187
if self.storage is not None:
4188+
if refresh_session:
4189+
self.refresh_from_storage(session_id=session_id)
4190+
41334191
self.agent_session = cast(
41344192
AgentSession,
41354193
self.storage.upsert(session=self.get_agent_session(session_id=session_id, user_id=user_id)),
@@ -4209,13 +4267,13 @@ def new_session(self) -> None:
42094267
self.session_id = str(uuid4())
42104268
self.load_session(force=True)
42114269

4212-
def format_message_with_state_variables(self, msg: Any) -> Any:
4270+
def format_message_with_state_variables(self, message: Any) -> Any:
42134271
"""Format a message with the session state variables."""
42144272
import re
42154273
import string
42164274

4217-
if not isinstance(msg, str):
4218-
return msg
4275+
if not isinstance(message, str):
4276+
return message
42194277

42204278
format_variables = ChainMap(
42214279
self.session_state or {},
@@ -4224,7 +4282,7 @@ def format_message_with_state_variables(self, msg: Any) -> Any:
42244282
self.extra_data or {},
42254283
{"user_id": self.user_id} if self.user_id is not None else {},
42264284
)
4227-
converted_msg = msg
4285+
converted_msg = message
42284286
for var_name in format_variables.keys():
42294287
# Only convert standalone {var_name} patterns, not nested ones
42304288
pattern = r"\{" + re.escape(var_name) + r"\}"
@@ -4238,7 +4296,7 @@ def format_message_with_state_variables(self, msg: Any) -> Any:
42384296
return result
42394297
except Exception as e:
42404298
log_warning(f"Template substitution failed: {e}")
4241-
return msg
4299+
return message
42424300

42434301
def get_system_message(self, session_id: str, user_id: Optional[str] = None) -> Optional[Message]:
42444302
"""Return the system message for the Agent.
@@ -4264,6 +4322,7 @@ def get_system_message(self, session_id: str, user_id: Optional[str] = None) ->
42644322
# Format the system message with the session state variables
42654323
if self.add_state_in_messages:
42664324
sys_message_content = self.format_message_with_state_variables(sys_message_content)
4325+
print("HELLO", sys_message_content)
42674326

42684327
# Add the JSON output prompt if response_model is provided and the model does not support native structured outputs or JSON schema outputs
42694328
# or if use_json_mode is True
@@ -4280,7 +4339,6 @@ def get_system_message(self, session_id: str, user_id: Optional[str] = None) ->
42804339

42814340
# type: ignore
42824341
return Message(role=self.system_message_role, content=sys_message_content)
4283-
42844342
# 2. If create_default_system_message is False, return None.
42854343
if not self.create_default_system_message:
42864344
return None
@@ -4295,6 +4353,7 @@ def get_system_message(self, session_id: str, user_id: Optional[str] = None) ->
42954353
_instructions = self.instructions
42964354
if callable(self.instructions):
42974355
import inspect
4356+
42984357
signature = inspect.signature(self.instructions)
42994358
if "agent" in signature.parameters:
43004359
_instructions = self.instructions(agent=self)
@@ -4305,7 +4364,7 @@ def get_system_message(self, session_id: str, user_id: Optional[str] = None) ->
43054364
instructions.append(_instructions)
43064365
elif isinstance(_instructions, list):
43074366
instructions.extend(_instructions)
4308-
4367+
43094368
# 3.1.1 Add instructions from the Model
43104369
_model_instructions = self.model.get_instructions_for_model(self._tools_for_model)
43114370
if _model_instructions is not None:

libs/agno/agno/knowledge/gcs/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def gcs_blobs(self) -> List[storage.Blob]:
2525
if self.blob_name is not None:
2626
blobs_to_read.append(self.bucket.blob(self.blob_name)) # type: ignore
2727
elif self.prefix is not None:
28-
blobs_to_read.extend(self.bucket.list_blobs(prefix=self.prefix)) # type: ignore
28+
blobs_to_read.extend(self.bucket.list_blobs(prefix=self.prefix)) # type: ignore
2929
else:
3030
blobs_to_read.extend(self.bucket.list_blobs()) # type: ignore
3131
return list(blobs_to_read)

0 commit comments

Comments
 (0)