agno 1.7.12__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 +3 -3
- agno/app/agui/utils.py +1 -1
- agno/app/fastapi/async_router.py +13 -10
- agno/knowledge/agent.py +8 -4
- agno/knowledge/gcs/pdf.py +2 -2
- agno/media.py +26 -5
- agno/models/dashscope/dashscope.py +14 -5
- agno/models/google/gemini.py +39 -12
- 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/duckduckgo.py +8 -16
- agno/tools/e2b.py +1 -1
- agno/tools/github.py +26 -14
- agno/tools/gmail.py +1 -1
- agno/tools/memori.py +387 -0
- agno/tools/neo4j.py +132 -0
- agno/tools/scrapegraph.py +65 -0
- agno/utils/location.py +2 -2
- agno/vectordb/pgvector/pgvector.py +23 -39
- agno/vectordb/qdrant/qdrant.py +22 -0
- agno/workflow/v2/step.py +4 -0
- agno/workflow/v2/types.py +11 -1
- agno/workflow/v2/workflow.py +54 -1
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/METADATA +10 -4
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/RECORD +31 -29
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/WHEEL +0 -0
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/entry_points.txt +0 -0
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/licenses/LICENSE +0 -0
- {agno-1.7.12.dist-info → agno-1.8.1.dist-info}/top_level.txt +0 -0
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
|
|
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/app/agui/utils.py
CHANGED
|
@@ -129,7 +129,7 @@ def _create_events_from_chunk(
|
|
|
129
129
|
Process a single chunk and return events to emit + updated message_started state.
|
|
130
130
|
Returns: (events_to_emit, new_message_started_state)
|
|
131
131
|
"""
|
|
132
|
-
events_to_emit = []
|
|
132
|
+
events_to_emit: List[BaseEvent] = []
|
|
133
133
|
|
|
134
134
|
# Extract content if the contextual event is a content event
|
|
135
135
|
if chunk.event == RunEvent.run_response_content:
|
agno/app/fastapi/async_router.py
CHANGED
|
@@ -13,7 +13,7 @@ from agno.media import Audio, Image, Video
|
|
|
13
13
|
from agno.media import File as FileMedia
|
|
14
14
|
from agno.run.response import RunResponseErrorEvent
|
|
15
15
|
from agno.run.team import RunResponseErrorEvent as TeamRunResponseErrorEvent
|
|
16
|
-
from agno.run.team import TeamRunResponseEvent
|
|
16
|
+
from agno.run.team import TeamRunResponse, TeamRunResponseEvent
|
|
17
17
|
from agno.run.v2.workflow import WorkflowErrorEvent
|
|
18
18
|
from agno.team.team import Team
|
|
19
19
|
from agno.utils.log import logger
|
|
@@ -425,15 +425,18 @@ def get_async_router(
|
|
|
425
425
|
)
|
|
426
426
|
return run_response.to_dict()
|
|
427
427
|
elif team:
|
|
428
|
-
team_run_response =
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
428
|
+
team_run_response = cast(
|
|
429
|
+
TeamRunResponse,
|
|
430
|
+
await team.arun(
|
|
431
|
+
message=message,
|
|
432
|
+
session_id=session_id,
|
|
433
|
+
user_id=user_id,
|
|
434
|
+
images=base64_images if base64_images else None,
|
|
435
|
+
audio=base64_audios if base64_audios else None,
|
|
436
|
+
videos=base64_videos if base64_videos else None,
|
|
437
|
+
files=document_files if document_files else None,
|
|
438
|
+
stream=False,
|
|
439
|
+
),
|
|
437
440
|
)
|
|
438
441
|
return team_run_response.to_dict()
|
|
439
442
|
elif workflow:
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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/knowledge/gcs/pdf.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import AsyncIterator, Iterator, List, Optional
|
|
1
|
+
from typing import Any, AsyncIterator, Dict, Iterator, List, Optional
|
|
2
2
|
|
|
3
3
|
from agno.document import Document
|
|
4
4
|
from agno.document.reader.gcs.pdf_reader import GCSPDFReader
|
|
@@ -93,7 +93,7 @@ class GCSPDFKnowledgeBase(GCSKnowledgeBase):
|
|
|
93
93
|
document_iterator = self.async_document_lists
|
|
94
94
|
async for document_list in document_iterator: # type: ignore
|
|
95
95
|
documents_to_load = document_list
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
# Track metadata for filtering capabilities and collect metadata for filters
|
|
98
98
|
filters_metadata: Optional[Dict[str, Any]] = None
|
|
99
99
|
for doc in document_list:
|
agno/media.py
CHANGED
|
@@ -38,13 +38,34 @@ class ImageArtifact(Media):
|
|
|
38
38
|
mime_type: Optional[str] = None
|
|
39
39
|
alt_text: Optional[str] = None
|
|
40
40
|
|
|
41
|
+
def _normalise_content(self) -> Optional[Union[str, bytes]]:
|
|
42
|
+
if self.content is None:
|
|
43
|
+
return None
|
|
44
|
+
content_normalised: Union[str, bytes] = self.content
|
|
45
|
+
if content_normalised and isinstance(content_normalised, bytes):
|
|
46
|
+
from base64 import b64encode
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# First try to decode as UTF-8
|
|
50
|
+
content_normalised = content_normalised.decode("utf-8") # type: ignore
|
|
51
|
+
except UnicodeDecodeError:
|
|
52
|
+
# Fallback to base64 encoding for binary content
|
|
53
|
+
content_normalised = b64encode(bytes(content_normalised)).decode("utf-8") # type: ignore
|
|
54
|
+
except Exception:
|
|
55
|
+
# Last resort: try to convert to base64
|
|
56
|
+
try:
|
|
57
|
+
content_normalised = b64encode(bytes(content_normalised)).decode("utf-8") # type: ignore
|
|
58
|
+
except Exception:
|
|
59
|
+
pass
|
|
60
|
+
return content_normalised
|
|
61
|
+
|
|
41
62
|
def to_dict(self) -> Dict[str, Any]:
|
|
63
|
+
content_normalised = self._normalise_content()
|
|
64
|
+
|
|
42
65
|
response_dict = {
|
|
43
66
|
"id": self.id,
|
|
44
67
|
"url": self.url,
|
|
45
|
-
"content":
|
|
46
|
-
if self.content and isinstance(self.content, bytes)
|
|
47
|
-
else self.content,
|
|
68
|
+
"content": content_normalised,
|
|
48
69
|
"mime_type": self.mime_type,
|
|
49
70
|
"alt_text": self.alt_text,
|
|
50
71
|
}
|
|
@@ -136,7 +157,7 @@ class Video(BaseModel):
|
|
|
136
157
|
|
|
137
158
|
@classmethod
|
|
138
159
|
def from_artifact(cls, artifact: VideoArtifact) -> "Video":
|
|
139
|
-
return cls(url=artifact.url)
|
|
160
|
+
return cls(url=artifact.url, content=artifact.content, format=artifact.mime_type)
|
|
140
161
|
|
|
141
162
|
|
|
142
163
|
class Audio(BaseModel):
|
|
@@ -308,7 +329,7 @@ class Image(BaseModel):
|
|
|
308
329
|
|
|
309
330
|
@classmethod
|
|
310
331
|
def from_artifact(cls, artifact: ImageArtifact) -> "Image":
|
|
311
|
-
return cls(url=artifact.url)
|
|
332
|
+
return cls(url=artifact.url, content=artifact.content, format=artifact.mime_type)
|
|
312
333
|
|
|
313
334
|
|
|
314
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 (
|
|
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:
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
agno/models/google/gemini.py
CHANGED
|
@@ -30,9 +30,11 @@ try:
|
|
|
30
30
|
GoogleSearch,
|
|
31
31
|
GoogleSearchRetrieval,
|
|
32
32
|
Part,
|
|
33
|
+
Retrieval,
|
|
33
34
|
ThinkingConfig,
|
|
34
35
|
Tool,
|
|
35
36
|
UrlContext,
|
|
37
|
+
VertexAISearch,
|
|
36
38
|
)
|
|
37
39
|
from google.genai.types import (
|
|
38
40
|
File as GeminiFile,
|
|
@@ -70,6 +72,8 @@ class Gemini(Model):
|
|
|
70
72
|
grounding: bool = False
|
|
71
73
|
grounding_dynamic_threshold: Optional[float] = None
|
|
72
74
|
url_context: bool = False
|
|
75
|
+
vertexai_search: bool = False
|
|
76
|
+
vertexai_search_datastore: Optional[str] = None
|
|
73
77
|
|
|
74
78
|
temperature: Optional[float] = None
|
|
75
79
|
top_p: Optional[float] = None
|
|
@@ -204,7 +208,9 @@ class Gemini(Model):
|
|
|
204
208
|
builtin_tools = []
|
|
205
209
|
|
|
206
210
|
if self.grounding:
|
|
207
|
-
log_info(
|
|
211
|
+
log_info(
|
|
212
|
+
"Grounding enabled. This is a legacy tool. For Gemini 2.0+ Please use enable `search` flag instead."
|
|
213
|
+
)
|
|
208
214
|
builtin_tools.append(
|
|
209
215
|
Tool(
|
|
210
216
|
google_search=GoogleSearchRetrieval(
|
|
@@ -223,6 +229,15 @@ class Gemini(Model):
|
|
|
223
229
|
log_info("URL context enabled.")
|
|
224
230
|
builtin_tools.append(Tool(url_context=UrlContext()))
|
|
225
231
|
|
|
232
|
+
if self.vertexai_search:
|
|
233
|
+
log_info("Vertex AI Search enabled.")
|
|
234
|
+
if not self.vertexai_search_datastore:
|
|
235
|
+
log_error("vertexai_search_datastore must be provided when vertexai_search is enabled.")
|
|
236
|
+
raise ValueError("vertexai_search_datastore must be provided when vertexai_search is enabled.")
|
|
237
|
+
builtin_tools.append(
|
|
238
|
+
Tool(retrieval=Retrieval(vertex_ai_search=VertexAISearch(datastore=self.vertexai_search_datastore)))
|
|
239
|
+
)
|
|
240
|
+
|
|
226
241
|
# Set tools in config
|
|
227
242
|
if builtin_tools:
|
|
228
243
|
if tools:
|
|
@@ -778,12 +793,18 @@ class Gemini(Model):
|
|
|
778
793
|
grounding_metadata = response.candidates[0].grounding_metadata.model_dump()
|
|
779
794
|
citations_raw["grounding_metadata"] = grounding_metadata
|
|
780
795
|
|
|
781
|
-
chunks = grounding_metadata.get("grounding_chunks", [])
|
|
782
|
-
citation_pairs = [
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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))
|
|
787
808
|
|
|
788
809
|
# Create citation objects from filtered pairs
|
|
789
810
|
grounding_urls = [UrlCitation(url=url, title=title) for url, title in citation_pairs]
|
|
@@ -892,11 +913,17 @@ class Gemini(Model):
|
|
|
892
913
|
|
|
893
914
|
# Extract url and title
|
|
894
915
|
chunks = grounding_metadata.pop("grounding_chunks", None) or []
|
|
895
|
-
citation_pairs = [
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
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))
|
|
900
927
|
|
|
901
928
|
# Create citation objects from filtered pairs
|
|
902
929
|
citations.urls = [UrlCitation(url=url, title=title) for url, title in citation_pairs]
|
agno/models/openai/chat.py
CHANGED
|
@@ -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
|
|
127
|
-
|
|
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
|
-
|
|
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(
|
agno/models/openai/responses.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
769
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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})
|