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,277 @@
|
|
|
1
|
+
"""Constrained qwen3-vl-plus perception for explicitly supplied data only."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections import OrderedDict
|
|
7
|
+
from collections.abc import Callable, Mapping, Sequence
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from src.llm.vision import QwenVisionClient, resolve_vision_settings
|
|
11
|
+
|
|
12
|
+
from .contract import MultimodalInputError, MultimodalInputSpec
|
|
13
|
+
from .rendering import PreviewRenderError, render_png_preview, supports_remote_preview
|
|
14
|
+
from .safety import violates_noncausal_policy
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
QWEN3_VL_PLUS = "qwen3-vl-plus"
|
|
18
|
+
_FOCUSES = ("mechanism", "measurement", "boundary", "contradiction", "theory")
|
|
19
|
+
OBSERVATION_SCHEMA: dict[str, Any] = {
|
|
20
|
+
"type": "object",
|
|
21
|
+
"properties": {
|
|
22
|
+
"finding": {"type": "string", "minLength": 4, "maxLength": 700},
|
|
23
|
+
"candidate_explanation": {"type": "string", "minLength": 4, "maxLength": 700},
|
|
24
|
+
"alternative_explanations": {
|
|
25
|
+
"type": "array",
|
|
26
|
+
"minItems": 1,
|
|
27
|
+
"maxItems": 3,
|
|
28
|
+
"items": {"type": "string", "minLength": 3, "maxLength": 400},
|
|
29
|
+
},
|
|
30
|
+
"discriminating_prediction": {"type": "string", "minLength": 4, "maxLength": 500},
|
|
31
|
+
"falsifier": {"type": "string", "minLength": 4, "maxLength": 500},
|
|
32
|
+
"claim_limits": {"type": "string", "minLength": 8, "maxLength": 500},
|
|
33
|
+
"confidence": {"type": "string", "enum": ["low", "medium", "high"]},
|
|
34
|
+
"focus": {"type": "string", "enum": list(_FOCUSES)},
|
|
35
|
+
},
|
|
36
|
+
"required": [
|
|
37
|
+
"finding",
|
|
38
|
+
"candidate_explanation",
|
|
39
|
+
"alternative_explanations",
|
|
40
|
+
"discriminating_prediction",
|
|
41
|
+
"falsifier",
|
|
42
|
+
"claim_limits",
|
|
43
|
+
"confidence",
|
|
44
|
+
"focus",
|
|
45
|
+
],
|
|
46
|
+
"additionalProperties": False,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_remote_perception(
|
|
51
|
+
*,
|
|
52
|
+
input_spec: MultimodalInputSpec,
|
|
53
|
+
local_context: Mapping[str, Any],
|
|
54
|
+
config: Any,
|
|
55
|
+
max_calls: int,
|
|
56
|
+
max_preview_pixels: int,
|
|
57
|
+
max_preview_bytes: int,
|
|
58
|
+
client_factory: Callable[..., Any] | None = None,
|
|
59
|
+
) -> tuple[list[dict[str, Any]], list[str], dict[str, str]]:
|
|
60
|
+
"""Return validated observation cards without retaining previews or raw replies."""
|
|
61
|
+
|
|
62
|
+
if not bool(local_context.get("remote_perception_authorized")):
|
|
63
|
+
return [], [], {}
|
|
64
|
+
if max_calls < 1:
|
|
65
|
+
raise MultimodalInputError("max_vl_calls must be at least 1 when remote perception is enabled.")
|
|
66
|
+
settings = _resolve_pinned_vision_settings(config)
|
|
67
|
+
factory = client_factory or QwenVisionClient
|
|
68
|
+
client = factory(
|
|
69
|
+
model=settings["model"],
|
|
70
|
+
provider=settings["provider"],
|
|
71
|
+
api_key=settings["api_key"],
|
|
72
|
+
base_url=settings["base_url"],
|
|
73
|
+
timeout=settings["timeout"],
|
|
74
|
+
config=config,
|
|
75
|
+
)
|
|
76
|
+
records_by_id = {record.record_id: record for record in input_spec.records}
|
|
77
|
+
findings_by_id = {
|
|
78
|
+
str(finding.get("record_id")): finding
|
|
79
|
+
for finding in local_context.get("native_findings", [])
|
|
80
|
+
if isinstance(finding, Mapping) and finding.get("status") == "success"
|
|
81
|
+
}
|
|
82
|
+
selected_ids = {
|
|
83
|
+
str(record_id)
|
|
84
|
+
for record_id in local_context.get("selected_record_ids", [])
|
|
85
|
+
if str(record_id) in records_by_id and str(record_id) in findings_by_id
|
|
86
|
+
}
|
|
87
|
+
selected_records = [
|
|
88
|
+
record
|
|
89
|
+
for record in sorted(input_spec.records, key=lambda item: item.input_index)
|
|
90
|
+
if record.record_id in selected_ids
|
|
91
|
+
]
|
|
92
|
+
previewable_records = [
|
|
93
|
+
record for record in selected_records if supports_remote_preview(record)
|
|
94
|
+
]
|
|
95
|
+
candidates = _representative_preview_records(previewable_records)
|
|
96
|
+
observations: list[dict[str, Any]] = []
|
|
97
|
+
limitations: list[str] = []
|
|
98
|
+
sent_record_ids: list[str] = []
|
|
99
|
+
remote_call_count = 0
|
|
100
|
+
for record in candidates:
|
|
101
|
+
if remote_call_count >= max_calls:
|
|
102
|
+
break
|
|
103
|
+
record_id = record.record_id
|
|
104
|
+
finding = findings_by_id[record_id]
|
|
105
|
+
try:
|
|
106
|
+
preview = render_png_preview(
|
|
107
|
+
record,
|
|
108
|
+
finding,
|
|
109
|
+
max_pixels=max_preview_pixels,
|
|
110
|
+
max_bytes=max_preview_bytes,
|
|
111
|
+
)
|
|
112
|
+
except PreviewRenderError as exc:
|
|
113
|
+
limitations.append(f"Record {record_id} was not sent for remote perception: {exc}")
|
|
114
|
+
continue
|
|
115
|
+
try:
|
|
116
|
+
response = client.describe_json(
|
|
117
|
+
preview,
|
|
118
|
+
prompt=_observation_prompt(record.modality),
|
|
119
|
+
schema=OBSERVATION_SCHEMA,
|
|
120
|
+
media_type="image/png",
|
|
121
|
+
max_tokens=min(int(settings["max_tokens"]), 1200),
|
|
122
|
+
)
|
|
123
|
+
except Exception as exc:
|
|
124
|
+
raise MultimodalInputError(
|
|
125
|
+
"Remote perception failed for an explicitly authorized preview; no fallback model was used."
|
|
126
|
+
) from exc
|
|
127
|
+
remote_call_count += 1
|
|
128
|
+
sent_record_ids.append(record_id)
|
|
129
|
+
observation = _sanitize_observation(response, record_id=record_id, modality=record.modality)
|
|
130
|
+
if observation is None:
|
|
131
|
+
limitations.append(
|
|
132
|
+
f"Record {record_id} returned an observation outside the non-causal claim policy."
|
|
133
|
+
)
|
|
134
|
+
continue
|
|
135
|
+
observations.append(observation)
|
|
136
|
+
unsupported_modalities = sorted(
|
|
137
|
+
{record.modality for record in selected_records if not supports_remote_preview(record)}
|
|
138
|
+
)
|
|
139
|
+
if unsupported_modalities:
|
|
140
|
+
limitations.append(
|
|
141
|
+
"Selected records for these modalities have no Batch B remote preview and remained local-only: "
|
|
142
|
+
+ ", ".join(unsupported_modalities)
|
|
143
|
+
)
|
|
144
|
+
if len(candidates) > max_calls:
|
|
145
|
+
excluded_record_ids = [record.record_id for record in candidates[max_calls:]]
|
|
146
|
+
limitations.append(
|
|
147
|
+
f"Remote perception used the configured budget of {max_calls} stratified cross-modality previews; "
|
|
148
|
+
f"eligible record IDs deferred by budget: {', '.join(excluded_record_ids)}."
|
|
149
|
+
)
|
|
150
|
+
return observations, limitations, {
|
|
151
|
+
"provider": settings["provider"],
|
|
152
|
+
"model": settings["model"],
|
|
153
|
+
"record_ids": sent_record_ids,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _representative_preview_records(
|
|
158
|
+
records: Sequence[Any],
|
|
159
|
+
) -> list[Any]:
|
|
160
|
+
"""Round-robin selected records across modalities and metadata strata."""
|
|
161
|
+
|
|
162
|
+
by_modality: "OrderedDict[str, list[Any]]" = OrderedDict()
|
|
163
|
+
for record in records:
|
|
164
|
+
by_modality.setdefault(record.modality, []).append(record)
|
|
165
|
+
modality_queues: "OrderedDict[str, list[Any]]" = OrderedDict()
|
|
166
|
+
for modality, modality_records in by_modality.items():
|
|
167
|
+
by_stratum: "OrderedDict[tuple[str, str, str, str], list[Any]]" = OrderedDict()
|
|
168
|
+
for record in modality_records:
|
|
169
|
+
metadata = record.metadata
|
|
170
|
+
stratum = tuple(
|
|
171
|
+
str(metadata.get(key, ""))
|
|
172
|
+
for key in ("label", "group", "condition", "timepoint")
|
|
173
|
+
)
|
|
174
|
+
by_stratum.setdefault(stratum, []).append(record)
|
|
175
|
+
queue: list[Any] = []
|
|
176
|
+
round_index = 0
|
|
177
|
+
while True:
|
|
178
|
+
added = False
|
|
179
|
+
for stratum_records in by_stratum.values():
|
|
180
|
+
if round_index < len(stratum_records):
|
|
181
|
+
queue.append(stratum_records[round_index])
|
|
182
|
+
added = True
|
|
183
|
+
if not added:
|
|
184
|
+
break
|
|
185
|
+
round_index += 1
|
|
186
|
+
modality_queues[modality] = queue
|
|
187
|
+
ordered: list[Any] = []
|
|
188
|
+
round_index = 0
|
|
189
|
+
while True:
|
|
190
|
+
added = False
|
|
191
|
+
for queue in modality_queues.values():
|
|
192
|
+
if round_index < len(queue):
|
|
193
|
+
ordered.append(queue[round_index])
|
|
194
|
+
added = True
|
|
195
|
+
if not added:
|
|
196
|
+
return ordered
|
|
197
|
+
round_index += 1
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def _resolve_pinned_vision_settings(config: Any) -> dict[str, Any]:
|
|
201
|
+
multimodal = _multimodal_settings(config)
|
|
202
|
+
configured_model = str(multimodal.get("quality_model") or "").strip()
|
|
203
|
+
if configured_model != QWEN3_VL_PLUS:
|
|
204
|
+
raise MultimodalInputError(
|
|
205
|
+
"Remote multimodal perception requires survey.multimodal_evidence.quality_model=qwen3-vl-plus."
|
|
206
|
+
)
|
|
207
|
+
try:
|
|
208
|
+
settings = resolve_vision_settings(config, batch=False)
|
|
209
|
+
except Exception as exc:
|
|
210
|
+
raise MultimodalInputError("Remote vision settings could not be resolved.") from exc
|
|
211
|
+
if str(settings.get("model") or "").strip() != QWEN3_VL_PLUS:
|
|
212
|
+
raise MultimodalInputError(
|
|
213
|
+
"Remote multimodal perception is pinned to qwen3-vl-plus; batch or flash models are not permitted."
|
|
214
|
+
)
|
|
215
|
+
if str(settings.get("provider") or "").strip().casefold() != "qwen":
|
|
216
|
+
raise MultimodalInputError(
|
|
217
|
+
"Remote multimodal perception requires the Qwen provider for qwen3-vl-plus."
|
|
218
|
+
)
|
|
219
|
+
return dict(settings)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _multimodal_settings(config: Any) -> Mapping[str, Any]:
|
|
223
|
+
survey = config.get("survey") if hasattr(config, "get") and config.get("survey") is not None else config
|
|
224
|
+
settings = survey.get("multimodal_evidence", {}) if hasattr(survey, "get") else {}
|
|
225
|
+
return settings if isinstance(settings, Mapping) or hasattr(settings, "get") else {}
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _observation_prompt(modality: str) -> str:
|
|
229
|
+
return f"""You are reviewing one sanitized PNG preview derived from an explicitly supplied {modality} record.
|
|
230
|
+
Return exactly one JSON object matching the provided schema. Treat only visible preview content as evidence.
|
|
231
|
+
|
|
232
|
+
Rules:
|
|
233
|
+
- Describe a local pattern, not a settled scientific result. Never claim causality, universality, priority, or proof.
|
|
234
|
+
- Provide a tentative candidate explanation, at least one competing explanation, one discriminating prediction, and one falsifier.
|
|
235
|
+
- State clear limits: this is one bounded representative preview and cannot identify people, patients, identities, raw records, or metadata.
|
|
236
|
+
- Do not transcribe raw data, infer hidden values, or mention file names or paths.
|
|
237
|
+
- Use concise scientific English; use confidence=low unless the visible preview itself is unambiguous.
|
|
238
|
+
"""
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _sanitize_observation(
|
|
242
|
+
value: Mapping[str, Any],
|
|
243
|
+
*,
|
|
244
|
+
record_id: str,
|
|
245
|
+
modality: str,
|
|
246
|
+
) -> dict[str, Any] | None:
|
|
247
|
+
raw = dict(value)
|
|
248
|
+
text_fields = (
|
|
249
|
+
"finding",
|
|
250
|
+
"candidate_explanation",
|
|
251
|
+
"discriminating_prediction",
|
|
252
|
+
"falsifier",
|
|
253
|
+
"claim_limits",
|
|
254
|
+
)
|
|
255
|
+
normalized = {key: _clean_text(raw.get(key), limit=700) for key in text_fields}
|
|
256
|
+
alternatives = [
|
|
257
|
+
_clean_text(item, limit=400)
|
|
258
|
+
for item in raw.get("alternative_explanations", [])
|
|
259
|
+
if _clean_text(item, limit=400)
|
|
260
|
+
][:3]
|
|
261
|
+
if not all(normalized.values()) or not alternatives:
|
|
262
|
+
return None
|
|
263
|
+
if any(violates_noncausal_policy(item) for item in [*normalized.values(), *alternatives]):
|
|
264
|
+
return None
|
|
265
|
+
return {
|
|
266
|
+
"observation_id": "",
|
|
267
|
+
"record_ids": [record_id],
|
|
268
|
+
"modality": modality,
|
|
269
|
+
**normalized,
|
|
270
|
+
"alternative_explanations": alternatives,
|
|
271
|
+
"confidence": str(raw.get("confidence") or "low"),
|
|
272
|
+
"focus": str(raw.get("focus") or "mechanism"),
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def _clean_text(value: Any, *, limit: int) -> str:
|
|
277
|
+
return re.sub(r"\s+", " ", str(value or "").strip())[:limit]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""External epistemic bindings for data-anchored SH query variants."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
SUPPORTED_EPISTEMIC_ROLES = frozenset(
|
|
11
|
+
{
|
|
12
|
+
"support",
|
|
13
|
+
"counter",
|
|
14
|
+
"alternative_explanation",
|
|
15
|
+
"boundary",
|
|
16
|
+
"measurement_confound",
|
|
17
|
+
"discriminating_test",
|
|
18
|
+
}
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def normalize_query_variant_bindings(value: Any) -> list[dict[str, str]]:
|
|
23
|
+
"""Validate outer-only metadata without expanding the SH schema."""
|
|
24
|
+
|
|
25
|
+
if value is None:
|
|
26
|
+
return []
|
|
27
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
28
|
+
raise ValueError("query_variant_bindings must be a list.")
|
|
29
|
+
bindings: list[dict[str, str]] = []
|
|
30
|
+
seen: set[tuple[str, str, str]] = set()
|
|
31
|
+
for index, item in enumerate(value, start=1):
|
|
32
|
+
if not isinstance(item, Mapping):
|
|
33
|
+
raise ValueError(f"query_variant_binding {index} must be an object.")
|
|
34
|
+
binding = {
|
|
35
|
+
"sub_hypothesis_id": _text(item.get("sub_hypothesis_id"), limit=120),
|
|
36
|
+
"slot_name": _text(item.get("slot_name"), limit=160),
|
|
37
|
+
"query_variant_id": _text(item.get("query_variant_id"), limit=80),
|
|
38
|
+
"epistemic_role": _text(item.get("epistemic_role"), limit=80).casefold(),
|
|
39
|
+
"evidence_mode": _text(item.get("evidence_mode"), limit=80).casefold(),
|
|
40
|
+
"required_result": _text(item.get("required_result"), limit=700),
|
|
41
|
+
"claim_id": _text(item.get("claim_id"), limit=120),
|
|
42
|
+
}
|
|
43
|
+
if not all(binding[key] for key in ("sub_hypothesis_id", "slot_name", "query_variant_id", "epistemic_role", "required_result", "claim_id")):
|
|
44
|
+
raise ValueError(f"query_variant_binding {index} has a missing required field.")
|
|
45
|
+
if binding["epistemic_role"] not in SUPPORTED_EPISTEMIC_ROLES:
|
|
46
|
+
raise ValueError(
|
|
47
|
+
f"query_variant_binding {index} has unsupported epistemic_role '{binding['epistemic_role']}'."
|
|
48
|
+
)
|
|
49
|
+
key = (binding["sub_hypothesis_id"], binding["slot_name"], binding["query_variant_id"])
|
|
50
|
+
if key in seen:
|
|
51
|
+
raise ValueError(f"Duplicate query_variant_binding for {'.'.join(key)}.")
|
|
52
|
+
seen.add(key)
|
|
53
|
+
bindings.append(binding)
|
|
54
|
+
return bindings
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def query_binding_lookup(value: Any) -> dict[tuple[str, str, str], dict[str, str]]:
|
|
58
|
+
return {
|
|
59
|
+
(binding["sub_hypothesis_id"], binding["slot_name"], binding["query_variant_id"]): binding
|
|
60
|
+
for binding in normalize_query_variant_bindings(value)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _text(value: Any, *, limit: int) -> str:
|
|
65
|
+
return re.sub(r"\s+", " ", str(value or "").strip())[:limit]
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"""Build bounded, metadata-free in-memory previews for remote perception."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
from typing import Any, Mapping
|
|
7
|
+
|
|
8
|
+
from .contract import ValidatedMultimodalRecord
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_PREVIEWABLE_MODALITIES = frozenset({"image", "signal", "audio", "threeD", "trajectory"})
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PreviewRenderError(ValueError):
|
|
15
|
+
"""Raised when a selected record cannot become a bounded PNG preview."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def supports_remote_preview(record: ValidatedMultimodalRecord) -> bool:
|
|
19
|
+
return record.modality in _PREVIEWABLE_MODALITIES
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def render_png_preview(
|
|
23
|
+
record: ValidatedMultimodalRecord,
|
|
24
|
+
native_finding: Mapping[str, Any],
|
|
25
|
+
*,
|
|
26
|
+
max_pixels: int,
|
|
27
|
+
max_bytes: int,
|
|
28
|
+
) -> bytes:
|
|
29
|
+
"""Return a re-encoded PNG held only in memory.
|
|
30
|
+
|
|
31
|
+
Image records retain their pixels but lose source metadata. Other allowed
|
|
32
|
+
modalities become an aggregate-only card, so no source samples or table
|
|
33
|
+
contents are sent to a remote model.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if max_pixels < 1 or max_bytes < 1:
|
|
37
|
+
raise PreviewRenderError("Preview limits must be positive.")
|
|
38
|
+
if not supports_remote_preview(record):
|
|
39
|
+
raise PreviewRenderError("The selected modality has no remote preview in Batch B.")
|
|
40
|
+
if record.modality == "image":
|
|
41
|
+
return _render_image(record, max_pixels=max_pixels, max_bytes=max_bytes)
|
|
42
|
+
return _render_aggregate_card(
|
|
43
|
+
record,
|
|
44
|
+
native_finding,
|
|
45
|
+
max_pixels=max_pixels,
|
|
46
|
+
max_bytes=max_bytes,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _render_image(
|
|
51
|
+
record: ValidatedMultimodalRecord,
|
|
52
|
+
*,
|
|
53
|
+
max_pixels: int,
|
|
54
|
+
max_bytes: int,
|
|
55
|
+
) -> bytes:
|
|
56
|
+
try:
|
|
57
|
+
from PIL import Image, ImageOps
|
|
58
|
+
except ImportError as exc:
|
|
59
|
+
raise PreviewRenderError("Pillow is required to prepare image previews.") from exc
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
with Image.open(record.source_path) as source:
|
|
63
|
+
source.load()
|
|
64
|
+
transformed = ImageOps.exif_transpose(source).convert("RGB")
|
|
65
|
+
clean = Image.new("RGB", transformed.size)
|
|
66
|
+
clean.paste(transformed)
|
|
67
|
+
except Exception as exc:
|
|
68
|
+
raise PreviewRenderError("The selected image could not be rendered safely.") from exc
|
|
69
|
+
return _encode_bounded_png(clean, max_pixels=max_pixels, max_bytes=max_bytes)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _render_aggregate_card(
|
|
73
|
+
record: ValidatedMultimodalRecord,
|
|
74
|
+
native_finding: Mapping[str, Any],
|
|
75
|
+
*,
|
|
76
|
+
max_pixels: int,
|
|
77
|
+
max_bytes: int,
|
|
78
|
+
) -> bytes:
|
|
79
|
+
try:
|
|
80
|
+
from PIL import Image, ImageDraw
|
|
81
|
+
except ImportError as exc:
|
|
82
|
+
raise PreviewRenderError("Pillow is required to prepare aggregate previews.") from exc
|
|
83
|
+
|
|
84
|
+
metrics = native_finding.get("metrics")
|
|
85
|
+
metric_count = len(metrics) if isinstance(metrics, Mapping) else 0
|
|
86
|
+
image = Image.new("RGB", (960, 540), "white")
|
|
87
|
+
draw = ImageDraw.Draw(image)
|
|
88
|
+
draw.rectangle((0, 0, 960, 86), fill=(20, 55, 93))
|
|
89
|
+
draw.text((34, 28), "Aggregate multimodal preview", fill="white")
|
|
90
|
+
draw.text((34, 130), f"Modality: {record.modality}", fill=(24, 24, 24))
|
|
91
|
+
draw.text((34, 184), "This visual contains bounded local summary metadata only.", fill=(24, 24, 24))
|
|
92
|
+
draw.text((34, 222), "Individual records, samples, and source paths are not included.", fill=(24, 24, 24))
|
|
93
|
+
draw.text((34, 286), f"Local aggregate metric groups available: {metric_count}", fill=(24, 24, 24))
|
|
94
|
+
draw.text((34, 390), "Interpret only visible aggregate structure; do not infer causality.", fill=(112, 38, 38))
|
|
95
|
+
return _encode_bounded_png(image, max_pixels=max_pixels, max_bytes=max_bytes)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _encode_bounded_png(image: Any, *, max_pixels: int, max_bytes: int) -> bytes:
|
|
99
|
+
width, height = image.size
|
|
100
|
+
if width < 1 or height < 1:
|
|
101
|
+
raise PreviewRenderError("Preview image dimensions must be positive.")
|
|
102
|
+
if width * height > max_pixels:
|
|
103
|
+
scale = (max_pixels / float(width * height)) ** 0.5
|
|
104
|
+
resampling = getattr(getattr(image, "Resampling", None), "LANCZOS", 1)
|
|
105
|
+
image = image.resize(
|
|
106
|
+
(max(1, int(width * scale)), max(1, int(height * scale))),
|
|
107
|
+
resampling,
|
|
108
|
+
)
|
|
109
|
+
resampling = getattr(getattr(image, "Resampling", None), "LANCZOS", 1)
|
|
110
|
+
for _ in range(6):
|
|
111
|
+
buffer = BytesIO()
|
|
112
|
+
image.save(buffer, format="PNG", optimize=True)
|
|
113
|
+
payload = buffer.getvalue()
|
|
114
|
+
if len(payload) <= max_bytes:
|
|
115
|
+
return payload
|
|
116
|
+
width, height = image.size
|
|
117
|
+
if width == 1 and height == 1:
|
|
118
|
+
break
|
|
119
|
+
image = image.resize((max(1, int(width * 0.7)), max(1, int(height * 0.7))), resampling)
|
|
120
|
+
raise PreviewRenderError("The sanitized PNG preview exceeds the configured byte limit.")
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Shared lexical guard for local observations that must remain non-causal."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_PROHIBITED_ASSERTIONS = (
|
|
10
|
+
re.compile(
|
|
11
|
+
r"\b(?:proves?|proven|establishes?|confirms?|demonstrates?|"
|
|
12
|
+
r"universally?|first[- ]ever)\b",
|
|
13
|
+
flags=re.IGNORECASE,
|
|
14
|
+
),
|
|
15
|
+
re.compile(
|
|
16
|
+
r"\b(?:causes?|causal(?:ly)?|drives?|leads?\s+to|induces?|"
|
|
17
|
+
r"results?\s+in|produces?|creates?|determines?|mediates?|triggers?|"
|
|
18
|
+
r"gives?\s+rise\s+to|is responsible for)\b",
|
|
19
|
+
flags=re.IGNORECASE,
|
|
20
|
+
),
|
|
21
|
+
re.compile(r"\b(?:shows?|indicates?|suggests?)\s+that\b", flags=re.IGNORECASE),
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def violates_noncausal_policy(value: Any) -> bool:
|
|
26
|
+
text = str(value or "")
|
|
27
|
+
for pattern in _PROHIBITED_ASSERTIONS:
|
|
28
|
+
for match in pattern.finditer(text):
|
|
29
|
+
if _is_explicitly_tentative(text, match.start()):
|
|
30
|
+
continue
|
|
31
|
+
return True
|
|
32
|
+
return False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _is_explicitly_tentative(text: str, start: int) -> bool:
|
|
36
|
+
prefix = text[max(0, start - 36) : start].casefold()
|
|
37
|
+
return bool(
|
|
38
|
+
re.search(
|
|
39
|
+
r"\b(?:may|might|could|can|possibly|potentially)\s+(?:\w+\s+){0,2}$",
|
|
40
|
+
prefix,
|
|
41
|
+
)
|
|
42
|
+
)
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections import OrderedDict
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Iterable
|
|
6
|
+
|
|
7
|
+
from .contract import ValidatedMultimodalRecord
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class SamplingResult:
|
|
12
|
+
records: tuple[ValidatedMultimodalRecord, ...]
|
|
13
|
+
policy: str
|
|
14
|
+
truncated_strata_by_modality: dict[str, int]
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def selected_record_ids(self) -> list[str]:
|
|
18
|
+
return [record.record_id for record in self.records]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _stratum_key(record: ValidatedMultimodalRecord) -> tuple[str, str, str, str]:
|
|
22
|
+
metadata = record.metadata
|
|
23
|
+
return tuple(str(metadata.get(key, "")) for key in ("label", "group", "condition", "timepoint"))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def select_stratified_records(
|
|
27
|
+
records: Iterable[ValidatedMultimodalRecord],
|
|
28
|
+
*,
|
|
29
|
+
max_records_per_modality: int,
|
|
30
|
+
) -> SamplingResult:
|
|
31
|
+
if max_records_per_modality < 1:
|
|
32
|
+
raise ValueError("max_records_per_modality must be at least 1.")
|
|
33
|
+
by_modality: "OrderedDict[str, list[ValidatedMultimodalRecord]]" = OrderedDict()
|
|
34
|
+
for record in sorted(records, key=lambda item: item.input_index):
|
|
35
|
+
by_modality.setdefault(record.modality, []).append(record)
|
|
36
|
+
|
|
37
|
+
selected: list[ValidatedMultimodalRecord] = []
|
|
38
|
+
truncated_strata_by_modality: dict[str, int] = {}
|
|
39
|
+
for modality, modality_records in by_modality.items():
|
|
40
|
+
strata: "OrderedDict[tuple[str, str, str, str], list[ValidatedMultimodalRecord]]" = OrderedDict()
|
|
41
|
+
for record in modality_records:
|
|
42
|
+
strata.setdefault(_stratum_key(record), []).append(record)
|
|
43
|
+
stratum_records = list(strata.values())
|
|
44
|
+
if len(stratum_records) > max_records_per_modality:
|
|
45
|
+
truncated_strata_by_modality[modality] = len(stratum_records) - max_records_per_modality
|
|
46
|
+
modality_selected: list[ValidatedMultimodalRecord] = [
|
|
47
|
+
group[0] for group in stratum_records[:max_records_per_modality]
|
|
48
|
+
]
|
|
49
|
+
next_indices = [1 for _ in modality_selected]
|
|
50
|
+
while len(modality_selected) < max_records_per_modality:
|
|
51
|
+
added = False
|
|
52
|
+
for group_index, group in enumerate(stratum_records[:max_records_per_modality]):
|
|
53
|
+
next_index = next_indices[group_index]
|
|
54
|
+
if next_index >= len(group):
|
|
55
|
+
continue
|
|
56
|
+
modality_selected.append(group[next_index])
|
|
57
|
+
next_indices[group_index] += 1
|
|
58
|
+
added = True
|
|
59
|
+
if len(modality_selected) >= max_records_per_modality:
|
|
60
|
+
break
|
|
61
|
+
if not added:
|
|
62
|
+
break
|
|
63
|
+
selected.extend(modality_selected)
|
|
64
|
+
|
|
65
|
+
return SamplingResult(
|
|
66
|
+
records=tuple(sorted(selected, key=lambda item: item.input_index)),
|
|
67
|
+
policy="stratified_by_label_group_condition_timepoint_round_robin_v1",
|
|
68
|
+
truncated_strata_by_modality=truncated_strata_by_modality,
|
|
69
|
+
)
|