agno 1.7.6__py3-none-any.whl → 1.7.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/tools/postgres.py CHANGED
@@ -2,14 +2,12 @@ import csv
2
2
  from typing import Any, Dict, List, Optional
3
3
 
4
4
  try:
5
- import psycopg2
6
- from psycopg2 import sql
7
- from psycopg2.extensions import connection as PgConnection
8
- from psycopg2.extras import DictCursor
5
+ import psycopg
6
+ from psycopg import sql
7
+ from psycopg.connection import Connection as PgConnection
8
+ from psycopg.rows import DictRow, dict_row
9
9
  except ImportError:
10
- raise ImportError(
11
- "`psycopg2` not installed. Please install using `pip install psycopg2`. If you face issues, try `pip install psycopg2-binary`."
12
- )
10
+ raise ImportError("`psycopg` not installed. Please install using `pip install 'psycopg-binary'`.")
13
11
 
14
12
  from agno.tools import Toolkit
15
13
  from agno.utils.log import log_debug, log_error
@@ -18,7 +16,7 @@ from agno.utils.log import log_debug, log_error
18
16
  class PostgresTools(Toolkit):
19
17
  def __init__(
20
18
  self,
21
- connection: Optional[PgConnection] = None,
19
+ connection: Optional[PgConnection[DictRow]] = None,
22
20
  db_name: Optional[str] = None,
23
21
  user: Optional[str] = None,
24
22
  password: Optional[str] = None,
@@ -31,7 +29,7 @@ class PostgresTools(Toolkit):
31
29
  table_schema: str = "public",
32
30
  **kwargs,
33
31
  ):
34
- self._connection: Optional[PgConnection] = connection
32
+ self._connection: Optional[PgConnection[DictRow]] = connection
35
33
  self.db_name: Optional[str] = db_name
36
34
  self.user: Optional[str] = user
37
35
  self.password: Optional[str] = password
@@ -55,16 +53,16 @@ class PostgresTools(Toolkit):
55
53
  super().__init__(name="postgres_tools", tools=tools, **kwargs)
56
54
 
57
55
  @property
58
- def connection(self) -> PgConnection:
56
+ def connection(self) -> PgConnection[DictRow]:
59
57
  """
60
- Returns the Postgres psycopg2 connection.
61
- :return psycopg2.extensions.connection: psycopg2 connection
58
+ Returns the Postgres psycopg connection.
59
+ :return psycopg.connection.Connection: psycopg connection
62
60
  """
63
61
  if self._connection is None or self._connection.closed:
64
62
  log_debug("Establishing new PostgreSQL connection.")
65
- connection_kwargs: Dict[str, Any] = {"cursor_factory": DictCursor}
63
+ connection_kwargs: Dict[str, Any] = {"row_factory": dict_row}
66
64
  if self.db_name:
67
- connection_kwargs["database"] = self.db_name
65
+ connection_kwargs["dbname"] = self.db_name
68
66
  if self.user:
69
67
  connection_kwargs["user"] = self.user
70
68
  if self.password:
@@ -76,8 +74,8 @@ class PostgresTools(Toolkit):
76
74
 
77
75
  connection_kwargs["options"] = f"-c search_path={self.table_schema}"
78
76
 
79
- self._connection = psycopg2.connect(**connection_kwargs)
80
- self._connection.set_session(readonly=True)
77
+ self._connection = psycopg.connect(**connection_kwargs)
78
+ self._connection.read_only = True
81
79
 
82
80
  return self._connection
83
81
 
@@ -110,10 +108,10 @@ class PostgresTools(Toolkit):
110
108
  return f"Query returned no results.\nColumns: {', '.join(columns)}"
111
109
 
112
110
  header = ",".join(columns)
113
- data_rows = [",".join(map(str, row)) for row in rows]
111
+ data_rows = [",".join(map(str, row.values())) for row in rows]
114
112
  return f"{header}\n" + "\n".join(data_rows)
115
113
 
116
- except psycopg2.Error as e:
114
+ except psycopg.Error as e:
117
115
  log_error(f"Database error: {e}")
118
116
  if self.connection and not self.connection.closed:
119
117
  self.connection.rollback()
@@ -203,15 +201,18 @@ class PostgresTools(Toolkit):
203
201
  cursor.execute(query)
204
202
  stats = cursor.fetchone()
205
203
  summary_parts.append(f"\n--- Column: {col_name} (Type: {data_type}) ---")
206
- for key, value in stats.items():
207
- val_str = (
208
- f"{value:.2f}" if isinstance(value, (float, int)) and value is not None else str(value)
209
- )
210
- summary_parts.append(f" {key}: {val_str}")
204
+ if stats is not None:
205
+ for key, value in stats.items():
206
+ val_str = (
207
+ f"{value:.2f}" if isinstance(value, float) and value is not None else str(value)
208
+ )
209
+ summary_parts.append(f" {key}: {val_str}")
210
+ else:
211
+ summary_parts.append(" No statistics available")
211
212
 
212
213
  return "\n".join(summary_parts)
213
214
 
214
- except psycopg2.Error as e:
215
+ except psycopg.Error as e:
215
216
  return f"Error summarizing table: {e}"
216
217
 
217
218
  def inspect_query(self, query: str) -> str:
@@ -239,15 +240,19 @@ class PostgresTools(Toolkit):
239
240
  try:
240
241
  with self.connection.cursor() as cursor:
241
242
  cursor.execute(stmt)
243
+
244
+ if cursor.description is None:
245
+ return f"Error: Query returned no description for table '{table}'."
246
+
242
247
  columns = [desc[0] for desc in cursor.description]
243
248
 
244
249
  with open(path, "w", newline="", encoding="utf-8") as f:
245
250
  writer = csv.writer(f)
246
251
  writer.writerow(columns)
247
- writer.writerows(cursor)
252
+ writer.writerows(row.values() for row in cursor)
248
253
 
249
254
  return f"Successfully exported table '{table}' to '{path}'."
250
- except (psycopg2.Error, IOError) as e:
255
+ except (psycopg.Error, IOError) as e:
251
256
  return f"Error exporting table: {e}"
252
257
 
253
258
  def run_query(self, query: str) -> str:
agno/tools/zep.py CHANGED
@@ -118,7 +118,7 @@ class ZepTools(Toolkit):
118
118
 
119
119
  # Create session associated with the user
120
120
  try:
121
- self.zep_client.memory.add_session(session_id=self.session_id, user_id=self.user_id)
121
+ self.zep_client.thread.create(thread_id=self.session_id, user_id=self.user_id)
122
122
  log_debug(f"Created session {self.session_id} for user {self.user_id}")
123
123
  except Exception as e:
124
124
  log_debug(f"Session may already exist: {e}")
@@ -148,7 +148,7 @@ class ZepTools(Toolkit):
148
148
 
149
149
  try:
150
150
  zep_message = ZepMessage(
151
- role_type=role,
151
+ role=role,
152
152
  content=content,
153
153
  )
154
154
 
@@ -156,8 +156,8 @@ class ZepTools(Toolkit):
156
156
  ignore_roles_list = ["assistant"] if self.ignore_assistant_messages else None
157
157
 
158
158
  # Add message to Zep memory
159
- self.zep_client.memory.add( # type: ignore
160
- session_id=self.session_id,
159
+ self.zep_client.thread.add_messages( # type: ignore
160
+ thread_id=self.session_id,
161
161
  messages=[zep_message],
162
162
  ignore_roles=ignore_roles_list,
163
163
  )
@@ -171,7 +171,7 @@ class ZepTools(Toolkit):
171
171
  """
172
172
  Retrieves the memory for the current Zep session.
173
173
  Args:
174
- memory_type: The type of memory to retrieve ('context', 'messages', 'relevant_facts').
174
+ memory_type: The type of memory to retrieve ('context', 'messages').
175
175
  Returns:
176
176
  The requested memory content as a string, or an error string.
177
177
  """
@@ -181,21 +181,16 @@ class ZepTools(Toolkit):
181
181
 
182
182
  try:
183
183
  log_debug(f"Getting Zep memory for session {self.session_id}")
184
- memory_data = self.zep_client.memory.get(session_id=self.session_id) # type: ignore
185
184
 
186
185
  if memory_type == "context":
187
186
  # Ensure context is a string
188
- return memory_data.context or "No context available."
187
+ user_context = self.zep_client.thread.get_user_context(thread_id=self.session_id, mode="basic") # type: ignore
188
+ log_debug(f"Memory data: {user_context}")
189
+ return user_context.context or "No context available."
189
190
  elif memory_type == "messages":
191
+ messages_list = self.zep_client.thread.get(thread_id=self.session_id) # type: ignore
190
192
  # Ensure messages string representation is returned
191
- return str(memory_data.messages) if memory_data.messages else "No messages available."
192
- elif memory_type == "relevant_facts":
193
- # Return all relevant facts from memory
194
- if memory_data.relevant_facts:
195
- facts_str = "\n".join([f"- {fact.fact}" for fact in memory_data.relevant_facts])
196
- return f"Relevant facts:\n{facts_str}"
197
- else:
198
- return "No relevant facts available."
193
+ return str(messages_list.messages) if messages_list.messages else "No messages available."
199
194
  else:
200
195
  warning_msg = f"Unsupported memory_type requested: {memory_type}. Returning empty string."
201
196
  log_warning(warning_msg)
@@ -328,7 +323,7 @@ class ZepAsyncTools(Toolkit):
328
323
 
329
324
  # Create session associated with the user
330
325
  try:
331
- await self.zep_client.memory.add_session(session_id=self.session_id, user_id=self.user_id) # type: ignore
326
+ await self.zep_client.thread.create(thread_id=self.session_id, user_id=self.user_id) # type: ignore
332
327
  log_debug(f"Created session {self.session_id} for user {self.user_id}")
333
328
  except Exception as e:
334
329
  log_debug(f"Session may already exist: {e}")
@@ -361,7 +356,7 @@ class ZepAsyncTools(Toolkit):
361
356
 
362
357
  try:
363
358
  zep_message = ZepMessage(
364
- role_type=role,
359
+ role=role,
365
360
  content=content,
366
361
  )
367
362
 
@@ -369,8 +364,8 @@ class ZepAsyncTools(Toolkit):
369
364
  ignore_roles_list = ["assistant"] if self.ignore_assistant_messages else None
370
365
 
371
366
  # Add message to Zep memory
372
- await self.zep_client.memory.add( # type: ignore
373
- session_id=self.session_id,
367
+ await self.zep_client.thread.add_messages( # type: ignore
368
+ thread_id=self.session_id,
374
369
  messages=[zep_message],
375
370
  ignore_roles=ignore_roles_list,
376
371
  )
@@ -384,7 +379,7 @@ class ZepAsyncTools(Toolkit):
384
379
  """
385
380
  Retrieves the memory for the current Zep session.
386
381
  Args:
387
- memory_type: The type of memory to retrieve ('context', 'messages', 'relevant_facts').
382
+ memory_type: The type of memory to retrieve ('context', 'messages').
388
383
  Returns:
389
384
  The requested memory content as a string, or an error string.
390
385
  """
@@ -396,25 +391,19 @@ class ZepAsyncTools(Toolkit):
396
391
  return "Error: Zep client/session not initialized."
397
392
 
398
393
  try:
399
- memory_data = await self.zep_client.memory.get(session_id=self.session_id) # type: ignore
400
-
401
394
  if memory_type == "context":
402
395
  # Ensure context is a string
403
- return memory_data.context or "No context available."
396
+ user_context = await self.zep_client.thread.get_user_context(thread_id=self.session_id, mode="basic") # type: ignore
397
+ log_debug(f"Memory data: {user_context}")
398
+ return user_context.context or "No context available."
404
399
  elif memory_type == "messages":
405
400
  # Ensure messages string representation is returned
406
- return str(memory_data.messages) if memory_data.messages else "No messages available."
407
- elif memory_type == "relevant_facts":
408
- # Return relevant facts from memory
409
- if memory_data.relevant_facts:
410
- facts_str = "\n".join([f"- {fact.fact}" for fact in memory_data.relevant_facts])
411
- return f"Relevant facts:\n{facts_str}"
412
- else:
413
- return "No relevant facts available."
401
+ messages_list = await self.zep_client.thread.get(thread_id=self.session_id) # type: ignore
402
+ return str(messages_list.messages) if messages_list.messages else "No messages available."
414
403
  else:
415
404
  warning_msg = f"Unsupported memory_type requested: {memory_type}. Returning context."
416
405
  log_warning(warning_msg)
417
- return memory_data.context or "No context available."
406
+ return "No context available."
418
407
 
419
408
  except Exception as e:
420
409
  error_msg = f"Failed to get Zep memory for session {self.session_id}: {e}"
agno/utils/events.py CHANGED
@@ -7,6 +7,8 @@ from agno.reasoning.step import ReasoningStep
7
7
  from agno.run.response import (
8
8
  MemoryUpdateCompletedEvent,
9
9
  MemoryUpdateStartedEvent,
10
+ OutputModelResponseCompletedEvent,
11
+ OutputModelResponseStartedEvent,
10
12
  ParserModelResponseCompletedEvent,
11
13
  ParserModelResponseStartedEvent,
12
14
  ReasoningCompletedEvent,
@@ -25,6 +27,8 @@ from agno.run.response import (
25
27
  )
26
28
  from agno.run.team import MemoryUpdateCompletedEvent as TeamMemoryUpdateCompletedEvent
27
29
  from agno.run.team import MemoryUpdateStartedEvent as TeamMemoryUpdateStartedEvent
30
+ from agno.run.team import OutputModelResponseCompletedEvent as TeamOutputModelResponseCompletedEvent
31
+ from agno.run.team import OutputModelResponseStartedEvent as TeamOutputModelResponseStartedEvent
28
32
  from agno.run.team import ParserModelResponseCompletedEvent as TeamParserModelResponseCompletedEvent
29
33
  from agno.run.team import ParserModelResponseStartedEvent as TeamParserModelResponseStartedEvent
30
34
  from agno.run.team import ReasoningCompletedEvent as TeamReasoningCompletedEvent
@@ -356,6 +360,7 @@ def create_run_response_content_event(
356
360
  content: Optional[Any] = None,
357
361
  content_type: Optional[str] = None,
358
362
  thinking: Optional[str] = None,
363
+ reasoning_content: Optional[str] = None,
359
364
  redacted_thinking: Optional[str] = None,
360
365
  citations: Optional[Citations] = None,
361
366
  response_audio: Optional[AudioResponse] = None,
@@ -372,6 +377,7 @@ def create_run_response_content_event(
372
377
  content=content,
373
378
  content_type=content_type or "str",
374
379
  thinking=thinking_combined,
380
+ reasoning_content=reasoning_content,
375
381
  citations=citations,
376
382
  response_audio=response_audio,
377
383
  image=image,
@@ -452,3 +458,47 @@ def create_team_parser_model_response_completed_event(
452
458
  team_session_id=from_run_response.team_session_id, # type: ignore
453
459
  run_id=from_run_response.run_id,
454
460
  )
461
+
462
+
463
+ def create_output_model_response_started_event(from_run_response: RunResponse) -> OutputModelResponseStartedEvent:
464
+ return OutputModelResponseStartedEvent(
465
+ session_id=from_run_response.session_id,
466
+ agent_id=from_run_response.agent_id, # type: ignore
467
+ agent_name=from_run_response.agent_name, # type: ignore
468
+ team_session_id=from_run_response.team_session_id, # type: ignore
469
+ run_id=from_run_response.run_id,
470
+ )
471
+
472
+
473
+ def create_output_model_response_completed_event(from_run_response: RunResponse) -> OutputModelResponseCompletedEvent:
474
+ return OutputModelResponseCompletedEvent(
475
+ session_id=from_run_response.session_id,
476
+ agent_id=from_run_response.agent_id, # type: ignore
477
+ agent_name=from_run_response.agent_name, # type: ignore
478
+ team_session_id=from_run_response.team_session_id, # type: ignore
479
+ run_id=from_run_response.run_id,
480
+ )
481
+
482
+
483
+ def create_team_output_model_response_started_event(
484
+ from_run_response: TeamRunResponse,
485
+ ) -> TeamOutputModelResponseStartedEvent:
486
+ return TeamOutputModelResponseStartedEvent(
487
+ session_id=from_run_response.session_id,
488
+ team_id=from_run_response.team_id, # type: ignore
489
+ team_name=from_run_response.team_name, # type: ignore
490
+ team_session_id=from_run_response.team_session_id, # type: ignore
491
+ run_id=from_run_response.run_id,
492
+ )
493
+
494
+
495
+ def create_team_output_model_response_completed_event(
496
+ from_run_response: TeamRunResponse,
497
+ ) -> TeamOutputModelResponseCompletedEvent:
498
+ return TeamOutputModelResponseCompletedEvent(
499
+ session_id=from_run_response.session_id,
500
+ team_id=from_run_response.team_id, # type: ignore
501
+ team_name=from_run_response.team_name, # type: ignore
502
+ team_session_id=from_run_response.team_session_id, # type: ignore
503
+ run_id=from_run_response.run_id,
504
+ )
@@ -277,6 +277,7 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
277
277
  type="tool_use",
278
278
  )
279
279
  )
280
+
280
281
  # Skip empty assistant responses
281
282
  if message.role == "assistant" and not content:
282
283
  continue
agno/utils/response.py CHANGED
@@ -65,13 +65,15 @@ def format_tool_calls(tool_calls: List[ToolExecution]) -> List[str]:
65
65
  List[str]: List of formatted tool call strings
66
66
  """
67
67
  formatted_tool_calls = []
68
+
68
69
  for tool_call in tool_calls:
69
- if tool_call.tool_name and tool_call.tool_args:
70
+ if tool_call.tool_name is not None:
70
71
  tool_name = tool_call.tool_name
71
72
  args_str = ""
72
73
  if tool_call.tool_args is not None:
73
74
  args_str = ", ".join(f"{k}={v}" for k, v in tool_call.tool_args.items())
74
75
  formatted_tool_calls.append(f"{tool_name}({args_str})")
76
+
75
77
  return formatted_tool_calls
76
78
 
77
79
 
@@ -1631,7 +1631,7 @@ class Workflow:
1631
1631
  videos: Optional[List[Video]] = None,
1632
1632
  stream: bool = False,
1633
1633
  stream_intermediate_steps: bool = False,
1634
- markdown: bool = True,
1634
+ markdown: bool = False,
1635
1635
  show_time: bool = True,
1636
1636
  show_step_details: bool = True,
1637
1637
  console: Optional[Any] = None,
@@ -1699,7 +1699,7 @@ class Workflow:
1699
1699
  audio: Optional[List[Audio]] = None,
1700
1700
  images: Optional[List[Image]] = None,
1701
1701
  videos: Optional[List[Video]] = None,
1702
- markdown: bool = True,
1702
+ markdown: bool = False,
1703
1703
  show_time: bool = True,
1704
1704
  show_step_details: bool = True,
1705
1705
  console: Optional[Any] = None,
@@ -1856,7 +1856,7 @@ class Workflow:
1856
1856
  images: Optional[List[Image]] = None,
1857
1857
  videos: Optional[List[Video]] = None,
1858
1858
  stream_intermediate_steps: bool = False,
1859
- markdown: bool = True,
1859
+ markdown: bool = False,
1860
1860
  show_time: bool = True,
1861
1861
  show_step_details: bool = True,
1862
1862
  console: Optional[Any] = None,
@@ -2407,7 +2407,7 @@ class Workflow:
2407
2407
  videos: Optional[List[Video]] = None,
2408
2408
  stream: bool = False,
2409
2409
  stream_intermediate_steps: bool = False,
2410
- markdown: bool = True,
2410
+ markdown: bool = False,
2411
2411
  show_time: bool = True,
2412
2412
  show_step_details: bool = True,
2413
2413
  console: Optional[Any] = None,
@@ -2471,7 +2471,7 @@ class Workflow:
2471
2471
  audio: Optional[List[Audio]] = None,
2472
2472
  images: Optional[List[Image]] = None,
2473
2473
  videos: Optional[List[Video]] = None,
2474
- markdown: bool = True,
2474
+ markdown: bool = False,
2475
2475
  show_time: bool = True,
2476
2476
  show_step_details: bool = True,
2477
2477
  console: Optional[Any] = None,
@@ -2629,7 +2629,7 @@ class Workflow:
2629
2629
  images: Optional[List[Image]] = None,
2630
2630
  videos: Optional[List[Video]] = None,
2631
2631
  stream_intermediate_steps: bool = False,
2632
- markdown: bool = True,
2632
+ markdown: bool = False,
2633
2633
  show_time: bool = True,
2634
2634
  show_step_details: bool = True,
2635
2635
  console: Optional[Any] = None,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 1.7.6
3
+ Version: 1.7.8
4
4
  Summary: Agno: a lightweight library for building Multi-Agent Systems
5
5
  Author-email: Ashpreet Bedi <ashpreet@agno.com>
6
6
  License: Copyright (c) Agno, Inc.
@@ -421,7 +421,6 @@ Requires-Dist: types-pyyaml; extra == "dev"
421
421
  Requires-Dist: types-aiofiles; extra == "dev"
422
422
  Requires-Dist: fastapi; extra == "dev"
423
423
  Requires-Dist: uvicorn; extra == "dev"
424
- Requires-Dist: arxiv; extra == "dev"
425
424
  Provides-Extra: integration-tests
426
425
  Requires-Dist: exa_py; extra == "integration-tests"
427
426
  Requires-Dist: duckduckgo-search; extra == "integration-tests"
@@ -481,6 +480,8 @@ Provides-Extra: agentql
481
480
  Requires-Dist: agentql; extra == "agentql"
482
481
  Provides-Extra: apify
483
482
  Requires-Dist: apify-client; extra == "apify"
483
+ Provides-Extra: arxiv
484
+ Requires-Dist: arxiv; extra == "arxiv"
484
485
  Provides-Extra: brave
485
486
  Requires-Dist: brave-search; extra == "brave"
486
487
  Provides-Extra: browserbase
@@ -528,8 +529,9 @@ Requires-Dist: newspaper4k; extra == "newspaper"
528
529
  Requires-Dist: lxml_html_clean; extra == "newspaper"
529
530
  Provides-Extra: opencv
530
531
  Requires-Dist: opencv-python; extra == "opencv"
531
- Provides-Extra: psycopg2
532
- Requires-Dist: psycopg2-binary; extra == "psycopg2"
532
+ Provides-Extra: psycopg
533
+ Requires-Dist: psycopg-binary; extra == "psycopg"
534
+ Requires-Dist: psycopg; extra == "psycopg"
533
535
  Provides-Extra: todoist
534
536
  Requires-Dist: todoist-api-python; extra == "todoist"
535
537
  Provides-Extra: valyu
@@ -550,7 +552,6 @@ Provides-Extra: sql
550
552
  Requires-Dist: sqlalchemy; extra == "sql"
551
553
  Provides-Extra: postgres
552
554
  Requires-Dist: psycopg-binary; extra == "postgres"
553
- Requires-Dist: psycopg; extra == "postgres"
554
555
  Provides-Extra: sqlite
555
556
  Requires-Dist: sqlalchemy; extra == "sqlite"
556
557
  Provides-Extra: gcs
@@ -632,6 +633,7 @@ Requires-Dist: agno[openai]; extra == "models"
632
633
  Requires-Dist: agno[portkey]; extra == "models"
633
634
  Provides-Extra: tools
634
635
  Requires-Dist: agno[apify]; extra == "tools"
636
+ Requires-Dist: agno[arxiv]; extra == "tools"
635
637
  Requires-Dist: agno[brave]; extra == "tools"
636
638
  Requires-Dist: agno[exa]; extra == "tools"
637
639
  Requires-Dist: agno[cartesia]; extra == "tools"
@@ -660,7 +662,7 @@ Requires-Dist: agno[oxylabs]; extra == "tools"
660
662
  Requires-Dist: agno[zep]; extra == "tools"
661
663
  Requires-Dist: agno[mem0]; extra == "tools"
662
664
  Requires-Dist: agno[google_bigquery]; extra == "tools"
663
- Requires-Dist: agno[psycopg2]; extra == "tools"
665
+ Requires-Dist: agno[psycopg]; extra == "tools"
664
666
  Provides-Extra: storage
665
667
  Requires-Dist: agno[sql]; extra == "storage"
666
668
  Requires-Dist: agno[postgres]; extra == "storage"