foxclaw 0.1.0 → 0.1.2

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 (495) hide show
  1. package/README.md +54 -29
  2. package/bin/foxclaw.cjs +51 -0
  3. package/index.js +11 -10
  4. package/package.json +46 -38
  5. package/python/.dockerignore +35 -0
  6. package/python/.env.example +165 -0
  7. package/python/.gitattributes +46 -0
  8. package/python/.github/workflows/ci.yml +122 -0
  9. package/python/.github/workflows/release.yml +183 -0
  10. package/python/Dockerfile +64 -0
  11. package/python/LICENSE +21 -0
  12. package/python/MANIFEST.in +14 -0
  13. package/python/README.md +192 -0
  14. package/python/README_CN.md +324 -0
  15. package/python/bench/bm25_audit.py +199 -0
  16. package/python/bench/bm25_regression.py +178 -0
  17. package/python/bench/bm25_stress.py +159 -0
  18. package/python/bench/cd_audit.py +238 -0
  19. package/python/bench/chaos.py +142 -0
  20. package/python/bench/concurrency.py +103 -0
  21. package/python/bench/e2e_real_models.py +95 -0
  22. package/python/bench/llm_retry_test.py +248 -0
  23. package/python/bench/load_test.py +196 -0
  24. package/python/bench/vector_ivf_upgrade.py +134 -0
  25. package/python/docs/CHANNELS.md +195 -0
  26. package/python/docs/GOVERNANCE.md +164 -0
  27. package/python/docs/RELEASE.md +109 -0
  28. package/python/docs/RUN.md +75 -0
  29. package/python/docs/RUN_CN.md +121 -0
  30. package/python/docs//351/205/215/347/275/256/346/214/207/345/215/227.md +152 -0
  31. package/python/evals/__init__.py +0 -0
  32. package/python/evals/governance/README.md +61 -0
  33. package/python/evals/governance/__init__.py +0 -0
  34. package/python/evals/governance/cases.py +152 -0
  35. package/python/evals/governance/compare_embeddings.py +94 -0
  36. package/python/evals/governance/convergence.svg +11 -0
  37. package/python/evals/governance/report.json +371 -0
  38. package/python/evals/governance/run_evals.py +400 -0
  39. package/python/examples/governance_middleware_demo.py +77 -0
  40. package/python/examples/langgraph_governance_demo.py +108 -0
  41. package/python/examples/sample_itinerary.csv +6 -0
  42. package/python/examples/sample_knowledge.csv +9 -0
  43. package/python/examples/sample_transactions.csv +14 -0
  44. package/python/foxclaw/__init__.py +526 -0
  45. package/python/foxclaw/agents/__init__.py +12 -0
  46. package/python/foxclaw/agents/agents.py +35 -0
  47. package/python/foxclaw/agents/base.py +865 -0
  48. package/python/foxclaw/agents/impl/__init__.py +1 -0
  49. package/python/foxclaw/agents/impl/_helpers.py +78 -0
  50. package/python/foxclaw/agents/impl/finance.py +248 -0
  51. package/python/foxclaw/agents/impl/office.py +367 -0
  52. package/python/foxclaw/agents/impl/supervisor.py +659 -0
  53. package/python/foxclaw/agents/mixins/_context.py +151 -0
  54. package/python/foxclaw/agents/mixins/augment.py +149 -0
  55. package/python/foxclaw/agents/mixins/llm.py +66 -0
  56. package/python/foxclaw/agents/mixins/memory.py +35 -0
  57. package/python/foxclaw/agents/mixins/preference.py +81 -0
  58. package/python/foxclaw/agents/mixins/prompt.py +77 -0
  59. package/python/foxclaw/agents/mixins/skillbox.py +58 -0
  60. package/python/foxclaw/agents/mixins/tool_exec.py +443 -0
  61. package/python/foxclaw/api/__init__.py +283 -0
  62. package/python/foxclaw/api/app.py +123 -0
  63. package/python/foxclaw/api/middleware.py +317 -0
  64. package/python/foxclaw/api/routes/ahs.py +417 -0
  65. package/python/foxclaw/api/routes/attachments.py +117 -0
  66. package/python/foxclaw/api/routes/browser.py +39 -0
  67. package/python/foxclaw/api/routes/domain.py +314 -0
  68. package/python/foxclaw/api/routes/knowledge.py +163 -0
  69. package/python/foxclaw/api/routes/llm_admin.py +461 -0
  70. package/python/foxclaw/api/routes/memory.py +89 -0
  71. package/python/foxclaw/api/routes/orchestration.py +41 -0
  72. package/python/foxclaw/api/routes/recovery.py +43 -0
  73. package/python/foxclaw/api/routes/review.py +275 -0
  74. package/python/foxclaw/api/routes/search.py +37 -0
  75. package/python/foxclaw/api/routes/system.py +327 -0
  76. package/python/foxclaw/api/routes/task.py +281 -0
  77. package/python/foxclaw/api/routes/ws.py +68 -0
  78. package/python/foxclaw/api/spine.py +55 -0
  79. package/python/foxclaw/browser/__init__.py +13 -0
  80. package/python/foxclaw/browser/action/__init__.py +1 -0
  81. package/python/foxclaw/browser/action/action_executor.py +54 -0
  82. package/python/foxclaw/browser/action/action_types.py +45 -0
  83. package/python/foxclaw/browser/action/action_verifier.py +68 -0
  84. package/python/foxclaw/browser/action/url_policy.py +147 -0
  85. package/python/foxclaw/browser/adapter/__init__.py +1 -0
  86. package/python/foxclaw/browser/adapter/base_adapter.py +23 -0
  87. package/python/foxclaw/browser/adapter/web_adapter.py +46 -0
  88. package/python/foxclaw/browser/driver/__init__.py +1 -0
  89. package/python/foxclaw/browser/driver/base_driver.py +61 -0
  90. package/python/foxclaw/browser/driver/playwright_driver.py +166 -0
  91. package/python/foxclaw/browser/perception/__init__.py +1 -0
  92. package/python/foxclaw/browser/perception/dom_extractor.py +43 -0
  93. package/python/foxclaw/browser/perception/element_indexer.py +57 -0
  94. package/python/foxclaw/browser/perception/semantic_reducer.py +33 -0
  95. package/python/foxclaw/browser/perception/snapshot.py +29 -0
  96. package/python/foxclaw/browser/service.py +148 -0
  97. package/python/foxclaw/browser/session/__init__.py +1 -0
  98. package/python/foxclaw/browser/session/session_manager.py +57 -0
  99. package/python/foxclaw/capability/__init__.py +12 -0
  100. package/python/foxclaw/capability/embedding_service.py +67 -0
  101. package/python/foxclaw/channels/__init__.py +140 -0
  102. package/python/foxclaw/channels/discord.py +184 -0
  103. package/python/foxclaw/channels/dispatcher.py +204 -0
  104. package/python/foxclaw/channels/feishu.py +225 -0
  105. package/python/foxclaw/channels/openclaw_bridge.py +156 -0
  106. package/python/foxclaw/channels/signing.py +187 -0
  107. package/python/foxclaw/channels/telegram.py +121 -0
  108. package/python/foxclaw/channels/weixin.py +225 -0
  109. package/python/foxclaw/cli.py +154 -0
  110. package/python/foxclaw/cognitive/__init__.py +4 -0
  111. package/python/foxclaw/cognitive/reasoner.py +82 -0
  112. package/python/foxclaw/cognitive/state.py +147 -0
  113. package/python/foxclaw/core/__init__.py +9 -0
  114. package/python/foxclaw/core/audit_chain.py +133 -0
  115. package/python/foxclaw/core/audit_sink.py +100 -0
  116. package/python/foxclaw/core/compaction.py +283 -0
  117. package/python/foxclaw/core/config.py +65 -0
  118. package/python/foxclaw/core/confirm_nonce.py +157 -0
  119. package/python/foxclaw/core/dispatch.py +27 -0
  120. package/python/foxclaw/core/envfile.py +48 -0
  121. package/python/foxclaw/core/failure_policy.py +151 -0
  122. package/python/foxclaw/core/gateway.py +85 -0
  123. package/python/foxclaw/core/governance_audit.py +175 -0
  124. package/python/foxclaw/core/idempotency.py +289 -0
  125. package/python/foxclaw/core/injection_guard.py +221 -0
  126. package/python/foxclaw/core/memory_adapter.py +516 -0
  127. package/python/foxclaw/core/metrics.py +89 -0
  128. package/python/foxclaw/core/model_router.py +1097 -0
  129. package/python/foxclaw/core/paths.py +103 -0
  130. package/python/foxclaw/core/recovery.py +362 -0
  131. package/python/foxclaw/core/scheduler.py +144 -0
  132. package/python/foxclaw/core/secrets.py +134 -0
  133. package/python/foxclaw/core/sessions.py +157 -0
  134. package/python/foxclaw/core/settings.py +430 -0
  135. package/python/foxclaw/core/shared_write.py +198 -0
  136. package/python/foxclaw/core/steering_queue.py +153 -0
  137. package/python/foxclaw/core/system_bus.py +84 -0
  138. package/python/foxclaw/core/thought_chain.py +318 -0
  139. package/python/foxclaw/core/token_meter.py +235 -0
  140. package/python/foxclaw/core/tools.py +172 -0
  141. package/python/foxclaw/core/tracing.py +390 -0
  142. package/python/foxclaw/core/truth_pipeline.py +114 -0
  143. package/python/foxclaw/core/vec_recall_adapter.py +503 -0
  144. package/python/foxclaw/core/verify.py +181 -0
  145. package/python/foxclaw/core/web_search.py +242 -0
  146. package/python/foxclaw/finance/__init__.py +15 -0
  147. package/python/foxclaw/finance/evidence.py +153 -0
  148. package/python/foxclaw/finance/fx.py +100 -0
  149. package/python/foxclaw/finance/graph.py +188 -0
  150. package/python/foxclaw/finance/models.py +150 -0
  151. package/python/foxclaw/finance/nlparse.py +575 -0
  152. package/python/foxclaw/finance/policy.py +329 -0
  153. package/python/foxclaw/finance/reasoner.py +129 -0
  154. package/python/foxclaw/finance/reasoning.py +340 -0
  155. package/python/foxclaw/finance/reconcile.py +180 -0
  156. package/python/foxclaw/finance/runtime.py +527 -0
  157. package/python/foxclaw/governance/__init__.py +24 -0
  158. package/python/foxclaw/governance/middleware.py +184 -0
  159. package/python/foxclaw/harness/__init__.py +17 -0
  160. package/python/foxclaw/harness/_base.py +72 -0
  161. package/python/foxclaw/harness/_policy.py +359 -0
  162. package/python/foxclaw/harness/drift.py +89 -0
  163. package/python/foxclaw/harness/event_bus.py +31 -0
  164. package/python/foxclaw/harness/harness.py +446 -0
  165. package/python/foxclaw/harness/loop_detection.py +152 -0
  166. package/python/foxclaw/itinerary/__init__.py +10 -0
  167. package/python/foxclaw/itinerary/amap_client.py +117 -0
  168. package/python/foxclaw/itinerary/models.py +80 -0
  169. package/python/foxclaw/itinerary/reasoner.py +101 -0
  170. package/python/foxclaw/itinerary/reasoning.py +174 -0
  171. package/python/foxclaw/itinerary/runtime.py +148 -0
  172. package/python/foxclaw/kernel/__init__.py +13 -0
  173. package/python/foxclaw/kernel/capabilities.py +73 -0
  174. package/python/foxclaw/kernel/policy_gate.py +114 -0
  175. package/python/foxclaw/kernel/process_manager.py +205 -0
  176. package/python/foxclaw/kernel/scheduler.py +131 -0
  177. package/python/foxclaw/kernel/sentinel.py +453 -0
  178. package/python/foxclaw/kernel/sentinel_client.py +246 -0
  179. package/python/foxclaw/kernel/spine.py +97 -0
  180. package/python/foxclaw/knowledge/__init__.py +376 -0
  181. package/python/foxclaw/knowledge/truth_crystallizer.py +92 -0
  182. package/python/foxclaw/learning/__init__.py +12 -0
  183. package/python/foxclaw/learning/knowledge_graph.py +149 -0
  184. package/python/foxclaw/learning/models.py +93 -0
  185. package/python/foxclaw/learning/reasoner.py +70 -0
  186. package/python/foxclaw/learning/runtime.py +363 -0
  187. package/python/foxclaw/learning/spaced_repetition.py +96 -0
  188. package/python/foxclaw/mcp_server.py +189 -0
  189. package/python/foxclaw/orchestration/__init__.py +184 -0
  190. package/python/foxclaw/pipeline/__init__.py +23 -0
  191. package/python/foxclaw/pipeline/cognitive_pipeline.py +481 -0
  192. package/python/foxclaw/pipeline/supervisor_stages.py +230 -0
  193. package/python/foxclaw/planning/__init__.py +17 -0
  194. package/python/foxclaw/planning/llm_planner.py +232 -0
  195. package/python/foxclaw/planning/planner.py +99 -0
  196. package/python/foxclaw/search/__init__.py +24 -0
  197. package/python/foxclaw/search/agent_port.py +194 -0
  198. package/python/foxclaw/search/evidence.py +113 -0
  199. package/python/foxclaw/search/external_search.py +50 -0
  200. package/python/foxclaw/search/hkc_read_port.py +60 -0
  201. package/python/foxclaw/search/internal_search.py +73 -0
  202. package/python/foxclaw/search/models.py +129 -0
  203. package/python/foxclaw/search/planner.py +44 -0
  204. package/python/foxclaw/search/retriever.py +39 -0
  205. package/python/foxclaw/search/runtime.py +134 -0
  206. package/python/foxclaw/search/strategy.py +44 -0
  207. package/python/foxclaw/skills/__init__.py +613 -0
  208. package/python/foxclaw/skills/hooks_spec.py +73 -0
  209. package/python/foxclaw/skills/lifecycle.py +278 -0
  210. package/python/foxclaw/skills/markdown_skill.py +174 -0
  211. package/python/foxclaw/skills/markdown_store.py +39 -0
  212. package/python/foxclaw/skills/mcp_client.py +190 -0
  213. package/python/foxclaw/skills/mcp_store.py +43 -0
  214. package/python/foxclaw/skills/permission.py +180 -0
  215. package/python/foxclaw/skills/pipeline.py +386 -0
  216. package/python/foxclaw/skills/policy.py +80 -0
  217. package/python/foxclaw/skills/review.py +72 -0
  218. package/python/foxclaw/skills/store.py +57 -0
  219. package/python/frontend/assets/core_bg.jpg +0 -0
  220. package/python/frontend/deck/auth.js +32 -0
  221. package/python/frontend/deck/chat_model_picker.js +537 -0
  222. package/python/frontend/deck/domain.js +296 -0
  223. package/python/frontend/deck/main.js +1950 -0
  224. package/python/frontend/deck/markdown.js +48 -0
  225. package/python/frontend/deck/settings.js +324 -0
  226. package/python/frontend/deck/state.js +6 -0
  227. package/python/frontend/deck/util.js +6 -0
  228. package/python/frontend/deck/visuals.js +212 -0
  229. package/python/frontend/deck.css +855 -0
  230. package/python/frontend/deck.html +686 -0
  231. package/python/frontend/deck_v2.html +341 -0
  232. package/python/frontend/planet/index.html +266 -0
  233. package/python/frontend/planet/planet.css +579 -0
  234. package/python/frontend/planet/planet.js +1637 -0
  235. package/python/frontend/planet/vendor/three.core.min.js +6 -0
  236. package/python/frontend/planet/vendor/three.module.min.js +6 -0
  237. package/python/frontend/v3/assets/index-BD7VPbMV.js +33 -0
  238. package/python/frontend/v3/assets/index-CAvnYWGH.css +1 -0
  239. package/python/frontend/v3/index.html +15 -0
  240. package/python/frontend/widget_runtime.js +295 -0
  241. package/python/frontend/widgets.js +696 -0
  242. package/python/frontend_src/index.html +14 -0
  243. package/python/frontend_src/package-lock.json +1376 -0
  244. package/python/frontend_src/package.json +19 -0
  245. package/python/frontend_src/src/App.vue +62 -0
  246. package/python/frontend_src/src/api.js +89 -0
  247. package/python/frontend_src/src/components/ChatDock.vue +377 -0
  248. package/python/frontend_src/src/components/ComposerMenu.vue +75 -0
  249. package/python/frontend_src/src/components/DocViewer.vue +171 -0
  250. package/python/frontend_src/src/components/Inspector.vue +241 -0
  251. package/python/frontend_src/src/components/LeftRail.vue +394 -0
  252. package/python/frontend_src/src/components/MenuPopover.vue +130 -0
  253. package/python/frontend_src/src/components/MessageStream.vue +401 -0
  254. package/python/frontend_src/src/components/ModelPicker.vue +355 -0
  255. package/python/frontend_src/src/components/QuickIngest.vue +118 -0
  256. package/python/frontend_src/src/components/Stage.vue +226 -0
  257. package/python/frontend_src/src/components/ToolMenu.vue +144 -0
  258. package/python/frontend_src/src/components/TopBar.vue +90 -0
  259. package/python/frontend_src/src/components/panels/ActivityPanel.vue +84 -0
  260. package/python/frontend_src/src/components/panels/AgentsPanel.vue +86 -0
  261. package/python/frontend_src/src/components/panels/KnowledgePanel.vue +441 -0
  262. package/python/frontend_src/src/components/panels/MemoryPanel.vue +118 -0
  263. package/python/frontend_src/src/components/panels/PanelBox.vue +51 -0
  264. package/python/frontend_src/src/components/panels/ReviewPanel.vue +308 -0
  265. package/python/frontend_src/src/components/panels/SettingsPanel.vue +254 -0
  266. package/python/frontend_src/src/components/panels/TasksPanel.vue +74 -0
  267. package/python/frontend_src/src/components/panels/ToolsPanel.vue +640 -0
  268. package/python/frontend_src/src/components/panels/UsagePanel.vue +105 -0
  269. package/python/frontend_src/src/lib/fmt.js +48 -0
  270. package/python/frontend_src/src/lib/md.js +204 -0
  271. package/python/frontend_src/src/main.js +7 -0
  272. package/python/frontend_src/src/stores/app.js +106 -0
  273. package/python/frontend_src/src/stores/chat.js +484 -0
  274. package/python/frontend_src/src/stores/model.js +104 -0
  275. package/python/frontend_src/src/stores/panels.js +330 -0
  276. package/python/frontend_src/src/styles/base.css +138 -0
  277. package/python/frontend_src/src/styles/panel.css +117 -0
  278. package/python/frontend_src/src/styles/tokens.css +78 -0
  279. package/python/frontend_src/vite.config.js +46 -0
  280. package/python/pyproject.toml +265 -0
  281. package/python/skills/README.md +123 -0
  282. package/python/skills/execution-contract/SKILL.md +30 -0
  283. package/python/skills/general-assistant/SKILL.md +34 -0
  284. package/python/skills/planner/SKILL.md +33 -0
  285. package/python/skills/schedule-itinerary/SKILL.md +35 -0
  286. package/python/skills/skill-composition/SKILL.md +31 -0
  287. package/python/skills/skill-guard/SKILL.md +67 -0
  288. package/python/skills/skill-memory/SKILL.md +50 -0
  289. package/python/skills/skill-router/SKILL.md +35 -0
  290. package/python/tests/conftest.py +76 -0
  291. package/python/tests/run_all.py +263 -0
  292. package/python/tests/test_agent_lifecycle.py +360 -0
  293. package/python/tests/test_ahs_p2.py +414 -0
  294. package/python/tests/test_anthropic_tools.py +206 -0
  295. package/python/tests/test_api.py +87 -0
  296. package/python/tests/test_api_auth.py +83 -0
  297. package/python/tests/test_api_host_guard.py +69 -0
  298. package/python/tests/test_attachments.py +167 -0
  299. package/python/tests/test_audit_chain.py +121 -0
  300. package/python/tests/test_audit_sink.py +136 -0
  301. package/python/tests/test_browser.py +153 -0
  302. package/python/tests/test_browser_request_guard.py +184 -0
  303. package/python/tests/test_capabilities.py +158 -0
  304. package/python/tests/test_channel_signing.py +192 -0
  305. package/python/tests/test_channel_trust.py +109 -0
  306. package/python/tests/test_channels.py +184 -0
  307. package/python/tests/test_cli.py +46 -0
  308. package/python/tests/test_compaction_toolpair.py +119 -0
  309. package/python/tests/test_config_layering.py +65 -0
  310. package/python/tests/test_confirm_nonce.py +197 -0
  311. package/python/tests/test_context_compaction_wiring.py +130 -0
  312. package/python/tests/test_dispatch.py +95 -0
  313. package/python/tests/test_domain_evals.py +403 -0
  314. package/python/tests/test_domain_import.py +78 -0
  315. package/python/tests/test_domain_reasoners.py +148 -0
  316. package/python/tests/test_e2e.py +178 -0
  317. package/python/tests/test_emb_cache.py +107 -0
  318. package/python/tests/test_embedding_singleton.py +89 -0
  319. package/python/tests/test_envfile.py +83 -0
  320. package/python/tests/test_evals_smoke.py +114 -0
  321. package/python/tests/test_event_spine.py +182 -0
  322. package/python/tests/test_evidence_semantic.py +101 -0
  323. package/python/tests/test_evidence_sse.py +97 -0
  324. package/python/tests/test_file_ingest.py +146 -0
  325. package/python/tests/test_finance.py +153 -0
  326. package/python/tests/test_finance_ledger.py +252 -0
  327. package/python/tests/test_finance_persist.py +178 -0
  328. package/python/tests/test_gateway.py +144 -0
  329. package/python/tests/test_governance.py +468 -0
  330. package/python/tests/test_governance_audit.py +359 -0
  331. package/python/tests/test_governance_middleware.py +143 -0
  332. package/python/tests/test_hkc.py +286 -0
  333. package/python/tests/test_hkc_index_build_manager.py +339 -0
  334. package/python/tests/test_hkc_manifest_reuse.py +317 -0
  335. package/python/tests/test_hkc_search_event_sync.py +533 -0
  336. package/python/tests/test_import_path_guard.py +161 -0
  337. package/python/tests/test_injection_guard.py +211 -0
  338. package/python/tests/test_integration.py +141 -0
  339. package/python/tests/test_itinerary.py +103 -0
  340. package/python/tests/test_kee_semantic.py +89 -0
  341. package/python/tests/test_kernel.py +222 -0
  342. package/python/tests/test_learning.py +110 -0
  343. package/python/tests/test_llm_admin.py +160 -0
  344. package/python/tests/test_llm_planner.py +366 -0
  345. package/python/tests/test_markdown_skills.py +73 -0
  346. package/python/tests/test_markdown_skills_openclaw.py +71 -0
  347. package/python/tests/test_mcp_connectors.py +109 -0
  348. package/python/tests/test_mcp_guardian_discovery.py +91 -0
  349. package/python/tests/test_mcp_no_memory.py +43 -0
  350. package/python/tests/test_mcp_policy_gate.py +77 -0
  351. package/python/tests/test_mcp_server.py +95 -0
  352. package/python/tests/test_memory_recall.py +95 -0
  353. package/python/tests/test_metrics.py +66 -0
  354. package/python/tests/test_multi_chain.py +102 -0
  355. package/python/tests/test_multimodal.py +135 -0
  356. package/python/tests/test_negotiation.py +146 -0
  357. package/python/tests/test_orchestration.py +119 -0
  358. package/python/tests/test_orchestration_decoupled.py +109 -0
  359. package/python/tests/test_p3_p5_hardening.py +338 -0
  360. package/python/tests/test_parallel_dispatch.py +215 -0
  361. package/python/tests/test_pipeline.py +533 -0
  362. package/python/tests/test_plan_visible.py +86 -0
  363. package/python/tests/test_planner.py +95 -0
  364. package/python/tests/test_policy_gate.py +109 -0
  365. package/python/tests/test_reasoner.py +185 -0
  366. package/python/tests/test_release_check.py +263 -0
  367. package/python/tests/test_resume_checkpoint.py +156 -0
  368. package/python/tests/test_routing.py +95 -0
  369. package/python/tests/test_scheduler.py +141 -0
  370. package/python/tests/test_search.py +204 -0
  371. package/python/tests/test_secrets.py +159 -0
  372. package/python/tests/test_semantic.py +78 -0
  373. package/python/tests/test_sessions.py +91 -0
  374. package/python/tests/test_settings.py +281 -0
  375. package/python/tests/test_shadow_learning.py +330 -0
  376. package/python/tests/test_shared_write.py +207 -0
  377. package/python/tests/test_skill_taint_gate.py +154 -0
  378. package/python/tests/test_skills.py +133 -0
  379. package/python/tests/test_stats_and_events.py +165 -0
  380. package/python/tests/test_streaming.py +60 -0
  381. package/python/tests/test_supervisor_pipeline.py +177 -0
  382. package/python/tests/test_supervisor_triage.py +108 -0
  383. package/python/tests/test_taint.py +117 -0
  384. package/python/tests/test_task_stream.py +85 -0
  385. package/python/tests/test_thinking_sse.py +67 -0
  386. package/python/tests/test_thinking_stream.py +76 -0
  387. package/python/tests/test_tool_center.py +129 -0
  388. package/python/tests/test_tool_parallel.py +113 -0
  389. package/python/tests/test_tool_params.py +103 -0
  390. package/python/tests/test_tool_use.py +242 -0
  391. package/python/tests/test_toplevel_concurrency.py +117 -0
  392. package/python/tests/test_tracing.py +290 -0
  393. package/python/tests/test_truth_crystallizer.py +127 -0
  394. package/python/tests/test_truth_flow.py +93 -0
  395. package/python/tests/test_url_policy.py +53 -0
  396. package/python/tests/test_verify_evidence.py +225 -0
  397. package/python/tests/test_version_sync.py +71 -0
  398. package/python/tests/test_web_trigger.py +99 -0
  399. package/python/tools/check_faiss_type.py +31 -0
  400. package/python/tools/check_log.ps1 +46 -0
  401. package/python/tools/ivf_status.py +72 -0
  402. package/python/tools/mypy_baseline.txt +5 -0
  403. package/python/tools/mypy_ratchet.py +97 -0
  404. package/python/tools/pack.py +178 -0
  405. package/python/tools/release_check.py +349 -0
  406. package/python/tools/restart_foxclaw.ps1 +74 -0
  407. package/python/tools/shared_write_audit.py +204 -0
  408. package/python/tools/size_baseline.txt +28 -0
  409. package/python/tools/size_check.py +102 -0
  410. package/python/tools/verify_env.bat +2 -0
  411. package/python/tools/verify_env.py +25 -0
  412. package/python/vendor/HMR_LICENSE +37 -0
  413. package/python/vendor/HMR_README.md +65 -0
  414. package/python/vendor/hkc/LICENSE +37 -0
  415. package/python/vendor/hkc/hkc_ace/__init__.py +0 -0
  416. package/python/vendor/hkc/hkc_ace/ace.py +457 -0
  417. package/python/vendor/hkc/hkc_ace/skill_taxonomy.json +465 -0
  418. package/python/vendor/hkc/hkc_api/__init__.py +0 -0
  419. package/python/vendor/hkc/hkc_api/container.py +416 -0
  420. package/python/vendor/hkc/hkc_api/ingest_jobs.py +103 -0
  421. package/python/vendor/hkc/hkc_api/main.py +157 -0
  422. package/python/vendor/hkc/hkc_api/routes/__init__.py +0 -0
  423. package/python/vendor/hkc/hkc_api/routes/ability.py +151 -0
  424. package/python/vendor/hkc/hkc_api/routes/config.py +51 -0
  425. package/python/vendor/hkc/hkc_api/routes/knowledge.py +630 -0
  426. package/python/vendor/hkc/hkc_api/routes/search.py +88 -0
  427. package/python/vendor/hkc/hkc_api/schemas.py +153 -0
  428. package/python/vendor/hkc/hkc_api/synthesis.py +179 -0
  429. package/python/vendor/hkc/hkc_core/__init__.py +0 -0
  430. package/python/vendor/hkc/hkc_core/graph/__init__.py +0 -0
  431. package/python/vendor/hkc/hkc_core/graph/base.py +188 -0
  432. package/python/vendor/hkc/hkc_core/graph/sqlite_store.py +789 -0
  433. package/python/vendor/hkc/hkc_core/models/__init__.py +0 -0
  434. package/python/vendor/hkc/hkc_core/models/enums.py +65 -0
  435. package/python/vendor/hkc/hkc_core/models/ku.py +307 -0
  436. package/python/vendor/hkc/hkc_core/utils/__init__.py +0 -0
  437. package/python/vendor/hkc/hkc_core/utils/id_gen.py +113 -0
  438. package/python/vendor/hkc/hkc_crystallizer/__init__.py +44 -0
  439. package/python/vendor/hkc/hkc_crystallizer/adapters.py +94 -0
  440. package/python/vendor/hkc/hkc_crystallizer/candidate.py +145 -0
  441. package/python/vendor/hkc/hkc_crystallizer/crystallizer.py +179 -0
  442. package/python/vendor/hkc/hkc_crystallizer/ingress.py +90 -0
  443. package/python/vendor/hkc/hkc_kde/__init__.py +0 -0
  444. package/python/vendor/hkc/hkc_kde/chunker.py +197 -0
  445. package/python/vendor/hkc/hkc_kde/extractor.py +623 -0
  446. package/python/vendor/hkc/hkc_kde/kde.py +298 -0
  447. package/python/vendor/hkc/hkc_kde/loaders/__init__.py +0 -0
  448. package/python/vendor/hkc/hkc_kde/loaders/ebook_loader.py +153 -0
  449. package/python/vendor/hkc/hkc_kde/loaders/office_loader.py +194 -0
  450. package/python/vendor/hkc/hkc_kde/loaders/pdf_loader.py +162 -0
  451. package/python/vendor/hkc/hkc_kde/loaders/text_loader.py +157 -0
  452. package/python/vendor/hkc/hkc_kde/loaders/web_loader.py +91 -0
  453. package/python/vendor/hkc/hkc_kde/models.py +74 -0
  454. package/python/vendor/hkc/hkc_kde/packager.py +329 -0
  455. package/python/vendor/hkc/hkc_kde/relation_disc.py +151 -0
  456. package/python/vendor/hkc/hkc_kee/__init__.py +0 -0
  457. package/python/vendor/hkc/hkc_kee/dedup.py +227 -0
  458. package/python/vendor/hkc/hkc_kee/fingerprint.py +106 -0
  459. package/python/vendor/hkc/hkc_kee/kee.py +541 -0
  460. package/python/vendor/hkc/hkc_kep/__init__.py +0 -0
  461. package/python/vendor/hkc/hkc_kep/event_bus.py +205 -0
  462. package/python/vendor/hkc/hkc_sdk/__init__.py +63 -0
  463. package/python/vendor/hkc/hkc_sdk/base.py +151 -0
  464. package/python/vendor/hkc/hkc_sdk/direct_client.py +153 -0
  465. package/python/vendor/hkc/hkc_sdk/exceptions.py +39 -0
  466. package/python/vendor/hkc/hkc_sdk/http_client.py +166 -0
  467. package/python/vendor/hkc/hkc_sdk/models.py +171 -0
  468. package/python/vendor/hkc/hkc_search/__init__.py +0 -0
  469. package/python/vendor/hkc/hkc_search/bm25.py +325 -0
  470. package/python/vendor/hkc/hkc_search/embedding_backends.py +374 -0
  471. package/python/vendor/hkc/hkc_search/graph_search.py +132 -0
  472. package/python/vendor/hkc/hkc_search/hybrid.py +331 -0
  473. package/python/vendor/hkc/hkc_search/index_build_manager.py +378 -0
  474. package/python/vendor/hkc/hkc_search/vector_search.py +484 -0
  475. package/python/vendor/hmr/__init__.py +0 -0
  476. package/python/vendor/hmr/core/__init__.py +0 -0
  477. package/python/vendor/hmr/core/hmr.py +684 -0
  478. package/python/vendor/hmr/core/models.py +310 -0
  479. package/python/vendor/hmr/engines/__init__.py +0 -0
  480. package/python/vendor/hmr/engines/evolution.py +710 -0
  481. package/python/vendor/hmr/engines/jit_compiler.py +472 -0
  482. package/python/vendor/hmr/engines/lifecycle.py +264 -0
  483. package/python/vendor/hmr/engines/policy.py +625 -0
  484. package/python/vendor/hmr/engines/recall.py +260 -0
  485. package/python/vendor/hmr/engines/runtime_state.py +158 -0
  486. package/python/vendor/hmr/engines/scheduler.py +323 -0
  487. package/python/vendor/hmr/engines/semantic.py +150 -0
  488. package/python/vendor/hmr/engines/temporal.py +261 -0
  489. package/python/vendor/hmr/engines/thought_chain.py +635 -0
  490. package/python/vendor/hmr/graph/__init__.py +0 -0
  491. package/python/vendor/hmr/graph/cwg.py +338 -0
  492. package/python/vendor/hmr/graph/memory_graph.py +530 -0
  493. package/python/vendor/hmr/storage/__init__.py +0 -0
  494. package/python/vendor/hmr/storage/memory_fs.py +300 -0
  495. package/python/vendor/hmr/storage/vector_store.py +520 -0
@@ -0,0 +1,374 @@
1
+ """
2
+ hkc-search / embedding_backends.py
3
+ 可插拔的 embedding 后端。
4
+
5
+ 设计目标:把"如何生成向量"和"如何检索向量"解耦。
6
+ VectorIndex 只依赖 EmbeddingBackend 接口,不关心底层是本地模型还是远程服务。
7
+
8
+ 三种后端:
9
+ LocalSTBackend : 进程内 sentence-transformers(需要模型权重在本地)
10
+ TEIBackend : 远程 HTTP 调用 Text Embeddings Inference 服务
11
+ StubBackend : 离线/测试用,返回确定性伪向量,不依赖任何模型
12
+
13
+ 切换方式:
14
+ backend = TEIBackend("http://localhost:8080")
15
+ index = VectorIndex(backend=backend)
16
+ """
17
+ from __future__ import annotations
18
+ import hashlib
19
+ import logging
20
+ from abc import ABC, abstractmethod
21
+
22
+ import numpy as np
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+
27
+ class EmbeddingBackend(ABC):
28
+ """
29
+ 所有 embedding 后端的统一接口。
30
+ encode() 必须返回 L2 归一化后的 float32 向量(shape: [n, dim])。
31
+ """
32
+
33
+ @property
34
+ @abstractmethod
35
+ def dim(self) -> int:
36
+ """向量维度。"""
37
+ ...
38
+
39
+ @abstractmethod
40
+ def encode(self, texts: list[str]) -> np.ndarray:
41
+ """把文本列表编码为归一化向量矩阵。"""
42
+ ...
43
+
44
+ @property
45
+ def name(self) -> str:
46
+ return self.__class__.__name__
47
+
48
+
49
+ def _l2_normalize(vecs: np.ndarray) -> np.ndarray:
50
+ """L2 归一化,使内积等价于余弦相似度。"""
51
+ norms = np.linalg.norm(vecs, axis=1, keepdims=True)
52
+ norms[norms == 0] = 1.0 # 防止除零
53
+ return (vecs / norms).astype(np.float32)
54
+
55
+
56
+ # ─────────────────────────────────────────────────────────────
57
+ # 1. 本地 sentence-transformers
58
+ # ─────────────────────────────────────────────────────────────
59
+
60
+ import threading as _threading
61
+ import time as _time
62
+ _ST_MODELS: dict = {} # name -> (model_or_None, ts)
63
+ _ST_LOCK = _threading.Lock()
64
+ _ST_FAIL_COOLDOWN = 60.0 # 加载失败后 N 秒内不重试(防瞬时失败后永久降级需重启)
65
+
66
+
67
+ def get_st_model(model_name: str):
68
+ """进程内共享缓存 SentenceTransformer(同名只加载一次)——HKC 检索 + KEE 共用,防重复占内存。
69
+ 成功永久缓存;失败仅缓存 60s(冷却后重试,从瞬时失败恢复,无需重启)。"""
70
+ with _ST_LOCK:
71
+ entry = _ST_MODELS.get(model_name)
72
+ if entry is not None:
73
+ model, ts = entry
74
+ if model is not None:
75
+ return model
76
+ if (_time.monotonic() - ts) < _ST_FAIL_COOLDOWN:
77
+ return None # 冷却期内不重试
78
+ try:
79
+ from sentence_transformers import SentenceTransformer
80
+ m = SentenceTransformer(model_name)
81
+ # 限制 torch 内部线程数:全量索引构建(bge-m3 编码 1w+ 条)默认会用满所有核,
82
+ # 把 CPU 打满导致 uvicorn 事件循环饿死(启动后 HTTP 无响应/超时)。留核给事件循环。
83
+ # HKC_EMB_TORCH_THREADS 可覆盖(设 0 表示不限制)。
84
+ try:
85
+ import torch
86
+ _nt = int(os.environ.get("HKC_EMB_TORCH_THREADS", "2"))
87
+ if _nt > 0:
88
+ torch.set_num_threads(_nt)
89
+ except Exception:
90
+ pass
91
+ except Exception as e:
92
+ logger.warning("加载 embedding 模型失败(降级,%ds 后重试): %s: %s",
93
+ int(_ST_FAIL_COOLDOWN), model_name, e)
94
+ m = None
95
+ _ST_MODELS[model_name] = (m, _time.monotonic())
96
+ return m
97
+
98
+
99
+ # ── 共享向量缓存(检索/KEE/去重 共用一份)──────────────────────────
100
+ # 动机:编码是真开销(每条一次模型前向)。同一文本在多处被反复编码——检索每次查询都重编
101
+ # query、KEE 冲突检测两两比、KEE 去重逐名比。此前各层各自造了零散缓存(kee/_EMB_CACHE、
102
+ # dedup/_sem_cache),互不共享。这里在**编码咽喉**收敛成一份:
103
+ # · 批量感知:只编未命中的那些,命中的直接取
104
+ # · 编码在锁外做(慢操作绝不持锁);最坏并发重复编一次(无害)
105
+ # · LRU 有界:淘汰最久未用,防长跑内存膨胀(与 metrics/gateway 同款教训)
106
+ # 纯函数语义(同模型+同文本→同向量),故无需失效逻辑。
107
+ import os
108
+ from collections import OrderedDict as _OrderedDict
109
+
110
+ _VEC_CACHE: "_OrderedDict" = _OrderedDict() # (model_name, text) -> 1D np.ndarray
111
+ _VEC_LOCK = _threading.Lock()
112
+ _VEC_MAX = max(256, int(os.environ.get("HKC_EMB_CACHE_MAX", "8000")))
113
+ _VEC_STATS = {"hits": 0, "misses": 0}
114
+
115
+
116
+ def encode_cached(model, model_name: str, texts: list) -> np.ndarray:
117
+ """带共享缓存的批量编码。返回 float32 (n, dim);未命中的才真编码。"""
118
+ if not texts:
119
+ return np.asarray(model.encode(texts, normalize_embeddings=True,
120
+ show_progress_bar=False), dtype=np.float32)
121
+ out = [None] * len(texts)
122
+ miss_idx, miss_txt = [], []
123
+ with _VEC_LOCK:
124
+ for i, t in enumerate(texts):
125
+ v = _VEC_CACHE.get((model_name, t))
126
+ if v is None:
127
+ miss_idx.append(i); miss_txt.append(t)
128
+ else:
129
+ _VEC_CACHE.move_to_end((model_name, t)) # LRU:标记为最近使用
130
+ out[i] = v
131
+ _VEC_STATS["hits"] += 1
132
+ if miss_txt: # 锁外编码(慢)
133
+ vecs = np.asarray(model.encode(miss_txt, normalize_embeddings=True,
134
+ show_progress_bar=False), dtype=np.float32)
135
+ with _VEC_LOCK:
136
+ for j, i in enumerate(miss_idx):
137
+ out[i] = vecs[j]
138
+ _VEC_CACHE[(model_name, texts[i])] = vecs[j]
139
+ _VEC_CACHE.move_to_end((model_name, texts[i]))
140
+ _VEC_STATS["misses"] += 1
141
+ while len(_VEC_CACHE) > _VEC_MAX:
142
+ _VEC_CACHE.popitem(last=False) # 淘汰最久未用
143
+ # np.asarray(列表) 会新建二维数组 → 调用方拿到的是副本,改它不会污染缓存
144
+ return np.asarray(out, dtype=np.float32)
145
+
146
+
147
+ def emb_cache_stats() -> dict:
148
+ with _VEC_LOCK:
149
+ h, m = _VEC_STATS["hits"], _VEC_STATS["misses"]
150
+ return {"hits": h, "misses": m, "size": len(_VEC_CACHE), "max": _VEC_MAX,
151
+ "hit_rate": round(h / (h + m), 4) if (h + m) else 0.0}
152
+
153
+
154
+ def emb_cache_clear():
155
+ with _VEC_LOCK:
156
+ _VEC_CACHE.clear()
157
+ _VEC_STATS["hits"] = _VEC_STATS["misses"] = 0
158
+
159
+
160
+ class LocalSTBackend(EmbeddingBackend):
161
+ """
162
+ 进程内 sentence-transformers(经 get_st_model 共享缓存)。
163
+ 需要模型权重已经在本地(首次会尝试从 HuggingFace 下载)。
164
+ """
165
+
166
+ def __init__(
167
+ self,
168
+ model_name: str = "paraphrase-multilingual-MiniLM-L12-v2",
169
+ dim: int = 384,
170
+ ):
171
+ self._model_name = model_name
172
+ self._dim = dim
173
+ self._model = None # 懒加载
174
+
175
+ @property
176
+ def dim(self) -> int:
177
+ return self._dim
178
+
179
+ @property
180
+ def name(self) -> str:
181
+ return f"LocalST({self._model_name})"
182
+
183
+ def _get_model(self):
184
+ if self._model is None:
185
+ m = get_st_model(self._model_name) # 共享缓存,同名只加载一次
186
+ if m is None:
187
+ raise ImportError(
188
+ "sentence-transformers 未安装或模型加载失败: " + self._model_name)
189
+ self._model = m
190
+ # 用实际模型维度校正 dim(兼容新旧方法名 get_embedding_dimension/get_sentence_...)
191
+ _fn = (getattr(self._model, "get_embedding_dimension", None)
192
+ or getattr(self._model, "get_sentence_embedding_dimension", None))
193
+ actual_dim = _fn() if _fn else None
194
+ if actual_dim and actual_dim != self._dim:
195
+ logger.info("模型维度修正: %d → %d", self._dim, actual_dim)
196
+ self._dim = actual_dim
197
+ return self._model
198
+
199
+ def encode(self, texts: list[str]) -> np.ndarray:
200
+ # 经共享缓存:重复文本(如反复检索同一 query)不再重编码
201
+ return encode_cached(self._get_model(), self._model_name, texts)
202
+
203
+
204
+ # ─────────────────────────────────────────────────────────────
205
+ # 2. TEI 远程服务
206
+ # ─────────────────────────────────────────────────────────────
207
+
208
+ class TEIBackend(EmbeddingBackend):
209
+ """
210
+ 通过 HTTP 调用 Hugging Face Text Embeddings Inference 服务。
211
+
212
+ TEI 启动方式(在有 GPU/网络的机器上):
213
+ docker run -p 8080:80 --gpus all \\
214
+ ghcr.io/huggingface/text-embeddings-inference:1.x \\
215
+ --model-id <model>
216
+
217
+ API:POST /embed body={"inputs": "text" | ["t1","t2"]}
218
+ 返回:[[float,...], ...]
219
+ """
220
+
221
+ def __init__(
222
+ self,
223
+ base_url: str = "http://localhost:8080",
224
+ dim: int = 384,
225
+ timeout: int = 30,
226
+ batch_size: int = 32,
227
+ ):
228
+ self._base_url = base_url.rstrip("/")
229
+ self._dim = dim
230
+ self._timeout = timeout
231
+ self._batch_size = batch_size
232
+ self._dim_checked = False
233
+
234
+ @property
235
+ def dim(self) -> int:
236
+ return self._dim
237
+
238
+ @property
239
+ def name(self) -> str:
240
+ return f"TEI({self._base_url})"
241
+
242
+ def health_check(self) -> bool:
243
+ """检查 TEI 服务是否在线。"""
244
+ try:
245
+ import requests
246
+ resp = requests.get(f"{self._base_url}/health", timeout=5)
247
+ return resp.status_code == 200
248
+ except Exception as e:
249
+ logger.warning("TEI health check 失败: %s", e)
250
+ return False
251
+
252
+ def encode(self, texts: list[str]) -> np.ndarray:
253
+ try:
254
+ import requests
255
+ except ImportError:
256
+ raise ImportError("requests 未安装: pip install requests")
257
+
258
+ if not texts:
259
+ return np.zeros((0, self._dim), dtype=np.float32)
260
+
261
+ all_vecs: list[list[float]] = []
262
+
263
+ # 分批请求,避免单次 payload 过大
264
+ for i in range(0, len(texts), self._batch_size):
265
+ batch = texts[i:i + self._batch_size]
266
+ resp = requests.post(
267
+ f"{self._base_url}/embed",
268
+ json={"inputs": batch},
269
+ timeout=self._timeout,
270
+ headers={"Content-Type": "application/json"},
271
+ )
272
+ resp.raise_for_status()
273
+ vecs = resp.json() # 期望 [[...], [...]]
274
+
275
+ # 校验返回格式:必须是 list,且元素是 list(向量)
276
+ if not isinstance(vecs, list):
277
+ raise ValueError(
278
+ f"TEI 返回非预期格式({type(vecs).__name__}),"
279
+ f"可能是错误响应: {str(vecs)[:200]}"
280
+ )
281
+ # 单条输入若被 TEI 扁平化为 1D,包装回 2D
282
+ if vecs and not isinstance(vecs[0], list):
283
+ vecs = [vecs]
284
+ all_vecs.extend(vecs)
285
+
286
+ if not all_vecs:
287
+ return np.zeros((0, self._dim), dtype=np.float32)
288
+
289
+ arr = np.array(all_vecs, dtype=np.float32)
290
+
291
+ # 保证二维(即使只有一条)
292
+ if arr.ndim == 1:
293
+ arr = arr.reshape(1, -1)
294
+
295
+ # 首次校正维度
296
+ if not self._dim_checked and arr.ndim == 2:
297
+ if arr.shape[1] != self._dim:
298
+ logger.info("TEI 维度修正: %d → %d", self._dim, arr.shape[1])
299
+ self._dim = arr.shape[1]
300
+ self._dim_checked = True
301
+
302
+ # TEI 通常已归一化,但保险起见再归一化一次
303
+ return _l2_normalize(arr)
304
+
305
+
306
+ # ─────────────────────────────────────────────────────────────
307
+ # 3. Stub(离线 / 测试)
308
+ # ─────────────────────────────────────────────────────────────
309
+
310
+ class StubBackend(EmbeddingBackend):
311
+ """
312
+ 确定性伪向量,不依赖任何模型或网络。
313
+ 用 hash 把文本映射到固定向量,相同文本得到相同向量。
314
+
315
+ 用途:
316
+ - 离线测试索引、检索、持久化逻辑
317
+ - CI 环境无法下载模型时的占位
318
+ 注意:伪向量没有语义,只能验证流程不能验证检索质量。
319
+ """
320
+
321
+ def __init__(self, dim: int = 384):
322
+ self._dim = dim
323
+
324
+ @property
325
+ def dim(self) -> int:
326
+ return self._dim
327
+
328
+ @property
329
+ def name(self) -> str:
330
+ return f"Stub(dim={self._dim})"
331
+
332
+ def encode(self, texts: list[str]) -> np.ndarray:
333
+ vecs = np.zeros((len(texts), self._dim), dtype=np.float32)
334
+ for i, text in enumerate(texts):
335
+ # 用文本 hash 作为随机种子,生成确定性向量
336
+ seed = int(hashlib.md5(text.encode("utf-8")).hexdigest()[:8], 16)
337
+ rng = np.random.default_rng(seed)
338
+ vecs[i] = rng.standard_normal(self._dim)
339
+ return _l2_normalize(vecs)
340
+
341
+
342
+ # ─────────────────────────────────────────────────────────────
343
+ # 工厂函数
344
+ # ─────────────────────────────────────────────────────────────
345
+
346
+ def make_backend(
347
+ kind: str = "auto",
348
+ **kwargs,
349
+ ) -> EmbeddingBackend:
350
+ """
351
+ 根据 kind 创建后端。
352
+
353
+ kind:
354
+ "local" → LocalSTBackend
355
+ "tei" → TEIBackend
356
+ "stub" → StubBackend
357
+ "auto" → 优先 local,失败回退 stub
358
+ """
359
+ if kind == "local":
360
+ return LocalSTBackend(**kwargs)
361
+ if kind == "tei":
362
+ return TEIBackend(**kwargs)
363
+ if kind == "stub":
364
+ return StubBackend(**kwargs)
365
+
366
+ # auto:尝试 local,不可用则 stub
367
+ backend = LocalSTBackend(**{k: v for k, v in kwargs.items()
368
+ if k in ("model_name", "dim")})
369
+ try:
370
+ backend._get_model()
371
+ return backend
372
+ except Exception as e:
373
+ logger.warning("本地模型不可用(%s),回退到 StubBackend", e)
374
+ return StubBackend(dim=kwargs.get("dim", 384))
@@ -0,0 +1,132 @@
1
+ """
2
+ hkc-search / graph_search.py
3
+ 图谱检索(Hot 层)。
4
+
5
+ 给定一个起始 KU ID,BFS 展开邻居,按关系权重和深度衰减排分。
6
+ 适用于:
7
+ - "查找与 X 相关的所有概念"
8
+ - "X 的证据链"
9
+ - "从 A 到 B 的知识路径"
10
+ """
11
+ from __future__ import annotations
12
+ import logging
13
+ from dataclasses import dataclass
14
+ from collections import deque
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # 深度衰减因子:每深一层分数 × DECAY
19
+ DEPTH_DECAY = 0.7
20
+
21
+
22
+ @dataclass
23
+ class GraphResult:
24
+ ku_id: str
25
+ score: float # 基于深度衰减和关系权重
26
+ depth: int
27
+ rel_type: str
28
+ name: str
29
+ summary: str
30
+
31
+
32
+ class GraphSearch:
33
+
34
+ def __init__(self, graph_store):
35
+ self.store = graph_store
36
+
37
+ def search_neighbors(
38
+ self,
39
+ start_id: str,
40
+ max_depth: int = 2,
41
+ top_k: int = 20,
42
+ rel_types: list[str] | None = None, # None = 所有关系类型
43
+ ) -> list[GraphResult]:
44
+ """
45
+ 从 start_id 出发 BFS,返回邻居 KU 及关系信息。
46
+ 按 score(深度衰减 × 关系权重)排序。
47
+ """
48
+ start_ku = self.store.get(start_id)
49
+ if not start_ku:
50
+ return []
51
+
52
+ visited: set[str] = {start_id}
53
+ results: list[GraphResult] = []
54
+ # 队列:(ku_id, depth, accumulated_score, rel_type)
55
+ queue: deque = deque([(start_id, 0, 1.0, "")])
56
+
57
+ while queue:
58
+ ku_id, depth, score, rel_type = queue.popleft()
59
+
60
+ if depth > 0:
61
+ ku = self.store.get(ku_id)
62
+ if ku:
63
+ results.append(GraphResult(
64
+ ku_id = ku_id,
65
+ score = score,
66
+ depth = depth,
67
+ rel_type = rel_type,
68
+ name = ku.name,
69
+ summary = ku.summary[:100],
70
+ ))
71
+
72
+ if depth >= max_depth:
73
+ continue
74
+
75
+ # 展开出边和入边
76
+ rels = self.store.get_relations(ku_id, direction="both")
77
+ for rel in rels:
78
+ # 过滤关系类型
79
+ if rel_types and rel.rel_type not in rel_types:
80
+ continue
81
+
82
+ neighbor_id = (
83
+ rel.to_ku if rel.from_ku == ku_id else rel.from_ku
84
+ )
85
+ if neighbor_id in visited:
86
+ continue
87
+ visited.add(neighbor_id)
88
+
89
+ next_score = score * rel.weight * DEPTH_DECAY
90
+ queue.append((neighbor_id, depth + 1, next_score, rel.rel_type))
91
+
92
+ # 按分数排序
93
+ results.sort(key=lambda r: r.score, reverse=True)
94
+ return results[:top_k]
95
+
96
+ def find_path(
97
+ self,
98
+ from_id: str,
99
+ to_id: str,
100
+ ) -> list[str]:
101
+ """
102
+ 返回两个 KU 之间的最短路径(KU ID 列表)。
103
+ 不可达返回空列表。
104
+ """
105
+ return self.store.shortest_path(from_id, to_id)
106
+
107
+ def search_by_domain(
108
+ self,
109
+ domain: str,
110
+ ku_types: list[str] | None = None,
111
+ top_k: int = 20,
112
+ ) -> list[GraphResult]:
113
+ """
114
+ 按领域过滤 KU,按置信度排序。
115
+ 不走图谱,直接从 GraphStore 查询。
116
+ """
117
+ kus = self.store.query_by_domain(domain, limit=top_k * 2)
118
+ if ku_types:
119
+ kus = [k for k in kus if k.ku_type.value in ku_types]
120
+
121
+ results = []
122
+ for ku in kus[:top_k]:
123
+ results.append(GraphResult(
124
+ ku_id = ku.ku_id,
125
+ score = ku.confidence,
126
+ depth = 0,
127
+ rel_type = "",
128
+ name = ku.name,
129
+ summary = ku.summary[:100],
130
+ ))
131
+ results.sort(key=lambda r: r.score, reverse=True)
132
+ return results