agno 1.8.0__py3-none-any.whl → 1.8.1__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
@@ -352,6 +352,7 @@ class Agent:
352
352
  agent_id: Optional[str] = None,
353
353
  introduction: Optional[str] = None,
354
354
  user_id: Optional[str] = None,
355
+ app_id: Optional[str] = None,
355
356
  session_id: Optional[str] = None,
356
357
  session_name: Optional[str] = None,
357
358
  session_state: Optional[Dict[str, Any]] = None,
@@ -443,6 +444,7 @@ class Agent:
443
444
  self.agent_id = agent_id
444
445
  self.introduction = introduction
445
446
  self.user_id = user_id
447
+ self.app_id = app_id
446
448
 
447
449
  self.session_id = session_id
448
450
  self.session_name = session_name
@@ -747,9 +749,7 @@ class Agent:
747
749
  self.session_id = session_id = str(uuid4())
748
750
 
749
751
  # Use the default user_id when necessary
750
- if user_id is not None and user_id != "":
751
- user_id = user_id
752
- else:
752
+ if user_id is None or user_id == "":
753
753
  user_id = self.user_id
754
754
 
755
755
  # Determine the session_state
agno/knowledge/agent.py CHANGED
@@ -161,7 +161,8 @@ class AgentKnowledge(BaseModel):
161
161
 
162
162
  # Upsert documents if upsert is True and vector db supports upsert
163
163
  if upsert and self.vector_db.upsert_available():
164
- self.vector_db.upsert(documents=documents_to_load, filters=doc.meta_data)
164
+ for doc in documents_to_load:
165
+ self.vector_db.upsert(documents=[doc], filters=doc.meta_data)
165
166
  # Insert documents
166
167
  else:
167
168
  # Filter out documents which already exist in the vector db
@@ -170,7 +171,8 @@ class AgentKnowledge(BaseModel):
170
171
  documents_to_load = self.filter_existing_documents(document_list)
171
172
 
172
173
  if documents_to_load:
173
- self.vector_db.insert(documents=documents_to_load, filters=doc.meta_data)
174
+ for doc in documents_to_load:
175
+ self.vector_db.insert(documents=[doc], filters=doc.meta_data)
174
176
 
175
177
  num_documents += len(documents_to_load)
176
178
  log_info(f"Added {num_documents} documents to knowledge base")
@@ -204,7 +206,8 @@ class AgentKnowledge(BaseModel):
204
206
 
205
207
  # Upsert documents if upsert is True and vector db supports upsert
206
208
  if upsert and self.vector_db.upsert_available():
207
- await self.vector_db.async_upsert(documents=documents_to_load, filters=doc.meta_data)
209
+ for doc in documents_to_load:
210
+ await self.vector_db.async_upsert(documents=[doc], filters=doc.meta_data)
208
211
  # Insert documents
209
212
  else:
210
213
  # Filter out documents which already exist in the vector db
@@ -213,7 +216,8 @@ class AgentKnowledge(BaseModel):
213
216
  documents_to_load = await self.async_filter_existing_documents(document_list)
214
217
 
215
218
  if documents_to_load:
216
- await self.vector_db.async_insert(documents=documents_to_load, filters=doc.meta_data)
219
+ for doc in documents_to_load:
220
+ await self.vector_db.async_insert(documents=[doc], filters=doc.meta_data)
217
221
 
218
222
  num_documents += len(documents_to_load)
219
223
  log_info(f"Added {num_documents} documents to knowledge base")
agno/media.py CHANGED
@@ -157,7 +157,7 @@ class Video(BaseModel):
157
157
 
158
158
  @classmethod
159
159
  def from_artifact(cls, artifact: VideoArtifact) -> "Video":
160
- return cls(url=artifact.url)
160
+ return cls(url=artifact.url, content=artifact.content, format=artifact.mime_type)
161
161
 
162
162
 
163
163
  class Audio(BaseModel):
@@ -329,7 +329,7 @@ class Image(BaseModel):
329
329
 
330
330
  @classmethod
331
331
  def from_artifact(cls, artifact: ImageArtifact) -> "Image":
332
- return cls(url=artifact.url)
332
+ return cls(url=artifact.url, content=artifact.content, format=artifact.mime_type)
333
333
 
334
334
 
335
335
  class File(BaseModel):
@@ -19,7 +19,7 @@ class DashScope(OpenAILike):
19
19
  provider (str): The provider name. Defaults to "Qwen".
20
20
  api_key (Optional[str]): The DashScope API key.
21
21
  base_url (str): The base URL. Defaults to "https://dashscope-intl.aliyuncs.com/compatible-mode/v1".
22
- enable_thinking (Optional[bool]): Enable thinking process (DashScope native parameter). Defaults to None.
22
+ enable_thinking (bool): Enable thinking process (DashScope native parameter). Defaults to False.
23
23
  include_thoughts (Optional[bool]): Include thinking process in response (alternative parameter). Defaults to None.
24
24
  """
25
25
 
@@ -31,8 +31,9 @@ class DashScope(OpenAILike):
31
31
  base_url: str = "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
32
32
 
33
33
  # Thinking parameters
34
- enable_thinking: Optional[bool] = None
34
+ enable_thinking: bool = False
35
35
  include_thoughts: Optional[bool] = None
36
+ thinking_budget: Optional[int] = None
36
37
 
37
38
  # DashScope supports structured outputs
38
39
  supports_native_structured_outputs: bool = True
@@ -75,7 +76,15 @@ class DashScope(OpenAILike):
75
76
  ) -> Dict[str, Any]:
76
77
  params = super().get_request_params(response_format=response_format, tools=tools, tool_choice=tool_choice)
77
78
 
78
- should_include_thoughts = self.enable_thinking or self.include_thoughts
79
- if should_include_thoughts:
80
- params["extra_body"] = {"enable_thinking": True}
79
+ if self.include_thoughts is not None:
80
+ self.enable_thinking = self.include_thoughts
81
+
82
+ if self.enable_thinking is not None:
83
+ params["extra_body"] = {
84
+ "enable_thinking": self.enable_thinking,
85
+ }
86
+
87
+ if self.thinking_budget is not None:
88
+ params["extra_body"]["thinking_budget"] = self.thinking_budget
89
+
81
90
  return params
@@ -793,12 +793,18 @@ class Gemini(Model):
793
793
  grounding_metadata = response.candidates[0].grounding_metadata.model_dump()
794
794
  citations_raw["grounding_metadata"] = grounding_metadata
795
795
 
796
- chunks = grounding_metadata.get("grounding_chunks", [])
797
- citation_pairs = [
798
- (chunk.get("web", {}).get("uri"), chunk.get("web", {}).get("title"))
799
- for chunk in chunks
800
- if chunk.get("web", {}).get("uri")
801
- ]
796
+ chunks = grounding_metadata.get("grounding_chunks", []) or []
797
+ citation_pairs = []
798
+ for chunk in chunks:
799
+ if not isinstance(chunk, dict):
800
+ continue
801
+ web = chunk.get("web")
802
+ if not isinstance(web, dict):
803
+ continue
804
+ uri = web.get("uri")
805
+ title = web.get("title")
806
+ if uri:
807
+ citation_pairs.append((uri, title))
802
808
 
803
809
  # Create citation objects from filtered pairs
804
810
  grounding_urls = [UrlCitation(url=url, title=title) for url, title in citation_pairs]
@@ -907,11 +913,17 @@ class Gemini(Model):
907
913
 
908
914
  # Extract url and title
909
915
  chunks = grounding_metadata.pop("grounding_chunks", None) or []
910
- citation_pairs = [
911
- (chunk.get("web", {}).get("uri"), chunk.get("web", {}).get("title"))
912
- for chunk in chunks
913
- if chunk.get("web", {}).get("uri")
914
- ]
916
+ citation_pairs = []
917
+ for chunk in chunks:
918
+ if not isinstance(chunk, dict):
919
+ continue
920
+ web = chunk.get("web")
921
+ if not isinstance(web, dict):
922
+ continue
923
+ uri = web.get("uri")
924
+ title = web.get("title")
925
+ if uri:
926
+ citation_pairs.append((uri, title))
915
927
 
916
928
  # Create citation objects from filtered pairs
917
929
  citations.urls = [UrlCitation(url=url, title=title) for url, title in citation_pairs]
@@ -77,7 +77,7 @@ class OpenAIChat(Model):
77
77
  max_retries: Optional[int] = None
78
78
  default_headers: Optional[Any] = None
79
79
  default_query: Optional[Any] = None
80
- http_client: Optional[httpx.Client] = None
80
+ http_client: Optional[Union[httpx.Client, httpx.AsyncClient]] = None
81
81
  client_params: Optional[Dict[str, Any]] = None
82
82
 
83
83
  # The role to map the message role to.
@@ -123,8 +123,11 @@ class OpenAIChat(Model):
123
123
  OpenAIClient: An instance of the OpenAI client.
124
124
  """
125
125
  client_params: Dict[str, Any] = self._get_client_params()
126
- if self.http_client is not None:
127
- client_params["http_client"] = self.http_client
126
+ if self.http_client:
127
+ if isinstance(self.http_client, httpx.Client):
128
+ client_params["http_client"] = self.http_client
129
+ else:
130
+ log_warning("http_client is not an instance of httpx.Client.")
128
131
  return OpenAIClient(**client_params)
129
132
 
130
133
  def get_async_client(self) -> AsyncOpenAIClient:
@@ -136,7 +139,14 @@ class OpenAIChat(Model):
136
139
  """
137
140
  client_params: Dict[str, Any] = self._get_client_params()
138
141
  if self.http_client:
139
- client_params["http_client"] = self.http_client
142
+ if isinstance(self.http_client, httpx.AsyncClient):
143
+ client_params["http_client"] = self.http_client
144
+ else:
145
+ log_warning("http_client is not an instance of httpx.AsyncClient. Using default httpx.AsyncClient.")
146
+ # Create a new async HTTP client with custom limits
147
+ client_params["http_client"] = httpx.AsyncClient(
148
+ limits=httpx.Limits(max_connections=1000, max_keepalive_connections=100)
149
+ )
140
150
  else:
141
151
  # Create a new async HTTP client with custom limits
142
152
  client_params["http_client"] = httpx.AsyncClient(
@@ -44,6 +44,7 @@ class OpenAIResponses(Model):
44
44
  reasoning: Optional[Dict[str, Any]] = None
45
45
  verbosity: Optional[Literal["low", "medium", "high"]] = None
46
46
  reasoning_effort: Optional[Literal["minimal", "medium", "high"]] = None
47
+ reasoning_summary: Optional[Literal["auto", "concise", "detailed"]] = None
47
48
  store: Optional[bool] = None
48
49
  temperature: Optional[float] = None
49
50
  top_p: Optional[float] = None
@@ -84,6 +85,18 @@ class OpenAIResponses(Model):
84
85
  """Return True if the contextual used model is a known reasoning model."""
85
86
  return self.id.startswith("o3") or self.id.startswith("o4-mini") or self.id.startswith("gpt-5")
86
87
 
88
+ def _set_reasoning_request_param(self, base_params: Dict[str, Any]) -> Dict[str, Any]:
89
+ """Set the reasoning request parameter."""
90
+ base_params["reasoning"] = self.reasoning or {}
91
+
92
+ if self.reasoning_effort is not None:
93
+ base_params["reasoning"]["effort"] = self.reasoning_effort
94
+
95
+ if self.reasoning_summary is not None:
96
+ base_params["reasoning"]["summary"] = self.reasoning_summary
97
+
98
+ return base_params
99
+
87
100
  def _get_client_params(self) -> Dict[str, Any]:
88
101
  """
89
102
  Get client parameters for API requests.
@@ -185,12 +198,8 @@ class OpenAIResponses(Model):
185
198
  "user": self.user,
186
199
  "service_tier": self.service_tier,
187
200
  }
188
-
189
- # Handle reasoning parameter - convert reasoning_effort to reasoning format
190
- if self.reasoning is not None:
191
- base_params["reasoning"] = self.reasoning
192
- elif self.reasoning_effort is not None:
193
- base_params["reasoning"] = {"effort": self.reasoning_effort}
201
+ # Populate the reasoning parameter
202
+ base_params = self._set_reasoning_request_param(base_params)
194
203
 
195
204
  # Build text parameter
196
205
  text_params: Dict[str, Any] = {}
@@ -478,7 +487,6 @@ class OpenAIResponses(Model):
478
487
  request_params = self.get_request_params(
479
488
  messages=messages, response_format=response_format, tools=tools, tool_choice=tool_choice
480
489
  )
481
-
482
490
  return self.get_client().responses.create(
483
491
  model=self.id,
484
492
  input=self._format_messages(messages), # type: ignore
@@ -730,7 +738,10 @@ class OpenAIResponses(Model):
730
738
 
731
739
  # Add role
732
740
  model_response.role = "assistant"
741
+ reasoning_summary: str = ""
742
+
733
743
  for output in response.output:
744
+ # Add content
734
745
  if output.type == "message":
735
746
  model_response.content = response.output_text
736
747
 
@@ -746,6 +757,8 @@ class OpenAIResponses(Model):
746
757
  citations.urls.append(UrlCitation(url=annotation.url, title=annotation.title))
747
758
  if citations.urls or citations.documents:
748
759
  model_response.citations = citations
760
+
761
+ # Add tool calls
749
762
  elif output.type == "function_call":
750
763
  if model_response.tool_calls is None:
751
764
  model_response.tool_calls = []
@@ -765,10 +778,24 @@ class OpenAIResponses(Model):
765
778
  model_response.extra = model_response.extra or {}
766
779
  model_response.extra.setdefault("tool_call_ids", []).append(output.call_id)
767
780
 
768
- # i.e. we asked for reasoning, so we need to add the reasoning content
769
- if self.reasoning is not None:
781
+ # Add reasoning summary
782
+ elif output.type == "reasoning":
783
+ if reasoning_summaries := getattr(output, "summary", None):
784
+ for summary in reasoning_summaries:
785
+ if isinstance(summary, dict):
786
+ summary_text = summary.get("text")
787
+ else:
788
+ summary_text = getattr(summary, "text", None)
789
+ if summary_text:
790
+ reasoning_summary = (reasoning_summary or "") + summary_text
791
+
792
+ # Add reasoning content
793
+ if reasoning_summary is not None:
794
+ model_response.reasoning_content = reasoning_summary
795
+ elif self.reasoning is not None:
770
796
  model_response.reasoning_content = response.output_text
771
797
 
798
+ # Add metrics
772
799
  if response.usage is not None:
773
800
  model_response.response_usage = response.usage
774
801
 
@@ -835,7 +862,8 @@ class OpenAIResponses(Model):
835
862
  model_response.content = stream_event.delta
836
863
  stream_data.response_content += stream_event.delta
837
864
 
838
- if self.reasoning is not None:
865
+ # Treat the output_text deltas as reasoning content if the reasoning summary is not requested.
866
+ if self.reasoning is not None and self.reasoning_summary is None:
839
867
  model_response.reasoning_content = stream_event.delta
840
868
  stream_data.response_thinking += stream_event.delta
841
869
 
@@ -868,7 +896,24 @@ class OpenAIResponses(Model):
868
896
 
869
897
  elif stream_event.type == "response.completed":
870
898
  model_response = ModelResponse()
871
- # Add usage metrics if present
899
+
900
+ # Add reasoning summary
901
+ if self.reasoning_summary is not None:
902
+ summary_text: str = ""
903
+ for out in getattr(stream_event.response, "output", []) or []:
904
+ if getattr(out, "type", None) == "reasoning":
905
+ summaries = getattr(out, "summary", None)
906
+ if summaries:
907
+ for s in summaries:
908
+ text_val = s.get("text") if isinstance(s, dict) else getattr(s, "text", None)
909
+ if text_val:
910
+ if summary_text:
911
+ summary_text += "\n\n"
912
+ summary_text += text_val
913
+ if summary_text:
914
+ model_response.reasoning_content = summary_text
915
+
916
+ # Add metrics
872
917
  if stream_event.response.usage is not None:
873
918
  model_response.response_usage = stream_event.response.usage
874
919
 
agno/team/team.py CHANGED
@@ -157,6 +157,8 @@ class Team:
157
157
  add_datetime_to_instructions: bool = False
158
158
  # If True, add the current location to the instructions to give the team a sense of location
159
159
  add_location_to_instructions: bool = False
160
+ # Allows for custom timezone for datetime instructions following the TZ Database format (e.g. "Etc/UTC")
161
+ timezone_identifier: Optional[str] = None
160
162
  # If True, add the tools available to team members to the system message
161
163
  add_member_tools_to_system_message: bool = True
162
164
 
@@ -328,6 +330,7 @@ class Team:
328
330
  markdown: bool = False,
329
331
  add_datetime_to_instructions: bool = False,
330
332
  add_location_to_instructions: bool = False,
333
+ timezone_identifier: Optional[str] = None,
331
334
  add_member_tools_to_system_message: bool = True,
332
335
  system_message: Optional[Union[str, Callable, Message]] = None,
333
336
  system_message_role: str = "system",
@@ -411,6 +414,7 @@ class Team:
411
414
  self.markdown = markdown
412
415
  self.add_datetime_to_instructions = add_datetime_to_instructions
413
416
  self.add_location_to_instructions = add_location_to_instructions
417
+ self.timezone_identifier = timezone_identifier
414
418
  self.add_member_tools_to_system_message = add_member_tools_to_system_message
415
419
  self.system_message = system_message
416
420
  self.system_message_role = system_message_role
@@ -5308,7 +5312,19 @@ class Team:
5308
5312
  if self.add_datetime_to_instructions:
5309
5313
  from datetime import datetime
5310
5314
 
5311
- additional_information.append(f"The current time is {datetime.now()}")
5315
+ tz = None
5316
+
5317
+ if self.timezone_identifier:
5318
+ try:
5319
+ from zoneinfo import ZoneInfo
5320
+
5321
+ tz = ZoneInfo(self.timezone_identifier)
5322
+ except Exception:
5323
+ log_warning("Invalid timezone identifier")
5324
+
5325
+ time = datetime.now(tz) if tz else datetime.now()
5326
+
5327
+ additional_information.append(f"The current time is {time}.")
5312
5328
 
5313
5329
  # 1.3.3 Add the current location
5314
5330
  if self.add_location_to_instructions:
agno/tools/confluence.py CHANGED
@@ -2,6 +2,8 @@ import json
2
2
  from os import getenv
3
3
  from typing import Any, List, Optional
4
4
 
5
+ import requests
6
+
5
7
  from agno.tools import Toolkit
6
8
  from agno.utils.log import log_info, logger
7
9
 
@@ -55,14 +57,22 @@ class ConfluenceTools(Toolkit):
55
57
  if not self.password:
56
58
  raise ValueError("Confluence API KEY or password not provided")
57
59
 
58
- self.confluence = Confluence(
59
- url=self.url, username=self.username, password=self.password, verify_ssl=verify_ssl
60
- )
60
+ session = requests.Session()
61
+ session.verify = verify_ssl
62
+
61
63
  if not verify_ssl:
62
64
  import urllib3
63
65
 
64
66
  urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
65
67
 
68
+ self.confluence = Confluence(
69
+ url=self.url,
70
+ username=self.username,
71
+ password=self.password,
72
+ verify_ssl=verify_ssl,
73
+ session=session,
74
+ )
75
+
66
76
  tools: List[Any] = []
67
77
  tools.append(self.get_page_content)
68
78
  tools.append(self.get_space_key)
@@ -87,6 +97,9 @@ class ConfluenceTools(Toolkit):
87
97
  try:
88
98
  log_info(f"Retrieving page content from space '{space_name}'")
89
99
  key = self.get_space_key(space_name=space_name)
100
+ if key == "No space found":
101
+ return json.dumps({"error": f"Space '{space_name}' not found"})
102
+
90
103
  page = self.confluence.get_page_by_title(key, page_title, expand=expand)
91
104
  if page:
92
105
  log_info(f"Successfully retrieved page '{page_title}' from space '{space_name}'")
@@ -106,7 +119,20 @@ class ConfluenceTools(Toolkit):
106
119
  str: List of space details as a string.
107
120
  """
108
121
  log_info("Retrieving details for all Confluence spaces")
109
- results = self.confluence.get_all_spaces()["results"]
122
+ results = []
123
+ start = 0
124
+ limit = 50
125
+
126
+ while True:
127
+ spaces_data = self.confluence.get_all_spaces(start=start, limit=limit)
128
+ if not spaces_data.get("results"):
129
+ break
130
+ results.extend(spaces_data["results"])
131
+
132
+ if len(spaces_data["results"]) < limit:
133
+ break
134
+ start += limit
135
+
110
136
  return str(results)
111
137
 
112
138
  def get_space_key(self, space_name: str):
@@ -118,13 +144,29 @@ class ConfluenceTools(Toolkit):
118
144
  Returns:
119
145
  str: Space key or "No space found" if space doesn't exist.
120
146
  """
121
- result = self.confluence.get_all_spaces()
122
- spaces = result["results"]
147
+ start = 0
148
+ limit = 50
149
+
150
+ while True:
151
+ result = self.confluence.get_all_spaces(start=start, limit=limit)
152
+ if not result.get("results"):
153
+ break
154
+
155
+ spaces = result["results"]
156
+
157
+ for space in spaces:
158
+ if space["name"].lower() == space_name.lower():
159
+ log_info(f"Found space key for '{space_name}': {space['key']}")
160
+ return space["key"]
161
+
162
+ for space in spaces:
163
+ if space["key"] == space_name:
164
+ log_info(f"'{space_name}' is already a space key")
165
+ return space_name
123
166
 
124
- for space in spaces:
125
- if space["name"] == space_name:
126
- log_info(f"Found space key for '{space_name}'")
127
- return space["key"]
167
+ if len(spaces) < limit:
168
+ break
169
+ start += limit
128
170
 
129
171
  logger.warning(f"No space named {space_name} found")
130
172
  return "No space found"
@@ -140,9 +182,17 @@ class ConfluenceTools(Toolkit):
140
182
  """
141
183
  log_info(f"Retrieving all pages from space '{space_name}'")
142
184
  space_key = self.get_space_key(space_name)
185
+
186
+ if space_key == "No space found":
187
+ return json.dumps({"error": f"Space '{space_name}' not found"})
188
+
143
189
  page_details = self.confluence.get_all_pages_from_space(
144
190
  space_key, status=None, expand=None, content_type="page"
145
191
  )
192
+
193
+ if not page_details:
194
+ return json.dumps({"error": f"No pages found in space '{space_name}'"})
195
+
146
196
  page_details = str([{"id": page["id"], "title": page["title"]} for page in page_details])
147
197
  return page_details
148
198
 
@@ -160,6 +210,9 @@ class ConfluenceTools(Toolkit):
160
210
  """
161
211
  try:
162
212
  space_key = self.get_space_key(space_name=space_name)
213
+ if space_key == "No space found":
214
+ return json.dumps({"error": f"Space '{space_name}' not found"})
215
+
163
216
  page = self.confluence.create_page(space_key, title, body, parent_id=parent_id)
164
217
  log_info(f"Page created: {title} with ID {page['id']}")
165
218
  return json.dumps({"id": page["id"], "title": title})
agno/tools/e2b.py CHANGED
@@ -58,7 +58,7 @@ class E2BTools(Toolkit):
58
58
 
59
59
  # According to official docs, the parameter is 'timeout' (in seconds), not 'timeout_ms'
60
60
  try:
61
- self.sandbox = Sandbox(api_key=self.api_key, timeout=timeout, **self.sandbox_options)
61
+ self.sandbox = Sandbox.create(api_key=self.api_key, timeout=timeout, **self.sandbox_options)
62
62
  except Exception as e:
63
63
  logger.error(f"Warning: Could not create sandbox: {e}")
64
64
  raise e
agno/tools/gmail.py CHANGED
@@ -133,7 +133,7 @@ class GmailTools(Toolkit):
133
133
  send_email (bool): Enable sending emails. Defaults to True.
134
134
  search_emails (bool): Enable searching emails. Defaults to True.
135
135
  send_email_reply (bool): Enable sending email replies. Defaults to True.
136
- creds (Optional[Credentials]): Pre-existing credentials. Defaults to None.
136
+ creds (Optional[Credentials]): Pre-fetched OAuth credentials. Use this to skip a new auth flow. Defaults to None.
137
137
  credentials_path (Optional[str]): Path to credentials file. Defaults to None.
138
138
  token_path (Optional[str]): Path to token file. Defaults to None.
139
139
  scopes (Optional[List[str]]): Custom OAuth scopes. If None, uses DEFAULT_SCOPES.
agno/tools/neo4j.py ADDED
@@ -0,0 +1,132 @@
1
+ import os
2
+ from typing import Any, List, Optional
3
+
4
+ try:
5
+ from neo4j import GraphDatabase
6
+ except ImportError:
7
+ raise ImportError("`neo4j` not installed. Please install using `pip install neo4j`")
8
+
9
+ from agno.tools import Toolkit
10
+ from agno.utils.log import log_debug, logger
11
+
12
+
13
+ class Neo4jTools(Toolkit):
14
+ def __init__(
15
+ self,
16
+ uri: Optional[str] = None,
17
+ user: Optional[str] = None,
18
+ password: Optional[str] = None,
19
+ database: Optional[str] = None,
20
+ list_labels: bool = True,
21
+ list_relationships: bool = True,
22
+ get_schema: bool = True,
23
+ run_cypher: bool = True,
24
+ **kwargs,
25
+ ):
26
+ """
27
+ Initialize the Neo4jTools toolkit.
28
+ Connection parameters (uri/user/password or host/port) can be provided.
29
+ If not provided, falls back to NEO4J_URI, NEO4J_USERNAME, NEO4J_PASSWORD env vars.
30
+
31
+ Args:
32
+ uri (Optional[str]): The Neo4j URI.
33
+ user (Optional[str]): The Neo4j username.
34
+ password (Optional[str]): The Neo4j password.
35
+ host (Optional[str]): The Neo4j host.
36
+ port (Optional[int]): The Neo4j port.
37
+ database (Optional[str]): The Neo4j database.
38
+ list_labels (bool): Whether to list node labels.
39
+ list_relationships (bool): Whether to list relationship types.
40
+ get_schema (bool): Whether to get the schema.
41
+ run_cypher (bool): Whether to run Cypher queries.
42
+ **kwargs: Additional keyword arguments.
43
+ """
44
+ # Determine the connection URI and credentials
45
+ uri = uri or os.getenv("NEO4J_URI", "bolt://localhost:7687")
46
+ user = user or os.getenv("NEO4J_USERNAME")
47
+ password = password or os.getenv("NEO4J_PASSWORD")
48
+
49
+ if user is None or password is None:
50
+ raise ValueError("Username or password for Neo4j not provided")
51
+
52
+ # Create the Neo4j driver
53
+ try:
54
+ self.driver = GraphDatabase.driver(uri, auth=(user, password)) # type: ignore
55
+ self.driver.verify_connectivity()
56
+ log_debug("Connected to Neo4j database")
57
+ except Exception as e:
58
+ logger.error(f"Failed to connect to Neo4j: {e}")
59
+ raise
60
+
61
+ self.database = database or "neo4j"
62
+
63
+ # Register toolkit methods as tools
64
+ tools: List[Any] = []
65
+ if list_labels:
66
+ tools.append(self.list_labels)
67
+ if list_relationships:
68
+ tools.append(self.list_relationship_types)
69
+ if get_schema:
70
+ tools.append(self.get_schema)
71
+ if run_cypher:
72
+ tools.append(self.run_cypher_query)
73
+ super().__init__(name="neo4j_tools", tools=tools, **kwargs)
74
+
75
+ def list_labels(self) -> list:
76
+ """
77
+ Retrieve all node labels present in the connected Neo4j database.
78
+ """
79
+ try:
80
+ log_debug("Listing node labels in Neo4j database")
81
+ with self.driver.session(database=self.database) as session:
82
+ result = session.run("CALL db.labels()")
83
+ labels = [record["label"] for record in result]
84
+ return labels
85
+ except Exception as e:
86
+ logger.error(f"Error listing labels: {e}")
87
+ return []
88
+
89
+ def list_relationship_types(self) -> list:
90
+ """
91
+ Retrieve all relationship types present in the connected Neo4j database.
92
+ """
93
+ try:
94
+ log_debug("Listing relationship types in Neo4j database")
95
+ with self.driver.session(database=self.database) as session:
96
+ result = session.run("CALL db.relationshipTypes()")
97
+ types = [record["relationshipType"] for record in result]
98
+ return types
99
+ except Exception as e:
100
+ logger.error(f"Error listing relationship types: {e}")
101
+ return []
102
+
103
+ def get_schema(self) -> list:
104
+ """
105
+ Retrieve a visualization of the database schema, including nodes and relationships.
106
+ """
107
+ try:
108
+ log_debug("Retrieving Neo4j schema visualization")
109
+ with self.driver.session(database=self.database) as session:
110
+ result = session.run("CALL db.schema.visualization()")
111
+ schema_data = result.data()
112
+ return schema_data
113
+ except Exception as e:
114
+ logger.error(f"Error getting Neo4j schema: {e}")
115
+ return []
116
+
117
+ def run_cypher_query(self, query: str) -> list:
118
+ """
119
+ Execute an arbitrary Cypher query against the connected Neo4j database.
120
+
121
+ Args:
122
+ query (str): The Cypher query string to execute.
123
+ """
124
+ try:
125
+ log_debug(f"Running Cypher query: {query}")
126
+ with self.driver.session(database=self.database) as session:
127
+ result = session.run(query) # type: ignore[arg-type]
128
+ data = result.data()
129
+ return data
130
+ except Exception as e:
131
+ logger.error(f"Error running Cypher query: {e}")
132
+ return []
agno/utils/location.py CHANGED
@@ -10,10 +10,10 @@ def get_location() -> Dict[str, Any]:
10
10
  try:
11
11
  response = requests.get("https://api.ipify.org?format=json", timeout=5)
12
12
  ip = response.json()["ip"]
13
- response = requests.get(f"https://ipapi.co/{ip}/json/", timeout=5)
13
+ response = requests.get(f"http://ip-api.com/json/{ip}", timeout=5)
14
14
  if response.status_code == 200:
15
15
  data = response.json()
16
- return {"city": data.get("city"), "region": data.get("region"), "country": data.get("country_name")}
16
+ return {"city": data.get("city"), "region": data.get("region"), "country": data.get("country")}
17
17
  except Exception as e:
18
18
  log_warning(f"Failed to get location: {e}")
19
19
  return {}
@@ -704,3 +704,25 @@ class Qdrant(VectorDb):
704
704
 
705
705
  def delete(self) -> bool:
706
706
  return self.client.delete_collection(collection_name=self.collection)
707
+
708
+ def close(self) -> None:
709
+ """Close the Qdrant client connections."""
710
+ if self._client is not None:
711
+ try:
712
+ self._client.close()
713
+ log_debug("Qdrant client closed successfully")
714
+ except Exception as e:
715
+ log_debug(f"Error closing Qdrant client: {e}")
716
+ finally:
717
+ self._client = None
718
+
719
+ async def async_close(self) -> None:
720
+ """Close the Qdrant client connections asynchronously."""
721
+ if self._async_client is not None:
722
+ try:
723
+ await self._async_client.close()
724
+ log_debug("Async Qdrant client closed successfully")
725
+ except Exception as e:
726
+ log_debug(f"Error closing async Qdrant client: {e}")
727
+ finally:
728
+ self._async_client = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: agno
3
- Version: 1.8.0
3
+ Version: 1.8.1
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.
@@ -553,6 +553,8 @@ Provides-Extra: oxylabs
553
553
  Requires-Dist: oxylabs; extra == "oxylabs"
554
554
  Provides-Extra: trafilatura
555
555
  Requires-Dist: trafilatura; extra == "trafilatura"
556
+ Provides-Extra: neo4j
557
+ Requires-Dist: neo4j; extra == "neo4j"
556
558
  Provides-Extra: sql
557
559
  Requires-Dist: sqlalchemy; extra == "sql"
558
560
  Provides-Extra: postgres
@@ -670,6 +672,7 @@ Requires-Dist: agno[memori]; extra == "tools"
670
672
  Requires-Dist: agno[google_bigquery]; extra == "tools"
671
673
  Requires-Dist: agno[psycopg]; extra == "tools"
672
674
  Requires-Dist: agno[trafilatura]; extra == "tools"
675
+ Requires-Dist: agno[neo4j]; extra == "tools"
673
676
  Provides-Extra: storage
674
677
  Requires-Dist: agno[sql]; extra == "storage"
675
678
  Requires-Dist: agno[postgres]; extra == "storage"
@@ -2,10 +2,10 @@ agno/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  agno/constants.py,sha256=UkeazwDTE0WS7NB1pw9GxFJPhCgWLepAaMsdmLknupQ,527
3
3
  agno/debug.py,sha256=zzYxYwfF5AfHgQ6JU7oCmPK4yc97Y5xxOb5fiezq8nA,449
4
4
  agno/exceptions.py,sha256=HWuuNFS5J0l1RYJsdUrSx51M22aFEoh9ltoeonXBoBw,2891
5
- agno/media.py,sha256=H9uW9FAcf__kV93br8GLjXSTGLOBds0pPYs7hTUk7OQ,13746
5
+ agno/media.py,sha256=Vkd1rixJ8i5eeDLFxkOeDVFMljj0lidJr3gofYP1q3Y,13852
6
6
  agno/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
7
  agno/agent/__init__.py,sha256=Ai6GVyw-0rkA2eYAfoEQIvbi_mrWQUxuPFaFbSDJYCQ,1306
8
- agno/agent/agent.py,sha256=Ely8Zz-h0LQwiv8ifOHfFBJsGDetVDAJ_nSRoXK4xLA,382017
8
+ agno/agent/agent.py,sha256=DQEn-wNb9_kDoSKqI4nnwIktVFhfY6U-7U5Q0Pz0QS0,382035
9
9
  agno/agent/metrics.py,sha256=Lf7JYgPPdqRCyPfCDVUjnmUZ1SkWXrJClL80aW2ffEw,4379
10
10
  agno/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
11
  agno/api/agent.py,sha256=J-Y4HI-J0Bu6r9gxRYCM3U7SnVBGwLIouDy806KSIIw,2821
@@ -138,7 +138,7 @@ agno/infra/db_app.py,sha256=W_XeDK0FhIkc--kfB0Jd34MJmgOfFQeT9M2y5840vlA,1794
138
138
  agno/infra/resource.py,sha256=yPRZI4uprQtaugLD6x8_WAmFTPNPy5rlLvT1-bNIa5c,8491
139
139
  agno/infra/resources.py,sha256=FKmDaDFup4cDiSA_y8eZRxpXEAmlEPxWzXKcY8xqF5w,1681
140
140
  agno/knowledge/__init__.py,sha256=H1opQqY6oTPYJiLAiaIHtPuaVCry5GAhPoyT1ojdJwg,85
141
- agno/knowledge/agent.py,sha256=j3Nk6_n-ITX7DuAja3o-yRrbGCOyv69KyL1H24fL0MQ,29754
141
+ agno/knowledge/agent.py,sha256=vu64HY1gI3KA5AVgkSkelwrw5PxA40O7ltPVSjdpMzA,29914
142
142
  agno/knowledge/arxiv.py,sha256=0q1teC40wmau50ZTmdbvewCNxgxF_92QtfhJ87Evagk,1140
143
143
  agno/knowledge/combined.py,sha256=6aHPfbjdKSfkE0D-vt8CocU3WOVAQaPWhuNjKBVLrv4,1262
144
144
  agno/knowledge/csv.py,sha256=uUusY3Gf2HrYDtAOxoXsWfw11z95NEE9x71YNDqoK6g,5732
@@ -215,7 +215,7 @@ agno/models/cerebras/cerebras_openai.py,sha256=Ya4ixZUXRtofbH_jSp9HZX6YuW3ECY9tJ
215
215
  agno/models/cohere/__init__.py,sha256=4kFUnfPEL3__hd1TRW7fZxh7D_DctcpY5QDV58lR6s0,72
216
216
  agno/models/cohere/chat.py,sha256=5n7JELgoN0cszeWkGJeRVaV8TyplyIOTBx_4W-26ah0,15615
217
217
  agno/models/dashscope/__init__.py,sha256=lHZTGvs7fVeX4N1G7JNMa4mfsSQHgQq02DsSVqYFqpw,86
218
- agno/models/dashscope/dashscope.py,sha256=BmWlplRAO20fkgzfyEencwNiQtDt3zLB_Fbw2SKBlaI,3136
218
+ agno/models/dashscope/dashscope.py,sha256=ezhnM9lQ0Gg6u17aVNy82VrjUhgoefdtYhPwTvGrCfg,3370
219
219
  agno/models/deepinfra/__init__.py,sha256=24gMCeFHNbHw6l5gHZ1GwVg02546E9F_0yIZVSK15C8,86
220
220
  agno/models/deepinfra/deepinfra.py,sha256=JtUMJfDmkgRNGVB0kg367Rxsz1v6pfTTgipRRrI8-q8,878
221
221
  agno/models/deepseek/__init__.py,sha256=Q73VJ6rA0LqQbC0AWO6o5PWwr-Fdez7Imdar7X07LyU,82
@@ -223,7 +223,7 @@ agno/models/deepseek/deepseek.py,sha256=IsLAGroVdWgaw1FAab3ZYuqqAlIRFYTUrrheJI9a
223
223
  agno/models/fireworks/__init__.py,sha256=qIDjKUnwmrnwfa9B2Y3ybRyuUsF7Pzw6_bVq4N6M0Cg,86
224
224
  agno/models/fireworks/fireworks.py,sha256=Oh9gQeSBN223xUoc0WDKeHEzB8da1x9EnVvohXqB62U,905
225
225
  agno/models/google/__init__.py,sha256=bEOSroFJ4__38XaCgBUWiOe_Qga66ZRm_gis__yIMmc,74
226
- agno/models/google/gemini.py,sha256=CvYk1JAcb6Lr4GzVkUiUDEWhnBdErbNS1TwWa1_xpOo,43989
226
+ agno/models/google/gemini.py,sha256=ZLPvzFWoy-rrb9IMCaVwxAXMyBUrTH8cp0iiYPvop2c,44437
227
227
  agno/models/groq/__init__.py,sha256=gODf5IA4yJKlwTEYsUywmA-dsiQVyL2_yWMc8VncdVU,66
228
228
  agno/models/groq/groq.py,sha256=MR_zzSQiaqzQUhLsxgfbD0UzBBFf-R8H8vmiBMUH1zE,19880
229
229
  agno/models/huggingface/__init__.py,sha256=VgdYkgSHqsFLhvJ9lSUCyEZfest8hbCAUpWU6WCk-_c,94
@@ -252,9 +252,9 @@ agno/models/ollama/__init__.py,sha256=wZD1kXYL5PWz5h3CUj1kn1wLfECEKr9fEvJwbvg8A-
252
252
  agno/models/ollama/chat.py,sha256=g-U0XeR4S1s8LrVUHaXx_84sBb3ljwl67BeuPWPaGUM,13138
253
253
  agno/models/ollama/tools.py,sha256=PLYT9VSCGSwKAHNDEgOtyKg0HuUlYUxzGzvhoK19Vr0,19297
254
254
  agno/models/openai/__init__.py,sha256=OssVgQRpsriU6aJZ3lIp_jFuqvX6y78L4Fd3uTlmI3E,225
255
- agno/models/openai/chat.py,sha256=yLsLcaBYHZXNh43zrYmNYWCHbJpKPWK1OG3RwjBPrGw,30449
255
+ agno/models/openai/chat.py,sha256=VurDmXsFgV0hcB3pKQZ31f-HJNweM4aNsTT0lkiUKRA,31071
256
256
  agno/models/openai/like.py,sha256=wmw9PfAVqluBs4MMY73dgjelKn1yl5JDKyCRvaNFjFw,745
257
- agno/models/openai/responses.py,sha256=v8eyMGRCjU3XPrKC02PCOfXXJokEDXCYFOO-3DHt7xo,39652
257
+ agno/models/openai/responses.py,sha256=URWR087iY28s9hceKc687oj3VeXh7wzcqTFIb51Sggk,41734
258
258
  agno/models/openrouter/__init__.py,sha256=ZpZhNyy_EGSXp58uC9e2iyjnxBctql7GaY8rUG-599I,90
259
259
  agno/models/openrouter/openrouter.py,sha256=Ng-_ztpq_lghGI3tM94nsC8minKhiZ6d265c6IYXtg4,869
260
260
  agno/models/perplexity/__init__.py,sha256=JNmOElDLwcZ9_Lk5owkEdgwmAhaH3YJ-VJqOI8rgp5c,90
@@ -332,7 +332,7 @@ agno/storage/workflow/mongodb.py,sha256=x-0Jl2WovupTfwuVNOSndE9-7V4U7BBIjejtJ1Wa
332
332
  agno/storage/workflow/postgres.py,sha256=66bvx6eT7PtFvd4EtTCfI2smynAyvpjvAPYtPo-PCNg,91
333
333
  agno/storage/workflow/sqlite.py,sha256=PLqEA1YC8AtIklINr6wy8lzK6KABEqvlJW-nz5KacWM,85
334
334
  agno/team/__init__.py,sha256=OSkwJhm4uSoOwpHLeDdcH4q2R_BmfS-7a9_aPxB-Skw,967
335
- agno/team/team.py,sha256=vnIwYSKZnCBJYM03rvb-OjmbjjvKXMe44LBd2okPj3s,383585
335
+ agno/team/team.py,sha256=8NQ8s94nE6ay9IoLcaSM9f4ssISsA4M_HMzSO-seS6w,384194
336
336
  agno/tools/__init__.py,sha256=jNll2sELhPPbqm5nPeT4_uyzRO2_KRTW-8Or60kioS0,210
337
337
  agno/tools/agentql.py,sha256=w6FlCfhuS0cc2BHa9K6dZjqO1ycA66fSZbR_nvXiVSo,3813
338
338
  agno/tools/airflow.py,sha256=2ZCwx65w_tSXm4xEzZQR_teOiXJlnEgIqU9AgQTQemI,2493
@@ -351,7 +351,7 @@ agno/tools/calcom.py,sha256=HnPemDdChx3L7G5Br-TY4jqed_ZcxOTLfB-Ysg5g2LQ,9549
351
351
  agno/tools/calculator.py,sha256=JNIQj0EY2LVYRg6MkVX43cc3yJfF1DDKxAN_wSUiH14,5717
352
352
  agno/tools/cartesia.py,sha256=obAgm8BUK_VRb7sLhT2pU2tHkTb0cWlw7tuAojvjhec,7121
353
353
  agno/tools/clickup_tool.py,sha256=MievuuoRGKcnLgwH_g0Btatg8Xy_vdU9P-YfPGu5f6I,8781
354
- agno/tools/confluence.py,sha256=RUlc9QwHx6W52f0-6x7M_VPs2cEHWL1ATkPIZar8VJk,7435
354
+ agno/tools/confluence.py,sha256=7grcAZ4SySkRnAEnHitkbQ_YDy1cIUuqvaIVh2ilhY4,8878
355
355
  agno/tools/crawl4ai.py,sha256=p0e4i4d02Vgr1TclelLu1dhTbsz3MyryBDkO1m_YFWI,6365
356
356
  agno/tools/csv_toolkit.py,sha256=tpGRmTb0JtEz0uvtD9oHIJsf-IkqmI3IkgTlOoah2X8,7448
357
357
  agno/tools/dalle.py,sha256=ECoc7S22WrnOoR_LbMcT9IJlk-Uk_1WaUyjxoOAuioU,3910
@@ -362,7 +362,7 @@ agno/tools/discord.py,sha256=4g43nZ2fzLkNIj7uQYHdN4hvqeZPh_kMlsYJCYFG328,5661
362
362
  agno/tools/docker.py,sha256=vLbllXQZTyxZyusLNali_V5dwuRierLRBkoC-9QP38o,26408
363
363
  agno/tools/duckdb.py,sha256=XHgZRQ0gkwKmik5eOpQXon53Elz39zKd_WYEn6VjvI8,15384
364
364
  agno/tools/duckduckgo.py,sha256=b7AWf55lvw-EqZH7_8_p_oxpXJuocw3XoUeGSbf-Xn0,3083
365
- agno/tools/e2b.py,sha256=I6rfW7pSoyORrD9DPYTXayxeM8RsNkWgibWU4wrXRTI,26308
365
+ agno/tools/e2b.py,sha256=cE6baszcnTDr2j3WdmvcELWg1v5wme1EhtWTw-8gVT8,26315
366
366
  agno/tools/eleven_labs.py,sha256=ThYLqJvYXdOx9tKgGwm7XxU2QBWlRhtMLQJtMFNafj4,6711
367
367
  agno/tools/email.py,sha256=Y-ezAZzau3Mt8icOoSW_om9b21M215DOMPNbWvx7ZGc,2251
368
368
  agno/tools/evm.py,sha256=w97nGMIuP_YmkogHpqQhuusUdJA08iUeIWMLkG-_wjw,5205
@@ -374,7 +374,7 @@ agno/tools/firecrawl.py,sha256=0jeJlupLXCJTf2tDckEXlCV6EpLeDEO_G27-w4EWDL0,4985
374
374
  agno/tools/function.py,sha256=VWgQAjIqxJl7MJrrMmMr7tNNvz3L3m7jXaUoIqxdlKY,36119
375
375
  agno/tools/giphy.py,sha256=HKzTHEmiUdrkJsguG0qkpnsmXPikbrQyCoAHvAzI6XU,2422
376
376
  agno/tools/github.py,sha256=mrXaJssPM5xrl25ZOgsb7B31QBgQomd3R97adcCLqMs,73558
377
- agno/tools/gmail.py,sha256=p7zKW2NLuRIYsZZ6ru4WMdwMPQpoBt4JbuCwfMI3YTs,28845
377
+ agno/tools/gmail.py,sha256=GHb78BWgdezHNPBTm3IwsRSMA8w9bVDi0_KtNDszdp8,28884
378
378
  agno/tools/google_bigquery.py,sha256=i93QJLJCH39sZDMPWYmie_A_ElBxVC6D36TVDcSaplo,4423
379
379
  agno/tools/google_maps.py,sha256=iZa6FgjNID_G6T96ZXUOe5JnXIWn0c3AgzFU7O6S2WE,10123
380
380
  agno/tools/googlecalendar.py,sha256=s8BPKvDZGeLD2idlQG-vqDqVpza6PupF5AzAElnAK_M,27457
@@ -394,6 +394,7 @@ agno/tools/memori.py,sha256=3_Ullv4Z21PiEgVTOXh5jmVScvOVV6odbRV83LhG7fs,14390
394
394
  agno/tools/mlx_transcribe.py,sha256=_6GgaWcyb30f82jG_P-PAtxzZBjsyG4GeRP_uH5h8RU,6331
395
395
  agno/tools/models_labs.py,sha256=BGtXCDtLpGehmVNNfyMnBcLbnVh7P-4xbLRsgNzDWSM,6467
396
396
  agno/tools/moviepy_video.py,sha256=YA7EJbx5NDxNiS2bpR-B88USn80GHzM7vpjNDjF-_G4,12798
397
+ agno/tools/neo4j.py,sha256=M5vqeoenCaSjFzyRZPc0_ulXpLjr8DgkKbtKcqpsGtQ,5029
397
398
  agno/tools/newspaper.py,sha256=rgTZ_J6lv7478La_sfpGaS08MZ5g5BZcq3Dm8jKkyDs,1202
398
399
  agno/tools/newspaper4k.py,sha256=6Xv4yKRjJNsbYi9Yn2HR8CiqDhhLLv5DTFeftlMfgRo,2960
399
400
  agno/tools/openai.py,sha256=xgm4EVP9YzW6SlFXY3vNITNmrxHjmeSsWBLwMESRwkM,6920
@@ -469,7 +470,7 @@ agno/utils/http.py,sha256=t7sr34VD9M___MYBlX6p4XKEqkvuXRJNJG7Y1ebU2bk,2673
469
470
  agno/utils/json_io.py,sha256=D_sNT1fJVN39LajJEmVa9NzTtWhHsVD4T1OYQ5r-Y9A,982
470
471
  agno/utils/json_schema.py,sha256=VPbK-Gdo0qOQKco7MZnv1d2Fe-9mt1PRBO1SNFIvBHY,8555
471
472
  agno/utils/load_env.py,sha256=La-Am4O2VqmctIA9pVglaEiLOOpjj5jxPALy5x3ra-c,665
472
- agno/utils/location.py,sha256=eDhYAoal_-jkzqAOWGKioY16JFoxXrNmI5wTP-BxxXw,663
473
+ agno/utils/location.py,sha256=VhXIwSWdr7L4DEJoUeVI92Y-rJ32NUcYzFRnzLgX-es,658
473
474
  agno/utils/log.py,sha256=D1niLzfy7E8874_NYZ65QLkxd66HkJi_P7Xeo0dXLic,5989
474
475
  agno/utils/mcp.py,sha256=f-wC1_osXgFG8y_1xuD0foAm2r91s_jSGWAJPUdHNzc,2637
475
476
  agno/utils/media.py,sha256=uk-JUyUZiE9vx0Dm1u7i326lm4lUcDarQbKqnvwHbwQ,4568
@@ -529,7 +530,7 @@ agno/vectordb/pgvector/pgvector.py,sha256=tm1khucKVr38IeyvKpe5nL6PopaLAoIAnThUWA
529
530
  agno/vectordb/pineconedb/__init__.py,sha256=D7iThXtUCxNO0Nyjunv5Z91Jc1vHG1pgAFXthqD1I_w,92
530
531
  agno/vectordb/pineconedb/pineconedb.py,sha256=NaYmkskY1eLAWIM-HSBa-DVHEMQ6ZuZDqqUmmHZhlro,18399
531
532
  agno/vectordb/qdrant/__init__.py,sha256=x1ReQt79f9aI_T4JUWb36KNFnvdd-kVwZ1sLsU4sW7Q,76
532
- agno/vectordb/qdrant/qdrant.py,sha256=JnsCgCFnQJ8A9SdamIhBC2BndqlHZhR9f5bO9y4n3fI,28645
533
+ agno/vectordb/qdrant/qdrant.py,sha256=pzUfTlSm2cZvHIq2Se9EE928vdgRaw1Jsl10mEou9pM,29488
533
534
  agno/vectordb/singlestore/__init__.py,sha256=Cuaq_pvpX5jsUv3tWlOFnlrF4VGykGIIK5hfhnW6J2k,249
534
535
  agno/vectordb/singlestore/index.py,sha256=p9LYQlVINlZZvZORfiDE3AIFinx07idDHr9_mM3EXAg,1527
535
536
  agno/vectordb/singlestore/singlestore.py,sha256=ZWEEuyxymFbeV2ssr7UyoDo35O98E1hWCa94BKLidr8,16457
@@ -557,9 +558,9 @@ agno/workspace/enums.py,sha256=MxF1CUMXBaZMTKLEfiR-7kEhTki2Gfz6W7u49RdYYaE,123
557
558
  agno/workspace/helpers.py,sha256=Mp-VlRsPVhW10CfDWYVhc9ANLk9RjNurDfCgXmycZCg,2066
558
559
  agno/workspace/operator.py,sha256=CNLwVR45eE5dSRjto2o0c9NgCi2xD-JZR5uLt9kfIt8,30758
559
560
  agno/workspace/settings.py,sha256=bcyHHN7lH1LPSMt4i_20XpTjZLoNXdzwyW-G9nHYV40,5703
560
- agno-1.8.0.dist-info/licenses/LICENSE,sha256=m2rfTWFUfIwCaQqgT2WeBjuKzMKEJRwnaiofg9n8MsQ,16751
561
- agno-1.8.0.dist-info/METADATA,sha256=aemwm5N2rfpJRZIaM3cpdhCjIbrty4iER3UiM4JBdVo,44506
562
- agno-1.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
563
- agno-1.8.0.dist-info/entry_points.txt,sha256=Be-iPnPVabMohESsuUdV5w6IAYEIlpc2emJZbyNnfGI,88
564
- agno-1.8.0.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
565
- agno-1.8.0.dist-info/RECORD,,
561
+ agno-1.8.1.dist-info/licenses/LICENSE,sha256=m2rfTWFUfIwCaQqgT2WeBjuKzMKEJRwnaiofg9n8MsQ,16751
562
+ agno-1.8.1.dist-info/METADATA,sha256=_-DhUAh5k3DGfTzB4D5_er70GAjy2VxqR4Gx_Rsusgo,44612
563
+ agno-1.8.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
564
+ agno-1.8.1.dist-info/entry_points.txt,sha256=Be-iPnPVabMohESsuUdV5w6IAYEIlpc2emJZbyNnfGI,88
565
+ agno-1.8.1.dist-info/top_level.txt,sha256=MKyeuVesTyOKIXUhc-d_tPa2Hrh0oTA4LM0izowpx70,5
566
+ agno-1.8.1.dist-info/RECORD,,
File without changes