signalwire-sdk 3.4.0.dev110__tar.gz → 3.4.1.dev111__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 (255) hide show
  1. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/PKG-INFO +1 -1
  2. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/pyproject.toml +1 -1
  3. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/__init__.py +1 -1
  4. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agent_server.py +1 -1
  5. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/build_search.py +48 -9
  6. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/document_processor.py +52 -1
  7. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/index_builder.py +16 -2
  8. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/migration.py +40 -13
  9. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/pgvector_backend.py +94 -20
  10. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/query_processor.py +54 -3
  11. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/search_engine.py +75 -10
  12. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/search_service.py +54 -3
  13. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/PKG-INFO +1 -1
  14. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/LICENSE +0 -0
  15. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/README.md +0 -0
  16. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/sw-agent-init.1 +0 -0
  17. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/sw-search.1 +0 -0
  18. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/swaig-test.1 +0 -0
  19. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/setup.cfg +0 -0
  20. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/setup.py +0 -0
  21. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agents/__init__.py +0 -0
  22. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agents/bedrock.py +0 -0
  23. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/__init__.py +0 -0
  24. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/client.py +0 -0
  25. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/gateway.py +0 -0
  26. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/handoff.py +0 -0
  27. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/__init__.py +0 -0
  28. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/config.py +0 -0
  29. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/__init__.py +0 -0
  30. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/agent_loader.py +0 -0
  31. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/argparse_helpers.py +0 -0
  32. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/dynamic_config.py +0 -0
  33. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/service_loader.py +0 -0
  34. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/dokku.py +0 -0
  35. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/__init__.py +0 -0
  36. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/datamap_exec.py +0 -0
  37. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/webhook_exec.py +0 -0
  38. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/init_project.py +0 -0
  39. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/__init__.py +0 -0
  40. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/output_formatter.py +0 -0
  41. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/swml_dump.py +0 -0
  42. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/__init__.py +0 -0
  43. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/data_generation.py +0 -0
  44. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/data_overrides.py +0 -0
  45. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/mock_env.py +0 -0
  46. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/swaig_test_wrapper.py +0 -0
  47. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/test_swaig.py +0 -0
  48. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/types.py +0 -0
  49. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/__init__.py +0 -0
  50. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/_agent_host.py +0 -0
  51. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/__init__.py +0 -0
  52. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/config/__init__.py +0 -0
  53. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/deployment/__init__.py +0 -0
  54. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/deployment/handlers/__init__.py +0 -0
  55. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/prompt/__init__.py +0 -0
  56. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/prompt/manager.py +0 -0
  57. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/routing/__init__.py +0 -0
  58. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/security/__init__.py +0 -0
  59. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/swml/__init__.py +0 -0
  60. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/__init__.py +0 -0
  61. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/decorator.py +0 -0
  62. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/registry.py +0 -0
  63. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/type_inference.py +0 -0
  64. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent_base.py +0 -0
  65. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/auth_handler.py +0 -0
  66. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/capabilities.py +0 -0
  67. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/config_loader.py +0 -0
  68. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/contexts.py +0 -0
  69. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/data_map.py +0 -0
  70. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/function_result.py +0 -0
  71. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/logging_config.py +0 -0
  72. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/__init__.py +0 -0
  73. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/_mixin_host.py +0 -0
  74. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/ai_config_mixin.py +0 -0
  75. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/auth_mixin.py +0 -0
  76. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/mcp_server_mixin.py +0 -0
  77. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/prompt_mixin.py +0 -0
  78. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/serverless_mixin.py +0 -0
  79. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/skill_mixin.py +0 -0
  80. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/state_mixin.py +0 -0
  81. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/tool_mixin.py +0 -0
  82. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/web_mixin.py +0 -0
  83. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/pom_builder.py +0 -0
  84. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/post_prompt.py +0 -0
  85. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/post_prompt_generated.py +0 -0
  86. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/__init__.py +0 -0
  87. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/security_utils.py +0 -0
  88. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/session_manager.py +0 -0
  89. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/webhook_middleware.py +0 -0
  90. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/webhook_validator.py +0 -0
  91. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security_config.py +0 -0
  92. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/skill_base.py +0 -0
  93. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/skill_manager.py +0 -0
  94. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_actions_generated.py +0 -0
  95. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_function.py +0 -0
  96. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_request_generated.py +0 -0
  97. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_builder.py +0 -0
  98. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_handler.py +0 -0
  99. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_renderer.py +0 -0
  100. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_service.py +0 -0
  101. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_verbs_generated.py +0 -0
  102. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/web.py +0 -0
  103. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/livewire/__init__.py +0 -0
  104. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/livewire/plugins.py +0 -0
  105. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/__init__.py +0 -0
  106. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/gateway_service.py +0 -0
  107. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/mcp_manager.py +0 -0
  108. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/session_manager.py +0 -0
  109. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/__init__.py +0 -0
  110. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/pom.py +0 -0
  111. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/pom_tool.py +0 -0
  112. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/__init__.py +0 -0
  113. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/concierge.py +0 -0
  114. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/faq_bot.py +0 -0
  115. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/info_gatherer.py +0 -0
  116. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/receptionist.py +0 -0
  117. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/survey.py +0 -0
  118. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/py.typed +0 -0
  119. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/__init__.py +0 -0
  120. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/call.py +0 -0
  121. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/client.py +0 -0
  122. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/constants.py +0 -0
  123. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/event.py +0 -0
  124. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/message.py +0 -0
  125. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/protocol_types_generated.py +0 -0
  126. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/__init__.py +0 -0
  127. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_base.py +0 -0
  128. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_pagination.py +0 -0
  129. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_request_options.py +0 -0
  130. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/call_handler.py +0 -0
  131. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/client.py +0 -0
  132. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/__init__.py +0 -0
  133. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/_client_tree_generated.py +0 -0
  134. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/addresses.py +0 -0
  135. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling.py +0 -0
  136. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling_resources_generated.py +0 -0
  137. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling_types_generated.py +0 -0
  138. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat.py +0 -0
  139. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat_resources_generated.py +0 -0
  140. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat_types_generated.py +0 -0
  141. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere.py +0 -0
  142. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere_resources_generated.py +0 -0
  143. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere_types_generated.py +0 -0
  144. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric.py +0 -0
  145. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric_resources_generated.py +0 -0
  146. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric_types_generated.py +0 -0
  147. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fax_resources_generated.py +0 -0
  148. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fax_types_generated.py +0 -0
  149. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/imported_numbers.py +0 -0
  150. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs.py +0 -0
  151. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs_resources_generated.py +0 -0
  152. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs_types_generated.py +0 -0
  153. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/lookup.py +0 -0
  154. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/message_resources_generated.py +0 -0
  155. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/message_types_generated.py +0 -0
  156. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/messages_resources_generated.py +0 -0
  157. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/messages_types_generated.py +0 -0
  158. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/mfa.py +0 -0
  159. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/number_groups.py +0 -0
  160. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/phone_numbers.py +0 -0
  161. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project.py +0 -0
  162. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project_resources_generated.py +0 -0
  163. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project_types_generated.py +0 -0
  164. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/projects_resources_generated.py +0 -0
  165. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/projects_types_generated.py +0 -0
  166. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub.py +0 -0
  167. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub_resources_generated.py +0 -0
  168. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub_types_generated.py +0 -0
  169. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/queues.py +0 -0
  170. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/recordings.py +0 -0
  171. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/registry.py +0 -0
  172. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/relay_rest_resources_generated.py +0 -0
  173. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/relay_rest_types_generated.py +0 -0
  174. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/short_codes.py +0 -0
  175. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/sip_profile.py +0 -0
  176. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/swml_webhooks_types_generated.py +0 -0
  177. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/verified_callers.py +0 -0
  178. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video.py +0 -0
  179. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video_resources_generated.py +0 -0
  180. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video_types_generated.py +0 -0
  181. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/voice_resources_generated.py +0 -0
  182. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/voice_types_generated.py +0 -0
  183. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/schema.json +0 -0
  184. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/__init__.py +0 -0
  185. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/models.py +0 -0
  186. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/README.md +0 -0
  187. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/__init__.py +0 -0
  188. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/README.md +0 -0
  189. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/__init__.py +0 -0
  190. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/skill.py +0 -0
  191. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/README.md +0 -0
  192. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/__init__.py +0 -0
  193. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/skill.py +0 -0
  194. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/README.md +0 -0
  195. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/__init__.py +0 -0
  196. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/skill.py +0 -0
  197. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/README.md +0 -0
  198. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/__init__.py +0 -0
  199. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/skill.py +0 -0
  200. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/README.md +0 -0
  201. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/__init__.py +0 -0
  202. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/skill.py +0 -0
  203. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/google_maps/__init__.py +0 -0
  204. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/google_maps/skill.py +0 -0
  205. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/README.md +0 -0
  206. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/__init__.py +0 -0
  207. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/skill.py +0 -0
  208. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/README.md +0 -0
  209. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/__init__.py +0 -0
  210. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/skill.py +0 -0
  211. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/README.md +0 -0
  212. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/__init__.py +0 -0
  213. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/skill.py +0 -0
  214. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/README.md +0 -0
  215. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/__init__.py +0 -0
  216. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/skill.py +0 -0
  217. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/README.md +0 -0
  218. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/__init__.py +0 -0
  219. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/skill.py +0 -0
  220. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/README.md +0 -0
  221. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/__init__.py +0 -0
  222. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/skill.py +0 -0
  223. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/registry.py +0 -0
  224. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/README.md +0 -0
  225. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/__init__.py +0 -0
  226. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/skill.py +0 -0
  227. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/README.md +0 -0
  228. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/__init__.py +0 -0
  229. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/skill.py +0 -0
  230. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/README.md +0 -0
  231. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/__init__.py +0 -0
  232. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/skill.py +0 -0
  233. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/README.md +0 -0
  234. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/__init__.py +0 -0
  235. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill.py +0 -0
  236. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill_improved.py +0 -0
  237. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill_original.py +0 -0
  238. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/README.md +0 -0
  239. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/__init__.py +0 -0
  240. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/skill.py +0 -0
  241. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/__init__.py +0 -0
  242. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/pom_utils.py +0 -0
  243. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/schema_utils.py +0 -0
  244. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/token_generators.py +0 -0
  245. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/url_validator.py +0 -0
  246. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/validators.py +0 -0
  247. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/web/__init__.py +0 -0
  248. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/web/web_service.py +0 -0
  249. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/SOURCES.txt +0 -0
  250. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/dependency_links.txt +0 -0
  251. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/entry_points.txt +0 -0
  252. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/requires.txt +0 -0
  253. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/top_level.txt +0 -0
  254. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/tests/test_examples.py +0 -0
  255. {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/tests/test_mcp_integration.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: signalwire-sdk
3
- Version: 3.4.0.dev110
3
+ Version: 3.4.1.dev111
4
4
  Summary: SignalWire SDK
5
5
  Author-email: SignalWire Team <info@signalwire.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "signalwire-sdk"
7
- version = "3.4.0.dev110"
7
+ version = "3.4.1.dev111"
8
8
  description = "SignalWire SDK"
9
9
  authors = [
10
10
  {name = "SignalWire Team", email = "info@signalwire.com"}
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
52
52
  from signalwire.utils.schema_utils import SchemaValidationError
53
53
  from signalwire.web import WebService
54
54
 
55
- __version__ = "3.4.0.dev110"
55
+ __version__ = "3.4.1.dev111"
56
56
 
57
57
  # §6.2-python (owner decision: NO packaging split — one package, all deps required;
58
58
  # IMPORT-TIME behavior only): the agent/web symbols are lazy-imported via module
@@ -68,7 +68,7 @@ class AgentServer:
68
68
  self.app = FastAPI(
69
69
  title="SignalWire AI Agents",
70
70
  description="Hosted SignalWire AI Agents",
71
- version="3.4.0.dev110",
71
+ version="3.4.1.dev111",
72
72
  redirect_slashes=False,
73
73
  docs_url=None,
74
74
  redoc_url=None,
@@ -252,6 +252,17 @@ Examples:
252
252
  help="Chunk size in words for sliding window strategy (default: 50)",
253
253
  )
254
254
 
255
+ parser.add_argument(
256
+ "--min-chunk-size",
257
+ type=int,
258
+ default=0,
259
+ help=(
260
+ "Markdown strategy: minimum words before a heading starts a new "
261
+ "chunk. Sections shorter than this merge into the next one instead "
262
+ "of being emitted alone (default: 0, split at every heading)"
263
+ ),
264
+ )
265
+
255
266
  parser.add_argument(
256
267
  "--overlap-size",
257
268
  type=int,
@@ -292,7 +303,12 @@ Examples:
292
303
  "--index-nlp-backend",
293
304
  choices=["nltk", "spacy"],
294
305
  default="nltk",
295
- help="NLP backend for document processing: nltk (fast, default) or spacy (better quality, slower)",
306
+ help=(
307
+ "NLP backend for document processing: nltk (fast, default) or "
308
+ "spacy (slower; also expands content with WordNet synonyms, whose "
309
+ "effect depends on your corpus and is written into the index -- "
310
+ "measure it before choosing, see preprocess_document_content)"
311
+ ),
296
312
  )
297
313
 
298
314
  parser.add_argument("--verbose", action="store_true", help="Enable verbose output")
@@ -484,6 +500,7 @@ Examples:
484
500
  chunking_strategy=args.chunking_strategy,
485
501
  max_sentences_per_chunk=args.max_sentences_per_chunk,
486
502
  chunk_size=args.chunk_size,
503
+ min_chunk_size=args.min_chunk_size,
487
504
  chunk_overlap=args.overlap_size,
488
505
  split_newlines=args.split_newlines,
489
506
  index_nlp_backend=args.index_nlp_backend,
@@ -593,6 +610,7 @@ Examples:
593
610
  chunking_strategy=args.chunking_strategy,
594
611
  max_sentences_per_chunk=args.max_sentences_per_chunk,
595
612
  chunk_size=args.chunk_size,
613
+ min_chunk_size=args.min_chunk_size,
596
614
  chunk_overlap=args.overlap_size,
597
615
  split_newlines=args.split_newlines,
598
616
  index_nlp_backend=args.index_nlp_backend,
@@ -741,7 +759,11 @@ def search_command() -> None:
741
759
  "--query-nlp-backend",
742
760
  choices=["nltk", "spacy"],
743
761
  default="nltk",
744
- help="NLP backend for query processing: nltk (fast, default) or spacy (better quality, slower)",
762
+ help=(
763
+ "NLP backend for query processing: nltk (fast, default) or spacy "
764
+ "(slower; expands the query with WordNet synonyms, whose effect "
765
+ "depends on your corpus -- measure it, see preprocess_query)"
766
+ ),
745
767
  )
746
768
  parser.add_argument(
747
769
  "--keyword-weight",
@@ -1417,9 +1439,17 @@ def console_entry_point() -> None:
1417
1439
  """Console script entry point for pip installation"""
1418
1440
  import sys
1419
1441
 
1420
- # Fast help check - show help without importing heavy modules
1442
+ # Fast help check - show help without importing heavy modules.
1443
+ #
1444
+ # NOTE: this text is hand-maintained and has drifted from the parser three
1445
+ # times over. It omitted the `markdown` and `json` strategies (both real and
1446
+ # in daily use), and it advertised all-mpnet-base-v2 as the default model
1447
+ # when DEFAULT_MODEL is all-MiniLM-L6-v2 -- a 768-dim answer to a 384-dim
1448
+ # question, which is the kind of thing someone builds a mismatched index on.
1449
+ # Anything added to the parser below must be added here too, or it is
1450
+ # undiscoverable.
1421
1451
  if len(sys.argv) > 1 and sys.argv[1] in ["--help", "-h"]:
1422
- print("""usage: sw-search [-h] [--output OUTPUT] [--chunking-strategy {sentence,sliding,paragraph,page,semantic,topic,qa}]
1452
+ print("""usage: sw-search [-h] [--output OUTPUT] [--chunking-strategy {sentence,sliding,paragraph,page,semantic,topic,qa,json,markdown}]
1423
1453
  [--max-sentences-per-chunk MAX_SENTENCES_PER_CHUNK] [--chunk-size CHUNK_SIZE]
1424
1454
  [--overlap-size OVERLAP_SIZE] [--split-newlines SPLIT_NEWLINES] [--file-types FILE_TYPES]
1425
1455
  [--exclude EXCLUDE] [--languages LANGUAGES] [--model MODEL] [--tags TAGS]
@@ -1435,12 +1465,20 @@ positional arguments:
1435
1465
  options:
1436
1466
  -h, --help show this help message and exit
1437
1467
  --output OUTPUT Output .swsearch file (default: sources.swsearch)
1438
- --chunking-strategy {sentence,sliding,paragraph,page,semantic,topic,qa}
1439
- Chunking strategy to use (default: sentence)
1468
+ --chunking-strategy {sentence,sliding,paragraph,page,semantic,topic,qa,json,markdown}
1469
+ Chunking strategy to use (default: sentence). Use
1470
+ "markdown" for documentation with code blocks.
1440
1471
  --max-sentences-per-chunk MAX_SENTENCES_PER_CHUNK
1441
1472
  Maximum sentences per chunk for sentence strategy (default: 5)
1442
1473
  --chunk-size CHUNK_SIZE
1443
- Chunk size in words for sliding window strategy (default: 50)
1474
+ Chunk size in words (default: 50). For the markdown
1475
+ strategy this is the split threshold, applied as
1476
+ chunk_size * 6 characters.
1477
+ --min-chunk-size MIN_CHUNK_SIZE
1478
+ Markdown strategy: minimum words before a heading starts
1479
+ a new chunk. Shorter sections merge into the next one
1480
+ instead of being emitted alone (default: 0, split at
1481
+ every heading).
1444
1482
  --overlap-size OVERLAP_SIZE
1445
1483
  Overlap size in words for sliding window strategy (default: 10)
1446
1484
  --split-newlines SPLIT_NEWLINES
@@ -1450,10 +1488,11 @@ options:
1450
1488
  --exclude EXCLUDE Comma-separated glob patterns to exclude (e.g., "**/test/**,**/__pycache__/**")
1451
1489
  --languages LANGUAGES
1452
1490
  Comma-separated language codes (default: en)
1453
- --model MODEL Sentence transformer model name (default: sentence-transformers/all-mpnet-base-v2)
1491
+ --model MODEL Sentence transformer model name or alias (mini/base/
1492
+ large). Default: mini (sentence-transformers/all-MiniLM-L6-v2)
1454
1493
  --tags TAGS Comma-separated tags to add to all chunks
1455
1494
  --index-nlp-backend {nltk,spacy}
1456
- NLP backend for document processing: nltk (fast, default) or spacy (better quality, slower)
1495
+ NLP backend for document processing: nltk (fast, default) or spacy (slower, expands with WordNet synonyms — effect depends on your corpus, measure it)
1457
1496
  --verbose Enable verbose output
1458
1497
  --validate Validate the created index after building
1459
1498
  --semantic-threshold SEMANTIC_THRESHOLD
@@ -116,6 +116,7 @@ class DocumentProcessor:
116
116
  max_sentences_per_chunk: int = 5,
117
117
  chunk_size: int = 50,
118
118
  chunk_overlap: int = 10,
119
+ min_chunk_size: int = 0,
119
120
  split_newlines: int | None = None,
120
121
  index_nlp_backend: str = "nltk",
121
122
  verbose: bool = False,
@@ -137,6 +138,11 @@ class DocumentProcessor:
137
138
  - 'json': JSON structure-aware chunking
138
139
  - 'markdown': Markdown structure-aware chunking with code block detection
139
140
  max_sentences_per_chunk: For sentence strategy (default: 5)
141
+ min_chunk_size: For markdown strategy - minimum words before a
142
+ heading starts a new chunk. Sections shorter than this are
143
+ merged forward into the next one instead of being emitted
144
+ alone. Defaults to 0, which preserves the previous behaviour
145
+ of splitting at every heading.
140
146
  chunk_size: For sliding strategy - words per chunk (default: 50)
141
147
  chunk_overlap: For sliding strategy - overlap in words (default: 10)
142
148
  split_newlines: For sentence strategy - split on multiple newlines (optional)
@@ -148,6 +154,7 @@ class DocumentProcessor:
148
154
  self.chunking_strategy = chunking_strategy
149
155
  self.max_sentences_per_chunk = max_sentences_per_chunk
150
156
  self.chunk_size = chunk_size
157
+ self.min_chunk_size = min_chunk_size
151
158
  self.chunk_overlap = chunk_overlap
152
159
  self.split_newlines = split_newlines
153
160
  self.semantic_threshold = semantic_threshold
@@ -364,7 +371,7 @@ class DocumentProcessor:
364
371
 
365
372
  def _extract_powerpoint(self, file_path: str) -> Any:
366
373
  """Extract text from PowerPoint files"""
367
- if not Presentation:
374
+ if Presentation is None:
368
375
  return json.dumps(
369
376
  {"error": "python-pptx not available for PowerPoint processing"}
370
377
  )
@@ -461,6 +468,12 @@ class DocumentProcessor:
461
468
  tokens = md.parse(content)
462
469
  lines = content.split("\n")
463
470
  threshold_chars = max(1, self.chunk_size * 6)
471
+ # Minimum size before a heading is allowed to start a new chunk. Without
472
+ # this the walker flushes at EVERY heading, so a document with dense
473
+ # headings yields chunks that are a title plus one sentence -- true to
474
+ # the structure but too thin to retrieve on, and they crowd out the
475
+ # substantial chunks around them. 0 keeps the original behaviour.
476
+ min_chars = max(0, self.min_chunk_size * 6)
464
477
 
465
478
  # Group top-level tokens into atomic blocks.
466
479
  # A "block" here = heading OR one non-heading container whose source
@@ -537,6 +550,19 @@ class DocumentProcessor:
537
550
 
538
551
  for block in blocks:
539
552
  if block["type"] == "heading":
553
+ # Too small to stand alone: fold this section into the one being
554
+ # built rather than emitting it. The heading line stays in the
555
+ # text, so the reader still sees the structure; only the chunk
556
+ # boundary moves.
557
+ if min_chars and 0 < current_size < min_chars:
558
+ hierarchy = [
559
+ *hierarchy[: block["level"] - 1],
560
+ block["heading_text"],
561
+ ]
562
+ current_lines.extend(block["source_lines"])
563
+ current_end_line = block["end_line"]
564
+ current_size += sum(len(line) + 1 for line in block["source_lines"])
565
+ continue
540
566
  # Flush whatever preceded this heading under the old hierarchy.
541
567
  flush()
542
568
  level = block["level"]
@@ -938,7 +964,13 @@ class DocumentProcessor:
938
964
  if not sentences:
939
965
  return 1
940
966
 
967
+ # `if not sentences` does not catch a list of EMPTY sentences, which is
968
+ # what a line of dots or an ASCII rule splits into -- ". . . " becomes
969
+ # ['', '', '', '']. That averages to 0.0 and the division below raises
970
+ # ZeroDivisionError on a document that is merely ugly, not invalid.
941
971
  avg_sentence_length = sum(len(s) for s in sentences) / len(sentences)
972
+ if avg_sentence_length <= 0:
973
+ return 1
942
974
  # Target chunk size divided by average sentence length
943
975
  optimal_sentences = max(1, int(self.chunk_size / avg_sentence_length))
944
976
  return min(optimal_sentences, 10) # Cap at 10 sentences for readability
@@ -1696,6 +1728,7 @@ class DocumentProcessor:
1696
1728
  # reachable by either. Chunk COUNT and order are untouched, so
1697
1729
  # chunk_index still addresses the same source chunk.
1698
1730
  current_heading = ""
1731
+ current_doc = None
1699
1732
  for idx, json_chunk in enumerate(data["chunks"]):
1700
1733
  if not isinstance(json_chunk, dict) or "content" not in json_chunk:
1701
1734
  logger.warning(f"Skipping invalid chunk {idx} in {filename}")
@@ -1741,6 +1774,24 @@ class DocumentProcessor:
1741
1774
  # Create chunk with proper structure
1742
1775
  chunk_text = json_chunk["content"]
1743
1776
  stripped = chunk_text.lstrip()
1777
+
1778
+ # One JSON file routinely holds chunks from many source
1779
+ # documents, and current_heading would otherwise carry across
1780
+ # the boundary: the last heading of one document prepended to
1781
+ # the first subsection of the next, asserting a subject the
1782
+ # chunk does not have. It does not fire on the corpus this was
1783
+ # written for -- every chunk there opens with its own h1, so
1784
+ # the heading is reset each time, and a scan of 16,894 chunks
1785
+ # found no instance -- but that is a property of those inputs,
1786
+ # not of this loop. Reset when the document changes.
1787
+ doc_key = (
1788
+ json_metadata.get("url")
1789
+ or json_metadata.get("source")
1790
+ or json_metadata.get("filename")
1791
+ )
1792
+ if doc_key != current_doc:
1793
+ current_doc = doc_key
1794
+ current_heading = ""
1744
1795
  if stripped.startswith("# ") and not stripped.startswith("## "):
1745
1796
  # A new topic: remember its heading, and leave it alone -
1746
1797
  # it already names itself.
@@ -47,6 +47,7 @@ class IndexBuilder:
47
47
  chunking_strategy: str = "sentence",
48
48
  max_sentences_per_chunk: int = 5,
49
49
  chunk_size: int = 50,
50
+ min_chunk_size: int = 0,
50
51
  chunk_overlap: int = 10,
51
52
  split_newlines: int | None = None,
52
53
  index_nlp_backend: str = "nltk",
@@ -64,6 +65,8 @@ class IndexBuilder:
64
65
  chunking_strategy: Strategy for chunking documents ('sentence', 'sliding', 'paragraph', 'page', 'semantic', 'topic', 'qa', 'json')
65
66
  max_sentences_per_chunk: For sentence strategy (default: 5)
66
67
  chunk_size: For sliding strategy - words per chunk (default: 50)
68
+ min_chunk_size: For markdown strategy - minimum words before a
69
+ heading starts a new chunk (default: 0, split at every heading)
67
70
  chunk_overlap: For sliding strategy - overlap in words (default: 10)
68
71
  split_newlines: For sentence strategy - split on multiple newlines (optional)
69
72
  index_nlp_backend: NLP backend for indexing (default: 'nltk')
@@ -77,6 +80,7 @@ class IndexBuilder:
77
80
  self.chunking_strategy = chunking_strategy
78
81
  self.max_sentences_per_chunk = max_sentences_per_chunk
79
82
  self.chunk_size = chunk_size
83
+ self.min_chunk_size = min_chunk_size
80
84
  self.chunk_overlap = chunk_overlap
81
85
  self.split_newlines = split_newlines
82
86
  self.index_nlp_backend = index_nlp_backend
@@ -106,6 +110,7 @@ class IndexBuilder:
106
110
  chunking_strategy=chunking_strategy,
107
111
  max_sentences_per_chunk=max_sentences_per_chunk,
108
112
  chunk_size=chunk_size,
113
+ min_chunk_size=min_chunk_size,
109
114
  chunk_overlap=chunk_overlap,
110
115
  split_newlines=split_newlines,
111
116
  index_nlp_backend=self.index_nlp_backend,
@@ -679,6 +684,7 @@ class IndexBuilder:
679
684
  "embedding_model": self.model_name,
680
685
  "embedding_dimensions": str(embedding_dimensions),
681
686
  "chunk_size": str(self.chunk_size),
687
+ "min_chunk_size": str(self.min_chunk_size),
682
688
  "chunk_overlap": str(self.chunk_overlap),
683
689
  "preprocessing_version": "1.0",
684
690
  "languages": json.dumps(languages),
@@ -778,6 +784,7 @@ class IndexBuilder:
778
784
  if not Path(index_file).exists():
779
785
  return {"valid": False, "error": "Index file does not exist"}
780
786
 
787
+ conn = None
781
788
  try:
782
789
  conn = sqlite3.connect(index_file)
783
790
  cursor = conn.cursor()
@@ -804,8 +811,6 @@ class IndexBuilder:
804
811
  cursor.execute("SELECT COUNT(DISTINCT filename) FROM chunks")
805
812
  file_count = cursor.fetchone()[0]
806
813
 
807
- conn.close()
808
-
809
814
  return {
810
815
  "valid": True,
811
816
  "chunk_count": chunk_count,
@@ -815,6 +820,15 @@ class IndexBuilder:
815
820
 
816
821
  except Exception as e:
817
822
  return {"valid": False, "error": str(e)}
823
+ finally:
824
+ # Every failure path here used to leak the handle: the early return
825
+ # for missing tables, and the except that turns any error into
826
+ # {"valid": False}. Validating a directory of indexes -- which is
827
+ # what this function is for -- leaked one descriptor per bad index,
828
+ # and on Windows kept each file locked. Close it once, on the way
829
+ # out, however we leave.
830
+ if conn is not None:
831
+ conn.close()
818
832
 
819
833
  def _store_chunks_pgvector(
820
834
  self,
@@ -73,6 +73,7 @@ class SearchIndexMigrator:
73
73
  "config": {},
74
74
  }
75
75
 
76
+ sqlite_conn = None
76
77
  try:
77
78
  # Connect to SQLite
78
79
  if self.verbose:
@@ -272,7 +273,14 @@ class SearchIndexMigrator:
272
273
  logger.error(f"Migration failed: {e}")
273
274
  raise
274
275
  finally:
275
- sqlite_conn.close()
276
+ # Guarded because sqlite3.connect itself can raise -- a missing
277
+ # file, a permissions problem, a corrupt database. The name is then
278
+ # unbound and this finally raised UnboundLocalError ON TOP of the
279
+ # real failure, so the caller was told "cannot access local
280
+ # variable" instead of "unable to open database file". A cleanup
281
+ # handler must not be able to destroy the diagnosis.
282
+ if sqlite_conn is not None:
283
+ sqlite_conn.close()
276
284
 
277
285
  return stats
278
286
 
@@ -314,6 +322,10 @@ class SearchIndexMigrator:
314
322
  print(f"Connecting to pgvector collection: {collection_name}")
315
323
 
316
324
  pgvector = PgVectorBackend(connection_string)
325
+ # Bound before the try so the finally can never raise NameError over
326
+ # the top of a real failure -- the same defect this commit fixes for
327
+ # sqlite_conn in migrate_sqlite_to_pgvector.
328
+ sqlite_out = None
317
329
 
318
330
  try:
319
331
  # Get collection stats and config
@@ -336,6 +348,7 @@ class SearchIndexMigrator:
336
348
 
337
349
  conn = sqlite3.connect(output_path)
338
350
  cursor = conn.cursor()
351
+ sqlite_out = conn # closed in the finally below, not inline
339
352
 
340
353
  # Create schema (matching index_builder.py)
341
354
  cursor.execute("""
@@ -425,9 +438,17 @@ class SearchIndexMigrator:
425
438
  print("This feature is planned for future development.")
426
439
 
427
440
  conn.commit()
428
- conn.close()
429
441
 
430
442
  finally:
443
+ # The inline conn.close() above only ran when everything succeeded,
444
+ # while this finally closed pgvector alone -- so any failure between
445
+ # opening the file and committing leaked the SQLite handle and left
446
+ # a half-written .swsearch locked on Windows.
447
+ if sqlite_out is not None:
448
+ try:
449
+ sqlite_out.close()
450
+ except Exception: # cleanup must not mask the real error
451
+ logger.warning("Could not close SQLite output file", exc_info=True)
431
452
  pgvector.close()
432
453
 
433
454
  return stats
@@ -449,21 +470,27 @@ class SearchIndexMigrator:
449
470
  info["type"] = "sqlite"
450
471
  info["path"] = index_path
451
472
 
473
+ # try/finally rather than a trailing close(): a corrupt or
474
+ # partially written index makes any of these statements raise, and
475
+ # the handle would otherwise be held until GC -- which on Windows
476
+ # keeps the file locked against the very repair the caller is
477
+ # probably attempting.
452
478
  conn = sqlite3.connect(index_path)
453
- cursor = conn.cursor()
454
-
455
- # Get config
456
- cursor.execute("SELECT key, value FROM config")
457
- info["config"] = dict(cursor.fetchall())
479
+ try:
480
+ cursor = conn.cursor()
458
481
 
459
- # Get stats
460
- cursor.execute("SELECT COUNT(*) FROM chunks")
461
- info["total_chunks"] = cursor.fetchone()[0]
482
+ # Get config
483
+ cursor.execute("SELECT key, value FROM config")
484
+ info["config"] = dict(cursor.fetchall())
462
485
 
463
- cursor.execute("SELECT COUNT(DISTINCT filename) FROM chunks")
464
- info["total_files"] = cursor.fetchone()[0]
486
+ # Get stats
487
+ cursor.execute("SELECT COUNT(*) FROM chunks")
488
+ info["total_chunks"] = cursor.fetchone()[0]
465
489
 
466
- conn.close()
490
+ cursor.execute("SELECT COUNT(DISTINCT filename) FROM chunks")
491
+ info["total_files"] = cursor.fetchone()[0]
492
+ finally:
493
+ conn.close()
467
494
 
468
495
  else:
469
496
  info["type"] = "unknown"
@@ -135,11 +135,32 @@ class PgVectorBackend:
135
135
  )
136
136
 
137
137
  # Create indexes
138
+ #
139
+ # HNSW, not IVFFlat. IVFFlat partitions into `lists` clusters and
140
+ # visits `probes` of them, which has two costs measured on a 21k-row
141
+ # SignalWire collection at lists=100:
142
+ #
143
+ # - recall is capped by how many lists you dare probe, and the
144
+ # ceiling is a CLIFF, not a curve: at probes=50 the planner
145
+ # decides half the index costs more than the table, drops to a
146
+ # Seq Scan, and latency goes 10ms -> 113ms for +6 points. There
147
+ # is nothing available in between.
148
+ # - re-clustering on every rebuild moves which chunks a given
149
+ # probes value can reach. Rebuilding the index alone, with no
150
+ # corpus change, swung recall@1 on one question class by 15
151
+ # points, which makes before/after measurement across a rebuild
152
+ # nearly worthless.
153
+ #
154
+ # HNSW has neither. Measured through the full hybrid pipeline over a
155
+ # 360-case golden set, HNSW ef_search=200 against IVFFlat probes=30:
156
+ # recall@1 64%->67%, recall@5 79%->84%, wrong-answer rate 25%->22%,
157
+ # end-to-end search latency 72ms->66ms, and no question class worse.
158
+ # Rebuilding the HNSW index from scratch reproduced every number to
159
+ # within 2 points, against IVFFlat's 15.
138
160
  cursor.execute(
139
161
  psycopg2_sql.SQL("""
140
162
  CREATE INDEX IF NOT EXISTS {idx}
141
- ON {tbl} USING ivfflat (embedding vector_cosine_ops)
142
- WITH (lists = 100)
163
+ ON {tbl} USING hnsw (embedding vector_cosine_ops)
143
164
  """).format(idx=idx_embedding, tbl=tbl)
144
165
  )
145
166
 
@@ -628,8 +649,23 @@ class PgVectorSearchBackend:
628
649
  ) -> list[dict[str, Any]]:
629
650
  """Perform vector similarity search"""
630
651
  with self.conn.cursor() as cursor:
631
- # Set probes for IVFFlat index to ensure we get enough results
632
- cursor.execute("SET LOCAL ivfflat.probes = %s", (max(count, 10),))
652
+ # Set probes for IVFFlat index to ensure we get enough results.
653
+ #
654
+ # NOT "SET LOCAL". _connect() enables autocommit - see the docstring
655
+ # there, it is deliberate, to avoid holding locks that block
656
+ # DROP TABLE during reindex. Under autocommit every statement is its
657
+ # own transaction, so a transaction-scoped SET LOCAL is discarded
658
+ # before the SELECT below ever runs and the index falls back to the
659
+ # default probes=1. On a lists=100 index that reads 1% of the table:
660
+ # measured vector recall@5 was 25% at probes=1 against 55% with
661
+ # probes applied, for ~2ms. Plain SET is session-scoped, survives
662
+ # autocommit, and opens no transaction of its own.
663
+ # Both are set because both index types are in the wild: collections
664
+ # built before the HNSW switch still carry an IVFFlat index and
665
+ # would silently fall back to probes=1 if only ef_search were set.
666
+ # Each GUC is inert against the other index type.
667
+ cursor.execute("SET ivfflat.probes = %s", (max(count, 10),))
668
+ cursor.execute("SET hnsw.ef_search = %s", (max(count * 6, 200),))
633
669
  # Build query parts
634
670
  tbl = psycopg2_sql.Identifier(self.table_name)
635
671
  parts = [
@@ -1044,6 +1080,58 @@ class PgVectorSearchBackend:
1044
1080
 
1045
1081
  return list(candidates.values())
1046
1082
 
1083
+ # Non-semantic signals may reorder results; they may not choose them.
1084
+ #
1085
+ # Every source used to feed one pool -- base = max(all signals) + 0.1 per
1086
+ # agreeing source -- but the signals are not measured on the same scale.
1087
+ # Cosine similarity is bounded by what the embedding model can express and
1088
+ # tops out near 0.62 on a real corpus. Metadata term coverage maxes at 0.60
1089
+ # by construction, a basename hit is a flat 0.67, and both collect the same
1090
+ # agreement boost. A constant therefore outranks a similarity by design.
1091
+ #
1092
+ # Measured on "play audio to the caller": call-flow-builder/gather-input,
1093
+ # cosine 0.555, scored 0.800 and took rank 1 over swml/reference/calling/play
1094
+ # at 0.620. A page about GATHERING INPUT answered a question about PLAYING
1095
+ # AUDIO, because term coverage saturated where the vector could not.
1096
+ #
1097
+ # Normalising the constants into the cosine range was the other option and
1098
+ # it is worse: that range moves with the corpus and the embedding model, so
1099
+ # the calibration would rot silently. This fails safe instead. A chunk the
1100
+ # vector search actually found keeps its cosine as the score and earns at
1101
+ # most TIEBREAK_MAX from agreement -- enough to order near-ties, never enough
1102
+ # to leapfrog a materially better match. A chunk the vector search never
1103
+ # returned is mapped into the band below the weakest semantic hit: still
1104
+ # reachable, still ordered among its peers, but it cannot take the top from
1105
+ # a page that is genuinely closer to the question.
1106
+ TIEBREAK_MAX = 0.05
1107
+
1108
+ def _blend(
1109
+ self,
1110
+ all_sources: dict[Any, dict[str, float]],
1111
+ results_map: dict[Any, dict[str, Any]],
1112
+ agreement_boost: float,
1113
+ ) -> None:
1114
+ # max, not min: vector search returns `count` rows, so the weakest one
1115
+ # is the count-th best cosine and a floor built from it would move with
1116
+ # `count` -- making the same candidate score differently depending on
1117
+ # how many results were asked for. The best score does not move.
1118
+ vector_scores = [s["vector"] for s in all_sources.values() if "vector" in s]
1119
+ ceiling = max(vector_scores) if vector_scores else None
1120
+ for chunk_id, result in results_map.items():
1121
+ sources = all_sources[chunk_id]
1122
+ if "vector" in sources:
1123
+ boost = agreement_boost * (len(sources) - 1)
1124
+ score = sources["vector"] + min(self.TIEBREAK_MAX, boost)
1125
+ else:
1126
+ base = max(sources.values())
1127
+ # Scale into [0, ceiling) so ordering survives but the top does
1128
+ # not. Without any vector result there is nothing to rank against,
1129
+ # so keep the old behaviour rather than invent a floor.
1130
+ score = base * ceiling * 0.99 if ceiling is not None else base
1131
+ result["score"] = min(1.0, score)
1132
+ result["sources"] = sources
1133
+ result["final_score"] = result["score"]
1134
+
1047
1135
  def _merge_results(
1048
1136
  self,
1049
1137
  vector_results: list[dict[str, Any]],
@@ -1073,13 +1161,7 @@ class PgVectorSearchBackend:
1073
1161
  results_map[chunk_id] = result.copy()
1074
1162
  all_sources.setdefault(chunk_id, {})["keyword"] = result["score"]
1075
1163
 
1076
- # Score: max signal wins, boost for agreement
1077
- for chunk_id, result in results_map.items():
1078
- sources = all_sources[chunk_id]
1079
- scores = list(sources.values())
1080
- base = max(scores)
1081
- boost = agreement_boost * (len(scores) - 1)
1082
- result["score"] = min(1.0, base + boost)
1164
+ self._blend(all_sources, results_map, agreement_boost)
1083
1165
 
1084
1166
  merged = list(results_map.values())
1085
1167
  merged.sort(key=lambda x: x["score"], reverse=True)
@@ -1123,15 +1205,7 @@ class PgVectorSearchBackend:
1123
1205
  results_map[chunk_id] = result.copy()
1124
1206
  all_sources.setdefault(chunk_id, {})["metadata"] = result["score"]
1125
1207
 
1126
- # Score: max signal wins, boost for each additional agreeing source
1127
- for chunk_id, result in results_map.items():
1128
- sources = all_sources[chunk_id]
1129
- scores = list(sources.values())
1130
- base = max(scores)
1131
- boost = agreement_boost * (len(scores) - 1)
1132
- result["score"] = min(1.0, base + boost)
1133
- result["sources"] = sources
1134
- result["final_score"] = result["score"]
1208
+ self._blend(all_sources, results_map, agreement_boost)
1135
1209
 
1136
1210
  merged = list(results_map.values())
1137
1211
  merged.sort(key=lambda x: x["score"], reverse=True)