agno 2.3.1__py3-none-any.whl → 2.3.3__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 +514 -186
- agno/compression/__init__.py +3 -0
- agno/compression/manager.py +176 -0
- agno/db/dynamo/dynamo.py +11 -0
- agno/db/firestore/firestore.py +5 -1
- agno/db/gcs_json/gcs_json_db.py +5 -2
- agno/db/in_memory/in_memory_db.py +5 -2
- agno/db/json/json_db.py +5 -1
- agno/db/migrations/manager.py +4 -4
- agno/db/mongo/async_mongo.py +158 -34
- agno/db/mongo/mongo.py +6 -2
- agno/db/mysql/mysql.py +48 -54
- agno/db/postgres/async_postgres.py +61 -51
- agno/db/postgres/postgres.py +42 -50
- agno/db/redis/redis.py +5 -0
- agno/db/redis/utils.py +5 -5
- agno/db/schemas/memory.py +7 -5
- agno/db/singlestore/singlestore.py +99 -108
- agno/db/sqlite/async_sqlite.py +32 -30
- agno/db/sqlite/sqlite.py +34 -30
- agno/knowledge/reader/pdf_reader.py +2 -2
- agno/knowledge/reader/tavily_reader.py +0 -1
- agno/memory/__init__.py +14 -1
- agno/memory/manager.py +223 -8
- agno/memory/strategies/__init__.py +15 -0
- agno/memory/strategies/base.py +67 -0
- agno/memory/strategies/summarize.py +196 -0
- agno/memory/strategies/types.py +37 -0
- agno/models/anthropic/claude.py +84 -80
- agno/models/aws/bedrock.py +38 -16
- agno/models/aws/claude.py +97 -277
- agno/models/azure/ai_foundry.py +8 -4
- agno/models/base.py +101 -14
- agno/models/cerebras/cerebras.py +18 -7
- agno/models/cerebras/cerebras_openai.py +4 -2
- agno/models/cohere/chat.py +8 -4
- agno/models/google/gemini.py +578 -20
- agno/models/groq/groq.py +18 -5
- agno/models/huggingface/huggingface.py +17 -6
- agno/models/ibm/watsonx.py +16 -6
- agno/models/litellm/chat.py +17 -7
- agno/models/message.py +19 -5
- agno/models/meta/llama.py +20 -4
- agno/models/mistral/mistral.py +8 -4
- agno/models/ollama/chat.py +17 -6
- agno/models/openai/chat.py +17 -6
- agno/models/openai/responses.py +23 -9
- agno/models/vertexai/claude.py +99 -5
- agno/os/interfaces/agui/router.py +1 -0
- agno/os/interfaces/agui/utils.py +97 -57
- agno/os/router.py +16 -1
- agno/os/routers/memory/memory.py +146 -0
- agno/os/routers/memory/schemas.py +26 -0
- agno/os/schema.py +21 -6
- agno/os/utils.py +134 -10
- agno/run/base.py +2 -1
- agno/run/workflow.py +1 -1
- agno/team/team.py +571 -225
- agno/tools/mcp/mcp.py +1 -1
- agno/utils/agent.py +119 -1
- agno/utils/dttm.py +33 -0
- agno/utils/models/ai_foundry.py +9 -2
- agno/utils/models/claude.py +12 -5
- agno/utils/models/cohere.py +9 -2
- agno/utils/models/llama.py +9 -2
- agno/utils/models/mistral.py +4 -2
- agno/utils/print_response/agent.py +37 -2
- agno/utils/print_response/team.py +52 -0
- agno/utils/tokens.py +41 -0
- agno/workflow/types.py +2 -2
- {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/METADATA +45 -40
- {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/RECORD +75 -68
- {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/WHEEL +0 -0
- {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.1.dist-info → agno-2.3.3.dist-info}/top_level.txt +0 -0
agno/models/openai/chat.py
CHANGED
|
@@ -302,19 +302,22 @@ class OpenAIChat(Model):
|
|
|
302
302
|
cleaned_dict = {k: v for k, v in model_dict.items() if v is not None}
|
|
303
303
|
return cleaned_dict
|
|
304
304
|
|
|
305
|
-
def _format_message(self, message: Message) -> Dict[str, Any]:
|
|
305
|
+
def _format_message(self, message: Message, compress_tool_results: bool = False) -> Dict[str, Any]:
|
|
306
306
|
"""
|
|
307
307
|
Format a message into the format expected by OpenAI.
|
|
308
308
|
|
|
309
309
|
Args:
|
|
310
310
|
message (Message): The message to format.
|
|
311
|
+
compress_tool_results: Whether to compress tool results.
|
|
311
312
|
|
|
312
313
|
Returns:
|
|
313
314
|
Dict[str, Any]: The formatted message.
|
|
314
315
|
"""
|
|
316
|
+
tool_result = message.get_content(use_compressed_content=compress_tool_results)
|
|
317
|
+
|
|
315
318
|
message_dict: Dict[str, Any] = {
|
|
316
319
|
"role": self.role_map[message.role] if self.role_map else self.default_role_map[message.role],
|
|
317
|
-
"content":
|
|
320
|
+
"content": tool_result,
|
|
318
321
|
"name": message.name,
|
|
319
322
|
"tool_call_id": message.tool_call_id,
|
|
320
323
|
"tool_calls": message.tool_calls,
|
|
@@ -374,6 +377,7 @@ class OpenAIChat(Model):
|
|
|
374
377
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
375
378
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
376
379
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
380
|
+
compress_tool_results: bool = False,
|
|
377
381
|
) -> ModelResponse:
|
|
378
382
|
"""
|
|
379
383
|
Send a chat completion request to the OpenAI API and parse the response.
|
|
@@ -384,6 +388,7 @@ class OpenAIChat(Model):
|
|
|
384
388
|
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
385
389
|
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
386
390
|
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
391
|
+
compress_tool_results: Whether to compress tool results.
|
|
387
392
|
|
|
388
393
|
Returns:
|
|
389
394
|
ModelResponse: The chat completion response from the API.
|
|
@@ -396,7 +401,7 @@ class OpenAIChat(Model):
|
|
|
396
401
|
|
|
397
402
|
provider_response = self.get_client().chat.completions.create(
|
|
398
403
|
model=self.id,
|
|
399
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
404
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
400
405
|
**self.get_request_params(
|
|
401
406
|
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
402
407
|
),
|
|
@@ -454,6 +459,7 @@ class OpenAIChat(Model):
|
|
|
454
459
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
455
460
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
456
461
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
462
|
+
compress_tool_results: bool = False,
|
|
457
463
|
) -> ModelResponse:
|
|
458
464
|
"""
|
|
459
465
|
Sends an asynchronous chat completion request to the OpenAI API.
|
|
@@ -464,6 +470,7 @@ class OpenAIChat(Model):
|
|
|
464
470
|
response_format (Optional[Union[Dict, Type[BaseModel]]]): The response format to use.
|
|
465
471
|
tools (Optional[List[Dict[str, Any]]]): The tools to use.
|
|
466
472
|
tool_choice (Optional[Union[str, Dict[str, Any]]]): The tool choice to use.
|
|
473
|
+
compress_tool_results: Whether to compress tool results.
|
|
467
474
|
|
|
468
475
|
Returns:
|
|
469
476
|
ModelResponse: The chat completion response from the API.
|
|
@@ -475,7 +482,7 @@ class OpenAIChat(Model):
|
|
|
475
482
|
assistant_message.metrics.start_timer()
|
|
476
483
|
response = await self.get_async_client().chat.completions.create(
|
|
477
484
|
model=self.id,
|
|
478
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
485
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
479
486
|
**self.get_request_params(
|
|
480
487
|
response_format=response_format, tools=tools, tool_choice=tool_choice, run_response=run_response
|
|
481
488
|
),
|
|
@@ -533,12 +540,14 @@ class OpenAIChat(Model):
|
|
|
533
540
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
534
541
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
535
542
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
543
|
+
compress_tool_results: bool = False,
|
|
536
544
|
) -> Iterator[ModelResponse]:
|
|
537
545
|
"""
|
|
538
546
|
Send a streaming chat completion request to the OpenAI API.
|
|
539
547
|
|
|
540
548
|
Args:
|
|
541
549
|
messages (List[Message]): A list of messages to send to the model.
|
|
550
|
+
compress_tool_results: Whether to compress tool results.
|
|
542
551
|
|
|
543
552
|
Returns:
|
|
544
553
|
Iterator[ModelResponse]: An iterator of model responses.
|
|
@@ -552,7 +561,7 @@ class OpenAIChat(Model):
|
|
|
552
561
|
|
|
553
562
|
for chunk in self.get_client().chat.completions.create(
|
|
554
563
|
model=self.id,
|
|
555
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
564
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
556
565
|
stream=True,
|
|
557
566
|
stream_options={"include_usage": True},
|
|
558
567
|
**self.get_request_params(
|
|
@@ -609,12 +618,14 @@ class OpenAIChat(Model):
|
|
|
609
618
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
610
619
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
611
620
|
run_response: Optional[Union[RunOutput, TeamRunOutput]] = None,
|
|
621
|
+
compress_tool_results: bool = False,
|
|
612
622
|
) -> AsyncIterator[ModelResponse]:
|
|
613
623
|
"""
|
|
614
624
|
Sends an asynchronous streaming chat completion request to the OpenAI API.
|
|
615
625
|
|
|
616
626
|
Args:
|
|
617
627
|
messages (List[Message]): A list of messages to send to the model.
|
|
628
|
+
compress_tool_results: Whether to compress tool results.
|
|
618
629
|
|
|
619
630
|
Returns:
|
|
620
631
|
Any: An asynchronous iterator of model responses.
|
|
@@ -628,7 +639,7 @@ class OpenAIChat(Model):
|
|
|
628
639
|
|
|
629
640
|
async_stream = await self.get_async_client().chat.completions.create(
|
|
630
641
|
model=self.id,
|
|
631
|
-
messages=[self._format_message(m) for m in messages], # type: ignore
|
|
642
|
+
messages=[self._format_message(m, compress_tool_results) for m in messages], # type: ignore
|
|
632
643
|
stream=True,
|
|
633
644
|
stream_options={"include_usage": True},
|
|
634
645
|
**self.get_request_params(
|
agno/models/openai/responses.py
CHANGED
|
@@ -395,12 +395,15 @@ class OpenAIResponses(Model):
|
|
|
395
395
|
|
|
396
396
|
return formatted_tools
|
|
397
397
|
|
|
398
|
-
def _format_messages(
|
|
398
|
+
def _format_messages(
|
|
399
|
+
self, messages: List[Message], compress_tool_results: bool = False
|
|
400
|
+
) -> List[Union[Dict[str, Any], ResponseReasoningItem]]:
|
|
399
401
|
"""
|
|
400
402
|
Format a message into the format expected by OpenAI.
|
|
401
403
|
|
|
402
404
|
Args:
|
|
403
405
|
messages (List[Message]): The message to format.
|
|
406
|
+
compress_tool_results: Whether to compress tool results.
|
|
404
407
|
|
|
405
408
|
Returns:
|
|
406
409
|
Dict[str, Any]: The formatted message.
|
|
@@ -445,7 +448,7 @@ class OpenAIResponses(Model):
|
|
|
445
448
|
if message.role in ["user", "system"]:
|
|
446
449
|
message_dict: Dict[str, Any] = {
|
|
447
450
|
"role": self.role_map[message.role],
|
|
448
|
-
"content": message.
|
|
451
|
+
"content": message.get_content(use_compressed_content=compress_tool_results),
|
|
449
452
|
}
|
|
450
453
|
message_dict = {k: v for k, v in message_dict.items() if v is not None}
|
|
451
454
|
|
|
@@ -469,7 +472,9 @@ class OpenAIResponses(Model):
|
|
|
469
472
|
|
|
470
473
|
# Tool call result
|
|
471
474
|
elif message.role == "tool":
|
|
472
|
-
|
|
475
|
+
tool_result = message.get_content(use_compressed_content=compress_tool_results)
|
|
476
|
+
|
|
477
|
+
if message.tool_call_id and tool_result is not None:
|
|
473
478
|
function_call_id = message.tool_call_id
|
|
474
479
|
# Normalize: if a fc_* id was provided, translate to its corresponding call_* id
|
|
475
480
|
if isinstance(function_call_id, str) and function_call_id in fc_id_to_call_id:
|
|
@@ -477,7 +482,7 @@ class OpenAIResponses(Model):
|
|
|
477
482
|
else:
|
|
478
483
|
call_id_value = function_call_id
|
|
479
484
|
formatted_messages.append(
|
|
480
|
-
{"type": "function_call_output", "call_id": call_id_value, "output":
|
|
485
|
+
{"type": "function_call_output", "call_id": call_id_value, "output": tool_result}
|
|
481
486
|
)
|
|
482
487
|
# Tool Calls
|
|
483
488
|
elif message.tool_calls is not None and len(message.tool_calls) > 0:
|
|
@@ -519,6 +524,7 @@ class OpenAIResponses(Model):
|
|
|
519
524
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
520
525
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
521
526
|
run_response: Optional[RunOutput] = None,
|
|
527
|
+
compress_tool_results: bool = False,
|
|
522
528
|
) -> ModelResponse:
|
|
523
529
|
"""
|
|
524
530
|
Send a request to the OpenAI Responses API.
|
|
@@ -535,7 +541,7 @@ class OpenAIResponses(Model):
|
|
|
535
541
|
|
|
536
542
|
provider_response = self.get_client().responses.create(
|
|
537
543
|
model=self.id,
|
|
538
|
-
input=self._format_messages(messages), # type: ignore
|
|
544
|
+
input=self._format_messages(messages, compress_tool_results), # type: ignore
|
|
539
545
|
**request_params,
|
|
540
546
|
)
|
|
541
547
|
|
|
@@ -588,6 +594,7 @@ class OpenAIResponses(Model):
|
|
|
588
594
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
589
595
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
590
596
|
run_response: Optional[RunOutput] = None,
|
|
597
|
+
compress_tool_results: bool = False,
|
|
591
598
|
) -> ModelResponse:
|
|
592
599
|
"""
|
|
593
600
|
Sends an asynchronous request to the OpenAI Responses API.
|
|
@@ -604,7 +611,7 @@ class OpenAIResponses(Model):
|
|
|
604
611
|
|
|
605
612
|
provider_response = await self.get_async_client().responses.create(
|
|
606
613
|
model=self.id,
|
|
607
|
-
input=self._format_messages(messages), # type: ignore
|
|
614
|
+
input=self._format_messages(messages, compress_tool_results), # type: ignore
|
|
608
615
|
**request_params,
|
|
609
616
|
)
|
|
610
617
|
|
|
@@ -657,6 +664,7 @@ class OpenAIResponses(Model):
|
|
|
657
664
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
658
665
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
659
666
|
run_response: Optional[RunOutput] = None,
|
|
667
|
+
compress_tool_results: bool = False,
|
|
660
668
|
) -> Iterator[ModelResponse]:
|
|
661
669
|
"""
|
|
662
670
|
Send a streaming request to the OpenAI Responses API.
|
|
@@ -674,7 +682,7 @@ class OpenAIResponses(Model):
|
|
|
674
682
|
|
|
675
683
|
for chunk in self.get_client().responses.create(
|
|
676
684
|
model=self.id,
|
|
677
|
-
input=self._format_messages(messages), # type: ignore
|
|
685
|
+
input=self._format_messages(messages, compress_tool_results), # type: ignore
|
|
678
686
|
stream=True,
|
|
679
687
|
**request_params,
|
|
680
688
|
):
|
|
@@ -730,6 +738,7 @@ class OpenAIResponses(Model):
|
|
|
730
738
|
tools: Optional[List[Dict[str, Any]]] = None,
|
|
731
739
|
tool_choice: Optional[Union[str, Dict[str, Any]]] = None,
|
|
732
740
|
run_response: Optional[RunOutput] = None,
|
|
741
|
+
compress_tool_results: bool = False,
|
|
733
742
|
) -> AsyncIterator[ModelResponse]:
|
|
734
743
|
"""
|
|
735
744
|
Sends an asynchronous streaming request to the OpenAI Responses API.
|
|
@@ -747,7 +756,7 @@ class OpenAIResponses(Model):
|
|
|
747
756
|
|
|
748
757
|
async_stream = await self.get_async_client().responses.create(
|
|
749
758
|
model=self.id,
|
|
750
|
-
input=self._format_messages(messages), # type: ignore
|
|
759
|
+
input=self._format_messages(messages, compress_tool_results), # type: ignore
|
|
751
760
|
stream=True,
|
|
752
761
|
**request_params,
|
|
753
762
|
)
|
|
@@ -793,7 +802,11 @@ class OpenAIResponses(Model):
|
|
|
793
802
|
raise ModelProviderError(message=str(exc), model_name=self.name, model_id=self.id) from exc
|
|
794
803
|
|
|
795
804
|
def format_function_call_results(
|
|
796
|
-
self,
|
|
805
|
+
self,
|
|
806
|
+
messages: List[Message],
|
|
807
|
+
function_call_results: List[Message],
|
|
808
|
+
tool_call_ids: List[str],
|
|
809
|
+
compress_tool_results: bool = False,
|
|
797
810
|
) -> None:
|
|
798
811
|
"""
|
|
799
812
|
Handle the results of function calls.
|
|
@@ -802,6 +815,7 @@ class OpenAIResponses(Model):
|
|
|
802
815
|
messages (List[Message]): The list of conversation messages.
|
|
803
816
|
function_call_results (List[Message]): The results of the function calls.
|
|
804
817
|
tool_ids (List[str]): The tool ids.
|
|
818
|
+
compress_tool_results (bool): Whether to compress tool results.
|
|
805
819
|
"""
|
|
806
820
|
if len(function_call_results) > 0:
|
|
807
821
|
for _fc_message_index, _fc_message in enumerate(function_call_results):
|
agno/models/vertexai/claude.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from os import getenv
|
|
3
|
-
from typing import Any, Dict, Optional
|
|
3
|
+
from typing import Any, Dict, List, Optional, Type, Union
|
|
4
4
|
|
|
5
5
|
import httpx
|
|
6
|
+
from pydantic import BaseModel
|
|
6
7
|
|
|
7
8
|
from agno.models.anthropic import Claude as AnthropicClaude
|
|
8
9
|
from agno.utils.http import get_default_async_client, get_default_sync_client
|
|
9
|
-
from agno.utils.log import log_warning
|
|
10
|
+
from agno.utils.log import log_debug, log_warning
|
|
11
|
+
from agno.utils.models.claude import format_tools_for_model
|
|
10
12
|
|
|
11
13
|
try:
|
|
12
14
|
from anthropic import AnthropicVertex, AsyncAnthropicVertex
|
|
@@ -26,14 +28,23 @@ class Claude(AnthropicClaude):
|
|
|
26
28
|
name: str = "Claude"
|
|
27
29
|
provider: str = "VertexAI"
|
|
28
30
|
|
|
29
|
-
client: Optional[AnthropicVertex] = None # type: ignore
|
|
30
|
-
async_client: Optional[AsyncAnthropicVertex] = None # type: ignore
|
|
31
|
-
|
|
32
31
|
# Client parameters
|
|
33
32
|
region: Optional[str] = None
|
|
34
33
|
project_id: Optional[str] = None
|
|
35
34
|
base_url: Optional[str] = None
|
|
36
35
|
|
|
36
|
+
client: Optional[AnthropicVertex] = None # type: ignore
|
|
37
|
+
async_client: Optional[AsyncAnthropicVertex] = None # type: ignore
|
|
38
|
+
|
|
39
|
+
def __post_init__(self):
|
|
40
|
+
"""Validate model configuration after initialization"""
|
|
41
|
+
# Validate thinking support immediately at model creation
|
|
42
|
+
if self.thinking:
|
|
43
|
+
self._validate_thinking_support()
|
|
44
|
+
# Overwrite output schema support for VertexAI Claude
|
|
45
|
+
self.supports_native_structured_outputs = False
|
|
46
|
+
self.supports_json_schema_outputs = False
|
|
47
|
+
|
|
37
48
|
def _get_client_params(self) -> Dict[str, Any]:
|
|
38
49
|
client_params: Dict[str, Any] = {}
|
|
39
50
|
|
|
@@ -94,3 +105,86 @@ class Claude(AnthropicClaude):
|
|
|
94
105
|
_client_params["http_client"] = get_default_async_client()
|
|
95
106
|
self.async_client = AsyncAnthropicVertex(**_client_params)
|
|
96
107
|
return self.async_client
|
|
108
|
+
|
|
109
|
+
def get_request_params(
|
|
110
|
+
self,
|
|
111
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
112
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
113
|
+
) -> Dict[str, Any]:
|
|
114
|
+
"""
|
|
115
|
+
Generate keyword arguments for API requests.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Dict[str, Any]: The keyword arguments for API requests.
|
|
119
|
+
"""
|
|
120
|
+
# Validate thinking support if thinking is enabled
|
|
121
|
+
if self.thinking:
|
|
122
|
+
self._validate_thinking_support()
|
|
123
|
+
|
|
124
|
+
_request_params: Dict[str, Any] = {}
|
|
125
|
+
if self.max_tokens:
|
|
126
|
+
_request_params["max_tokens"] = self.max_tokens
|
|
127
|
+
if self.thinking:
|
|
128
|
+
_request_params["thinking"] = self.thinking
|
|
129
|
+
if self.temperature:
|
|
130
|
+
_request_params["temperature"] = self.temperature
|
|
131
|
+
if self.stop_sequences:
|
|
132
|
+
_request_params["stop_sequences"] = self.stop_sequences
|
|
133
|
+
if self.top_p:
|
|
134
|
+
_request_params["top_p"] = self.top_p
|
|
135
|
+
if self.top_k:
|
|
136
|
+
_request_params["top_k"] = self.top_k
|
|
137
|
+
if self.timeout:
|
|
138
|
+
_request_params["timeout"] = self.timeout
|
|
139
|
+
|
|
140
|
+
# Build betas list - include existing betas and add new one if needed
|
|
141
|
+
betas_list = list(self.betas) if self.betas else []
|
|
142
|
+
|
|
143
|
+
# Include betas if any are present
|
|
144
|
+
if betas_list:
|
|
145
|
+
_request_params["betas"] = betas_list
|
|
146
|
+
|
|
147
|
+
if self.request_params:
|
|
148
|
+
_request_params.update(self.request_params)
|
|
149
|
+
|
|
150
|
+
if _request_params:
|
|
151
|
+
log_debug(f"Calling {self.provider} with request parameters: {_request_params}", log_level=2)
|
|
152
|
+
return _request_params
|
|
153
|
+
|
|
154
|
+
def _prepare_request_kwargs(
|
|
155
|
+
self,
|
|
156
|
+
system_message: str,
|
|
157
|
+
tools: Optional[List[Dict[str, Any]]] = None,
|
|
158
|
+
response_format: Optional[Union[Dict, Type[BaseModel]]] = None,
|
|
159
|
+
) -> Dict[str, Any]:
|
|
160
|
+
"""
|
|
161
|
+
Prepare the request keyword arguments for the API call.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
system_message (str): The concatenated system messages.
|
|
165
|
+
tools: Optional list of tools
|
|
166
|
+
response_format: Optional response format (Pydantic model or dict)
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
Dict[str, Any]: The request keyword arguments.
|
|
170
|
+
"""
|
|
171
|
+
# Pass response_format and tools to get_request_params for beta header handling
|
|
172
|
+
request_kwargs = self.get_request_params(response_format=response_format, tools=tools).copy()
|
|
173
|
+
if system_message:
|
|
174
|
+
if self.cache_system_prompt:
|
|
175
|
+
cache_control = (
|
|
176
|
+
{"type": "ephemeral", "ttl": "1h"}
|
|
177
|
+
if self.extended_cache_time is not None and self.extended_cache_time is True
|
|
178
|
+
else {"type": "ephemeral"}
|
|
179
|
+
)
|
|
180
|
+
request_kwargs["system"] = [{"text": system_message, "type": "text", "cache_control": cache_control}]
|
|
181
|
+
else:
|
|
182
|
+
request_kwargs["system"] = [{"text": system_message, "type": "text"}]
|
|
183
|
+
|
|
184
|
+
# Format tools (this will handle strict mode)
|
|
185
|
+
if tools:
|
|
186
|
+
request_kwargs["tools"] = format_tools_for_model(tools)
|
|
187
|
+
|
|
188
|
+
if request_kwargs:
|
|
189
|
+
log_debug(f"Calling {self.provider} with request parameters: {request_kwargs}", log_level=2)
|
|
190
|
+
return request_kwargs
|
|
@@ -33,6 +33,7 @@ async def run_agent(agent: Agent, run_input: RunAgentInput) -> AsyncIterator[Bas
|
|
|
33
33
|
try:
|
|
34
34
|
# Preparing the input for the Agent and emitting the run started event
|
|
35
35
|
messages = convert_agui_messages_to_agno_messages(run_input.messages or [])
|
|
36
|
+
|
|
36
37
|
yield RunStartedEvent(type=EventType.RUN_STARTED, thread_id=run_input.thread_id, run_id=run_id)
|
|
37
38
|
|
|
38
39
|
# Look for user_id in run_input.forwarded_props
|
agno/os/interfaces/agui/utils.py
CHANGED
|
@@ -28,7 +28,7 @@ from agno.models.message import Message
|
|
|
28
28
|
from agno.run.agent import RunContentEvent, RunEvent, RunOutputEvent, RunPausedEvent
|
|
29
29
|
from agno.run.team import RunContentEvent as TeamRunContentEvent
|
|
30
30
|
from agno.run.team import TeamRunEvent, TeamRunOutputEvent
|
|
31
|
-
from agno.utils.log import log_warning
|
|
31
|
+
from agno.utils.log import log_debug, log_warning
|
|
32
32
|
from agno.utils.message import get_text_from_message
|
|
33
33
|
|
|
34
34
|
|
|
@@ -116,23 +116,43 @@ class EventBuffer:
|
|
|
116
116
|
|
|
117
117
|
def convert_agui_messages_to_agno_messages(messages: List[AGUIMessage]) -> List[Message]:
|
|
118
118
|
"""Convert AG-UI messages to Agno messages."""
|
|
119
|
-
|
|
119
|
+
# First pass: collect all tool_call_ids that have results
|
|
120
|
+
tool_call_ids_with_results: Set[str] = set()
|
|
121
|
+
for msg in messages:
|
|
122
|
+
if msg.role == "tool" and msg.tool_call_id:
|
|
123
|
+
tool_call_ids_with_results.add(msg.tool_call_id)
|
|
124
|
+
|
|
125
|
+
# Second pass: convert messages
|
|
126
|
+
result: List[Message] = []
|
|
127
|
+
seen_tool_call_ids: Set[str] = set()
|
|
128
|
+
|
|
120
129
|
for msg in messages:
|
|
121
130
|
if msg.role == "tool":
|
|
131
|
+
# Deduplicate tool results - keep only first occurrence
|
|
132
|
+
if msg.tool_call_id in seen_tool_call_ids:
|
|
133
|
+
log_debug(f"Skipping duplicate AGUI tool result: {msg.tool_call_id}")
|
|
134
|
+
continue
|
|
135
|
+
seen_tool_call_ids.add(msg.tool_call_id)
|
|
122
136
|
result.append(Message(role="tool", tool_call_id=msg.tool_call_id, content=msg.content))
|
|
137
|
+
|
|
123
138
|
elif msg.role == "assistant":
|
|
124
139
|
tool_calls = None
|
|
125
140
|
if msg.tool_calls:
|
|
126
|
-
tool_calls
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
)
|
|
133
|
-
)
|
|
141
|
+
# Filter tool_calls to only those with results in this message sequence
|
|
142
|
+
filtered_calls = [call for call in msg.tool_calls if call.id in tool_call_ids_with_results]
|
|
143
|
+
if filtered_calls:
|
|
144
|
+
tool_calls = [call.model_dump() for call in filtered_calls]
|
|
145
|
+
result.append(Message(role="assistant", content=msg.content, tool_calls=tool_calls))
|
|
146
|
+
|
|
134
147
|
elif msg.role == "user":
|
|
135
148
|
result.append(Message(role="user", content=msg.content))
|
|
149
|
+
|
|
150
|
+
elif msg.role == "system":
|
|
151
|
+
pass # Skip - agent builds its own system message from configuration
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
log_warning(f"Unknown AGUI message role: {msg.role}")
|
|
155
|
+
|
|
136
156
|
return result
|
|
137
157
|
|
|
138
158
|
|
|
@@ -250,7 +270,25 @@ def _create_events_from_chunk(
|
|
|
250
270
|
parent_message_id = event_buffer.get_parent_message_id_for_tool_call()
|
|
251
271
|
|
|
252
272
|
if not parent_message_id:
|
|
253
|
-
|
|
273
|
+
# Create parent message for tool calls without preceding assistant message
|
|
274
|
+
parent_message_id = str(uuid.uuid4())
|
|
275
|
+
|
|
276
|
+
# Emit a text message to serve as the parent
|
|
277
|
+
text_start = TextMessageStartEvent(
|
|
278
|
+
type=EventType.TEXT_MESSAGE_START,
|
|
279
|
+
message_id=parent_message_id,
|
|
280
|
+
role="assistant",
|
|
281
|
+
)
|
|
282
|
+
events_to_emit.append(text_start)
|
|
283
|
+
|
|
284
|
+
text_end = TextMessageEndEvent(
|
|
285
|
+
type=EventType.TEXT_MESSAGE_END,
|
|
286
|
+
message_id=parent_message_id,
|
|
287
|
+
)
|
|
288
|
+
events_to_emit.append(text_end)
|
|
289
|
+
|
|
290
|
+
# Set this as the pending parent for subsequent tool calls in this batch
|
|
291
|
+
event_buffer.set_pending_tool_calls_parent_id(parent_message_id)
|
|
254
292
|
|
|
255
293
|
start_event = ToolCallStartEvent(
|
|
256
294
|
type=EventType.TOOL_CALL_START,
|
|
@@ -341,58 +379,60 @@ def _create_completion_events(
|
|
|
341
379
|
end_message_event = TextMessageEndEvent(type=EventType.TEXT_MESSAGE_END, message_id=message_id)
|
|
342
380
|
events_to_emit.append(end_message_event)
|
|
343
381
|
|
|
344
|
-
#
|
|
345
|
-
if isinstance(chunk, RunPausedEvent)
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
)
|
|
353
|
-
events_to_emit.append(assistant_start_event)
|
|
354
|
-
|
|
355
|
-
# Add any text content if present for the assistant message
|
|
356
|
-
if chunk.content:
|
|
357
|
-
content_event = TextMessageContentEvent(
|
|
358
|
-
type=EventType.TEXT_MESSAGE_CONTENT,
|
|
382
|
+
# Emit external execution tools
|
|
383
|
+
if isinstance(chunk, RunPausedEvent):
|
|
384
|
+
external_tools = chunk.tools_awaiting_external_execution
|
|
385
|
+
if external_tools:
|
|
386
|
+
# First, emit an assistant message for external tool calls
|
|
387
|
+
assistant_message_id = str(uuid.uuid4())
|
|
388
|
+
assistant_start_event = TextMessageStartEvent(
|
|
389
|
+
type=EventType.TEXT_MESSAGE_START,
|
|
359
390
|
message_id=assistant_message_id,
|
|
360
|
-
|
|
391
|
+
role="assistant",
|
|
361
392
|
)
|
|
362
|
-
events_to_emit.append(
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
for tool in chunk.tools:
|
|
373
|
-
if tool.tool_call_id is None or tool.tool_name is None:
|
|
374
|
-
continue
|
|
393
|
+
events_to_emit.append(assistant_start_event)
|
|
394
|
+
|
|
395
|
+
# Add any text content if present for the assistant message
|
|
396
|
+
if chunk.content:
|
|
397
|
+
content_event = TextMessageContentEvent(
|
|
398
|
+
type=EventType.TEXT_MESSAGE_CONTENT,
|
|
399
|
+
message_id=assistant_message_id,
|
|
400
|
+
delta=str(chunk.content),
|
|
401
|
+
)
|
|
402
|
+
events_to_emit.append(content_event)
|
|
375
403
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
parent_message_id=assistant_message_id, # Use the assistant message as parent
|
|
404
|
+
# End the assistant message
|
|
405
|
+
assistant_end_event = TextMessageEndEvent(
|
|
406
|
+
type=EventType.TEXT_MESSAGE_END,
|
|
407
|
+
message_id=assistant_message_id,
|
|
381
408
|
)
|
|
382
|
-
events_to_emit.append(
|
|
409
|
+
events_to_emit.append(assistant_end_event)
|
|
410
|
+
|
|
411
|
+
# Emit tool call events for external execution
|
|
412
|
+
for tool in external_tools:
|
|
413
|
+
if tool.tool_call_id is None or tool.tool_name is None:
|
|
414
|
+
continue
|
|
415
|
+
|
|
416
|
+
start_event = ToolCallStartEvent(
|
|
417
|
+
type=EventType.TOOL_CALL_START,
|
|
418
|
+
tool_call_id=tool.tool_call_id,
|
|
419
|
+
tool_call_name=tool.tool_name,
|
|
420
|
+
parent_message_id=assistant_message_id, # Use the assistant message as parent
|
|
421
|
+
)
|
|
422
|
+
events_to_emit.append(start_event)
|
|
383
423
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
424
|
+
args_event = ToolCallArgsEvent(
|
|
425
|
+
type=EventType.TOOL_CALL_ARGS,
|
|
426
|
+
tool_call_id=tool.tool_call_id,
|
|
427
|
+
delta=json.dumps(tool.tool_args),
|
|
428
|
+
)
|
|
429
|
+
events_to_emit.append(args_event)
|
|
390
430
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
431
|
+
end_event = ToolCallEndEvent(
|
|
432
|
+
type=EventType.TOOL_CALL_END,
|
|
433
|
+
tool_call_id=tool.tool_call_id,
|
|
434
|
+
)
|
|
435
|
+
events_to_emit.append(end_event)
|
|
396
436
|
|
|
397
437
|
run_finished_event = RunFinishedEvent(type=EventType.RUN_FINISHED, thread_id=thread_id, run_id=run_id)
|
|
398
438
|
events_to_emit.append(run_finished_event)
|
agno/os/router.py
CHANGED
|
@@ -139,6 +139,22 @@ async def _get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict
|
|
|
139
139
|
kwargs.pop("knowledge_filters")
|
|
140
140
|
log_warning(f"Invalid FilterExpr in knowledge_filters: {e}")
|
|
141
141
|
|
|
142
|
+
# Handle output_schema - convert JSON schema to dynamic Pydantic model
|
|
143
|
+
if output_schema := kwargs.get("output_schema"):
|
|
144
|
+
try:
|
|
145
|
+
if isinstance(output_schema, str):
|
|
146
|
+
from agno.os.utils import json_schema_to_pydantic_model
|
|
147
|
+
|
|
148
|
+
schema_dict = json.loads(output_schema)
|
|
149
|
+
dynamic_model = json_schema_to_pydantic_model(schema_dict)
|
|
150
|
+
kwargs["output_schema"] = dynamic_model
|
|
151
|
+
except json.JSONDecodeError:
|
|
152
|
+
kwargs.pop("output_schema")
|
|
153
|
+
log_warning(f"Invalid output_schema JSON: {output_schema}")
|
|
154
|
+
except Exception as e:
|
|
155
|
+
kwargs.pop("output_schema")
|
|
156
|
+
log_warning(f"Failed to create output_schema model: {e}")
|
|
157
|
+
|
|
142
158
|
# Parse boolean and null values
|
|
143
159
|
for key, value in kwargs.items():
|
|
144
160
|
if isinstance(value, str) and value.lower() in ["true", "false"]:
|
|
@@ -1794,7 +1810,6 @@ def get_base_router(
|
|
|
1794
1810
|
raise HTTPException(status_code=404, detail="Database not found")
|
|
1795
1811
|
|
|
1796
1812
|
if target_version:
|
|
1797
|
-
|
|
1798
1813
|
# Use the session table as proxy for the database schema version
|
|
1799
1814
|
if isinstance(db, AsyncBaseDb):
|
|
1800
1815
|
current_version = await db.get_latest_schema_version(db.session_table_name)
|