fastworkflow 3.1.0__tar.gz → 3.1.2__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 (216) hide show
  1. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/PKG-INFO +2 -2
  2. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/intent_detection.py +18 -1
  3. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/docs/integrate-chat-agent/reference.md +5 -1
  4. fastworkflow-3.1.2/fastworkflow/utils/fuzzy_match.py +170 -0
  5. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/signatures.py +43 -6
  6. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/pyproject.toml +2 -2
  7. fastworkflow-3.1.0/fastworkflow/utils/fuzzy_match.py +0 -70
  8. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/LICENSE +0 -0
  9. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/README.md +0 -0
  10. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/.DS_Store +0 -0
  11. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/__init__.py +0 -0
  12. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_commands/.gitkeep +0 -0
  13. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/__init__.py +0 -0
  14. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/__init__.py +0 -0
  15. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/abort.py +0 -0
  16. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/ErrorCorrection/you_misunderstood.py +0 -0
  17. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/go_up.py +0 -0
  18. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/reset_context.py +0 -0
  19. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_can_i_do.py +0 -0
  20. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/IntentDetection/what_is_current_context.py +0 -0
  21. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/__init__.py +0 -0
  22. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/_commands/wildcard.py +0 -0
  23. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/command_context_model.json +0 -0
  24. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/_workflows/command_metadata_extraction/parameter_extraction.py +0 -0
  25. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/active_workflow.py +0 -0
  26. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/__main__.py +0 -0
  27. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/ast_class_extractor.py +0 -0
  28. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/class_analysis_structures.py +0 -0
  29. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/cli_specification.md +0 -0
  30. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/command_dependency_resolver.py +0 -0
  31. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/command_file_generator.py +0 -0
  32. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/command_file_template.py +0 -0
  33. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/command_import_utils.py +0 -0
  34. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/command_stub_generator.py +0 -0
  35. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/context_folder_generator.py +0 -0
  36. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/context_model_generator.py +0 -0
  37. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/dependency_manager.py +0 -0
  38. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/dir_scanner.py +0 -0
  39. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/documentation_generator.py +0 -0
  40. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/genai_postprocessor.py +0 -0
  41. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/inheritance_block_regenerator.py +0 -0
  42. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/libcst_transformers.py +0 -0
  43. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/navigator_stub_generator.py +0 -0
  44. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/pydantic_model_generator.py +0 -0
  45. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/build/utterance_generator.py +0 -0
  46. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/cache_matching.py +0 -0
  47. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/chat_session.py +0 -0
  48. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/checkpoint_store.py +0 -0
  49. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/cli.py +0 -0
  50. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_context_model.py +0 -0
  51. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_directory.py +0 -0
  52. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_executor.py +0 -0
  53. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_interfaces.py +0 -0
  54. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_metadata_api.py +0 -0
  55. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/command_routing.py +0 -0
  56. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/conversation_history_io.py +0 -0
  57. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/distillation.py +0 -0
  58. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/docs/context_modules_prd.txt +0 -0
  59. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/docs/integrate-chat-agent/SKILL.md +0 -0
  60. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/README.md +0 -0
  61. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/_commands/WorkItem/get_status.py +0 -0
  62. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/_commands/generate_report.py +0 -0
  63. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/_commands/startup.py +0 -0
  64. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/simple_workflow_template.json +0 -0
  65. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/extended_workflow_example/workflow_inheritance_model.json +0 -0
  66. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/fastworkflow.env +0 -0
  67. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/fastworkflow.passwords.env +0 -0
  68. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/hello_world/_commands/README.md +0 -0
  69. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/hello_world/_commands/add_two_numbers.py +0 -0
  70. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/hello_world/_commands/context_inheritance_model.json +0 -0
  71. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/hello_world/application/add_two_numbers.py +0 -0
  72. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_1/_commands/context_inheritance_model.json +0 -0
  73. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_1/_commands/send_message.py +0 -0
  74. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_1/application/send_message.py +0 -0
  75. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_2/_commands/User/_User.py +0 -0
  76. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_2/_commands/User/send_message.py +0 -0
  77. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_2/_commands/context_inheritance_model.json +0 -0
  78. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_2/_commands/startup.py +0 -0
  79. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_2/application/user.py +0 -0
  80. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/_PremiumUser.py +0 -0
  81. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/PremiumUser/send_priority_message.py +0 -0
  82. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/User/_User.py +0 -0
  83. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/User/send_message.py +0 -0
  84. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/context_inheritance_model.json +0 -0
  85. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/_commands/initialize_user.py +0 -0
  86. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_3/application/user.py +0 -0
  87. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/_ChatRoom.py +0 -0
  88. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/add_user.py +0 -0
  89. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/broadcast_message.py +0 -0
  90. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/get_current_user.py +0 -0
  91. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/list_users.py +0 -0
  92. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/ChatRoom/set_current_user.py +0 -0
  93. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/_PremiumUser.py +0 -0
  94. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/PremiumUser/send_priority_message.py +0 -0
  95. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/User/_User.py +0 -0
  96. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/User/send_message.py +0 -0
  97. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/context_inheritance_model.json +0 -0
  98. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/_commands/set_root_context.py +0 -0
  99. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/application/chatroom.py +0 -0
  100. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/application/user.py +0 -0
  101. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/context_hierarchy_model.json +0 -0
  102. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/messaging_app_4/startup_action.json +0 -0
  103. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/calculate.py +0 -0
  104. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/cancel_pending_order.py +0 -0
  105. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/exchange_delivered_order_items.py +0 -0
  106. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_email.py +0 -0
  107. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/find_user_id_by_name_zip.py +0 -0
  108. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/get_order_details.py +0 -0
  109. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/get_product_details.py +0 -0
  110. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/get_user_details.py +0 -0
  111. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/list_all_product_types.py +0 -0
  112. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_address.py +0 -0
  113. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_items.py +0 -0
  114. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/modify_pending_order_payment.py +0 -0
  115. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/modify_user_address.py +0 -0
  116. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/return_delivered_order_items.py +0 -0
  117. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/_commands/transfer_to_human_agents.py +0 -0
  118. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/context_inheritance_model.json +0 -0
  119. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/retail_data/__init__.py +0 -0
  120. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/retail_data/orders.json +0 -0
  121. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/retail_data/products.json +0 -0
  122. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/retail_data/users.json +0 -0
  123. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/calculate.py +0 -0
  124. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/cancel_pending_order.py +0 -0
  125. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/exchange_delivered_order_items.py +0 -0
  126. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_email.py +0 -0
  127. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/find_user_id_by_name_zip.py +0 -0
  128. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/get_order_details.py +0 -0
  129. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/get_product_details.py +0 -0
  130. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/get_user_details.py +0 -0
  131. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/list_all_product_types.py +0 -0
  132. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_address.py +0 -0
  133. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_items.py +0 -0
  134. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/modify_pending_order_payment.py +0 -0
  135. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/modify_user_address.py +0 -0
  136. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/return_delivered_order_items.py +0 -0
  137. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/think.py +0 -0
  138. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/tool.py +0 -0
  139. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/tools/transfer_to_human_agents.py +0 -0
  140. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/retail_workflow/workflow_description.txt +0 -0
  141. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/__init__.py +0 -0
  142. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/_WorkItem.py +0 -0
  143. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/add_child_workitem.py +0 -0
  144. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/get_status.py +0 -0
  145. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/go_to_workitem.py +0 -0
  146. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/mark_as_complete.py +0 -0
  147. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_first_child_workitem.py +0 -0
  148. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_last_child_workitem.py +0 -0
  149. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_next_workitem.py +0 -0
  150. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/move_to_previous_workitem.py +0 -0
  151. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_all_child_workitems.py +0 -0
  152. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/remove_child_workitem.py +0 -0
  153. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/WorkItem/show_schema.py +0 -0
  154. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/_commands/startup.py +0 -0
  155. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/application/__init__.py +0 -0
  156. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/application/workitem.py +0 -0
  157. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/simple_workflow_template.json +0 -0
  158. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/examples/simple_workflow_template/startup_action.json +0 -0
  159. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/intent_clarification_agent.py +0 -0
  160. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/kvstore.py +0 -0
  161. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/mcp_server.py +0 -0
  162. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/model_pipeline_training.py +0 -0
  163. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/nlu_labels.py +0 -0
  164. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/refine/__main__.py +0 -0
  165. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run/__init__.py +0 -0
  166. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run/__main__.py +0 -0
  167. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/README.md +0 -0
  168. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/__init__.py +0 -0
  169. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/__main__.py +0 -0
  170. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/checkpoint.py +0 -0
  171. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/conversation_store.py +0 -0
  172. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/jwt_manager.py +0 -0
  173. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/mcp_specific.py +0 -0
  174. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/redoc_2_standalone_html.py +0 -0
  175. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/server_memory.py +0 -0
  176. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/turns.py +0 -0
  177. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/run_fastapi_mcp/utils.py +0 -0
  178. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/serialization_hooks.py +0 -0
  179. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/session_state_store.py +0 -0
  180. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/state_paths.py +0 -0
  181. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/state_serialization.py +0 -0
  182. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/storage_keys.py +0 -0
  183. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/__init__.py +0 -0
  184. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/__main__.py +0 -0
  185. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/artifact_versioning.py +0 -0
  186. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/class_balance.py +0 -0
  187. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/determinism.py +0 -0
  188. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/duplicate_detection.py +0 -0
  189. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/generate_synthetic.py +0 -0
  190. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/heldout_evaluation.py +0 -0
  191. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/param_example_cache.py +0 -0
  192. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/personas.py +0 -0
  193. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/selective_training.py +0 -0
  194. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/training_report.py +0 -0
  195. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/train/utterance_cache.py +0 -0
  196. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/turn.py +0 -0
  197. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/user_message_queues.py +0 -0
  198. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/__init__.py +0 -0
  199. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/chat_adapter.py +0 -0
  200. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/context_utils.py +0 -0
  201. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/dspy_cache_utils.py +0 -0
  202. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/dspy_logger.py +0 -0
  203. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/dspy_utils.py +0 -0
  204. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/env.py +0 -0
  205. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/generate_param_examples.py +0 -0
  206. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/insights_loader.py +0 -0
  207. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/logging.py +0 -0
  208. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/parameterize_func_decorator.py +0 -0
  209. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/pydantic_model_2_dspy_signature_class.py +0 -0
  210. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/python_utils.py +0 -0
  211. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/react.py +0 -0
  212. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/utils/startup_progress.py +0 -0
  213. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/workflow.py +0 -0
  214. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/workflow_agent.py +0 -0
  215. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/workflow_execution_context.py +0 -0
  216. {fastworkflow-3.1.0 → fastworkflow-3.1.2}/fastworkflow/workflow_inheritance_model.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastworkflow
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: A framework for rapidly building large-scale, deterministic, interactive workflows with a fault-tolerant, conversational UX
5
5
  License: Apache-2.0
6
6
  Keywords: fastworkflow,ai,workflow,llm,openai
@@ -15,7 +15,7 @@ Provides-Extra: training
15
15
  Requires-Dist: aiohttp (>=3.14.3)
16
16
  Requires-Dist: colorama (>=0.4.6,<0.5.0)
17
17
  Requires-Dist: cryptography (>=50.0.0)
18
- Requires-Dist: datasets (>=4.0.0,<5.0.0) ; extra == "training"
18
+ Requires-Dist: datasets (>=5.0.1,<6.0.0) ; extra == "training"
19
19
  Requires-Dist: dspy (>=3.0.1,<4.0.0)
20
20
  Requires-Dist: fastapi (>=0.120.1) ; extra == "server" or extra == "fastapi"
21
21
  Requires-Dist: fastapi-mcp (>=0.4.0,<0.5.0) ; extra == "server" or extra == "fastapi"
@@ -111,12 +111,29 @@ class CommandNamePrediction:
111
111
  command = command.replace(f"{tentative_command_name}", "").strip().replace(" ", " ")
112
112
  else:
113
113
  # Use Levenshtein distance for fuzzy matching with the full command part after @
114
+ # No match is ([], None), never (None, None) — len() here is safe.
114
115
  best_matched_commands, _ = find_best_matches(
115
116
  command.replace(" ", "_"),
116
117
  command_name_dict.keys(),
117
118
  threshold=0.3 # Adjust threshold as needed
118
119
  )
119
- if best_matched_commands:
120
+ if (
121
+ len(best_matched_commands) > 1
122
+ and nlu_pipeline_stage == NLUPipelineStage.INTENT_DETECTION
123
+ ):
124
+ # Commands sharing a prefix tie at distance 0, because scoring
125
+ # compares only the leading len(input) characters, and
126
+ # command_name_dict iterates a set — so picking [0] would choose
127
+ # nondeterministically between them across processes. Leave the
128
+ # name unset so the classifier and its ambiguity prompt decide.
129
+ # The clarification stages are deliberately excluded: they have
130
+ # no classifier to fall back to.
131
+ logger.warning(
132
+ f"Fuzzy pre-match tied across {best_matched_commands} for "
133
+ f"utterance '{command}' in context '{command_context_name}'. "
134
+ "Deferring to the classifier instead of picking one."
135
+ )
136
+ elif best_matched_commands:
120
137
  command_name = best_matched_commands[0]
121
138
 
122
139
  if nlu_pipeline_stage == NLUPipelineStage.INTENT_DETECTION:
@@ -168,7 +168,11 @@ class ResponseGenerator:
168
168
  ```
169
169
  - Use `default="NOT_FOUND"` for parameters so missing values are detected, not hallucinated.
170
170
  - Use `Field` `description` + `examples` (and `pattern`/`min_length` where helpful) to drive accurate parameter extraction.
171
- - Optional hooks: `db_lookup(workflow_snapshot, command)`, `process_extracted_parameters(...)`.
171
+ - Optional hooks: `db_lookup(workflow, field_name, field_value) -> tuple[bool, str | None, list[str]]`,
172
+ `process_extracted_parameters(...)`. `db_lookup` runs for fields marked
173
+ `json_schema_extra={'db_lookup': True}` and has three return states: `(True, value, [])`
174
+ overwrites the field, `(False, None, [suggestions])` fails validation, and
175
+ `(False, None, [])` does neither — use that last one for a value your hook does not own.
172
176
  - For LLM-generated responses, use `fastworkflow.utils.dspy_utils.dspySignature(Signature.Input, Signature.Output)` with `dspy.Predict`.
173
177
 
174
178
  ### Context model — `_commands/context_inheritance_model.json`
@@ -0,0 +1,170 @@
1
+ import re
2
+ import sys
3
+ from typing import Optional
4
+ import Levenshtein
5
+
6
+ # Windows are the (normalized) input length. db_lookup scores catalogue
7
+ # fields — names, emails, ids — tens to a few hundred characters, not
8
+ # megabytes. Rounding error on max_normalized * max_length is about
9
+ # sys.float_info.epsilon * max_length; at one million characters that is
10
+ # still ~2e-10. The nudge must exceed that scaled error so a product one
11
+ # ulp below an integer (e.g. (1/49)*49) is not floored, and stay << 1 so
12
+ # a fractional edit count such as 0.8 (bound 0.2 on a 4-char window) is
13
+ # not promoted to the next integer.
14
+ _MAX_SCORED_WINDOW = 1_000_000
15
+ _SCORE_CUTOFF_NUDGE = 8 * sys.float_info.epsilon * _MAX_SCORED_WINDOW
16
+
17
+ def normalize_text(text):
18
+ """
19
+ Normalize text by removing spaces, @ symbol, underscores, and converting to lowercase
20
+ """
21
+ return re.sub(r'[@\s_]', '', str(text).lower())
22
+
23
+ def normalized_levenshtein_distance(
24
+ s1, s2, max_normalized: Optional[float] = None):
25
+ """Calculate normalized Levenshtein distance.
26
+
27
+ ``max_normalized`` is an optional upper bound. Distances that cannot
28
+ beat it are not fully computed; a value strictly greater than
29
+ ``max_normalized`` is returned instead. Callers must treat that
30
+ sentinel as "worse", never as a tie at the bound.
31
+ """
32
+ max_length = max(len(s1), len(s2))
33
+ if max_length == 0:
34
+ return 0.0
35
+ if max_normalized is None:
36
+ return Levenshtein.distance(s1, s2) / max_length
37
+ cutoff = int(max_normalized * max_length + _SCORE_CUTOFF_NUDGE)
38
+ distance = Levenshtein.distance(s1, s2, score_cutoff=cutoff)
39
+ if distance > cutoff:
40
+ return max_normalized + 1.0
41
+ return distance / max_length
42
+
43
+ def best_window_distance(
44
+ normalized_input: str,
45
+ normalized_candidate: str,
46
+ max_normalized: float = 1.0,
47
+ ) -> float:
48
+ """Smallest normalized distance between the input and any equal-length window
49
+ of the candidate.
50
+
51
+ Exact containment is distance 0.0 and returns immediately. Remaining
52
+ windows use Levenshtein's ``score_cutoff`` so a window that cannot beat
53
+ ``max_normalized`` (or the best window seen so far) is abandoned early.
54
+ If no window beats ``max_normalized``, a value strictly greater than it
55
+ is returned so the caller does not invent a tie at the bound.
56
+ """
57
+ if normalized_input in normalized_candidate:
58
+ return 0.0
59
+
60
+ len_input = len(normalized_input)
61
+ n_windows = max(len(normalized_candidate) - len_input, 0) + 1
62
+ best: Optional[float] = None
63
+ bound = max_normalized
64
+ for i in range(n_windows):
65
+ dist = normalized_levenshtein_distance(
66
+ normalized_input,
67
+ normalized_candidate[i:i + len_input],
68
+ max_normalized=bound,
69
+ )
70
+ if dist > bound:
71
+ continue
72
+ if best is None or dist < best:
73
+ best = dist
74
+ bound = dist
75
+ if best == 0.0:
76
+ return 0.0
77
+ return best if best is not None else max_normalized + 1.0
78
+
79
+ def find_best_matches(input_text: str,
80
+ text_list: list[str],
81
+ threshold: float=0.4,
82
+ best_window: bool=False
83
+ ) -> tuple[list[str], Optional[float]]:
84
+ """Find best match(es) using normalized Levenshtein distance.
85
+
86
+ The function now returns *all* entries whose normalized Levenshtein
87
+ distance equals the minimum distance computed for the provided
88
+ ``input_text``. This is useful when multiple candidates tie for the
89
+ smallest distance.
90
+
91
+ Parameters
92
+ ----------
93
+ best_window
94
+ By default each candidate is scored on its *leading* ``len(input_text)``
95
+ characters, so a candidate that contains the input anywhere else is
96
+ scored as though it did not contain it at all: searching for
97
+ ``Garrison`` scores ``Barry Jones`` (0.375) ahead of ``Aaron Garrison``
98
+ (0.750). When true, every window of that length is scored and the best
99
+ one wins, so a containing candidate always reaches distance 0.0.
100
+
101
+ This is opt-in rather than the default because it can only *lower*
102
+ distances, and therefore admits strictly more matches at any fixed
103
+ threshold. Callers that route on the first match instead of treating
104
+ ties as ambiguity depend on the narrower leading-window behaviour.
105
+
106
+ Returns
107
+ -------
108
+ tuple[list[str], Optional[float]]
109
+ A tuple containing the list of best-matching original strings
110
+ and the corresponding distance. If the best distance exceeds
111
+ the ``threshold`` value, or if ``text_list`` is empty,
112
+ ``([], None)`` is returned — never ``(None, None)``. Callers
113
+ must treat an empty list as "no match" (truthiness or ``len``);
114
+ ``best_matches is None`` is always false.
115
+ """
116
+
117
+ # Ensure we have a concrete list (e.g., when a generator is passed)
118
+ text_list = list(text_list)
119
+
120
+ normalized_input = normalize_text(input_text)
121
+ normalized_list = [normalize_text(text) for text in text_list]
122
+
123
+ if not normalized_list:
124
+ return ([], None)
125
+
126
+ if best_window:
127
+ # Containment is distance 0.0. Once any candidate contains the
128
+ # input, later candidates can only tie by also containing it —
129
+ # there is no need to slide windows over the rest of the list.
130
+ contained = [
131
+ text for text, normalized in zip(text_list, normalized_list)
132
+ if normalized_input in normalized
133
+ ]
134
+ if contained:
135
+ return (contained, 0.0)
136
+
137
+ normalized_distances = []
138
+ best_so_far = 1.0
139
+ for normalized in normalized_list:
140
+ dist = best_window_distance(
141
+ normalized_input, normalized, max_normalized=best_so_far)
142
+ if dist < best_so_far:
143
+ best_so_far = dist
144
+ normalized_distances.append(dist)
145
+ else:
146
+ len_input = len(normalized_input)
147
+ # Compute distances between the input and every candidate truncated to len of input
148
+ normalized_distances = [
149
+ normalized_levenshtein_distance(normalized_input, normalized[:len_input])
150
+ for normalized in normalized_list
151
+ ]
152
+
153
+ # Determine the minimal distance observed
154
+ best_distance = min(normalized_distances)
155
+
156
+ # Gather *all* indices whose distance equals the minimal distance
157
+ # Use a small tolerance to account for floating-point precision issues
158
+ tolerance = 1e-12
159
+ best_match_indices = [
160
+ i for i, dist in enumerate(normalized_distances)
161
+ if abs(dist - best_distance) <= tolerance
162
+ ]
163
+
164
+ # Map indices back to the original (non-normalized) texts
165
+ best_matches = [text_list[i] for i in best_match_indices]
166
+
167
+ if best_distance <= threshold:
168
+ return (best_matches, best_distance)
169
+
170
+ return ([], None)
@@ -68,8 +68,38 @@ class DatabaseValidator:
68
68
  """Generic validator for database lookups with fuzzy matching"""
69
69
 
70
70
  @staticmethod
71
- def fuzzy_match(value: str, key_values: list[str], threshold: float = 0.2) -> Tuple[bool, Optional[str], List[str]]:
72
- """Find the closest matching value in the specified database."""
71
+ def fuzzy_match(value: str, key_values: list[str], threshold: float = 0.6,
72
+ suggest_threshold: float = 0.7,
73
+ auto_apply_threshold: float = 0.2) -> Tuple[bool, Optional[str], List[str]]:
74
+ """Find the closest matching value in the specified database.
75
+
76
+ Three stages run in order, and each one short-circuits: a
77
+ case-insensitive exact match, then Levenshtein, then ``difflib``.
78
+
79
+ Parameters
80
+ ----------
81
+ threshold
82
+ ``difflib`` cutoff for the final suggestion-only stage. Only values
83
+ that no candidate came within ``suggest_threshold`` of reach it, so
84
+ a loose cutoff here yields unrelated suggestions rather than useful
85
+ ones — and because any non-empty suggestion list fails validation,
86
+ those unrelated suggestions reject values that are merely absent
87
+ from this particular candidate list.
88
+ suggest_threshold
89
+ Maximum Levenshtein distance at which a candidate is considered at
90
+ all. Beyond this the value falls through to ``difflib``.
91
+ auto_apply_threshold
92
+ Maximum Levenshtein distance at which a *unique* match is reported
93
+ as matched, which makes the framework overwrite the user's value.
94
+ Kept strict deliberately: the distance at which a real typo lands
95
+ and the distance at which an unrelated value lands are separable,
96
+ and being generous here silently substitutes non-members (``Batman``
97
+ scores 0.333 against a customer list). Note the metric is
98
+ edits/length, so a one-edit typo on a value shorter than about five
99
+ characters exceeds this and is offered as a suggestion instead.
100
+ Raise it per command if an extra clarification turn costs more than
101
+ a wrong substitution.
102
+ """
73
103
  global NOT_FOUND
74
104
  if not NOT_FOUND:
75
105
  NOT_FOUND = fastworkflow.get_env_var("NOT_FOUND")
@@ -84,10 +114,12 @@ class DatabaseValidator:
84
114
  if match is not None:
85
115
  return True, match, []
86
116
 
87
- best_matches, _ = find_best_matches(value, key_values, threshold = 0.7)
88
- if len(best_matches) == 1:
89
- return True, best_matches[0], []
90
- elif len(best_matches) > 1:
117
+ best_matches, best_distance = find_best_matches(
118
+ value, key_values, threshold = suggest_threshold, best_window = True)
119
+ if best_matches:
120
+ if len(best_matches) == 1 and best_distance <= auto_apply_threshold:
121
+ return True, best_matches[0], []
122
+ # Ambiguous, or close enough to offer but too far to apply unannounced.
91
123
  return False, None, best_matches
92
124
 
93
125
  lowercase_matches = get_close_matches(
@@ -634,6 +666,11 @@ Today's date is {today}.
634
666
  # matched, corrected_value, field_suggestions = DatabaseValidator.fuzzy_match(field_value, key_values)
635
667
 
636
668
  if matched:
669
+ if corrected_value != field_value:
670
+ logger.debug(
671
+ f"db_lookup substituted {subject_command_name}.{field_name}: "
672
+ f"'{field_value}' -> '{corrected_value}'"
673
+ )
637
674
  setattr(cmd_parameters, field_name, corrected_value)
638
675
  elif field_suggestions:
639
676
  invalid_fields.append(f"{field_name} '{field_value}'")
@@ -9,7 +9,7 @@ repository = "https://github.com/radiantlogicinc/fastworkflow"
9
9
 
10
10
  [tool.poetry]
11
11
  name = "fastworkflow"
12
- version = "3.1.0"
12
+ version = "3.1.2"
13
13
  description = "A framework for rapidly building large-scale, deterministic, interactive workflows with a fault-tolerant, conversational UX"
14
14
  authors = ["Dhar Rawal <drawal@radiantlogic.com>"]
15
15
  license = "Apache-2.0"
@@ -76,7 +76,7 @@ starlette = {version = ">=1.3.1", optional = true} # CVE-2025-62727, CVE-2025-5
76
76
  # and future starlette releases will require httpx2. Floor matches starlette's `full` extra (httpx2>=2.0.0).
77
77
  httpx2 = {version = ">=2.0.0", optional = true}
78
78
  pydantic-settings = {version = ">=2.14.2", optional = true} # GHSA-4xgf-cpjx-pc3j (NestedSecretsSettingsSource follows symlinks outside secrets_dir -> local file read + secrets_dir_max_size bypass, fixed 2.14.2). Pulled transitively by mcp/fastapi-mcp in the `server` extra.
79
- datasets = {version = "^4.0.0", optional = true}
79
+ datasets = {version = "^5.0.1", optional = true} # CVE-2026-66007 / PYSEC-2026-3716 (path traversal via unsanitized file_name; fixed 5.0.1)
80
80
 
81
81
  # Security floor constraints for transitive dependencies of the CORE (client) install.
82
82
  # These ensure users get patched versions even though these are not direct deps.
@@ -1,70 +0,0 @@
1
- import re
2
- from typing import Optional
3
- import Levenshtein
4
-
5
- def normalize_text(text):
6
- """
7
- Normalize text by removing spaces, @ symbol, underscores, and converting to lowercase
8
- """
9
- return re.sub(r'[@\s_]', '', str(text).lower())
10
-
11
- def normalized_levenshtein_distance(s1, s2):
12
- """Calculate normalized Levenshtein distance"""
13
- distance = Levenshtein.distance(s1, s2)
14
- max_length = max(len(s1), len(s2))
15
- return 0.0 if max_length == 0 else distance / max_length
16
-
17
- def find_best_matches(input_text: str,
18
- text_list: list[str],
19
- threshold: float=0.4
20
- ) -> tuple[list[str], float] | tuple[None, None]:
21
- """Find best match(es) using normalized Levenshtein distance.
22
-
23
- The function now returns *all* entries whose normalized Levenshtein
24
- distance equals the minimum distance computed for the provided
25
- ``input_text``. This is useful when multiple candidates tie for the
26
- smallest distance.
27
-
28
- Returns
29
- -------
30
- tuple[list[str], float] | tuple[None, None]
31
- A tuple containing the list of best-matching original strings
32
- and the corresponding distance. If the best distance exceeds
33
- the ``threshold`` value, ``(None, None)`` is returned.
34
- """
35
-
36
- # Ensure we have a concrete list (e.g., when a generator is passed)
37
- text_list = list(text_list)
38
-
39
- normalized_input = normalize_text(input_text)
40
- normalized_list = [normalize_text(text) for text in text_list]
41
-
42
- # If empty list, return None
43
- if not normalized_list:
44
- return ([], None)
45
-
46
- len_input = len(normalized_input)
47
- # Compute distances between the input and every candidate truncated to len of input
48
- normalized_distances = [
49
- normalized_levenshtein_distance(normalized_input, normalized[:len_input])
50
- for normalized in normalized_list
51
- ]
52
-
53
- # Determine the minimal distance observed
54
- best_distance = min(normalized_distances)
55
-
56
- # Gather *all* indices whose distance equals the minimal distance
57
- # Use a small tolerance to account for floating-point precision issues
58
- tolerance = 1e-12
59
- best_match_indices = [
60
- i for i, dist in enumerate(normalized_distances)
61
- if abs(dist - best_distance) <= tolerance
62
- ]
63
-
64
- # Map indices back to the original (non-normalized) texts
65
- best_matches = [text_list[i] for i in best_match_indices]
66
-
67
- if best_distance <= threshold:
68
- return (best_matches, best_distance)
69
-
70
- return ([], None)
File without changes
File without changes