haystack-ai 3.2.0.dev20260902000321__tar.gz → 3.2.0.dev20260903000331__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 (295) hide show
  1. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/PKG-INFO +1 -1
  2. haystack_ai-3.2.0.dev20260903000331/VERSION.txt +1 -0
  3. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/output_adapter.py +5 -1
  4. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/openai_responses.py +8 -4
  5. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/recursive_splitter.py +7 -7
  6. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/conditional_router.py +5 -1
  7. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/chat_message.py +11 -4
  8. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/hooks.py +132 -85
  9. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/stores.py +25 -8
  10. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/types/protocol.py +28 -9
  11. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/hf.py +6 -3
  12. haystack_ai-3.2.0.dev20260902000321/VERSION.txt +0 -1
  13. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/.gitignore +0 -0
  14. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/LICENSE +0 -0
  15. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/README.md +0 -0
  16. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/__init__.py +0 -0
  17. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/__init__.py +0 -0
  18. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/__init__.py +0 -0
  19. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/agent.py +0 -0
  20. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/state/__init__.py +0 -0
  21. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/state/state.py +0 -0
  22. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/state/state_utils.py +0 -0
  23. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/tool_calling.py +0 -0
  24. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/agents/utils.py +0 -0
  25. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/builders/__init__.py +0 -0
  26. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/builders/answer_builder.py +0 -0
  27. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/builders/chat_prompt_builder.py +0 -0
  28. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/builders/prompt_builder.py +0 -0
  29. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/caching/__init__.py +0 -0
  30. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/caching/cache_checker.py +0 -0
  31. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/__init__.py +0 -0
  32. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/csv.py +0 -0
  33. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/docx.py +0 -0
  34. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/file_to_file_content.py +0 -0
  35. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/html.py +0 -0
  36. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/__init__.py +0 -0
  37. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/document_to_image.py +0 -0
  38. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/file_to_document.py +0 -0
  39. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/file_to_image.py +0 -0
  40. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/image_utils.py +0 -0
  41. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/image/pdf_to_image.py +0 -0
  42. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/json.py +0 -0
  43. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/markdown.py +0 -0
  44. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/msg.py +0 -0
  45. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/multi_file_converter.py +0 -0
  46. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/pdfminer.py +0 -0
  47. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/pptx.py +0 -0
  48. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/pypdf.py +0 -0
  49. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/txt.py +0 -0
  50. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/utils.py +0 -0
  51. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/converters/xlsx.py +0 -0
  52. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/__init__.py +0 -0
  53. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/azure_document_embedder.py +0 -0
  54. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/azure_text_embedder.py +0 -0
  55. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/mock_document_embedder.py +0 -0
  56. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/mock_text_embedder.py +0 -0
  57. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/mock_utils.py +0 -0
  58. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/openai_document_embedder.py +0 -0
  59. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/openai_text_embedder.py +0 -0
  60. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/types/__init__.py +0 -0
  61. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/embedders/types/protocol.py +0 -0
  62. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/__init__.py +0 -0
  63. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/answer_exact_match.py +0 -0
  64. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/context_relevance.py +0 -0
  65. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/document_map.py +0 -0
  66. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/document_mrr.py +0 -0
  67. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/document_ndcg.py +0 -0
  68. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/document_recall.py +0 -0
  69. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/faithfulness.py +0 -0
  70. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/llm_evaluator.py +0 -0
  71. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/evaluators/sas_evaluator.py +0 -0
  72. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/extractors/__init__.py +0 -0
  73. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/extractors/image/__init__.py +0 -0
  74. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/extractors/image/llm_document_content_extractor.py +0 -0
  75. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/extractors/llm_metadata_extractor.py +0 -0
  76. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/extractors/regex_text_extractor.py +0 -0
  77. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/fetchers/__init__.py +0 -0
  78. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/fetchers/link_content.py +0 -0
  79. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/__init__.py +0 -0
  80. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/__init__.py +0 -0
  81. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/azure.py +0 -0
  82. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/azure_responses.py +0 -0
  83. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/fallback.py +0 -0
  84. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/llm.py +0 -0
  85. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/mock.py +0 -0
  86. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/openai.py +0 -0
  87. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/types/__init__.py +0 -0
  88. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/chat/types/protocol.py +0 -0
  89. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/openai_image_generator.py +0 -0
  90. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/generators/utils.py +0 -0
  91. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/__init__.py +0 -0
  92. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/answer_joiner.py +0 -0
  93. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/branch.py +0 -0
  94. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/document_joiner.py +0 -0
  95. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/list_joiner.py +0 -0
  96. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/joiners/string_joiner.py +0 -0
  97. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/__init__.py +0 -0
  98. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/csv_document_cleaner.py +0 -0
  99. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/csv_document_splitter.py +0 -0
  100. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/document_cleaner.py +0 -0
  101. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/document_preprocessor.py +0 -0
  102. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/document_splitter.py +0 -0
  103. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/embedding_based_document_splitter.py +0 -0
  104. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/hierarchical_document_splitter.py +0 -0
  105. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/markdown_header_splitter.py +0 -0
  106. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/python_code_splitter.py +0 -0
  107. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/sentence_tokenizer.py +0 -0
  108. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/preprocessors/text_cleaner.py +0 -0
  109. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/query/__init__.py +0 -0
  110. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/query/query_expander.py +0 -0
  111. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/rankers/__init__.py +0 -0
  112. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/rankers/llm_ranker.py +0 -0
  113. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/rankers/lost_in_the_middle.py +0 -0
  114. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/rankers/meta_field.py +0 -0
  115. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/rankers/meta_field_grouping_ranker.py +0 -0
  116. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/__init__.py +0 -0
  117. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/auto_merging_retriever.py +0 -0
  118. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/filter_retriever.py +0 -0
  119. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/in_memory/__init__.py +0 -0
  120. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/in_memory/bm25_retriever.py +0 -0
  121. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/in_memory/embedding_retriever.py +0 -0
  122. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/multi_query_embedding_retriever.py +0 -0
  123. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/multi_query_text_retriever.py +0 -0
  124. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/multi_retriever.py +0 -0
  125. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/sentence_window_retriever.py +0 -0
  126. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/text_embedding_retriever.py +0 -0
  127. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/types/__init__.py +0 -0
  128. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/retrievers/types/protocol.py +0 -0
  129. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/__init__.py +0 -0
  130. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/document_length_router.py +0 -0
  131. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/document_type_router.py +0 -0
  132. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/file_type_router.py +0 -0
  133. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/llm_messages_router.py +0 -0
  134. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/routers/metadata_router.py +0 -0
  135. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/samplers/__init__.py +0 -0
  136. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/samplers/top_p.py +0 -0
  137. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/validators/__init__.py +0 -0
  138. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/validators/json_schema.py +0 -0
  139. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/writers/__init__.py +0 -0
  140. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/components/writers/document_writer.py +0 -0
  141. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/__init__.py +0 -0
  142. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/component/__init__.py +0 -0
  143. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/component/component.py +0 -0
  144. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/component/sockets.py +0 -0
  145. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/component/types.py +0 -0
  146. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/errors.py +0 -0
  147. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/__init__.py +0 -0
  148. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/base.py +0 -0
  149. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/breakpoint.py +0 -0
  150. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/component_checks.py +0 -0
  151. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/descriptions.py +0 -0
  152. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/draw.py +0 -0
  153. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/pipeline.py +0 -0
  154. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/pipeline/utils.py +0 -0
  155. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/serialization.py +0 -0
  156. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/serialization_security.py +0 -0
  157. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/super_component/__init__.py +0 -0
  158. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/super_component/super_component.py +0 -0
  159. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/super_component/utils.py +0 -0
  160. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/core/type_utils.py +0 -0
  161. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/data/abbreviations/de.txt +0 -0
  162. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/data/abbreviations/en.txt +0 -0
  163. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/__init__.py +0 -0
  164. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/answer.py +0 -0
  165. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/breakpoints.py +0 -0
  166. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/byte_stream.py +0 -0
  167. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/document.py +0 -0
  168. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/file_content.py +0 -0
  169. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/image_content.py +0 -0
  170. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/skill_info.py +0 -0
  171. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/sparse_embedding.py +0 -0
  172. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/dataclasses/streaming_chunk.py +0 -0
  173. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/__init__.py +0 -0
  174. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/errors/__init__.py +0 -0
  175. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/errors/errors.py +0 -0
  176. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/in_memory/__init__.py +0 -0
  177. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/in_memory/document_store.py +0 -0
  178. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/types/__init__.py +0 -0
  179. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/types/filter_policy.py +0 -0
  180. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/types/policy.py +0 -0
  181. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/document_stores/types/protocol.py +0 -0
  182. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/errors.py +0 -0
  183. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/evaluation/__init__.py +0 -0
  184. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/evaluation/eval_run_result.py +0 -0
  185. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/__init__.py +0 -0
  186. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/budget/__init__.py +0 -0
  187. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/budget/hooks.py +0 -0
  188. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/__init__.py +0 -0
  189. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/hooks.py +0 -0
  190. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/sliding_window.py +0 -0
  191. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/summarization.py +0 -0
  192. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/tool_result_pruning.py +0 -0
  193. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/types/__init__.py +0 -0
  194. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/types/protocol.py +0 -0
  195. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/compaction/utils.py +0 -0
  196. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/from_function.py +0 -0
  197. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/__init__.py +0 -0
  198. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/dataclasses.py +0 -0
  199. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/hooks.py +0 -0
  200. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/policies.py +0 -0
  201. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/strategies.py +0 -0
  202. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/types/__init__.py +0 -0
  203. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/types/protocol.py +0 -0
  204. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/human_in_the_loop/user_interfaces.py +0 -0
  205. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/invocation.py +0 -0
  206. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/protocol.py +0 -0
  207. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/__init__.py +0 -0
  208. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/policies.py +0 -0
  209. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/tool_result_offloading/types/__init__.py +0 -0
  210. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/hooks/utils.py +0 -0
  211. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/lazy_imports.py +0 -0
  212. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/logging.py +0 -0
  213. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/marshal/__init__.py +0 -0
  214. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/marshal/protocol.py +0 -0
  215. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/marshal/yaml.py +0 -0
  216. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/py.typed +0 -0
  217. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/skill_stores/__init__.py +0 -0
  218. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/skill_stores/file_system/__init__.py +0 -0
  219. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/skill_stores/file_system/skill_store.py +0 -0
  220. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/skill_stores/types/__init__.py +0 -0
  221. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/skill_stores/types/protocol.py +0 -0
  222. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/telemetry/__init__.py +0 -0
  223. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/telemetry/_environment.py +0 -0
  224. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/telemetry/_telemetry.py +0 -0
  225. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/__init__.py +0 -0
  226. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/callable_serialization/random_callable.py +0 -0
  227. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/document_store.py +0 -0
  228. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/document_store_async.py +0 -0
  229. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/factory.py +0 -0
  230. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/__init__.py +0 -0
  231. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/accumulate.py +0 -0
  232. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/add_value.py +0 -0
  233. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/concatenate.py +0 -0
  234. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/double.py +0 -0
  235. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/fstring.py +0 -0
  236. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/future_annotations.py +0 -0
  237. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/greet.py +0 -0
  238. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/hello.py +0 -0
  239. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/joiner.py +0 -0
  240. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/parity.py +0 -0
  241. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/remainder.py +0 -0
  242. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/repeat.py +0 -0
  243. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/subtract.py +0 -0
  244. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/sum.py +0 -0
  245. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/text_splitter.py +0 -0
  246. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/sample_components/threshold.py +0 -0
  247. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/testing/test_utils.py +0 -0
  248. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/__init__.py +0 -0
  249. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/approximate_counter.py +0 -0
  250. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/openai_counter.py +0 -0
  251. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/tiktoken_counter.py +0 -0
  252. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/types/__init__.py +0 -0
  253. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/types/protocol.py +0 -0
  254. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/token_counters/utils.py +0 -0
  255. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/__init__.py +0 -0
  256. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/agent_tool.py +0 -0
  257. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/component_tool.py +0 -0
  258. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/errors.py +0 -0
  259. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/from_function.py +0 -0
  260. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/parameters_schema_utils.py +0 -0
  261. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/pipeline_tool.py +0 -0
  262. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/searchable_toolset.py +0 -0
  263. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/serde_utils.py +0 -0
  264. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/skills/__init__.py +0 -0
  265. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/skills/skill_toolset.py +0 -0
  266. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/tool.py +0 -0
  267. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/tool_types.py +0 -0
  268. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/toolset.py +0 -0
  269. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tools/utils.py +0 -0
  270. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tracing/__init__.py +0 -0
  271. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tracing/logging_tracer.py +0 -0
  272. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tracing/tracer.py +0 -0
  273. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/tracing/utils.py +0 -0
  274. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/__init__.py +0 -0
  275. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/async_utils.py +0 -0
  276. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/auth.py +0 -0
  277. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/azure.py +0 -0
  278. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/base_serialization.py +0 -0
  279. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/callable_serialization.py +0 -0
  280. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/dataclasses.py +0 -0
  281. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/deserialization.py +0 -0
  282. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/device.py +0 -0
  283. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/experimental.py +0 -0
  284. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/filters.py +0 -0
  285. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/http_client.py +0 -0
  286. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/jinja2_chat_extension.py +0 -0
  287. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/jinja2_extensions.py +0 -0
  288. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/jinja2_sandbox.py +0 -0
  289. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/jupyter.py +0 -0
  290. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/misc.py +0 -0
  291. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/requests_utils.py +0 -0
  292. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/type_serialization.py +0 -0
  293. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/utils/url_validation.py +0 -0
  294. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/haystack/version.py +0 -0
  295. {haystack_ai-3.2.0.dev20260902000321 → haystack_ai-3.2.0.dev20260903000331}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: haystack-ai
3
- Version: 3.2.0.dev20260902000321
3
+ Version: 3.2.0.dev20260903000331
4
4
  Summary: LLM framework to build customizable, production-ready LLM applications. Connect components (models, vector DBs, file converters) to pipelines or agents that can interact with your data.
5
5
  Project-URL: CI: GitHub, https://github.com/deepset-ai/haystack/actions
6
6
  Project-URL: Docs: RTD, https://haystack.deepset.ai/overview/intro
@@ -0,0 +1 @@
1
+ 3.2.0.dev20260903000331
@@ -136,8 +136,12 @@ class OutputAdapter:
136
136
  # we try to evaluate it and would fail.
137
137
  # This must be done cause the output could be different literal structures.
138
138
  # This doesn't support any user types.
139
+ # When the declared output_type is str we skip literal evaluation so that a
140
+ # rendered string that happens to be a valid Python literal (e.g. "1,000" -> (1, 0),
141
+ # "42" -> 42, "None" -> None) is returned unchanged instead of being coerced to
142
+ # another type, which would violate the declared output_type.
139
143
  with contextlib.suppress(Exception):
140
- if not self._unsafe:
144
+ if not self._unsafe and self.output_type is not str:
141
145
  output_result = ast.literal_eval(output_result)
142
146
 
143
147
  adapted_outputs["output"] = output_result
@@ -16,6 +16,7 @@ from haystack import component, default_from_dict, default_to_dict, logging
16
16
  from haystack.components.generators.utils import _normalize_messages, _serialize_object
17
17
  from haystack.dataclasses import (
18
18
  ChatMessage,
19
+ ChatRole,
19
20
  ComponentInfo,
20
21
  FileContent,
21
22
  ImageContent,
@@ -992,10 +993,13 @@ def _convert_chat_message_to_responses_api_format(message: ChatMessage) -> list[
992
993
  reasonings = message.reasonings
993
994
  files = message.files
994
995
 
995
- if not any([text_contents, tool_calls, tool_call_results, images, reasonings, files]):
996
+ has_content = any([text_contents, tool_calls, tool_call_results, images, reasonings, files])
997
+ # We convert an assistant message with no content part into a message with empty content, which the API accepts
998
+ if not has_content and not message.is_from(ChatRole.ASSISTANT):
996
999
  raise ValueError(
997
- """A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, `ToolCallResult`,
998
- `ImageContent`, `FileContent`, or `ReasoningContent`."""
1000
+ f"A `ChatMessage` from `{message._role.value}` must contain at least one `TextContent`, `ToolCall`, "
1001
+ "`ToolCallResult`, `ImageContent`, `FileContent`, or `ReasoningContent`. Only assistant messages can "
1002
+ "be empty."
999
1003
  )
1000
1004
  if len(tool_call_results) > 0 and len(message._content) > 1:
1001
1005
  raise ValueError(
@@ -1068,7 +1072,7 @@ def _convert_chat_message_to_responses_api_format(message: ChatMessage) -> list[
1068
1072
  formatted_messages.extend(formatted_tool_calls)
1069
1073
 
1070
1074
  # system and assistant messages
1071
- if text_contents:
1075
+ if text_contents or not formatted_messages:
1072
1076
  openai_msg["content"] = " ".join(text_contents)
1073
1077
  formatted_messages.append(openai_msg)
1074
1078
 
@@ -350,17 +350,11 @@ class RecursiveDocumentSplitter:
350
350
  if current_chunk:
351
351
  chunks.append("".join(current_chunk))
352
352
 
353
- if self.split_overlap > 0:
354
- chunks = self._apply_overlap(chunks)
355
-
356
353
  if chunks:
357
354
  return chunks
358
355
 
359
356
  # if no separator worked, fall back to word- or character-level chunking
360
- chunks = self._fall_back_to_fixed_chunking(text, self.split_units)
361
- if self.split_overlap > 0:
362
- chunks = self._apply_overlap(chunks)
363
- return chunks
357
+ return self._fall_back_to_fixed_chunking(text, self.split_units)
364
358
 
365
359
  def _fall_back_to_fixed_chunking(self, text: str, split_units: Literal["word", "char", "token"]) -> list[str]:
366
360
  """
@@ -429,6 +423,12 @@ class RecursiveDocumentSplitter:
429
423
  def _run_one(self, doc: Document) -> list[Document]:
430
424
  chunks = self._chunk_text(doc.content) # type: ignore # the caller already check for a non-empty doc.content
431
425
  chunks = chunks[:-1] if len(chunks[-1]) == 0 else chunks # remove last empty chunk if it exists
426
+
427
+ # apply the overlap once, on the fully chunked list, so that chunks produced
428
+ # at inner recursion levels don't get the overlap applied a second time
429
+ if self.split_overlap > 0:
430
+ chunks = self._apply_overlap(chunks)
431
+
432
432
  current_position = 0
433
433
  current_page = 1
434
434
 
@@ -458,8 +458,12 @@ class ConditionalRouter:
458
458
  # we try to evaluate it and would fail.
459
459
  # This must be done cause the output could be different literal structures.
460
460
  # This doesn't support any user types.
461
+ # When the declared output_type is str we skip literal evaluation so that a
462
+ # rendered string that happens to be a valid Python literal (e.g. "1,000" -> (1, 0),
463
+ # "42" -> 42, "None" -> None) is returned unchanged instead of being coerced to
464
+ # another type, which would violate the declared output_type.
461
465
  with contextlib.suppress(Exception):
462
- if not self._unsafe:
466
+ if not self._unsafe and output_type is not str:
463
467
  output_value = ast.literal_eval(output_value)
464
468
 
465
469
  # Validate output type if needed
@@ -646,10 +646,12 @@ class ChatMessage:
646
646
  If the message format is invalid, or if `require_tool_call_ids` is True and any Tool Call is missing an
647
647
  `id` attribute.
648
648
  """
649
- if not self.texts and not self.tool_calls and not self.tool_call_results and not self.images and not self.files:
649
+ has_content = bool(self.texts or self.tool_calls or self.tool_call_results or self.images or self.files)
650
+ # We convert an assistant message with no content part into a message with empty content, which the API accepts
651
+ if not has_content and not self.is_from(ChatRole.ASSISTANT):
650
652
  raise ValueError(
651
- "A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, "
652
- "`ToolCallResult`, `ImageContent`, or `FileContent`."
653
+ f"A `ChatMessage` from `{self._role.value}` must contain at least one `TextContent`, `ToolCall`, "
654
+ "`ToolCallResult`, `ImageContent`, or `FileContent`. Only assistant messages can be empty."
653
655
  )
654
656
  if len(self.tool_call_results) > 0 and len(self._content) > 1:
655
657
  raise ValueError(
@@ -745,6 +747,9 @@ class ChatMessage:
745
747
  raise ValueError("`ToolCall` must have a non-null `id` attribute to be used with OpenAI.")
746
748
  openai_tool_calls.append(openai_tool_call)
747
749
  openai_msg["tool_calls"] = openai_tool_calls
750
+ # The API rejects a message carrying neither content nor tool calls, so send empty content for it.
751
+ if "content" not in openai_msg and "tool_calls" not in openai_msg:
752
+ openai_msg["content"] = ""
748
753
  return openai_msg
749
754
 
750
755
  @staticmethod
@@ -766,7 +771,9 @@ class ChatMessage:
766
771
  raise ValueError(f"Unsupported role: {role}")
767
772
 
768
773
  if role == "assistant":
769
- if not content and not tool_calls:
774
+ # An empty string is valid content for an assistant message: that is how a reply with nothing to send
775
+ # is serialized. Other falsy content requires tool calls.
776
+ if not content and content != "" and not tool_calls:
770
777
  raise ValueError("For assistant messages, either `content` or `tool_calls` must be present.")
771
778
  if tool_calls:
772
779
  for tc in tool_calls:
@@ -2,23 +2,28 @@
2
2
  #
3
3
  # SPDX-License-Identifier: Apache-2.0
4
4
 
5
+ import base64
5
6
  import json
7
+ import mimetypes
8
+ from pathlib import Path
6
9
  from typing import Any
7
10
 
8
11
  from haystack import logging
9
12
  from haystack.components.agents.state.state import State
10
13
  from haystack.components.agents.state.state_utils import replace_values
11
14
  from haystack.core.serialization import default_from_dict, default_to_dict
12
- from haystack.dataclasses import ChatMessage, TextContent
13
- from haystack.dataclasses.chat_message import ToolCallResultContentT
15
+ from haystack.dataclasses import ChatMessage, FileContent, ImageContent, TextContent
14
16
  from haystack.hooks.tool_result_offloading.types import OffloadPolicy, ToolResultStore
15
17
  from haystack.utils.deserialization import deserialize_component_inplace
16
18
 
19
+ # Extension used for a binary block whose MIME type is unknown or maps to no known extension.
20
+ _FALLBACK_EXTENSION = ".bin"
21
+
17
22
  logger = logging.getLogger(__name__)
18
23
 
19
- # Meta key marking an already-offloaded tool-result message (its value is the store reference). The offloaded pointer
20
- # is itself a tool result in the trailing block the hook scans, so this marker stops a second offload hook registered
21
- # under `after_tool` from offloading the pointer text again and writing a junk file.
24
+ # Meta key marking an already-offloaded tool-result message; its value is the list of store references written.
25
+ # Stops a second `after_tool` offload hook from offloading the pointer text again, since the pointer is itself a
26
+ # tool result.
22
27
  _OFFLOADED_META_KEY = "tool_result_offloaded"
23
28
 
24
29
  # Key under which a per-run store override may be supplied via the Agent's `hook_context` (e.g. a request-scoped
@@ -26,24 +31,6 @@ _OFFLOADED_META_KEY = "tool_result_offloaded"
26
31
  RESULT_STORE_CONTEXT_KEY = "tool_result_store"
27
32
 
28
33
 
29
- def _result_store_key(tool_name: str, tool_call_id: str | None, step: int, index: int) -> str:
30
- """
31
- Build a per-result store key that is stable and unique within a run.
32
-
33
- Combining the step, tool name, and tool call id keeps results from different tools and different steps from
34
- colliding. When the tool call carries no id (it is optional and not every generator sets it), the result's
35
- position in the step's batch is used instead, so two id-less calls to the same tool in the same step do not
36
- collide.
37
-
38
- :param tool_name: The name of the tool that produced the result.
39
- :param tool_call_id: The id of the originating tool call, or None when the call carried no id.
40
- :param step: The Agent's current step count.
41
- :param index: The result's position within this step's batch of tool results, used when `tool_call_id` is None.
42
- :returns: A file-name-like key for the store, e.g. `2_web_search_call-123.txt`.
43
- """
44
- return f"{step}_{tool_name}_{tool_call_id or f'call{index}'}.txt"
45
-
46
-
47
34
  def _fresh_tool_results_start(messages: list[ChatMessage]) -> int:
48
35
  """
49
36
  Return the index at which the trailing run of tool-result messages begins.
@@ -62,23 +49,18 @@ def _fresh_tool_results_start(messages: list[ChatMessage]) -> int:
62
49
  return index
63
50
 
64
51
 
65
- def _offloadable_text(content: ToolCallResultContentT) -> str | None:
52
+ def _content_block_payload(content_block: TextContent | ImageContent | FileContent) -> str:
66
53
  """
67
- Return the text of a tool result if it can be offloaded as text, otherwise None.
54
+ Return the string a content block contributes to the conversation.
68
55
 
69
- A plain string is returned as-is; a non-empty sequence made up entirely of `TextContent` blocks is concatenated
70
- into a single string. Anything else (e.g. a result containing image or file content) returns None and is left in
71
- context.
56
+ For an image or a file this is the base64 payload, which is what actually occupies the context window.
72
57
 
73
- :param content: The tool result content to inspect.
74
- :returns: The offloadable text, or None when the content is not purely text.
58
+ :param content_block: The content block to inspect.
59
+ :returns: The content block's text or base64 payload.
75
60
  """
76
- if isinstance(content, str):
77
- return content
78
- texts = [block.text for block in content if isinstance(block, TextContent)]
79
- if texts and len(texts) == len(content):
80
- return "".join(texts)
81
- return None
61
+ if isinstance(content_block, TextContent):
62
+ return content_block.text
63
+ return content_block.base64_image if isinstance(content_block, ImageContent) else content_block.base64_data
82
64
 
83
65
 
84
66
  def _serialize_offload_strategies(strategies: dict[str | tuple[str, ...], OffloadPolicy]) -> dict[str, Any]:
@@ -158,10 +140,10 @@ class ToolResultOffloadHook:
158
140
  any tool without a more specific entry. More specific keys win. A tool with no matching key (and no `"*"`) is not
159
141
  offloaded.
160
142
 
161
- Only successful, text tool output is offloaded. Error results (including `before_tool` human-in-the-loop
162
- rejections) are always left in context. Non-text results (image or file content) are also left in context, and a
163
- warning is logged when such a result has a matching offload policy; supporting only text is a deliberate choice
164
- for now. Each result is offloaded at most once, even though the hook runs on every tool step.
143
+ Only successful tool output is offloaded; error results are always left in context. Each part of a result is
144
+ written to its own store entry and the pointer says where each one went. Image and file content is only offloaded
145
+ to a store that sets `supports_binary_content`; with a text-only store the result stays in context and a warning
146
+ is logged. Each result is offloaded at most once, even though the hook runs on every tool step.
165
147
 
166
148
  The hook keeps no mutable state, so a single instance can be shared across concurrent runs. The constructor
167
149
  `store`, however, is shared by every run that does not override it — fine for single-user or local use, but in a
@@ -187,8 +169,9 @@ class ToolResultOffloadHook:
187
169
  :param store: Where offloaded results are written. Can be overridden per run via `hook_context`.
188
170
  :param offload_strategies: Mapping of tool name (or a tuple of tool names, or the wildcard `"*"`) to the
189
171
  `OffloadPolicy` that decides whether that tool's results are offloaded.
190
- :param preview_chars: Number of leading characters of the original result to include in the pointer left in
191
- the conversation, so the model knows roughly what was offloaded.
172
+ :param preview_chars: Number of leading characters of each offloaded text to include in the pointer left in
173
+ the conversation, so the model knows roughly what was offloaded. Image and file blocks are described by
174
+ their MIME type and size instead.
192
175
  """
193
176
  self.store = store
194
177
  self.offload_strategies = offload_strategies
@@ -217,29 +200,25 @@ class ToolResultOffloadHook:
217
200
  :returns: None. The hook mutates `state` in place.
218
201
  """
219
202
  messages = state.data.get("messages") or []
220
- start = _fresh_tool_results_start(messages)
203
+ start = _fresh_tool_results_start(messages=messages)
221
204
  if start == len(messages):
222
205
  return
223
- store = self._resolve_store(state)
206
+
207
+ # The hook instance is shared across concurrent runs, so a run isolates itself by carrying its own store in
208
+ # `hook_context`.
209
+ hook_context = state.data.get("hook_context") or {}
210
+ store = hook_context.get(RESULT_STORE_CONTEXT_KEY, self.store)
211
+
224
212
  rewritten: list[ChatMessage] = list(messages[:start])
225
213
  changed = False
226
214
  for index, message in enumerate(messages[start:]):
227
- new_message = self._maybe_offload(message, store, state, index)
215
+ new_message = self._maybe_offload(message=message, store=store, state=state, index=index)
228
216
  rewritten.append(new_message)
229
217
  changed = changed or new_message is not message
230
- if changed:
231
- state.set("messages", rewritten, handler_override=replace_values)
232
-
233
- def _resolve_store(self, state: State) -> ToolResultStore:
234
- """
235
- Return the store to write to for this run.
236
218
 
237
- :param state: The Agent's live `State`, whose `hook_context` may carry a per-run store override under
238
- `RESULT_STORE_CONTEXT_KEY`.
239
- :returns: The per-run store from `hook_context` if provided, otherwise the store the hook was built with.
240
- """
241
- context = state.data.get("hook_context") or {}
242
- return context.get(RESULT_STORE_CONTEXT_KEY, self.store)
219
+ # Only write back to state when at least one message changed
220
+ if changed:
221
+ state.set(key="messages", value=rewritten, handler_override=replace_values)
243
222
 
244
223
  def _policy_for(self, tool_name: str) -> OffloadPolicy | None:
245
224
  """
@@ -264,11 +243,12 @@ class ToolResultOffloadHook:
264
243
 
265
244
  A message is left as-is when it is not a tool result, when the result is an error (including `before_tool`
266
245
  human-in-the-loop rejections), when it was already offloaded (e.g. another offload hook under `after_tool`
267
- handled it), when no policy applies, when the result is non-text (contains image or file content), or when the
268
- policy declines to offload.
246
+ handled it), when no policy applies, when the result is empty, when the result carries image or file content
247
+ that `store` cannot store, or when the policy declines to offload.
269
248
 
270
- Otherwise the result text is written to `store` and the message is rebuilt with a pointer in place of the full
271
- result, preserving its origin and error flag and marking it offloaded.
249
+ Otherwise the result is written to `store` and the message is rebuilt with a pointer in place of the full
250
+ result, preserving its origin and error flag and marking it offloaded. Each part of the result goes to its own
251
+ store entry.
272
252
 
273
253
  :param message: The message to consider offloading.
274
254
  :param store: The store to write the result to.
@@ -283,48 +263,115 @@ class ToolResultOffloadHook:
283
263
  return message
284
264
 
285
265
  tool_name = result.origin.tool_name
286
- policy = self._policy_for(tool_name)
266
+ policy = self._policy_for(tool_name=tool_name)
287
267
 
288
268
  # If no policy applies, leave the result in context
289
269
  if policy is None:
290
270
  return message
291
271
 
292
- # A policy matched, so an offload was wanted. Offloading only supports text results (a string or a sequence
293
- # of TextContent) for now, by design; leave image/file content in context and warn since the intent was to
294
- # offload it.
295
- text = _offloadable_text(result.result)
296
- if text is None:
272
+ # A plain string result is handled as a single text block, so everything below has one shape to work with.
273
+ content_blocks: list[TextContent | ImageContent | FileContent] = (
274
+ [TextContent(text=result.result)] if isinstance(result.result, str) else list(result.result)
275
+ )
276
+ if not content_blocks:
277
+ return message
278
+
279
+ # Check whether the store can store binary content before offloading an image or file result. A text-only store
280
+ # leaves the result in context and logs a warning.
281
+ if not getattr(store, "supports_binary_content", False) and not all(
282
+ isinstance(content_block, TextContent) for content_block in content_blocks
283
+ ):
297
284
  logger.warning(
298
- "Tool '{tool}' produced a non-text result; leaving it in context. Result offloading currently "
299
- "supports text results only.",
285
+ "Tool '{tool}' produced a result with image or file content, but {store} does not support binary "
286
+ "content; leaving the result in context.",
300
287
  tool=tool_name,
288
+ store=type(store).__name__,
301
289
  )
302
290
  return message
303
291
 
304
- # If the policy declines to offload, leave the result in context
305
- if not policy.should_offload(tool_name, text, state):
292
+ # The policy sizes up the result by the string it occupies in the conversation, which for an image or a file
293
+ # block is its base64 payload.
294
+ payload = "".join(_content_block_payload(content_block=content_block) for content_block in content_blocks)
295
+ if not policy.should_offload(tool_name=tool_name, result=payload, state=state):
306
296
  return message
307
297
 
308
- key = _result_store_key(tool_name, result.origin.id, state.data.get("step_count", 0), index)
309
- reference = store.write(key=key, content=text)
298
+ # Step, tool name, and call id keep results from different tools and steps from colliding. A tool call id is
299
+ # optional, so an id-less call falls back to its position in this step's batch.
300
+ step = state.data.get("step_count", 0)
301
+ prefix = f"{step}_{tool_name}_{result.origin.id or f'call{index}'}"
302
+ references, pointer = self._offload_content_blocks(content_blocks=content_blocks, store=store, prefix=prefix)
303
+
310
304
  return ChatMessage.from_tool(
311
- tool_result=self._pointer(reference, text),
305
+ tool_result=pointer,
312
306
  origin=result.origin,
313
307
  error=result.error,
314
- meta={**message.meta, _OFFLOADED_META_KEY: reference},
308
+ meta={**message.meta, _OFFLOADED_META_KEY: references},
315
309
  )
316
310
 
317
- def _pointer(self, reference: str, result: str) -> str:
311
+ def _offload_content_blocks(
312
+ self, content_blocks: list[TextContent | ImageContent | FileContent], store: ToolResultStore, prefix: str
313
+ ) -> tuple[list[str], str]:
314
+ """
315
+ Write a result's content blocks to the store and build the pointer that replaces them in the conversation.
316
+
317
+ Every content block goes to its own store entry. A single block keeps `prefix` as its key and gets a one-line
318
+ pointer; several blocks get position-suffixed keys and one numbered pointer line each.
319
+
320
+ :param content_blocks: The result's content blocks, in order.
321
+ :param store: The store to write to.
322
+ :param prefix: The result's store key prefix, as described above.
323
+ :returns: The store references written, and the pointer text for the conversation.
324
+ """
325
+ references: list[str] = []
326
+ descriptions: list[str] = []
327
+ single = len(content_blocks) == 1
328
+ for position, content_block in enumerate(content_blocks):
329
+ reference, description = self._offload_content_block(
330
+ content_block=content_block, store=store, key_prefix=prefix if single else f"{prefix}_{position}"
331
+ )
332
+ references.append(reference)
333
+ descriptions.append(description)
334
+
335
+ if len(descriptions) == 1:
336
+ return references, f"Tool result offloaded to {descriptions[0]}"
337
+
338
+ numbered = [f"{position}. {description}" for position, description in enumerate(descriptions, start=1)]
339
+ return references, "\n".join([f"Tool result offloaded to {len(descriptions)} files:", *numbered])
340
+
341
+ def _offload_content_block(
342
+ self, content_block: TextContent | ImageContent | FileContent, store: ToolResultStore, key_prefix: str
343
+ ) -> tuple[str, str]:
318
344
  """
319
- Build the compact pointer that replaces a full result in the conversation.
345
+ Write a single content block to the store and describe where it went.
320
346
 
321
- :param reference: The store reference the result was written to.
322
- :param result: The original result string, used for its length and a leading preview.
323
- :returns: A one-line pointer carrying the reference, the result length, and a `preview_chars`-long preview.
347
+ :param content_block: The content block to offload.
348
+ :param store: The store to write to.
349
+ :param key_prefix: The content block's store key without its extension.
350
+ :returns: The store reference the content block was written to, and a one-line description for the pointer.
324
351
  """
325
- ellip = "..." if len(result) > self.preview_chars else ""
326
- preview = result[: self.preview_chars]
327
- return f"Tool result offloaded to '{reference}' ({len(result)} characters). Preview: {preview}{ellip}"
352
+ if isinstance(content_block, TextContent):
353
+ text = content_block.text
354
+ reference = store.write(key=f"{key_prefix}.txt", content=text)
355
+ # An ellipsis marks a preview that was cut short, so the model can tell it is not the whole text.
356
+ preview = f"{text[: self.preview_chars]}{'...' if len(text) > self.preview_chars else ''}"
357
+ return reference, f"text ({len(text)} characters) at '{reference}'. Preview: {preview}"
358
+
359
+ if isinstance(content_block, ImageContent):
360
+ data = base64.b64decode(content_block.base64_image)
361
+ label = content_block.mime_type or "image"
362
+ filename = None
363
+ else:
364
+ data = base64.b64decode(content_block.base64_data)
365
+ label = content_block.mime_type or "file"
366
+ filename = content_block.filename
367
+
368
+ # What the tool called the file wins over its MIME type. Only the suffix is taken.
369
+ mime_extension = mimetypes.guess_extension(content_block.mime_type) if content_block.mime_type else None
370
+ extension = (Path(filename).suffix if filename else "") or mime_extension or _FALLBACK_EXTENSION
371
+ reference = store.write(key=f"{key_prefix}{extension}", content=data)
372
+
373
+ named = f" named '{filename}'" if filename else ""
374
+ return reference, f"{label}{named} ({len(data)} bytes) at '{reference}'"
328
375
 
329
376
  def to_dict(self) -> dict[str, Any]:
330
377
  """
@@ -335,7 +382,7 @@ class ToolResultOffloadHook:
335
382
  return default_to_dict(
336
383
  self,
337
384
  store=self.store.to_dict(),
338
- offload_strategies=_serialize_offload_strategies(self.offload_strategies),
385
+ offload_strategies=_serialize_offload_strategies(strategies=self.offload_strategies),
339
386
  preview_chars=self.preview_chars,
340
387
  )
341
388
 
@@ -351,5 +398,5 @@ class ToolResultOffloadHook:
351
398
  if init_params.get("store") is not None:
352
399
  deserialize_component_inplace(init_params, key="store")
353
400
  if init_params.get("offload_strategies") is not None:
354
- init_params["offload_strategies"] = _deserialize_offload_strategies(init_params["offload_strategies"])
401
+ init_params["offload_strategies"] = _deserialize_offload_strategies(data=init_params["offload_strategies"])
355
402
  return default_from_dict(cls, data)
@@ -20,8 +20,13 @@ class FileSystemToolResultStore(ToolResultStore):
20
20
  reference = store.write(key="search_1.txt", content="...")
21
21
  store.read(reference)
22
22
  ```
23
+
24
+ Binary content is supported too: `write` takes bytes (an offloaded image or file) and `read` returns them
25
+ unchanged.
23
26
  """
24
27
 
28
+ supports_binary_content = True
29
+
25
30
  def __init__(self, root: str | Path) -> None:
26
31
  """
27
32
  Initialize the store with the root directory results are written under.
@@ -49,35 +54,47 @@ class FileSystemToolResultStore(ToolResultStore):
49
54
  raise ValueError(f"{subject} '{path_like}' resolves outside the store root '{root}'.")
50
55
  return resolved
51
56
 
52
- def write(self, *, key: str, content: str) -> str:
57
+ def write(self, *, key: str, content: str | bytes) -> str:
53
58
  """
54
59
  Write `content` to `<root>/<key>`, creating parent directories, and return the file path.
55
60
 
61
+ Text is written UTF-8 encoded; bytes (an offloaded image or file) are written verbatim.
62
+
56
63
  The resolved target must stay within the root directory: a `key` that escapes it (e.g. containing `../` or an
57
64
  absolute path) is rejected, so a tool-provided key cannot write outside the store.
58
65
 
59
66
  :param key: Relative file name for the result within the store root.
60
- :param content: The tool result to persist.
67
+ :param content: The tool result to persist, as text or as raw bytes.
61
68
  :returns: The absolute path the content was written to, as a string, for use with `read`.
62
69
  :raises ValueError: If `key` resolves to a location outside the store root.
63
70
  """
64
71
  path = self._resolve_in_root(key, subject="Result key")
65
72
  path.parent.mkdir(parents=True, exist_ok=True)
66
- path.write_text(content, encoding="utf-8")
73
+ if isinstance(content, bytes):
74
+ path.write_bytes(content)
75
+ else:
76
+ path.write_text(content, encoding="utf-8")
67
77
  return str(path)
68
78
 
69
- def read(self, reference: str) -> str:
79
+ def read(self, reference: str) -> str | bytes:
70
80
  """
71
81
  Read back the content previously written to `reference`.
72
82
 
73
- The resolved reference must stay within the store root: callers must treat it as an opaque
74
- store-scoped reference, not as an arbitrary filesystem path.
83
+ A file whose bytes are valid UTF-8 is returned as a string, so text results round trip unchanged; anything
84
+ else (an offloaded image or file) is returned as raw bytes.
85
+
86
+ The resolved reference must stay within the store root: it is a store-scoped reference returned by `write`,
87
+ to be passed back unchanged, not an arbitrary filesystem path callers can build themselves.
75
88
 
76
89
  :param reference: A store reference returned by `write`.
77
- :returns: The stored content.
90
+ :returns: The stored content, as text when it decodes as UTF-8 and as bytes otherwise.
78
91
  :raises ValueError: If `reference` resolves to a location outside the store root.
79
92
  """
80
- return self._resolve_in_root(reference, subject="Result reference").read_text(encoding="utf-8")
93
+ data = self._resolve_in_root(reference, subject="Result reference").read_bytes()
94
+ try:
95
+ return data.decode("utf-8")
96
+ except UnicodeDecodeError:
97
+ return data
81
98
 
82
99
  def to_dict(self) -> dict[str, Any]:
83
100
  """
@@ -13,25 +13,42 @@ class ToolResultStore(Protocol):
13
13
  A place a `ToolResultOffloadHook` writes offloaded tool results to, and reads them back from.
14
14
 
15
15
  Implementations decide where and how the content lives (local disk, an isolated sandbox filesystem, object
16
- storage, ...). `write` returns an opaque reference string that the Agent puts in the conversation in place of the
17
- full result; `read` resolves that reference back to the original content.
16
+ storage, ...). `write` returns a reference string that the Agent puts in the conversation in place of the full
17
+ result; `read` resolves that reference back to the original content. Only the store interprets a reference -
18
+ callers pass it back to `read` unchanged.
19
+
20
+ A store that sets `supports_binary_content` takes bytes in `write` and gives them back from `read`. One that
21
+ leaves it False is only ever given text, and image and file results stay in the conversation instead.
18
22
 
19
23
  Implement both `to_dict` and `from_dict` to make a custom store serializable; the default implementations below
20
24
  cover stores whose constructor takes no arguments.
21
25
  """
22
26
 
23
- def write(self, *, key: str, content: str) -> str:
27
+ # Whether the store handles binary content: `write` accepting bytes and `read` returning them. When False, the
28
+ # store is only ever given text, and the image and file content of a tool result stays in the conversation.
29
+ supports_binary_content: bool = False
30
+
31
+ def write(self, *, key: str, content: str | bytes) -> str:
24
32
  """
25
- Persist `content` under `key` and return an opaque reference to it.
33
+ Persist `content` under `key` and return a reference to it.
26
34
 
27
- :param key: A stable, per-result identifier the hook derives from the tool call (e.g. a file name).
28
- :param content: The tool result to persist.
35
+ :param key: A stable, per-result identifier the hook derives from the tool call (e.g. a file name). It carries
36
+ an extension matching the content, so a store that maps keys to files can use it as-is.
37
+ :param content: The tool result to persist. Text arrives as a string. Image and file content arrives as the
38
+ decoded bytes of its base64 payload, and only when the store sets `supports_binary_content` to True - a
39
+ text-only store may narrow this parameter to `str`.
29
40
  :returns: A reference string (e.g. a path or URI) that `read` can later resolve.
30
41
  """
31
42
  ...
32
43
 
33
- def read(self, reference: str) -> str:
34
- """Return the content previously stored under `reference`."""
44
+ def read(self, reference: str) -> str | bytes:
45
+ """
46
+ Return the content previously stored under `reference`.
47
+
48
+ :param reference: A reference string returned by `write`.
49
+ :returns: The stored content: a string for content written as text, bytes for binary content such as an
50
+ offloaded image or file. A store that does not support binary content only ever returns a string.
51
+ """
35
52
  ...
36
53
 
37
54
  def to_dict(self) -> dict[str, Any]:
@@ -60,7 +77,9 @@ class OffloadPolicy(Protocol):
60
77
  Return whether the given tool result should be offloaded.
61
78
 
62
79
  :param tool_name: The name of the tool that produced the result.
63
- :param result: The tool result as a string (the content that would otherwise stay in the conversation).
80
+ :param result: The tool result as a string (the content that would otherwise stay in the conversation). For a
81
+ result carrying image or file blocks, this is the text and base64 payloads of all its blocks joined
82
+ together, so its length reflects the context the result actually occupies.
64
83
  :param state: The Agent's live `State`, for policies that decide based on run context.
65
84
  :returns: True to offload the result to the store, False to leave it in context.
66
85
  """
@@ -5,7 +5,7 @@
5
5
  from typing import Any
6
6
 
7
7
  from haystack import logging
8
- from haystack.dataclasses import ChatMessage, ImageContent, ReasoningContent, TextContent
8
+ from haystack.dataclasses import ChatMessage, ChatRole, ImageContent, ReasoningContent, TextContent
9
9
  from haystack.lazy_imports import LazyImport
10
10
 
11
11
  with LazyImport(message="Run 'pip install \"transformers[torch]\"'") as torch_import:
@@ -69,9 +69,12 @@ def convert_message_to_hf_format(message: ChatMessage) -> dict[str, Any]:
69
69
  # ReasoningContent is for human transparency only, not sent to the API
70
70
  non_reasoning_content = [c for c in message._content if not isinstance(c, ReasoningContent)]
71
71
 
72
- if not text_contents and not tool_calls and not tool_call_results and not images:
72
+ has_content = bool(text_contents or tool_calls or tool_call_results or images)
73
+ # We convert an assistant message with no content part into a message with empty content, which the API accepts
74
+ if not has_content and not message.is_from(ChatRole.ASSISTANT):
73
75
  raise ValueError(
74
- "A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, `ToolCallResult`, or `ImageContent`."
76
+ f"A `ChatMessage` from `{message._role.value}` must contain at least one `TextContent`, `ToolCall`, "
77
+ "`ToolCallResult`, or `ImageContent`. Only assistant messages can be empty."
75
78
  )
76
79
  if len(tool_call_results) > 0 and len(non_reasoning_content) > 1:
77
80
  raise ValueError(