qwen-sci 0.2.2__py3-none-any.whl

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 (549) hide show
  1. qwen_sci-0.2.2.dist-info/METADATA +492 -0
  2. qwen_sci-0.2.2.dist-info/RECORD +549 -0
  3. qwen_sci-0.2.2.dist-info/WHEEL +4 -0
  4. qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
  5. qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
  6. src/1.txt +1 -0
  7. src/__init__.py +1 -0
  8. src/__main__.py +5 -0
  9. src/agents/__init__.py +1 -0
  10. src/agents/experiment_design_agent/__init__.py +268 -0
  11. src/agents/experiment_design_agent/artifacts.py +747 -0
  12. src/agents/experiment_design_agent/cache.py +319 -0
  13. src/agents/experiment_design_agent/completeness.py +130 -0
  14. src/agents/experiment_design_agent/contracts.py +771 -0
  15. src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
  16. src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
  17. src/agents/experiment_design_agent/discipline_catalog.py +178 -0
  18. src/agents/experiment_design_agent/evidence_cards.py +338 -0
  19. src/agents/experiment_design_agent/evidence_planner.py +452 -0
  20. src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
  21. src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
  22. src/agents/experiment_design_agent/idea_adapter.py +100 -0
  23. src/agents/experiment_design_agent/idea_intake.py +87 -0
  24. src/agents/experiment_design_agent/llm_json.py +328 -0
  25. src/agents/experiment_design_agent/orchestrator.py +1220 -0
  26. src/agents/experiment_design_agent/reasoning_context.py +183 -0
  27. src/agents/experiment_design_agent/reasoning_validation.py +546 -0
  28. src/agents/experiment_design_agent/run.py +481 -0
  29. src/agents/experiment_design_agent/run_logging.py +292 -0
  30. src/agents/experiment_design_agent/scope_gate.py +198 -0
  31. src/agents/experiment_design_agent/study_type_composer.py +1360 -0
  32. src/agents/experiment_design_agent/survey_evidence.py +1381 -0
  33. src/agents/experiment_design_agent/template_router.py +195 -0
  34. src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
  35. src/agents/idea_agent/__init__.py +1 -0
  36. src/agents/idea_agent/agent/__init__.py +6 -0
  37. src/agents/idea_agent/agent/artifacts.py +308 -0
  38. src/agents/idea_agent/agent/base.py +122 -0
  39. src/agents/idea_agent/agent/ligagent.py +488 -0
  40. src/agents/idea_agent/agent/mcts.py +2060 -0
  41. src/agents/idea_agent/agent/prompts/__init__.py +48 -0
  42. src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
  43. src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
  44. src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
  45. src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
  46. src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
  47. src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
  48. src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
  49. src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
  50. src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
  51. src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
  52. src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
  53. src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
  54. src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
  55. src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
  56. src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
  57. src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
  58. src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
  59. src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
  60. src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
  61. src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
  62. src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
  63. src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
  64. src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
  65. src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
  66. src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
  67. src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
  68. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
  69. src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
  70. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
  71. src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
  72. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
  73. src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
  74. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
  75. src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
  76. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
  77. src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
  78. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
  79. src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
  80. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
  81. src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
  82. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
  83. src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
  84. src/agents/idea_agent/example/idea_result.json +900 -0
  85. src/agents/idea_agent/run.py +276 -0
  86. src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
  87. src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
  88. src/agents/idea_agent/tests/test_idea_debate.py +480 -0
  89. src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
  90. src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
  91. src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
  92. src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
  93. src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
  94. src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
  95. src/agents/idea_agent/tests/test_result_persistence.py +183 -0
  96. src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
  97. src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
  98. src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
  99. src/agents/idea_agent/utils/core/__init__.py +1 -0
  100. src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
  101. src/agents/idea_agent/utils/core/chat_errors.py +95 -0
  102. src/agents/idea_agent/utils/core/chat_router.py +130 -0
  103. src/agents/idea_agent/utils/core/chat_transport.py +188 -0
  104. src/agents/idea_agent/utils/core/config_loader.py +166 -0
  105. src/agents/idea_agent/utils/core/json_utils.py +28 -0
  106. src/agents/idea_agent/utils/core/logger.py +287 -0
  107. src/agents/idea_agent/utils/core/progress.py +58 -0
  108. src/agents/idea_agent/utils/core/response_parsing.py +57 -0
  109. src/agents/idea_agent/utils/core/run_inputs.py +198 -0
  110. src/agents/idea_agent/utils/mcts/__init__.py +1 -0
  111. src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
  112. src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
  113. src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
  114. src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
  115. src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
  116. src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
  117. src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
  118. src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
  119. src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
  120. src/agents/idea_agent/utils/papers/__init__.py +1 -0
  121. src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
  122. src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
  123. src/agents/idea_agent/utils/prompting/__init__.py +1 -0
  124. src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
  125. src/agents/idea_agent/utils/workflow/__init__.py +1 -0
  126. src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
  127. src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
  128. src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
  129. src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
  130. src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
  131. src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
  132. src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
  133. src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
  134. src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
  135. src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
  136. src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
  137. src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
  138. src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
  139. src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
  140. src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
  141. src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
  142. src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
  143. src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
  144. src/agents/research_plan_author/__init__.py +50 -0
  145. src/agents/research_plan_author/artifacts.py +270 -0
  146. src/agents/research_plan_author/authoring_blueprint.py +1246 -0
  147. src/agents/research_plan_author/bibtex_renderer.py +252 -0
  148. src/agents/research_plan_author/contract_repair.py +131 -0
  149. src/agents/research_plan_author/contracts.py +745 -0
  150. src/agents/research_plan_author/cross_section_editor.py +232 -0
  151. src/agents/research_plan_author/document_quality.py +987 -0
  152. src/agents/research_plan_author/idea_evolution.py +223 -0
  153. src/agents/research_plan_author/input_loader.py +76 -0
  154. src/agents/research_plan_author/latex_compiler.py +260 -0
  155. src/agents/research_plan_author/latex_safety.py +159 -0
  156. src/agents/research_plan_author/llm_json.py +74 -0
  157. src/agents/research_plan_author/markdown_renderer.py +275 -0
  158. src/agents/research_plan_author/pdf_validator.py +149 -0
  159. src/agents/research_plan_author/render.py +166 -0
  160. src/agents/research_plan_author/render_artifacts.py +187 -0
  161. src/agents/research_plan_author/run.py +765 -0
  162. src/agents/research_plan_author/run_logging.py +209 -0
  163. src/agents/research_plan_author/section_cache.py +184 -0
  164. src/agents/research_plan_author/section_composer.py +1764 -0
  165. src/agents/research_plan_author/section_router.py +209 -0
  166. src/agents/research_plan_author/semantic_validator.py +254 -0
  167. src/agents/research_plan_author/source_bundle.py +46 -0
  168. src/agents/research_plan_author/source_registry.py +208 -0
  169. src/agents/research_plan_author/survey_source_loader.py +137 -0
  170. src/agents/research_plan_author/template_adapter.py +173 -0
  171. src/agents/research_plan_author/template_profile.py +204 -0
  172. src/agents/research_plan_author/tex_renderer.py +496 -0
  173. src/agents/research_plan_author/theory_presentation.py +134 -0
  174. src/agents/research_plan_author/theory_spine.py +703 -0
  175. src/agents/survey_agent/Dockerfile +54 -0
  176. src/agents/survey_agent/README.md +350 -0
  177. src/agents/survey_agent/config/deep_survey.yaml +217 -0
  178. src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
  179. src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
  180. src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
  181. src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
  182. src/agents/survey_agent/env_report.md +348 -0
  183. src/agents/survey_agent/modules/__init__.py +0 -0
  184. src/agents/survey_agent/modules/code_collector.py +2194 -0
  185. src/agents/survey_agent/modules/code_report_generator.py +1282 -0
  186. src/agents/survey_agent/modules/data_manager.py +1713 -0
  187. src/agents/survey_agent/modules/database.py +167 -0
  188. src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
  189. src/agents/survey_agent/modules/fulltext_http.py +134 -0
  190. src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
  191. src/agents/survey_agent/modules/judge.py +434 -0
  192. src/agents/survey_agent/modules/outcome_RAG.py +236 -0
  193. src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
  194. src/agents/survey_agent/modules/pe.py +1729 -0
  195. src/agents/survey_agent/modules/pseudo_creator.py +782 -0
  196. src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
  197. src/agents/survey_agent/modules/refine_agent.py +1952 -0
  198. src/agents/survey_agent/modules/survey_generator.py +5691 -0
  199. src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
  200. src/agents/survey_agent/modules/visual_prompts.py +327 -0
  201. src/agents/survey_agent/modules/work_analyzer.py +2750 -0
  202. src/agents/survey_agent/modules/work_collector.py +5649 -0
  203. src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
  204. src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
  205. src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
  206. src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
  207. src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
  208. src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
  209. src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
  210. src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
  211. src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
  212. src/agents/survey_agent/utils/__init__.py +0 -0
  213. src/agents/survey_agent/utils/ablation_utils.py +317 -0
  214. src/agents/survey_agent/utils/api_call.py +2518 -0
  215. src/agents/survey_agent/utils/chat_utils.py +82 -0
  216. src/agents/survey_agent/utils/config_utils.py +70 -0
  217. src/agents/survey_agent/utils/convert_to_md.py +29 -0
  218. src/agents/survey_agent/utils/err_info.py +24 -0
  219. src/agents/survey_agent/utils/file_utils.py +176 -0
  220. src/agents/survey_agent/utils/gpu_utils.py +145 -0
  221. src/agents/survey_agent/utils/graph_visualizer.py +279 -0
  222. src/agents/survey_agent/utils/html_utils.py +181 -0
  223. src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
  224. src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
  225. src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
  226. src/agents/survey_agent/utils/mineru_utils.py +396 -0
  227. src/agents/survey_agent/utils/paper.py +144 -0
  228. src/agents/survey_agent/utils/read_yaml.py +15 -0
  229. src/agents/survey_agent/utils/repo_utils.py +167 -0
  230. src/agents/survey_agent/utils/rich_logger.py +77 -0
  231. src/agents/survey_agent/utils/step2v2.py +874 -0
  232. src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
  233. src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
  234. src/agents/survey_agent/utils/utils.py +118 -0
  235. src/agents/survey_agent/utils/work.py +2 -0
  236. src/cli.py +1611 -0
  237. src/config/__init__.py +213 -0
  238. src/config/default.yaml +1122 -0
  239. src/harness/LICENSE +21 -0
  240. src/harness/VENDORED.md +14 -0
  241. src/harness/src/__init__.py +0 -0
  242. src/harness/src/openharness/__init__.py +0 -0
  243. src/harness/src/openharness/__main__.py +6 -0
  244. src/harness/src/openharness/api/__init__.py +21 -0
  245. src/harness/src/openharness/api/client.py +281 -0
  246. src/harness/src/openharness/api/codex_client.py +407 -0
  247. src/harness/src/openharness/api/copilot_auth.py +240 -0
  248. src/harness/src/openharness/api/copilot_client.py +134 -0
  249. src/harness/src/openharness/api/errors.py +19 -0
  250. src/harness/src/openharness/api/openai_client.py +577 -0
  251. src/harness/src/openharness/api/provider.py +186 -0
  252. src/harness/src/openharness/api/registry.py +437 -0
  253. src/harness/src/openharness/api/usage.py +17 -0
  254. src/harness/src/openharness/auth/__init__.py +29 -0
  255. src/harness/src/openharness/auth/external.py +610 -0
  256. src/harness/src/openharness/auth/flows.py +189 -0
  257. src/harness/src/openharness/auth/manager.py +482 -0
  258. src/harness/src/openharness/auth/storage.py +269 -0
  259. src/harness/src/openharness/autopilot/__init__.py +23 -0
  260. src/harness/src/openharness/autopilot/service.py +2239 -0
  261. src/harness/src/openharness/autopilot/types.py +91 -0
  262. src/harness/src/openharness/bridge/__init__.py +20 -0
  263. src/harness/src/openharness/bridge/manager.py +106 -0
  264. src/harness/src/openharness/bridge/session_runner.py +46 -0
  265. src/harness/src/openharness/bridge/types.py +37 -0
  266. src/harness/src/openharness/bridge/work_secret.py +41 -0
  267. src/harness/src/openharness/channels/UPSTREAM +6 -0
  268. src/harness/src/openharness/channels/__init__.py +22 -0
  269. src/harness/src/openharness/channels/adapter.py +131 -0
  270. src/harness/src/openharness/channels/bus/__init__.py +6 -0
  271. src/harness/src/openharness/channels/bus/events.py +38 -0
  272. src/harness/src/openharness/channels/bus/queue.py +44 -0
  273. src/harness/src/openharness/channels/impl/__init__.py +6 -0
  274. src/harness/src/openharness/channels/impl/base.py +141 -0
  275. src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
  276. src/harness/src/openharness/channels/impl/discord.py +311 -0
  277. src/harness/src/openharness/channels/impl/email.py +410 -0
  278. src/harness/src/openharness/channels/impl/feishu.py +1342 -0
  279. src/harness/src/openharness/channels/impl/manager.py +257 -0
  280. src/harness/src/openharness/channels/impl/matrix.py +700 -0
  281. src/harness/src/openharness/channels/impl/mochat.py +897 -0
  282. src/harness/src/openharness/channels/impl/qq.py +141 -0
  283. src/harness/src/openharness/channels/impl/slack.py +284 -0
  284. src/harness/src/openharness/channels/impl/telegram.py +525 -0
  285. src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
  286. src/harness/src/openharness/cli.py +2551 -0
  287. src/harness/src/openharness/commands/__init__.py +21 -0
  288. src/harness/src/openharness/commands/registry.py +2783 -0
  289. src/harness/src/openharness/config/__init__.py +34 -0
  290. src/harness/src/openharness/config/paths.py +160 -0
  291. src/harness/src/openharness/config/schema.py +119 -0
  292. src/harness/src/openharness/config/settings.py +1104 -0
  293. src/harness/src/openharness/coordinator/__init__.py +12 -0
  294. src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
  295. src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
  296. src/harness/src/openharness/engine/__init__.py +80 -0
  297. src/harness/src/openharness/engine/cost_tracker.py +24 -0
  298. src/harness/src/openharness/engine/messages.py +222 -0
  299. src/harness/src/openharness/engine/query.py +1188 -0
  300. src/harness/src/openharness/engine/query_engine.py +313 -0
  301. src/harness/src/openharness/engine/stream_events.py +90 -0
  302. src/harness/src/openharness/hooks/__init__.py +50 -0
  303. src/harness/src/openharness/hooks/events.py +20 -0
  304. src/harness/src/openharness/hooks/executor.py +242 -0
  305. src/harness/src/openharness/hooks/hot_reload.py +31 -0
  306. src/harness/src/openharness/hooks/loader.py +68 -0
  307. src/harness/src/openharness/hooks/schemas.py +66 -0
  308. src/harness/src/openharness/hooks/types.py +38 -0
  309. src/harness/src/openharness/keybindings/__init__.py +14 -0
  310. src/harness/src/openharness/keybindings/default_bindings.py +11 -0
  311. src/harness/src/openharness/keybindings/loader.py +22 -0
  312. src/harness/src/openharness/keybindings/parser.py +18 -0
  313. src/harness/src/openharness/keybindings/resolver.py +13 -0
  314. src/harness/src/openharness/mcp/__init__.py +79 -0
  315. src/harness/src/openharness/mcp/client.py +298 -0
  316. src/harness/src/openharness/mcp/config.py +16 -0
  317. src/harness/src/openharness/mcp/types.py +76 -0
  318. src/harness/src/openharness/memory/__init__.py +25 -0
  319. src/harness/src/openharness/memory/agent.py +91 -0
  320. src/harness/src/openharness/memory/manager.py +183 -0
  321. src/harness/src/openharness/memory/memdir.py +52 -0
  322. src/harness/src/openharness/memory/migrate.py +137 -0
  323. src/harness/src/openharness/memory/paths.py +22 -0
  324. src/harness/src/openharness/memory/relevance.py +145 -0
  325. src/harness/src/openharness/memory/scan.py +116 -0
  326. src/harness/src/openharness/memory/schema.py +443 -0
  327. src/harness/src/openharness/memory/search.py +71 -0
  328. src/harness/src/openharness/memory/team.py +90 -0
  329. src/harness/src/openharness/memory/types.py +33 -0
  330. src/harness/src/openharness/memory/usage.py +153 -0
  331. src/harness/src/openharness/output_styles/__init__.py +5 -0
  332. src/harness/src/openharness/output_styles/loader.py +42 -0
  333. src/harness/src/openharness/permissions/__init__.py +26 -0
  334. src/harness/src/openharness/permissions/checker.py +200 -0
  335. src/harness/src/openharness/permissions/modes.py +13 -0
  336. src/harness/src/openharness/personalization/__init__.py +6 -0
  337. src/harness/src/openharness/personalization/extractor.py +138 -0
  338. src/harness/src/openharness/personalization/rules.py +64 -0
  339. src/harness/src/openharness/personalization/session_hook.py +65 -0
  340. src/harness/src/openharness/platforms.py +86 -0
  341. src/harness/src/openharness/plugins/__init__.py +53 -0
  342. src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
  343. src/harness/src/openharness/plugins/installer.py +39 -0
  344. src/harness/src/openharness/plugins/loader.py +730 -0
  345. src/harness/src/openharness/plugins/schemas.py +24 -0
  346. src/harness/src/openharness/plugins/types.py +59 -0
  347. src/harness/src/openharness/prompts/__init__.py +14 -0
  348. src/harness/src/openharness/prompts/claudemd.py +48 -0
  349. src/harness/src/openharness/prompts/context.py +187 -0
  350. src/harness/src/openharness/prompts/environment.py +135 -0
  351. src/harness/src/openharness/prompts/system_prompt.py +109 -0
  352. src/harness/src/openharness/sandbox/Dockerfile +6 -0
  353. src/harness/src/openharness/sandbox/__init__.py +33 -0
  354. src/harness/src/openharness/sandbox/adapter.py +148 -0
  355. src/harness/src/openharness/sandbox/docker_backend.py +232 -0
  356. src/harness/src/openharness/sandbox/docker_image.py +103 -0
  357. src/harness/src/openharness/sandbox/path_validator.py +37 -0
  358. src/harness/src/openharness/sandbox/session.py +63 -0
  359. src/harness/src/openharness/services/__init__.py +30 -0
  360. src/harness/src/openharness/services/autodream/__init__.py +34 -0
  361. src/harness/src/openharness/services/autodream/backup.py +104 -0
  362. src/harness/src/openharness/services/autodream/lock.py +138 -0
  363. src/harness/src/openharness/services/autodream/prompt.py +128 -0
  364. src/harness/src/openharness/services/autodream/service.py +313 -0
  365. src/harness/src/openharness/services/compact/__init__.py +1725 -0
  366. src/harness/src/openharness/services/cron.py +137 -0
  367. src/harness/src/openharness/services/cron_scheduler.py +595 -0
  368. src/harness/src/openharness/services/lsp/__init__.py +216 -0
  369. src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
  370. src/harness/src/openharness/services/oauth/__init__.py +0 -0
  371. src/harness/src/openharness/services/session_backend.py +97 -0
  372. src/harness/src/openharness/services/session_memory/__init__.py +139 -0
  373. src/harness/src/openharness/services/session_storage.py +230 -0
  374. src/harness/src/openharness/services/token_estimation.py +15 -0
  375. src/harness/src/openharness/services/tool_outputs.py +55 -0
  376. src/harness/src/openharness/skills/__init__.py +44 -0
  377. src/harness/src/openharness/skills/_frontmatter.py +97 -0
  378. src/harness/src/openharness/skills/bundled/__init__.py +75 -0
  379. src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
  380. src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
  381. src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
  382. src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
  383. src/harness/src/openharness/skills/bundled/content/review.md +33 -0
  384. src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
  385. src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
  386. src/harness/src/openharness/skills/bundled/content/test.md +38 -0
  387. src/harness/src/openharness/skills/loader.py +229 -0
  388. src/harness/src/openharness/skills/registry.py +29 -0
  389. src/harness/src/openharness/skills/types.py +24 -0
  390. src/harness/src/openharness/state/__init__.py +6 -0
  391. src/harness/src/openharness/state/app_state.py +30 -0
  392. src/harness/src/openharness/state/store.py +40 -0
  393. src/harness/src/openharness/swarm/__init__.py +70 -0
  394. src/harness/src/openharness/swarm/in_process.py +693 -0
  395. src/harness/src/openharness/swarm/lockfile.py +24 -0
  396. src/harness/src/openharness/swarm/mailbox.py +522 -0
  397. src/harness/src/openharness/swarm/permission_sync.py +1168 -0
  398. src/harness/src/openharness/swarm/registry.py +410 -0
  399. src/harness/src/openharness/swarm/spawn_utils.py +229 -0
  400. src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
  401. src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
  402. src/harness/src/openharness/swarm/types.py +398 -0
  403. src/harness/src/openharness/swarm/worktree.py +315 -0
  404. src/harness/src/openharness/tasks/__init__.py +18 -0
  405. src/harness/src/openharness/tasks/local_agent_task.py +28 -0
  406. src/harness/src/openharness/tasks/local_shell_task.py +17 -0
  407. src/harness/src/openharness/tasks/manager.py +475 -0
  408. src/harness/src/openharness/tasks/stop_task.py +11 -0
  409. src/harness/src/openharness/tasks/types.py +32 -0
  410. src/harness/src/openharness/themes/__init__.py +21 -0
  411. src/harness/src/openharness/themes/builtin.py +89 -0
  412. src/harness/src/openharness/themes/loader.py +55 -0
  413. src/harness/src/openharness/themes/schema.py +54 -0
  414. src/harness/src/openharness/tools/__init__.py +107 -0
  415. src/harness/src/openharness/tools/agent_tool.py +141 -0
  416. src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
  417. src/harness/src/openharness/tools/base.py +80 -0
  418. src/harness/src/openharness/tools/bash_tool.py +259 -0
  419. src/harness/src/openharness/tools/brief_tool.py +33 -0
  420. src/harness/src/openharness/tools/config_tool.py +54 -0
  421. src/harness/src/openharness/tools/cron_create_tool.py +105 -0
  422. src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
  423. src/harness/src/openharness/tools/cron_list_tool.py +69 -0
  424. src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
  425. src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
  426. src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
  427. src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
  428. src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
  429. src/harness/src/openharness/tools/file_edit_tool.py +95 -0
  430. src/harness/src/openharness/tools/file_read_tool.py +72 -0
  431. src/harness/src/openharness/tools/file_write_tool.py +87 -0
  432. src/harness/src/openharness/tools/glob_tool.py +175 -0
  433. src/harness/src/openharness/tools/grep_tool.py +374 -0
  434. src/harness/src/openharness/tools/image_generation_tool.py +447 -0
  435. src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
  436. src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
  437. src/harness/src/openharness/tools/lsp_tool.py +154 -0
  438. src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
  439. src/harness/src/openharness/tools/mcp_tool.py +72 -0
  440. src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
  441. src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
  442. src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
  443. src/harness/src/openharness/tools/send_message_tool.py +58 -0
  444. src/harness/src/openharness/tools/skill_tool.py +43 -0
  445. src/harness/src/openharness/tools/sleep_tool.py +32 -0
  446. src/harness/src/openharness/tools/task_create_tool.py +56 -0
  447. src/harness/src/openharness/tools/task_get_tool.py +33 -0
  448. src/harness/src/openharness/tools/task_list_tool.py +35 -0
  449. src/harness/src/openharness/tools/task_output_tool.py +35 -0
  450. src/harness/src/openharness/tools/task_stop_tool.py +30 -0
  451. src/harness/src/openharness/tools/task_update_tool.py +50 -0
  452. src/harness/src/openharness/tools/team_create_tool.py +31 -0
  453. src/harness/src/openharness/tools/team_delete_tool.py +30 -0
  454. src/harness/src/openharness/tools/todo_write_tool.py +46 -0
  455. src/harness/src/openharness/tools/tool_search_tool.py +38 -0
  456. src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
  457. src/harness/src/openharness/tools/web_search_tool.py +119 -0
  458. src/harness/src/openharness/ui/__init__.py +5 -0
  459. src/harness/src/openharness/ui/app.py +320 -0
  460. src/harness/src/openharness/ui/backend_host.py +941 -0
  461. src/harness/src/openharness/ui/coordinator_drain.py +198 -0
  462. src/harness/src/openharness/ui/input.py +32 -0
  463. src/harness/src/openharness/ui/output.py +265 -0
  464. src/harness/src/openharness/ui/permission_dialog.py +14 -0
  465. src/harness/src/openharness/ui/protocol.py +247 -0
  466. src/harness/src/openharness/ui/react_launcher.py +179 -0
  467. src/harness/src/openharness/ui/runtime.py +799 -0
  468. src/harness/src/openharness/ui/textual_app.py +495 -0
  469. src/harness/src/openharness/utils/__init__.py +0 -0
  470. src/harness/src/openharness/utils/file_lock.py +86 -0
  471. src/harness/src/openharness/utils/fs.py +98 -0
  472. src/harness/src/openharness/utils/helpers.py +77 -0
  473. src/harness/src/openharness/utils/network_guard.py +340 -0
  474. src/harness/src/openharness/utils/shell.py +147 -0
  475. src/harness/src/openharness/vim/__init__.py +5 -0
  476. src/harness/src/openharness/vim/transitions.py +8 -0
  477. src/harness/src/openharness/voice/__init__.py +7 -0
  478. src/harness/src/openharness/voice/keyterms.py +10 -0
  479. src/harness/src/openharness/voice/stream_stt.py +8 -0
  480. src/harness/src/openharness/voice/voice_mode.py +44 -0
  481. src/llm/__init__.py +29 -0
  482. src/llm/image_generation.py +402 -0
  483. src/llm/provider_registry.py +419 -0
  484. src/llm/runtime_env.py +64 -0
  485. src/llm/structured_output.py +131 -0
  486. src/llm/vision.py +198 -0
  487. src/memory/__init__.py +2 -0
  488. src/memory/api/__init__.py +1 -0
  489. src/memory/api/base_memory_system_api.py +98 -0
  490. src/memory/api/base_symbolic_memory_system_api.py +190 -0
  491. src/memory/api/base_vector_memory_system_api.py +116 -0
  492. src/memory/api/faiss_memory_system_api.py +228 -0
  493. src/memory/api/slot_process_api.py +827 -0
  494. src/memory/api/symbolic_memory_system_api.py +572 -0
  495. src/memory/memory_system/__init__.py +48 -0
  496. src/memory/memory_system/component_taxonomy.py +430 -0
  497. src/memory/memory_system/llm.py +437 -0
  498. src/memory/memory_system/models.py +168 -0
  499. src/memory/memory_system/user_prompt.py +418 -0
  500. src/memory/memory_system/utils.py +282 -0
  501. src/memory/memory_system/vectorstore.py +311 -0
  502. src/memory/memory_system/working_slot.py +99 -0
  503. src/pipeline/__init__.py +129 -0
  504. src/pipeline/contracts.py +531 -0
  505. src/pipeline/discipline_taxonomy.py +916 -0
  506. src/pipeline/evidence_coverage_ledger.py +1150 -0
  507. src/pipeline/evidence_refinement.py +620 -0
  508. src/pipeline/experiment_to_symbolic.py +137 -0
  509. src/pipeline/multimodal_evidence/__init__.py +28 -0
  510. src/pipeline/multimodal_evidence/capabilities.py +107 -0
  511. src/pipeline/multimodal_evidence/claims.py +135 -0
  512. src/pipeline/multimodal_evidence/contract.py +291 -0
  513. src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
  514. src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
  515. src/pipeline/multimodal_evidence/manifest.py +254 -0
  516. src/pipeline/multimodal_evidence/native_analysis.py +479 -0
  517. src/pipeline/multimodal_evidence/perception.py +277 -0
  518. src/pipeline/multimodal_evidence/query_binding.py +65 -0
  519. src/pipeline/multimodal_evidence/rendering.py +120 -0
  520. src/pipeline/multimodal_evidence/safety.py +42 -0
  521. src/pipeline/multimodal_evidence/sampling.py +69 -0
  522. src/pipeline/multimodal_evidence/service.py +148 -0
  523. src/pipeline/multimodal_evidence/survey_integration.py +460 -0
  524. src/pipeline/paper_identity.py +52 -0
  525. src/pipeline/research_design_inventory.py +338 -0
  526. src/pipeline/research_domain_resolution.py +669 -0
  527. src/pipeline/research_identity.py +548 -0
  528. src/pipeline/research_question_contract.py +646 -0
  529. src/pipeline/retrieval_lanes.py +889 -0
  530. src/pipeline/run_loop.py +862 -0
  531. src/pipeline/science_events.py +30 -0
  532. src/pipeline/science_manifests.py +602 -0
  533. src/pipeline/science_run.py +918 -0
  534. src/pipeline/science_stages.py +709 -0
  535. src/pipeline/science_workflow.py +807 -0
  536. src/pipeline/sh_cluster_projection.py +306 -0
  537. src/pipeline/sh_graph_provenance.py +648 -0
  538. src/pipeline/subhypothesis_decomposition.py +392 -0
  539. src/pipeline/subhypothesis_evidence.py +856 -0
  540. src/pipeline/survey_evidence_plan.py +858 -0
  541. src/pipeline/survey_gap_adjudication.py +647 -0
  542. src/pipeline/survey_gap_candidates.py +840 -0
  543. src/pipeline/survey_gap_ledger.py +683 -0
  544. src/pipeline/survey_gap_triage.py +401 -0
  545. src/pipeline/survey_handoff_persistence.py +481 -0
  546. src/pipeline/survey_handoff_projection.py +457 -0
  547. src/pipeline/survey_idea_handoff.py +1112 -0
  548. src/pipeline/survey_idea_loader.py +289 -0
  549. src/research_run_ids.py +11 -0
@@ -0,0 +1,1075 @@
1
+ import sqlite3
2
+ from typing import List, Dict, Optional, Tuple
3
+ import hydra
4
+ import sys
5
+ import os
6
+ import json
7
+ sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
8
+ from utils.rich_logger import get_logger
9
+ from utils.step2v2_extractor import (
10
+ build_main_extraction_prompt,
11
+ build_baseline_extraction_prompt,
12
+ extract_regex_candidates,
13
+ clean_title_latex,
14
+ aggregate_results,
15
+ validate_and_parse_main,
16
+ validate_and_parse_baseline,
17
+ format_extraction_result,
18
+ )
19
+ from utils.api_call import ChatAgent
20
+ from utils.config_utils import merge_with_default_survey_config, resolve_repo_relative_path
21
+ from utils.mineru_section_packer import (
22
+ DEFAULT_EXCLUDED_SECTION_PATTERNS,
23
+ derive_effective_body_budget,
24
+ pack_mineru_markdown_by_complete_sections,
25
+ render_packet_outline,
26
+ )
27
+ from modules.data_manager import DataManager
28
+ import diskcache as dc
29
+ import re
30
+
31
+
32
+ class PaperGraphRetriever:
33
+ def __init__(self, config, data_manager = None):
34
+ self.config = config
35
+ self.logger = get_logger("PaperGraphRetriever")
36
+ self.db_path = resolve_repo_relative_path(
37
+ self.config.ModuleInfo.PaperGraphRetriever.db_path
38
+ )
39
+ self.chat_agent = ChatAgent(config)
40
+ if not data_manager:
41
+ self.data_manager = DataManager(config)
42
+ else:
43
+ self.data_manager = data_manager
44
+
45
+ # Initialize diskcache for keynote caching
46
+ cache_path = getattr(config.BasicInfo, 'cache_path', '/tmp/paper_graph_cache')
47
+ os.makedirs(cache_path, exist_ok=True)
48
+ self.graph_keynotes_cache = dc.Cache(
49
+ os.path.join(cache_path, "graph_keynotes")
50
+ )
51
+
52
+ # Initialize constructed_nodes directory for saving extraction results
53
+ self.constructed_nodes_path = os.path.join(cache_path, "constructed_nodes")
54
+ os.makedirs(self.constructed_nodes_path, exist_ok=True)
55
+
56
+ def search_by_paper_title(self, title_query: str, limit: int = 20):
57
+ """
58
+ 根据 paper_title 模糊搜索论文(大小写不敏感)
59
+
60
+ Args:
61
+ title_query: 标题关键词,支持模糊匹配
62
+ limit: 返回结果数量限制,默认20
63
+
64
+ Returns:
65
+ list: 匹配的论文节点列表,每项为包含节点信息的字典
66
+ """
67
+ conn = sqlite3.connect(self.db_path)
68
+ conn.row_factory = sqlite3.Row
69
+ cursor = conn.cursor()
70
+
71
+ pattern = f"%{title_query}%"
72
+ # 使用 LOWER() 实现大小写不敏感的模糊匹配
73
+ sql = """
74
+ SELECT id, node_type, paper_id, paper_title, pub_year,
75
+ source_venue, full_name, acronym, summary
76
+ FROM nodes
77
+ WHERE LOWER(paper_title) LIKE LOWER(?)
78
+ ORDER BY
79
+ CASE node_type
80
+ WHEN 'Core' THEN 1
81
+ WHEN 'Baseline' THEN 2
82
+ WHEN 'Dataset' THEN 3
83
+ ELSE 4
84
+ END,
85
+ pub_year DESC
86
+ LIMIT ?
87
+ """
88
+ cursor.execute(sql, (pattern, limit))
89
+ rows = cursor.fetchall()
90
+
91
+ results = [dict(row) for row in rows]
92
+
93
+ conn.close()
94
+ return results
95
+
96
+
97
+ def search_by_node_id(self, node_id: str, limit: int = 20):
98
+ """
99
+ 根据 node_id 精确搜索论文
100
+
101
+ Args:
102
+ paper_id: 论文ID(如 'vlm', 'nlp' 等)
103
+ limit: 返回结果数量限制,默认20
104
+
105
+ Returns:
106
+ list: 匹配的论文节点列表,每项为包含节点信息的字典
107
+ """
108
+ conn = sqlite3.connect(self.db_path)
109
+ conn.row_factory = sqlite3.Row
110
+ cursor = conn.cursor()
111
+
112
+ sql = """
113
+ SELECT id, node_type, paper_id, paper_title, pub_year,
114
+ source_venue, full_name, acronym, summary
115
+ FROM nodes
116
+ WHERE id = ?
117
+ ORDER BY
118
+ CASE node_type
119
+ WHEN 'Core' THEN 1
120
+ WHEN 'Baseline' THEN 2
121
+ WHEN 'Dataset' THEN 3
122
+ ELSE 4
123
+ END,
124
+ pub_year DESC
125
+ LIMIT ?
126
+ """
127
+ cursor.execute(sql, (node_id, limit))
128
+ rows = cursor.fetchall()
129
+
130
+ results = [dict(row) for row in rows]
131
+
132
+ conn.close()
133
+ return results
134
+
135
+
136
+ def get_adjacent_nodes(self, node_id: str, edge_types: list = None, include_edges: bool = False):
137
+ """
138
+ 获取与指定节点相邻的所有节点
139
+
140
+ Args:
141
+ node_id: 节点ID(可以是论文的 id 字段)
142
+ edge_types: 可选的边类型过滤列表
143
+ include_edges: 是否同时返回边信息
144
+
145
+ Returns:
146
+ 如果 include_edges=False: 返回相邻节点的信息列表
147
+ 如果 include_edges=True: 返回 (相邻节点列表, 边列表) 的元组
148
+ """
149
+ conn = sqlite3.connect(self.db_path)
150
+ conn.row_factory = sqlite3.Row
151
+ cursor = conn.cursor()
152
+
153
+ out_edges_sql = """
154
+ SELECT source, target, edge_type, summary, keywords, insight
155
+ FROM edges
156
+ WHERE source = ?
157
+ """
158
+ in_edges_sql = """
159
+ SELECT source, target, edge_type, summary, keywords, insight
160
+ FROM edges
161
+ WHERE target = ?
162
+ """
163
+
164
+ params = [node_id]
165
+ if edge_types:
166
+ placeholders = ','.join(['?' for _ in edge_types])
167
+ out_edges_sql += f" AND edge_type IN ({placeholders})"
168
+ in_edges_sql += f" AND edge_type IN ({placeholders})"
169
+ params = [node_id] + edge_types
170
+
171
+ out_edges = cursor.execute(out_edges_sql, params).fetchall()
172
+ in_edges = cursor.execute(in_edges_sql, params).fetchall()
173
+
174
+ neighbor_ids = set()
175
+ all_edges = []
176
+
177
+ for e in out_edges:
178
+ neighbor_ids.add(e['target'])
179
+ all_edges.append(dict(e))
180
+
181
+ for e in in_edges:
182
+ neighbor_ids.add(e['source'])
183
+ all_edges.append(dict(e))
184
+
185
+ if neighbor_ids:
186
+ placeholders = ','.join(['?' for _ in neighbor_ids])
187
+ nodes_sql = f"""
188
+ SELECT id, node_type, paper_id, paper_title, pub_year,
189
+ source_venue, full_name, acronym, summary
190
+ FROM nodes
191
+ WHERE id IN ({placeholders})
192
+ ORDER BY
193
+ CASE node_type
194
+ WHEN 'Core' THEN 1
195
+ WHEN 'Baseline' THEN 2
196
+ WHEN 'Dataset' THEN 3
197
+ ELSE 4
198
+ END
199
+ """
200
+ cursor.execute(nodes_sql, list(neighbor_ids))
201
+ neighbors = [dict(row) for row in cursor.fetchall()]
202
+ else:
203
+ neighbors = []
204
+
205
+ conn.close()
206
+
207
+ if include_edges:
208
+ return neighbors, all_edges
209
+ return neighbors
210
+
211
+
212
+ def debug_table(self, table_name: str, limit: int = 10, where_clause: str = None, where_params: tuple = None):
213
+ """Debug函数:查看任意表的数据结构和内容"""
214
+ conn = sqlite3.connect(self.db_path)
215
+ conn.row_factory = sqlite3.Row
216
+ cursor = conn.cursor()
217
+
218
+ cursor.execute(f"PRAGMA table_info({table_name})")
219
+ columns = [dict(row) for row in cursor.fetchall()]
220
+
221
+ if where_clause:
222
+ sql = f"SELECT * FROM {table_name} WHERE {where_clause} LIMIT ?"
223
+ params = (where_params if where_params else ()) + (limit,)
224
+ else:
225
+ sql = f"SELECT * FROM {table_name} LIMIT ?"
226
+ params = (limit,)
227
+
228
+ cursor.execute(sql, params)
229
+ rows = [dict(row) for row in cursor.fetchall()]
230
+
231
+ conn.close()
232
+
233
+ return {
234
+ "columns": columns,
235
+ "rows": rows,
236
+ "total_columns": len(columns),
237
+ "total_rows_fetched": len(rows)
238
+ }
239
+
240
+
241
+ def print_debug_table(self, table_name: str, limit: int = 5, where_clause: str = None, where_params: tuple = None):
242
+ result = self.debug_table(table_name, limit, where_clause, where_params)
243
+
244
+ print(f"\n{'='*60}")
245
+ print(f"表名: {table_name}")
246
+ print(f"{'='*60}")
247
+
248
+ print(f"\n【列结构】(共 {result['total_columns']} 列)")
249
+ print("-" * 40)
250
+ for col in result['columns']:
251
+ print(f" {col['name']:30} | {col['type']:10} | nullable: {col['notnull']}")
252
+
253
+ print(f"\n【数据】(显示前 {result['total_rows_fetched']} 条)")
254
+ print("-" * 40)
255
+
256
+ if not result['rows']:
257
+ print(" (无数据)")
258
+ else:
259
+ for i, row in enumerate(result['rows']):
260
+ print(f"\n --- 第 {i+1} 条 ---")
261
+ for key, value in row.items():
262
+ value_str = str(value) if value is not None else "NULL"
263
+ if len(value_str) > 80:
264
+ value_str = value_str[:80] + "..."
265
+ print(f" {key}: {value_str}")
266
+
267
+ print(f"\n{'='*60}\n")
268
+ return result
269
+
270
+
271
+ def print_all_tables(self, limit: int = 5):
272
+ """打印数据库中所有表的列信息和前若干行数据"""
273
+ conn = sqlite3.connect(self.db_path)
274
+ conn.row_factory = sqlite3.Row
275
+ cursor = conn.cursor()
276
+
277
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name")
278
+ tables = [row['name'] for row in cursor.fetchall()]
279
+
280
+ if not tables:
281
+ print("数据库中没有表")
282
+ conn.close()
283
+ return
284
+
285
+ print(f"\n{'='*80}")
286
+ print(f"数据库: {self.db_path}")
287
+ print(f"包含 {len(tables)} 个表")
288
+ print(f"{'='*80}")
289
+
290
+ for table_name in tables:
291
+ print(f"\n{'='*80}")
292
+ print(f"表名: {table_name}")
293
+ print(f"{'='*80}")
294
+
295
+ cursor.execute(f"PRAGMA table_info({table_name})")
296
+ columns = [dict(row) for row in cursor.fetchall()]
297
+
298
+ print(f"\n【列结构】(共 {len(columns)} 列)")
299
+ print("-" * 60)
300
+ for col in columns:
301
+ pk = " (PK)" if col['pk'] else ""
302
+ nullable = "" if col['notnull'] else " (nullable)"
303
+ print(f" {col['cid']:3} | {col['name']:30} | {col['type']:15}{pk}{nullable}")
304
+
305
+ cursor.execute(f"SELECT * FROM {table_name} LIMIT ?", (limit,))
306
+ rows = cursor.fetchall()
307
+ total_rows = cursor.execute(f"SELECT COUNT(*) FROM {table_name}").fetchone()[0]
308
+
309
+ print(f"\n【数据】(共 {total_rows} 条,显示前 {min(limit, len(rows))} 条)")
310
+ print("-" * 60)
311
+
312
+ if not rows:
313
+ print(" (无数据)")
314
+ else:
315
+ for i, row in enumerate(rows):
316
+ row_dict = dict(row)
317
+ print(f"\n --- 第 {i+1} 条 ---")
318
+ for key, value in row_dict.items():
319
+ value_str = str(value) if value is not None else "NULL"
320
+ if len(value_str) > 100:
321
+ value_str = value_str[:100] + "..."
322
+ print(f" {key}: {value_str}")
323
+
324
+ conn.close()
325
+ print(f"\n{'='*80}\n")
326
+
327
+ def expand_nodes_with_lineage(
328
+ self,
329
+ node_ids: List[str],
330
+ max_step: int = 2,
331
+ ) -> Tuple[List[str], Dict[str, List[Dict[str, object]]]]:
332
+ """Expand local graph nodes while retaining root/parent/depth paths.
333
+
334
+ The public ``expand_nodes`` return value predates SH provenance and is
335
+ intentionally preserved below. This companion method exposes only
336
+ traversal facts; callers must still assess a returned paper before
337
+ treating it as scientific evidence.
338
+ """
339
+
340
+ roots = [str(node_id) for node_id in node_ids if str(node_id)]
341
+ root_set = set(roots)
342
+ visited_nodes = set(roots)
343
+ seen_paths = {(root, root) for root in roots}
344
+ current_layer = [
345
+ {"root_node_id": root, "node_id": root, "lineage_depth": 0}
346
+ for root in roots
347
+ ]
348
+ lineage_by_node: Dict[str, List[Dict[str, object]]] = {}
349
+
350
+ for _step in range(max(0, int(max_step))):
351
+ next_layer: List[Dict[str, object]] = []
352
+ for path in current_layer:
353
+ root_node_id = str(path["root_node_id"])
354
+ parent_node_id = str(path["node_id"])
355
+ parent_depth = int(path["lineage_depth"])
356
+ neighbors, _edges = self.get_adjacent_nodes(
357
+ parent_node_id,
358
+ include_edges=True,
359
+ )
360
+ for node in neighbors:
361
+ node_id = str(node.get("id") or "")
362
+ if not node_id or node_id in root_set:
363
+ continue
364
+ path_key = (root_node_id, node_id)
365
+ if path_key in seen_paths:
366
+ continue
367
+ seen_paths.add(path_key)
368
+ visited_nodes.add(node_id)
369
+ record: Dict[str, object] = {
370
+ "root_node_id": root_node_id,
371
+ "parent_node_id": parent_node_id,
372
+ "lineage_depth": parent_depth + 1,
373
+ }
374
+ lineage_by_node.setdefault(node_id, []).append(record)
375
+ next_layer.append(
376
+ {
377
+ "root_node_id": root_node_id,
378
+ "node_id": node_id,
379
+ "lineage_depth": parent_depth + 1,
380
+ }
381
+ )
382
+ if not next_layer:
383
+ break
384
+ current_layer = next_layer
385
+ return list(visited_nodes), lineage_by_node
386
+
387
+ def expand_nodes(self, node_ids: List[str], max_step: int = 2):
388
+ """Backward-compatible local expansion without exposing provenance."""
389
+
390
+ nodes, _lineage = self.expand_nodes_with_lineage(node_ids, max_step)
391
+ return nodes
392
+
393
+ def format_details(self, details: dict):
394
+ if not details:
395
+ return "No details found."
396
+
397
+ formatted = f"Paper Title: {details.get('paper_title', 'N/A')}\n"
398
+ formatted += f"Paper Type: {details.get('paper_type', 'N/A')}\n"
399
+ formatted += f"Domain: {details.get('paper_domain', 'N/A')}\n"
400
+ formatted += f"Quote: {details.get('quote', 'N/A')}\n"
401
+ formatted += f"Summary: {details.get('summary', 'N/A')}\n"
402
+ formatted += f"Keywords: {details.get('keywords', 'N/A')}\n"
403
+ formatted += f"Insight: {details.get('insight', 'N/A')}\n"
404
+
405
+ return formatted
406
+
407
+ def title_to_id(self, title: str) -> str:
408
+ """
409
+ Convert paper title to node id.
410
+
411
+ Args:
412
+ title: Paper title (exact match or fuzzy match)
413
+
414
+ Returns:
415
+ Node id string
416
+
417
+ Raises:
418
+ ValueError: If title not found
419
+ """
420
+ # Use existing search function
421
+ results = self.search_by_paper_title(title, limit=1)
422
+
423
+ if not results:
424
+ raise ValueError(f"Title not found in paper graph: {title}, cannot convert to id")
425
+
426
+ return results[0]['id']
427
+
428
+ def id_to_title(self, node_id: str) -> str:
429
+ """
430
+ Convert node id to paper title.
431
+
432
+ Args:
433
+ node_id: Node id in the graph
434
+
435
+ Returns:
436
+ Paper title string
437
+
438
+ Raises:
439
+ ValueError: If id not found
440
+ """
441
+ # Use existing search function
442
+ results = self.search_by_node_id(node_id, limit=1)
443
+
444
+ if not results or not results[0].get('paper_title'):
445
+ raise ValueError(f"Node id not found in paper graph: {node_id}, cannot find title")
446
+
447
+ return results[0]['paper_title']
448
+
449
+ def get_node_details(self, node_id: str):
450
+ details = self.search_by_node_id(node_id, 1)
451
+ if not details:
452
+ return None
453
+ return details[0]
454
+
455
+ def _validate_keynotes(self, keynote, source = "Unknown"):
456
+ if not isinstance(keynote, str):
457
+ self.logger.error(f"source {source} keynote not string")
458
+ return False
459
+ if len(keynote) < 50:
460
+ self.logger.error(f"source {source} keynote too short")
461
+ return False
462
+
463
+ # Parse keynote and validate content
464
+ lines = keynote.strip().split('\n')
465
+ keynote_dict = {}
466
+ for line in lines:
467
+ if ':' in line:
468
+ key, value = line.split(':', 1)
469
+ keynote_dict[key.strip()] = value.strip()
470
+
471
+ # Check 1: Paper Type should not be "N/A" or "baseline"
472
+ paper_type = keynote_dict.get('Paper Type', '').lower()
473
+ if paper_type == 'n/a' or paper_type == 'baseline':
474
+ self.logger.error(f"source {source} paper_type is N/A or baseline: {paper_type}")
475
+ return False
476
+
477
+ # Check 2: Count N/A occurrences - if >= 3, consider invalid
478
+ na_count = 0
479
+ for key, value in keynote_dict.items():
480
+ if value.upper() == 'N/A':
481
+ na_count += 1
482
+
483
+ if na_count >= 3:
484
+ self.logger.error(f"source {source} has too many N/A values: {na_count}")
485
+ return False
486
+
487
+ return True
488
+
489
+ def read_papers_and_write_keynotes(self, paper_ids: List[str]):
490
+ _, err_ids = self.get_paper_keynote(paper_ids)
491
+ return err_ids
492
+
493
+ def get_paper_keynote(self, paper_ids: List[str], use_graph_id: bool = False):
494
+ """
495
+ Get paper keynote information.
496
+ If node_type is 'baseline' or node info is missing, extract information.
497
+
498
+ Caching strategy:
499
+ 1. Check diskcache first (fastest)
500
+ 2. If not in cache, extract and store in both cache and database
501
+
502
+ Args:
503
+ paper_ids: List of paper/node IDs
504
+ use_graph_id: If True, paper_ids are already graph IDs
505
+
506
+ Returns:
507
+ List of formatted keynote strings (same order as paper_ids)
508
+ """
509
+ error_ids = []
510
+ if not use_graph_id:
511
+ graph_ids = [None]*len(paper_ids)
512
+ for idx, paper_id in enumerate(paper_ids):
513
+ try:
514
+ title = self.data_manager.get_paper_title(paper_id)
515
+ graph_id = self.title_to_id(title)
516
+ graph_ids[idx] = graph_id
517
+ except Exception as e:
518
+ self.logger.warning(f"Fail to convert ds_id to graph_id before retrieve in graph: {e}")
519
+ else:
520
+ graph_ids = paper_ids
521
+
522
+ nodes_to_be_constructed = []
523
+ nodes_indices = []
524
+ results = [None] * len(graph_ids)
525
+
526
+ # Step 1: Check cache and existing nodes
527
+ for idx, node_id in enumerate(graph_ids):
528
+ if not node_id:
529
+ error_ids.append(paper_ids[idx])
530
+ continue
531
+
532
+ # Try to get from cache first
533
+ cache_key = f"keynote_{node_id}"
534
+ if cache_key in self.graph_keynotes_cache and self._validate_keynotes(self.graph_keynotes_cache[cache_key], "cache"):
535
+ self.logger.info(f"Found keynote in cache for {node_id}") #: {self.graph_keynotes_cache[cache_key]}
536
+ results[idx] = self.graph_keynotes_cache[cache_key]
537
+ continue
538
+
539
+ # Check database for existing keynote
540
+ db_keynote = self._read_keynote_from_db(node_id)
541
+ if db_keynote and self._validate_keynotes(db_keynote, "SQL keynotes"):
542
+ self.logger.info(f"Found keynote in database for {node_id}")
543
+ results[idx] = db_keynote
544
+ self.graph_keynotes_cache[cache_key] = db_keynote # Also add to cache
545
+ continue
546
+
547
+ # Need to extract
548
+ need_extract = True
549
+ details = self.get_node_details(node_id)
550
+ if details and details.get('node_type', 'baseline').lower() != 'baseline':
551
+ keynote = self.format_details(details)
552
+ if self._validate_keynotes(keynote):
553
+ results[idx] = keynote
554
+ # Cache the keynote
555
+ self.graph_keynotes_cache[cache_key] = keynote
556
+ self._write_keynote_to_db(node_id, keynote)
557
+ need_extract = False
558
+ else:
559
+ need_extract = True
560
+ if need_extract:
561
+ if details and details.get('node_type', '').lower() == 'baseline':
562
+ self.logger.info("baseline node. Need to extract information")
563
+ else:
564
+ self.logger.info("lack in paper graph. Need to extract information")
565
+ nodes_to_be_constructed.append(node_id)
566
+ nodes_indices.append(idx)
567
+
568
+ # Step 2: Extract information for nodes that need it
569
+ if nodes_to_be_constructed:
570
+ extraction_results = self.extract_node_info(nodes_to_be_constructed)
571
+ for i, extraction in enumerate(extraction_results):
572
+ keynote = format_extraction_result(extraction) if extraction else None
573
+ results[nodes_indices[i]] = keynote
574
+
575
+ # Cache the extracted keynote
576
+ node_id = nodes_to_be_constructed[i]
577
+ if keynote:
578
+ self.logger.info(f"generated keynote: {keynote}")
579
+ cache_key = f"keynote_{node_id}"
580
+ self.graph_keynotes_cache[cache_key] = keynote
581
+ self._write_keynote_to_db(node_id, keynote)
582
+
583
+ # Save the full extraction result dict as JSON file
584
+ if extraction:
585
+ self._save_extraction_result_to_json(node_id, extraction)
586
+
587
+ # Step 3: Collect error IDs
588
+ for idx, result in enumerate(results):
589
+ if not result:
590
+ error_ids.append(paper_ids[idx])
591
+
592
+ return results, error_ids
593
+
594
+ def _write_keynote_to_db(self, node_id: str, keynote: str):
595
+ """Write keynote to database. Creates keynote column if not exists."""
596
+ try:
597
+ conn = sqlite3.connect(self.db_path)
598
+ cursor = conn.cursor()
599
+
600
+ # Check if keynote column exists, create if not
601
+ cursor.execute("PRAGMA table_info(nodes)")
602
+ columns = [col[1] for col in cursor.fetchall()]
603
+
604
+ if 'keynote' not in columns:
605
+ self.logger.info("Creating 'keynote' column in nodes table")
606
+ cursor.execute("ALTER TABLE nodes ADD COLUMN keynote TEXT")
607
+
608
+ # Update keynote
609
+ cursor.execute(
610
+ "UPDATE nodes SET keynote = ? WHERE id = ?",
611
+ (keynote, node_id)
612
+ )
613
+ conn.commit()
614
+ conn.close()
615
+ self.logger.info(f"Wrote keynote to database for {node_id}")
616
+ except Exception as e:
617
+ self.logger.warning(f"Failed to write keynote to database for {node_id}: {e}")
618
+
619
+ def _read_keynote_from_db(self, node_id: str) -> Optional[str]:
620
+ """Read keynote from database."""
621
+ try:
622
+ conn = sqlite3.connect(self.db_path)
623
+ conn.row_factory = sqlite3.Row
624
+ cursor = conn.cursor()
625
+
626
+ # Check if keynote column exists
627
+ cursor.execute("PRAGMA table_info(nodes)")
628
+ columns = [col[1] for col in cursor.fetchall()]
629
+
630
+ if 'keynote' not in columns:
631
+ conn.close()
632
+ return None
633
+
634
+ cursor.execute("SELECT keynote FROM nodes WHERE id = ?", (node_id,))
635
+ row = cursor.fetchone()
636
+ conn.close()
637
+
638
+ if row and row['keynote']:
639
+ return row['keynote']
640
+ return None
641
+ except Exception as e:
642
+ self.logger.warning(f"Failed to read keynote from database for {node_id}: {e}")
643
+ return None
644
+
645
+ def _save_extraction_result_to_json(self, node_id: str, extraction_result: dict):
646
+ """Save the full extraction result dict as JSON file to constructed_nodes directory."""
647
+ import json
648
+ try:
649
+ # Sanitize node_id for filename (remove invalid characters)
650
+ safe_node_id = re.sub(r'[<>:"/\\|?*]', '_', str(node_id))
651
+ json_path = os.path.join(self.constructed_nodes_path, f"{safe_node_id}.json")
652
+
653
+ with open(json_path, 'w', encoding='utf-8') as f:
654
+ json.dump(extraction_result, f, indent=2, ensure_ascii=False)
655
+
656
+ self.logger.info(f"Saved extraction result to {json_path}")
657
+ except Exception as e:
658
+ self.logger.warning(f"Failed to save extraction result to JSON for {node_id}: {e}")
659
+
660
+ def get_paper_markdown(self, node_id: str) -> Optional[str]:
661
+ """Get the raw markdown text for a paper via data_manager."""
662
+ try:
663
+ paper_title = self.id_to_title(node_id)
664
+ paper_info = self.data_manager.get_paper_with_title(paper_title)
665
+ ds_id = self.data_manager._resolve_paper_reference_id(paper_info)
666
+ return self.data_manager.get_paper_raw_markdown(ds_id)
667
+ except Exception as e:
668
+ self.logger.warning(f"Failed to get markdown for paper {node_id}: {e}")
669
+ return None
670
+
671
+ def get_source_info(self, node_id: str) -> dict:
672
+ """Get source info (venue, year) for a paper from database."""
673
+ conn = sqlite3.connect(self.db_path)
674
+ conn.row_factory = sqlite3.Row
675
+ cursor = conn.cursor()
676
+ cursor.execute(
677
+ "SELECT source_venue, pub_year FROM nodes WHERE id = ?",
678
+ (node_id,)
679
+ )
680
+ row = cursor.fetchone()
681
+ conn.close()
682
+
683
+ if row:
684
+ return {
685
+ 'source_venue': row['source_venue'] or 'Unknown',
686
+ 'pub_year': str(row['pub_year']) if row['pub_year'] else '2024'
687
+ }
688
+ return {'source_venue': 'Unknown', 'pub_year': '2024'}
689
+
690
+ def extract_node_info(self, node_ids: List[str]) -> List[dict]:
691
+ """
692
+ Extract key information from papers that need re-extraction.
693
+
694
+ Args:
695
+ paper_ids: List of paper/node IDs to extract information for
696
+
697
+ Returns:
698
+ List of extraction results
699
+ """
700
+ # Step 1: Collect markdown texts and source info for all papers
701
+ papers = {}
702
+ source_info = {}
703
+ for node_id in node_ids:
704
+ md = self.get_paper_markdown(node_id)
705
+ if md:
706
+ papers[node_id] = md
707
+ source_info[node_id] = self.get_source_info(node_id)
708
+ else:
709
+ self.logger.warning(f"Markdown not found for paper {node_id}, skipping")
710
+
711
+ if not papers:
712
+ self.logger.error("No markdown found for any of the requested papers")
713
+ return []
714
+
715
+ self.logger.info(f"Extracting info for {len(papers)} papers via batch_remote_chat_with_retry")
716
+
717
+ # Step 2: Extract with retry
718
+ final_results = self._extract_with_retry(papers, source_info)
719
+
720
+ self.logger.info(f"Extracted info for {len(final_results.keys())} papers")
721
+ results_list = []
722
+ for node_id in node_ids:
723
+ results_list.append(final_results.get(node_id, None))
724
+
725
+ return results_list
726
+
727
+ def _extract_with_retry(self, papers: Dict[str, str], source_info: Dict[str, dict]) -> List[dict]:
728
+ """Extract paper info using batch_remote_chat_with_retry."""
729
+ node_ids = list(papers.keys())
730
+ markdowns = list(papers.values())
731
+
732
+ # ===== Step 1: Main Extraction =====
733
+ main_prompts, main_metadata = self._build_main_prompts(node_ids, markdowns)
734
+ if not main_prompts:
735
+ self.logger.warning(
736
+ "No graph-extraction prompts were created from safely bounded complete sections."
737
+ )
738
+ return {}
739
+ self.logger.info(f"Calling batch_remote_chat_with_retry for main extraction ({len(main_prompts)} papers)")
740
+
741
+ # Build info_dict with metadata list for validation function
742
+ main_info_dict = {'metadata': main_metadata}
743
+
744
+ main_results = self.chat_agent.batch_remote_chat_with_retry(
745
+ prompts=main_prompts,
746
+ validate_fn=self._make_main_validate_fn(main_metadata),
747
+ max_retry=self.config.ModuleInfo.WorkAnalyzer.graph_keynote_extraction_batch_retry,
748
+ desc="Main extraction for information missing nodes in graph",
749
+ temperature=self.config.ModuleInfo.WorkAnalyzer.graph_graph_keynote_extraction_temperature,
750
+ info_dict=main_info_dict,
751
+ workers=int(
752
+ getattr(
753
+ self.config.ModuleInfo.WorkAnalyzer,
754
+ "fulltext_section_batch_worker",
755
+ 1,
756
+ )
757
+ or 1
758
+ ),
759
+ max_in_flight_tokens=(
760
+ int(
761
+ getattr(
762
+ self.config.ModuleInfo.WorkAnalyzer,
763
+ "fulltext_section_max_in_flight_tokens",
764
+ 800_000,
765
+ )
766
+ or 800_000
767
+ )
768
+ if bool(
769
+ getattr(
770
+ self.config.ModuleInfo.WorkAnalyzer,
771
+ "fulltext_section_packing_enabled",
772
+ True,
773
+ )
774
+ )
775
+ else None
776
+ ),
777
+ strict_input_budget=bool(
778
+ getattr(
779
+ self.config.ModuleInfo.WorkAnalyzer,
780
+ "fulltext_section_packing_enabled",
781
+ True,
782
+ )
783
+ ),
784
+ max_output_tokens=int(
785
+ getattr(
786
+ self.config.ModuleInfo.WorkAnalyzer,
787
+ "fulltext_section_max_output_tokens",
788
+ 16000,
789
+ )
790
+ or 16000
791
+ ),
792
+ )
793
+ main_results = self._merge_section_main_results(main_results, node_ids)
794
+
795
+ # Skip baseline extraction since format_extraction_result doesn't use graph_data
796
+ # (baselines and datasets are only used for graph building, not keynote strings)
797
+ baseline_results = None
798
+
799
+ # ===== Step 2: Aggregate =====
800
+ aggregated = aggregate_results(main_results, baseline_results, source_info)
801
+ final_results = {}
802
+ for result_dict in aggregated:
803
+ final_results[result_dict["node_id"]] = result_dict
804
+
805
+ return final_results
806
+
807
+ def _make_main_validate_fn(self, metadata_list: List[dict]):
808
+ """Create a validate function with closure for main extraction."""
809
+ def validate_fn(response: str, info_dict: dict = None) -> Tuple[bool, dict]:
810
+ # info_dict should contain {'idx': current_index, 'metadata': [...]} or just the metadata dict directly
811
+ if info_dict:
812
+ # If info_dict has 'idx', use it to get the correct metadata
813
+ if 'idx' in info_dict and 'metadata' in info_dict:
814
+ idx = info_dict.get('idx', 0)
815
+ paper_meta = info_dict['metadata'][idx] if idx < len(info_dict['metadata']) else {}
816
+ else:
817
+ # info_dict itself contains the paper metadata (node_id, title, regex_candidates)
818
+ paper_meta = info_dict
819
+ else:
820
+ paper_meta = {}
821
+ return validate_and_parse_main(response, paper_meta)
822
+ return validate_fn
823
+
824
+ def _make_baseline_validate_fn(self, metadata_list: List[dict]):
825
+ """Create a validate function with closure for baseline extraction."""
826
+ def validate_fn(response: str, info_dict: dict = None) -> Tuple[bool, dict]:
827
+ idx = info_dict.get('idx', 0) if info_dict else 0
828
+ paper_meta = metadata_list[idx] if idx < len(metadata_list) else {}
829
+ return validate_and_parse_baseline(response, paper_meta)
830
+ return validate_fn
831
+
832
+ def _build_main_prompts(self, node_ids: List[str], markdowns: List[str]) -> Tuple[List[str], List[dict]]:
833
+ """Build schema-extraction prompts from whole MinerU Markdown sections."""
834
+ prompts = []
835
+ metadata = []
836
+ work_config = self.config.ModuleInfo.WorkAnalyzer
837
+ section_packing_enabled = bool(
838
+ getattr(work_config, "fulltext_section_packing_enabled", True)
839
+ )
840
+ if section_packing_enabled:
841
+ max_output_tokens = int(
842
+ getattr(work_config, "fulltext_section_max_output_tokens", 16000)
843
+ or 16000
844
+ )
845
+ body_budget = derive_effective_body_budget(
846
+ configured_max_body_tokens=int(
847
+ getattr(work_config, "fulltext_section_max_tokens", 512000) or 512000
848
+ ),
849
+ context_window_tokens=int(self.config.APIInfo.llm_max_context_length),
850
+ max_output_tokens=max_output_tokens,
851
+ prompt_reserve_tokens=int(
852
+ getattr(work_config, "fulltext_section_prompt_reserve_tokens", 24000)
853
+ or 24000
854
+ ),
855
+ )
856
+ max_input_tokens = int(self.config.APIInfo.llm_max_context_length) - max_output_tokens
857
+
858
+ for node_id, markdown in zip(node_ids, markdowns):
859
+ title = "Unknown Title"
860
+ for line in markdown.split('\n')[:20]:
861
+ match = re.match(r'^#\s+(.+)$', line.strip())
862
+ if match:
863
+ title = clean_title_latex(match.group(1))
864
+ break
865
+
866
+ if not section_packing_enabled:
867
+ regex_candidates = extract_regex_candidates(markdown)
868
+ system, user = build_main_extraction_prompt(markdown, title, regex_candidates)
869
+ prompts.append(f"{system}\n\n{user}")
870
+ metadata.append(
871
+ {'node_id': node_id, 'title': title, 'regex_candidates': regex_candidates}
872
+ )
873
+ continue
874
+
875
+ dynamic_body_budget = body_budget
876
+ for _ in range(128):
877
+ packing = pack_mineru_markdown_by_complete_sections(
878
+ markdown,
879
+ max_body_tokens=dynamic_body_budget,
880
+ count_tokens=self.chat_agent.estimate_tokens,
881
+ excluded_heading_patterns=DEFAULT_EXCLUDED_SECTION_PATTERNS,
882
+ )
883
+ if packing.status in {"no_sections", "unsplittable_section"}:
884
+ detail = (
885
+ f"section={packing.unsplittable_section.heading!r} "
886
+ f"tokens={packing.unsplittable_section.token_count}"
887
+ if packing.unsplittable_section is not None
888
+ else "no MinerU ## section boundaries"
889
+ )
890
+ self.logger.warning(
891
+ "Skipping unsafe full-text graph extraction for %s: %s.",
892
+ node_id,
893
+ detail,
894
+ )
895
+ break
896
+
897
+ candidate_prompts = []
898
+ candidate_metadata = []
899
+ overflowing = []
900
+ for packet in packing.packets:
901
+ included, omitted = render_packet_outline(packing, packet)
902
+ regex_candidates = extract_regex_candidates(packet.markdown)
903
+ # Even a single packet intentionally omits References and
904
+ # Acknowledgments. Tell the extractor exactly what it sees
905
+ # instead of presenting this safe subset as a full paper.
906
+ section_context = {
907
+ "included_headings": included,
908
+ "omitted_headings": omitted,
909
+ }
910
+ system, user = build_main_extraction_prompt(
911
+ packet.markdown,
912
+ packing.paper_title or title,
913
+ regex_candidates,
914
+ section_context=section_context,
915
+ )
916
+ prompt = f"{system}\n\n{user}"
917
+ prompt_token_count = int(self.chat_agent.estimate_tokens(prompt))
918
+ if prompt_token_count > max_input_tokens:
919
+ overflowing.append((packet, prompt_token_count))
920
+ continue
921
+ candidate_prompts.append(prompt)
922
+ candidate_metadata.append(
923
+ {
924
+ 'node_id': node_id,
925
+ 'title': packing.paper_title or title,
926
+ 'regex_candidates': regex_candidates,
927
+ 'packet_index': packet.index,
928
+ 'packet_count': len(packing.packets),
929
+ }
930
+ )
931
+
932
+ if not overflowing:
933
+ prompts.extend(candidate_prompts)
934
+ metadata.extend(candidate_metadata)
935
+ break
936
+
937
+ packet, prompt_token_count = overflowing[0]
938
+ if len(packet.section_indices) <= 1:
939
+ self.logger.warning(
940
+ "Skipping graph extraction for %s because complete section "
941
+ "%r cannot fit its final prompt (%s > %s tokens).",
942
+ node_id,
943
+ packet.headings[0],
944
+ prompt_token_count,
945
+ max_input_tokens,
946
+ )
947
+ break
948
+
949
+ next_body_budget = min(
950
+ dynamic_body_budget - 1,
951
+ packet.body_token_count - max(1, prompt_token_count - max_input_tokens),
952
+ )
953
+ if next_body_budget <= 0 or next_body_budget >= dynamic_body_budget:
954
+ self.logger.warning(
955
+ "Skipping graph extraction for %s because complete sections "
956
+ "cannot be repacked into its final prompt budget.",
957
+ node_id,
958
+ )
959
+ break
960
+ dynamic_body_budget = next_body_budget
961
+ else:
962
+ self.logger.warning(
963
+ "Stopped complete-section graph repacking for %s after 128 attempts.",
964
+ node_id,
965
+ )
966
+
967
+ return prompts, metadata
968
+
969
+ @staticmethod
970
+ def _merge_section_main_results(main_results: List[dict], node_ids: List[str]) -> List[dict]:
971
+ """Merge per-packet schema extractions without inventing cross-section facts."""
972
+ grouped = {node_id: [] for node_id in node_ids}
973
+ for result in main_results:
974
+ if result and result.get("node_id") in grouped:
975
+ grouped[result["node_id"]].append(result)
976
+
977
+ merged_results = []
978
+ list_fields = (
979
+ "problems",
980
+ "core_contributions",
981
+ "core_relations",
982
+ "components",
983
+ "innovations",
984
+ "limitations",
985
+ "future_work",
986
+ )
987
+ for node_id in node_ids:
988
+ packet_results = grouped[node_id]
989
+ if not packet_results:
990
+ continue
991
+ merged = {"metadata": {}}
992
+ for packet_result in packet_results:
993
+ payload = packet_result.get("result") or {}
994
+ metadata = payload.get("metadata") or {}
995
+ for key, value in metadata.items():
996
+ if key == "structured_summary" and isinstance(value, dict):
997
+ summary = merged["metadata"].setdefault("structured_summary", {})
998
+ for summary_key, summary_value in value.items():
999
+ if summary_value and not summary.get(summary_key):
1000
+ summary[summary_key] = summary_value
1001
+ elif value and not merged["metadata"].get(key):
1002
+ merged["metadata"][key] = value
1003
+ for field in list_fields:
1004
+ values = payload.get(field) or []
1005
+ if not isinstance(values, list):
1006
+ continue
1007
+ existing = merged.setdefault(field, [])
1008
+ seen = {json.dumps(item, sort_keys=True, ensure_ascii=False) for item in existing}
1009
+ for item in values:
1010
+ fingerprint = json.dumps(item, sort_keys=True, ensure_ascii=False)
1011
+ if fingerprint not in seen:
1012
+ existing.append(item)
1013
+ seen.add(fingerprint)
1014
+
1015
+ core_names = [
1016
+ item.get("name")
1017
+ for item in merged.get("core_contributions", [])
1018
+ if isinstance(item, dict) and item.get("name")
1019
+ ]
1020
+ merged_results.append(
1021
+ {
1022
+ "node_id": node_id,
1023
+ "result": merged,
1024
+ "core_names": core_names,
1025
+ "original_title": next(
1026
+ (
1027
+ item.get("original_title")
1028
+ for item in packet_results
1029
+ if item.get("original_title")
1030
+ ),
1031
+ None,
1032
+ ),
1033
+ }
1034
+ )
1035
+ return merged_results
1036
+
1037
+ def _build_baseline_prompts(
1038
+ self,
1039
+ node_ids: List[str],
1040
+ markdowns: List[str],
1041
+ core_names_map: Dict[str, List[str]]
1042
+ ) -> Tuple[List[str], List[dict]]:
1043
+ """Build baseline extraction prompts for papers."""
1044
+ prompts = []
1045
+ metadata = []
1046
+
1047
+ for node_id, markdown in zip(node_ids, markdowns):
1048
+ core_names = core_names_map.get(node_id, [])
1049
+ regex_candidates = extract_regex_candidates(markdown)
1050
+ system, user = build_baseline_extraction_prompt(markdown, core_names, regex_candidates)
1051
+
1052
+ prompts.append(f"{system}\n\n{user}")
1053
+ metadata.append({'node_id': node_id, 'core_names': core_names})
1054
+
1055
+ return prompts, metadata
1056
+
1057
+
1058
+ @hydra.main(config_path="../config", config_name="deep_survey_batch_others_huoshan", version_base=None)
1059
+ def main(config):
1060
+ config = merge_with_default_survey_config(config)
1061
+ retriever = PaperGraphRetriever(config)
1062
+ # retriever.print_debug_table("nodes", limit = 10)
1063
+ ids = ["Robust Training Methods", "Evaluation Protocol", "TWIST", "BUGFARM", "Multilingual Domain Adaptation with Adapters", "VAENAR-TTS",
1064
+ "Uniform-Sum Compression Ratio Sampling", "SAQA", "Two-Branch Swin Block", "MixA-Q"]
1065
+ # id_list = retriever.expand_nodes([retriever.title_to_id("Attention Is All You Need")], max_step = 2)
1066
+ returned = retriever.get_paper_keynote(ids, True)
1067
+ returned = [r for r in returned if not r is None]
1068
+ print(len(returned))
1069
+ print(returned)
1070
+ # retriever.print_all_tables(limit = 1)
1071
+
1072
+
1073
+
1074
+ if __name__ == "__main__":
1075
+ main()