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.
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/PKG-INFO +1 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/pyproject.toml +1 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/__init__.py +1 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agent_server.py +1 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/build_search.py +48 -9
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/document_processor.py +52 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/index_builder.py +16 -2
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/migration.py +40 -13
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/pgvector_backend.py +94 -20
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/query_processor.py +54 -3
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/search_engine.py +75 -10
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/search_service.py +54 -3
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/PKG-INFO +1 -1
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/LICENSE +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/sw-agent-init.1 +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/sw-search.1 +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/man/swaig-test.1 +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/setup.cfg +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/setup.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agents/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/agents/bedrock.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/client.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/gateway.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/ai_chat/handoff.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/config.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/agent_loader.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/argparse_helpers.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/dynamic_config.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/core/service_loader.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/dokku.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/datamap_exec.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/execution/webhook_exec.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/init_project.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/output_formatter.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/output/swml_dump.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/data_generation.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/data_overrides.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/simulation/mock_env.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/swaig_test_wrapper.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/test_swaig.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/cli/types.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/_agent_host.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/config/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/deployment/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/deployment/handlers/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/prompt/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/prompt/manager.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/routing/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/security/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/swml/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/decorator.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/registry.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent/tools/type_inference.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/agent_base.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/auth_handler.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/capabilities.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/config_loader.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/contexts.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/data_map.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/function_result.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/logging_config.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/_mixin_host.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/ai_config_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/auth_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/mcp_server_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/prompt_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/serverless_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/skill_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/state_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/tool_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/mixins/web_mixin.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/pom_builder.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/post_prompt.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/post_prompt_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/security_utils.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/session_manager.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/webhook_middleware.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security/webhook_validator.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/security_config.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/skill_base.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/skill_manager.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_actions_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_function.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swaig_request_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_builder.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_handler.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_renderer.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_service.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/swml_verbs_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/core/web.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/livewire/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/livewire/plugins.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/gateway_service.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/mcp_manager.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/mcp_gateway/session_manager.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/pom.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/pom/pom_tool.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/concierge.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/faq_bot.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/info_gatherer.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/receptionist.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/prefabs/survey.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/py.typed +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/call.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/client.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/constants.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/event.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/message.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/relay/protocol_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_base.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_pagination.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/_request_options.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/call_handler.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/client.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/_client_tree_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/addresses.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/calling_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/chat_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/datasphere_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fabric_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fax_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/fax_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/imported_numbers.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/logs_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/lookup.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/message_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/message_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/messages_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/messages_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/mfa.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/number_groups.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/phone_numbers.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/project_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/projects_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/projects_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/pubsub_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/queues.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/recordings.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/registry.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/relay_rest_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/relay_rest_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/short_codes.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/sip_profile.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/swml_webhooks_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/verified_callers.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/video_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/voice_resources_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/rest/namespaces/voice_types_generated.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/schema.json +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/search/models.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/api_ninjas_trivia/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/claude_skills/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datasphere_serverless/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/datetime/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/google_maps/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/google_maps/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/info_gatherer/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/joke/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/math/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/mcp_gateway/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/native_vector_search/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/play_background_file/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/registry.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/spider/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/swml_transfer/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/weather_api/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill_improved.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/web_search/skill_original.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/README.md +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/skills/wikipedia_search/skill.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/pom_utils.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/schema_utils.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/token_generators.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/url_validator.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/utils/validators.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/web/__init__.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/web/web_service.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/SOURCES.txt +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/dependency_links.txt +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/entry_points.txt +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/requires.txt +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire_sdk.egg-info/top_level.txt +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/tests/test_examples.py +0 -0
- {signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/tests/test_mcp_integration.py +0 -0
{signalwire_sdk-3.4.0.dev110 → signalwire_sdk-3.4.1.dev111}/signalwire/signalwire/__init__.py
RENAMED
|
@@ -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.
|
|
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
|
|
@@ -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=
|
|
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=
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
461
|
-
|
|
482
|
+
# Get config
|
|
483
|
+
cursor.execute("SELECT key, value FROM config")
|
|
484
|
+
info["config"] = dict(cursor.fetchall())
|
|
462
485
|
|
|
463
|
-
|
|
464
|
-
|
|
486
|
+
# Get stats
|
|
487
|
+
cursor.execute("SELECT COUNT(*) FROM chunks")
|
|
488
|
+
info["total_chunks"] = cursor.fetchone()[0]
|
|
465
489
|
|
|
466
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|