agno 1.7.6__py3-none-any.whl → 1.7.7__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 CHANGED
@@ -713,15 +713,6 @@ class Agent:
713
713
  if self.workflow_session_state is not None:
714
714
  self.workflow_session_state["current_user_id"] = user_id
715
715
 
716
- def _reset_session_state(self) -> None:
717
- """Reset the session state for the agent."""
718
- if self.team_session_state is not None:
719
- self.team_session_state.pop("current_session_id", None)
720
- self.team_session_state.pop("current_user_id", None)
721
- if self.session_state is not None:
722
- self.session_state.pop("current_session_id", None)
723
- self.session_state.pop("current_user_id", None)
724
-
725
716
  def _initialize_session(
726
717
  self,
727
718
  session_id: Optional[str] = None,
@@ -1162,8 +1153,6 @@ class Agent:
1162
1153
  )
1163
1154
  else:
1164
1155
  return self.run_response
1165
- finally:
1166
- self._reset_session_state()
1167
1156
 
1168
1157
  # If we get here, all retries failed
1169
1158
  if last_exception is not None:
@@ -1540,8 +1529,6 @@ class Agent:
1540
1529
  )
1541
1530
  else:
1542
1531
  return self.run_response
1543
- finally:
1544
- self._reset_session_state()
1545
1532
 
1546
1533
  # If we get here, all retries failed
1547
1534
  if last_exception is not None:
@@ -1797,8 +1784,6 @@ class Agent:
1797
1784
  return self.create_run_response(
1798
1785
  run_state=RunStatus.cancelled, content="Operation cancelled by user", run_response=run_response
1799
1786
  )
1800
- finally:
1801
- self._reset_session_state()
1802
1787
 
1803
1788
  # If we get here, all retries failed
1804
1789
  if last_exception is not None:
@@ -2196,8 +2181,6 @@ class Agent:
2196
2181
  return self.create_run_response(
2197
2182
  run_state=RunStatus.cancelled, content="Operation cancelled by user", run_response=run_response
2198
2183
  )
2199
- finally:
2200
- self._reset_session_state()
2201
2184
 
2202
2185
  # If we get here, all retries failed
2203
2186
  if last_exception is not None:
@@ -5299,6 +5282,8 @@ class Agent:
5299
5282
  """
5300
5283
  from agno.document import Document
5301
5284
 
5285
+ if num_documents is None and self.knowledge is not None:
5286
+ num_documents = self.knowledge.num_documents
5302
5287
  # Validate the filters against known valid filter keys
5303
5288
  if self.knowledge is not None:
5304
5289
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -5338,9 +5323,6 @@ class Agent:
5338
5323
  ):
5339
5324
  return None
5340
5325
 
5341
- if num_documents is None:
5342
- num_documents = self.knowledge.num_documents
5343
-
5344
5326
  log_debug(f"Searching knowledge base with filters: {filters}")
5345
5327
  relevant_docs: List[Document] = self.knowledge.search(
5346
5328
  query=query, num_documents=num_documents, filters=filters
@@ -5361,6 +5343,9 @@ class Agent:
5361
5343
  """Get relevant documents from knowledge base asynchronously."""
5362
5344
  from agno.document import Document
5363
5345
 
5346
+ if num_documents is None and self.knowledge is not None:
5347
+ num_documents = self.knowledge.num_documents
5348
+
5364
5349
  # Validate the filters against known valid filter keys
5365
5350
  if self.knowledge is not None:
5366
5351
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -5404,9 +5389,6 @@ class Agent:
5404
5389
  ):
5405
5390
  return None
5406
5391
 
5407
- if num_documents is None:
5408
- num_documents = self.knowledge.num_documents
5409
-
5410
5392
  log_debug(f"Searching knowledge base with filters: {filters}")
5411
5393
  relevant_docs: List[Document] = await self.knowledge.async_search(
5412
5394
  query=query, num_documents=num_documents, filters=filters
@@ -1,5 +1,5 @@
1
1
  from os import getenv
2
- from typing import Any, Dict, List, Optional, Union
2
+ from typing import Any, Callable, Dict, List, Optional, Union
3
3
  from urllib.parse import quote
4
4
  from uuid import uuid4
5
5
 
@@ -110,13 +110,14 @@ class Playground:
110
110
  def get_async_router(self) -> APIRouter:
111
111
  return get_async_playground_router(self.agents, self.workflows, self.teams, self.app_id)
112
112
 
113
- def get_app(self, use_async: bool = True, prefix: str = "/v1") -> FastAPI:
113
+ def get_app(self, use_async: bool = True, prefix: str = "/v1", lifespan: Optional[Callable] = None) -> FastAPI:
114
114
  if not self.api_app:
115
115
  self.api_app = FastAPI(
116
116
  title=self.settings.title,
117
117
  docs_url="/docs" if self.settings.docs_enabled else None,
118
118
  redoc_url="/redoc" if self.settings.docs_enabled else None,
119
119
  openapi_url="/openapi.json" if self.settings.docs_enabled else None,
120
+ lifespan=lifespan,
120
121
  )
121
122
 
122
123
  if not self.api_app:
agno/embedder/openai.py CHANGED
@@ -16,7 +16,7 @@ except ImportError:
16
16
  @dataclass
17
17
  class OpenAIEmbedder(Embedder):
18
18
  id: str = "text-embedding-3-small"
19
- dimensions: int = 1536
19
+ dimensions: Optional[int] = None
20
20
  encoding_format: Literal["float", "base64"] = "float"
21
21
  user: Optional[str] = None
22
22
  api_key: Optional[str] = None
@@ -26,6 +26,10 @@ class OpenAIEmbedder(Embedder):
26
26
  client_params: Optional[Dict[str, Any]] = None
27
27
  openai_client: Optional[OpenAIClient] = None
28
28
 
29
+ def __post_init__(self):
30
+ if self.dimensions is None:
31
+ self.dimensions = 3072 if self.id == "text-embedding-3-large" else 1536
32
+
29
33
  @property
30
34
  def client(self) -> OpenAIClient:
31
35
  if self.openai_client:
@@ -570,8 +570,16 @@ class Claude(Model):
570
570
  }
571
571
 
572
572
  elif isinstance(response, ContentBlockStopEvent):
573
+ # Handle completed thinking content
574
+ if response.content_block.type == "thinking": # type: ignore
575
+ model_response.thinking = response.content_block.thinking # type: ignore
576
+ # Store signature if available
577
+ if hasattr(response.content_block, "signature"): # type: ignore
578
+ model_response.provider_data = {
579
+ "signature": response.content_block.signature, # type: ignore
580
+ }
573
581
  # Handle tool calls
574
- if response.content_block.type == "tool_use": # type: ignore
582
+ elif response.content_block.type == "tool_use": # type: ignore
575
583
  tool_use = response.content_block # type: ignore
576
584
  tool_name = tool_use.name
577
585
  tool_input = tool_use.input
@@ -713,15 +713,29 @@ class Gemini(Model):
713
713
  if isinstance(text_content, str):
714
714
  # Check if this is a thought summary
715
715
  if hasattr(part, "thought") and part.thought:
716
- model_response.reasoning_content = text_content
716
+ # Add all parts as single message
717
+ if model_response.reasoning_content is None:
718
+ model_response.reasoning_content = text_content
719
+ else:
720
+ model_response.reasoning_content += text_content
717
721
  else:
718
- model_response.content = text_content
722
+ if model_response.content is None:
723
+ model_response.content = text_content
724
+ else:
725
+ model_response.content += text_content
719
726
  else:
720
727
  content_str = str(text_content) if text_content is not None else ""
721
728
  if hasattr(part, "thought") and part.thought:
722
- model_response.reasoning_content = content_str
729
+ # Add all parts as single message
730
+ if model_response.reasoning_content is None:
731
+ model_response.reasoning_content = content_str
732
+ else:
733
+ model_response.reasoning_content += content_str
723
734
  else:
724
- model_response.content = content_str
735
+ if model_response.content is None:
736
+ model_response.content = content_str
737
+ else:
738
+ model_response.content += content_str
725
739
 
726
740
  if hasattr(part, "inline_data") and part.inline_data is not None:
727
741
  model_response.image = ImageArtifact(
@@ -803,9 +817,15 @@ class Gemini(Model):
803
817
  text_content = str(part.text) if part.text is not None else ""
804
818
  # Check if this is a thought summary
805
819
  if hasattr(part, "thought") and part.thought:
806
- model_response.reasoning_content = text_content
820
+ if model_response.reasoning_content is None:
821
+ model_response.reasoning_content = text_content
822
+ else:
823
+ model_response.reasoning_content += text_content
807
824
  else:
808
- model_response.content = text_content
825
+ if model_response.content is None:
826
+ model_response.content = text_content
827
+ else:
828
+ model_response.content += text_content
809
829
 
810
830
  if hasattr(part, "inline_data") and part.inline_data is not None:
811
831
  model_response.image = ImageArtifact(
@@ -9,6 +9,7 @@ from agno.models.base import Model
9
9
  from agno.models.message import Message
10
10
  from agno.models.response import ModelResponse
11
11
  from agno.utils.log import log_debug, log_error, log_warning
12
+ from agno.utils.openai import _format_file_for_message, audio_to_message, images_to_message
12
13
 
13
14
  try:
14
15
  import litellm
@@ -73,6 +74,31 @@ class LiteLLM(Model):
73
74
  for m in messages:
74
75
  msg = {"role": m.role, "content": m.content if m.content is not None else ""}
75
76
 
77
+ # Handle media
78
+ if (m.images is not None and len(m.images) > 0) or (m.audio is not None and len(m.audio) > 0):
79
+ if isinstance(m.content, str):
80
+ content_list = [{"type": "text", "text": m.content}]
81
+ if m.images is not None:
82
+ content_list.extend(images_to_message(images=m.images))
83
+ if m.audio is not None:
84
+ content_list.extend(audio_to_message(audio=m.audio))
85
+ msg["content"] = content_list
86
+
87
+ if m.videos is not None and len(m.videos) > 0:
88
+ log_warning("Video input is currently unsupported by LLM providers.")
89
+
90
+ # Handle files
91
+ if m.files is not None:
92
+ if isinstance(msg["content"], str):
93
+ content_list = [{"type": "text", "text": msg["content"]}]
94
+ else:
95
+ content_list = msg["content"]
96
+ for file in m.files:
97
+ file_part = _format_file_for_message(file)
98
+ if file_part:
99
+ content_list.append(file_part)
100
+ msg["content"] = content_list
101
+
76
102
  # Handle tool calls in assistant messages
77
103
  if m.role == "assistant" and m.tool_calls:
78
104
  msg["tool_calls"] = [
@@ -95,12 +121,8 @@ class LiteLLM(Model):
95
121
  if m.images is not None and len(m.images) > 0:
96
122
  log_warning("Image input is currently unsupported.")
97
123
 
98
- if m.files is not None and len(m.files) > 0:
99
- log_warning("File input is currently unsupported.")
100
-
101
124
  if m.videos is not None and len(m.videos) > 0:
102
125
  log_warning("Video input is currently unsupported.")
103
-
104
126
  formatted_messages.append(msg)
105
127
 
106
128
  return formatted_messages
agno/models/message.py CHANGED
@@ -248,6 +248,7 @@ class Message(BaseModel):
248
248
  "tool_calls": self.tool_calls,
249
249
  "thinking": self.thinking,
250
250
  "redacted_thinking": self.redacted_thinking,
251
+ "provider_data": self.provider_data,
251
252
  }
252
253
  # Filter out None and empty collections
253
254
  message_dict = {
agno/storage/mysql.py CHANGED
@@ -131,6 +131,7 @@ class MySQLStorage(Storage):
131
131
  elif self.mode == "workflow_v2":
132
132
  specific_columns = [
133
133
  Column("workflow_id", String(255), index=True),
134
+ Column("workflow_name", String(255), index=True),
134
135
  Column("workflow_data", JSON),
135
136
  Column("runs", JSON),
136
137
  ]
agno/storage/postgres.py CHANGED
@@ -30,7 +30,7 @@ class PostgresStorage(Storage):
30
30
  db_engine: Optional[Engine] = None,
31
31
  schema_version: int = 1,
32
32
  auto_upgrade_schema: bool = False,
33
- mode: Optional[Literal["agent", "team", "workflow"]] = "agent",
33
+ mode: Optional[Literal["agent", "team", "workflow", "workflow_v2"]] = "agent",
34
34
  ):
35
35
  """
36
36
  This class provides agent storage using a PostgreSQL table.
@@ -131,6 +131,7 @@ class PostgresStorage(Storage):
131
131
  elif self.mode == "workflow_v2":
132
132
  specific_columns = [
133
133
  Column("workflow_id", String, index=True),
134
+ Column("workflow_name", String, index=True),
134
135
  Column("workflow_data", postgresql.JSONB),
135
136
  Column("runs", postgresql.JSONB),
136
137
  ]
@@ -570,9 +571,9 @@ class PostgresStorage(Storage):
570
571
  stmt = postgresql.insert(self.table).values(
571
572
  session_id=session.session_id,
572
573
  workflow_id=session.workflow_id, # type: ignore
573
- workflow_name=session.workflow_name, # type: ignore
574
574
  user_id=session.user_id,
575
575
  runs=session_dict.get("runs"),
576
+ workflow_name=session.workflow_name, # type: ignore
576
577
  workflow_data=session.workflow_data, # type: ignore
577
578
  session_data=session.session_data,
578
579
  extra_data=session.extra_data,
@@ -583,9 +584,9 @@ class PostgresStorage(Storage):
583
584
  index_elements=["session_id"],
584
585
  set_=dict(
585
586
  workflow_id=session.workflow_id, # type: ignore
586
- workflow_name=session.workflow_name, # type: ignore
587
587
  user_id=session.user_id,
588
588
  runs=session_dict.get("runs"),
589
+ workflow_name=session.workflow_name, # type: ignore
589
590
  workflow_data=session.workflow_data, # type: ignore
590
591
  session_data=session.session_data,
591
592
  extra_data=session.extra_data,
agno/team/team.py CHANGED
@@ -688,15 +688,6 @@ class Team:
688
688
  if self.workflow_session_state is not None:
689
689
  self.workflow_session_state["current_user_id"] = user_id
690
690
 
691
- def _reset_session_state(self) -> None:
692
- """Reset the session state for the agent."""
693
- if self.team_session_state is not None:
694
- self.team_session_state.pop("current_session_id", None)
695
- self.team_session_state.pop("current_user_id", None)
696
- if self.session_state is not None:
697
- self.session_state.pop("current_session_id", None)
698
- self.session_state.pop("current_user_id", None)
699
-
700
691
  def _initialize_session(
701
692
  self,
702
693
  session_id: Optional[str] = None,
@@ -974,8 +965,6 @@ class Team:
974
965
  from_run_response=run_response,
975
966
  session_id=session_id,
976
967
  )
977
- finally:
978
- self._reset_session_state()
979
968
 
980
969
  # If we get here, all retries failed
981
970
  if last_exception is not None:
@@ -1363,8 +1352,6 @@ class Team:
1363
1352
  from_run_response=run_response,
1364
1353
  session_id=session_id,
1365
1354
  )
1366
- finally:
1367
- self._reset_session_state()
1368
1355
 
1369
1356
  # If we get here, all retries failed
1370
1357
  if last_exception is not None:
@@ -7385,6 +7372,9 @@ class Team:
7385
7372
  """Return a list of references from the knowledge base"""
7386
7373
  from agno.document import Document
7387
7374
 
7375
+ if num_documents is None and self.knowledge is not None:
7376
+ num_documents = self.knowledge.num_documents
7377
+
7388
7378
  # Validate the filters against known valid filter keys
7389
7379
  if self.knowledge is not None:
7390
7380
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -7419,9 +7409,6 @@ class Team:
7419
7409
  if self.knowledge is None or self.knowledge.vector_db is None:
7420
7410
  return None
7421
7411
 
7422
- if num_documents is None:
7423
- num_documents = self.knowledge.num_documents
7424
-
7425
7412
  log_debug(f"Searching knowledge base with filters: {filters}")
7426
7413
  relevant_docs: List[Document] = self.knowledge.search(
7427
7414
  query=query, num_documents=num_documents, filters=filters
@@ -7442,6 +7429,9 @@ class Team:
7442
7429
  """Get relevant documents from knowledge base asynchronously."""
7443
7430
  from agno.document import Document
7444
7431
 
7432
+ if num_documents is None and self.knowledge is not None:
7433
+ num_documents = self.knowledge.num_documents
7434
+
7445
7435
  # Validate the filters against known valid filter keys
7446
7436
  if self.knowledge is not None:
7447
7437
  valid_filters, invalid_keys = self.knowledge.validate_filters(filters) # type: ignore
@@ -7478,9 +7468,6 @@ class Team:
7478
7468
  if self.knowledge is None or self.knowledge.vector_db is None:
7479
7469
  return None
7480
7470
 
7481
- if num_documents is None:
7482
- num_documents = self.knowledge.num_documents
7483
-
7484
7471
  log_debug(f"Searching knowledge base with filters: {filters}")
7485
7472
  relevant_docs: List[Document] = await self.knowledge.async_search(
7486
7473
  query=query, num_documents=num_documents, filters=filters
agno/tools/jina.py CHANGED
@@ -5,7 +5,7 @@ import httpx
5
5
  from pydantic import BaseModel, Field, HttpUrl
6
6
 
7
7
  from agno.tools import Toolkit
8
- from agno.utils.log import log_info, logger
8
+ from agno.utils.log import logger
9
9
 
10
10
 
11
11
  class JinaReaderToolsConfig(BaseModel):
@@ -14,6 +14,7 @@ class JinaReaderToolsConfig(BaseModel):
14
14
  search_url: HttpUrl = Field("https://s.jina.ai/", description="Search URL for Jina Reader API") # type: ignore
15
15
  max_content_length: int = Field(10000, description="Maximum content length in characters")
16
16
  timeout: Optional[int] = Field(None, description="Timeout for Jina Reader API requests")
17
+ search_query_content: Optional[bool] = Field(False, description="Toggle full URL content in query search result")
17
18
 
18
19
 
19
20
  class JinaReaderTools(Toolkit):
@@ -26,14 +27,17 @@ class JinaReaderTools(Toolkit):
26
27
  timeout: Optional[int] = None,
27
28
  read_url: bool = True,
28
29
  search_query: bool = False,
30
+ search_query_content: bool = True,
29
31
  **kwargs,
30
32
  ):
33
+ self.api_key = api_key or getenv("JINA_API_KEY")
31
34
  self.config: JinaReaderToolsConfig = JinaReaderToolsConfig(
32
- api_key=api_key,
35
+ api_key=self.api_key,
33
36
  base_url=base_url,
34
37
  search_url=search_url,
35
38
  max_content_length=max_content_length,
36
39
  timeout=timeout,
40
+ search_query_content=search_query_content,
37
41
  )
38
42
 
39
43
  tools: List[Any] = []
@@ -47,7 +51,6 @@ class JinaReaderTools(Toolkit):
47
51
  def read_url(self, url: str) -> str:
48
52
  """Reads a URL and returns the truncated content using Jina Reader API."""
49
53
  full_url = f"{self.config.base_url}{url}"
50
- log_info(f"Reading URL: {full_url}")
51
54
  try:
52
55
  response = httpx.get(full_url, headers=self._get_headers())
53
56
  response.raise_for_status()
@@ -60,10 +63,14 @@ class JinaReaderTools(Toolkit):
60
63
 
61
64
  def search_query(self, query: str) -> str:
62
65
  """Performs a web search using Jina Reader API and returns the truncated results."""
63
- full_url = f"{self.config.search_url}{query}"
64
- log_info(f"Performing search: {full_url}")
66
+ full_url = f"{self.config.search_url}"
67
+ headers = self._get_headers()
68
+ if not self.config.search_query_content:
69
+ headers["X-Respond-With"] = "no-content" # to avoid returning full content in search results
70
+
71
+ body = {"q": query}
65
72
  try:
66
- response = httpx.get(full_url, headers=self._get_headers())
73
+ response = httpx.post(full_url, headers=headers, json=body)
67
74
  response.raise_for_status()
68
75
  content = response.json()
69
76
  return self._truncate_content(str(content))