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/__init__.py
CHANGED
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
|
|
@@ -1476,7 +1476,7 @@ class Agent:
|
|
|
1476
1476
|
|
|
1477
1477
|
# Read existing session from storage
|
|
1478
1478
|
if self.context is not None:
|
|
1479
|
-
self.
|
|
1479
|
+
await self.aresolve_run_context()
|
|
1480
1480
|
|
|
1481
1481
|
# Prepare arguments for the model
|
|
1482
1482
|
self.set_default_model()
|
|
@@ -2152,7 +2152,7 @@ class Agent:
|
|
|
2152
2152
|
|
|
2153
2153
|
# Read existing session from storage
|
|
2154
2154
|
if self.context is not None:
|
|
2155
|
-
self.
|
|
2155
|
+
await self.aresolve_run_context()
|
|
2156
2156
|
|
|
2157
2157
|
# Prepare arguments for the model
|
|
2158
2158
|
self.set_default_model()
|
agno/agent/metrics.py
CHANGED
|
@@ -69,7 +69,10 @@ class SessionMetrics:
|
|
|
69
69
|
# Add values from other
|
|
70
70
|
if other.prompt_tokens_details:
|
|
71
71
|
for key, value in other.prompt_tokens_details.items():
|
|
72
|
-
|
|
72
|
+
existing_value = result.prompt_tokens_details.get(key, 0)
|
|
73
|
+
if not isinstance(existing_value, int):
|
|
74
|
+
continue
|
|
75
|
+
result.prompt_tokens_details[key] = existing_value + value
|
|
73
76
|
|
|
74
77
|
# Handle completion_tokens_details similarly
|
|
75
78
|
if self.completion_tokens_details or other.completion_tokens_details:
|
agno/app/agui/utils.py
CHANGED
|
@@ -27,6 +27,7 @@ from agno.models.message import Message
|
|
|
27
27
|
from agno.run.response import RunEvent, RunResponseContentEvent, RunResponseEvent, RunResponsePausedEvent
|
|
28
28
|
from agno.run.team import RunResponseContentEvent as TeamRunResponseContentEvent
|
|
29
29
|
from agno.run.team import TeamRunEvent, TeamRunResponseEvent
|
|
30
|
+
from agno.utils.message import get_text_from_message
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
@dataclass
|
|
@@ -106,17 +107,23 @@ def extract_team_response_chunk_content(response: TeamRunResponseContentEvent) -
|
|
|
106
107
|
members_content.append(f"Team member: {member_content}")
|
|
107
108
|
members_response = "\n".join(members_content) if members_content else ""
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
# Handle structured outputs
|
|
111
|
+
main_content = get_text_from_message(response.content) if response.content is not None else ""
|
|
112
|
+
|
|
113
|
+
return main_content + members_response
|
|
110
114
|
|
|
111
115
|
|
|
112
116
|
def extract_response_chunk_content(response: RunResponseContentEvent) -> str:
|
|
113
117
|
"""Given a response stream chunk, find and extract the content."""
|
|
118
|
+
|
|
114
119
|
if hasattr(response, "messages") and response.messages: # type: ignore
|
|
115
120
|
for msg in reversed(response.messages): # type: ignore
|
|
116
121
|
if hasattr(msg, "role") and msg.role == "assistant" and hasattr(msg, "content") and msg.content:
|
|
117
|
-
|
|
122
|
+
# Handle structured outputs from messages
|
|
123
|
+
return get_text_from_message(msg.content)
|
|
118
124
|
|
|
119
|
-
|
|
125
|
+
# Handle structured outputs
|
|
126
|
+
return get_text_from_message(response.content) if response.content is not None else ""
|
|
120
127
|
|
|
121
128
|
|
|
122
129
|
def _create_events_from_chunk(
|
|
@@ -202,11 +209,11 @@ def _create_events_from_chunk(
|
|
|
202
209
|
|
|
203
210
|
# Handle reasoning
|
|
204
211
|
elif chunk.event == RunEvent.reasoning_started:
|
|
205
|
-
|
|
206
|
-
events_to_emit.append(
|
|
212
|
+
step_started_event = StepStartedEvent(type=EventType.STEP_STARTED, step_name="reasoning")
|
|
213
|
+
events_to_emit.append(step_started_event)
|
|
207
214
|
elif chunk.event == RunEvent.reasoning_completed:
|
|
208
|
-
|
|
209
|
-
events_to_emit.append(
|
|
215
|
+
step_finished_event = StepFinishedEvent(type=EventType.STEP_FINISHED, step_name="reasoning")
|
|
216
|
+
events_to_emit.append(step_finished_event)
|
|
210
217
|
|
|
211
218
|
return events_to_emit, message_started
|
|
212
219
|
|
|
@@ -220,7 +227,7 @@ def _create_completion_events(
|
|
|
220
227
|
run_id: str,
|
|
221
228
|
) -> List[BaseEvent]:
|
|
222
229
|
"""Create events for run completion."""
|
|
223
|
-
events_to_emit = []
|
|
230
|
+
events_to_emit: List[BaseEvent] = []
|
|
224
231
|
|
|
225
232
|
# End remaining active tool calls if needed
|
|
226
233
|
for tool_call_id in list(event_buffer.active_tool_call_ids):
|
|
@@ -271,7 +278,7 @@ def _create_completion_events(
|
|
|
271
278
|
|
|
272
279
|
def _emit_event_logic(event: BaseEvent, event_buffer: EventBuffer) -> List[BaseEvent]:
|
|
273
280
|
"""Process an event through the buffer and return events to actually emit."""
|
|
274
|
-
events_to_emit = []
|
|
281
|
+
events_to_emit: List[BaseEvent] = []
|
|
275
282
|
|
|
276
283
|
if event_buffer.is_blocked():
|
|
277
284
|
# Handle events related to the current blocking tool call
|
agno/app/discord/client.py
CHANGED
|
@@ -8,6 +8,7 @@ from agno.agent.agent import Agent, RunResponse
|
|
|
8
8
|
from agno.media import Audio, File, Image, Video
|
|
9
9
|
from agno.team.team import Team, TeamRunResponse
|
|
10
10
|
from agno.utils.log import log_info, log_warning
|
|
11
|
+
from agno.utils.message import get_text_from_message
|
|
11
12
|
|
|
12
13
|
try:
|
|
13
14
|
import discord
|
|
@@ -167,7 +168,10 @@ class DiscordClient:
|
|
|
167
168
|
thread=thread, message=f"Reasoning: \n{response.reasoning_content}", italics=True
|
|
168
169
|
)
|
|
169
170
|
|
|
170
|
-
|
|
171
|
+
# Handle structured outputs properly
|
|
172
|
+
content_message = get_text_from_message(response.content) if response.content is not None else ""
|
|
173
|
+
|
|
174
|
+
await self._send_discord_messages(thread=thread, message=content_message)
|
|
171
175
|
|
|
172
176
|
async def _send_discord_messages(self, thread: discord.channel, message: str, italics: bool = False): # type: ignore
|
|
173
177
|
if len(message) < 1500:
|
|
@@ -528,6 +528,7 @@ def get_async_playground_router(
|
|
|
528
528
|
session_id=session.session_id,
|
|
529
529
|
session_name=session.session_data.get("session_name") if session.session_data else None,
|
|
530
530
|
created_at=session.created_at,
|
|
531
|
+
updated_at=session.updated_at,
|
|
531
532
|
)
|
|
532
533
|
)
|
|
533
534
|
return agent_sessions
|
|
@@ -751,6 +752,7 @@ def get_async_playground_router(
|
|
|
751
752
|
"session_id": session.session_id,
|
|
752
753
|
"session_name": session.session_data.get("session_name") if session.session_data else None,
|
|
753
754
|
"created_at": session.created_at,
|
|
755
|
+
"updated_at": session.updated_at,
|
|
754
756
|
} # type: ignore
|
|
755
757
|
)
|
|
756
758
|
return workflow_sessions
|
|
@@ -945,6 +947,7 @@ def get_async_playground_router(
|
|
|
945
947
|
session_id=session.session_id,
|
|
946
948
|
session_name=session.session_data.get("session_name") if session.session_data else None,
|
|
947
949
|
created_at=session.created_at,
|
|
950
|
+
updated_at=session.updated_at,
|
|
948
951
|
)
|
|
949
952
|
)
|
|
950
953
|
return team_sessions
|
agno/app/playground/schemas.py
CHANGED
|
@@ -91,6 +91,7 @@ class AgentSessionsResponse(BaseModel):
|
|
|
91
91
|
session_id: Optional[str] = None
|
|
92
92
|
session_name: Optional[str] = None
|
|
93
93
|
created_at: Optional[int] = None
|
|
94
|
+
updated_at: Optional[int] = None
|
|
94
95
|
|
|
95
96
|
|
|
96
97
|
class MemoryResponse(BaseModel):
|
|
@@ -115,6 +116,7 @@ class WorkflowSessionResponse(BaseModel):
|
|
|
115
116
|
session_id: Optional[str] = None
|
|
116
117
|
session_name: Optional[str] = None
|
|
117
118
|
created_at: Optional[int] = None
|
|
119
|
+
updated_at: Optional[int] = None
|
|
118
120
|
|
|
119
121
|
|
|
120
122
|
class WorkflowGetResponse(BaseModel):
|
|
@@ -213,6 +215,7 @@ class TeamSessionResponse(BaseModel):
|
|
|
213
215
|
session_id: Optional[str] = None
|
|
214
216
|
session_name: Optional[str] = None
|
|
215
217
|
created_at: Optional[int] = None
|
|
218
|
+
updated_at: Optional[int] = None
|
|
216
219
|
|
|
217
220
|
|
|
218
221
|
class TeamRenameRequest(BaseModel):
|
|
@@ -528,6 +528,7 @@ def get_sync_playground_router(
|
|
|
528
528
|
session_id=session.session_id,
|
|
529
529
|
session_name=session.session_data.get("session_name") if session.session_data else None,
|
|
530
530
|
created_at=session.created_at,
|
|
531
|
+
updated_at=session.updated_at,
|
|
531
532
|
)
|
|
532
533
|
)
|
|
533
534
|
return agent_sessions
|
|
@@ -743,6 +744,7 @@ def get_sync_playground_router(
|
|
|
743
744
|
"session_id": session.session_id,
|
|
744
745
|
"session_name": session.session_data.get("session_name") if session.session_data else None,
|
|
745
746
|
"created_at": session.created_at,
|
|
747
|
+
"updated_at": session.updated_at,
|
|
746
748
|
} # type: ignore
|
|
747
749
|
)
|
|
748
750
|
return workflow_sessions
|
|
@@ -940,6 +942,7 @@ def get_sync_playground_router(
|
|
|
940
942
|
session_id=session.session_id,
|
|
941
943
|
session_name=session.session_data.get("session_name") if session.session_data else None,
|
|
942
944
|
created_at=session.created_at,
|
|
945
|
+
updated_at=session.updated_at,
|
|
943
946
|
)
|
|
944
947
|
)
|
|
945
948
|
return team_sessions
|
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/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):
|
agno/models/anthropic/claude.py
CHANGED
|
@@ -11,7 +11,7 @@ from agno.models.base import Model
|
|
|
11
11
|
from agno.models.message import Citations, DocumentCitation, Message, UrlCitation
|
|
12
12
|
from agno.models.response import ModelResponse
|
|
13
13
|
from agno.utils.log import log_debug, log_error, log_warning
|
|
14
|
-
from agno.utils.models.claude import MCPServerConfiguration, format_messages
|
|
14
|
+
from agno.utils.models.claude import MCPServerConfiguration, format_messages, format_tools_for_model
|
|
15
15
|
|
|
16
16
|
try:
|
|
17
17
|
from anthropic import (
|
|
@@ -176,59 +176,12 @@ class Claude(Model):
|
|
|
176
176
|
request_kwargs["system"] = [{"text": system_message, "type": "text"}]
|
|
177
177
|
|
|
178
178
|
if tools:
|
|
179
|
-
request_kwargs["tools"] =
|
|
179
|
+
request_kwargs["tools"] = format_tools_for_model(tools)
|
|
180
180
|
|
|
181
181
|
if request_kwargs:
|
|
182
182
|
log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
|
|
183
183
|
return request_kwargs
|
|
184
184
|
|
|
185
|
-
def _format_tools_for_model(self, tools: Optional[List[Dict[str, Any]]] = None) -> Optional[List[Dict[str, Any]]]:
|
|
186
|
-
"""
|
|
187
|
-
Transforms function definitions into a format accepted by the Anthropic API.
|
|
188
|
-
"""
|
|
189
|
-
if not tools:
|
|
190
|
-
return None
|
|
191
|
-
|
|
192
|
-
parsed_tools: List[Dict[str, Any]] = []
|
|
193
|
-
for tool_def in tools:
|
|
194
|
-
if tool_def.get("type", "") != "function":
|
|
195
|
-
parsed_tools.append(tool_def)
|
|
196
|
-
continue
|
|
197
|
-
|
|
198
|
-
func_def = tool_def.get("function", {})
|
|
199
|
-
parameters: Dict[str, Any] = func_def.get("parameters", {})
|
|
200
|
-
properties: Dict[str, Any] = parameters.get("properties", {})
|
|
201
|
-
required_params: List[str] = []
|
|
202
|
-
|
|
203
|
-
for param_name, param_info in properties.items():
|
|
204
|
-
param_type = param_info.get("type", "")
|
|
205
|
-
param_type_list: List[str] = [param_type] if isinstance(param_type, str) else param_type or []
|
|
206
|
-
|
|
207
|
-
if "null" not in param_type_list:
|
|
208
|
-
required_params.append(param_name)
|
|
209
|
-
|
|
210
|
-
input_properties: Dict[str, Dict[str, Union[str, List[str]]]] = {}
|
|
211
|
-
for param_name, param_info in properties.items():
|
|
212
|
-
input_properties[param_name] = {
|
|
213
|
-
"description": param_info.get("description", ""),
|
|
214
|
-
}
|
|
215
|
-
if "type" not in param_info and "anyOf" in param_info:
|
|
216
|
-
input_properties[param_name]["anyOf"] = param_info["anyOf"]
|
|
217
|
-
else:
|
|
218
|
-
input_properties[param_name]["type"] = param_info.get("type", "")
|
|
219
|
-
|
|
220
|
-
tool = {
|
|
221
|
-
"name": func_def.get("name") or "",
|
|
222
|
-
"description": func_def.get("description") or "",
|
|
223
|
-
"input_schema": {
|
|
224
|
-
"type": parameters.get("type", "object"),
|
|
225
|
-
"properties": input_properties,
|
|
226
|
-
"required": required_params,
|
|
227
|
-
},
|
|
228
|
-
}
|
|
229
|
-
parsed_tools.append(tool)
|
|
230
|
-
return parsed_tools
|
|
231
|
-
|
|
232
185
|
def invoke(
|
|
233
186
|
self,
|
|
234
187
|
messages: List[Message],
|
|
@@ -570,16 +523,7 @@ class Claude(Model):
|
|
|
570
523
|
}
|
|
571
524
|
|
|
572
525
|
elif isinstance(response, ContentBlockStopEvent):
|
|
573
|
-
|
|
574
|
-
if response.content_block.type == "thinking": # type: ignore
|
|
575
|
-
model_response.thinking = response.content_block.thinking # type: ignore
|
|
576
|
-
# Store signature if available
|
|
577
|
-
if hasattr(response.content_block, "signature"): # type: ignore
|
|
578
|
-
model_response.provider_data = {
|
|
579
|
-
"signature": response.content_block.signature, # type: ignore
|
|
580
|
-
}
|
|
581
|
-
# Handle tool calls
|
|
582
|
-
elif response.content_block.type == "tool_use": # type: ignore
|
|
526
|
+
if response.content_block.type == "tool_use": # type: ignore
|
|
583
527
|
tool_use = response.content_block # type: ignore
|
|
584
528
|
tool_name = tool_use.name
|
|
585
529
|
tool_input = tool_use.input
|
agno/models/aws/bedrock.py
CHANGED
|
@@ -179,14 +179,10 @@ class AwsBedrock(Model):
|
|
|
179
179
|
required = []
|
|
180
180
|
|
|
181
181
|
for param_name, param_info in func_def.get("parameters", {}).get("properties", {}).items():
|
|
182
|
-
|
|
183
|
-
if isinstance(param_type, list):
|
|
184
|
-
param_type = [t for t in param_type if t != "null"][0]
|
|
182
|
+
properties[param_name] = param_info.copy()
|
|
185
183
|
|
|
186
|
-
properties[param_name]
|
|
187
|
-
"
|
|
188
|
-
"description": param_info.get("description") or "",
|
|
189
|
-
}
|
|
184
|
+
if "description" not in properties[param_name]:
|
|
185
|
+
properties[param_name]["description"] = ""
|
|
190
186
|
|
|
191
187
|
if "null" not in (
|
|
192
188
|
param_info.get("type") if isinstance(param_info.get("type"), list) else [param_info.get("type")]
|
|
@@ -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
|
@@ -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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
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
|
-
|
|
912
|
-
|
|
913
|
-
|
|
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]
|
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
|
|