igel-qe-core 1.0.0

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 (394) hide show
  1. package/dist/cli/index.js +38 -0
  2. package/dist/mcp/server.js +104 -0
  3. package/knowledge_base/__init__.py +3 -0
  4. package/knowledge_base/__pycache__/__init__.cpython-314.pyc +0 -0
  5. package/knowledge_base/__pycache__/__init__.cpython-37.pyc +0 -0
  6. package/knowledge_base/__pycache__/__init__.cpython-38.pyc +0 -0
  7. package/knowledge_base/__pycache__/confidence.cpython-37.pyc +0 -0
  8. package/knowledge_base/__pycache__/config.cpython-314.pyc +0 -0
  9. package/knowledge_base/__pycache__/config.cpython-37.pyc +0 -0
  10. package/knowledge_base/__pycache__/config.cpython-38.pyc +0 -0
  11. package/knowledge_base/__pycache__/context_fusion.cpython-314.pyc +0 -0
  12. package/knowledge_base/__pycache__/context_fusion.cpython-37.pyc +0 -0
  13. package/knowledge_base/__pycache__/feedback_analytics.cpython-37.pyc +0 -0
  14. package/knowledge_base/__pycache__/feedback_applier.cpython-37.pyc +0 -0
  15. package/knowledge_base/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  16. package/knowledge_base/__pycache__/feedback_curator.cpython-37.pyc +0 -0
  17. package/knowledge_base/__pycache__/feedback_handler.cpython-37.pyc +0 -0
  18. package/knowledge_base/__pycache__/generate.cpython-37.pyc +0 -0
  19. package/knowledge_base/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  20. package/knowledge_base/__pycache__/input_classifier.cpython-314.pyc +0 -0
  21. package/knowledge_base/__pycache__/input_classifier.cpython-37.pyc +0 -0
  22. package/knowledge_base/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  23. package/knowledge_base/__pycache__/knowledge_graph.cpython-314.pyc +0 -0
  24. package/knowledge_base/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  25. package/knowledge_base/__pycache__/manual_tc_parser.cpython-314.pyc +0 -0
  26. package/knowledge_base/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  27. package/knowledge_base/__pycache__/precedence.cpython-37.pyc +0 -0
  28. package/knowledge_base/__pycache__/prompts.cpython-314.pyc +0 -0
  29. package/knowledge_base/__pycache__/prompts.cpython-37.pyc +0 -0
  30. package/knowledge_base/__pycache__/reranker.cpython-314.pyc +0 -0
  31. package/knowledge_base/__pycache__/reranker.cpython-37.pyc +0 -0
  32. package/knowledge_base/__pycache__/retrieval_debug.cpython-314.pyc +0 -0
  33. package/knowledge_base/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  34. package/knowledge_base/__pycache__/retriever.cpython-314.pyc +0 -0
  35. package/knowledge_base/__pycache__/retriever.cpython-37.pyc +0 -0
  36. package/knowledge_base/__pycache__/server.cpython-37.pyc +0 -0
  37. package/knowledge_base/__pycache__/tc_transformer.cpython-314.pyc +0 -0
  38. package/knowledge_base/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  39. package/knowledge_base/__pycache__/test_generator.cpython-314.pyc +0 -0
  40. package/knowledge_base/__pycache__/test_generator.cpython-37.pyc +0 -0
  41. package/knowledge_base/agents/__init__.py +6 -0
  42. package/knowledge_base/agents/__pycache__/__init__.cpython-37.pyc +0 -0
  43. package/knowledge_base/agents/__pycache__/__init__.cpython-38.pyc +0 -0
  44. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-37.pyc +0 -0
  45. package/knowledge_base/agents/__pycache__/fusion_agent.cpython-38.pyc +0 -0
  46. package/knowledge_base/agents/__pycache__/graph.cpython-37.pyc +0 -0
  47. package/knowledge_base/agents/__pycache__/graph.cpython-38.pyc +0 -0
  48. package/knowledge_base/agents/__pycache__/healing_agent.cpython-37.pyc +0 -0
  49. package/knowledge_base/agents/__pycache__/intent_agent.cpython-37.pyc +0 -0
  50. package/knowledge_base/agents/__pycache__/intent_agent.cpython-38.pyc +0 -0
  51. package/knowledge_base/agents/__pycache__/kg_agent.cpython-37.pyc +0 -0
  52. package/knowledge_base/agents/__pycache__/kg_agent.cpython-38.pyc +0 -0
  53. package/knowledge_base/agents/__pycache__/memory_agent.cpython-37.pyc +0 -0
  54. package/knowledge_base/agents/__pycache__/memory_agent.cpython-38.pyc +0 -0
  55. package/knowledge_base/agents/__pycache__/rca_agent.cpython-37.pyc +0 -0
  56. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-37.pyc +0 -0
  57. package/knowledge_base/agents/__pycache__/reflection_agent.cpython-38.pyc +0 -0
  58. package/knowledge_base/agents/__pycache__/regression_agent.cpython-37.pyc +0 -0
  59. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-37.pyc +0 -0
  60. package/knowledge_base/agents/__pycache__/retrieval_agent.cpython-38.pyc +0 -0
  61. package/knowledge_base/agents/__pycache__/script_agent.cpython-37.pyc +0 -0
  62. package/knowledge_base/agents/__pycache__/script_agent.cpython-38.pyc +0 -0
  63. package/knowledge_base/agents/__pycache__/state.cpython-37.pyc +0 -0
  64. package/knowledge_base/agents/__pycache__/state.cpython-38.pyc +0 -0
  65. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-37.pyc +0 -0
  66. package/knowledge_base/agents/__pycache__/temporal_agent.cpython-38.pyc +0 -0
  67. package/knowledge_base/agents/__pycache__/validation_agent.cpython-37.pyc +0 -0
  68. package/knowledge_base/agents/__pycache__/validation_agent.cpython-38.pyc +0 -0
  69. package/knowledge_base/agents/automation_design_agent.py +123 -0
  70. package/knowledge_base/agents/code_intelligence_agent.py +147 -0
  71. package/knowledge_base/agents/confidence_agent.py +94 -0
  72. package/knowledge_base/agents/execution_validation_agent.py +129 -0
  73. package/knowledge_base/agents/feedback_agent.py +56 -0
  74. package/knowledge_base/agents/fusion_agent.py +113 -0
  75. package/knowledge_base/agents/graph.py +152 -0
  76. package/knowledge_base/agents/healing_agent.py +62 -0
  77. package/knowledge_base/agents/intent_agent.py +76 -0
  78. package/knowledge_base/agents/kg_agent.py +91 -0
  79. package/knowledge_base/agents/memory_agent.py +62 -0
  80. package/knowledge_base/agents/rca_agent.py +53 -0
  81. package/knowledge_base/agents/reflection_agent.py +62 -0
  82. package/knowledge_base/agents/regression_agent.py +81 -0
  83. package/knowledge_base/agents/requirement_agent.py +71 -0
  84. package/knowledge_base/agents/retrieval_agent.py +66 -0
  85. package/knowledge_base/agents/scenario_agent.py +81 -0
  86. package/knowledge_base/agents/script_agent.py +121 -0
  87. package/knowledge_base/agents/script_generator_agent.py +77 -0
  88. package/knowledge_base/agents/state.py +53 -0
  89. package/knowledge_base/agents/temporal_agent.py +48 -0
  90. package/knowledge_base/agents/testcase_agent.py +111 -0
  91. package/knowledge_base/agents/validation_agent.py +49 -0
  92. package/knowledge_base/agents/workflow_engine.py +87 -0
  93. package/knowledge_base/alembic/README.md +104 -0
  94. package/knowledge_base/alembic/__pycache__/env.cpython-37.pyc +0 -0
  95. package/knowledge_base/alembic/__pycache__/env.cpython-38.pyc +0 -0
  96. package/knowledge_base/alembic/env.py +76 -0
  97. package/knowledge_base/alembic/script.py.mako +5 -0
  98. package/knowledge_base/alembic/versions/.gitkeep +26 -0
  99. package/knowledge_base/alembic/versions/001_phase0_feedback.py +122 -0
  100. package/knowledge_base/alembic/versions/002_feedback_promotions.py +60 -0
  101. package/knowledge_base/alembic/versions/003_enterprise_hardening.py +74 -0
  102. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-37.pyc +0 -0
  103. package/knowledge_base/alembic/versions/__pycache__/001_phase0_feedback.cpython-38.pyc +0 -0
  104. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-37.pyc +0 -0
  105. package/knowledge_base/alembic/versions/__pycache__/002_feedback_promotions.cpython-38.pyc +0 -0
  106. package/knowledge_base/api/__init__.py +1 -0
  107. package/knowledge_base/api/__pycache__/__init__.cpython-37.pyc +0 -0
  108. package/knowledge_base/api/__pycache__/__init__.cpython-38.pyc +0 -0
  109. package/knowledge_base/api/__pycache__/middleware.cpython-38.pyc +0 -0
  110. package/knowledge_base/api/__pycache__/server.cpython-37.pyc +0 -0
  111. package/knowledge_base/api/__pycache__/server.cpython-38.pyc +0 -0
  112. package/knowledge_base/api/middleware.py +78 -0
  113. package/knowledge_base/api/server.py +778 -0
  114. package/knowledge_base/chunking/__init__.py +0 -0
  115. package/knowledge_base/chunking/__pycache__/__init__.cpython-37.pyc +0 -0
  116. package/knowledge_base/chunking/__pycache__/__init__.cpython-38.pyc +0 -0
  117. package/knowledge_base/chunking/__pycache__/chunker.cpython-37.pyc +0 -0
  118. package/knowledge_base/chunking/__pycache__/chunker.cpython-38.pyc +0 -0
  119. package/knowledge_base/chunking/__pycache__/validators.cpython-37.pyc +0 -0
  120. package/knowledge_base/chunking/__pycache__/validators.cpython-38.pyc +0 -0
  121. package/knowledge_base/chunking/chunker.py +402 -0
  122. package/knowledge_base/chunking/validators.py +144 -0
  123. package/knowledge_base/cli/__init__.py +1 -0
  124. package/knowledge_base/cli/__pycache__/__init__.cpython-37.pyc +0 -0
  125. package/knowledge_base/cli/__pycache__/__init__.cpython-38.pyc +0 -0
  126. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-37.pyc +0 -0
  127. package/knowledge_base/cli/__pycache__/feedback_cli.cpython-38.pyc +0 -0
  128. package/knowledge_base/cli/__pycache__/generate.cpython-37.pyc +0 -0
  129. package/knowledge_base/cli/__pycache__/generate.cpython-38.pyc +0 -0
  130. package/knowledge_base/cli/__pycache__/generate_qcapps134.cpython-37.pyc +0 -0
  131. package/knowledge_base/cli/feedback_cli.py +121 -0
  132. package/knowledge_base/cli/generate.py +308 -0
  133. package/knowledge_base/cli/generate_qcapps134.py +391 -0
  134. package/knowledge_base/config/.env +100 -0
  135. package/knowledge_base/config/.env.example +75 -0
  136. package/knowledge_base/config/__init__.py +5 -0
  137. package/knowledge_base/config/__pycache__/__init__.cpython-37.pyc +0 -0
  138. package/knowledge_base/config/__pycache__/__init__.cpython-38.pyc +0 -0
  139. package/knowledge_base/config/__pycache__/settings.cpython-37.pyc +0 -0
  140. package/knowledge_base/config/__pycache__/settings.cpython-38.pyc +0 -0
  141. package/knowledge_base/config/settings.py +280 -0
  142. package/knowledge_base/connectors/__init__.py +5 -0
  143. package/knowledge_base/connectors/__pycache__/__init__.cpython-37.pyc +0 -0
  144. package/knowledge_base/connectors/__pycache__/__init__.cpython-38.pyc +0 -0
  145. package/knowledge_base/connectors/__pycache__/base.cpython-37.pyc +0 -0
  146. package/knowledge_base/connectors/__pycache__/base.cpython-38.pyc +0 -0
  147. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-37.pyc +0 -0
  148. package/knowledge_base/connectors/__pycache__/bitbucket_connector.cpython-38.pyc +0 -0
  149. package/knowledge_base/connectors/__pycache__/community_connector.cpython-38.pyc +0 -0
  150. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-37.pyc +0 -0
  151. package/knowledge_base/connectors/__pycache__/confluence_connector.cpython-38.pyc +0 -0
  152. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-37.pyc +0 -0
  153. package/knowledge_base/connectors/__pycache__/jira_connector.cpython-38.pyc +0 -0
  154. package/knowledge_base/connectors/__pycache__/kb_igel_connector.cpython-38.pyc +0 -0
  155. package/knowledge_base/connectors/__pycache__/playwright_fetcher.cpython-38.pyc +0 -0
  156. package/knowledge_base/connectors/__pycache__/query_bridge.cpython-38.pyc +0 -0
  157. package/knowledge_base/connectors/__pycache__/search_backends.cpython-38.pyc +0 -0
  158. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-37.pyc +0 -0
  159. package/knowledge_base/connectors/__pycache__/serviceNow_connector.cpython-38.pyc +0 -0
  160. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-37.pyc +0 -0
  161. package/knowledge_base/connectors/__pycache__/sync_scheduler.cpython-38.pyc +0 -0
  162. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-37.pyc +0 -0
  163. package/knowledge_base/connectors/__pycache__/teamcity_connector.cpython-38.pyc +0 -0
  164. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-37.pyc +0 -0
  165. package/knowledge_base/connectors/__pycache__/testRail_connector.cpython-38.pyc +0 -0
  166. package/knowledge_base/connectors/__pycache__/web_fetcher.cpython-38.pyc +0 -0
  167. package/knowledge_base/connectors/base.py +44 -0
  168. package/knowledge_base/connectors/bitbucket_connector.py +25 -0
  169. package/knowledge_base/connectors/community_connector.py +112 -0
  170. package/knowledge_base/connectors/confluence_connector.py +165 -0
  171. package/knowledge_base/connectors/jira_connector.py +174 -0
  172. package/knowledge_base/connectors/kb_igel_connector.py +145 -0
  173. package/knowledge_base/connectors/playwright_fetcher.py +50 -0
  174. package/knowledge_base/connectors/query_bridge.py +170 -0
  175. package/knowledge_base/connectors/search_backends.py +368 -0
  176. package/knowledge_base/connectors/serviceNow_connector.py +25 -0
  177. package/knowledge_base/connectors/sync_scheduler.py +111 -0
  178. package/knowledge_base/connectors/teamcity_connector.py +25 -0
  179. package/knowledge_base/connectors/testRail_connector.py +25 -0
  180. package/knowledge_base/connectors/web_fetcher.py +280 -0
  181. package/knowledge_base/core/__init__.py +8 -0
  182. package/knowledge_base/core/__pycache__/__init__.cpython-37.pyc +0 -0
  183. package/knowledge_base/core/__pycache__/__init__.cpython-38.pyc +0 -0
  184. package/knowledge_base/core/__pycache__/cache.cpython-38.pyc +0 -0
  185. package/knowledge_base/core/__pycache__/circuit_breaker.cpython-38.pyc +0 -0
  186. package/knowledge_base/core/__pycache__/confidence.cpython-37.pyc +0 -0
  187. package/knowledge_base/core/__pycache__/confidence.cpython-38.pyc +0 -0
  188. package/knowledge_base/core/__pycache__/logging_setup.cpython-38.pyc +0 -0
  189. package/knowledge_base/core/__pycache__/metrics.cpython-38.pyc +0 -0
  190. package/knowledge_base/core/__pycache__/precedence.cpython-37.pyc +0 -0
  191. package/knowledge_base/core/__pycache__/precedence.cpython-38.pyc +0 -0
  192. package/knowledge_base/core/__pycache__/request_context.cpython-38.pyc +0 -0
  193. package/knowledge_base/core/cache.py +98 -0
  194. package/knowledge_base/core/circuit_breaker.py +33 -0
  195. package/knowledge_base/core/confidence.py +254 -0
  196. package/knowledge_base/core/logging_setup.py +39 -0
  197. package/knowledge_base/core/metrics.py +63 -0
  198. package/knowledge_base/core/precedence.py +354 -0
  199. package/knowledge_base/core/request_context.py +54 -0
  200. package/knowledge_base/db/__init__.py +0 -0
  201. package/knowledge_base/db/__pycache__/__init__.cpython-314.pyc +0 -0
  202. package/knowledge_base/db/__pycache__/__init__.cpython-37.pyc +0 -0
  203. package/knowledge_base/db/__pycache__/__init__.cpython-38.pyc +0 -0
  204. package/knowledge_base/db/__pycache__/client.cpython-314.pyc +0 -0
  205. package/knowledge_base/db/__pycache__/client.cpython-37.pyc +0 -0
  206. package/knowledge_base/db/__pycache__/client.cpython-38.pyc +0 -0
  207. package/knowledge_base/db/__pycache__/connector_sync.cpython-38.pyc +0 -0
  208. package/knowledge_base/db/__pycache__/feedback_schema.cpython-37.pyc +0 -0
  209. package/knowledge_base/db/__pycache__/feedback_schema.cpython-38.pyc +0 -0
  210. package/knowledge_base/db/__pycache__/kg_introspect.cpython-314.pyc +0 -0
  211. package/knowledge_base/db/__pycache__/kg_introspect.cpython-37.pyc +0 -0
  212. package/knowledge_base/db/__pycache__/kg_introspect.cpython-38.pyc +0 -0
  213. package/knowledge_base/db/__pycache__/schema.cpython-37.pyc +0 -0
  214. package/knowledge_base/db/__pycache__/schema.cpython-38.pyc +0 -0
  215. package/knowledge_base/db/client.py +100 -0
  216. package/knowledge_base/db/connector_sync.py +43 -0
  217. package/knowledge_base/db/feedback_schema.py +548 -0
  218. package/knowledge_base/db/igel_schema.py +150 -0
  219. package/knowledge_base/db/kg_introspect.py +116 -0
  220. package/knowledge_base/db/migrations.py +22 -0
  221. package/knowledge_base/db/schema.py +649 -0
  222. package/knowledge_base/deploy/Dockerfile +23 -0
  223. package/knowledge_base/deploy/feedback_automation.sh +18 -0
  224. package/knowledge_base/deploy/firecrawl-docker.sh +50 -0
  225. package/knowledge_base/deploy/igel-testgen.service +26 -0
  226. package/knowledge_base/deploy/install_reranker.sh +92 -0
  227. package/knowledge_base/deploy/requirements.txt +68 -0
  228. package/knowledge_base/deploy/run_server.sh +35 -0
  229. package/knowledge_base/docs/TEST_GENERATION_GUIDE.md +174 -0
  230. package/knowledge_base/embeddings/__init__.py +0 -0
  231. package/knowledge_base/embeddings/__pycache__/__init__.cpython-314.pyc +0 -0
  232. package/knowledge_base/embeddings/__pycache__/__init__.cpython-37.pyc +0 -0
  233. package/knowledge_base/embeddings/__pycache__/__init__.cpython-38.pyc +0 -0
  234. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-314.pyc +0 -0
  235. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-37.pyc +0 -0
  236. package/knowledge_base/embeddings/__pycache__/azure_embedder.cpython-38.pyc +0 -0
  237. package/knowledge_base/embeddings/azure_embedder.py +99 -0
  238. package/knowledge_base/eval/__init__.py +33 -0
  239. package/knowledge_base/eval/__main__.py +172 -0
  240. package/knowledge_base/eval/__pycache__/__init__.cpython-37.pyc +0 -0
  241. package/knowledge_base/eval/__pycache__/__init__.cpython-38.pyc +0 -0
  242. package/knowledge_base/eval/__pycache__/__main__.cpython-37.pyc +0 -0
  243. package/knowledge_base/eval/__pycache__/baseline.cpython-37.pyc +0 -0
  244. package/knowledge_base/eval/__pycache__/baseline.cpython-38.pyc +0 -0
  245. package/knowledge_base/eval/__pycache__/harness.cpython-37.pyc +0 -0
  246. package/knowledge_base/eval/__pycache__/harness.cpython-38.pyc +0 -0
  247. package/knowledge_base/eval/__pycache__/scorers.cpython-37.pyc +0 -0
  248. package/knowledge_base/eval/__pycache__/scorers.cpython-38.pyc +0 -0
  249. package/knowledge_base/eval/baseline.py +213 -0
  250. package/knowledge_base/eval/harness.py +400 -0
  251. package/knowledge_base/eval/queries_50.json +302 -0
  252. package/knowledge_base/eval/scorers.py +494 -0
  253. package/knowledge_base/exemplars/README.md +7 -0
  254. package/knowledge_base/exemplars/__pycache__/pytest_reference_snippet.cpython-37.pyc +0 -0
  255. package/knowledge_base/exemplars/gold_manual_concise_snippet.md +8 -0
  256. package/knowledge_base/exemplars/gold_manual_detailed_snippet.md +7 -0
  257. package/knowledge_base/exemplars/pytest_reference_snippet.py +37 -0
  258. package/knowledge_base/feedback/__init__.py +10 -0
  259. package/knowledge_base/feedback/__pycache__/__init__.cpython-37.pyc +0 -0
  260. package/knowledge_base/feedback/__pycache__/__init__.cpython-38.pyc +0 -0
  261. package/knowledge_base/feedback/__pycache__/analytics.cpython-37.pyc +0 -0
  262. package/knowledge_base/feedback/__pycache__/analytics.cpython-38.pyc +0 -0
  263. package/knowledge_base/feedback/__pycache__/applier.cpython-37.pyc +0 -0
  264. package/knowledge_base/feedback/__pycache__/applier.cpython-38.pyc +0 -0
  265. package/knowledge_base/feedback/__pycache__/curator.cpython-37.pyc +0 -0
  266. package/knowledge_base/feedback/__pycache__/curator.cpython-38.pyc +0 -0
  267. package/knowledge_base/feedback/__pycache__/handler.cpython-37.pyc +0 -0
  268. package/knowledge_base/feedback/__pycache__/handler.cpython-38.pyc +0 -0
  269. package/knowledge_base/feedback/analytics.py +72 -0
  270. package/knowledge_base/feedback/applier.py +93 -0
  271. package/knowledge_base/feedback/curator.py +49 -0
  272. package/knowledge_base/feedback/handler.py +151 -0
  273. package/knowledge_base/generation/__init__.py +5 -0
  274. package/knowledge_base/generation/__pycache__/__init__.cpython-37.pyc +0 -0
  275. package/knowledge_base/generation/__pycache__/__init__.cpython-38.pyc +0 -0
  276. package/knowledge_base/generation/__pycache__/input_classifier.cpython-37.pyc +0 -0
  277. package/knowledge_base/generation/__pycache__/input_classifier.cpython-38.pyc +0 -0
  278. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-37.pyc +0 -0
  279. package/knowledge_base/generation/__pycache__/manual_tc_parser.cpython-38.pyc +0 -0
  280. package/knowledge_base/generation/__pycache__/prompts.cpython-37.pyc +0 -0
  281. package/knowledge_base/generation/__pycache__/prompts.cpython-38.pyc +0 -0
  282. package/knowledge_base/generation/__pycache__/tc_transformer.cpython-37.pyc +0 -0
  283. package/knowledge_base/generation/__pycache__/test_generator.cpython-37.pyc +0 -0
  284. package/knowledge_base/generation/__pycache__/test_generator.cpython-38.pyc +0 -0
  285. package/knowledge_base/generation/input_classifier.py +148 -0
  286. package/knowledge_base/generation/manual_tc_parser.py +180 -0
  287. package/knowledge_base/generation/prompts.py +755 -0
  288. package/knowledge_base/generation/tc_transformer.py +368 -0
  289. package/knowledge_base/generation/test_generator.py +553 -0
  290. package/knowledge_base/graph_db/__init__.py +5 -0
  291. package/knowledge_base/graph_db/__pycache__/__init__.cpython-37.pyc +0 -0
  292. package/knowledge_base/graph_db/__pycache__/__init__.cpython-38.pyc +0 -0
  293. package/knowledge_base/graph_db/__pycache__/graphiti_client.cpython-37.pyc +0 -0
  294. package/knowledge_base/graph_db/__pycache__/kg_traversal.cpython-37.pyc +0 -0
  295. package/knowledge_base/graph_db/__pycache__/migrate_kg.cpython-37.pyc +0 -0
  296. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-37.pyc +0 -0
  297. package/knowledge_base/graph_db/__pycache__/neo4j_client.cpython-38.pyc +0 -0
  298. package/knowledge_base/graph_db/__pycache__/schema.cpython-37.pyc +0 -0
  299. package/knowledge_base/graph_db/__pycache__/temporal_ingest.cpython-37.pyc +0 -0
  300. package/knowledge_base/graph_db/graphiti_client.py +127 -0
  301. package/knowledge_base/graph_db/kg_traversal.py +192 -0
  302. package/knowledge_base/graph_db/migrate_kg.py +93 -0
  303. package/knowledge_base/graph_db/neo4j_client.py +116 -0
  304. package/knowledge_base/graph_db/schema.py +22 -0
  305. package/knowledge_base/graph_db/temporal_ingest.py +59 -0
  306. package/knowledge_base/ingestion/__init__.py +1 -0
  307. package/knowledge_base/ingestion/__pycache__/__init__.cpython-37.pyc +0 -0
  308. package/knowledge_base/ingestion/__pycache__/__init__.cpython-38.pyc +0 -0
  309. package/knowledge_base/ingestion/__pycache__/ingest.cpython-37.pyc +0 -0
  310. package/knowledge_base/ingestion/__pycache__/ingest.cpython-38.pyc +0 -0
  311. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-37.pyc +0 -0
  312. package/knowledge_base/ingestion/__pycache__/ingest_images.cpython-38.pyc +0 -0
  313. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-37.pyc +0 -0
  314. package/knowledge_base/ingestion/__pycache__/kg_extractor.cpython-38.pyc +0 -0
  315. package/knowledge_base/ingestion/__pycache__/web_ingest.cpython-38.pyc +0 -0
  316. package/knowledge_base/ingestion/ingest.py +346 -0
  317. package/knowledge_base/ingestion/ingest_images.py +330 -0
  318. package/knowledge_base/ingestion/kg_extractor.py +358 -0
  319. package/knowledge_base/ingestion/web_ingest.py +83 -0
  320. package/knowledge_base/intelligence/__init__.py +1 -0
  321. package/knowledge_base/intelligence/__pycache__/__init__.cpython-37.pyc +0 -0
  322. package/knowledge_base/intelligence/__pycache__/execution_ingest.cpython-37.pyc +0 -0
  323. package/knowledge_base/intelligence/__pycache__/predictive_engine.cpython-37.pyc +0 -0
  324. package/knowledge_base/intelligence/__pycache__/risk_scorer.cpython-37.pyc +0 -0
  325. package/knowledge_base/intelligence/execution_ingest.py +34 -0
  326. package/knowledge_base/intelligence/predictive_engine.py +7 -0
  327. package/knowledge_base/intelligence/risk_scorer.py +8 -0
  328. package/knowledge_base/memory/__init__.py +6 -0
  329. package/knowledge_base/memory/__pycache__/__init__.cpython-37.pyc +0 -0
  330. package/knowledge_base/memory/__pycache__/__init__.cpython-38.pyc +0 -0
  331. package/knowledge_base/memory/__pycache__/cognee_client.cpython-37.pyc +0 -0
  332. package/knowledge_base/memory/__pycache__/cognee_client.cpython-38.pyc +0 -0
  333. package/knowledge_base/memory/__pycache__/ingest_memory.cpython-37.pyc +0 -0
  334. package/knowledge_base/memory/__pycache__/memory_types.cpython-37.pyc +0 -0
  335. package/knowledge_base/memory/__pycache__/memory_types.cpython-38.pyc +0 -0
  336. package/knowledge_base/memory/cognee_client.py +164 -0
  337. package/knowledge_base/memory/ingest_memory.py +60 -0
  338. package/knowledge_base/memory/memory_types.py +23 -0
  339. package/knowledge_base/models/test_requirement.py +84 -0
  340. package/knowledge_base/parsers/__init__.py +0 -0
  341. package/knowledge_base/parsers/__pycache__/__init__.cpython-37.pyc +0 -0
  342. package/knowledge_base/parsers/__pycache__/__init__.cpython-38.pyc +0 -0
  343. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-37.pyc +0 -0
  344. package/knowledge_base/parsers/__pycache__/csv_parser.cpython-38.pyc +0 -0
  345. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-37.pyc +0 -0
  346. package/knowledge_base/parsers/__pycache__/doc_parser.cpython-38.pyc +0 -0
  347. package/knowledge_base/parsers/__pycache__/html_parser.cpython-38.pyc +0 -0
  348. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-37.pyc +0 -0
  349. package/knowledge_base/parsers/__pycache__/image_extractor.cpython-38.pyc +0 -0
  350. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-37.pyc +0 -0
  351. package/knowledge_base/parsers/__pycache__/markdown_parser.cpython-38.pyc +0 -0
  352. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-37.pyc +0 -0
  353. package/knowledge_base/parsers/__pycache__/pdf_parser.cpython-38.pyc +0 -0
  354. package/knowledge_base/parsers/csv_parser.py +126 -0
  355. package/knowledge_base/parsers/doc_parser.py +176 -0
  356. package/knowledge_base/parsers/html_parser.py +151 -0
  357. package/knowledge_base/parsers/image_extractor.py +370 -0
  358. package/knowledge_base/parsers/markdown_parser.py +116 -0
  359. package/knowledge_base/parsers/pdf_parser.py +178 -0
  360. package/knowledge_base/retrieval/__init__.py +1 -0
  361. package/knowledge_base/retrieval/__pycache__/__init__.cpython-37.pyc +0 -0
  362. package/knowledge_base/retrieval/__pycache__/__init__.cpython-38.pyc +0 -0
  363. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-37.pyc +0 -0
  364. package/knowledge_base/retrieval/__pycache__/context_fusion.cpython-38.pyc +0 -0
  365. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-37.pyc +0 -0
  366. package/knowledge_base/retrieval/__pycache__/knowledge_graph.cpython-38.pyc +0 -0
  367. package/knowledge_base/retrieval/__pycache__/reranker.cpython-37.pyc +0 -0
  368. package/knowledge_base/retrieval/__pycache__/reranker.cpython-38.pyc +0 -0
  369. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-37.pyc +0 -0
  370. package/knowledge_base/retrieval/__pycache__/retrieval_debug.cpython-38.pyc +0 -0
  371. package/knowledge_base/retrieval/__pycache__/retriever.cpython-37.pyc +0 -0
  372. package/knowledge_base/retrieval/__pycache__/retriever.cpython-38.pyc +0 -0
  373. package/knowledge_base/retrieval/__pycache__/unified_query.cpython-38.pyc +0 -0
  374. package/knowledge_base/retrieval/__pycache__/web_retriever.cpython-38.pyc +0 -0
  375. package/knowledge_base/retrieval/context_fusion.py +264 -0
  376. package/knowledge_base/retrieval/knowledge_graph.py +267 -0
  377. package/knowledge_base/retrieval/reranker.py +308 -0
  378. package/knowledge_base/retrieval/retrieval_debug.py +69 -0
  379. package/knowledge_base/retrieval/retriever.py +885 -0
  380. package/knowledge_base/retrieval/unified_query.py +189 -0
  381. package/knowledge_base/retrieval/web_retriever.py +337 -0
  382. package/knowledge_base/validators/__init__.py +1 -0
  383. package/knowledge_base/validators/__pycache__/__init__.cpython-314.pyc +0 -0
  384. package/knowledge_base/validators/__pycache__/__init__.cpython-37.pyc +0 -0
  385. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-314.pyc +0 -0
  386. package/knowledge_base/validators/__pycache__/step_fidelity.cpython-37.pyc +0 -0
  387. package/knowledge_base/validators/step_fidelity.py +88 -0
  388. package/knowledge_base/vision/__init__.py +0 -0
  389. package/knowledge_base/vision/__pycache__/__init__.cpython-37.pyc +0 -0
  390. package/knowledge_base/vision/__pycache__/__init__.cpython-38.pyc +0 -0
  391. package/knowledge_base/vision/__pycache__/image_describer.cpython-37.pyc +0 -0
  392. package/knowledge_base/vision/__pycache__/image_describer.cpython-38.pyc +0 -0
  393. package/knowledge_base/vision/image_describer.py +202 -0
  394. package/package.json +31 -0
@@ -0,0 +1,267 @@
1
+ """GraphRAG helpers: query expansion over knowledge_graph_nodes / knowledge_graph_edges."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import re
7
+ from dataclasses import dataclass
8
+
9
+ from knowledge_base.db.client import get_conn
10
+ from knowledge_base.db.kg_introspect import get_kg_layout
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass
16
+ class KGContext:
17
+ """Structured graph context fused into prompts."""
18
+
19
+ text_block: str
20
+ node_ids: list[str]
21
+ node_count: int
22
+ edge_count: int
23
+
24
+
25
+ def classify_query_style(query: str) -> str:
26
+ if re.search(r"app\.[a-z0-9_.]+", query, re.IGNORECASE):
27
+ return "exact"
28
+ if re.search(r"\b(step|workflow|scenario|procedure)\b", query, re.IGNORECASE):
29
+ return "workflow"
30
+ return "semantic"
31
+
32
+
33
+ def _q(col: str) -> str:
34
+ return '"' + col.replace('"', '""') + '"'
35
+
36
+
37
+ def _search_terms(query: str) -> list[str]:
38
+ q = query.strip()
39
+ keys = re.findall(r"app\.[a-z0-9_.]+", q, flags=re.IGNORECASE)
40
+ tokens = [t for t in re.split(r"[^\w\-./]+", q) if len(t) >= 3]
41
+ out: list[str] = []
42
+ for x in keys + tokens[:12]:
43
+ lx = x.lower()
44
+ if lx not in {y.lower() for y in out}:
45
+ out.append(x)
46
+ if len(out) >= 16:
47
+ break
48
+ return out
49
+
50
+
51
+ def _find_matching_node_ids(
52
+ *,
53
+ lay,
54
+ terms: list[str],
55
+ seed_parent_ids: list[str] | None,
56
+ limit: int,
57
+ ) -> list[str]:
58
+ if not lay.node_id or not lay.value_col:
59
+ return []
60
+
61
+ nid = _q(lay.node_id)
62
+ vcol = _q(lay.value_col)
63
+ tname = _q(lay.nodes_table)
64
+
65
+ clauses: list[str] = []
66
+ params: list[object] = []
67
+
68
+ pat_clauses: list[str] = []
69
+ for t in terms:
70
+ esc = "%" + t.replace("%", r"\%").replace("_", r"\_") + "%"
71
+ pat_clauses.append(f"{vcol} ILIKE %s")
72
+ params.append(esc)
73
+
74
+ if lay.normalized_col:
75
+ ncol = _q(lay.normalized_col)
76
+ for t in terms:
77
+ esc = "%" + t.lower() + "%"
78
+ pat_clauses.append(f"{ncol} ILIKE %s")
79
+ params.append(esc)
80
+
81
+ if pat_clauses:
82
+ clauses.append("(" + " OR ".join(pat_clauses) + ")")
83
+
84
+ if lay.parent_chunk_col and seed_parent_ids:
85
+ pcol = _q(lay.parent_chunk_col)
86
+ clauses.append(f"{pcol}::uuid = ANY(%s::uuid[])")
87
+ params.append(seed_parent_ids[:512])
88
+
89
+ if not clauses:
90
+ return []
91
+
92
+ where_sql = "(" + (" OR ".join(clauses)) + ")" if len(clauses) > 1 else clauses[0]
93
+ sql = f"SELECT {nid} FROM {tname} WHERE {where_sql} LIMIT %s"
94
+ params.append(limit)
95
+
96
+ with get_conn() as conn:
97
+ with conn.cursor() as cur:
98
+ cur.execute(sql, tuple(params))
99
+ return [str(r[0]) for r in cur.fetchall()]
100
+
101
+
102
+ def _expand_neighbors(
103
+ layout,
104
+ frontier: list[str],
105
+ *,
106
+ exclude: set[str],
107
+ ) -> tuple[list[str], int]:
108
+ if (
109
+ not frontier
110
+ or not getattr(layout, "source_col", None)
111
+ or not getattr(layout, "target_col", None)
112
+ ):
113
+ return [], 0
114
+
115
+ src_q = _q(layout.source_col)
116
+ tgt_q = _q(layout.target_col)
117
+ tbl = _q(layout.edges_table)
118
+
119
+ sql = (
120
+ f"SELECT {src_q}::text, {tgt_q}::text FROM {tbl} WHERE "
121
+ f"{src_q}::uuid = ANY(%s::uuid[]) OR {tgt_q}::uuid = ANY(%s::uuid[])"
122
+ )
123
+ batch = frontier[:512]
124
+ with get_conn() as conn:
125
+ with conn.cursor() as cur:
126
+ cur.execute(sql, (batch, batch))
127
+ rows = cur.fetchall()
128
+
129
+ new_neighbors: list[str] = []
130
+ for a, b in rows:
131
+ for nid in (a, b):
132
+ if nid and nid not in exclude:
133
+ new_neighbors.append(str(nid))
134
+ exclude.add(str(nid))
135
+ return new_neighbors, len(rows)
136
+
137
+
138
+ def retrieve_kg_context(
139
+ query: str,
140
+ max_hops: int = 2,
141
+ max_nodes: int = 48,
142
+ seed_parent_chunk_ids: list[str] | None = None,
143
+ feature_area: str | None = None,
144
+ ) -> KGContext:
145
+ """
146
+ Expand around nodes matching query terms (+ optional anchor parent chunks).
147
+ Routes to Neo4j when KG_BACKEND=neo4j, else PostgreSQL.
148
+ """
149
+ from knowledge_base.config import cfg
150
+
151
+ if cfg.KG_BACKEND == "neo4j":
152
+ from knowledge_base.graph_db.kg_traversal import retrieve_kg_context_neo4j
153
+
154
+ return retrieve_kg_context_neo4j(
155
+ query,
156
+ max_hops=max_hops,
157
+ max_nodes=max_nodes,
158
+ feature_area=feature_area,
159
+ seed_parent_chunk_ids=seed_parent_chunk_ids,
160
+ )
161
+
162
+ lay = get_kg_layout()
163
+ if lay is None or not lay.node_id or not lay.value_col:
164
+ return KGContext(text_block="", node_ids=[], node_count=0, edge_count=0)
165
+
166
+ hops = max(0, min(int(max_hops), 3))
167
+ terms = _search_terms(query)
168
+ seeds_parent = seed_parent_chunk_ids or []
169
+
170
+ matched = _find_matching_node_ids(
171
+ lay=lay,
172
+ terms=terms,
173
+ seed_parent_ids=seeds_parent or None,
174
+ limit=max_nodes * 4,
175
+ )
176
+
177
+ expanded_ids = list(dict.fromkeys(matched))
178
+ exclude = set(expanded_ids)
179
+ total_edges = 0
180
+
181
+ can_expand = (
182
+ hops > 0
183
+ and bool(lay.edges_table)
184
+ and lay.source_col
185
+ and lay.target_col
186
+ )
187
+
188
+ frontier = expanded_ids[:]
189
+ for _ in range(hops):
190
+ if not can_expand or not frontier:
191
+ break
192
+ new_nodes, ec = _expand_neighbors(lay, frontier, exclude=exclude)
193
+ total_edges += ec
194
+ frontier = []
195
+ for nid in new_nodes:
196
+ if nid not in expanded_ids and len(expanded_ids) < max_nodes * 4:
197
+ expanded_ids.append(nid)
198
+ frontier.append(nid)
199
+ if len(expanded_ids) >= max_nodes * 4:
200
+ break
201
+
202
+ capped = expanded_ids[: max_nodes * 2]
203
+ text_block = _format_nodes_block(lay, capped)
204
+
205
+ return KGContext(
206
+ text_block=text_block,
207
+ node_ids=capped,
208
+ node_count=len(expanded_ids),
209
+ edge_count=total_edges,
210
+ )
211
+
212
+
213
+ def _format_nodes_block(layout, node_ids: list[str]) -> str:
214
+ """Format KG nodes grouped by type so the LLM can map them to executable steps."""
215
+ if not node_ids or not layout.node_id or not layout.value_col:
216
+ return ""
217
+
218
+ nid = _q(layout.node_id)
219
+ vcol = _q(layout.value_col)
220
+ tname = _q(layout.nodes_table)
221
+
222
+ select_list = [vcol]
223
+ type_col = layout.node_type_col
224
+ if type_col:
225
+ select_list.append(_q(type_col))
226
+
227
+ sql = (
228
+ "SELECT DISTINCT " + ", ".join(select_list) + " FROM " + tname + " WHERE "
229
+ f"{nid}::uuid = ANY(%s::uuid[])"
230
+ )
231
+ with get_conn() as conn:
232
+ with conn.cursor() as cur:
233
+ cur.execute(sql, (node_ids[:200],))
234
+ rows = cur.fetchall()
235
+
236
+ if not rows:
237
+ return ""
238
+
239
+ grouped: dict[str, list[str]] = {}
240
+ for row in rows:
241
+ value = (str(row[0]).strip() if row[0] else "")
242
+ ntype = (str(row[1]).strip() if len(row) > 1 and row[1] else "entity")
243
+ if value:
244
+ grouped.setdefault(ntype, []).append(value)
245
+
246
+ _TYPE_HEADINGS = {
247
+ "registry_key": "Registry keys (use with `get`/`set` on device shell)",
248
+ "ui_path": "TC Setup / UI navigation paths (exact menu paths for configuration)",
249
+ "feature": "Features & sections (cross-document relationships)",
250
+ }
251
+
252
+ lines = ["—— Knowledge graph entities (use these in test steps for accuracy) ——"]
253
+ for ntype in ("ui_path", "registry_key", "feature"):
254
+ items = grouped.pop(ntype, [])
255
+ if not items:
256
+ continue
257
+ heading = _TYPE_HEADINGS.get(ntype, ntype)
258
+ lines.append(f"\n[{heading}]")
259
+ for val in items[:30]:
260
+ lines.append(f" • {val}")
261
+
262
+ for ntype, items in sorted(grouped.items()):
263
+ lines.append(f"\n[{ntype}]")
264
+ for val in items[:20]:
265
+ lines.append(f" • {val}")
266
+
267
+ return "\n".join(lines)
@@ -0,0 +1,308 @@
1
+ """
2
+ Pluggable cross-encoder reranking for retrieval (after RRF, before budget assembly).
3
+
4
+ Backends:
5
+ local — sentence-transformers CrossEncoder (OSS, CPU-optimized by default)
6
+ cohere — Cohere Rerank API (optional hosted)
7
+ none — passthrough (scores inferred from rank order)
8
+
9
+ CPU defaults (192.168.204.65 server — no GPU):
10
+ Model: cross-encoder/ms-marco-MiniLM-L-6-v2 (~80MB, ~2-5s for 16 passages)
11
+ top_n: 16
12
+ max_length: 256 tokens per query-passage pair
13
+ batch_size: 8
14
+
15
+ Multilingual OSS alternative (slower on CPU, ~30-60s):
16
+ RERANKER_MODEL=BAAI/bge-reranker-base
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ import os
22
+ from dataclasses import dataclass, field
23
+ from time import perf_counter
24
+
25
+ from knowledge_base.config import cfg
26
+
27
+ logger = logging.getLogger(__name__)
28
+
29
+ _CROSS_ENCODER = None
30
+ _CROSS_ENCODER_KEY: str | None = None
31
+ _DEPS_PREPARED = False
32
+
33
+
34
+ def _prepare_reranker_environment() -> None:
35
+ """Fix dependency conflicts and tune PyTorch for CPU inference."""
36
+ global _DEPS_PREPARED
37
+ if _DEPS_PREPARED:
38
+ return
39
+ try:
40
+ import markupsafe
41
+
42
+ if not hasattr(markupsafe, "soft_unicode"):
43
+ markupsafe.soft_unicode = getattr(markupsafe, "soft_str", str)
44
+ except ImportError:
45
+ pass
46
+
47
+ device = _resolve_device()
48
+ if device == "cpu":
49
+ threads = max(1, int(getattr(cfg, "RERANKER_TORCH_THREADS", 4)))
50
+ os.environ.setdefault("OMP_NUM_THREADS", str(threads))
51
+ os.environ.setdefault("MKL_NUM_THREADS", str(threads))
52
+ try:
53
+ import torch
54
+
55
+ torch.set_num_threads(threads)
56
+ torch.set_grad_enabled(False)
57
+ except ImportError:
58
+ pass
59
+
60
+ _DEPS_PREPARED = True
61
+
62
+
63
+ def _resolve_device() -> str:
64
+ explicit = (getattr(cfg, "RERANKER_DEVICE", None) or "cpu").strip().lower()
65
+ if explicit not in ("cpu", "cuda", "mps"):
66
+ explicit = "cpu"
67
+ if explicit == "cuda":
68
+ try:
69
+ import torch
70
+
71
+ if not torch.cuda.is_available():
72
+ logger.info("RERANKER_DEVICE=cuda requested but no GPU — using cpu")
73
+ return "cpu"
74
+ except ImportError:
75
+ return "cpu"
76
+ return explicit
77
+
78
+
79
+ def _load_cross_encoder(model_name: str):
80
+ """Load sentence-transformers CrossEncoder with CPU-optimized settings."""
81
+ global _CROSS_ENCODER, _CROSS_ENCODER_KEY
82
+ _prepare_reranker_environment()
83
+
84
+ device = _resolve_device()
85
+ max_length = max(64, int(getattr(cfg, "RERANKER_MAX_LENGTH", 256)))
86
+ cache_key = f"{model_name}|{device}|{max_length}"
87
+
88
+ if _CROSS_ENCODER is not None and _CROSS_ENCODER_KEY == cache_key:
89
+ return _CROSS_ENCODER
90
+
91
+ try:
92
+ from sentence_transformers import CrossEncoder
93
+ except ImportError as e:
94
+ raise ImportError(
95
+ "sentence-transformers not available. Run: "
96
+ "bash knowledge_base/deploy/install_reranker.sh"
97
+ ) from e
98
+
99
+ logger.info(
100
+ "Loading OSS CrossEncoder: model=%s device=%s max_length=%d",
101
+ model_name,
102
+ device,
103
+ max_length,
104
+ )
105
+ t0 = perf_counter()
106
+ _CROSS_ENCODER = CrossEncoder(model_name, max_length=max_length, device=device)
107
+ _CROSS_ENCODER_KEY = cache_key
108
+ logger.info("Reranker ready in %.1fs", perf_counter() - t0)
109
+ return _CROSS_ENCODER
110
+
111
+
112
+ @dataclass
113
+ class RerankItem:
114
+ child_chunk_id: str
115
+ passage: str
116
+ parent_passage: str = ""
117
+ section_title: str = ""
118
+ source_url: str = ""
119
+ metadata: dict = field(default_factory=dict)
120
+
121
+
122
+ def build_rerank_passage(item: RerankItem) -> str:
123
+ """Build a compact passage for cross-encoder input (CPU-friendly length)."""
124
+ passage_limit = int(getattr(cfg, "RERANKER_PASSAGE_CHARS", 2000))
125
+ parent_limit = int(getattr(cfg, "RERANKER_PARENT_CHARS", 1200))
126
+
127
+ parts: list[str] = []
128
+ if item.section_title:
129
+ parts.append(f"Section: {item.section_title[:200]}")
130
+ if item.parent_passage and item.parent_passage.strip() != item.passage.strip():
131
+ parts.append(f"Context:\n{item.parent_passage[:parent_limit]}")
132
+ parts.append(f"Passage:\n{item.passage[:passage_limit]}")
133
+ combined = "\n\n".join(parts)
134
+ return combined[: passage_limit + parent_limit + 250]
135
+
136
+
137
+ def rerank_passages(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
138
+ """
139
+ Return list of (child_chunk_id, score) sorted by descending relevance.
140
+ Scores normalized roughly to [0, 1] within the batch where applicable.
141
+ """
142
+ if not items:
143
+ return []
144
+ backend = (cfg.RERANKER_BACKEND or "none").lower().strip()
145
+
146
+ if backend == "none":
147
+ return _score_by_rank_fallback(items)
148
+
149
+ if backend == "cohere":
150
+ return _rerank_cohere(query, items)
151
+
152
+ if backend == "local":
153
+ return _rerank_local(query, items)
154
+
155
+ logger.warning("Unknown RERANKER_BACKEND=%r; using rank fallback", backend)
156
+ return _score_by_rank_fallback(items)
157
+
158
+
159
+ def apply_rerank_threshold(
160
+ ranked: list[tuple[str, float]],
161
+ threshold: float | None = None,
162
+ ) -> list[tuple[str, float]]:
163
+ """Filter reranked results below RERANKER_SCORE_THRESHOLD."""
164
+ cutoff = threshold if threshold is not None else getattr(cfg, "RERANKER_SCORE_THRESHOLD", None)
165
+ if cutoff is None or cutoff <= 0:
166
+ return ranked
167
+ filtered = [(cid, sc) for cid, sc in ranked if sc >= cutoff]
168
+ if len(filtered) < len(ranked):
169
+ logger.debug(
170
+ "rerank threshold %.2f removed %d/%d items",
171
+ cutoff,
172
+ len(ranked) - len(filtered),
173
+ len(ranked),
174
+ )
175
+ return filtered or ranked[: max(1, len(ranked) // 3)]
176
+
177
+
178
+ def _score_by_rank_fallback(items: list[RerankItem]) -> list[tuple[str, float]]:
179
+ n = len(items)
180
+ if n == 0:
181
+ return []
182
+ return [(it.child_chunk_id, 1.0 - (i / max(n, 1))) for i, it in enumerate(items)]
183
+
184
+
185
+ def _normalize_scores(pairs: list[tuple[str, float]]) -> list[tuple[str, float]]:
186
+ if not pairs:
187
+ return []
188
+ vals = [s for _, s in pairs]
189
+ lo, hi = min(vals), max(vals)
190
+ if hi <= lo:
191
+ return [(cid, 0.5) for cid, _ in pairs]
192
+ return [(cid, (s - lo) / (hi - lo)) for cid, s in pairs]
193
+
194
+
195
+ def _rerank_local(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
196
+ model_name = cfg.RERANKER_MODEL or "cross-encoder/ms-marco-MiniLM-L-6-v2"
197
+ try:
198
+ encoder = _load_cross_encoder(model_name)
199
+ except ImportError as e:
200
+ logger.error("%s", e)
201
+ return _score_by_rank_fallback(items)
202
+ except Exception as e:
203
+ logger.error("Failed to load reranker model %s: %s", model_name, e)
204
+ return _score_by_rank_fallback(items)
205
+
206
+ # Cap items — retriever should already limit, but enforce here for safety
207
+ top_n = max(1, int(getattr(cfg, "RERANKER_TOP_N", 16)))
208
+ items = items[:top_n]
209
+
210
+ pairs = [(query, build_rerank_passage(it)) for it in items]
211
+ batch_size = max(1, int(getattr(cfg, "RERANKER_BATCH_SIZE", 8)))
212
+
213
+ t0 = perf_counter()
214
+ raw = encoder.predict(pairs, show_progress_bar=False, batch_size=batch_size)
215
+ elapsed = perf_counter() - t0
216
+ logger.info(
217
+ "Reranked %d passages in %.2fs (model=%s, device=%s)",
218
+ len(items),
219
+ elapsed,
220
+ model_name,
221
+ _resolve_device(),
222
+ )
223
+
224
+ scored = [(items[i].child_chunk_id, float(raw[i])) for i in range(len(items))]
225
+ scored.sort(key=lambda x: x[1], reverse=True)
226
+ normalized = _normalize_scores(scored)
227
+ return apply_rerank_threshold(normalized)
228
+
229
+
230
+ def _rerank_cohere(query: str, items: list[RerankItem]) -> list[tuple[str, float]]:
231
+ try:
232
+ import cohere
233
+ except ImportError as e:
234
+ logger.error("cohere package not installed: %s", e)
235
+ return _score_by_rank_fallback(items)
236
+
237
+ key = getattr(cfg, "COHERE_API_KEY", "") or ""
238
+ if not key:
239
+ logger.error("COHERE_API_KEY not set — cannot use cohere reranker")
240
+ return _score_by_rank_fallback(items)
241
+
242
+ client = cohere.Client(key)
243
+ docs = [build_rerank_passage(it) for it in items]
244
+ model = getattr(cfg, "COHERE_RERANK_MODEL", None) or "rerank-multilingual-v3.0"
245
+ top_n = min(len(docs), max(cfg.RERANKER_TOP_N, len(docs)))
246
+ rsp = client.rerank(model=model, query=query, documents=docs, top_n=top_n)
247
+
248
+ out: list[tuple[str, float]] = []
249
+ for r in rsp.results:
250
+ out.append((items[r.index].child_chunk_id, float(r.relevance_score)))
251
+ out.sort(key=lambda x: x[1], reverse=True)
252
+ normalized = _normalize_scores(out)
253
+ return apply_rerank_threshold(normalized)
254
+
255
+
256
+ def rerank_web_pages(
257
+ query: str,
258
+ pages: list[tuple[str, str, str]],
259
+ ) -> list[tuple[str, float]]:
260
+ """
261
+ Rerank web pages by pure query-passage relevancy (url, title, content).
262
+ Uses the same cross-encoder as local RAG but without score threshold filtering.
263
+ """
264
+ if not pages:
265
+ return []
266
+
267
+ items = [
268
+ RerankItem(
269
+ child_chunk_id=url,
270
+ passage=content,
271
+ section_title=title,
272
+ source_url=url,
273
+ )
274
+ for url, title, content in pages
275
+ ]
276
+ backend = (cfg.RERANKER_BACKEND or "none").lower().strip()
277
+ if backend == "local":
278
+ model_name = cfg.RERANKER_MODEL or "cross-encoder/ms-marco-MiniLM-L-6-v2"
279
+ try:
280
+ encoder = _load_cross_encoder(model_name)
281
+ except Exception as e:
282
+ logger.warning("web reranker unavailable: %s", e)
283
+ return [(url, 0.5 - i * 0.01) for i, (url, _, _) in enumerate(pages)]
284
+
285
+ pairs = [(query, build_rerank_passage(it)) for it in items]
286
+ batch_size = max(1, int(getattr(cfg, "RERANKER_BATCH_SIZE", 8)))
287
+ raw = encoder.predict(pairs, show_progress_bar=False, batch_size=batch_size)
288
+ scored = [(pages[i][0], float(raw[i])) for i in range(len(pages))]
289
+ scored.sort(key=lambda x: x[1], reverse=True)
290
+ return _normalize_scores(scored)
291
+
292
+ if backend == "cohere":
293
+ ranked = _rerank_cohere(query, items)
294
+ return ranked
295
+
296
+ return [(url, 0.5 - i * 0.01) for i, (url, _, _) in enumerate(pages)]
297
+
298
+
299
+ def warm_start_reranker() -> None:
300
+ """Pre-load cross-encoder model at startup."""
301
+ if cfg.RERANKER_BACKEND == "local":
302
+ _load_cross_encoder()
303
+
304
+
305
+ def is_reranker_loaded() -> bool:
306
+ if cfg.RERANKER_BACKEND != "local":
307
+ return True
308
+ return _CROSS_ENCODER is not None
@@ -0,0 +1,69 @@
1
+ """Retrieval observability: scores, ranks, inclusion reasons for tuning GraphRAG."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import logging
7
+ from dataclasses import dataclass, field, asdict
8
+ from typing import Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ @dataclass
14
+ class ChunkScoreTrace:
15
+ child_chunk_id: str
16
+ parent_chunk_id: str
17
+ source_file: str
18
+ section_title: str
19
+ vector_rank: int | None
20
+ fts_rank: int | None
21
+ rrf_score: float
22
+ rerank_score: float | None
23
+ included: bool
24
+ exclusion_reason: str # "" when included
25
+
26
+
27
+ @dataclass
28
+ class RetrievalDebugReport:
29
+ query: str
30
+ query_classification: str
31
+ total_vector_candidates: int
32
+ total_fts_candidates: int
33
+ rrf_merged_count: int
34
+ reranker_applied: bool
35
+ post_rerank_count: int
36
+ kg_nodes_found: int
37
+ kg_edges_traversed: int
38
+ final_chunk_count: int
39
+ total_tokens_used: int
40
+ token_budget: int
41
+ traces: list[ChunkScoreTrace] = field(default_factory=list)
42
+ timing_ms: dict[str, float] = field(default_factory=dict)
43
+
44
+ def to_json(self) -> str:
45
+ return json.dumps(asdict(self), indent=2)
46
+
47
+ def log_summary(self) -> None:
48
+ logger.info(
49
+ "retrieval_debug: class=%s vec=%d fts=%d rrf_merged=%d rerank=%s "
50
+ "final=%d kg_nodes=%d kg_edges=%d tokens~%d/%d",
51
+ self.query_classification,
52
+ self.total_vector_candidates,
53
+ self.total_fts_candidates,
54
+ self.rrf_merged_count,
55
+ self.reranker_applied,
56
+ self.final_chunk_count,
57
+ self.kg_nodes_found,
58
+ self.kg_edges_traversed,
59
+ self.total_tokens_used,
60
+ self.token_budget,
61
+ )
62
+
63
+
64
+ def merge_traces_into_report(
65
+ base: RetrievalDebugReport,
66
+ traces: list[ChunkScoreTrace],
67
+ ) -> RetrievalDebugReport:
68
+ base.traces = traces
69
+ return base