agno 1.8.0__py3-none-any.whl → 1.8.2__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/__init__.py +8 -0
- agno/agent/agent.py +5 -5
- agno/agent/metrics.py +4 -1
- agno/app/agui/utils.py +16 -9
- agno/app/discord/client.py +5 -1
- agno/app/playground/async_router.py +3 -0
- agno/app/playground/schemas.py +3 -0
- agno/app/playground/sync_router.py +3 -0
- agno/knowledge/agent.py +8 -4
- agno/media.py +2 -2
- agno/models/anthropic/claude.py +3 -59
- agno/models/aws/bedrock.py +3 -7
- agno/models/dashscope/dashscope.py +14 -5
- agno/models/google/gemini.py +23 -11
- agno/models/openai/chat.py +14 -4
- agno/models/openai/responses.py +56 -11
- agno/team/team.py +17 -1
- agno/tools/confluence.py +63 -10
- agno/tools/e2b.py +1 -1
- agno/tools/firecrawl.py +5 -4
- agno/tools/gmail.py +1 -1
- agno/tools/linear.py +1 -1
- agno/tools/neo4j.py +132 -0
- agno/utils/gemini.py +31 -1
- agno/utils/location.py +2 -2
- agno/utils/models/claude.py +49 -0
- agno/vectordb/qdrant/qdrant.py +22 -0
- agno/workflow/v2/workflow.py +2 -1
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/METADATA +4 -1
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/RECORD +34 -33
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/WHEEL +0 -0
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/entry_points.txt +0 -0
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/licenses/LICENSE +0 -0
- {agno-1.8.0.dist-info → agno-1.8.2.dist-info}/top_level.txt +0 -0
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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 =
|
|
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
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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/firecrawl.py
CHANGED
|
@@ -6,7 +6,8 @@ from agno.tools import Toolkit
|
|
|
6
6
|
from agno.utils.log import logger
|
|
7
7
|
|
|
8
8
|
try:
|
|
9
|
-
from firecrawl import FirecrawlApp
|
|
9
|
+
from firecrawl import FirecrawlApp # type: ignore[attr-defined]
|
|
10
|
+
from firecrawl.types import ScrapeOptions
|
|
10
11
|
except ImportError:
|
|
11
12
|
raise ImportError("`firecrawl-py` not installed. Please install using `pip install firecrawl-py`")
|
|
12
13
|
|
|
@@ -87,7 +88,7 @@ class FirecrawlTools(Toolkit):
|
|
|
87
88
|
if self.formats:
|
|
88
89
|
params["formats"] = self.formats
|
|
89
90
|
|
|
90
|
-
scrape_result = self.app.
|
|
91
|
+
scrape_result = self.app.scrape(url, **params)
|
|
91
92
|
return json.dumps(scrape_result.model_dump(), cls=CustomJSONEncoder)
|
|
92
93
|
|
|
93
94
|
def crawl_website(self, url: str, limit: Optional[int] = None) -> str:
|
|
@@ -108,7 +109,7 @@ class FirecrawlTools(Toolkit):
|
|
|
108
109
|
|
|
109
110
|
params["poll_interval"] = self.poll_interval
|
|
110
111
|
|
|
111
|
-
crawl_result = self.app.
|
|
112
|
+
crawl_result = self.app.crawl(url, **params)
|
|
112
113
|
return json.dumps(crawl_result.model_dump(), cls=CustomJSONEncoder)
|
|
113
114
|
|
|
114
115
|
def map_website(self, url: str) -> str:
|
|
@@ -118,7 +119,7 @@ class FirecrawlTools(Toolkit):
|
|
|
118
119
|
url (str): The URL to map.
|
|
119
120
|
|
|
120
121
|
"""
|
|
121
|
-
map_result = self.app.
|
|
122
|
+
map_result = self.app.map(url)
|
|
122
123
|
return json.dumps(map_result.model_dump(), cls=CustomJSONEncoder)
|
|
123
124
|
|
|
124
125
|
def search(self, query: str, limit: Optional[int] = None):
|
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-
|
|
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/linear.py
CHANGED
|
@@ -217,7 +217,7 @@ class LinearTools(Toolkit):
|
|
|
217
217
|
"""
|
|
218
218
|
|
|
219
219
|
query = """
|
|
220
|
-
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String
|
|
220
|
+
mutation IssueCreate ($title: String!, $description: String!, $teamId: String!, $projectId: String, $assigneeId: String){
|
|
221
221
|
issueCreate(
|
|
222
222
|
input: { title: $title, description: $description, teamId: $teamId, projectId: $projectId, assigneeId: $assigneeId}
|
|
223
223
|
) {
|
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/gemini.py
CHANGED
|
@@ -175,7 +175,37 @@ def convert_schema(schema_dict: Dict[str, Any], root_schema: Optional[Dict[str,
|
|
|
175
175
|
|
|
176
176
|
elif schema_type == "array" and "items" in schema_dict:
|
|
177
177
|
items = convert_schema(schema_dict["items"], root_schema)
|
|
178
|
-
|
|
178
|
+
min_items = schema_dict.get("minItems")
|
|
179
|
+
max_items = schema_dict.get("maxItems")
|
|
180
|
+
return Schema(
|
|
181
|
+
type=Type.ARRAY,
|
|
182
|
+
description=description,
|
|
183
|
+
items=items,
|
|
184
|
+
min_items=min_items,
|
|
185
|
+
max_items=max_items,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
elif schema_type == "string":
|
|
189
|
+
schema_kwargs = {
|
|
190
|
+
"type": Type.STRING,
|
|
191
|
+
"description": description,
|
|
192
|
+
"default": default,
|
|
193
|
+
}
|
|
194
|
+
if "format" in schema_dict:
|
|
195
|
+
schema_kwargs["format"] = schema_dict["format"]
|
|
196
|
+
return Schema(**schema_kwargs)
|
|
197
|
+
|
|
198
|
+
elif schema_type in ("integer", "number"):
|
|
199
|
+
schema_kwargs = {
|
|
200
|
+
"type": schema_type.upper(),
|
|
201
|
+
"description": description,
|
|
202
|
+
"default": default,
|
|
203
|
+
}
|
|
204
|
+
if "maximum" in schema_dict:
|
|
205
|
+
schema_kwargs["maximum"] = schema_dict["maximum"]
|
|
206
|
+
if "minimum" in schema_dict:
|
|
207
|
+
schema_kwargs["minimum"] = schema_dict["minimum"]
|
|
208
|
+
return Schema(**schema_kwargs)
|
|
179
209
|
|
|
180
210
|
elif schema_type == "" and "anyOf" in schema_dict:
|
|
181
211
|
any_of = []
|
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"
|
|
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("
|
|
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 {}
|
agno/utils/models/claude.py
CHANGED
|
@@ -284,3 +284,52 @@ def format_messages(messages: List[Message]) -> Tuple[List[Dict[str, str]], str]
|
|
|
284
284
|
|
|
285
285
|
chat_messages.append({"role": ROLE_MAP[message.role], "content": content}) # type: ignore
|
|
286
286
|
return chat_messages, " ".join(system_messages)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def format_tools_for_model(tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
290
|
+
"""
|
|
291
|
+
Transforms function definitions into a format accepted by the Anthropic API.
|
|
292
|
+
"""
|
|
293
|
+
if not tools:
|
|
294
|
+
return None
|
|
295
|
+
|
|
296
|
+
parsed_tools: List[Dict[str, Any]] = []
|
|
297
|
+
for tool_def in tools:
|
|
298
|
+
if tool_def.get("type", "") != "function":
|
|
299
|
+
parsed_tools.append(tool_def)
|
|
300
|
+
continue
|
|
301
|
+
|
|
302
|
+
func_def = tool_def.get("function", {})
|
|
303
|
+
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
304
|
+
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
305
|
+
required: List[str] = parameters.get("required", [])
|
|
306
|
+
required_params: List[str] = required
|
|
307
|
+
|
|
308
|
+
if not required_params:
|
|
309
|
+
for param_name, param_info in properties.items():
|
|
310
|
+
param_type = param_info.get("type", "")
|
|
311
|
+
param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
|
|
312
|
+
|
|
313
|
+
if "null" not in param_type_list:
|
|
314
|
+
required_params.append(param_name)
|
|
315
|
+
|
|
316
|
+
input_properties: Dict[str, Any] = {}
|
|
317
|
+
for param_name, param_info in properties.items():
|
|
318
|
+
# Preserve the complete schema structure for complex types
|
|
319
|
+
input_properties[param_name] = param_info.copy()
|
|
320
|
+
|
|
321
|
+
# Ensure description is present (default to empty if missing)
|
|
322
|
+
if "description" not in input_properties[param_name]:
|
|
323
|
+
input_properties[param_name]["description"] = ""
|
|
324
|
+
|
|
325
|
+
tool = {
|
|
326
|
+
"name": func_def.get("name") or "",
|
|
327
|
+
"description": func_def.get("description") or "",
|
|
328
|
+
"input_schema": {
|
|
329
|
+
"type": parameters.get("type", "object"),
|
|
330
|
+
"properties": input_properties,
|
|
331
|
+
"required": required_params,
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
parsed_tools.append(tool)
|
|
335
|
+
return parsed_tools
|
agno/vectordb/qdrant/qdrant.py
CHANGED
|
@@ -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
|
agno/workflow/v2/workflow.py
CHANGED
|
@@ -3273,7 +3273,8 @@ class Workflow:
|
|
|
3273
3273
|
"""Update executor with workflow_session_state"""
|
|
3274
3274
|
if self.workflow_session_state is not None:
|
|
3275
3275
|
# Update session_state with workflow_session_state
|
|
3276
|
-
executor
|
|
3276
|
+
if hasattr(executor, "workflow_session_state"):
|
|
3277
|
+
executor.workflow_session_state = self.workflow_session_state
|
|
3277
3278
|
|
|
3278
3279
|
def _save_run_to_storage(self, workflow_run_response: WorkflowRunResponse) -> None:
|
|
3279
3280
|
"""Helper method to save workflow run response to storage"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agno
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.2
|
|
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"
|