agno 2.0.0a1__py3-none-any.whl → 2.0.0rc2__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.
Files changed (79) hide show
  1. agno/agent/agent.py +416 -41
  2. agno/api/agent.py +2 -2
  3. agno/api/evals.py +2 -2
  4. agno/api/os.py +1 -1
  5. agno/api/settings.py +2 -2
  6. agno/api/team.py +2 -2
  7. agno/db/dynamo/dynamo.py +0 -6
  8. agno/db/firestore/firestore.py +0 -6
  9. agno/db/in_memory/in_memory_db.py +0 -6
  10. agno/db/json/json_db.py +0 -6
  11. agno/db/mongo/mongo.py +8 -9
  12. agno/db/mysql/utils.py +0 -1
  13. agno/db/postgres/postgres.py +0 -10
  14. agno/db/postgres/utils.py +0 -1
  15. agno/db/redis/redis.py +0 -4
  16. agno/db/singlestore/singlestore.py +0 -10
  17. agno/db/singlestore/utils.py +0 -1
  18. agno/db/sqlite/sqlite.py +0 -4
  19. agno/db/sqlite/utils.py +0 -1
  20. agno/eval/accuracy.py +12 -5
  21. agno/integrations/discord/client.py +5 -1
  22. agno/knowledge/chunking/strategy.py +14 -14
  23. agno/knowledge/embedder/aws_bedrock.py +2 -2
  24. agno/knowledge/knowledge.py +156 -120
  25. agno/knowledge/reader/arxiv_reader.py +5 -5
  26. agno/knowledge/reader/csv_reader.py +6 -77
  27. agno/knowledge/reader/docx_reader.py +5 -5
  28. agno/knowledge/reader/firecrawl_reader.py +5 -5
  29. agno/knowledge/reader/json_reader.py +5 -5
  30. agno/knowledge/reader/markdown_reader.py +31 -9
  31. agno/knowledge/reader/pdf_reader.py +10 -123
  32. agno/knowledge/reader/reader_factory.py +65 -72
  33. agno/knowledge/reader/s3_reader.py +44 -114
  34. agno/knowledge/reader/text_reader.py +5 -5
  35. agno/knowledge/reader/url_reader.py +75 -31
  36. agno/knowledge/reader/web_search_reader.py +6 -29
  37. agno/knowledge/reader/website_reader.py +5 -5
  38. agno/knowledge/reader/wikipedia_reader.py +5 -5
  39. agno/knowledge/reader/youtube_reader.py +6 -6
  40. agno/knowledge/utils.py +10 -10
  41. agno/models/anthropic/claude.py +2 -49
  42. agno/models/aws/bedrock.py +3 -7
  43. agno/models/base.py +37 -6
  44. agno/models/message.py +7 -6
  45. agno/os/app.py +168 -64
  46. agno/os/interfaces/agui/agui.py +1 -1
  47. agno/os/interfaces/agui/utils.py +16 -9
  48. agno/os/interfaces/slack/slack.py +2 -3
  49. agno/os/interfaces/whatsapp/whatsapp.py +2 -3
  50. agno/os/mcp.py +235 -0
  51. agno/os/router.py +576 -19
  52. agno/os/routers/evals/evals.py +201 -12
  53. agno/os/routers/knowledge/knowledge.py +455 -18
  54. agno/os/routers/memory/memory.py +260 -29
  55. agno/os/routers/metrics/metrics.py +127 -7
  56. agno/os/routers/session/session.py +398 -25
  57. agno/os/schema.py +55 -2
  58. agno/os/settings.py +0 -1
  59. agno/run/agent.py +96 -2
  60. agno/run/cancel.py +0 -2
  61. agno/run/team.py +93 -2
  62. agno/run/workflow.py +25 -12
  63. agno/team/team.py +863 -1053
  64. agno/tools/function.py +65 -7
  65. agno/tools/linear.py +1 -1
  66. agno/tools/mcp.py +1 -2
  67. agno/utils/gemini.py +31 -1
  68. agno/utils/log.py +52 -2
  69. agno/utils/mcp.py +55 -3
  70. agno/utils/models/claude.py +41 -0
  71. agno/utils/print_response/team.py +177 -73
  72. agno/utils/streamlit.py +481 -0
  73. agno/workflow/workflow.py +17 -1
  74. {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/METADATA +1 -1
  75. {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/RECORD +78 -77
  76. agno/knowledge/reader/gcs_reader.py +0 -67
  77. {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/WHEEL +0 -0
  78. {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/licenses/LICENSE +0 -0
  79. {agno-2.0.0a1.dist-info → agno-2.0.0rc2.dist-info}/top_level.txt +0 -0
agno/os/router.py CHANGED
@@ -21,11 +21,17 @@ from agno.os.auth import get_authentication_dependency
21
21
  from agno.os.schema import (
22
22
  AgentResponse,
23
23
  AgentSummaryResponse,
24
+ BadRequestResponse,
24
25
  ConfigResponse,
26
+ HealthResponse,
25
27
  InterfaceResponse,
28
+ InternalServerErrorResponse,
26
29
  Model,
30
+ NotFoundResponse,
27
31
  TeamResponse,
28
32
  TeamSummaryResponse,
33
+ UnauthenticatedResponse,
34
+ ValidationErrorResponse,
29
35
  WorkflowResponse,
30
36
  WorkflowSummaryResponse,
31
37
  )
@@ -307,19 +313,127 @@ def get_base_router(
307
313
  os: "AgentOS",
308
314
  settings: AgnoAPISettings = AgnoAPISettings(),
309
315
  ) -> APIRouter:
310
- router = APIRouter(dependencies=[Depends(get_authentication_dependency(settings))])
316
+ """
317
+ Create the base FastAPI router with comprehensive OpenAPI documentation.
318
+
319
+ This router provides endpoints for:
320
+ - Core system operations (health, config, models)
321
+ - Agent management and execution
322
+ - Team collaboration and coordination
323
+ - Workflow automation and orchestration
324
+ - Real-time WebSocket communications
325
+
326
+ All endpoints include detailed documentation, examples, and proper error handling.
327
+ """
328
+ router = APIRouter(
329
+ dependencies=[Depends(get_authentication_dependency(settings))],
330
+ responses={
331
+ 400: {"description": "Bad Request", "model": BadRequestResponse},
332
+ 401: {"description": "Unauthorized", "model": UnauthenticatedResponse},
333
+ 404: {"description": "Not Found", "model": NotFoundResponse},
334
+ 422: {"description": "Validation Error", "model": ValidationErrorResponse},
335
+ 500: {"description": "Internal Server Error", "model": InternalServerErrorResponse},
336
+ },
337
+ )
311
338
 
312
339
  # -- Main Routes ---
313
340
 
314
- @router.get("/health", tags=["Core"])
315
- async def health_check():
316
- return JSONResponse(content={"status": "ok"})
341
+ @router.get(
342
+ "/health",
343
+ tags=["Core"],
344
+ operation_id="health_check",
345
+ summary="Health Check",
346
+ description="Check the health status of the AgentOS API. Returns a simple status indicator.",
347
+ response_model=HealthResponse,
348
+ responses={
349
+ 200: {
350
+ "description": "API is healthy and operational",
351
+ "content": {"application/json": {"example": {"status": "ok"}}},
352
+ }
353
+ },
354
+ )
355
+ async def health_check() -> HealthResponse:
356
+ return HealthResponse(status="ok")
317
357
 
318
358
  @router.get(
319
359
  "/config",
320
360
  response_model=ConfigResponse,
321
361
  response_model_exclude_none=True,
322
362
  tags=["Core"],
363
+ operation_id="get_config",
364
+ summary="Get OS Configuration",
365
+ description=(
366
+ "Retrieve the complete configuration of the AgentOS instance, including:\n\n"
367
+ "- Available models and databases\n"
368
+ "- Registered agents, teams, and workflows\n"
369
+ "- Chat, session, memory, knowledge, and evaluation configurations\n"
370
+ "- Available interfaces and their routes"
371
+ ),
372
+ responses={
373
+ 200: {
374
+ "description": "OS configuration retrieved successfully",
375
+ "content": {
376
+ "application/json": {
377
+ "example": {
378
+ "os_id": "demo",
379
+ "description": "Example AgentOS configuration",
380
+ "available_models": [],
381
+ "databases": ["9c884dc4-9066-448c-9074-ef49ec7eb73c"],
382
+ "session": {
383
+ "dbs": [
384
+ {
385
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
386
+ "domain_config": {"display_name": "Sessions"},
387
+ }
388
+ ]
389
+ },
390
+ "metrics": {
391
+ "dbs": [
392
+ {
393
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
394
+ "domain_config": {"display_name": "Metrics"},
395
+ }
396
+ ]
397
+ },
398
+ "memory": {
399
+ "dbs": [
400
+ {
401
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
402
+ "domain_config": {"display_name": "Memory"},
403
+ }
404
+ ]
405
+ },
406
+ "knowledge": {
407
+ "dbs": [
408
+ {
409
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
410
+ "domain_config": {"display_name": "Knowledge"},
411
+ }
412
+ ]
413
+ },
414
+ "evals": {
415
+ "dbs": [
416
+ {
417
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
418
+ "domain_config": {"display_name": "Evals"},
419
+ }
420
+ ]
421
+ },
422
+ "agents": [
423
+ {
424
+ "id": "main-agent",
425
+ "name": "Main Agent",
426
+ "db_id": "9c884dc4-9066-448c-9074-ef49ec7eb73c",
427
+ }
428
+ ],
429
+ "teams": [],
430
+ "workflows": [],
431
+ "interfaces": [],
432
+ }
433
+ }
434
+ },
435
+ }
436
+ },
323
437
  )
324
438
  async def config() -> ConfigResponse:
325
439
  return ConfigResponse(
@@ -347,8 +461,27 @@ def get_base_router(
347
461
  response_model=List[Model],
348
462
  response_model_exclude_none=True,
349
463
  tags=["Core"],
464
+ operation_id="get_models",
465
+ summary="Get Available Models",
466
+ description=(
467
+ "Retrieve a list of all unique models currently used by agents and teams in this OS instance. "
468
+ "This includes the model ID and provider information for each model."
469
+ ),
470
+ responses={
471
+ 200: {
472
+ "description": "List of models retrieved successfully",
473
+ "content": {
474
+ "application/json": {
475
+ "example": [
476
+ {"id": "gpt-4", "provider": "openai"},
477
+ {"id": "claude-3-sonnet", "provider": "anthropic"},
478
+ ]
479
+ }
480
+ },
481
+ }
482
+ },
350
483
  )
351
- async def get_models():
484
+ async def get_models() -> List[Model]:
352
485
  """Return the list of all models used by agents and teams in the contextual OS"""
353
486
  all_components: List[Union[Agent, Team]] = []
354
487
  if os.agents:
@@ -368,7 +501,41 @@ def get_base_router(
368
501
 
369
502
  # -- Agent routes ---
370
503
 
371
- @router.post("/agents/{agent_id}/runs", tags=["Agents"])
504
+ @router.post(
505
+ "/agents/{agent_id}/runs",
506
+ tags=["Agents"],
507
+ operation_id="create_agent_run",
508
+ response_model_exclude_none=True,
509
+ summary="Create Agent Run",
510
+ description=(
511
+ "Execute an agent with a message and optional media files. Supports both streaming and non-streaming responses.\n\n"
512
+ "**Features:**\n"
513
+ "- Text message input with optional session management\n"
514
+ "- Multi-media support: images (PNG, JPEG, WebP), audio (WAV, MP3), video (MP4, WebM, etc.)\n"
515
+ "- Document processing: PDF, CSV, DOCX, TXT, JSON\n"
516
+ "- Real-time streaming responses with Server-Sent Events (SSE)\n"
517
+ "- User and session context preservation\n\n"
518
+ "**Streaming Response:**\n"
519
+ "When `stream=true`, returns SSE events with `event` and `data` fields."
520
+ ),
521
+ responses={
522
+ 200: {
523
+ "description": "Agent run executed successfully",
524
+ "content": {
525
+ "text/event-stream": {
526
+ "examples": {
527
+ "event_strea": {
528
+ "summary": "Example event stream response",
529
+ "value": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n',
530
+ }
531
+ }
532
+ },
533
+ },
534
+ },
535
+ 400: {"description": "Invalid request or unsupported file type", "model": BadRequestResponse},
536
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
537
+ },
538
+ )
372
539
  async def create_agent_run(
373
540
  agent_id: str,
374
541
  message: str = Form(...),
@@ -475,6 +642,18 @@ def get_base_router(
475
642
  @router.post(
476
643
  "/agents/{agent_id}/runs/{run_id}/cancel",
477
644
  tags=["Agents"],
645
+ operation_id="cancel_agent_run",
646
+ response_model_exclude_none=True,
647
+ summary="Cancel Agent Run",
648
+ description=(
649
+ "Cancel a currently executing agent run. This will attempt to stop the agent's execution gracefully.\n\n"
650
+ "**Note:** Cancellation may not be immediate for all operations."
651
+ ),
652
+ responses={
653
+ 200: {},
654
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
655
+ 500: {"description": "Failed to cancel run", "model": InternalServerErrorResponse},
656
+ },
478
657
  )
479
658
  async def cancel_agent_run(
480
659
  agent_id: str,
@@ -492,6 +671,29 @@ def get_base_router(
492
671
  @router.post(
493
672
  "/agents/{agent_id}/runs/{run_id}/continue",
494
673
  tags=["Agents"],
674
+ operation_id="continue_agent_run",
675
+ response_model_exclude_none=True,
676
+ summary="Continue Agent Run",
677
+ description=(
678
+ "Continue a paused or incomplete agent run with updated tool results.\n\n"
679
+ "**Use Cases:**\n"
680
+ "- Resume execution after tool approval/rejection\n"
681
+ "- Provide manual tool execution results\n\n"
682
+ "**Tools Parameter:**\n"
683
+ "JSON string containing array of tool execution objects with results."
684
+ ),
685
+ responses={
686
+ 200: {
687
+ "description": "Agent run continued successfully",
688
+ "content": {
689
+ "text/event-stream": {
690
+ "example": 'event: RunContent\ndata: {"created_at": 1757348314, "run_id": "123..."}\n\n'
691
+ },
692
+ },
693
+ },
694
+ 400: {"description": "Invalid JSON in tools field or invalid tool structure", "model": BadRequestResponse},
695
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
696
+ },
495
697
  )
496
698
  async def continue_agent_run(
497
699
  agent_id: str,
@@ -555,8 +757,40 @@ def get_base_router(
555
757
  response_model=List[AgentResponse],
556
758
  response_model_exclude_none=True,
557
759
  tags=["Agents"],
760
+ operation_id="get_agents",
761
+ summary="List All Agents",
762
+ description=(
763
+ "Retrieve a comprehensive list of all agents configured in this OS instance.\n\n"
764
+ "**Returns:**\n"
765
+ "- Agent metadata (ID, name, description)\n"
766
+ "- Model configuration and capabilities\n"
767
+ "- Available tools and their configurations\n"
768
+ "- Session, knowledge, memory, and reasoning settings\n"
769
+ "- Only meaningful (non-default) configurations are included"
770
+ ),
771
+ responses={
772
+ 200: {
773
+ "description": "List of agents retrieved successfully",
774
+ "content": {
775
+ "application/json": {
776
+ "example": [
777
+ {
778
+ "id": "main-agent",
779
+ "name": "Main Agent",
780
+ "db_id": "c6bf0644-feb8-4930-a305-380dae5ad6aa",
781
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
782
+ "tools": None,
783
+ "sessions": {"session_table": "agno_sessions"},
784
+ "knowledge": {"knowledge_table": "main_knowledge"},
785
+ "system_message": {"markdown": True, "add_datetime_to_context": True},
786
+ }
787
+ ]
788
+ }
789
+ },
790
+ }
791
+ },
558
792
  )
559
- async def get_agents():
793
+ async def get_agents() -> List[AgentResponse]:
560
794
  """Return the list of all Agents present in the contextual OS"""
561
795
  if os.agents is None:
562
796
  return []
@@ -572,8 +806,40 @@ def get_base_router(
572
806
  response_model=AgentResponse,
573
807
  response_model_exclude_none=True,
574
808
  tags=["Agents"],
809
+ operation_id="get_agent",
810
+ summary="Get Agent Details",
811
+ description=(
812
+ "Retrieve detailed configuration and capabilities of a specific agent.\n\n"
813
+ "**Returns comprehensive agent information including:**\n"
814
+ "- Model configuration and provider details\n"
815
+ "- Complete tool inventory and configurations\n"
816
+ "- Session management settings\n"
817
+ "- Knowledge base and memory configurations\n"
818
+ "- Reasoning capabilities and settings\n"
819
+ "- System prompts and response formatting options"
820
+ ),
821
+ responses={
822
+ 200: {
823
+ "description": "Agent details retrieved successfully",
824
+ "content": {
825
+ "application/json": {
826
+ "example": {
827
+ "id": "main-agent",
828
+ "name": "Main Agent",
829
+ "db_id": "9e064c70-6821-4840-a333-ce6230908a70",
830
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
831
+ "tools": None,
832
+ "sessions": {"session_table": "agno_sessions"},
833
+ "knowledge": {"knowledge_table": "main_knowledge"},
834
+ "system_message": {"markdown": True, "add_datetime_to_context": True},
835
+ }
836
+ }
837
+ },
838
+ },
839
+ 404: {"description": "Agent not found", "model": NotFoundResponse},
840
+ },
575
841
  )
576
- async def get_agent(agent_id: str):
842
+ async def get_agent(agent_id: str) -> AgentResponse:
577
843
  agent = get_agent_by_id(agent_id, os.agents)
578
844
  if agent is None:
579
845
  raise HTTPException(status_code=404, detail="Agent not found")
@@ -582,7 +848,36 @@ def get_base_router(
582
848
 
583
849
  # -- Team routes ---
584
850
 
585
- @router.post("/teams/{team_id}/runs", tags=["Teams"])
851
+ @router.post(
852
+ "/teams/{team_id}/runs",
853
+ tags=["Teams"],
854
+ operation_id="create_team_run",
855
+ response_model_exclude_none=True,
856
+ summary="Create Team Run",
857
+ description=(
858
+ "Execute a team collaboration with multiple agents working together on a task.\n\n"
859
+ "**Features:**\n"
860
+ "- Text message input with optional session management\n"
861
+ "- Multi-media support: images (PNG, JPEG, WebP), audio (WAV, MP3), video (MP4, WebM, etc.)\n"
862
+ "- Document processing: PDF, CSV, DOCX, TXT, JSON\n"
863
+ "- Real-time streaming responses with Server-Sent Events (SSE)\n"
864
+ "- User and session context preservation\n\n"
865
+ "**Streaming Response:**\n"
866
+ "When `stream=true`, returns SSE events with `event` and `data` fields."
867
+ ),
868
+ responses={
869
+ 200: {
870
+ "description": "Team run executed successfully",
871
+ "content": {
872
+ "text/event-stream": {
873
+ "example": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n'
874
+ },
875
+ },
876
+ },
877
+ 400: {"description": "Invalid request or unsupported file type", "model": BadRequestResponse},
878
+ 404: {"description": "Team not found", "model": NotFoundResponse},
879
+ },
880
+ )
586
881
  async def create_team_run(
587
882
  team_id: str,
588
883
  message: str = Form(...),
@@ -686,6 +981,18 @@ def get_base_router(
686
981
  @router.post(
687
982
  "/teams/{team_id}/runs/{run_id}/cancel",
688
983
  tags=["Teams"],
984
+ operation_id="cancel_team_run",
985
+ response_model_exclude_none=True,
986
+ summary="Cancel Team Run",
987
+ description=(
988
+ "Cancel a currently executing team run. This will attempt to stop the team's execution gracefully.\n\n"
989
+ "**Note:** Cancellation may not be immediate for all operations."
990
+ ),
991
+ responses={
992
+ 200: {},
993
+ 404: {"description": "Team not found", "model": NotFoundResponse},
994
+ 500: {"description": "Failed to cancel team run", "model": InternalServerErrorResponse},
995
+ },
689
996
  )
690
997
  async def cancel_team_run(
691
998
  team_id: str,
@@ -705,8 +1012,83 @@ def get_base_router(
705
1012
  response_model=List[TeamResponse],
706
1013
  response_model_exclude_none=True,
707
1014
  tags=["Teams"],
1015
+ operation_id="get_teams",
1016
+ summary="List All Teams",
1017
+ description=(
1018
+ "Retrieve a comprehensive list of all teams configured in this OS instance.\n\n"
1019
+ "**Returns team information including:**\n"
1020
+ "- Team metadata (ID, name, description, execution mode)\n"
1021
+ "- Model configuration for team coordination\n"
1022
+ "- Team member roster with roles and capabilities\n"
1023
+ "- Knowledge sharing and memory configurations"
1024
+ ),
1025
+ responses={
1026
+ 200: {
1027
+ "description": "List of teams retrieved successfully",
1028
+ "content": {
1029
+ "application/json": {
1030
+ "example": [
1031
+ {
1032
+ "team_id": "basic-team",
1033
+ "name": "Basic Team",
1034
+ "mode": "coordinate",
1035
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1036
+ "tools": [
1037
+ {
1038
+ "name": "transfer_task_to_member",
1039
+ "description": "Use this function to transfer a task to the selected team member.\nYou must provide a clear and concise description of the task the member should achieve AND the expected output.",
1040
+ "parameters": {
1041
+ "type": "object",
1042
+ "properties": {
1043
+ "member_id": {
1044
+ "type": "string",
1045
+ "description": "(str) The ID of the member to transfer the task to. Use only the ID of the member, not the ID of the team followed by the ID of the member.",
1046
+ },
1047
+ "task_description": {
1048
+ "type": "string",
1049
+ "description": "(str) A clear and concise description of the task the member should achieve.",
1050
+ },
1051
+ "expected_output": {
1052
+ "type": "string",
1053
+ "description": "(str) The expected output from the member (optional).",
1054
+ },
1055
+ },
1056
+ "additionalProperties": False,
1057
+ "required": ["member_id", "task_description"],
1058
+ },
1059
+ }
1060
+ ],
1061
+ "members": [
1062
+ {
1063
+ "agent_id": "basic-agent",
1064
+ "name": "Basic Agent",
1065
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI gpt-4o"},
1066
+ "memory": {
1067
+ "app_name": "Memory",
1068
+ "app_url": None,
1069
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1070
+ },
1071
+ "session_table": "agno_sessions",
1072
+ "memory_table": "agno_memories",
1073
+ }
1074
+ ],
1075
+ "enable_agentic_context": False,
1076
+ "memory": {
1077
+ "app_name": "agno_memories",
1078
+ "app_url": "/memory/1",
1079
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1080
+ },
1081
+ "async_mode": False,
1082
+ "session_table": "agno_sessions",
1083
+ "memory_table": "agno_memories",
1084
+ }
1085
+ ]
1086
+ }
1087
+ },
1088
+ }
1089
+ },
708
1090
  )
709
- async def get_teams():
1091
+ async def get_teams() -> List[TeamResponse]:
710
1092
  """Return the list of all Teams present in the contextual OS"""
711
1093
  if os.teams is None:
712
1094
  return []
@@ -722,8 +1104,86 @@ def get_base_router(
722
1104
  response_model=TeamResponse,
723
1105
  response_model_exclude_none=True,
724
1106
  tags=["Teams"],
1107
+ operation_id="get_team",
1108
+ summary="Get Team Details",
1109
+ description=("Retrieve detailed configuration and member information for a specific team."),
1110
+ responses={
1111
+ 200: {
1112
+ "description": "Team details retrieved successfully",
1113
+ "content": {
1114
+ "application/json": {
1115
+ "example": {
1116
+ "team_id": "basic-team",
1117
+ "name": "Basic Team",
1118
+ "description": None,
1119
+ "mode": "coordinate",
1120
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1121
+ "tools": [
1122
+ {
1123
+ "name": "transfer_task_to_member",
1124
+ "description": "Use this function to transfer a task to the selected team member.\nYou must provide a clear and concise description of the task the member should achieve AND the expected output.",
1125
+ "parameters": {
1126
+ "type": "object",
1127
+ "properties": {
1128
+ "member_id": {
1129
+ "type": "string",
1130
+ "description": "(str) The ID of the member to transfer the task to. Use only the ID of the member, not the ID of the team followed by the ID of the member.",
1131
+ },
1132
+ "task_description": {
1133
+ "type": "string",
1134
+ "description": "(str) A clear and concise description of the task the member should achieve.",
1135
+ },
1136
+ "expected_output": {
1137
+ "type": "string",
1138
+ "description": "(str) The expected output from the member (optional).",
1139
+ },
1140
+ },
1141
+ "additionalProperties": False,
1142
+ "required": ["member_id", "task_description"],
1143
+ },
1144
+ }
1145
+ ],
1146
+ "instructions": None,
1147
+ "members": [
1148
+ {
1149
+ "agent_id": "basic-agent",
1150
+ "name": "Basic Agent",
1151
+ "description": None,
1152
+ "instructions": None,
1153
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI gpt-4o"},
1154
+ "tools": None,
1155
+ "memory": {
1156
+ "app_name": "Memory",
1157
+ "app_url": None,
1158
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1159
+ },
1160
+ "knowledge": None,
1161
+ "session_table": "agno_sessions",
1162
+ "memory_table": "agno_memories",
1163
+ "knowledge_table": None,
1164
+ }
1165
+ ],
1166
+ "expected_output": None,
1167
+ "dependencies": None,
1168
+ "enable_agentic_context": False,
1169
+ "memory": {
1170
+ "app_name": "Memory",
1171
+ "app_url": None,
1172
+ "model": {"name": "OpenAIChat", "model": "gpt-4o", "provider": "OpenAI"},
1173
+ },
1174
+ "knowledge": None,
1175
+ "async_mode": False,
1176
+ "session_table": "agno_sessions",
1177
+ "memory_table": "agno_memories",
1178
+ "knowledge_table": None,
1179
+ }
1180
+ }
1181
+ },
1182
+ },
1183
+ 404: {"description": "Team not found", "model": NotFoundResponse},
1184
+ },
725
1185
  )
726
- async def get_team(team_id: str):
1186
+ async def get_team(team_id: str) -> TeamResponse:
727
1187
  team = get_team_by_id(team_id, os.teams)
728
1188
  if team is None:
729
1189
  raise HTTPException(status_code=404, detail="Team not found")
@@ -732,7 +1192,10 @@ def get_base_router(
732
1192
 
733
1193
  # -- Workflow routes ---
734
1194
 
735
- @router.websocket("/workflows/ws")
1195
+ @router.websocket(
1196
+ "/workflows/ws",
1197
+ name="workflow_websocket",
1198
+ )
736
1199
  async def workflow_websocket_endpoint(websocket: WebSocket):
737
1200
  """WebSocket endpoint for receiving real-time workflow events"""
738
1201
  await websocket_manager.connect(websocket)
@@ -749,9 +1212,10 @@ def get_base_router(
749
1212
  elif action == "start-workflow":
750
1213
  # Handle workflow execution directly via WebSocket
751
1214
  await handle_workflow_via_websocket(websocket, message, os)
752
-
753
1215
  except Exception as e:
754
- logger.error(f"WebSocket error: {e}")
1216
+ if "1012" not in str(e):
1217
+ logger.error(f"WebSocket error: {e}")
1218
+ finally:
755
1219
  # Clean up any run_ids associated with this websocket
756
1220
  runs_to_remove = [run_id for run_id, ws in websocket_manager.active_connections.items() if ws == websocket]
757
1221
  for run_id in runs_to_remove:
@@ -759,11 +1223,38 @@ def get_base_router(
759
1223
 
760
1224
  @router.get(
761
1225
  "/workflows",
762
- response_model=List[WorkflowResponse],
1226
+ response_model=List[WorkflowSummaryResponse],
763
1227
  response_model_exclude_none=True,
764
1228
  tags=["Workflows"],
1229
+ operation_id="get_workflows",
1230
+ summary="List All Workflows",
1231
+ description=(
1232
+ "Retrieve a comprehensive list of all workflows configured in this OS instance.\n\n"
1233
+ "**Return Information:**\n"
1234
+ "- Workflow metadata (ID, name, description)\n"
1235
+ "- Input schema requirements\n"
1236
+ "- Step sequence and execution flow\n"
1237
+ "- Associated agents and teams"
1238
+ ),
1239
+ responses={
1240
+ 200: {
1241
+ "description": "List of workflows retrieved successfully",
1242
+ "content": {
1243
+ "application/json": {
1244
+ "example": [
1245
+ {
1246
+ "id": "content-creation-workflow",
1247
+ "name": "Content Creation Workflow",
1248
+ "description": "Automated content creation from blog posts to social media",
1249
+ "db_id": "123",
1250
+ }
1251
+ ]
1252
+ }
1253
+ },
1254
+ }
1255
+ },
765
1256
  )
766
- async def get_workflows():
1257
+ async def get_workflows() -> List[WorkflowSummaryResponse]:
767
1258
  if os.workflows is None:
768
1259
  return []
769
1260
 
@@ -774,15 +1265,67 @@ def get_base_router(
774
1265
  response_model=WorkflowResponse,
775
1266
  response_model_exclude_none=True,
776
1267
  tags=["Workflows"],
1268
+ operation_id="get_workflow",
1269
+ summary="Get Workflow Details",
1270
+ description=("Retrieve detailed configuration and step information for a specific workflow."),
1271
+ responses={
1272
+ 200: {
1273
+ "description": "Workflow details retrieved successfully",
1274
+ "content": {
1275
+ "application/json": {
1276
+ "example": {
1277
+ "id": "content-creation-workflow",
1278
+ "name": "Content Creation Workflow",
1279
+ "description": "Automated content creation from blog posts to social media",
1280
+ "db_id": "123",
1281
+ }
1282
+ }
1283
+ },
1284
+ },
1285
+ 404: {"description": "Workflow not found", "model": NotFoundResponse},
1286
+ },
777
1287
  )
778
- async def get_workflow(workflow_id: str):
1288
+ async def get_workflow(workflow_id: str) -> WorkflowResponse:
779
1289
  workflow = get_workflow_by_id(workflow_id, os.workflows)
780
1290
  if workflow is None:
781
1291
  raise HTTPException(status_code=404, detail="Workflow not found")
782
1292
 
783
1293
  return WorkflowResponse.from_workflow(workflow)
784
1294
 
785
- @router.post("/workflows/{workflow_id}/runs", tags=["Workflows"])
1295
+ @router.post(
1296
+ "/workflows/{workflow_id}/runs",
1297
+ tags=["Workflows"],
1298
+ operation_id="create_workflow_run",
1299
+ response_model_exclude_none=True,
1300
+ summary="Execute Workflow",
1301
+ description=(
1302
+ "Execute a workflow with the provided input data. Workflows can run in streaming or batch mode.\n\n"
1303
+ "**Execution Modes:**\n"
1304
+ "- **Streaming (`stream=true`)**: Real-time step-by-step execution updates via SSE\n"
1305
+ "- **Non-Streaming (`stream=false`)**: Complete workflow execution with final result\n\n"
1306
+ "**Workflow Execution Process:**\n"
1307
+ "1. Input validation against workflow schema\n"
1308
+ "2. Sequential or parallel step execution based on workflow design\n"
1309
+ "3. Data flow between steps with transformation\n"
1310
+ "4. Error handling and automatic retries where configured\n"
1311
+ "5. Final result compilation and response\n\n"
1312
+ "**Session Management:**\n"
1313
+ "Workflows support session continuity for stateful execution across multiple runs."
1314
+ ),
1315
+ responses={
1316
+ 200: {
1317
+ "description": "Workflow executed successfully",
1318
+ "content": {
1319
+ "text/event-stream": {
1320
+ "example": 'event: RunStarted\ndata: {"content": "Hello!", "run_id": "123..."}\n\n'
1321
+ },
1322
+ },
1323
+ },
1324
+ 400: {"description": "Invalid input data or workflow configuration", "model": BadRequestResponse},
1325
+ 404: {"description": "Workflow not found", "model": NotFoundResponse},
1326
+ 500: {"description": "Workflow execution error", "model": InternalServerErrorResponse},
1327
+ },
1328
+ )
786
1329
  async def create_workflow_run(
787
1330
  workflow_id: str,
788
1331
  message: str = Form(...),
@@ -828,7 +1371,21 @@ def get_base_router(
828
1371
  # Handle unexpected runtime errors
829
1372
  raise HTTPException(status_code=500, detail=f"Error running workflow: {str(e)}")
830
1373
 
831
- @router.post("/workflows/{workflow_id}/runs/{run_id}/cancel", tags=["Workflows"])
1374
+ @router.post(
1375
+ "/workflows/{workflow_id}/runs/{run_id}/cancel",
1376
+ tags=["Workflows"],
1377
+ operation_id="cancel_workflow_run",
1378
+ summary="Cancel Workflow Run",
1379
+ description=(
1380
+ "Cancel a currently executing workflow run, stopping all active steps and cleanup.\n"
1381
+ "**Note:** Complex workflows with multiple parallel steps may take time to fully cancel."
1382
+ ),
1383
+ responses={
1384
+ 200: {},
1385
+ 404: {"description": "Workflow or run not found", "model": NotFoundResponse},
1386
+ 500: {"description": "Failed to cancel workflow run", "model": InternalServerErrorResponse},
1387
+ },
1388
+ )
832
1389
  async def cancel_workflow_run(workflow_id: str, run_id: str):
833
1390
  workflow = get_workflow_by_id(workflow_id, os.workflows)
834
1391