agno 2.3.13__py3-none-any.whl → 2.3.14__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 +1131 -1402
- agno/eval/__init__.py +21 -8
- agno/knowledge/embedder/azure_openai.py +0 -1
- agno/knowledge/embedder/google.py +1 -1
- agno/models/anthropic/claude.py +4 -1
- agno/models/base.py +8 -4
- agno/models/openai/responses.py +2 -2
- agno/os/app.py +39 -0
- agno/os/interfaces/a2a/router.py +619 -9
- agno/os/interfaces/a2a/utils.py +31 -32
- agno/os/middleware/jwt.py +5 -5
- agno/os/routers/agents/schema.py +14 -1
- agno/os/routers/teams/schema.py +14 -1
- agno/os/utils.py +61 -53
- agno/reasoning/anthropic.py +85 -1
- agno/reasoning/azure_ai_foundry.py +93 -1
- agno/reasoning/deepseek.py +91 -1
- agno/reasoning/gemini.py +81 -1
- agno/reasoning/groq.py +103 -1
- agno/reasoning/manager.py +1244 -0
- agno/reasoning/ollama.py +93 -1
- agno/reasoning/openai.py +113 -1
- agno/reasoning/vertexai.py +85 -1
- agno/run/agent.py +11 -0
- agno/run/base.py +1 -1
- agno/run/team.py +11 -0
- agno/session/team.py +0 -3
- agno/team/team.py +1201 -1445
- agno/utils/events.py +69 -2
- agno/utils/hooks.py +4 -10
- agno/utils/print_response/agent.py +26 -0
- agno/utils/print_response/team.py +11 -0
- agno/utils/prompts.py +8 -6
- agno/utils/string.py +46 -0
- agno/utils/team.py +1 -1
- agno/vectordb/milvus/milvus.py +32 -3
- {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/METADATA +3 -2
- {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/RECORD +41 -40
- {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/WHEEL +0 -0
- {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/licenses/LICENSE +0 -0
- {agno-2.3.13.dist-info → agno-2.3.14.dist-info}/top_level.txt +0 -0
agno/os/interfaces/a2a/utils.py
CHANGED
|
@@ -95,7 +95,6 @@ async def map_a2a_request_to_run_input(request_body: dict, stream: bool = True)
|
|
|
95
95
|
```json
|
|
96
96
|
{
|
|
97
97
|
"jsonrpc": "2.0",
|
|
98
|
-
"method": "message/send",
|
|
99
98
|
"id": "id",
|
|
100
99
|
"params": {
|
|
101
100
|
"message": {
|
|
@@ -325,7 +324,7 @@ async def stream_a2a_response(
|
|
|
325
324
|
final=False,
|
|
326
325
|
)
|
|
327
326
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
328
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
327
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
329
328
|
|
|
330
329
|
# 2. Send all content and secondary events
|
|
331
330
|
|
|
@@ -341,7 +340,7 @@ async def stream_a2a_response(
|
|
|
341
340
|
metadata={"agno_content_category": "content"},
|
|
342
341
|
)
|
|
343
342
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=message)
|
|
344
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
343
|
+
yield f"event: Message\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
345
344
|
|
|
346
345
|
# Send tool call events
|
|
347
346
|
elif isinstance(event, (ToolCallStartedEvent, TeamToolCallStartedEvent)):
|
|
@@ -361,7 +360,7 @@ async def stream_a2a_response(
|
|
|
361
360
|
metadata=metadata,
|
|
362
361
|
)
|
|
363
362
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
364
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
363
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
365
364
|
|
|
366
365
|
elif isinstance(event, (ToolCallCompletedEvent, TeamToolCallCompletedEvent)):
|
|
367
366
|
metadata = {"agno_event_type": "tool_call_completed"}
|
|
@@ -380,7 +379,7 @@ async def stream_a2a_response(
|
|
|
380
379
|
metadata=metadata,
|
|
381
380
|
)
|
|
382
381
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
383
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
382
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
384
383
|
|
|
385
384
|
# Send reasoning events
|
|
386
385
|
elif isinstance(event, (ReasoningStartedEvent, TeamReasoningStartedEvent)):
|
|
@@ -392,7 +391,7 @@ async def stream_a2a_response(
|
|
|
392
391
|
metadata={"agno_event_type": "reasoning_started"},
|
|
393
392
|
)
|
|
394
393
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
395
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
394
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
396
395
|
|
|
397
396
|
elif isinstance(event, (ReasoningStepEvent, TeamReasoningStepEvent)):
|
|
398
397
|
if event.reasoning_content:
|
|
@@ -415,7 +414,7 @@ async def stream_a2a_response(
|
|
|
415
414
|
metadata={"agno_content_category": "reasoning", "agno_event_type": "reasoning_step"},
|
|
416
415
|
)
|
|
417
416
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=reasoning_message)
|
|
418
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
417
|
+
yield f"event: Message\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
419
418
|
|
|
420
419
|
elif isinstance(event, (ReasoningCompletedEvent, TeamReasoningCompletedEvent)):
|
|
421
420
|
status_event = TaskStatusUpdateEvent(
|
|
@@ -426,7 +425,7 @@ async def stream_a2a_response(
|
|
|
426
425
|
metadata={"agno_event_type": "reasoning_completed"},
|
|
427
426
|
)
|
|
428
427
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
429
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
428
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
430
429
|
|
|
431
430
|
# Send memory update events
|
|
432
431
|
elif isinstance(event, (MemoryUpdateStartedEvent, TeamMemoryUpdateStartedEvent)):
|
|
@@ -438,7 +437,7 @@ async def stream_a2a_response(
|
|
|
438
437
|
metadata={"agno_event_type": "memory_update_started"},
|
|
439
438
|
)
|
|
440
439
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
441
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
440
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
442
441
|
|
|
443
442
|
elif isinstance(event, (MemoryUpdateCompletedEvent, TeamMemoryUpdateCompletedEvent)):
|
|
444
443
|
status_event = TaskStatusUpdateEvent(
|
|
@@ -449,7 +448,7 @@ async def stream_a2a_response(
|
|
|
449
448
|
metadata={"agno_event_type": "memory_update_completed"},
|
|
450
449
|
)
|
|
451
450
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
452
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
451
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
453
452
|
|
|
454
453
|
# Send workflow events
|
|
455
454
|
elif isinstance(event, WorkflowStepStartedEvent):
|
|
@@ -465,7 +464,7 @@ async def stream_a2a_response(
|
|
|
465
464
|
metadata=metadata,
|
|
466
465
|
)
|
|
467
466
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
468
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
467
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
469
468
|
|
|
470
469
|
elif isinstance(event, WorkflowStepCompletedEvent):
|
|
471
470
|
metadata = {"agno_event_type": "workflow_step_completed"}
|
|
@@ -480,7 +479,7 @@ async def stream_a2a_response(
|
|
|
480
479
|
metadata=metadata,
|
|
481
480
|
)
|
|
482
481
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
483
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
482
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
484
483
|
|
|
485
484
|
elif isinstance(event, WorkflowStepErrorEvent):
|
|
486
485
|
metadata = {"agno_event_type": "workflow_step_error"}
|
|
@@ -497,7 +496,7 @@ async def stream_a2a_response(
|
|
|
497
496
|
metadata=metadata,
|
|
498
497
|
)
|
|
499
498
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
500
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
499
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
501
500
|
|
|
502
501
|
# Send loop events
|
|
503
502
|
elif isinstance(event, LoopExecutionStartedEvent):
|
|
@@ -515,7 +514,7 @@ async def stream_a2a_response(
|
|
|
515
514
|
metadata=metadata,
|
|
516
515
|
)
|
|
517
516
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
518
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
517
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
519
518
|
|
|
520
519
|
elif isinstance(event, LoopIterationStartedEvent):
|
|
521
520
|
metadata = {"agno_event_type": "loop_iteration_started"}
|
|
@@ -534,7 +533,7 @@ async def stream_a2a_response(
|
|
|
534
533
|
metadata=metadata,
|
|
535
534
|
)
|
|
536
535
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
537
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
536
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
538
537
|
|
|
539
538
|
elif isinstance(event, LoopIterationCompletedEvent):
|
|
540
539
|
metadata = {"agno_event_type": "loop_iteration_completed"}
|
|
@@ -553,7 +552,7 @@ async def stream_a2a_response(
|
|
|
553
552
|
metadata=metadata,
|
|
554
553
|
)
|
|
555
554
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
556
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
555
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
557
556
|
|
|
558
557
|
elif isinstance(event, LoopExecutionCompletedEvent):
|
|
559
558
|
metadata = {"agno_event_type": "loop_execution_completed"}
|
|
@@ -570,7 +569,7 @@ async def stream_a2a_response(
|
|
|
570
569
|
metadata=metadata,
|
|
571
570
|
)
|
|
572
571
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
573
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
572
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
574
573
|
|
|
575
574
|
# Send parallel events
|
|
576
575
|
elif isinstance(event, ParallelExecutionStartedEvent):
|
|
@@ -588,7 +587,7 @@ async def stream_a2a_response(
|
|
|
588
587
|
metadata=metadata,
|
|
589
588
|
)
|
|
590
589
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
591
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
590
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
592
591
|
|
|
593
592
|
elif isinstance(event, ParallelExecutionCompletedEvent):
|
|
594
593
|
metadata = {"agno_event_type": "parallel_execution_completed"}
|
|
@@ -605,7 +604,7 @@ async def stream_a2a_response(
|
|
|
605
604
|
metadata=metadata,
|
|
606
605
|
)
|
|
607
606
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
608
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
607
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
609
608
|
|
|
610
609
|
# Send condition events
|
|
611
610
|
elif isinstance(event, ConditionExecutionStartedEvent):
|
|
@@ -623,7 +622,7 @@ async def stream_a2a_response(
|
|
|
623
622
|
metadata=metadata,
|
|
624
623
|
)
|
|
625
624
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
626
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
625
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
627
626
|
|
|
628
627
|
elif isinstance(event, ConditionExecutionCompletedEvent):
|
|
629
628
|
metadata = {"agno_event_type": "condition_execution_completed"}
|
|
@@ -642,7 +641,7 @@ async def stream_a2a_response(
|
|
|
642
641
|
metadata=metadata,
|
|
643
642
|
)
|
|
644
643
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
645
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
644
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
646
645
|
|
|
647
646
|
# Send router events
|
|
648
647
|
elif isinstance(event, RouterExecutionStartedEvent):
|
|
@@ -660,7 +659,7 @@ async def stream_a2a_response(
|
|
|
660
659
|
metadata=metadata,
|
|
661
660
|
)
|
|
662
661
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
663
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
662
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
664
663
|
|
|
665
664
|
elif isinstance(event, RouterExecutionCompletedEvent):
|
|
666
665
|
metadata = {"agno_event_type": "router_execution_completed"}
|
|
@@ -679,7 +678,7 @@ async def stream_a2a_response(
|
|
|
679
678
|
metadata=metadata,
|
|
680
679
|
)
|
|
681
680
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
682
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
681
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
683
682
|
|
|
684
683
|
# Send steps events
|
|
685
684
|
elif isinstance(event, StepsExecutionStartedEvent):
|
|
@@ -697,7 +696,7 @@ async def stream_a2a_response(
|
|
|
697
696
|
metadata=metadata,
|
|
698
697
|
)
|
|
699
698
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
700
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
699
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
701
700
|
|
|
702
701
|
elif isinstance(event, StepsExecutionCompletedEvent):
|
|
703
702
|
metadata = {"agno_event_type": "steps_execution_completed"}
|
|
@@ -716,7 +715,7 @@ async def stream_a2a_response(
|
|
|
716
715
|
metadata=metadata,
|
|
717
716
|
)
|
|
718
717
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=status_event)
|
|
719
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
718
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
720
719
|
|
|
721
720
|
# Capture completion event for final task construction
|
|
722
721
|
elif isinstance(event, (RunCompletedEvent, TeamRunCompletedEvent, WorkflowCompletedEvent)):
|
|
@@ -748,7 +747,7 @@ async def stream_a2a_response(
|
|
|
748
747
|
final=True,
|
|
749
748
|
)
|
|
750
749
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=final_status_event)
|
|
751
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
750
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
752
751
|
|
|
753
752
|
# 4. Send final task
|
|
754
753
|
# Handle cancelled case
|
|
@@ -778,7 +777,7 @@ async def stream_a2a_response(
|
|
|
778
777
|
history=[final_message],
|
|
779
778
|
)
|
|
780
779
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
|
|
781
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
780
|
+
yield f"event: Task\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
782
781
|
return
|
|
783
782
|
|
|
784
783
|
# Build from completion_event if available, otherwise use accumulated content
|
|
@@ -846,8 +845,8 @@ async def stream_a2a_response(
|
|
|
846
845
|
|
|
847
846
|
# Handle all other data as Message metadata
|
|
848
847
|
final_metadata: Dict[str, Any] = {}
|
|
849
|
-
if hasattr(completion_event, "metrics") and completion_event.metrics:
|
|
850
|
-
final_metadata["metrics"] = completion_event.metrics.
|
|
848
|
+
if hasattr(completion_event, "metrics") and completion_event.metrics: # type: ignore
|
|
849
|
+
final_metadata["metrics"] = completion_event.metrics.to_dict() # type: ignore
|
|
851
850
|
if hasattr(completion_event, "metadata") and completion_event.metadata:
|
|
852
851
|
final_metadata.update(completion_event.metadata)
|
|
853
852
|
|
|
@@ -880,7 +879,7 @@ async def stream_a2a_response(
|
|
|
880
879
|
artifacts=artifacts if artifacts else None,
|
|
881
880
|
)
|
|
882
881
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=task)
|
|
883
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
882
|
+
yield f"event: Task\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
884
883
|
|
|
885
884
|
|
|
886
885
|
async def stream_a2a_response_with_error_handling(
|
|
@@ -904,7 +903,7 @@ async def stream_a2a_response_with_error_handling(
|
|
|
904
903
|
final=True,
|
|
905
904
|
)
|
|
906
905
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_status_event)
|
|
907
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
906
|
+
yield f"event: TaskStatusUpdateEvent\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
|
908
907
|
|
|
909
908
|
# Send failed Task
|
|
910
909
|
error_message = A2AMessage(
|
|
@@ -921,4 +920,4 @@ async def stream_a2a_response_with_error_handling(
|
|
|
921
920
|
)
|
|
922
921
|
|
|
923
922
|
response = SendStreamingMessageSuccessResponse(id=request_id, result=failed_task)
|
|
924
|
-
yield json.dumps(response.model_dump(exclude_none=True))
|
|
923
|
+
yield f"event: Task\ndata: {json.dumps(response.model_dump(exclude_none=True))}\n\n"
|
agno/os/middleware/jwt.py
CHANGED
|
@@ -389,8 +389,9 @@ class JWTMiddleware(BaseHTTPMiddleware):
|
|
|
389
389
|
or JWT_JWKS env var for inline JWKS JSON content.
|
|
390
390
|
secret_key: (deprecated) Use verification_keys instead. If provided, will be added to verification_keys.
|
|
391
391
|
algorithm: JWT algorithm (default: RS256). Common options: RS256 (asymmetric), HS256 (symmetric).
|
|
392
|
-
validate: Whether to validate the JWT
|
|
393
|
-
without signature verification and no verification key is required.
|
|
392
|
+
validate: Whether to validate the JWT signature (default: True). If False, tokens are decoded
|
|
393
|
+
without signature verification and no verification key is required. Useful when
|
|
394
|
+
JWT verification is handled upstream (API Gateway, etc.).
|
|
394
395
|
authorization: Whether to add authorization checks to the request (i.e. validation of scopes)
|
|
395
396
|
token_source: Where to extract JWT token from (header, cookie, or both)
|
|
396
397
|
token_header_key: Header key for Authorization (default: "Authorization")
|
|
@@ -642,9 +643,8 @@ class JWTMiddleware(BaseHTTPMiddleware):
|
|
|
642
643
|
# Extract JWT token
|
|
643
644
|
token = self._extract_token(request)
|
|
644
645
|
if not token:
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
return self._create_error_response(401, error_msg, origin, cors_allowed_origins)
|
|
646
|
+
error_msg = self._get_missing_token_error_message()
|
|
647
|
+
return self._create_error_response(401, error_msg, origin, cors_allowed_origins)
|
|
648
648
|
|
|
649
649
|
try:
|
|
650
650
|
# Validate token and extract claims (with audience verification if configured)
|
agno/os/routers/agents/schema.py
CHANGED
|
@@ -215,11 +215,24 @@ class AgentResponse(BaseModel):
|
|
|
215
215
|
"build_user_context": agent.build_user_context,
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
# Handle output_schema name for both Pydantic models and JSON schemas
|
|
219
|
+
output_schema_name = None
|
|
220
|
+
if agent.output_schema is not None:
|
|
221
|
+
if isinstance(agent.output_schema, dict):
|
|
222
|
+
if "json_schema" in agent.output_schema:
|
|
223
|
+
output_schema_name = agent.output_schema["json_schema"].get("name", "JSONSchema")
|
|
224
|
+
elif "schema" in agent.output_schema and isinstance(agent.output_schema["schema"], dict):
|
|
225
|
+
output_schema_name = agent.output_schema["schema"].get("title", "JSONSchema")
|
|
226
|
+
else:
|
|
227
|
+
output_schema_name = agent.output_schema.get("title", "JSONSchema")
|
|
228
|
+
elif hasattr(agent.output_schema, "__name__"):
|
|
229
|
+
output_schema_name = agent.output_schema.__name__
|
|
230
|
+
|
|
218
231
|
response_settings_info: Dict[str, Any] = {
|
|
219
232
|
"retries": agent.retries,
|
|
220
233
|
"delay_between_retries": agent.delay_between_retries,
|
|
221
234
|
"exponential_backoff": agent.exponential_backoff,
|
|
222
|
-
"output_schema_name":
|
|
235
|
+
"output_schema_name": output_schema_name,
|
|
223
236
|
"parser_model_prompt": agent.parser_model_prompt,
|
|
224
237
|
"parse_response": agent.parse_response,
|
|
225
238
|
"structured_outputs": agent.structured_outputs,
|
agno/os/routers/teams/schema.py
CHANGED
|
@@ -197,8 +197,21 @@ class TeamResponse(BaseModel):
|
|
|
197
197
|
"resolve_in_context": team.resolve_in_context,
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
# Handle output_schema name for both Pydantic models and JSON schemas
|
|
201
|
+
output_schema_name = None
|
|
202
|
+
if team.output_schema is not None:
|
|
203
|
+
if isinstance(team.output_schema, dict):
|
|
204
|
+
if "json_schema" in team.output_schema:
|
|
205
|
+
output_schema_name = team.output_schema["json_schema"].get("name", "JSONSchema")
|
|
206
|
+
elif "schema" in team.output_schema and isinstance(team.output_schema["schema"], dict):
|
|
207
|
+
output_schema_name = team.output_schema["schema"].get("title", "JSONSchema")
|
|
208
|
+
else:
|
|
209
|
+
output_schema_name = team.output_schema.get("title", "JSONSchema")
|
|
210
|
+
elif hasattr(team.output_schema, "__name__"):
|
|
211
|
+
output_schema_name = team.output_schema.__name__
|
|
212
|
+
|
|
200
213
|
response_settings_info: Dict[str, Any] = {
|
|
201
|
-
"output_schema_name":
|
|
214
|
+
"output_schema_name": output_schema_name,
|
|
202
215
|
"parser_model_prompt": team.parser_model_prompt,
|
|
203
216
|
"parse_response": team.parse_response,
|
|
204
217
|
"use_json_mode": team.use_json_mode,
|
agno/os/utils.py
CHANGED
|
@@ -26,12 +26,21 @@ from agno.workflow.workflow import Workflow
|
|
|
26
26
|
|
|
27
27
|
async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[str, Any]:
|
|
28
28
|
"""Given a Request and an endpoint function, return a dictionary with all extra form data fields.
|
|
29
|
+
|
|
29
30
|
Args:
|
|
30
31
|
request: The FastAPI Request object
|
|
31
32
|
endpoint_func: The function exposing the endpoint that received the request
|
|
32
33
|
|
|
34
|
+
Supported form parameters:
|
|
35
|
+
- session_state: JSON string of session state dict
|
|
36
|
+
- dependencies: JSON string of dependencies dict
|
|
37
|
+
- metadata: JSON string of metadata dict
|
|
38
|
+
- knowledge_filters: JSON string of knowledge filters
|
|
39
|
+
- output_schema: JSON schema string (converted to Pydantic model by default)
|
|
40
|
+
- use_json_schema: If "true", keeps output_schema as dict instead of converting to Pydantic model
|
|
41
|
+
|
|
33
42
|
Returns:
|
|
34
|
-
A dictionary of kwargs
|
|
43
|
+
A dictionary of kwargs to pass to Agent/Team run methods
|
|
35
44
|
"""
|
|
36
45
|
import inspect
|
|
37
46
|
|
|
@@ -101,15 +110,25 @@ async def get_request_kwargs(request: Request, endpoint_func: Callable) -> Dict[
|
|
|
101
110
|
kwargs.pop("knowledge_filters")
|
|
102
111
|
log_warning(f"Invalid FilterExpr in knowledge_filters: {e}")
|
|
103
112
|
|
|
104
|
-
# Handle output_schema - convert JSON schema to
|
|
113
|
+
# Handle output_schema - convert JSON schema to Pydantic model or keep as dict
|
|
114
|
+
# use_json_schema is a control flag consumed here (not passed to Agent/Team)
|
|
115
|
+
# When true, output_schema stays as dict for direct JSON output
|
|
116
|
+
use_json_schema = kwargs.pop("use_json_schema", False)
|
|
117
|
+
if isinstance(use_json_schema, str):
|
|
118
|
+
use_json_schema = use_json_schema.lower() == "true"
|
|
119
|
+
|
|
105
120
|
if output_schema := kwargs.get("output_schema"):
|
|
106
121
|
try:
|
|
107
122
|
if isinstance(output_schema, str):
|
|
108
|
-
from agno.os.utils import json_schema_to_pydantic_model
|
|
109
|
-
|
|
110
123
|
schema_dict = json.loads(output_schema)
|
|
111
|
-
|
|
112
|
-
|
|
124
|
+
|
|
125
|
+
if use_json_schema:
|
|
126
|
+
# Keep as dict schema for direct JSON output
|
|
127
|
+
kwargs["output_schema"] = schema_dict
|
|
128
|
+
else:
|
|
129
|
+
# Convert to Pydantic model (default behavior)
|
|
130
|
+
dynamic_model = json_schema_to_pydantic_model(schema_dict)
|
|
131
|
+
kwargs["output_schema"] = dynamic_model
|
|
113
132
|
except json.JSONDecodeError:
|
|
114
133
|
kwargs.pop("output_schema")
|
|
115
134
|
log_warning(f"Invalid output_schema JSON: {output_schema}")
|
|
@@ -281,56 +300,45 @@ def get_session_name(session: Dict[str, Any]) -> str:
|
|
|
281
300
|
if session_data is not None and session_data.get("session_name") is not None:
|
|
282
301
|
return session_data["session_name"]
|
|
283
302
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
runs = session.get("runs", []) or []
|
|
287
|
-
|
|
288
|
-
# For teams, identify the first Team run and avoid using the first member's run
|
|
289
|
-
if session.get("session_type") == "team":
|
|
290
|
-
run = None
|
|
291
|
-
for r in runs:
|
|
292
|
-
# If agent_id is not present, it's a team run
|
|
293
|
-
if not r.get("agent_id"):
|
|
294
|
-
run = r
|
|
295
|
-
break
|
|
296
|
-
|
|
297
|
-
# Fallback to first run if no team run found
|
|
298
|
-
if run is None and runs:
|
|
299
|
-
run = runs[0]
|
|
300
|
-
|
|
301
|
-
elif session.get("session_type") == "workflow":
|
|
302
|
-
try:
|
|
303
|
-
workflow_run = runs[0]
|
|
304
|
-
workflow_input = workflow_run.get("input")
|
|
305
|
-
if isinstance(workflow_input, str):
|
|
306
|
-
return workflow_input
|
|
307
|
-
elif isinstance(workflow_input, dict):
|
|
308
|
-
try:
|
|
309
|
-
import json
|
|
310
|
-
|
|
311
|
-
return json.dumps(workflow_input)
|
|
312
|
-
except (TypeError, ValueError):
|
|
313
|
-
pass
|
|
314
|
-
|
|
315
|
-
workflow_name = session.get("workflow_data", {}).get("name")
|
|
316
|
-
return f"New {workflow_name} Session" if workflow_name else ""
|
|
317
|
-
except (KeyError, IndexError, TypeError):
|
|
318
|
-
return ""
|
|
319
|
-
|
|
320
|
-
# For agents, use the first run
|
|
321
|
-
else:
|
|
322
|
-
run = runs[0] if runs else None
|
|
303
|
+
runs = session.get("runs", []) or []
|
|
304
|
+
session_type = session.get("session_type")
|
|
323
305
|
|
|
324
|
-
|
|
306
|
+
# Handle workflows separately
|
|
307
|
+
if session_type == "workflow":
|
|
308
|
+
if not runs:
|
|
325
309
|
return ""
|
|
310
|
+
workflow_run = runs[0]
|
|
311
|
+
workflow_input = workflow_run.get("input")
|
|
312
|
+
if isinstance(workflow_input, str):
|
|
313
|
+
return workflow_input
|
|
314
|
+
elif isinstance(workflow_input, dict):
|
|
315
|
+
try:
|
|
316
|
+
return json.dumps(workflow_input)
|
|
317
|
+
except (TypeError, ValueError):
|
|
318
|
+
pass
|
|
319
|
+
workflow_name = session.get("workflow_data", {}).get("name")
|
|
320
|
+
return f"New {workflow_name} Session" if workflow_name else ""
|
|
321
|
+
|
|
322
|
+
# For team, filter to team runs (runs without agent_id); for agents, use all runs
|
|
323
|
+
if session_type == "team":
|
|
324
|
+
runs_to_check = [r for r in runs if not r.get("agent_id")]
|
|
325
|
+
else:
|
|
326
|
+
runs_to_check = runs
|
|
326
327
|
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
# Find the first user message across runs
|
|
329
|
+
for r in runs_to_check:
|
|
330
|
+
if r is None:
|
|
331
|
+
continue
|
|
332
|
+
run_dict = r if isinstance(r, dict) else r.to_dict()
|
|
333
|
+
|
|
334
|
+
for message in run_dict.get("messages") or []:
|
|
335
|
+
if message.get("role") == "user" and message.get("content"):
|
|
336
|
+
return message["content"]
|
|
337
|
+
|
|
338
|
+
run_input = r.get("input")
|
|
339
|
+
if run_input is not None:
|
|
340
|
+
return run_input.get("input_content")
|
|
329
341
|
|
|
330
|
-
if run and run.get("messages"):
|
|
331
|
-
for message in run["messages"]:
|
|
332
|
-
if message["role"] == "user":
|
|
333
|
-
return message["content"]
|
|
334
342
|
return ""
|
|
335
343
|
|
|
336
344
|
|
|
@@ -862,7 +870,7 @@ def _get_python_type_from_json_schema(field_schema: Dict[str, Any], field_name:
|
|
|
862
870
|
# Unknown or unspecified type - fallback to Any
|
|
863
871
|
if json_type:
|
|
864
872
|
logger.warning(f"Unknown JSON schema type '{json_type}' for field '{field_name}', using Any")
|
|
865
|
-
return Any
|
|
873
|
+
return Any # type: ignore
|
|
866
874
|
|
|
867
875
|
|
|
868
876
|
def json_schema_to_pydantic_model(schema: Dict[str, Any]) -> Type[BaseModel]:
|
agno/reasoning/anthropic.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import List, Optional
|
|
3
|
+
from typing import AsyncIterator, Iterator, List, Optional, Tuple
|
|
4
4
|
|
|
5
5
|
from agno.models.base import Model
|
|
6
6
|
from agno.models.message import Message
|
|
@@ -51,6 +51,48 @@ def get_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -
|
|
|
51
51
|
)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
+
def get_anthropic_reasoning_stream(
|
|
55
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
56
|
+
messages: List[Message],
|
|
57
|
+
) -> Iterator[Tuple[Optional[str], Optional[Message]]]:
|
|
58
|
+
"""
|
|
59
|
+
Stream reasoning content from Anthropic Claude model.
|
|
60
|
+
|
|
61
|
+
Yields:
|
|
62
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
63
|
+
- During streaming: (reasoning_content_delta, None)
|
|
64
|
+
- At the end: (None, final_message)
|
|
65
|
+
"""
|
|
66
|
+
from agno.run.agent import RunEvent
|
|
67
|
+
|
|
68
|
+
reasoning_content: str = ""
|
|
69
|
+
redacted_reasoning_content: Optional[str] = None
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
for event in reasoning_agent.run(input=messages, stream=True, stream_intermediate_steps=True):
|
|
73
|
+
if hasattr(event, "event"):
|
|
74
|
+
if event.event == RunEvent.run_content:
|
|
75
|
+
# Stream reasoning content as it arrives
|
|
76
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
77
|
+
reasoning_content += event.reasoning_content
|
|
78
|
+
yield (event.reasoning_content, None)
|
|
79
|
+
elif event.event == RunEvent.run_completed:
|
|
80
|
+
pass
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f"Reasoning error: {e}")
|
|
83
|
+
return
|
|
84
|
+
|
|
85
|
+
# Yield final message
|
|
86
|
+
if reasoning_content:
|
|
87
|
+
final_message = Message(
|
|
88
|
+
role="assistant",
|
|
89
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
90
|
+
reasoning_content=reasoning_content,
|
|
91
|
+
redacted_reasoning_content=redacted_reasoning_content,
|
|
92
|
+
)
|
|
93
|
+
yield (None, final_message)
|
|
94
|
+
|
|
95
|
+
|
|
54
96
|
async def aget_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Message]) -> Optional[Message]: # type: ignore # noqa: F821
|
|
55
97
|
"""Get reasoning from an Anthropic Claude model asynchronously."""
|
|
56
98
|
from agno.run.agent import RunOutput
|
|
@@ -78,3 +120,45 @@ async def aget_anthropic_reasoning(reasoning_agent: "Agent", messages: List[Mess
|
|
|
78
120
|
reasoning_content=reasoning_content,
|
|
79
121
|
redacted_reasoning_content=redacted_reasoning_content,
|
|
80
122
|
)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
async def aget_anthropic_reasoning_stream(
|
|
126
|
+
reasoning_agent: "Agent", # type: ignore # noqa: F821
|
|
127
|
+
messages: List[Message],
|
|
128
|
+
) -> AsyncIterator[Tuple[Optional[str], Optional[Message]]]:
|
|
129
|
+
"""
|
|
130
|
+
Stream reasoning content from Anthropic Claude model asynchronously.
|
|
131
|
+
|
|
132
|
+
Yields:
|
|
133
|
+
Tuple of (reasoning_content_delta, final_message)
|
|
134
|
+
- During streaming: (reasoning_content_delta, None)
|
|
135
|
+
- At the end: (None, final_message)
|
|
136
|
+
"""
|
|
137
|
+
from agno.run.agent import RunEvent
|
|
138
|
+
|
|
139
|
+
reasoning_content: str = ""
|
|
140
|
+
redacted_reasoning_content: Optional[str] = None
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
async for event in reasoning_agent.arun(input=messages, stream=True, stream_intermediate_steps=True):
|
|
144
|
+
if hasattr(event, "event"):
|
|
145
|
+
if event.event == RunEvent.run_content:
|
|
146
|
+
# Stream reasoning content as it arrives
|
|
147
|
+
if hasattr(event, "reasoning_content") and event.reasoning_content:
|
|
148
|
+
reasoning_content += event.reasoning_content
|
|
149
|
+
yield (event.reasoning_content, None)
|
|
150
|
+
elif event.event == RunEvent.run_completed:
|
|
151
|
+
pass
|
|
152
|
+
except Exception as e:
|
|
153
|
+
logger.warning(f"Reasoning error: {e}")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Yield final message
|
|
157
|
+
if reasoning_content:
|
|
158
|
+
final_message = Message(
|
|
159
|
+
role="assistant",
|
|
160
|
+
content=f"<thinking>\n{reasoning_content}\n</thinking>",
|
|
161
|
+
reasoning_content=reasoning_content,
|
|
162
|
+
redacted_reasoning_content=redacted_reasoning_content,
|
|
163
|
+
)
|
|
164
|
+
yield (None, final_message)
|