rasa-pro 3.14.0a20__py3-none-any.whl → 3.14.0a23__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.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (331) hide show
  1. rasa/__main__.py +15 -3
  2. rasa/agents/__init__.py +0 -0
  3. rasa/agents/agent_factory.py +122 -0
  4. rasa/agents/agent_manager.py +211 -0
  5. rasa/agents/constants.py +43 -0
  6. rasa/agents/core/__init__.py +0 -0
  7. rasa/agents/core/agent_protocol.py +107 -0
  8. rasa/agents/core/types.py +81 -0
  9. rasa/agents/exceptions.py +38 -0
  10. rasa/agents/protocol/__init__.py +5 -0
  11. rasa/agents/protocol/a2a/__init__.py +0 -0
  12. rasa/agents/protocol/a2a/a2a_agent.py +879 -0
  13. rasa/agents/protocol/mcp/__init__.py +0 -0
  14. rasa/agents/protocol/mcp/mcp_base_agent.py +726 -0
  15. rasa/agents/protocol/mcp/mcp_open_agent.py +327 -0
  16. rasa/agents/protocol/mcp/mcp_task_agent.py +522 -0
  17. rasa/agents/schemas/__init__.py +13 -0
  18. rasa/agents/schemas/agent_input.py +38 -0
  19. rasa/agents/schemas/agent_output.py +26 -0
  20. rasa/agents/schemas/agent_tool_result.py +65 -0
  21. rasa/agents/schemas/agent_tool_schema.py +186 -0
  22. rasa/agents/templates/__init__.py +0 -0
  23. rasa/agents/templates/mcp_open_agent_prompt_template.jinja2 +20 -0
  24. rasa/agents/templates/mcp_task_agent_prompt_template.jinja2 +22 -0
  25. rasa/agents/utils.py +206 -0
  26. rasa/agents/validation.py +485 -0
  27. rasa/api.py +24 -9
  28. rasa/builder/config.py +6 -2
  29. rasa/builder/guardrails/{lakera.py → clients.py} +55 -5
  30. rasa/builder/guardrails/constants.py +3 -0
  31. rasa/builder/guardrails/models.py +45 -10
  32. rasa/builder/guardrails/policy_checker.py +324 -0
  33. rasa/builder/guardrails/utils.py +42 -276
  34. rasa/builder/llm_service.py +32 -5
  35. rasa/builder/models.py +1 -0
  36. rasa/builder/project_generator.py +6 -1
  37. rasa/builder/service.py +16 -13
  38. rasa/builder/training_service.py +18 -24
  39. rasa/builder/validation_service.py +1 -1
  40. rasa/cli/arguments/default_arguments.py +12 -0
  41. rasa/cli/arguments/run.py +2 -0
  42. rasa/cli/arguments/train.py +2 -0
  43. rasa/cli/data.py +10 -8
  44. rasa/cli/dialogue_understanding_test.py +10 -7
  45. rasa/cli/e2e_test.py +9 -6
  46. rasa/cli/evaluate.py +4 -2
  47. rasa/cli/export.py +5 -2
  48. rasa/cli/inspect.py +8 -4
  49. rasa/cli/interactive.py +5 -4
  50. rasa/cli/llm_fine_tuning.py +11 -6
  51. rasa/cli/project_templates/finance/actions/general/__init__.py +0 -0
  52. rasa/cli/project_templates/finance/actions/general/action_human_handoff.py +49 -0
  53. rasa/cli/project_templates/finance/data/general/bot_challenge.yml +6 -0
  54. rasa/cli/project_templates/finance/data/general/goodbye.yml +1 -1
  55. rasa/cli/project_templates/finance/data/general/human_handoff.yml +1 -1
  56. rasa/cli/project_templates/finance/data/system/patterns/pattern_session_start.yml +1 -1
  57. rasa/cli/project_templates/finance/domain/general/_shared.yml +0 -14
  58. rasa/cli/project_templates/finance/domain/general/bot_challenge.yml +4 -0
  59. rasa/cli/project_templates/finance/domain/general/goodbye.yml +7 -0
  60. rasa/cli/project_templates/finance/domain/general/human_handoff.yml +3 -6
  61. rasa/cli/project_templates/finance/domain/general/welcome.yml +29 -1
  62. rasa/cli/project_templates/finance/tests/e2e_test_cases/accounts/check_balance.yml +9 -0
  63. rasa/cli/project_templates/finance/tests/e2e_test_cases/accounts/download_statements.yml +43 -0
  64. rasa/cli/project_templates/finance/tests/e2e_test_cases/cards/block_card.yml +55 -0
  65. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/bot_challenge.yml +8 -0
  66. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/feedback.yml +46 -0
  67. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/goodbye.yml +9 -0
  68. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/hello.yml +8 -0
  69. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/human_handoff.yml +35 -0
  70. rasa/cli/project_templates/finance/tests/e2e_test_cases/general/patterns.yml +22 -0
  71. rasa/cli/project_templates/finance/tests/e2e_test_cases/transfers/transfer_money.yml +56 -0
  72. rasa/cli/project_templates/telco/tests/e2e_test_cases/general/feedback.yml +1 -1
  73. rasa/cli/project_templates/telco/tests/e2e_test_cases/general/hello.yml +1 -1
  74. rasa/cli/project_templates/telco/tests/e2e_test_cases/general/human_handoff.yml +1 -1
  75. rasa/cli/project_templates/telco/tests/e2e_test_cases/general/patterns.yml +1 -1
  76. rasa/cli/project_templates/tutorial/credentials.yml +10 -0
  77. rasa/cli/run.py +12 -10
  78. rasa/cli/scaffold.py +4 -4
  79. rasa/cli/shell.py +9 -5
  80. rasa/cli/studio/studio.py +1 -1
  81. rasa/cli/test.py +34 -14
  82. rasa/cli/train.py +41 -28
  83. rasa/cli/utils.py +1 -393
  84. rasa/cli/validation/__init__.py +0 -0
  85. rasa/cli/validation/bot_config.py +223 -0
  86. rasa/cli/validation/config_path_validation.py +257 -0
  87. rasa/cli/x.py +8 -4
  88. rasa/constants.py +7 -1
  89. rasa/core/actions/action.py +51 -10
  90. rasa/core/actions/action_run_slot_rejections.py +1 -1
  91. rasa/core/actions/direct_custom_actions_executor.py +9 -2
  92. rasa/core/actions/grpc_custom_action_executor.py +1 -1
  93. rasa/core/agent.py +19 -2
  94. rasa/core/available_agents.py +229 -0
  95. rasa/core/brokers/kafka.py +1 -1
  96. rasa/core/channels/__init__.py +82 -35
  97. rasa/core/channels/development_inspector.py +3 -3
  98. rasa/core/channels/inspector/README.md +25 -13
  99. rasa/core/channels/inspector/dist/assets/{arc-35222594.js → arc-6177260a.js} +1 -1
  100. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-a0efbfd3.js → blockDiagram-38ab4fdb-b054f038.js} +1 -1
  101. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-0584c0f2.js → c4Diagram-3d4e48cf-f25427d5.js} +1 -1
  102. rasa/core/channels/inspector/dist/assets/channel-bf9cbb34.js +1 -0
  103. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-39f40dbe.js → classDiagram-70f12bd4-c7a2af53.js} +1 -1
  104. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-1ad755f3.js → classDiagram-v2-f2320105-58db65c0.js} +1 -1
  105. rasa/core/channels/inspector/dist/assets/clone-8f9083bb.js +1 -0
  106. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-b0f4f0fe.js → createText-2e5e7dd3-088372e2.js} +1 -1
  107. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-9039bff9.js → edges-e0da2a9e-58676240.js} +1 -1
  108. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-65c9b127.js → erDiagram-9861fffd-0c14d7c6.js} +1 -1
  109. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-4f08b38e.js → flowDb-956e92f1-ea63f85c.js} +1 -1
  110. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-e95c362a.js → flowDiagram-66a62f08-a2af48cd.js} +1 -1
  111. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-9ecd5b59.js +1 -0
  112. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-703c3015.js → flowchart-elk-definition-4a651766-6937abe7.js} +1 -1
  113. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-699328ea.js → ganttDiagram-c361ad54-7473f357.js} +1 -1
  114. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-04cf4b05.js → gitGraphDiagram-72cf32ee-d0c9405e.js} +1 -1
  115. rasa/core/channels/inspector/dist/assets/{graph-ee94449e.js → graph-0a6f8466.js} +1 -1
  116. rasa/core/channels/inspector/dist/assets/{index-3862675e-940162b4.js → index-3862675e-7610671a.js} +1 -1
  117. rasa/core/channels/inspector/dist/assets/index-74e01d94.js +1354 -0
  118. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-c79c2866.js → infoDiagram-f8f76790-be397dc7.js} +1 -1
  119. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-84489d30.js → journeyDiagram-49397b02-4cefbf62.js} +1 -1
  120. rasa/core/channels/inspector/dist/assets/{layout-a9aa9858.js → layout-e7fbc2bf.js} +1 -1
  121. rasa/core/channels/inspector/dist/assets/{line-eb73cf26.js → line-a8aa457c.js} +1 -1
  122. rasa/core/channels/inspector/dist/assets/{linear-b3399f9a.js → linear-3351e0d2.js} +1 -1
  123. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-b095bf1a.js → mindmap-definition-fc14e90a-b8cbf605.js} +1 -1
  124. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-07644b66.js → pieDiagram-8a3498a8-f327f774.js} +1 -1
  125. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-573a3f9c.js → quadrantDiagram-120e2f19-2854c591.js} +1 -1
  126. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-d457e1e1.js → requirementDiagram-deff3bca-964985d5.js} +1 -1
  127. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-9d26e1a2.js → sankeyDiagram-04a897e0-edeb4f33.js} +1 -1
  128. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3a9cde10.js → sequenceDiagram-704730f1-fcf70125.js} +1 -1
  129. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-4f3e8cec.js → stateDiagram-587899a1-0e770395.js} +1 -1
  130. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e617e5bf.js → stateDiagram-v2-d93cdb3a-af8dcd22.js} +1 -1
  131. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-eab30d2f.js → styles-6aaf32cf-36a9e70d.js} +1 -1
  132. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-09994be2.js → styles-9a916d00-884a8b5b.js} +1 -1
  133. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-b7110364.js → styles-c10674c1-dc097813.js} +1 -1
  134. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-3ebc92ad.js → svgDrawCommon-08f97a94-5a2c7eed.js} +1 -1
  135. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-7d13d2f2.js → timeline-definition-85554ec2-e89c4f6e.js} +1 -1
  136. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-488385e1.js → xychartDiagram-e933f94c-afb6fe56.js} +1 -1
  137. rasa/core/channels/inspector/dist/index.html +1 -1
  138. rasa/core/channels/inspector/package.json +18 -18
  139. rasa/core/channels/inspector/src/App.tsx +29 -4
  140. rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
  141. rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +4 -2
  142. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +7 -4
  143. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
  144. rasa/core/channels/inspector/src/helpers/formatters.ts +24 -3
  145. rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
  146. rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
  147. rasa/core/channels/inspector/src/theme/base/styles.ts +19 -1
  148. rasa/core/channels/inspector/src/types.ts +21 -0
  149. rasa/core/channels/inspector/yarn.lock +336 -189
  150. rasa/core/channels/studio_chat.py +6 -6
  151. rasa/core/channels/telegram.py +4 -9
  152. rasa/core/channels/voice_stream/browser_audio.py +2 -0
  153. rasa/core/channels/voice_stream/genesys.py +1 -1
  154. rasa/core/channels/voice_stream/tts/deepgram.py +140 -0
  155. rasa/core/channels/voice_stream/twilio_media_streams.py +5 -1
  156. rasa/core/channels/voice_stream/voice_channel.py +3 -0
  157. rasa/core/config/__init__.py +0 -0
  158. rasa/core/{available_endpoints.py → config/available_endpoints.py} +51 -16
  159. rasa/core/config/configuration.py +260 -0
  160. rasa/core/config/credentials.py +19 -0
  161. rasa/core/config/message_procesing_config.py +34 -0
  162. rasa/core/constants.py +5 -0
  163. rasa/core/iam_credentials_providers/aws_iam_credentials_providers.py +88 -3
  164. rasa/core/iam_credentials_providers/credentials_provider_protocol.py +2 -1
  165. rasa/core/lock_store.py +6 -4
  166. rasa/core/nlg/generator.py +1 -1
  167. rasa/core/policies/enterprise_search_policy.py +5 -3
  168. rasa/core/policies/flow_policy.py +4 -4
  169. rasa/core/policies/flows/agent_executor.py +632 -0
  170. rasa/core/policies/flows/flow_executor.py +137 -76
  171. rasa/core/policies/flows/mcp_tool_executor.py +298 -0
  172. rasa/core/policies/intentless_policy.py +1 -1
  173. rasa/core/policies/ted_policy.py +20 -12
  174. rasa/core/policies/unexpected_intent_policy.py +6 -0
  175. rasa/core/processor.py +68 -44
  176. rasa/core/redis_connection_factory.py +78 -20
  177. rasa/core/run.py +37 -8
  178. rasa/core/test.py +4 -0
  179. rasa/core/tracker_stores/sql_tracker_store.py +1 -1
  180. rasa/core/tracker_stores/tracker_store.py +3 -7
  181. rasa/core/train.py +1 -1
  182. rasa/core/training/interactive.py +20 -18
  183. rasa/core/training/story_conflict.py +5 -5
  184. rasa/core/utils.py +22 -23
  185. rasa/dialogue_understanding/commands/__init__.py +8 -0
  186. rasa/dialogue_understanding/commands/cancel_flow_command.py +19 -5
  187. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +21 -2
  188. rasa/dialogue_understanding/commands/clarify_command.py +20 -2
  189. rasa/dialogue_understanding/commands/continue_agent_command.py +91 -0
  190. rasa/dialogue_understanding/commands/knowledge_answer_command.py +21 -2
  191. rasa/dialogue_understanding/commands/restart_agent_command.py +162 -0
  192. rasa/dialogue_understanding/commands/start_flow_command.py +68 -7
  193. rasa/dialogue_understanding/commands/utils.py +124 -2
  194. rasa/dialogue_understanding/generator/command_parser.py +4 -0
  195. rasa/dialogue_understanding/generator/llm_based_command_generator.py +50 -12
  196. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  197. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +1 -1
  198. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +66 -0
  199. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +66 -0
  200. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +89 -0
  201. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +88 -0
  202. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +42 -7
  203. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +40 -3
  204. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +20 -3
  205. rasa/dialogue_understanding/patterns/cancel.py +27 -6
  206. rasa/dialogue_understanding/patterns/clarify.py +3 -14
  207. rasa/dialogue_understanding/patterns/continue_interrupted.py +239 -6
  208. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +46 -8
  209. rasa/dialogue_understanding/processor/command_processor.py +136 -15
  210. rasa/dialogue_understanding/stack/dialogue_stack.py +98 -2
  211. rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +57 -0
  212. rasa/dialogue_understanding/stack/utils.py +57 -3
  213. rasa/dialogue_understanding/utils.py +24 -4
  214. rasa/dialogue_understanding_test/du_test_runner.py +8 -3
  215. rasa/e2e_test/e2e_test_runner.py +13 -3
  216. rasa/engine/caching.py +2 -2
  217. rasa/engine/constants.py +1 -1
  218. rasa/engine/loader.py +12 -0
  219. rasa/engine/recipes/default_components.py +138 -49
  220. rasa/engine/recipes/default_recipe.py +108 -11
  221. rasa/engine/runner/dask.py +8 -5
  222. rasa/engine/validation.py +19 -6
  223. rasa/graph_components/validators/default_recipe_validator.py +86 -28
  224. rasa/hooks.py +5 -5
  225. rasa/llm_fine_tuning/utils.py +2 -2
  226. rasa/model_training.py +60 -47
  227. rasa/nlu/classifiers/diet_classifier.py +198 -98
  228. rasa/nlu/classifiers/logistic_regression_classifier.py +1 -4
  229. rasa/nlu/classifiers/mitie_intent_classifier.py +3 -0
  230. rasa/nlu/classifiers/sklearn_intent_classifier.py +1 -3
  231. rasa/nlu/extractors/crf_entity_extractor.py +9 -10
  232. rasa/nlu/extractors/mitie_entity_extractor.py +3 -0
  233. rasa/nlu/extractors/spacy_entity_extractor.py +3 -0
  234. rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +4 -0
  235. rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +5 -0
  236. rasa/nlu/featurizers/dense_featurizer/mitie_featurizer.py +2 -0
  237. rasa/nlu/featurizers/dense_featurizer/spacy_featurizer.py +3 -0
  238. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +4 -2
  239. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +4 -0
  240. rasa/nlu/selectors/response_selector.py +10 -2
  241. rasa/nlu/tokenizers/jieba_tokenizer.py +3 -4
  242. rasa/nlu/tokenizers/mitie_tokenizer.py +3 -2
  243. rasa/nlu/tokenizers/spacy_tokenizer.py +3 -2
  244. rasa/nlu/utils/mitie_utils.py +3 -0
  245. rasa/nlu/utils/spacy_utils.py +3 -2
  246. rasa/plugin.py +8 -8
  247. rasa/privacy/privacy_manager.py +12 -3
  248. rasa/server.py +15 -3
  249. rasa/shared/agents/__init__.py +0 -0
  250. rasa/shared/agents/auth/__init__.py +0 -0
  251. rasa/shared/agents/auth/agent_auth_factory.py +105 -0
  252. rasa/shared/agents/auth/agent_auth_manager.py +92 -0
  253. rasa/shared/agents/auth/auth_strategy/__init__.py +19 -0
  254. rasa/shared/agents/auth/auth_strategy/agent_auth_strategy.py +52 -0
  255. rasa/shared/agents/auth/auth_strategy/api_key_auth_strategy.py +42 -0
  256. rasa/shared/agents/auth/auth_strategy/bearer_token_auth_strategy.py +28 -0
  257. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +167 -0
  258. rasa/shared/agents/auth/constants.py +12 -0
  259. rasa/shared/agents/auth/types.py +12 -0
  260. rasa/shared/agents/utils.py +35 -0
  261. rasa/shared/constants.py +8 -0
  262. rasa/shared/core/constants.py +16 -1
  263. rasa/shared/core/domain.py +0 -7
  264. rasa/shared/core/events.py +327 -0
  265. rasa/shared/core/flows/constants.py +5 -0
  266. rasa/shared/core/flows/flow.py +1 -1
  267. rasa/shared/core/flows/flows_list.py +21 -5
  268. rasa/shared/core/flows/flows_yaml_schema.json +119 -184
  269. rasa/shared/core/flows/steps/call.py +49 -5
  270. rasa/shared/core/flows/steps/collect.py +98 -13
  271. rasa/shared/core/flows/validation.py +372 -8
  272. rasa/shared/core/flows/yaml_flows_io.py +3 -2
  273. rasa/shared/core/slots.py +2 -2
  274. rasa/shared/core/trackers.py +5 -2
  275. rasa/shared/exceptions.py +16 -0
  276. rasa/shared/importers/rasa.py +1 -1
  277. rasa/shared/importers/utils.py +9 -3
  278. rasa/shared/providers/llm/_base_litellm_client.py +41 -9
  279. rasa/shared/providers/llm/litellm_router_llm_client.py +8 -4
  280. rasa/shared/providers/llm/llm_client.py +7 -3
  281. rasa/shared/providers/llm/llm_response.py +66 -0
  282. rasa/shared/providers/llm/self_hosted_llm_client.py +8 -4
  283. rasa/shared/utils/common.py +24 -0
  284. rasa/shared/utils/health_check/health_check.py +7 -3
  285. rasa/shared/utils/llm.py +39 -16
  286. rasa/shared/utils/mcp/__init__.py +0 -0
  287. rasa/shared/utils/mcp/server_connection.py +247 -0
  288. rasa/shared/utils/mcp/utils.py +20 -0
  289. rasa/shared/utils/schemas/events.py +42 -0
  290. rasa/shared/utils/yaml.py +3 -1
  291. rasa/studio/pull/pull.py +3 -2
  292. rasa/studio/train.py +8 -7
  293. rasa/studio/upload.py +3 -6
  294. rasa/telemetry.py +69 -5
  295. rasa/tracing/config.py +45 -12
  296. rasa/tracing/constants.py +14 -0
  297. rasa/tracing/instrumentation/attribute_extractors.py +142 -9
  298. rasa/tracing/instrumentation/instrumentation.py +626 -21
  299. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +4 -4
  300. rasa/tracing/instrumentation/metrics.py +32 -0
  301. rasa/tracing/metric_instrument_provider.py +68 -0
  302. rasa/utils/common.py +92 -1
  303. rasa/utils/endpoints.py +11 -2
  304. rasa/utils/log_utils.py +96 -5
  305. rasa/utils/ml_utils.py +1 -1
  306. rasa/utils/pypred.py +38 -0
  307. rasa/utils/tensorflow/__init__.py +7 -0
  308. rasa/utils/tensorflow/callback.py +136 -101
  309. rasa/utils/tensorflow/crf.py +1 -1
  310. rasa/utils/tensorflow/data_generator.py +21 -8
  311. rasa/utils/tensorflow/layers.py +21 -11
  312. rasa/utils/tensorflow/metrics.py +7 -3
  313. rasa/utils/tensorflow/models.py +56 -8
  314. rasa/utils/tensorflow/rasa_layers.py +8 -6
  315. rasa/utils/tensorflow/transformer.py +2 -3
  316. rasa/utils/train_utils.py +54 -24
  317. rasa/validator.py +17 -13
  318. rasa/version.py +1 -1
  319. {rasa_pro-3.14.0a20.dist-info → rasa_pro-3.14.0a23.dist-info}/METADATA +48 -42
  320. {rasa_pro-3.14.0a20.dist-info → rasa_pro-3.14.0a23.dist-info}/RECORD +323 -251
  321. rasa/builder/scrape_rasa_docs.py +0 -97
  322. rasa/cli/project_templates/finance/data/general/agent_details.yml +0 -6
  323. rasa/cli/project_templates/finance/domain/_system/patterns/pattern_session_start.yml +0 -11
  324. rasa/cli/project_templates/finance/domain/general/agent_details.yml +0 -31
  325. rasa/core/channels/inspector/dist/assets/channel-8e08bed9.js +0 -1
  326. rasa/core/channels/inspector/dist/assets/clone-78c82dea.js +0 -1
  327. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2b08f601.js +0 -1
  328. rasa/core/channels/inspector/dist/assets/index-c941dcb3.js +0 -1336
  329. {rasa_pro-3.14.0a20.dist-info → rasa_pro-3.14.0a23.dist-info}/NOTICE +0 -0
  330. {rasa_pro-3.14.0a20.dist-info → rasa_pro-3.14.0a23.dist-info}/WHEEL +0 -0
  331. {rasa_pro-3.14.0a20.dist-info → rasa_pro-3.14.0a23.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,879 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import time
5
+ import uuid
6
+ from typing import Any, ClassVar, Dict, List, Optional
7
+ from urllib.parse import urlparse
8
+
9
+ import httpx
10
+ import structlog
11
+ from a2a.client import (
12
+ A2ACardResolver,
13
+ A2AClientError,
14
+ A2AClientHTTPError,
15
+ A2AClientJSONError,
16
+ Client,
17
+ ClientConfig,
18
+ ClientEvent,
19
+ ClientFactory,
20
+ )
21
+ from a2a.client.errors import A2AClientJSONRPCError
22
+ from a2a.types import (
23
+ AgentCard,
24
+ Artifact,
25
+ DataPart,
26
+ FilePart,
27
+ FileWithUri,
28
+ InternalError,
29
+ InvalidAgentResponseError,
30
+ Message,
31
+ Part,
32
+ Role,
33
+ Task,
34
+ TaskQueryParams,
35
+ TaskState,
36
+ TextPart,
37
+ TransportProtocol,
38
+ )
39
+ from pydantic import ValidationError
40
+
41
+ from rasa.agents.constants import (
42
+ A2A_AGENT_CONTEXT_ID_KEY,
43
+ A2A_AGENT_TASK_ID_KEY,
44
+ AGENT_DEFAULT_MAX_RETRIES,
45
+ AGENT_DEFAULT_TIMEOUT_SECONDS,
46
+ AGENT_METADATA_STRUCTURED_RESULTS_KEY,
47
+ MAX_AGENT_RETRY_DELAY_SECONDS,
48
+ )
49
+ from rasa.agents.core.agent_protocol import AgentProtocol
50
+ from rasa.agents.core.types import AgentStatus, ProtocolType
51
+ from rasa.agents.schemas import AgentInput, AgentOutput
52
+ from rasa.core.available_agents import AgentConfig
53
+ from rasa.shared.agents.auth.agent_auth_manager import AgentAuthManager
54
+ from rasa.shared.exceptions import (
55
+ AgentInitializationException,
56
+ InvalidParameterException,
57
+ RasaException,
58
+ )
59
+
60
+ A2A_TASK_POOLING_INITIAL_DELAY = 0.5
61
+ A2A_TASK_POOLING_MAX_WAIT = 60
62
+
63
+ structlogger = structlog.get_logger()
64
+
65
+
66
+ class A2AAgent(AgentProtocol):
67
+ """A2A client implementation"""
68
+
69
+ __SUPPORTED_OUTPUT_MODES: ClassVar[list[str]] = [
70
+ "text",
71
+ "text/plain",
72
+ "application/json",
73
+ ]
74
+
75
+ # ============================================================================
76
+ # Initialization & Setup
77
+ # ============================================================================
78
+
79
+ def __init__(
80
+ self,
81
+ name: str,
82
+ description: str,
83
+ agent_card_path: str,
84
+ timeout: int,
85
+ max_retries: int,
86
+ auth_config: Optional[Dict[str, Any]] = None,
87
+ ) -> None:
88
+ self._name = name
89
+ self._description = description
90
+ self._agent_card_path = agent_card_path
91
+ self._timeout = timeout
92
+ self._max_retries = max_retries
93
+ self._auth_config = auth_config
94
+
95
+ self.agent_card: Optional[AgentCard] = None
96
+ self._client: Optional[Client] = None
97
+
98
+ @classmethod
99
+ def from_config(cls, config: AgentConfig) -> AgentProtocol:
100
+ """Initialize the A2A Agent with the given configuration."""
101
+ agent_card_path = (
102
+ config.configuration.agent_card if config.configuration else None
103
+ )
104
+ if not agent_card_path:
105
+ raise InvalidParameterException(
106
+ "Agent card path or URL must be provided in the configuration "
107
+ "for A2A agents."
108
+ )
109
+
110
+ timeout = (
111
+ config.configuration.timeout
112
+ if config.configuration and config.configuration.timeout
113
+ else AGENT_DEFAULT_TIMEOUT_SECONDS
114
+ )
115
+ max_retries = (
116
+ config.configuration.max_retries
117
+ if config.configuration and config.configuration.max_retries
118
+ else AGENT_DEFAULT_MAX_RETRIES
119
+ )
120
+
121
+ _auth_config = config.configuration.auth if config.configuration else None
122
+ return cls(
123
+ name=config.agent.name,
124
+ description=config.agent.description,
125
+ agent_card_path=agent_card_path,
126
+ timeout=timeout,
127
+ max_retries=max_retries,
128
+ auth_config=_auth_config,
129
+ )
130
+
131
+ @property
132
+ def protocol_type(self) -> ProtocolType:
133
+ return ProtocolType.A2A
134
+
135
+ # ============================================================================
136
+ # Connection Management
137
+ # ============================================================================
138
+
139
+ async def connect(self) -> None:
140
+ """Fetch the AgentCard and initialize the A2A client."""
141
+ from rasa.nlu.utils import is_url
142
+
143
+ if is_url(self._agent_card_path):
144
+ self.agent_card = await A2AAgent._resolve_agent_card_with_retry(
145
+ self._agent_card_path, self._timeout, self._max_retries
146
+ )
147
+ else:
148
+ self.agent_card = A2AAgent._load_agent_card_from_file(self._agent_card_path)
149
+ structlogger.debug(
150
+ "a2a_agent.from_config",
151
+ event_info=f"Loaded agent card from {self._agent_card_path}",
152
+ agent_card=self.agent_card.model_dump(),
153
+ json_formatting=["agent_card"],
154
+ )
155
+
156
+ try:
157
+ self._client = self._init_client()
158
+ structlogger.debug(
159
+ "a2a_agent.connect.agent_client_initialized",
160
+ event_info=f"Initialized A2A client for agent '{self._name}'. "
161
+ f"Performing health check using the URL {self.agent_card.url}",
162
+ )
163
+ except Exception as exception:
164
+ structlogger.error(
165
+ "a2a_agent.connect.error",
166
+ event_info="Failed to initialize A2A client",
167
+ agent_name=self._name,
168
+ error=str(exception),
169
+ )
170
+ raise AgentInitializationException(
171
+ f"Failed to initialize A2A client for agent "
172
+ f"'{self._name}': {exception}"
173
+ ) from exception
174
+
175
+ await self._perform_health_check()
176
+ structlogger.debug(
177
+ "a2a_agent.connect.success",
178
+ event_info=f"Connected to A2A server '{self._name}' "
179
+ f"at {self.agent_card.url}",
180
+ )
181
+
182
+ async def disconnect(self) -> None:
183
+ """We don't need to explicitly disconnect the A2A client"""
184
+ return
185
+
186
+ # ============================================================================
187
+ # Core Protocol Methods
188
+ # ============================================================================
189
+
190
+ async def process_input(self, agent_input: AgentInput) -> AgentInput:
191
+ """Pre-process the input before sending it to the agent."""
192
+ # A2A-specific input processing logic
193
+ return agent_input
194
+
195
+ async def run(self, agent_input: AgentInput) -> AgentOutput:
196
+ """Send a message to Agent/server and return response."""
197
+ if not self._client or not self.agent_card:
198
+ structlogger.error(
199
+ "a2a_agent.run.error",
200
+ event_info="A2A client is not initialized. Call connect() first.",
201
+ )
202
+ return AgentOutput(
203
+ id=agent_input.id,
204
+ status=AgentStatus.FATAL_ERROR,
205
+ error_message="Client not initialized",
206
+ )
207
+
208
+ structlogger.info(
209
+ "a2a_agent.run.start",
210
+ event_info="Running A2A agent",
211
+ agent_name=self._name,
212
+ )
213
+ message = self._prepare_message(agent_input)
214
+
215
+ task_id: Optional[str] = None
216
+ events_received = 0
217
+ try:
218
+ async for event in self._client.send_message(message):
219
+ events_received += 1
220
+ agent_output = self._handle_send_message_response(agent_input, event)
221
+ if agent_output is not None:
222
+ return agent_output
223
+ else:
224
+ # Not a terminal response, save taskID (in case that's the only
225
+ # event, and we need to pool) and continue waiting for next events
226
+ if (
227
+ isinstance(event, tuple)
228
+ and len(event) == 2
229
+ and isinstance(event[0], Task)
230
+ ):
231
+ task_id = event[0].id
232
+ continue
233
+ except A2AClientJSONRPCError as e:
234
+ return self._handle_json_rpc_error_response(agent_input, e.error)
235
+ except A2AClientError as exception:
236
+ structlogger.error(
237
+ "a2a_agent.run.send_message.error",
238
+ event_info="Error during sending message to A2A agent",
239
+ agent_name=self._name,
240
+ error=str(exception),
241
+ )
242
+ return AgentOutput(
243
+ id=agent_input.id,
244
+ status=AgentStatus.FATAL_ERROR,
245
+ error_message=f"Send message error: {exception!s}",
246
+ )
247
+
248
+ # The stream has ended, but we didn't get a terminal response.
249
+ # Check if we received any events at all.
250
+ if events_received == 0:
251
+ structlogger.error(
252
+ "a2a_agent.run.no_events_received",
253
+ event_info="No events received from A2A agent after sending message",
254
+ agent_name=self._name,
255
+ )
256
+ return AgentOutput(
257
+ id=agent_input.id,
258
+ status=AgentStatus.RECOVERABLE_ERROR,
259
+ error_message="No events received from A2A agent",
260
+ )
261
+
262
+ # Now we need to poll the task until it reaches a terminal state.
263
+ if not task_id:
264
+ structlogger.error(
265
+ "a2a_agent.run.pooling.missing_id",
266
+ event_info="Missing task_id for polling",
267
+ agent_name=self._name,
268
+ task_id=task_id,
269
+ )
270
+ return AgentOutput(
271
+ id=agent_input.id,
272
+ status=AgentStatus.FATAL_ERROR,
273
+ error_message="Missing task_id for polling",
274
+ )
275
+ return await self._pool_task_until_terminal(
276
+ agent_input=agent_input,
277
+ task_id=task_id,
278
+ max_wait=A2A_TASK_POOLING_MAX_WAIT,
279
+ initial_delay=A2A_TASK_POOLING_INITIAL_DELAY,
280
+ max_delay=MAX_AGENT_RETRY_DELAY_SECONDS,
281
+ )
282
+
283
+ async def process_output(self, output: AgentOutput) -> AgentOutput:
284
+ """Post-process the output before returning it to Rasa."""
285
+ # A2A-specific output processing logic
286
+ return output
287
+
288
+ # ============================================================================
289
+ # Message Processing & Response Handling
290
+ # ============================================================================
291
+
292
+ def _handle_send_message_response(
293
+ self, agent_input: AgentInput, response: ClientEvent | Message
294
+ ) -> Optional[AgentOutput]:
295
+ """Handle possible response types from the A2A client:
296
+
297
+ In case of streaming, the response can be either exactly *one* Message,
298
+ or a *series* of tuples of (Task, Optional[TaskUpdateEvent]).
299
+
300
+ In case of pooling, the response can be either exactly *one* Message,
301
+ or exactly *one* tuple of (Task, None).
302
+
303
+ If the agent response is terminal (i.e., completed, failed, etc.),
304
+ this method will return an AgentOutput.
305
+ Otherwise, the task is still in progress (i.e., submitted, working), so this
306
+ method will return None, so that the streaming or pooling agent can continue
307
+ to wait for updates.
308
+ """
309
+ if isinstance(response, Message):
310
+ return self._handle_message_response(agent_input, response)
311
+ elif (
312
+ isinstance(response, tuple)
313
+ and len(response) == 2
314
+ and isinstance(response[0], Task)
315
+ ):
316
+ return self._handle_client_event(agent_input, response)
317
+ else:
318
+ # Currently, no other response types exist, so this branch is
319
+ # unreachable. It is kept as a safeguard against future changes
320
+ # to the A2A protocol: if new response types are introduced,
321
+ # the agent will log an error instead of crashing.
322
+ return self._handle_unexpected_response_type(agent_input, response)
323
+
324
+ def _handle_json_rpc_error_response(
325
+ self, agent_input: AgentInput, error: Any
326
+ ) -> AgentOutput:
327
+ structlogger.error(
328
+ "a2a_agent.run.error",
329
+ event_info="Received JSON-RPC error response from A2A agent",
330
+ agent_name=self._name,
331
+ error=str(error),
332
+ )
333
+ if isinstance(
334
+ error,
335
+ (
336
+ InternalError,
337
+ InvalidAgentResponseError,
338
+ ),
339
+ ):
340
+ return AgentOutput(
341
+ id=agent_input.id,
342
+ status=AgentStatus.RECOVERABLE_ERROR,
343
+ error_message=str(error),
344
+ )
345
+ else:
346
+ return AgentOutput(
347
+ id=agent_input.id,
348
+ status=AgentStatus.FATAL_ERROR,
349
+ error_message=str(error),
350
+ )
351
+
352
+ def _handle_client_event(
353
+ self, agent_input: AgentInput, client_event: ClientEvent
354
+ ) -> Optional[AgentOutput]:
355
+ task = client_event[0]
356
+ update_event = client_event[1]
357
+ structlogger.debug(
358
+ "a2a_agent.run.client_event_received",
359
+ event_info="Received client event from A2A",
360
+ task=task.model_dump() if task else None,
361
+ update_event=update_event.model_dump() if update_event else None,
362
+ json_formatting=["task", "update_event"],
363
+ )
364
+
365
+ return self._handle_task(agent_input=agent_input, task=task)
366
+
367
+ def _handle_message_response(
368
+ self, agent_input: AgentInput, message: Message
369
+ ) -> Optional[AgentOutput]:
370
+ structlogger.debug(
371
+ "a2a_agent.run.message_received",
372
+ event_info="Received message from A2A",
373
+ agent_name=self._name,
374
+ message=message.model_dump(),
375
+ json_formatting=["message"],
376
+ )
377
+ metadata = agent_input.metadata or {}
378
+ metadata[A2A_AGENT_CONTEXT_ID_KEY] = message.context_id
379
+
380
+ return AgentOutput(
381
+ id=agent_input.id,
382
+ status=AgentStatus.INPUT_REQUIRED,
383
+ response_message=self._generate_response_message_from_parts(message.parts),
384
+ metadata=metadata,
385
+ )
386
+
387
+ def _handle_unexpected_response_type(
388
+ self, agent_input: AgentInput, response_result: Any
389
+ ) -> AgentOutput:
390
+ structlogger.error(
391
+ "a2a_agent.run.unexpected_response_type",
392
+ event_info="Received unexpected response type from A2A server "
393
+ "during streaming",
394
+ agent_name=self._name,
395
+ response_type=type(response_result),
396
+ )
397
+ return AgentOutput(
398
+ id=agent_input.id,
399
+ status=AgentStatus.FATAL_ERROR,
400
+ error_message=f"Unexpected response type: {type(response_result)}",
401
+ )
402
+
403
+ def _handle_task(
404
+ self,
405
+ agent_input: AgentInput,
406
+ task: Task,
407
+ ) -> Optional[AgentOutput]:
408
+ """If the task status is terminal (i.e., completed, failed, etc.),
409
+ return an AgentOutput.
410
+ If the task is still in progress (i.e., submitted, working), return None,
411
+ so that the streaming or pooling agent can continue to wait for updates.
412
+ """
413
+ state = task.status.state
414
+
415
+ metadata = agent_input.metadata or {}
416
+ metadata[A2A_AGENT_CONTEXT_ID_KEY] = task.context_id
417
+ metadata[A2A_AGENT_TASK_ID_KEY] = task.id
418
+
419
+ if state == TaskState.input_required:
420
+ response_message = (
421
+ self._generate_response_message_from_parts(task.status.message.parts)
422
+ if task.status.message
423
+ else ""
424
+ ) # This should not happen, but as type of message property
425
+ # is optional, so we need to handle it
426
+ return AgentOutput(
427
+ id=agent_input.id,
428
+ status=AgentStatus.INPUT_REQUIRED,
429
+ response_message=response_message,
430
+ metadata=metadata,
431
+ )
432
+ elif state == TaskState.completed:
433
+ response_message = self._generate_completed_response_message(task)
434
+ structured_results = (
435
+ self._generate_structured_results_from_artifacts(
436
+ agent_input, task.artifacts
437
+ )
438
+ if task.artifacts
439
+ else None
440
+ )
441
+ return AgentOutput(
442
+ id=agent_input.id,
443
+ status=AgentStatus.COMPLETED,
444
+ response_message=response_message,
445
+ structured_results=structured_results,
446
+ metadata=metadata,
447
+ )
448
+ elif (
449
+ state == TaskState.failed
450
+ or state == TaskState.canceled
451
+ or state == TaskState.rejected
452
+ or state == TaskState.auth_required
453
+ ):
454
+ structlogger.error(
455
+ "a2a_agent.run_streaming_agent.unsuccessful_task_state",
456
+ event_info="Task execution finished with an unsuccessful state",
457
+ agent_name=self._name,
458
+ state=state,
459
+ )
460
+ return AgentOutput(
461
+ id=agent_input.id,
462
+ status=AgentStatus.RECOVERABLE_ERROR,
463
+ error_message=f"Task state: {state}",
464
+ metadata=metadata,
465
+ )
466
+ elif state == TaskState.submitted or state == TaskState.working:
467
+ # The task is still in progress, return None to continue waiting for updates
468
+ return None
469
+ elif state == TaskState.unknown:
470
+ # The task has an unknown state. Perhaps this is a transient condition.
471
+ # Return None to continue waiting for updates
472
+ structlogger.warning(
473
+ "a2a_agent.run_streaming_agent.unknown_task_state",
474
+ event_info="Task is in unknown state, continuing to wait for updates",
475
+ agent_name=self._name,
476
+ state=state,
477
+ )
478
+ return None
479
+ else:
480
+ structlogger.error(
481
+ "a2a_agent.run_streaming_agent.unexpected_task_state",
482
+ event_info="Unexpected task state received from A2A",
483
+ agent_name=self._name,
484
+ state=state,
485
+ )
486
+ return AgentOutput(
487
+ id=agent_input.id,
488
+ status=AgentStatus.FATAL_ERROR,
489
+ error_message=f"Unexpected task state: {state}",
490
+ metadata=metadata,
491
+ )
492
+
493
+ # ============================================================================
494
+ # Message Preparation & Formatting
495
+ # ============================================================================
496
+
497
+ @staticmethod
498
+ def _prepare_message(agent_input: AgentInput) -> Message:
499
+ parts: List[Part] = []
500
+ if agent_input.metadata and A2A_AGENT_CONTEXT_ID_KEY in agent_input.metadata:
501
+ # Agent knows the conversation history already, send the last
502
+ # user message only
503
+ parts.append(Part(root=TextPart(text=agent_input.user_message)))
504
+ else:
505
+ # Send the full conversation history
506
+ parts.append(Part(root=TextPart(text=agent_input.conversation_history)))
507
+
508
+ if len(agent_input.slots) > 0:
509
+ slots_dict: Dict[str, Any] = {
510
+ "slots": [
511
+ slot.model_dump(exclude={"type", "allowed_values"})
512
+ for slot in agent_input.slots
513
+ if slot.value is not None
514
+ ]
515
+ }
516
+ parts.append(Part(root=DataPart(data=slots_dict)))
517
+
518
+ agent_message = Message(
519
+ role=Role.user,
520
+ parts=parts,
521
+ message_id=str(uuid.uuid4()),
522
+ context_id=agent_input.metadata.get(A2A_AGENT_CONTEXT_ID_KEY, None),
523
+ task_id=agent_input.metadata.get(A2A_AGENT_TASK_ID_KEY, None),
524
+ )
525
+ structlogger.debug(
526
+ "a2a_agent.prepare_message",
527
+ event_info="Prepared message to send to A2A server",
528
+ agent_name=agent_input.id,
529
+ message=agent_message.model_dump(),
530
+ json_formatting=["message"],
531
+ )
532
+ return agent_message
533
+
534
+ # ============================================================================
535
+ # Task Management & Polling
536
+ # ============================================================================
537
+
538
+ async def _pool_task_until_terminal(
539
+ self,
540
+ agent_input: AgentInput,
541
+ task_id: str,
542
+ max_wait: int,
543
+ initial_delay: float,
544
+ max_delay: int,
545
+ ) -> AgentOutput:
546
+ """Poll the task status until it reaches a terminal state or times out."""
547
+ if not self._client:
548
+ structlogger.error(
549
+ "a2a_agent.pool_task_until_terminal.error",
550
+ event_info="A2A client is not initialized. Call connect() first.",
551
+ )
552
+ return AgentOutput(
553
+ id=agent_input.id,
554
+ status=AgentStatus.FATAL_ERROR,
555
+ error_message="Client not initialized",
556
+ )
557
+
558
+ structlogger.debug(
559
+ "a2a_agent.pool_task_until_terminal.start",
560
+ event_info="Start polling task from A2A server",
561
+ agent_name=self._name,
562
+ task_id=task_id,
563
+ max_wait=max_wait,
564
+ initial_delay=initial_delay,
565
+ max_delay=max_delay,
566
+ )
567
+ start_time = time.monotonic()
568
+ delay = initial_delay
569
+
570
+ while True:
571
+ try:
572
+ task = await self._client.get_task(TaskQueryParams(id=task_id))
573
+ agent_output = self._handle_task(agent_input=agent_input, task=task)
574
+ if agent_output is not None:
575
+ # Reached a terminal state, return the output
576
+ return agent_output
577
+
578
+ elapsed = time.monotonic() - start_time
579
+ if elapsed >= max_wait:
580
+ structlogger.debug(
581
+ "a2a_agent.pool_task_until_terminal.timeout",
582
+ event_info="Polling task from A2A server timed out",
583
+ agent_name=self._name,
584
+ task_id=task_id,
585
+ elapsed=elapsed,
586
+ max_wait=max_wait,
587
+ )
588
+ return AgentOutput(
589
+ id=agent_input.id,
590
+ status=AgentStatus.FATAL_ERROR,
591
+ error_message="Polling timed out",
592
+ )
593
+
594
+ structlogger.error(
595
+ "a2a_agent.pool_task_until_terminal.waiting",
596
+ event_info="Task not in terminal state yet, waiting to poll again",
597
+ delay=delay,
598
+ agent_name=self._name,
599
+ task_id=task_id,
600
+ elapsed=elapsed,
601
+ max_wait=max_wait,
602
+ )
603
+ await asyncio.sleep(delay)
604
+ # Exponential backoff with cap
605
+ delay = min(delay * 2, max_delay)
606
+
607
+ except A2AClientError as exception:
608
+ structlogger.error(
609
+ "a2a_agent.pool_task_until_terminal.error",
610
+ event_info="Error during polling task from A2A server",
611
+ agent_name=self._name,
612
+ error=str(exception),
613
+ )
614
+ return AgentOutput(
615
+ id=agent_input.id,
616
+ status=AgentStatus.FATAL_ERROR,
617
+ error_message=f"Polling error: {exception!s}",
618
+ )
619
+
620
+ # ============================================================================
621
+ # Response Generation & Formatting
622
+ # ============================================================================
623
+
624
+ @staticmethod
625
+ def _generate_response_message_from_parts(parts: Optional[List[Part]]) -> str:
626
+ """Convert a list of Part objects to a single string message."""
627
+ result = ""
628
+ if not parts:
629
+ return result
630
+ for part in parts:
631
+ if isinstance(part.root, TextPart):
632
+ result += part.root.text + "\n"
633
+ elif isinstance(part.root, DataPart):
634
+ # DataPart results will be returned as a part of the structured results,
635
+ # we don't need to include it in the response message
636
+ continue
637
+ elif isinstance(part.root, FilePart) and isinstance(
638
+ part.root.file, FileWithUri
639
+ ):
640
+ # If the file is a FileWithUri, we can include the URI
641
+ result += f"File: {part.root.file.uri}\n"
642
+ else:
643
+ structlogger.warning(
644
+ "a2a_agent._parts_to_single_message.warning",
645
+ event_info="Unsupported part type encountered",
646
+ part_type=type(part.root),
647
+ )
648
+ return result.strip()
649
+
650
+ @staticmethod
651
+ def _generate_completed_response_message(task: Task) -> str:
652
+ """Generate a response message for a completed task.
653
+ In case of completed tasks, the final message might be in
654
+ the task status message or in the artifacts (or both).
655
+ """
656
+ # We need to preserve the order of the message,
657
+ # but also make sure to remove any duplicates.
658
+ result: List[str] = []
659
+ if task.status.message:
660
+ message = A2AAgent._generate_response_message_from_parts(
661
+ task.status.message.parts
662
+ )
663
+ if message and message not in result:
664
+ result.append(message)
665
+ if task.artifacts:
666
+ for artifact in task.artifacts:
667
+ message = A2AAgent._generate_response_message_from_parts(artifact.parts)
668
+ if message and message not in result:
669
+ result.append(message)
670
+ return "\n".join(result)
671
+
672
+ @staticmethod
673
+ def _generate_structured_results_from_artifacts(
674
+ agent_input: AgentInput, artifacts: List[Artifact]
675
+ ) -> Optional[List[List[Dict[str, Any]]]]:
676
+ structured_results_of_current_iteration: List[Dict[str, Any]] = []
677
+ # There might be multiple artifacts in the response, each of them might
678
+ # contain multiple parts. We will treat each DataPart in each artifact
679
+ # as a separate tool result. The tool name will be the agent ID + index
680
+ # of the artifact + index of the part.
681
+ # E.g., foo_0_1, foo_0_2, foo_1_0, etc.
682
+ for artifact_index, artifact in enumerate(artifacts):
683
+ for part_index, part in enumerate(artifact.parts):
684
+ if isinstance(part.root, DataPart) and len(part.root.data) > 0:
685
+ structured_result = {
686
+ "name": f"{agent_input.id}_{artifact_index}_{part_index}",
687
+ "type": "data",
688
+ "result": part.root.data,
689
+ }
690
+ structured_results_of_current_iteration.append(structured_result)
691
+ elif isinstance(part.root, FilePart) and isinstance(
692
+ part.root.file, FileWithUri
693
+ ):
694
+ structured_result = {
695
+ "name": f"{agent_input.id}_{artifact_index}_{part_index}",
696
+ "type": "file",
697
+ "result ": {
698
+ "uri": part.root.file.uri,
699
+ "name": part.root.file.name,
700
+ "mime_type": part.root.file.mime_type,
701
+ },
702
+ }
703
+ structured_results_of_current_iteration.append(structured_result)
704
+
705
+ previous_structured_results: List[List[Dict[str, Any]]] = (
706
+ agent_input.metadata.get(AGENT_METADATA_STRUCTURED_RESULTS_KEY, []) or []
707
+ )
708
+ previous_structured_results.append(structured_results_of_current_iteration)
709
+
710
+ return previous_structured_results
711
+
712
+ # ============================================================================
713
+ # Agent Card Management
714
+ # ============================================================================
715
+
716
+ @staticmethod
717
+ def _load_agent_card_from_file(agent_card_path: str) -> AgentCard:
718
+ """Load agent card from JSON file."""
719
+ try:
720
+ with open(os.path.abspath(agent_card_path), "r") as f:
721
+ card_data = json.load(f)
722
+ return AgentCard.model_validate(card_data)
723
+
724
+ except FileNotFoundError as e:
725
+ raise AgentInitializationException(
726
+ f"Agent card file not found: {agent_card_path}"
727
+ ) from e
728
+ except (IOError, PermissionError) as e:
729
+ raise AgentInitializationException(
730
+ f"Error reading agent card file {agent_card_path}: {e}"
731
+ ) from e
732
+ except json.JSONDecodeError as e:
733
+ raise AgentInitializationException(
734
+ f"Invalid JSON in agent card file {agent_card_path}: {e}"
735
+ ) from e
736
+ except ValidationError as e:
737
+ raise AgentInitializationException(
738
+ f"Failed to load agent card from {agent_card_path}: {e}"
739
+ ) from e
740
+
741
+ @staticmethod
742
+ async def _resolve_agent_card_with_retry(
743
+ agent_card_path: str, timeout: int, max_retries: int
744
+ ) -> AgentCard:
745
+ """Resolve the agent card from a given path or URL."""
746
+ # split agent_card_path into base URL and path
747
+ try:
748
+ url_parts = urlparse(agent_card_path)
749
+ base_url = f"{url_parts.scheme}://{url_parts.netloc}"
750
+ path = url_parts.path
751
+ except ValueError:
752
+ raise RasaException(f"Could not parse the URL: '{agent_card_path}'.")
753
+ structlogger.debug(
754
+ "a2a_agent.resolve_agent_card",
755
+ event_info="Resolving agent card from remote URL",
756
+ agent_card_path=agent_card_path,
757
+ base_url=base_url,
758
+ path=path,
759
+ timeout=timeout,
760
+ )
761
+
762
+ for attempt in range(max_retries):
763
+ if attempt > 0:
764
+ structlogger.debug(
765
+ "a2a_agent.resolve_agent_card.retrying",
766
+ agent_card_path=f"{base_url}/{path}",
767
+ attempt=attempt + 1,
768
+ num_retries=max_retries,
769
+ )
770
+
771
+ try:
772
+ agent_card = await A2ACardResolver(
773
+ httpx.AsyncClient(timeout=timeout),
774
+ base_url=base_url,
775
+ agent_card_path=path,
776
+ ).get_agent_card()
777
+
778
+ if agent_card:
779
+ return agent_card
780
+ except (A2AClientHTTPError, A2AClientJSONError) as exception:
781
+ structlogger.warning(
782
+ "a2a_agent.resolve_agent_card.error",
783
+ event_info="Error while resolving agent card",
784
+ agent_card_path=agent_card_path,
785
+ attempt=attempt + 1,
786
+ num_retries=max_retries,
787
+ error=str(exception),
788
+ )
789
+ if attempt < max_retries - 1:
790
+ # exponential backoff - wait longer with each retry
791
+ # 1 second, 2 seconds, 4 seconds, etc.
792
+ await asyncio.sleep(min(2**attempt, MAX_AGENT_RETRY_DELAY_SECONDS))
793
+
794
+ raise AgentInitializationException(
795
+ f"Failed to resolve agent card from {agent_card_path} after "
796
+ f"{max_retries} attempts."
797
+ )
798
+
799
+ # ============================================================================
800
+ # Client Initialization & Health Checks
801
+ # ============================================================================
802
+
803
+ def _init_client(self) -> Client:
804
+ _agent_manager = AgentAuthManager.load_auth(self._auth_config)
805
+ auth_strategy = _agent_manager.get_auth() if _agent_manager else None
806
+ factory = ClientFactory(
807
+ config=ClientConfig(
808
+ httpx_client=httpx.AsyncClient(
809
+ timeout=self._timeout, auth=auth_strategy
810
+ ),
811
+ streaming=True,
812
+ supported_transports=[
813
+ TransportProtocol.jsonrpc,
814
+ TransportProtocol.http_json,
815
+ TransportProtocol.grpc,
816
+ ],
817
+ accepted_output_modes=self.__SUPPORTED_OUTPUT_MODES,
818
+ )
819
+ )
820
+ return factory.create(self.agent_card)
821
+
822
+ async def _perform_health_check(self) -> None:
823
+ if not self._client or not self.agent_card:
824
+ structlogger.error(
825
+ "a2a_agent.health_check.error",
826
+ event_info="A2A client is not initialized. Call connect() first.",
827
+ )
828
+ raise AgentInitializationException("Client not initialized")
829
+
830
+ try:
831
+ test_message = Message(
832
+ role=Role.user,
833
+ parts=[Part(root=TextPart(text="hello"))],
834
+ message_id=str(uuid.uuid4()),
835
+ )
836
+ async for event in self._client.send_message(test_message):
837
+ if (
838
+ isinstance(event, Message)
839
+ or isinstance(event, tuple)
840
+ and len(event) == 2
841
+ and isinstance(event[0], Task)
842
+ ):
843
+ # We got a valid response, health check succeeded
844
+ return
845
+
846
+ event_info = "Unexpected response type during health check"
847
+ structlogger.error(
848
+ "a2a_agent.health_check.unexpected_response",
849
+ event_info=event_info,
850
+ agent_name=self._name,
851
+ response=event,
852
+ url=str(self.agent_card.url),
853
+ )
854
+ raise AgentInitializationException(f"{event_info}: {event}")
855
+ # If the loop completes with no return, no events were received
856
+ event_info = (
857
+ f"Health check failed for A2A agent '{self._name}' "
858
+ f"at {self.agent_card.url}: no events received"
859
+ )
860
+ structlogger.error(
861
+ "a2a_agent.health_check.no_events",
862
+ event_info=event_info,
863
+ agent_name=self._name,
864
+ url=str(self.agent_card.url),
865
+ )
866
+ raise AgentInitializationException(event_info)
867
+ except Exception as exception:
868
+ event_info = (
869
+ f"Health check failed for A2A agent '{self._name}' at "
870
+ f"{self.agent_card.url}: {exception!s}"
871
+ )
872
+ structlogger.error(
873
+ "a2a_agent.health_check.failed",
874
+ event_info=event_info,
875
+ agent_name=self._name,
876
+ error=str(exception),
877
+ url=str(self.agent_card.url),
878
+ )
879
+ raise AgentInitializationException(event_info) from exception