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.
- qwen_sci-0.2.2.dist-info/METADATA +492 -0
- qwen_sci-0.2.2.dist-info/RECORD +549 -0
- qwen_sci-0.2.2.dist-info/WHEEL +4 -0
- qwen_sci-0.2.2.dist-info/entry_points.txt +6 -0
- qwen_sci-0.2.2.dist-info/licenses/LICENSE +21 -0
- src/1.txt +1 -0
- src/__init__.py +1 -0
- src/__main__.py +5 -0
- src/agents/__init__.py +1 -0
- src/agents/experiment_design_agent/__init__.py +268 -0
- src/agents/experiment_design_agent/artifacts.py +747 -0
- src/agents/experiment_design_agent/cache.py +319 -0
- src/agents/experiment_design_agent/completeness.py +130 -0
- src/agents/experiment_design_agent/contracts.py +771 -0
- src/agents/experiment_design_agent/counterexample_analyzer.py +187 -0
- src/agents/experiment_design_agent/design_evidence_paper_screener.py +470 -0
- src/agents/experiment_design_agent/discipline_catalog.py +178 -0
- src/agents/experiment_design_agent/evidence_cards.py +338 -0
- src/agents/experiment_design_agent/evidence_planner.py +452 -0
- src/agents/experiment_design_agent/formal_reasoning_planner.py +884 -0
- src/agents/experiment_design_agent/fulltext_acquisition.py +863 -0
- src/agents/experiment_design_agent/idea_adapter.py +100 -0
- src/agents/experiment_design_agent/idea_intake.py +87 -0
- src/agents/experiment_design_agent/llm_json.py +328 -0
- src/agents/experiment_design_agent/orchestrator.py +1220 -0
- src/agents/experiment_design_agent/reasoning_context.py +183 -0
- src/agents/experiment_design_agent/reasoning_validation.py +546 -0
- src/agents/experiment_design_agent/run.py +481 -0
- src/agents/experiment_design_agent/run_logging.py +292 -0
- src/agents/experiment_design_agent/scope_gate.py +198 -0
- src/agents/experiment_design_agent/study_type_composer.py +1360 -0
- src/agents/experiment_design_agent/survey_evidence.py +1381 -0
- src/agents/experiment_design_agent/template_router.py +195 -0
- src/agents/experiment_design_agent/variable_claim_extractor.py +93 -0
- src/agents/idea_agent/__init__.py +1 -0
- src/agents/idea_agent/agent/__init__.py +6 -0
- src/agents/idea_agent/agent/artifacts.py +308 -0
- src/agents/idea_agent/agent/base.py +122 -0
- src/agents/idea_agent/agent/ligagent.py +488 -0
- src/agents/idea_agent/agent/mcts.py +2060 -0
- src/agents/idea_agent/agent/prompts/__init__.py +48 -0
- src/agents/idea_agent/agent/prompts/advanced_analysis.py +235 -0
- src/agents/idea_agent/agent/prompts/algorithm_alignment.py +55 -0
- src/agents/idea_agent/agent/prompts/algorithm_structuring.py +48 -0
- src/agents/idea_agent/agent/prompts/component_extraction.py +62 -0
- src/agents/idea_agent/agent/prompts/component_novelty_evaluation.py +54 -0
- src/agents/idea_agent/agent/prompts/experiment_findings_extraction.py +39 -0
- src/agents/idea_agent/agent/prompts/idea_fusion.py +295 -0
- src/agents/idea_agent/agent/prompts/idea_introduction.py +30 -0
- src/agents/idea_agent/agent/prompts/idea_result_alignment.py +39 -0
- src/agents/idea_agent/agent/prompts/input_interpreter.py +52 -0
- src/agents/idea_agent/agent/prompts/keynote_ops.py +78 -0
- src/agents/idea_agent/agent/prompts/mcts_evaluation.py +235 -0
- src/agents/idea_agent/agent/prompts/mcts_generation.py +140 -0
- src/agents/idea_agent/agent/prompts/mechanism_commit_query.py +99 -0
- src/agents/idea_agent/agent/prompts/memory_selection.py +0 -0
- src/agents/idea_agent/agent/prompts/prompt_modes.py +16 -0
- src/agents/idea_agent/agent/prompts/rag_query.py +34 -0
- src/agents/idea_agent/agent/prompts/re_analysis_replan.py +48 -0
- src/agents/idea_agent/agent/prompts/reference_grounding.py +34 -0
- src/agents/idea_agent/agent/prompts/root_domain_classification.py +32 -0
- src/agents/idea_agent/agent/prompts/scientific_debate.py +88 -0
- src/agents/idea_agent/agent/prompts/scientific_materialization.py +45 -0
- src/agents/idea_agent/agent/prompts/skill_instantiation.py +193 -0
- src/agents/idea_agent/agent/prompts/theory_transfer_query.py +93 -0
- src/agents/idea_agent/agent/prompts/topic_background.py +15 -0
- src/agents/idea_agent/agent/skills/DEFAULT_SKILL_TEMPLATES.json +263 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/alternative-path-contrast/references/contrast_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/feedback-closed-loop/references/mechanism_cards.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/hierarchical-decomposition/references/hierarchy_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/mechanism-commit-innovation/references/mechanism_commit_patterns.md +24 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/multi-scale-coordinator/references/coordination_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/SKILL.md +41 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/speculative-execution-with-repair/references/speculation_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/SKILL.md +39 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/surgical-modularity/references/modularity_patterns.md +22 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/SKILL.md +40 -0
- src/agents/idea_agent/agent/skills/edit_operator_skills/theory-transfer-injection/references/transfer_patterns.md +24 -0
- src/agents/idea_agent/example/idea_result.json +900 -0
- src/agents/idea_agent/run.py +276 -0
- src/agents/idea_agent/tests/test_advanced_analysis_contract.py +90 -0
- src/agents/idea_agent/tests/test_gap_hypothesis_bridge.py +166 -0
- src/agents/idea_agent/tests/test_idea_debate.py +480 -0
- src/agents/idea_agent/tests/test_idea_result_v5.py +99 -0
- src/agents/idea_agent/tests/test_idea_synthesis.py +92 -0
- src/agents/idea_agent/tests/test_mature_idea_sources.py +27 -0
- src/agents/idea_agent/tests/test_multimodal_idea_integration.py +386 -0
- src/agents/idea_agent/tests/test_profile_materialization_and_scoring.py +219 -0
- src/agents/idea_agent/tests/test_profile_native_skill_rendering.py +122 -0
- src/agents/idea_agent/tests/test_result_persistence.py +183 -0
- src/agents/idea_agent/tests/test_scientific_intervention_profiles.py +241 -0
- src/agents/idea_agent/tests/test_scientific_rubric_and_prompts.py +559 -0
- src/agents/idea_agent/tests/test_strict_json_object_responses.py +48 -0
- src/agents/idea_agent/utils/core/__init__.py +1 -0
- src/agents/idea_agent/utils/core/ablation_inputs.py +60 -0
- src/agents/idea_agent/utils/core/chat_errors.py +95 -0
- src/agents/idea_agent/utils/core/chat_router.py +130 -0
- src/agents/idea_agent/utils/core/chat_transport.py +188 -0
- src/agents/idea_agent/utils/core/config_loader.py +166 -0
- src/agents/idea_agent/utils/core/json_utils.py +28 -0
- src/agents/idea_agent/utils/core/logger.py +287 -0
- src/agents/idea_agent/utils/core/progress.py +58 -0
- src/agents/idea_agent/utils/core/response_parsing.py +57 -0
- src/agents/idea_agent/utils/core/run_inputs.py +198 -0
- src/agents/idea_agent/utils/mcts/__init__.py +1 -0
- src/agents/idea_agent/utils/mcts/component_novelty.py +342 -0
- src/agents/idea_agent/utils/mcts/defect_registry.py +302 -0
- src/agents/idea_agent/utils/mcts/idea_routes.py +61 -0
- src/agents/idea_agent/utils/mcts/idea_taste_presets.py +312 -0
- src/agents/idea_agent/utils/mcts/mcts_helpers.py +738 -0
- src/agents/idea_agent/utils/mcts/mcts_runtime.py +3000 -0
- src/agents/idea_agent/utils/mcts/scientific_intervention_ontology.py +1561 -0
- src/agents/idea_agent/utils/mcts/scientific_rubric.py +211 -0
- src/agents/idea_agent/utils/mcts/skill_parsing.py +92 -0
- src/agents/idea_agent/utils/papers/__init__.py +1 -0
- src/agents/idea_agent/utils/papers/paper_graph_vector_store.py +499 -0
- src/agents/idea_agent/utils/papers/paper_repository.py +372 -0
- src/agents/idea_agent/utils/prompting/__init__.py +1 -0
- src/agents/idea_agent/utils/prompting/prompt_views.py +637 -0
- src/agents/idea_agent/utils/workflow/__init__.py +1 -0
- src/agents/idea_agent/utils/workflow/advanced_analysis_contract.py +244 -0
- src/agents/idea_agent/utils/workflow/gap_hypothesis_bridge.py +461 -0
- src/agents/idea_agent/utils/workflow/idea_contract.py +426 -0
- src/agents/idea_agent/utils/workflow/idea_debate.py +1238 -0
- src/agents/idea_agent/utils/workflow/idea_diversity.py +163 -0
- src/agents/idea_agent/utils/workflow/idea_fusion.py +889 -0
- src/agents/idea_agent/utils/workflow/idea_helpers.py +478 -0
- src/agents/idea_agent/utils/workflow/idea_portfolio.py +422 -0
- src/agents/idea_agent/utils/workflow/idea_synthesis.py +482 -0
- src/agents/idea_agent/utils/workflow/ligagent_flow.py +581 -0
- src/agents/idea_agent/utils/workflow/ligagent_handlers.py +1836 -0
- src/agents/idea_agent/utils/workflow/ligagent_helpers.py +1331 -0
- src/agents/idea_agent/utils/workflow/ligagent_utils.py +322 -0
- src/agents/idea_agent/utils/workflow/mature_idea_sources.py +648 -0
- src/agents/idea_agent/utils/workflow/multimodal_data_anchoring.py +578 -0
- src/agents/idea_agent/utils/workflow/result_persistence.py +774 -0
- src/agents/idea_agent/utils/workflow/stage_contract.py +76 -0
- src/agents/idea_agent/utils/workflow/workflow_runtime.py +288 -0
- src/agents/research_plan_author/__init__.py +50 -0
- src/agents/research_plan_author/artifacts.py +270 -0
- src/agents/research_plan_author/authoring_blueprint.py +1246 -0
- src/agents/research_plan_author/bibtex_renderer.py +252 -0
- src/agents/research_plan_author/contract_repair.py +131 -0
- src/agents/research_plan_author/contracts.py +745 -0
- src/agents/research_plan_author/cross_section_editor.py +232 -0
- src/agents/research_plan_author/document_quality.py +987 -0
- src/agents/research_plan_author/idea_evolution.py +223 -0
- src/agents/research_plan_author/input_loader.py +76 -0
- src/agents/research_plan_author/latex_compiler.py +260 -0
- src/agents/research_plan_author/latex_safety.py +159 -0
- src/agents/research_plan_author/llm_json.py +74 -0
- src/agents/research_plan_author/markdown_renderer.py +275 -0
- src/agents/research_plan_author/pdf_validator.py +149 -0
- src/agents/research_plan_author/render.py +166 -0
- src/agents/research_plan_author/render_artifacts.py +187 -0
- src/agents/research_plan_author/run.py +765 -0
- src/agents/research_plan_author/run_logging.py +209 -0
- src/agents/research_plan_author/section_cache.py +184 -0
- src/agents/research_plan_author/section_composer.py +1764 -0
- src/agents/research_plan_author/section_router.py +209 -0
- src/agents/research_plan_author/semantic_validator.py +254 -0
- src/agents/research_plan_author/source_bundle.py +46 -0
- src/agents/research_plan_author/source_registry.py +208 -0
- src/agents/research_plan_author/survey_source_loader.py +137 -0
- src/agents/research_plan_author/template_adapter.py +173 -0
- src/agents/research_plan_author/template_profile.py +204 -0
- src/agents/research_plan_author/tex_renderer.py +496 -0
- src/agents/research_plan_author/theory_presentation.py +134 -0
- src/agents/research_plan_author/theory_spine.py +703 -0
- src/agents/survey_agent/Dockerfile +54 -0
- src/agents/survey_agent/README.md +350 -0
- src/agents/survey_agent/config/deep_survey.yaml +217 -0
- src/agents/survey_agent/config/deep_survey_batch.yaml +176 -0
- src/agents/survey_agent/config/deep_survey_fast.yaml +183 -0
- src/agents/survey_agent/config/evaluate_baseline.yaml +197 -0
- src/agents/survey_agent/config/outcomeRAG.yaml +159 -0
- src/agents/survey_agent/env_report.md +348 -0
- src/agents/survey_agent/modules/__init__.py +0 -0
- src/agents/survey_agent/modules/code_collector.py +2194 -0
- src/agents/survey_agent/modules/code_report_generator.py +1282 -0
- src/agents/survey_agent/modules/data_manager.py +1713 -0
- src/agents/survey_agent/modules/database.py +167 -0
- src/agents/survey_agent/modules/fulltext_download_cache.py +462 -0
- src/agents/survey_agent/modules/fulltext_http.py +134 -0
- src/agents/survey_agent/modules/fulltext_resolution.py +749 -0
- src/agents/survey_agent/modules/judge.py +434 -0
- src/agents/survey_agent/modules/outcome_RAG.py +236 -0
- src/agents/survey_agent/modules/paper_graph_retriever.py +1075 -0
- src/agents/survey_agent/modules/pe.py +1729 -0
- src/agents/survey_agent/modules/pseudo_creator.py +782 -0
- src/agents/survey_agent/modules/pseudo_reviser.py +537 -0
- src/agents/survey_agent/modules/refine_agent.py +1952 -0
- src/agents/survey_agent/modules/survey_generator.py +5691 -0
- src/agents/survey_agent/modules/survey_visualizer.py +1817 -0
- src/agents/survey_agent/modules/visual_prompts.py +327 -0
- src/agents/survey_agent/modules/work_analyzer.py +2750 -0
- src/agents/survey_agent/modules/work_collector.py +5649 -0
- src/agents/survey_agent/scripts/baseline_evaluation.py +411 -0
- src/agents/survey_agent/scripts/run_deep_survey.py +224 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter.py +407 -0
- src/agents/survey_agent/scripts/run_deep_survey_adapter_with_input.py +409 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch.py +228 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_arg.py +243 -0
- src/agents/survey_agent/scripts/run_deep_survey_batch_settings.py +218 -0
- src/agents/survey_agent/scripts/run_keynotes_gen.py +128 -0
- src/agents/survey_agent/scripts/run_work_clusters.py +140 -0
- src/agents/survey_agent/utils/__init__.py +0 -0
- src/agents/survey_agent/utils/ablation_utils.py +317 -0
- src/agents/survey_agent/utils/api_call.py +2518 -0
- src/agents/survey_agent/utils/chat_utils.py +82 -0
- src/agents/survey_agent/utils/config_utils.py +70 -0
- src/agents/survey_agent/utils/convert_to_md.py +29 -0
- src/agents/survey_agent/utils/err_info.py +24 -0
- src/agents/survey_agent/utils/file_utils.py +176 -0
- src/agents/survey_agent/utils/gpu_utils.py +145 -0
- src/agents/survey_agent/utils/graph_visualizer.py +279 -0
- src/agents/survey_agent/utils/html_utils.py +181 -0
- src/agents/survey_agent/utils/latex_generator_intra_cluster.py +529 -0
- src/agents/survey_agent/utils/latex_generator_relation_table.py +193 -0
- src/agents/survey_agent/utils/mineru_section_packer.py +334 -0
- src/agents/survey_agent/utils/mineru_utils.py +396 -0
- src/agents/survey_agent/utils/paper.py +144 -0
- src/agents/survey_agent/utils/read_yaml.py +15 -0
- src/agents/survey_agent/utils/repo_utils.py +167 -0
- src/agents/survey_agent/utils/rich_logger.py +77 -0
- src/agents/survey_agent/utils/step2v2.py +874 -0
- src/agents/survey_agent/utils/step2v2_extractor.py +856 -0
- src/agents/survey_agent/utils/topic_survey_storage.py +309 -0
- src/agents/survey_agent/utils/utils.py +118 -0
- src/agents/survey_agent/utils/work.py +2 -0
- src/cli.py +1611 -0
- src/config/__init__.py +213 -0
- src/config/default.yaml +1122 -0
- src/harness/LICENSE +21 -0
- src/harness/VENDORED.md +14 -0
- src/harness/src/__init__.py +0 -0
- src/harness/src/openharness/__init__.py +0 -0
- src/harness/src/openharness/__main__.py +6 -0
- src/harness/src/openharness/api/__init__.py +21 -0
- src/harness/src/openharness/api/client.py +281 -0
- src/harness/src/openharness/api/codex_client.py +407 -0
- src/harness/src/openharness/api/copilot_auth.py +240 -0
- src/harness/src/openharness/api/copilot_client.py +134 -0
- src/harness/src/openharness/api/errors.py +19 -0
- src/harness/src/openharness/api/openai_client.py +577 -0
- src/harness/src/openharness/api/provider.py +186 -0
- src/harness/src/openharness/api/registry.py +437 -0
- src/harness/src/openharness/api/usage.py +17 -0
- src/harness/src/openharness/auth/__init__.py +29 -0
- src/harness/src/openharness/auth/external.py +610 -0
- src/harness/src/openharness/auth/flows.py +189 -0
- src/harness/src/openharness/auth/manager.py +482 -0
- src/harness/src/openharness/auth/storage.py +269 -0
- src/harness/src/openharness/autopilot/__init__.py +23 -0
- src/harness/src/openharness/autopilot/service.py +2239 -0
- src/harness/src/openharness/autopilot/types.py +91 -0
- src/harness/src/openharness/bridge/__init__.py +20 -0
- src/harness/src/openharness/bridge/manager.py +106 -0
- src/harness/src/openharness/bridge/session_runner.py +46 -0
- src/harness/src/openharness/bridge/types.py +37 -0
- src/harness/src/openharness/bridge/work_secret.py +41 -0
- src/harness/src/openharness/channels/UPSTREAM +6 -0
- src/harness/src/openharness/channels/__init__.py +22 -0
- src/harness/src/openharness/channels/adapter.py +131 -0
- src/harness/src/openharness/channels/bus/__init__.py +6 -0
- src/harness/src/openharness/channels/bus/events.py +38 -0
- src/harness/src/openharness/channels/bus/queue.py +44 -0
- src/harness/src/openharness/channels/impl/__init__.py +6 -0
- src/harness/src/openharness/channels/impl/base.py +141 -0
- src/harness/src/openharness/channels/impl/dingtalk.py +445 -0
- src/harness/src/openharness/channels/impl/discord.py +311 -0
- src/harness/src/openharness/channels/impl/email.py +410 -0
- src/harness/src/openharness/channels/impl/feishu.py +1342 -0
- src/harness/src/openharness/channels/impl/manager.py +257 -0
- src/harness/src/openharness/channels/impl/matrix.py +700 -0
- src/harness/src/openharness/channels/impl/mochat.py +897 -0
- src/harness/src/openharness/channels/impl/qq.py +141 -0
- src/harness/src/openharness/channels/impl/slack.py +284 -0
- src/harness/src/openharness/channels/impl/telegram.py +525 -0
- src/harness/src/openharness/channels/impl/whatsapp.py +159 -0
- src/harness/src/openharness/cli.py +2551 -0
- src/harness/src/openharness/commands/__init__.py +21 -0
- src/harness/src/openharness/commands/registry.py +2783 -0
- src/harness/src/openharness/config/__init__.py +34 -0
- src/harness/src/openharness/config/paths.py +160 -0
- src/harness/src/openharness/config/schema.py +119 -0
- src/harness/src/openharness/config/settings.py +1104 -0
- src/harness/src/openharness/coordinator/__init__.py +12 -0
- src/harness/src/openharness/coordinator/agent_definitions.py +975 -0
- src/harness/src/openharness/coordinator/coordinator_mode.py +520 -0
- src/harness/src/openharness/engine/__init__.py +80 -0
- src/harness/src/openharness/engine/cost_tracker.py +24 -0
- src/harness/src/openharness/engine/messages.py +222 -0
- src/harness/src/openharness/engine/query.py +1188 -0
- src/harness/src/openharness/engine/query_engine.py +313 -0
- src/harness/src/openharness/engine/stream_events.py +90 -0
- src/harness/src/openharness/hooks/__init__.py +50 -0
- src/harness/src/openharness/hooks/events.py +20 -0
- src/harness/src/openharness/hooks/executor.py +242 -0
- src/harness/src/openharness/hooks/hot_reload.py +31 -0
- src/harness/src/openharness/hooks/loader.py +68 -0
- src/harness/src/openharness/hooks/schemas.py +66 -0
- src/harness/src/openharness/hooks/types.py +38 -0
- src/harness/src/openharness/keybindings/__init__.py +14 -0
- src/harness/src/openharness/keybindings/default_bindings.py +11 -0
- src/harness/src/openharness/keybindings/loader.py +22 -0
- src/harness/src/openharness/keybindings/parser.py +18 -0
- src/harness/src/openharness/keybindings/resolver.py +13 -0
- src/harness/src/openharness/mcp/__init__.py +79 -0
- src/harness/src/openharness/mcp/client.py +298 -0
- src/harness/src/openharness/mcp/config.py +16 -0
- src/harness/src/openharness/mcp/types.py +76 -0
- src/harness/src/openharness/memory/__init__.py +25 -0
- src/harness/src/openharness/memory/agent.py +91 -0
- src/harness/src/openharness/memory/manager.py +183 -0
- src/harness/src/openharness/memory/memdir.py +52 -0
- src/harness/src/openharness/memory/migrate.py +137 -0
- src/harness/src/openharness/memory/paths.py +22 -0
- src/harness/src/openharness/memory/relevance.py +145 -0
- src/harness/src/openharness/memory/scan.py +116 -0
- src/harness/src/openharness/memory/schema.py +443 -0
- src/harness/src/openharness/memory/search.py +71 -0
- src/harness/src/openharness/memory/team.py +90 -0
- src/harness/src/openharness/memory/types.py +33 -0
- src/harness/src/openharness/memory/usage.py +153 -0
- src/harness/src/openharness/output_styles/__init__.py +5 -0
- src/harness/src/openharness/output_styles/loader.py +42 -0
- src/harness/src/openharness/permissions/__init__.py +26 -0
- src/harness/src/openharness/permissions/checker.py +200 -0
- src/harness/src/openharness/permissions/modes.py +13 -0
- src/harness/src/openharness/personalization/__init__.py +6 -0
- src/harness/src/openharness/personalization/extractor.py +138 -0
- src/harness/src/openharness/personalization/rules.py +64 -0
- src/harness/src/openharness/personalization/session_hook.py +65 -0
- src/harness/src/openharness/platforms.py +86 -0
- src/harness/src/openharness/plugins/__init__.py +53 -0
- src/harness/src/openharness/plugins/bundled/__init__.py +0 -0
- src/harness/src/openharness/plugins/installer.py +39 -0
- src/harness/src/openharness/plugins/loader.py +730 -0
- src/harness/src/openharness/plugins/schemas.py +24 -0
- src/harness/src/openharness/plugins/types.py +59 -0
- src/harness/src/openharness/prompts/__init__.py +14 -0
- src/harness/src/openharness/prompts/claudemd.py +48 -0
- src/harness/src/openharness/prompts/context.py +187 -0
- src/harness/src/openharness/prompts/environment.py +135 -0
- src/harness/src/openharness/prompts/system_prompt.py +109 -0
- src/harness/src/openharness/sandbox/Dockerfile +6 -0
- src/harness/src/openharness/sandbox/__init__.py +33 -0
- src/harness/src/openharness/sandbox/adapter.py +148 -0
- src/harness/src/openharness/sandbox/docker_backend.py +232 -0
- src/harness/src/openharness/sandbox/docker_image.py +103 -0
- src/harness/src/openharness/sandbox/path_validator.py +37 -0
- src/harness/src/openharness/sandbox/session.py +63 -0
- src/harness/src/openharness/services/__init__.py +30 -0
- src/harness/src/openharness/services/autodream/__init__.py +34 -0
- src/harness/src/openharness/services/autodream/backup.py +104 -0
- src/harness/src/openharness/services/autodream/lock.py +138 -0
- src/harness/src/openharness/services/autodream/prompt.py +128 -0
- src/harness/src/openharness/services/autodream/service.py +313 -0
- src/harness/src/openharness/services/compact/__init__.py +1725 -0
- src/harness/src/openharness/services/cron.py +137 -0
- src/harness/src/openharness/services/cron_scheduler.py +595 -0
- src/harness/src/openharness/services/lsp/__init__.py +216 -0
- src/harness/src/openharness/services/memory_extract/__init__.py +261 -0
- src/harness/src/openharness/services/oauth/__init__.py +0 -0
- src/harness/src/openharness/services/session_backend.py +97 -0
- src/harness/src/openharness/services/session_memory/__init__.py +139 -0
- src/harness/src/openharness/services/session_storage.py +230 -0
- src/harness/src/openharness/services/token_estimation.py +15 -0
- src/harness/src/openharness/services/tool_outputs.py +55 -0
- src/harness/src/openharness/skills/__init__.py +44 -0
- src/harness/src/openharness/skills/_frontmatter.py +97 -0
- src/harness/src/openharness/skills/bundled/__init__.py +75 -0
- src/harness/src/openharness/skills/bundled/content/commit.md +25 -0
- src/harness/src/openharness/skills/bundled/content/debug.md +25 -0
- src/harness/src/openharness/skills/bundled/content/diagnose.md +35 -0
- src/harness/src/openharness/skills/bundled/content/plan.md +39 -0
- src/harness/src/openharness/skills/bundled/content/review.md +33 -0
- src/harness/src/openharness/skills/bundled/content/simplify.md +26 -0
- src/harness/src/openharness/skills/bundled/content/skill-creator.md +175 -0
- src/harness/src/openharness/skills/bundled/content/test.md +38 -0
- src/harness/src/openharness/skills/loader.py +229 -0
- src/harness/src/openharness/skills/registry.py +29 -0
- src/harness/src/openharness/skills/types.py +24 -0
- src/harness/src/openharness/state/__init__.py +6 -0
- src/harness/src/openharness/state/app_state.py +30 -0
- src/harness/src/openharness/state/store.py +40 -0
- src/harness/src/openharness/swarm/__init__.py +70 -0
- src/harness/src/openharness/swarm/in_process.py +693 -0
- src/harness/src/openharness/swarm/lockfile.py +24 -0
- src/harness/src/openharness/swarm/mailbox.py +522 -0
- src/harness/src/openharness/swarm/permission_sync.py +1168 -0
- src/harness/src/openharness/swarm/registry.py +410 -0
- src/harness/src/openharness/swarm/spawn_utils.py +229 -0
- src/harness/src/openharness/swarm/subprocess_backend.py +171 -0
- src/harness/src/openharness/swarm/team_lifecycle.py +910 -0
- src/harness/src/openharness/swarm/types.py +398 -0
- src/harness/src/openharness/swarm/worktree.py +315 -0
- src/harness/src/openharness/tasks/__init__.py +18 -0
- src/harness/src/openharness/tasks/local_agent_task.py +28 -0
- src/harness/src/openharness/tasks/local_shell_task.py +17 -0
- src/harness/src/openharness/tasks/manager.py +475 -0
- src/harness/src/openharness/tasks/stop_task.py +11 -0
- src/harness/src/openharness/tasks/types.py +32 -0
- src/harness/src/openharness/themes/__init__.py +21 -0
- src/harness/src/openharness/themes/builtin.py +89 -0
- src/harness/src/openharness/themes/loader.py +55 -0
- src/harness/src/openharness/themes/schema.py +54 -0
- src/harness/src/openharness/tools/__init__.py +107 -0
- src/harness/src/openharness/tools/agent_tool.py +141 -0
- src/harness/src/openharness/tools/ask_user_question_tool.py +46 -0
- src/harness/src/openharness/tools/base.py +80 -0
- src/harness/src/openharness/tools/bash_tool.py +259 -0
- src/harness/src/openharness/tools/brief_tool.py +33 -0
- src/harness/src/openharness/tools/config_tool.py +54 -0
- src/harness/src/openharness/tools/cron_create_tool.py +105 -0
- src/harness/src/openharness/tools/cron_delete_tool.py +32 -0
- src/harness/src/openharness/tools/cron_list_tool.py +69 -0
- src/harness/src/openharness/tools/cron_toggle_tool.py +37 -0
- src/harness/src/openharness/tools/enter_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/enter_worktree_tool.py +80 -0
- src/harness/src/openharness/tools/exit_plan_mode_tool.py +28 -0
- src/harness/src/openharness/tools/exit_worktree_tool.py +42 -0
- src/harness/src/openharness/tools/file_edit_tool.py +95 -0
- src/harness/src/openharness/tools/file_read_tool.py +72 -0
- src/harness/src/openharness/tools/file_write_tool.py +87 -0
- src/harness/src/openharness/tools/glob_tool.py +175 -0
- src/harness/src/openharness/tools/grep_tool.py +374 -0
- src/harness/src/openharness/tools/image_generation_tool.py +447 -0
- src/harness/src/openharness/tools/image_to_text_tool.py +237 -0
- src/harness/src/openharness/tools/list_mcp_resources_tool.py +36 -0
- src/harness/src/openharness/tools/lsp_tool.py +154 -0
- src/harness/src/openharness/tools/mcp_auth_tool.py +71 -0
- src/harness/src/openharness/tools/mcp_tool.py +72 -0
- src/harness/src/openharness/tools/notebook_edit_tool.py +97 -0
- src/harness/src/openharness/tools/read_mcp_resource_tool.py +38 -0
- src/harness/src/openharness/tools/remote_trigger_tool.py +74 -0
- src/harness/src/openharness/tools/send_message_tool.py +58 -0
- src/harness/src/openharness/tools/skill_tool.py +43 -0
- src/harness/src/openharness/tools/sleep_tool.py +32 -0
- src/harness/src/openharness/tools/task_create_tool.py +56 -0
- src/harness/src/openharness/tools/task_get_tool.py +33 -0
- src/harness/src/openharness/tools/task_list_tool.py +35 -0
- src/harness/src/openharness/tools/task_output_tool.py +35 -0
- src/harness/src/openharness/tools/task_stop_tool.py +30 -0
- src/harness/src/openharness/tools/task_update_tool.py +50 -0
- src/harness/src/openharness/tools/team_create_tool.py +31 -0
- src/harness/src/openharness/tools/team_delete_tool.py +30 -0
- src/harness/src/openharness/tools/todo_write_tool.py +46 -0
- src/harness/src/openharness/tools/tool_search_tool.py +38 -0
- src/harness/src/openharness/tools/web_fetch_tool.py +117 -0
- src/harness/src/openharness/tools/web_search_tool.py +119 -0
- src/harness/src/openharness/ui/__init__.py +5 -0
- src/harness/src/openharness/ui/app.py +320 -0
- src/harness/src/openharness/ui/backend_host.py +941 -0
- src/harness/src/openharness/ui/coordinator_drain.py +198 -0
- src/harness/src/openharness/ui/input.py +32 -0
- src/harness/src/openharness/ui/output.py +265 -0
- src/harness/src/openharness/ui/permission_dialog.py +14 -0
- src/harness/src/openharness/ui/protocol.py +247 -0
- src/harness/src/openharness/ui/react_launcher.py +179 -0
- src/harness/src/openharness/ui/runtime.py +799 -0
- src/harness/src/openharness/ui/textual_app.py +495 -0
- src/harness/src/openharness/utils/__init__.py +0 -0
- src/harness/src/openharness/utils/file_lock.py +86 -0
- src/harness/src/openharness/utils/fs.py +98 -0
- src/harness/src/openharness/utils/helpers.py +77 -0
- src/harness/src/openharness/utils/network_guard.py +340 -0
- src/harness/src/openharness/utils/shell.py +147 -0
- src/harness/src/openharness/vim/__init__.py +5 -0
- src/harness/src/openharness/vim/transitions.py +8 -0
- src/harness/src/openharness/voice/__init__.py +7 -0
- src/harness/src/openharness/voice/keyterms.py +10 -0
- src/harness/src/openharness/voice/stream_stt.py +8 -0
- src/harness/src/openharness/voice/voice_mode.py +44 -0
- src/llm/__init__.py +29 -0
- src/llm/image_generation.py +402 -0
- src/llm/provider_registry.py +419 -0
- src/llm/runtime_env.py +64 -0
- src/llm/structured_output.py +131 -0
- src/llm/vision.py +198 -0
- src/memory/__init__.py +2 -0
- src/memory/api/__init__.py +1 -0
- src/memory/api/base_memory_system_api.py +98 -0
- src/memory/api/base_symbolic_memory_system_api.py +190 -0
- src/memory/api/base_vector_memory_system_api.py +116 -0
- src/memory/api/faiss_memory_system_api.py +228 -0
- src/memory/api/slot_process_api.py +827 -0
- src/memory/api/symbolic_memory_system_api.py +572 -0
- src/memory/memory_system/__init__.py +48 -0
- src/memory/memory_system/component_taxonomy.py +430 -0
- src/memory/memory_system/llm.py +437 -0
- src/memory/memory_system/models.py +168 -0
- src/memory/memory_system/user_prompt.py +418 -0
- src/memory/memory_system/utils.py +282 -0
- src/memory/memory_system/vectorstore.py +311 -0
- src/memory/memory_system/working_slot.py +99 -0
- src/pipeline/__init__.py +129 -0
- src/pipeline/contracts.py +531 -0
- src/pipeline/discipline_taxonomy.py +916 -0
- src/pipeline/evidence_coverage_ledger.py +1150 -0
- src/pipeline/evidence_refinement.py +620 -0
- src/pipeline/experiment_to_symbolic.py +137 -0
- src/pipeline/multimodal_evidence/__init__.py +28 -0
- src/pipeline/multimodal_evidence/capabilities.py +107 -0
- src/pipeline/multimodal_evidence/claims.py +135 -0
- src/pipeline/multimodal_evidence/contract.py +291 -0
- src/pipeline/multimodal_evidence/data_sh_compiler.py +505 -0
- src/pipeline/multimodal_evidence/handoff_binding.py +135 -0
- src/pipeline/multimodal_evidence/manifest.py +254 -0
- src/pipeline/multimodal_evidence/native_analysis.py +479 -0
- src/pipeline/multimodal_evidence/perception.py +277 -0
- src/pipeline/multimodal_evidence/query_binding.py +65 -0
- src/pipeline/multimodal_evidence/rendering.py +120 -0
- src/pipeline/multimodal_evidence/safety.py +42 -0
- src/pipeline/multimodal_evidence/sampling.py +69 -0
- src/pipeline/multimodal_evidence/service.py +148 -0
- src/pipeline/multimodal_evidence/survey_integration.py +460 -0
- src/pipeline/paper_identity.py +52 -0
- src/pipeline/research_design_inventory.py +338 -0
- src/pipeline/research_domain_resolution.py +669 -0
- src/pipeline/research_identity.py +548 -0
- src/pipeline/research_question_contract.py +646 -0
- src/pipeline/retrieval_lanes.py +889 -0
- src/pipeline/run_loop.py +862 -0
- src/pipeline/science_events.py +30 -0
- src/pipeline/science_manifests.py +602 -0
- src/pipeline/science_run.py +918 -0
- src/pipeline/science_stages.py +709 -0
- src/pipeline/science_workflow.py +807 -0
- src/pipeline/sh_cluster_projection.py +306 -0
- src/pipeline/sh_graph_provenance.py +648 -0
- src/pipeline/subhypothesis_decomposition.py +392 -0
- src/pipeline/subhypothesis_evidence.py +856 -0
- src/pipeline/survey_evidence_plan.py +858 -0
- src/pipeline/survey_gap_adjudication.py +647 -0
- src/pipeline/survey_gap_candidates.py +840 -0
- src/pipeline/survey_gap_ledger.py +683 -0
- src/pipeline/survey_gap_triage.py +401 -0
- src/pipeline/survey_handoff_persistence.py +481 -0
- src/pipeline/survey_handoff_projection.py +457 -0
- src/pipeline/survey_idea_handoff.py +1112 -0
- src/pipeline/survey_idea_loader.py +289 -0
- 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()
|