unique_toolkit 1.29.4__tar.gz → 1.31.0__tar.gz

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 (196) hide show
  1. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/CHANGELOG.md +6 -0
  2. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/PKG-INFO +7 -1
  3. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/pyproject.toml +8 -1
  4. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +37 -3
  5. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/config.py +21 -0
  6. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/display.py +40 -3
  7. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +388 -0
  8. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/__init__.py +3 -0
  9. unique_toolkit-1.31.0/unique_toolkit/app/fast_api_factory.py +131 -0
  10. unique_toolkit-1.31.0/unique_toolkit/app/webhook.py +77 -0
  11. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/infos.py +22 -1
  12. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/LICENSE +0 -0
  13. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/README.md +0 -0
  14. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/__init__.py +0 -0
  15. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/_base_service.py +0 -0
  16. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/_time_utils.py +0 -0
  17. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/api_calling/human_verification_manager.py +0 -0
  18. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/base_model_type_attribute.py +0 -0
  19. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/chunk_relevancy_sorter/config.py +0 -0
  20. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/chunk_relevancy_sorter/exception.py +0 -0
  21. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +0 -0
  22. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/chunk_relevancy_sorter/service.py +0 -0
  23. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +0 -0
  24. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/default_language_model.py +0 -0
  25. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/docx_generator/__init__.py +0 -0
  26. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/docx_generator/config.py +0 -0
  27. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/docx_generator/schemas.py +0 -0
  28. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/docx_generator/service.py +0 -0
  29. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
  30. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/endpoint_builder.py +0 -0
  31. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/endpoint_requestor.py +0 -0
  32. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/exception.py +0 -0
  33. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/experimental/endpoint_builder.py +0 -0
  34. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/experimental/endpoint_requestor.py +0 -0
  35. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/feature_flags/schema.py +0 -0
  36. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/pydantic/rjsf_tags.py +0 -0
  37. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/pydantic_helpers.py +0 -0
  38. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/referencing.py +0 -0
  39. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/string_utilities.py +0 -0
  40. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/tests/test_referencing.py +0 -0
  41. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/tests/test_string_utilities.py +0 -0
  42. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/token/image_token_counting.py +0 -0
  43. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/token/token_counting.py +0 -0
  44. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/__init__.py +0 -0
  45. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/files.py +0 -0
  46. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/image/encode.py +0 -0
  47. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/jinja/helpers.py +0 -0
  48. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/jinja/render.py +0 -0
  49. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/jinja/schema.py +0 -0
  50. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/jinja/utils.py +0 -0
  51. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/structured_output/__init__.py +0 -0
  52. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/structured_output/schema.py +0 -0
  53. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/utils/write_configuration.py +0 -0
  54. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/validate_required_values.py +0 -0
  55. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/_common/validators.py +0 -0
  56. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/__init__.py +0 -0
  57. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +0 -0
  58. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +0 -0
  59. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/config.py +0 -0
  60. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/context_relevancy/prompts.py +0 -0
  61. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/context_relevancy/schema.py +0 -0
  62. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/context_relevancy/service.py +0 -0
  63. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/evaluation_manager.py +0 -0
  64. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/exception.py +0 -0
  65. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/hallucination/constants.py +0 -0
  66. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +0 -0
  67. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/hallucination/prompts.py +0 -0
  68. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/hallucination/service.py +0 -0
  69. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/hallucination/utils.py +0 -0
  70. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/output_parser.py +0 -0
  71. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/schemas.py +0 -0
  72. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +0 -0
  73. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/evaluation/tests/test_output_parser.py +0 -0
  74. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/history_manager/history_construction_with_contents.py +0 -0
  75. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/history_manager/history_manager.py +0 -0
  76. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/history_manager/loop_token_reducer.py +0 -0
  77. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/history_manager/utils.py +0 -0
  78. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/message_log_manager/__init__.py +0 -0
  79. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/message_log_manager/service.py +0 -0
  80. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/postprocessor/postprocessor_manager.py +0 -0
  81. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/reference_manager/reference_manager.py +0 -0
  82. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/responses_api/__init__.py +0 -0
  83. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/responses_api/postprocessors/code_display.py +0 -0
  84. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +0 -0
  85. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/responses_api/stream_handler.py +0 -0
  86. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +0 -0
  87. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/thinking_manager/thinking_manager.py +0 -0
  88. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/__init__.py +0 -0
  89. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/__init__.py +0 -0
  90. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/config.py +0 -0
  91. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +0 -0
  92. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +0 -0
  93. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/evaluation/config.py +0 -0
  94. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +0 -0
  95. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +0 -0
  96. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/manager.py +0 -0
  97. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +0 -0
  98. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +0 -0
  99. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/references.py +0 -0
  100. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +0 -0
  101. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/prompts.py +0 -0
  102. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +0 -0
  103. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/response_watcher/service.py +0 -0
  104. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/tool/__init__.py +0 -0
  105. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/tool/_memory.py +0 -0
  106. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/tool/_schema.py +0 -0
  107. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/tool/config.py +0 -0
  108. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/a2a/tool/service.py +0 -0
  109. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/agent_chunks_hanlder.py +0 -0
  110. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/config.py +0 -0
  111. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/factory.py +0 -0
  112. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/mcp/__init__.py +0 -0
  113. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/mcp/manager.py +0 -0
  114. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/mcp/models.py +0 -0
  115. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/mcp/tool_wrapper.py +0 -0
  116. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/__init__.py +0 -0
  117. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/base.py +0 -0
  118. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +0 -0
  119. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +0 -0
  120. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +0 -0
  121. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/openai_builtin/manager.py +0 -0
  122. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/schemas.py +0 -0
  123. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/test/test_mcp_manager.py +0 -0
  124. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/test/test_tool_manager.py +0 -0
  125. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +0 -0
  126. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/tool.py +0 -0
  127. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/tool_manager.py +0 -0
  128. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/tool_progress_reporter.py +0 -0
  129. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/__init__.py +0 -0
  130. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/execution/__init__.py +0 -0
  131. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/execution/execution.py +0 -0
  132. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
  133. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/source_handling/schema.py +0 -0
  134. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +0 -0
  135. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +0 -0
  136. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/dev_util.py +0 -0
  137. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/init_logging.py +0 -0
  138. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/init_sdk.py +0 -0
  139. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/performance/async_tasks.py +0 -0
  140. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/performance/async_wrapper.py +0 -0
  141. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/schemas.py +0 -0
  142. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/unique_settings.py +0 -0
  143. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/app/verification.py +0 -0
  144. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/__init__.py +0 -0
  145. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/constants.py +0 -0
  146. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/deprecated/service.py +0 -0
  147. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/functions.py +0 -0
  148. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/rendering.py +0 -0
  149. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/responses_api.py +0 -0
  150. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/schemas.py +0 -0
  151. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/service.py +0 -0
  152. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/state.py +0 -0
  153. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/chat/utils.py +0 -0
  154. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/__init__.py +0 -0
  155. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/constants.py +0 -0
  156. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/functions.py +0 -0
  157. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/schemas.py +0 -0
  158. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/service.py +0 -0
  159. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/smart_rules.py +0 -0
  160. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/content/utils.py +0 -0
  161. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/__init__.py +0 -0
  162. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/constants.py +0 -0
  163. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/functions.py +0 -0
  164. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/schemas.py +0 -0
  165. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/service.py +0 -0
  166. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/embedding/utils.py +0 -0
  167. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/__init__.py +0 -0
  168. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/langchain/client.py +0 -0
  169. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/langchain/history.py +0 -0
  170. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/openai/__init__.py +0 -0
  171. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/openai/client.py +0 -0
  172. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/openai/message_builder.py +0 -0
  173. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/framework_utilities/utils.py +0 -0
  174. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/__init__.py +0 -0
  175. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/_responses_api_utils.py +0 -0
  176. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/builder.py +0 -0
  177. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/constants.py +0 -0
  178. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/default_language_model.py +0 -0
  179. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/functions.py +0 -0
  180. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/prompt.py +0 -0
  181. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/reference.py +0 -0
  182. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/schemas.py +0 -0
  183. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/service.py +0 -0
  184. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/language_model/utils.py +0 -0
  185. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/protocols/support.py +0 -0
  186. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/services/__init__.py +0 -0
  187. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/services/chat_service.py +0 -0
  188. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/services/knowledge_base.py +0 -0
  189. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/short_term_memory/__init__.py +0 -0
  190. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/short_term_memory/constants.py +0 -0
  191. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/short_term_memory/functions.py +0 -0
  192. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/short_term_memory/schemas.py +0 -0
  193. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/short_term_memory/service.py +0 -0
  194. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/smart_rules/__init__.py +0 -0
  195. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/smart_rules/compile.py +0 -0
  196. {unique_toolkit-1.29.4 → unique_toolkit-1.31.0}/unique_toolkit/test_utilities/events.py +0 -0
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.31.0] - 2025-11-20
9
+ - Adding model `litellm:anthropic-claude-opus-4-5` to `language_model/info.py`
10
+
11
+ ## [1.30.0] - 2025-11-26
12
+ - Add option to only display parts of sub agent responses.
13
+
8
14
  ## [1.29.4] - 2025-11-25
9
15
  - Add display name to openai builtin tools
10
16
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: unique_toolkit
3
- Version: 1.29.4
3
+ Version: 1.31.0
4
4
  Summary:
5
5
  License: Proprietary
6
6
  Author: Cedric Klinkert
@@ -121,6 +121,12 @@ All notable changes to this project will be documented in this file.
121
121
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
122
122
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
123
123
 
124
+ ## [1.31.0] - 2025-11-20
125
+ - Adding model `litellm:anthropic-claude-opus-4-5` to `language_model/info.py`
126
+
127
+ ## [1.30.0] - 2025-11-26
128
+ - Add option to only display parts of sub agent responses.
129
+
124
130
  ## [1.29.4] - 2025-11-25
125
131
  - Add display name to openai builtin tools
126
132
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "unique_toolkit"
3
- version = "1.29.4"
3
+ version = "1.31.0"
4
4
  description = ""
5
5
  authors = [
6
6
  "Cedric Klinkert <cedric.klinkert@unique.ch>",
@@ -45,6 +45,13 @@ openai = "^1.97.0"
45
45
  langchain = "^0.3.27"
46
46
  langchain-openai = "^0.3.28"
47
47
 
48
+ [tool.poetry.group.fastapi]
49
+ optional = true
50
+
51
+ [tool.poetry.group.fastapi.dependencies]
52
+ fastapi = "^0.119.0"
53
+ uvicorn = "^0.37.0"
54
+
48
55
  [tool.poetry.group.dev.dependencies]
49
56
  ruff = "0.12.10"
50
57
  pytest = "^7.4.3"
@@ -1,5 +1,5 @@
1
1
  import re
2
- from typing import Literal
2
+ from typing import Literal, NamedTuple
3
3
 
4
4
  from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
5
5
  SubAgentDisplayConfig,
@@ -126,7 +126,7 @@ def _get_display_template(
126
126
  if add_block_border:
127
127
  template = _wrap_with_block_border(template)
128
128
 
129
- return template
129
+ return template.strip()
130
130
 
131
131
 
132
132
  def _get_display_removal_re(
@@ -150,10 +150,40 @@ def _get_display_removal_re(
150
150
  return re.compile(pattern, flags=re.DOTALL)
151
151
 
152
152
 
153
+ class SubAgentAnswerPart(NamedTuple):
154
+ matching_text: str # Matching text as found in the answer
155
+ formatted_text: str # Formatted text to be displayed
156
+
157
+
158
+ def get_sub_agent_answer_parts(
159
+ answer: str,
160
+ display_config: SubAgentDisplayConfig,
161
+ ) -> list[SubAgentAnswerPart]:
162
+ if display_config.mode == SubAgentResponseDisplayMode.HIDDEN:
163
+ return []
164
+
165
+ if len(display_config.answer_substrings_config) == 0:
166
+ return [SubAgentAnswerPart(matching_text=answer, formatted_text=answer)]
167
+
168
+ substrings = []
169
+ for config in display_config.answer_substrings_config:
170
+ match = re.search(config.regexp, answer)
171
+ if match is not None:
172
+ text = match.group(0)
173
+ substrings.append(
174
+ SubAgentAnswerPart(
175
+ matching_text=text,
176
+ formatted_text=config.display_template.format(text),
177
+ )
178
+ )
179
+
180
+ return substrings
181
+
182
+
153
183
  def get_sub_agent_answer_display(
154
184
  display_name: str,
155
185
  display_config: SubAgentDisplayConfig,
156
- answer: str,
186
+ answer: str | list[str],
157
187
  assistant_id: str,
158
188
  ) -> str:
159
189
  template = _get_display_template(
@@ -162,6 +192,10 @@ def get_sub_agent_answer_display(
162
192
  add_block_border=display_config.add_block_border,
163
193
  display_title_template=display_config.display_title_template,
164
194
  )
195
+
196
+ if isinstance(answer, list):
197
+ answer = display_config.answer_substrings_separator.join(answer)
198
+
165
199
  return template.format(
166
200
  display_name=display_name, answer=answer, assistant_id=assistant_id
167
201
  )
@@ -13,6 +13,18 @@ class SubAgentResponseDisplayMode(StrEnum):
13
13
  PLAIN = "plain"
14
14
 
15
15
 
16
+ class SubAgentAnswerSubstringConfig(BaseModel):
17
+ model_config = get_configuration_dict()
18
+
19
+ regexp: str = Field(
20
+ description="The regular expression to use to extract the substring. The first capture group will always be used.",
21
+ )
22
+ display_template: str = Field(
23
+ default="{}",
24
+ description="The template to use to display the substring. It should contain exactly one empty placeholder '{}' for the substring.",
25
+ )
26
+
27
+
16
28
  class SubAgentDisplayConfig(BaseModel):
17
29
  model_config = get_configuration_dict()
18
30
 
@@ -47,3 +59,12 @@ class SubAgentDisplayConfig(BaseModel):
47
59
  default=False,
48
60
  description="If set, the sub agent references will be added to the main agent response references even in not mentioned in the main agent response text.",
49
61
  )
62
+
63
+ answer_substrings_config: list[SubAgentAnswerSubstringConfig] = Field(
64
+ default=[],
65
+ description="If set, only parts of the answer matching the provided regular expressions will be displayed.",
66
+ )
67
+ answer_substrings_separator: str = Field(
68
+ default="\n",
69
+ description="The separator to use between the substrings.",
70
+ )
@@ -10,6 +10,7 @@ from unique_toolkit._common.pydantic_helpers import get_configuration_dict
10
10
  from unique_toolkit.agentic.postprocessor.postprocessor_manager import Postprocessor
11
11
  from unique_toolkit.agentic.tools.a2a.postprocessing._display_utils import (
12
12
  get_sub_agent_answer_display,
13
+ get_sub_agent_answer_parts,
13
14
  remove_sub_agent_answer_from_text,
14
15
  )
15
16
  from unique_toolkit.agentic.tools.a2a.postprocessing._ref_utils import (
@@ -44,6 +45,15 @@ class SubAgentResponsesPostprocessorConfig(BaseModel):
44
45
  default=1, description="Time to sleep before updating the main agent message."
45
46
  )
46
47
 
48
+ remove_duplicate_answers: bool = Field(
49
+ default=False,
50
+ description="If set, duplicate answers will only be displayed once. If sub agent is configured to display only substrings, this will remove duplicate substrings across different responses.",
51
+ )
52
+ answer_separator: str = Field(
53
+ default="",
54
+ description="The separator to use between the different sub agent answers.",
55
+ )
56
+
47
57
 
48
58
  class SubAgentResponsesDisplayPostprocessor(Postprocessor):
49
59
  def __init__(
@@ -96,6 +106,8 @@ class SubAgentResponsesDisplayPostprocessor(Postprocessor):
96
106
  answers_displayed_before = []
97
107
  answers_displayed_after = []
98
108
 
109
+ all_answers_displayed = set()
110
+
99
111
  for assistant_id, responses in displayed_sub_agent_responses.items():
100
112
  for response in responses:
101
113
  message = response.message
@@ -105,6 +117,9 @@ class SubAgentResponsesDisplayPostprocessor(Postprocessor):
105
117
  loop_response=loop_response, response=message
106
118
  )
107
119
 
120
+ if tool_info.display_config.mode == SubAgentResponseDisplayMode.HIDDEN:
121
+ continue
122
+
108
123
  display_name = tool_info.display_name
109
124
  if len(responses) > 1:
110
125
  display_name += f" {response.sequence_number}"
@@ -116,13 +131,33 @@ class SubAgentResponsesDisplayPostprocessor(Postprocessor):
116
131
  response.sequence_number,
117
132
  )
118
133
 
119
- if tool_info.display_config.mode == SubAgentResponseDisplayMode.HIDDEN:
134
+ message_text = message["text"] or ""
135
+
136
+ answer_parts = get_sub_agent_answer_parts(
137
+ answer=message_text,
138
+ display_config=tool_info.display_config,
139
+ )
140
+
141
+ if len(answer_parts) == 0:
120
142
  continue
121
143
 
144
+ answer_display_texts = []
145
+ if self._config.remove_duplicate_answers:
146
+ for answer_part in answer_parts:
147
+ if answer_part.matching_text in all_answers_displayed:
148
+ continue
149
+
150
+ all_answers_displayed.add(answer_part.matching_text)
151
+ answer_display_texts.append(answer_part.formatted_text)
152
+ else:
153
+ answer_display_texts = [
154
+ answer_part.formatted_text for answer_part in answer_parts
155
+ ]
156
+
122
157
  answer = get_sub_agent_answer_display(
123
158
  display_name=display_name,
124
159
  display_config=tool_info.display_config,
125
- answer=message["text"] or "",
160
+ answer=answer_display_texts,
126
161
  assistant_id=assistant_id,
127
162
  )
128
163
 
@@ -135,6 +170,7 @@ class SubAgentResponsesDisplayPostprocessor(Postprocessor):
135
170
  text=loop_response.message.text,
136
171
  answers_before=answers_displayed_before,
137
172
  answers_after=answers_displayed_after,
173
+ sep=self._config.answer_separator,
138
174
  )
139
175
 
140
176
  return True
@@ -182,4 +218,5 @@ def _get_final_answer_display(
182
218
 
183
219
  if len(answers_after) > 0:
184
220
  text = text + sep + sep.join(answers_after)
185
- return text
221
+
222
+ return text.strip()
@@ -16,9 +16,11 @@ from unique_toolkit.agentic.tools.a2a.postprocessing._display_utils import (
16
16
  _wrap_with_details_tag,
17
17
  _wrap_with_quote_border,
18
18
  get_sub_agent_answer_display,
19
+ get_sub_agent_answer_parts,
19
20
  remove_sub_agent_answer_from_text,
20
21
  )
21
22
  from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
23
+ SubAgentAnswerSubstringConfig,
22
24
  SubAgentDisplayConfig,
23
25
  SubAgentResponseDisplayMode,
24
26
  )
@@ -1333,3 +1335,389 @@ def test_remove_sub_agent_answer__no_op_when_assistant_not_found() -> None:
1333
1335
  assert result == original_text
1334
1336
  assert "Present answer" in result
1335
1337
  assert "Present Agent" in result
1338
+
1339
+
1340
+ # Test get_sub_agent_answer_parts
1341
+
1342
+
1343
+ @pytest.mark.ai
1344
+ def test_get_sub_agent_answer_parts__returns_empty__when_hidden_mode() -> None:
1345
+ """
1346
+ Purpose: Verify empty list returned for HIDDEN display mode.
1347
+ Why this matters: Hidden mode should not extract any answer parts.
1348
+ Setup summary: Set mode to HIDDEN, assert empty list.
1349
+ """
1350
+ # Arrange
1351
+ answer = "Some answer text"
1352
+ config = SubAgentDisplayConfig(
1353
+ mode=SubAgentResponseDisplayMode.HIDDEN,
1354
+ )
1355
+
1356
+ # Act
1357
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1358
+
1359
+ # Assert
1360
+ assert result == []
1361
+
1362
+
1363
+ @pytest.mark.ai
1364
+ def test_get_sub_agent_answer_parts__returns_full_answer__when_no_config() -> None:
1365
+ """
1366
+ Purpose: Verify full answer returned when no substring config provided.
1367
+ Why this matters: Default behavior should return entire answer.
1368
+ Setup summary: Provide answer without substring config, assert full answer.
1369
+ """
1370
+ # Arrange
1371
+ answer = "This is the complete answer"
1372
+ config = SubAgentDisplayConfig(
1373
+ mode=SubAgentResponseDisplayMode.PLAIN,
1374
+ answer_substrings_config=[],
1375
+ )
1376
+
1377
+ # Act
1378
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1379
+
1380
+ # Assert
1381
+ assert len(result) == 1
1382
+ assert result[0].matching_text == answer
1383
+ assert result[0].formatted_text == answer
1384
+
1385
+
1386
+ @pytest.mark.ai
1387
+ def test_get_sub_agent_answer_parts__extracts_single_match__with_one_regexp() -> None:
1388
+ """
1389
+ Purpose: Verify single substring extracted with one regexp config.
1390
+ Why this matters: Core functionality for extracting specific answer parts.
1391
+ Setup summary: Provide answer with single regexp config, assert match extracted.
1392
+ """
1393
+ # Arrange
1394
+ answer = "The price is $42.99 for the item"
1395
+ config = SubAgentDisplayConfig(
1396
+ mode=SubAgentResponseDisplayMode.PLAIN,
1397
+ answer_substrings_config=[
1398
+ SubAgentAnswerSubstringConfig(regexp=r"\$\d+\.\d+"),
1399
+ ],
1400
+ )
1401
+
1402
+ # Act
1403
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1404
+
1405
+ # Assert
1406
+ assert len(result) == 1
1407
+ assert result[0].matching_text == "$42.99"
1408
+ assert result[0].formatted_text == "$42.99"
1409
+
1410
+
1411
+ @pytest.mark.ai
1412
+ def test_get_sub_agent_answer_parts__extracts_multiple_matches__with_multiple_regexps() -> (
1413
+ None
1414
+ ):
1415
+ """
1416
+ Purpose: Verify multiple substrings extracted with multiple regexp configs.
1417
+ Why this matters: Supports extracting different types of information.
1418
+ Setup summary: Provide answer with multiple regexp configs, assert all matches.
1419
+ """
1420
+ # Arrange
1421
+ answer = "Contact John at john@example.com or call 555-1234"
1422
+ config = SubAgentDisplayConfig(
1423
+ mode=SubAgentResponseDisplayMode.PLAIN,
1424
+ answer_substrings_config=[
1425
+ SubAgentAnswerSubstringConfig(
1426
+ regexp=r"\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b"
1427
+ ),
1428
+ SubAgentAnswerSubstringConfig(regexp=r"\d{3}-\d{4}"),
1429
+ ],
1430
+ )
1431
+
1432
+ # Act
1433
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1434
+
1435
+ # Assert
1436
+ assert len(result) == 2
1437
+ assert result[0].matching_text == "john@example.com"
1438
+ assert result[1].matching_text == "555-1234"
1439
+
1440
+
1441
+ @pytest.mark.ai
1442
+ def test_get_sub_agent_answer_parts__applies_display_template__to_matched_text() -> (
1443
+ None
1444
+ ):
1445
+ """
1446
+ Purpose: Verify display template is applied to format matched text.
1447
+ Why this matters: Allows customization of how extracted parts are displayed.
1448
+ Setup summary: Provide template with placeholder, assert formatted output.
1449
+ """
1450
+ # Arrange
1451
+ answer = "The temperature is 72 degrees"
1452
+ config = SubAgentDisplayConfig(
1453
+ mode=SubAgentResponseDisplayMode.PLAIN,
1454
+ answer_substrings_config=[
1455
+ SubAgentAnswerSubstringConfig(
1456
+ regexp=r"\d+",
1457
+ display_template="Temperature: {}°F",
1458
+ ),
1459
+ ],
1460
+ )
1461
+
1462
+ # Act
1463
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1464
+
1465
+ # Assert
1466
+ assert len(result) == 1
1467
+ assert result[0].matching_text == "72"
1468
+ assert result[0].formatted_text == "Temperature: 72°F"
1469
+
1470
+
1471
+ @pytest.mark.ai
1472
+ def test_get_sub_agent_answer_parts__returns_empty_list__when_no_matches() -> None:
1473
+ """
1474
+ Purpose: Verify empty list returned when regexp doesn't match answer.
1475
+ Why this matters: Handles cases where expected pattern not present.
1476
+ Setup summary: Provide regexp that doesn't match, assert empty list.
1477
+ """
1478
+ # Arrange
1479
+ answer = "This is plain text without numbers"
1480
+ config = SubAgentDisplayConfig(
1481
+ mode=SubAgentResponseDisplayMode.PLAIN,
1482
+ answer_substrings_config=[
1483
+ SubAgentAnswerSubstringConfig(regexp=r"\d+"),
1484
+ ],
1485
+ )
1486
+
1487
+ # Act
1488
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1489
+
1490
+ # Assert
1491
+ assert result == []
1492
+
1493
+
1494
+ @pytest.mark.ai
1495
+ def test_get_sub_agent_answer_parts__extracts_first_match_only__for_each_regexp() -> (
1496
+ None
1497
+ ):
1498
+ """
1499
+ Purpose: Verify only first match per regexp is extracted.
1500
+ Why this matters: Function uses re.search which finds first occurrence.
1501
+ Setup summary: Provide answer with multiple numbers, assert only first extracted.
1502
+ """
1503
+ # Arrange
1504
+ answer = "First number is 42 and second is 99"
1505
+ config = SubAgentDisplayConfig(
1506
+ mode=SubAgentResponseDisplayMode.PLAIN,
1507
+ answer_substrings_config=[
1508
+ SubAgentAnswerSubstringConfig(regexp=r"\d+"),
1509
+ ],
1510
+ )
1511
+
1512
+ # Act
1513
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1514
+
1515
+ # Assert
1516
+ assert len(result) == 1
1517
+ assert result[0].matching_text == "42"
1518
+
1519
+
1520
+ @pytest.mark.ai
1521
+ def test_get_sub_agent_answer_parts__handles_empty_answer__with_no_config() -> None:
1522
+ """
1523
+ Purpose: Verify empty answer returned as single part when no config.
1524
+ Why this matters: Edge case handling for empty content.
1525
+ Setup summary: Provide empty answer, assert single empty part.
1526
+ """
1527
+ # Arrange
1528
+ answer = ""
1529
+ config = SubAgentDisplayConfig(
1530
+ mode=SubAgentResponseDisplayMode.PLAIN,
1531
+ answer_substrings_config=[],
1532
+ )
1533
+
1534
+ # Act
1535
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1536
+
1537
+ # Assert
1538
+ assert len(result) == 1
1539
+ assert result[0].matching_text == ""
1540
+ assert result[0].formatted_text == ""
1541
+
1542
+
1543
+ @pytest.mark.ai
1544
+ def test_get_sub_agent_answer_parts__handles_empty_answer__with_regexp_config() -> None:
1545
+ """
1546
+ Purpose: Verify empty list returned for empty answer with regexp config.
1547
+ Why this matters: No matches possible in empty string.
1548
+ Setup summary: Provide empty answer with regexp, assert empty list.
1549
+ """
1550
+ # Arrange
1551
+ answer = ""
1552
+ config = SubAgentDisplayConfig(
1553
+ mode=SubAgentResponseDisplayMode.PLAIN,
1554
+ answer_substrings_config=[
1555
+ SubAgentAnswerSubstringConfig(regexp=r"\d+"),
1556
+ ],
1557
+ )
1558
+
1559
+ # Act
1560
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1561
+
1562
+ # Assert
1563
+ assert result == []
1564
+
1565
+
1566
+ @pytest.mark.ai
1567
+ def test_get_sub_agent_answer_parts__handles_multiline_answer__with_regexp() -> None:
1568
+ """
1569
+ Purpose: Verify regexp matching works across multiple lines.
1570
+ Why this matters: Answers can span multiple lines.
1571
+ Setup summary: Provide multiline answer with pattern, assert match found.
1572
+ """
1573
+ # Arrange
1574
+ answer = "Line 1\nThe code is ABC123\nLine 3"
1575
+ config = SubAgentDisplayConfig(
1576
+ mode=SubAgentResponseDisplayMode.PLAIN,
1577
+ answer_substrings_config=[
1578
+ SubAgentAnswerSubstringConfig(regexp=r"[A-Z]{3}\d{3}"),
1579
+ ],
1580
+ )
1581
+
1582
+ # Act
1583
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1584
+
1585
+ # Assert
1586
+ assert len(result) == 1
1587
+ assert result[0].matching_text == "ABC123"
1588
+
1589
+
1590
+ @pytest.mark.ai
1591
+ def test_get_sub_agent_answer_parts__handles_special_regex_chars__in_answer() -> None:
1592
+ """
1593
+ Purpose: Verify regexp can match content with special regex characters.
1594
+ Why this matters: Answers may contain special characters.
1595
+ Setup summary: Provide answer with special chars, use proper escaping in regexp.
1596
+ """
1597
+ # Arrange
1598
+ answer = "The expression is: [test] (value)"
1599
+ config = SubAgentDisplayConfig(
1600
+ mode=SubAgentResponseDisplayMode.PLAIN,
1601
+ answer_substrings_config=[
1602
+ SubAgentAnswerSubstringConfig(regexp=r"\[test\]"),
1603
+ ],
1604
+ )
1605
+
1606
+ # Act
1607
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1608
+
1609
+ # Assert
1610
+ assert len(result) == 1
1611
+ assert result[0].matching_text == "[test]"
1612
+
1613
+
1614
+ @pytest.mark.ai
1615
+ def test_get_sub_agent_answer_parts__skips_non_matching_configs__returns_matches_only() -> (
1616
+ None
1617
+ ):
1618
+ """
1619
+ Purpose: Verify only matching regexp configs produce results.
1620
+ Why this matters: Should not fail on partial matches, only return what matches.
1621
+ Setup summary: Provide multiple configs where only some match, assert partial results.
1622
+ """
1623
+ # Arrange
1624
+ answer = "Value is 42"
1625
+ config = SubAgentDisplayConfig(
1626
+ mode=SubAgentResponseDisplayMode.PLAIN,
1627
+ answer_substrings_config=[
1628
+ SubAgentAnswerSubstringConfig(regexp=r"\d+"), # Matches
1629
+ SubAgentAnswerSubstringConfig(regexp=r"[A-Z]{3}"), # Doesn't match
1630
+ SubAgentAnswerSubstringConfig(regexp=r"Value"), # Matches
1631
+ ],
1632
+ )
1633
+
1634
+ # Act
1635
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1636
+
1637
+ # Assert
1638
+ assert len(result) == 2
1639
+ assert result[0].matching_text == "42"
1640
+ assert result[1].matching_text == "Value"
1641
+
1642
+
1643
+ @pytest.mark.ai
1644
+ def test_get_sub_agent_answer_parts__preserves_order__of_configs_not_matches() -> None:
1645
+ """
1646
+ Purpose: Verify results follow config order, not match order in text.
1647
+ Why this matters: Predictable output order based on configuration.
1648
+ Setup summary: Provide configs in specific order, assert results match config order.
1649
+ """
1650
+ # Arrange
1651
+ answer = "first 123 then abc"
1652
+ config = SubAgentDisplayConfig(
1653
+ mode=SubAgentResponseDisplayMode.PLAIN,
1654
+ answer_substrings_config=[
1655
+ SubAgentAnswerSubstringConfig(regexp=r"[a-z]{3,}"), # Matches "first"
1656
+ SubAgentAnswerSubstringConfig(regexp=r"\d+"), # Matches "123"
1657
+ ],
1658
+ )
1659
+
1660
+ # Act
1661
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1662
+
1663
+ # Assert
1664
+ assert len(result) == 2
1665
+ # Results follow config order, not text order
1666
+ assert result[0].matching_text == "first"
1667
+ assert result[1].matching_text == "123"
1668
+
1669
+
1670
+ @pytest.mark.ai
1671
+ def test_get_sub_agent_answer_parts__handles_complex_template__with_multiple_placeholders() -> (
1672
+ None
1673
+ ):
1674
+ """
1675
+ Purpose: Verify complex display templates with formatting work correctly.
1676
+ Why this matters: Supports rich formatting of extracted content.
1677
+ Setup summary: Provide template with additional text, assert formatted correctly.
1678
+ """
1679
+ # Arrange
1680
+ answer = "User score: 95"
1681
+ config = SubAgentDisplayConfig(
1682
+ mode=SubAgentResponseDisplayMode.PLAIN,
1683
+ answer_substrings_config=[
1684
+ SubAgentAnswerSubstringConfig(
1685
+ regexp=r"\d+",
1686
+ display_template="**Score: {}%**",
1687
+ ),
1688
+ ],
1689
+ )
1690
+
1691
+ # Act
1692
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1693
+
1694
+ # Assert
1695
+ assert len(result) == 1
1696
+ assert result[0].matching_text == "95"
1697
+ assert result[0].formatted_text == "**Score: 95%**"
1698
+
1699
+
1700
+ @pytest.mark.ai
1701
+ def test_get_sub_agent_answer_parts__works_with_details_modes__extracts_normally() -> (
1702
+ None
1703
+ ):
1704
+ """
1705
+ Purpose: Verify extraction works regardless of display mode (except HIDDEN).
1706
+ Why this matters: Substring extraction independent of display mode.
1707
+ Setup summary: Use DETAILS modes, assert extraction still works.
1708
+ """
1709
+ # Arrange
1710
+ answer = "Result: SUCCESS"
1711
+ config = SubAgentDisplayConfig(
1712
+ mode=SubAgentResponseDisplayMode.DETAILS_CLOSED,
1713
+ answer_substrings_config=[
1714
+ SubAgentAnswerSubstringConfig(regexp=r"SUCCESS"),
1715
+ ],
1716
+ )
1717
+
1718
+ # Act
1719
+ result = get_sub_agent_answer_parts(answer=answer, display_config=config)
1720
+
1721
+ # Assert
1722
+ assert len(result) == 1
1723
+ assert result[0].matching_text == "SUCCESS"
@@ -47,5 +47,8 @@ from .schemas import (
47
47
  from .verification import (
48
48
  verify_signature_and_construct_event as verify_signature_and_construct_event,
49
49
  )
50
+ from .webhook import (
51
+ is_webhook_signature_valid as is_webhook_signature_valid,
52
+ )
50
53
 
51
54
  DOMAIN_NAME = "app"