eduevidence 5.2.0 → 6.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +105 -0
- package/README.md +142 -75
- package/README.zh-CN.md +73 -30
- package/SKILL.md +397 -131
- package/agents/openai.yaml +4 -0
- package/assets/readme/controlled-execution.svg +34 -0
- package/assets/readme/landing-tour.gif +0 -0
- package/assets/readme/logo.png +0 -0
- package/assets/readme/research-workflow.svg +56 -0
- package/assets/readme/studio-graph.png +0 -0
- package/assets/readme/studio-overview.png +0 -0
- package/assets/readme/studio-reports.png +0 -0
- package/assets/readme/studio-tour.gif +0 -0
- package/autoevolve/config.yaml +17 -0
- package/autoevolve/program.md +25 -0
- package/autoevolve/protected.manifest.yaml +34 -0
- package/benchmarks/adversarial/cases.jsonl +7 -0
- package/benchmarks/evidence-library.json +5268 -0
- package/benchmarks/partitions.json +8 -0
- package/bin/eduevidence.js +2 -1
- package/docs/architecture.md +496 -0
- package/docs/autoresearch-evolution-plan.md +2903 -0
- package/docs/autoresearch-implementation-status.md +101 -0
- package/docs/demo-storyboard.md +20 -0
- package/docs/demo-workplace-ai.md +92 -0
- package/docs/demo.md +32 -0
- package/docs/install-guide.md +150 -0
- package/docs/orchestration-role-model.md +1254 -0
- package/docs/release-closeout/README.md +17 -0
- package/docs/release-closeout/frontend-acceptance.md +23 -0
- package/docs/release-closeout/issues.md +19 -0
- package/docs/release-closeout/verification.md +28 -0
- package/docs/release-contract.md +108 -0
- package/docs/research-studio-guide.zh-CN.md +166 -0
- package/docs/sciverse-api.md +125 -0
- package/eduevidence_cli.py +29 -13
- package/engine/_resources.py +13 -0
- package/engine/autoevolve/__init__.py +3 -0
- package/engine/autoevolve/agent_view.py +167 -0
- package/engine/autoevolve/core.py +357 -0
- package/engine/autoevolve/events.py +11 -0
- package/engine/autoevolve/git_workspace.py +77 -0
- package/engine/autoevolve/projection.py +23 -0
- package/engine/autoevolve/runner.py +413 -0
- package/engine/autoevolve/trust.py +146 -0
- package/engine/autoresearch/__init__.py +6 -0
- package/engine/autoresearch/commit.py +132 -0
- package/engine/autoresearch/contracts.py +126 -0
- package/engine/autoresearch/controller.py +207 -0
- package/engine/autoresearch/events.py +12 -0
- package/engine/autoresearch/gap_priority.py +168 -0
- package/engine/autoresearch/projection.py +30 -0
- package/engine/autoresearch/research_memory.py +59 -0
- package/engine/autoresearch/saturation.py +91 -0
- package/engine/briefs.py +2 -1
- package/engine/capabilities.py +1 -0
- package/engine/contracts.py +3 -1
- package/engine/decision_policy.py +96 -0
- package/engine/evidence_graph.py +14 -10
- package/engine/evidencecore.py +7 -5
- package/engine/gaps.py +132 -73
- package/engine/ids.py +2 -0
- package/engine/judge_pack.py +65 -0
- package/engine/library.py +6 -2
- package/engine/library_builtin.py +3 -1
- package/engine/living.py +36 -5
- package/engine/meta_synthesis.py +3 -1
- package/engine/migration.py +88 -3
- package/engine/orchestration.py +460 -0
- package/engine/paths.py +2 -0
- package/engine/pilot.py +36 -33
- package/engine/project.py +2 -2
- package/engine/research_service.py +113 -0
- package/engine/studio_read_model.py +400 -0
- package/engine/taxonomy.py +211 -0
- package/engine/tribunal.py +44 -33
- package/engine/update.py +1 -0
- package/engine/versions.py +1 -1
- package/engine/worker_result.py +109 -0
- package/engine/workflows.py +70 -0
- package/examples/ai-coding-assistant-evidence/EduEvidence_Report.html +2934 -0
- package/examples/ai-coding-assistant-evidence/artifact_manifest.json +15 -0
- package/examples/ai-coding-assistant-evidence/citation_check.json +79 -0
- package/examples/ai-coding-assistant-evidence/claims.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/evaluation.json +35 -0
- package/examples/ai-coding-assistant-evidence/evidence.jsonl +12 -0
- package/examples/ai-coding-assistant-evidence/final_verdict.json +107 -0
- package/examples/ai-coding-assistant-evidence/frame.json +48 -0
- package/examples/ai-coding-assistant-evidence/gate_report.json +101 -0
- package/examples/ai-coding-assistant-evidence/intervention.json +51 -0
- package/examples/ai-coding-assistant-evidence/methodology.json +36 -0
- package/examples/ai-coding-assistant-evidence/raw_verdict.json +86 -0
- package/examples/ai-coding-assistant-evidence/report_spec.json +230 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_academic.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_claude.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab-dark.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_datalab.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/EduEvidence_Report_presentation.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_academic.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_claude.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab-dark.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_datalab.html +2934 -0
- package/examples/ai-coding-assistant-evidence/reports-5themes/report_presentation.html +2934 -0
- package/examples/ai-coding-assistant-evidence/result.json +1457 -0
- package/examples/ai-coding-assistant-evidence/result.zh.json +1457 -0
- package/examples/ai-coding-assistant-evidence/skeptic.json +72 -0
- package/examples/ai-coding-assistant-evidence/sources.jsonl +8 -0
- package/examples/ai-coding-assistant-evidence/verdict.json +107 -0
- package/examples/spaced-retrieval-practice/applicability.json +14 -0
- package/examples/spaced-retrieval-practice/artifact_manifest.json +15 -0
- package/examples/spaced-retrieval-practice/claims.jsonl +3 -0
- package/examples/spaced-retrieval-practice/evidence.jsonl +6 -0
- package/examples/spaced-retrieval-practice/final_verdict.json +93 -0
- package/examples/spaced-retrieval-practice/frame.json +58 -0
- package/examples/spaced-retrieval-practice/gate_report.json +101 -0
- package/examples/spaced-retrieval-practice/methodology.json +78 -0
- package/examples/spaced-retrieval-practice/report_spec.json +212 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_academic.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_claude.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab-dark.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_datalab.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/EduEvidence_Report_presentation.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_academic.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_claude.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_datalab-dark.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_datalab.html +2728 -0
- package/examples/spaced-retrieval-practice/reports-5themes/report_presentation.html +2728 -0
- package/examples/spaced-retrieval-practice/result.json +942 -0
- package/examples/spaced-retrieval-practice/result.zh.json +942 -0
- package/examples/spaced-retrieval-practice/skeptic.json +70 -0
- package/examples/spaced-retrieval-practice/sources.jsonl +7 -0
- package/examples/spaced-retrieval-practice/verdict.json +93 -0
- package/examples/workplace-ai-assistant/artifact_manifest.json +15 -0
- package/examples/workplace-ai-assistant/claims.jsonl +4 -0
- package/examples/workplace-ai-assistant/evaluation.json +19 -0
- package/examples/workplace-ai-assistant/evidence.jsonl +4 -0
- package/examples/workplace-ai-assistant/evidence_graph.json +444 -0
- package/examples/workplace-ai-assistant/final_verdict.json +78 -0
- package/examples/workplace-ai-assistant/frame.json +41 -0
- package/examples/workplace-ai-assistant/gate_report.json +101 -0
- package/examples/workplace-ai-assistant/intervention.json +27 -0
- package/examples/workplace-ai-assistant/legacy-link-check.json +16 -0
- package/examples/workplace-ai-assistant/methodology.json +60 -0
- package/examples/workplace-ai-assistant/report_spec.json +224 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_academic.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_claude.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab-dark.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_datalab.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/EduEvidence_Report_presentation.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_academic.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_claude.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_datalab-dark.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_datalab.html +2814 -0
- package/examples/workplace-ai-assistant/reports-5themes/report_presentation.html +2814 -0
- package/examples/workplace-ai-assistant/result.json +615 -0
- package/examples/workplace-ai-assistant/result.zh.json +615 -0
- package/examples/workplace-ai-assistant/search_log.json +19 -0
- package/examples/workplace-ai-assistant/skeptic.json +72 -0
- package/examples/workplace-ai-assistant/sources.jsonl +3 -0
- package/examples/workplace-ai-assistant/validation_result.json +9 -0
- package/examples/workplace-ai-assistant/verdict.json +78 -0
- package/install.sh +7 -7
- package/integrations/agent_mcp.py +2 -2
- package/integrations/orchestration_dispatch.py +146 -0
- package/package.json +46 -3
- package/pyproject.toml +14 -22
- package/references/autoresearch.md +30 -0
- package/references/evaluation-policy.md +24 -0
- package/references/orchestration.md +22 -0
- package/references/report-copy-style.md +67 -0
- package/references/retrieval-compliance.md +75 -0
- package/references/retrieval-protocol.md +20 -0
- package/references/scientific-invariants.md +19 -0
- package/retrieval/audit.py +178 -0
- package/retrieval/fetch.py +96 -0
- package/retrieval/sciverse.py +398 -0
- package/retrieval/search.py +47 -7
- package/schemas/applicability.schema.json +94 -0
- package/schemas/chart-spec.schema.json +10 -3
- package/schemas/evidence.schema.json +316 -43
- package/schemas/fetch-result.schema.json +2 -1
- package/schemas/intervention.schema.json +106 -21
- package/schemas/report-result.schema.json +12 -4
- package/schemas/report-spec.schema.json +98 -100
- package/schemas/skeptic.schema.json +86 -0
- package/schemas/source.schema.json +21 -2
- package/schemas/v2/finding.schema.json +5 -1
- package/schemas/v2/methodology-audit.schema.json +5 -1
- package/schemas/v2/outcome.schema.json +28 -5
- package/schemas/v2/project.schema.json +2 -2
- package/schemas/v2/run.schema.json +1 -1
- package/schemas/v2/study.schema.json +5 -1
- package/schemas/vNext/autoevolve-session.schema.json +34 -0
- package/schemas/vNext/eval-snapshot.schema.json +77 -0
- package/schemas/vNext/execution-plan.schema.json +50 -0
- package/schemas/vNext/gap-priority.schema.json +54 -0
- package/schemas/vNext/negative-search-record.schema.json +68 -0
- package/schemas/vNext/research-iteration.schema.json +87 -0
- package/schemas/vNext/research-strategy.schema.json +62 -0
- package/schemas/vNext/skill-experiment.schema.json +90 -0
- package/schemas/vNext/task-spec.schema.json +156 -0
- package/schemas/vNext/worker-result.schema.json +60 -0
- package/schemas/verdict.schema.json +164 -28
- package/scripts/benchmark_judge.py +2 -2
- package/scripts/benchmark_v3.py +26 -43
- package/scripts/build_esl_artifacts.py +4 -4
- package/scripts/build_evidence_library.py +2 -2
- package/scripts/build_gh_pages.py +98 -0
- package/scripts/build_readme_diagrams.py +72 -0
- package/scripts/build_report_variants.py +101 -0
- package/scripts/build_result.py +74 -9
- package/scripts/check_autoresearch_invariants.py +95 -0
- package/scripts/check_package_parity.py +85 -0
- package/scripts/check_protocol_alignment.py +375 -0
- package/scripts/check_versioned_schemas.py +254 -0
- package/scripts/claim_audit.py +13 -8
- package/scripts/compute_confidence.py +10 -0
- package/scripts/daily_evolve.py +30 -0
- package/scripts/dashboard_server.py +130 -101
- package/scripts/did_regression.py +17 -32
- package/scripts/enrich_projects_human_and_lieflat.py +1 -1
- package/scripts/evidence_score.py +5 -2
- package/scripts/generate_metrics.py +4 -3
- package/scripts/generate_new_projects.py +5 -5
- package/scripts/orchestrator.py +286 -36
- package/scripts/pre_verdict_gate.py +224 -26
- package/scripts/quickstart.py +18 -2
- package/scripts/rebake_all_5themes.py +1 -2
- package/scripts/research_auto_cli.py +475 -0
- package/scripts/run_workspace.py +24 -8
- package/scripts/search_provenance.py +64 -0
- package/scripts/serve_web.py +9 -10
- package/scripts/skill_lint.py +1 -1
- package/scripts/skill_payload.py +81 -0
- package/scripts/test_adversarial_empirical.py +26 -19
- package/scripts/validate_schema.py +46 -2
- package/scripts/vnext_cli.py +133 -0
- package/setup.py +12 -0
- package/skill/agents/evaluation-designer.md +20 -4
- package/skill/agents/evidence-analyst.md +19 -3
- package/skill/agents/evidence-judge.md +50 -2
- package/skill/agents/evidence-retriever.md +20 -3
- package/skill/agents/intervention-designer.md +20 -4
- package/skill/agents/method-reviewer.md +18 -2
- package/skill/agents/{education-planner.md → research-planner.md} +19 -3
- package/skill/agents/skeptic.md +18 -2
- package/skill/roles/registry.yaml +45 -0
- package/skill/sub-skills/aihot-trend-analysis/SKILL.md +28 -9
- package/skill/sub-skills/contradiction-analysis/SKILL.md +31 -11
- package/skill/sub-skills/data-analysis/SKILL.md +34 -15
- package/skill/sub-skills/ethics-review/SKILL.md +33 -10
- package/skill/sub-skills/evidence-extraction/SKILL.md +29 -11
- package/skill/sub-skills/evidence-review/SKILL.md +31 -12
- package/skill/sub-skills/gap-analysis/SKILL.md +31 -9
- package/skill/sub-skills/literature-review/SKILL.md +35 -14
- package/skill/sub-skills/methodology-audit/SKILL.md +29 -12
- package/skill/sub-skills/report-generation/SKILL.md +40 -6
- package/skill/sub-skills/research-planning/SKILL.md +41 -14
- package/skill/sub-skills/study-design/SKILL.md +30 -9
- package/skill/task-briefs/adjudicate.md +32 -7
- package/skill/task-briefs/applicability.md +38 -0
- package/skill/task-briefs/audit.md +32 -7
- package/skill/task-briefs/challenge.md +34 -5
- package/skill/task-briefs/evaluate.md +30 -5
- package/skill/task-briefs/extract.md +31 -8
- package/skill/task-briefs/frame.md +39 -10
- package/skill/task-briefs/intervene.md +32 -6
- package/skill/task-briefs/present.md +32 -8
- package/skill/task-briefs/projection.md +37 -0
- package/skill/task-briefs/retrieve.md +36 -6
- package/skill/workflows/decision-and-pilot.md +85 -0
- package/skill/workflows/evaluate-and-update.md +93 -0
- package/skill/workflows/evidence-review.md +117 -0
- package/visualization/eduevidence-report/assets/base.css +2 -2
- package/visualization/eduevidence-report/assets/reader.css +752 -0
- package/visualization/eduevidence-report/assets/reader.js +132 -0
- package/visualization/eduevidence-report/references/chart-selection-catalog.md +109 -0
- package/visualization/eduevidence-report/references/lieflat-composition.md +3 -1
- package/visualization/eduevidence-report/scripts/build_figures.py +25 -3
- package/visualization/eduevidence-report/scripts/build_infographics.py +5 -1
- package/visualization/eduevidence-report/scripts/build_report.py +561 -121
- package/visualization/eduevidence-report/scripts/charts_data.py +2 -0
- package/visualization/eduevidence-report/scripts/lieflat_engine.py +371 -136
- package/visualization/eduevidence-report/scripts/zh_labels.py +80 -1
- package/visualization/eduevidence-report/themes/academic.css +1 -1
- package/visualization/eduevidence-report/themes/claude.css +1 -1
- package/visualization/eduevidence-report/themes/datalab-dark.css +2 -2
- package/visualization/eduevidence-report/themes/datalab.css +2 -2
- package/visualization/eduevidence-report/themes/presentation.css +2 -2
- package/web/README.md +18 -0
- package/web/architecture.html +14885 -0
- package/web/index.html +53 -0
- package/web/studio/THIRD_PARTY_LICENSES.txt +146 -0
- package/web/studio/assets/index-B8tkF44Q.css +1 -0
- package/web/studio/assets/index-CQ6Keoyc.js +230 -0
- package/web/studio/config.json +1 -0
- package/web/studio/index.html +14 -0
- package/engine/__pycache__/__init__.cpython-312.pyc +0 -0
- package/engine/__pycache__/analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/bias.cpython-312.pyc +0 -0
- package/engine/__pycache__/briefs.cpython-312.pyc +0 -0
- package/engine/__pycache__/capabilities.cpython-312.pyc +0 -0
- package/engine/__pycache__/citation_check.cpython-312.pyc +0 -0
- package/engine/__pycache__/contracts.cpython-312.pyc +0 -0
- package/engine/__pycache__/datasets.cpython-312.pyc +0 -0
- package/engine/__pycache__/events.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_graph.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidence_review.cpython-312.pyc +0 -0
- package/engine/__pycache__/evidencecore.cpython-312.pyc +0 -0
- package/engine/__pycache__/gap_lens.cpython-312.pyc +0 -0
- package/engine/__pycache__/gaps.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_store.cpython-312.pyc +0 -0
- package/engine/__pycache__/graph_validate.cpython-312.pyc +0 -0
- package/engine/__pycache__/ids.cpython-312.pyc +0 -0
- package/engine/__pycache__/library.cpython-312.pyc +0 -0
- package/engine/__pycache__/library_builtin.cpython-312.pyc +0 -0
- package/engine/__pycache__/living.cpython-312.pyc +0 -0
- package/engine/__pycache__/log.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_analysis.cpython-312.pyc +0 -0
- package/engine/__pycache__/meta_synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/migration.cpython-312.pyc +0 -0
- package/engine/__pycache__/mode_router.cpython-312.pyc +0 -0
- package/engine/__pycache__/paths.cpython-312.pyc +0 -0
- package/engine/__pycache__/pilot.cpython-312.pyc +0 -0
- package/engine/__pycache__/planner.cpython-312.pyc +0 -0
- package/engine/__pycache__/project.cpython-312.pyc +0 -0
- package/engine/__pycache__/projections.cpython-312.pyc +0 -0
- package/engine/__pycache__/robustness.cpython-312.pyc +0 -0
- package/engine/__pycache__/run.cpython-312.pyc +0 -0
- package/engine/__pycache__/semantics.cpython-312.pyc +0 -0
- package/engine/__pycache__/study_design.cpython-312.pyc +0 -0
- package/engine/__pycache__/synthesis.cpython-312.pyc +0 -0
- package/engine/__pycache__/tribunal.cpython-312.pyc +0 -0
- package/engine/__pycache__/update.cpython-312.pyc +0 -0
- package/engine/__pycache__/versions.cpython-312.pyc +0 -0
- package/integrations/__pycache__/__init__.cpython-312.pyc +0 -0
- package/integrations/__pycache__/agent_mcp.cpython-312.pyc +0 -0
- package/integrations/__pycache__/smart_web_fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/__init__.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/corpus_store.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/dedupe.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/failures.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/fetch.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/search.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/source.cpython-312.pyc +0 -0
- package/retrieval/__pycache__/validate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/__init__.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_evaluator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_judge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_routing.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v2.cpython-312.pyc +0 -0
- package/scripts/__pycache__/benchmark_v3.cpython-312.pyc +0 -0
- package/scripts/__pycache__/build_result.cpython-312.pyc +0 -0
- package/scripts/__pycache__/claim_audit.cpython-312.pyc +0 -0
- package/scripts/__pycache__/complexity_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/compute_confidence.cpython-312.pyc +0 -0
- package/scripts/__pycache__/dashboard_server.cpython-312.pyc +0 -0
- package/scripts/__pycache__/did_regression.cpython-312.pyc +0 -0
- package/scripts/__pycache__/effect_calculator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_matrix.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_score.cpython-312.pyc +0 -0
- package/scripts/__pycache__/evidence_semantics.cpython-312.pyc +0 -0
- package/scripts/__pycache__/fetch_benchmark.cpython-312.pyc +0 -0
- package/scripts/__pycache__/lint_report_layout.cpython-312.pyc +0 -0
- package/scripts/__pycache__/orchestrator.cpython-312.pyc +0 -0
- package/scripts/__pycache__/pre_verdict_gate.cpython-312.pyc +0 -0
- package/scripts/__pycache__/recompute_demo_quality.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/render_report_html.cpython-312.pyc +0 -0
- package/scripts/__pycache__/run_workspace.cpython-312.pyc +0 -0
- package/scripts/__pycache__/skill_lint.cpython-312.pyc +0 -0
- package/scripts/__pycache__/startup_probe.cpython-312.pyc +0 -0
- package/scripts/__pycache__/sync_killer_demo_report.cpython-312.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.0.2.pyc +0 -0
- package/scripts/__pycache__/test_adversarial_empirical.cpython-312-pytest-9.1.1.pyc +0 -0
- package/scripts/__pycache__/validate_schema.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/adapter_contract.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_artifact_manifest.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_charts.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_figures.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_infographics.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/build_report.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/charts_data.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/lieflat_engine.cpython-312.pyc +0 -0
- package/visualization/eduevidence-report/scripts/__pycache__/zh_labels.cpython-312.pyc +0 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""sciverse.py — Sciverse Open Platform retrieval channel (key-based).
|
|
3
|
+
|
|
4
|
+
Sciverse exposes citation-grade academic retrieval to agents through four
|
|
5
|
+
endpoints used here (canonical spec: openapi.yaml v0.14.2 of
|
|
6
|
+
opendatalab/Sciverse-Agent-Tools, archived in docs/sciverse-api.md):
|
|
7
|
+
|
|
8
|
+
POST /meta-search structured metadata search -> Source-level hits
|
|
9
|
+
POST /agentic-search semantic chunk retrieval -> chunk locators
|
|
10
|
+
GET /content read original text slice -> evidence content
|
|
11
|
+
POST /meta-paper-relations citations / references -> citation chain
|
|
12
|
+
|
|
13
|
+
Scientific discipline encoded here (RULE 2): an /agentic-search chunk is a
|
|
14
|
+
discovery locator, never evidence. It must be expanded through ``/content``
|
|
15
|
+
and pass the fetch/validate gate before any extraction stage may use it.
|
|
16
|
+
|
|
17
|
+
The module is stdlib-only and never raises for network or API content issues:
|
|
18
|
+
every failure is reported as a typed status so the pipeline degrades cleanly
|
|
19
|
+
to the zero-config channels.
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import urllib.error
|
|
26
|
+
import urllib.parse
|
|
27
|
+
import urllib.request
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from typing import Any, List, Optional
|
|
30
|
+
|
|
31
|
+
from engine.log import get_log
|
|
32
|
+
|
|
33
|
+
log = get_log("sciverse")
|
|
34
|
+
|
|
35
|
+
BASE_URL = os.environ.get("SCIVERSE_BASE_URL", "https://api.sciverse.space").rstrip("/")
|
|
36
|
+
TOKEN_ENV = "SCIVERSE_API_TOKEN"
|
|
37
|
+
DEFAULT_TIMEOUT = 20
|
|
38
|
+
USER_AGENT = "EduEvidence-Research-Agent/6.1 (+https://github.com/37chengshan/eduevidence)"
|
|
39
|
+
|
|
40
|
+
#: Reading window used when expanding a chunk locator into evidence content.
|
|
41
|
+
DEFAULT_CONTENT_LIMIT = 4096
|
|
42
|
+
MAX_CONTENT_LIMIT = 16384 # LLM-facing ceiling recommended by the spec
|
|
43
|
+
|
|
44
|
+
#: Typed failure statuses (never exceptions) surfaced to callers and audits.
|
|
45
|
+
STATUS_OK = "ok"
|
|
46
|
+
STATUS_UNAVAILABLE = "SCIVERSE_UNAVAILABLE" # no token configured
|
|
47
|
+
STATUS_UNAUTHORIZED = "SCIVERSE_UNAUTHORIZED" # 401/403
|
|
48
|
+
STATUS_BAD_REQUEST = "SCIVERSE_BAD_REQUEST" # 400/404/429
|
|
49
|
+
STATUS_UPSTREAM = "SCIVERSE_UPSTREAM_ERROR" # 502/503/5xx
|
|
50
|
+
STATUS_NETWORK = "SCIVERSE_NETWORK_ERROR" # timeout / DNS / TLS
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def api_token() -> str:
|
|
54
|
+
"""Bearer token from the environment; empty string means unavailable."""
|
|
55
|
+
return os.environ.get(TOKEN_ENV, "").strip()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def available() -> bool:
|
|
59
|
+
return bool(api_token())
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass
|
|
63
|
+
class SciverseResponse:
|
|
64
|
+
"""One endpoint call outcome. ``data`` is empty unless ``ok`` is True."""
|
|
65
|
+
|
|
66
|
+
status: str
|
|
67
|
+
data: dict[str, Any] = field(default_factory=dict)
|
|
68
|
+
error: str = ""
|
|
69
|
+
http_status: Optional[int] = None
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def ok(self) -> bool:
|
|
73
|
+
return self.status == STATUS_OK
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _classify(http_status: int) -> str:
|
|
77
|
+
if http_status in (401, 403):
|
|
78
|
+
return STATUS_UNAUTHORIZED
|
|
79
|
+
if http_status in (400, 404, 429):
|
|
80
|
+
return STATUS_BAD_REQUEST
|
|
81
|
+
if http_status >= 500:
|
|
82
|
+
return STATUS_UPSTREAM
|
|
83
|
+
return STATUS_UPSTREAM
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _error_message(body: str) -> str:
|
|
87
|
+
"""Extract ApiError.message without leaking tokens or full payloads."""
|
|
88
|
+
try:
|
|
89
|
+
payload = json.loads(body)
|
|
90
|
+
except (ValueError, TypeError):
|
|
91
|
+
return body.strip()[:200]
|
|
92
|
+
if isinstance(payload, dict):
|
|
93
|
+
for key in ("message", "error", "detail"):
|
|
94
|
+
value = payload.get(key)
|
|
95
|
+
if isinstance(value, str) and value.strip():
|
|
96
|
+
return value.strip()[:200]
|
|
97
|
+
return ""
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _request(
|
|
101
|
+
method: str,
|
|
102
|
+
path: str,
|
|
103
|
+
*,
|
|
104
|
+
params: Optional[dict[str, Any]] = None,
|
|
105
|
+
payload: Optional[dict[str, Any]] = None,
|
|
106
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
107
|
+
) -> SciverseResponse:
|
|
108
|
+
"""Single typed HTTP call. Never raises; never logs the token."""
|
|
109
|
+
token = api_token()
|
|
110
|
+
if not token:
|
|
111
|
+
return SciverseResponse(STATUS_UNAVAILABLE, error=f"{TOKEN_ENV} is not set")
|
|
112
|
+
|
|
113
|
+
url = BASE_URL + path
|
|
114
|
+
if params:
|
|
115
|
+
clean = {k: v for k, v in params.items() if v is not None}
|
|
116
|
+
if clean:
|
|
117
|
+
url = f"{url}?{urllib.parse.urlencode(clean)}"
|
|
118
|
+
|
|
119
|
+
headers = {
|
|
120
|
+
"Authorization": f"Bearer {token}",
|
|
121
|
+
"Accept": "application/json",
|
|
122
|
+
"User-Agent": USER_AGENT,
|
|
123
|
+
}
|
|
124
|
+
body_bytes: Optional[bytes] = None
|
|
125
|
+
if payload is not None:
|
|
126
|
+
body_bytes = json.dumps(payload).encode("utf-8")
|
|
127
|
+
headers["Content-Type"] = "application/json"
|
|
128
|
+
|
|
129
|
+
req = urllib.request.Request(url, data=body_bytes, headers=headers, method=method)
|
|
130
|
+
try:
|
|
131
|
+
with urllib.request.urlopen(req, timeout=timeout) as resp:
|
|
132
|
+
raw = resp.read().decode("utf-8", errors="replace")
|
|
133
|
+
data = json.loads(raw) if raw.strip() else {}
|
|
134
|
+
return SciverseResponse(STATUS_OK, data=data if isinstance(data, dict) else {},
|
|
135
|
+
http_status=resp.status)
|
|
136
|
+
except urllib.error.HTTPError as exc:
|
|
137
|
+
detail = ""
|
|
138
|
+
try:
|
|
139
|
+
detail = _error_message(exc.read().decode("utf-8", errors="replace"))
|
|
140
|
+
except Exception: # pragma: no cover - body already consumed/closed
|
|
141
|
+
detail = ""
|
|
142
|
+
status = _classify(exc.code)
|
|
143
|
+
log.debug("sciverse http error path=%s status=%s", path, exc.code)
|
|
144
|
+
return SciverseResponse(status, error=detail or f"HTTP {exc.code}", http_status=exc.code)
|
|
145
|
+
except Exception as exc: # timeouts, DNS, TLS, decode
|
|
146
|
+
log.debug("sciverse network error path=%s err=%s", path, type(exc).__name__)
|
|
147
|
+
return SciverseResponse(STATUS_NETWORK, error=type(exc).__name__)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ---------------------------------------------------------------------------
|
|
151
|
+
# Endpoint wrappers (core four)
|
|
152
|
+
# ---------------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
def meta_search(
|
|
155
|
+
query: str,
|
|
156
|
+
*,
|
|
157
|
+
limit: int = 10,
|
|
158
|
+
year_from: Optional[int] = None,
|
|
159
|
+
year_to: Optional[int] = None,
|
|
160
|
+
filters: Optional[list[dict[str, Any]]] = None,
|
|
161
|
+
collection: str = "papers",
|
|
162
|
+
) -> SciverseResponse:
|
|
163
|
+
"""Structured metadata search (``/meta-search``). Source-level hits.
|
|
164
|
+
|
|
165
|
+
Returns paper metadata including ``unique_id`` (always) and ``doc_id``
|
|
166
|
+
(only when full text exists) — both are required for downstream
|
|
167
|
+
``/content`` and ``/meta-paper-relations`` calls.
|
|
168
|
+
"""
|
|
169
|
+
payload: dict[str, Any] = {
|
|
170
|
+
"collection": collection,
|
|
171
|
+
"query": query,
|
|
172
|
+
"page": 1,
|
|
173
|
+
"page_size": max(1, min(limit, 50)),
|
|
174
|
+
}
|
|
175
|
+
advanced: list[dict[str, Any]] = list(filters or [])
|
|
176
|
+
if year_from is not None:
|
|
177
|
+
advanced.append({"field": "publication_published_year",
|
|
178
|
+
"operator": "FILTER_OP_GTE", "value": year_from})
|
|
179
|
+
if year_to is not None:
|
|
180
|
+
advanced.append({"field": "publication_published_year",
|
|
181
|
+
"operator": "FILTER_OP_LTE", "value": year_to})
|
|
182
|
+
if advanced:
|
|
183
|
+
payload["filters_advanced"] = advanced
|
|
184
|
+
return _request("POST", "/meta-search", payload=payload)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def agentic_search(
|
|
188
|
+
query: str,
|
|
189
|
+
*,
|
|
190
|
+
top_k: int = 10,
|
|
191
|
+
mode: str = "balanced",
|
|
192
|
+
filters: Optional[dict[str, Any]] = None,
|
|
193
|
+
) -> SciverseResponse:
|
|
194
|
+
"""Natural-language chunk retrieval (``/agentic-search``).
|
|
195
|
+
|
|
196
|
+
Each hit is a locator (chunk_id/doc_id/offset/score) — a discovery aid.
|
|
197
|
+
``offset`` is a Unicode code-point offset usable directly by
|
|
198
|
+
:func:`read_content`.
|
|
199
|
+
"""
|
|
200
|
+
payload: dict[str, Any] = {
|
|
201
|
+
"query": query,
|
|
202
|
+
"top_k": max(1, min(top_k, 100)),
|
|
203
|
+
"mode": mode if mode in ("fast", "balanced", "quality") else "balanced",
|
|
204
|
+
}
|
|
205
|
+
if filters:
|
|
206
|
+
payload["filters"] = filters
|
|
207
|
+
return _request("POST", "/agentic-search", payload=payload)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def read_content(
|
|
211
|
+
doc_id: str,
|
|
212
|
+
*,
|
|
213
|
+
offset: int = 0,
|
|
214
|
+
limit: int = DEFAULT_CONTENT_LIMIT,
|
|
215
|
+
) -> SciverseResponse:
|
|
216
|
+
"""Read original text by code-point range (``/content``).
|
|
217
|
+
|
|
218
|
+
``offset`` is always sent explicitly: omitting it makes the server return
|
|
219
|
+
the whole document and ignore ``limit``.
|
|
220
|
+
"""
|
|
221
|
+
return _request("GET", "/content", params={
|
|
222
|
+
"doc_id": doc_id,
|
|
223
|
+
"offset": max(0, int(offset)),
|
|
224
|
+
"limit": max(1, min(int(limit), MAX_CONTENT_LIMIT)),
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def paper_relations(
|
|
229
|
+
unique_id: str,
|
|
230
|
+
*,
|
|
231
|
+
relation: str = "REFERENCES",
|
|
232
|
+
page: int = 1,
|
|
233
|
+
page_size: int = 25,
|
|
234
|
+
) -> SciverseResponse:
|
|
235
|
+
"""Paginate citations / references / related works (``/meta-paper-relations``).
|
|
236
|
+
|
|
237
|
+
``unique_id`` (not ``doc_id``) identifies the target paper. CITATIONS is
|
|
238
|
+
incoming (who cites me); REFERENCES is outgoing (who I cite).
|
|
239
|
+
"""
|
|
240
|
+
relation_name = relation.upper()
|
|
241
|
+
if relation_name not in ("CITATIONS", "REFERENCES", "RELATED_WORKS"):
|
|
242
|
+
relation_name = "REFERENCES"
|
|
243
|
+
return _request("POST", "/meta-paper-relations", payload={
|
|
244
|
+
"unique_id": unique_id,
|
|
245
|
+
"relation": relation_name,
|
|
246
|
+
"page": max(1, int(page)),
|
|
247
|
+
"page_size": max(1, min(int(page_size), 200)),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
# ---------------------------------------------------------------------------
|
|
252
|
+
# Chunk / relation record helpers (run-workspace working sets)
|
|
253
|
+
# ---------------------------------------------------------------------------
|
|
254
|
+
|
|
255
|
+
def chunk_records(response: SciverseResponse, *, query_id: str = "") -> list[dict[str, Any]]:
|
|
256
|
+
"""Normalize ``/agentic-search`` hits into chunk-locator records.
|
|
257
|
+
|
|
258
|
+
These records are a working set, never evidence: they carry the locator
|
|
259
|
+
(doc_id/offset) that :func:`read_content` needs.
|
|
260
|
+
"""
|
|
261
|
+
if not response.ok:
|
|
262
|
+
return []
|
|
263
|
+
records: list[dict[str, Any]] = []
|
|
264
|
+
for hit in response.data.get("hits") or []:
|
|
265
|
+
if not isinstance(hit, dict):
|
|
266
|
+
continue
|
|
267
|
+
doc_id = str(hit.get("doc_id") or "")
|
|
268
|
+
if not doc_id:
|
|
269
|
+
continue
|
|
270
|
+
records.append({
|
|
271
|
+
"chunk_id": str(hit.get("chunk_id") or ""),
|
|
272
|
+
"doc_id": doc_id,
|
|
273
|
+
"offset": int(hit.get("offset") or 0),
|
|
274
|
+
"score": float(hit.get("score") or 0.0),
|
|
275
|
+
"chunk": str(hit.get("chunk") or ""),
|
|
276
|
+
"title": str(hit.get("title") or ""),
|
|
277
|
+
"page_no": hit.get("page_no"),
|
|
278
|
+
"source_type": str(hit.get("source_type") or ""),
|
|
279
|
+
"query_id": query_id,
|
|
280
|
+
})
|
|
281
|
+
return records
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def relation_records(response: SciverseResponse, *, unique_id: str = "",
|
|
285
|
+
relation: str = "") -> list[dict[str, Any]]:
|
|
286
|
+
"""Normalize a ``/meta-paper-relations`` page into citation-chain records."""
|
|
287
|
+
if not response.ok:
|
|
288
|
+
return []
|
|
289
|
+
records: list[dict[str, Any]] = []
|
|
290
|
+
for item in response.data.get("items") or []:
|
|
291
|
+
if not isinstance(item, dict):
|
|
292
|
+
continue
|
|
293
|
+
records.append({
|
|
294
|
+
"source_unique_id": unique_id,
|
|
295
|
+
"relation": relation,
|
|
296
|
+
"id": str(item.get("id") or ""),
|
|
297
|
+
"id_type": str(item.get("id_type") or ""),
|
|
298
|
+
"title": str(item.get("title") or ""),
|
|
299
|
+
})
|
|
300
|
+
return records
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ---------------------------------------------------------------------------
|
|
304
|
+
# Router provider (priority: key-based academic channel)
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
class SciverseProvider:
|
|
308
|
+
"""MultiSearchRouter-compatible provider over ``/meta-search`` + ``/agentic-search``.
|
|
309
|
+
|
|
310
|
+
``meta_search`` is the Source-level channel; ``agentic`` (opt-in via
|
|
311
|
+
``include_chunks``) additionally records chunk locators on the hit so the
|
|
312
|
+
run workspace can expand them through ``/content``.
|
|
313
|
+
"""
|
|
314
|
+
|
|
315
|
+
name = "sciverse"
|
|
316
|
+
|
|
317
|
+
def __init__(self, *, token: Optional[str] = None, include_chunks: bool = True):
|
|
318
|
+
if token is not None:
|
|
319
|
+
os.environ[TOKEN_ENV] = token
|
|
320
|
+
self.include_chunks = include_chunks
|
|
321
|
+
|
|
322
|
+
def is_available(self) -> bool:
|
|
323
|
+
return available()
|
|
324
|
+
|
|
325
|
+
def search(self, query: str, limit: int = 10) -> List[Any]:
|
|
326
|
+
from retrieval.search import SearchHit
|
|
327
|
+
|
|
328
|
+
if not self.is_available():
|
|
329
|
+
return []
|
|
330
|
+
response = meta_search(query, limit=limit)
|
|
331
|
+
if not response.ok:
|
|
332
|
+
log.debug("sciverse meta-search unavailable status=%s", response.status)
|
|
333
|
+
return []
|
|
334
|
+
|
|
335
|
+
chunk_index: dict[str, dict[str, Any]] = {}
|
|
336
|
+
if self.include_chunks:
|
|
337
|
+
semantic = agentic_search(query, top_k=max(1, min(limit, 50)))
|
|
338
|
+
for record in chunk_records(semantic):
|
|
339
|
+
chunk_index.setdefault(record["doc_id"], record)
|
|
340
|
+
|
|
341
|
+
hits: List[Any] = []
|
|
342
|
+
for item in response.data.get("results") or []:
|
|
343
|
+
if not isinstance(item, dict):
|
|
344
|
+
continue
|
|
345
|
+
title = str(item.get("title") or "Untitled")
|
|
346
|
+
doi = _normalize_doi(item.get("doi"))
|
|
347
|
+
doc_id = str(item.get("doc_id") or "")
|
|
348
|
+
unique_id = str(item.get("unique_id") or "")
|
|
349
|
+
locator = chunk_index.get(doc_id, {}) if doc_id else {}
|
|
350
|
+
|
|
351
|
+
authors = [
|
|
352
|
+
str(a.get("name")) for a in (item.get("author") or [])
|
|
353
|
+
if isinstance(a, dict) and a.get("name")
|
|
354
|
+
]
|
|
355
|
+
hit = SearchHit(
|
|
356
|
+
title=title,
|
|
357
|
+
url=_source_url(doi),
|
|
358
|
+
snippet=str(item.get("abstract") or title),
|
|
359
|
+
provider=self.name,
|
|
360
|
+
doi=doi,
|
|
361
|
+
year=item.get("publication_published_year"),
|
|
362
|
+
citation_count=item.get("citation_count"),
|
|
363
|
+
authors=authors[:5],
|
|
364
|
+
is_academic=True,
|
|
365
|
+
score=1.35,
|
|
366
|
+
doc_id=doc_id or None,
|
|
367
|
+
chunk_id=locator.get("chunk_id"),
|
|
368
|
+
offset=locator.get("offset"),
|
|
369
|
+
unique_id=unique_id or None,
|
|
370
|
+
)
|
|
371
|
+
hits.append(hit)
|
|
372
|
+
return hits
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def _normalize_doi(value: Any) -> Optional[str]:
|
|
376
|
+
if not isinstance(value, str) or not value.strip():
|
|
377
|
+
return None
|
|
378
|
+
doi = value.strip()
|
|
379
|
+
for prefix in ("https://doi.org/", "http://doi.org/", "doi:"):
|
|
380
|
+
if doi.lower().startswith(prefix):
|
|
381
|
+
doi = doi[len(prefix):]
|
|
382
|
+
return doi or None
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _source_url(doi: Optional[str]) -> str:
|
|
386
|
+
"""Canonical citation pointer. Never fabricate a URL for a DOI-less record:
|
|
387
|
+
the empty string marks it as ``needs_manual_location`` downstream."""
|
|
388
|
+
return f"https://doi.org/{doi}" if doi else ""
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
__all__ = [
|
|
392
|
+
"BASE_URL", "TOKEN_ENV", "DEFAULT_CONTENT_LIMIT", "MAX_CONTENT_LIMIT",
|
|
393
|
+
"STATUS_OK", "STATUS_UNAVAILABLE", "STATUS_UNAUTHORIZED", "STATUS_BAD_REQUEST",
|
|
394
|
+
"STATUS_UPSTREAM", "STATUS_NETWORK",
|
|
395
|
+
"SciverseResponse", "SciverseProvider",
|
|
396
|
+
"api_token", "available", "meta_search", "agentic_search", "read_content",
|
|
397
|
+
"paper_relations", "chunk_records", "relation_records",
|
|
398
|
+
]
|
package/retrieval/search.py
CHANGED
|
@@ -11,12 +11,10 @@ Supports both:
|
|
|
11
11
|
- DuckDuckGo (Zero-auth general web search fallback)
|
|
12
12
|
|
|
13
13
|
2. User-Configured Search Channels (Key-Based):
|
|
14
|
+
- Sciverse (SCIVERSE_API_TOKEN) — citation-grade academic retrieval with
|
|
15
|
+
doc_id/chunk offset provenance (/meta-search + /agentic-search)
|
|
14
16
|
- Tavily (TAVILY_API_KEY)
|
|
15
17
|
- Brave Search (BRAVE_API_KEY)
|
|
16
|
-
- SerpAPI (SERPAPI_API_KEY)
|
|
17
|
-
- Serper (SERPER_API_KEY)
|
|
18
|
-
- Exa (EXA_API_KEY)
|
|
19
|
-
- Bocha (BOCHA_API_KEY)
|
|
20
18
|
|
|
21
19
|
Pure stdlib HTTP client with robust error handling, SSL verification,
|
|
22
20
|
timeout safeguards, and intelligent multi-source deduplication.
|
|
@@ -52,6 +50,16 @@ class SearchHit:
|
|
|
52
50
|
authors: List[str] = field(default_factory=list)
|
|
53
51
|
is_academic: bool = False
|
|
54
52
|
score: float = 1.0
|
|
53
|
+
#: Sciverse locators (None for every other provider). ``doc_id`` addresses
|
|
54
|
+
#: the full-text artifact for /content; ``chunk_id``/``offset`` locate the
|
|
55
|
+
#: retrieved passage (Unicode code points) inside it. They are discovery
|
|
56
|
+
#: locators, never evidence (RULE 2).
|
|
57
|
+
doc_id: Optional[str] = None
|
|
58
|
+
chunk_id: Optional[str] = None
|
|
59
|
+
offset: Optional[int] = None
|
|
60
|
+
#: Sciverse metadata identifier (always present); required by
|
|
61
|
+
#: /meta-paper-relations to page the citation chain.
|
|
62
|
+
unique_id: Optional[str] = None
|
|
55
63
|
|
|
56
64
|
def to_dict(self) -> dict:
|
|
57
65
|
return asdict(self)
|
|
@@ -389,6 +397,15 @@ class MultiSearchRouter:
|
|
|
389
397
|
"""Orchestrates zero-config and configured search channels with deduplication."""
|
|
390
398
|
|
|
391
399
|
def __init__(self):
|
|
400
|
+
self.academic_key_providers = []
|
|
401
|
+
try: # optional channel: activating it must never break the router
|
|
402
|
+
from retrieval.sciverse import SciverseProvider
|
|
403
|
+
|
|
404
|
+
provider = SciverseProvider()
|
|
405
|
+
if provider.is_available():
|
|
406
|
+
self.academic_key_providers.append(provider)
|
|
407
|
+
except Exception: # pragma: no cover - import/环境异常时静默降级
|
|
408
|
+
log.debug("sciverse provider unavailable at router init", exc_info=True)
|
|
392
409
|
self.zero_config_academic = [
|
|
393
410
|
OpenAlexProvider(),
|
|
394
411
|
SemanticScholarProvider(),
|
|
@@ -406,6 +423,9 @@ class MultiSearchRouter:
|
|
|
406
423
|
|
|
407
424
|
def get_provider_status(self) -> List[dict]:
|
|
408
425
|
status = []
|
|
426
|
+
for p in self.academic_key_providers:
|
|
427
|
+
status.append({"provider": p.name, "type": "academic_key",
|
|
428
|
+
"status": "active", "requires_key": True})
|
|
409
429
|
for p in self.zero_config_academic:
|
|
410
430
|
status.append({"provider": p.name, "type": "academic_zero_config", "status": "active", "requires_key": False})
|
|
411
431
|
for p in self.zero_config_web:
|
|
@@ -423,6 +443,7 @@ class MultiSearchRouter:
|
|
|
423
443
|
def search(self, query: str, limit: int = 15, academic_only: bool = False) -> List[SearchHit]:
|
|
424
444
|
all_hits: List[SearchHit] = []
|
|
425
445
|
seen_urls = set()
|
|
446
|
+
seen_locators: set[str] = set()
|
|
426
447
|
|
|
427
448
|
# 1. Try configured high-priority commercial providers if active
|
|
428
449
|
if not academic_only:
|
|
@@ -437,7 +458,26 @@ class MultiSearchRouter:
|
|
|
437
458
|
except Exception:
|
|
438
459
|
pass
|
|
439
460
|
|
|
440
|
-
# 2. Run
|
|
461
|
+
# 2. Run key-based academic providers (Sciverse: citation-grade
|
|
462
|
+
# retrieval with doc_id/offset provenance) before zero-config ones.
|
|
463
|
+
for kp in self.academic_key_providers:
|
|
464
|
+
try:
|
|
465
|
+
hits = kp.search(query, limit=limit)
|
|
466
|
+
for h in hits:
|
|
467
|
+
locator = getattr(h, "doc_id", None)
|
|
468
|
+
if locator and locator in seen_locators:
|
|
469
|
+
continue
|
|
470
|
+
if h.url and h.url in seen_urls:
|
|
471
|
+
continue
|
|
472
|
+
if h.url:
|
|
473
|
+
seen_urls.add(h.url)
|
|
474
|
+
if locator:
|
|
475
|
+
seen_locators.add(locator)
|
|
476
|
+
all_hits.append(h)
|
|
477
|
+
except Exception:
|
|
478
|
+
pass
|
|
479
|
+
|
|
480
|
+
# 3. Run Zero-Config Academic Providers
|
|
441
481
|
for ap in self.zero_config_academic:
|
|
442
482
|
try:
|
|
443
483
|
hits = ap.search(query, limit=limit)
|
|
@@ -448,7 +488,7 @@ class MultiSearchRouter:
|
|
|
448
488
|
except Exception:
|
|
449
489
|
pass
|
|
450
490
|
|
|
451
|
-
#
|
|
491
|
+
# 4. Run Zero-Config Web/Dynamic Providers if not academic_only
|
|
452
492
|
if not academic_only:
|
|
453
493
|
for wp in self.zero_config_web:
|
|
454
494
|
try:
|
|
@@ -460,7 +500,7 @@ class MultiSearchRouter:
|
|
|
460
500
|
except Exception:
|
|
461
501
|
pass
|
|
462
502
|
|
|
463
|
-
#
|
|
503
|
+
# 5. Fallback to verified offline domain corpus if external search returned 0 hits
|
|
464
504
|
if not all_hits:
|
|
465
505
|
try:
|
|
466
506
|
from retrieval.corpus_store import DomainCorpusStore
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://github.com/37chengshan/eduevidence/schemas/applicability.schema.json",
|
|
4
|
+
"title": "ApplicabilityAssessment",
|
|
5
|
+
"description": "Stage-7 deliverable: who the evidence applies to, in which contexts, for which outcomes, and where it stops. Either an assessment or an explicit NOT_CAPTURED record is required - silence used to render as an empty report section with no gate failing.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"additionalProperties": false,
|
|
8
|
+
"required": [
|
|
9
|
+
"status"
|
|
10
|
+
],
|
|
11
|
+
"properties": {
|
|
12
|
+
"status": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"enum": [
|
|
15
|
+
"ASSESSED",
|
|
16
|
+
"NOT_CAPTURED"
|
|
17
|
+
]
|
|
18
|
+
},
|
|
19
|
+
"reason": {
|
|
20
|
+
"type": "string"
|
|
21
|
+
},
|
|
22
|
+
"who": {
|
|
23
|
+
"type": "string"
|
|
24
|
+
},
|
|
25
|
+
"suitable_for": {
|
|
26
|
+
"type": "string"
|
|
27
|
+
},
|
|
28
|
+
"not_suitable_for": {
|
|
29
|
+
"type": "string"
|
|
30
|
+
},
|
|
31
|
+
"which_course": {
|
|
32
|
+
"type": "string"
|
|
33
|
+
},
|
|
34
|
+
"which_outcome": {
|
|
35
|
+
"type": "string"
|
|
36
|
+
},
|
|
37
|
+
"conditions": {
|
|
38
|
+
"type": [
|
|
39
|
+
"string",
|
|
40
|
+
"array"
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"target_population": {
|
|
44
|
+
"type": "string"
|
|
45
|
+
},
|
|
46
|
+
"target_context": {
|
|
47
|
+
"type": "string"
|
|
48
|
+
},
|
|
49
|
+
"boundary": {
|
|
50
|
+
"type": "string"
|
|
51
|
+
},
|
|
52
|
+
"notes": {
|
|
53
|
+
"type": "string"
|
|
54
|
+
},
|
|
55
|
+
"excluded_populations": {
|
|
56
|
+
"type": [
|
|
57
|
+
"string",
|
|
58
|
+
"array"
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"uncertainty": {
|
|
62
|
+
"type": [
|
|
63
|
+
"string",
|
|
64
|
+
"array"
|
|
65
|
+
]
|
|
66
|
+
},
|
|
67
|
+
"extensions": {
|
|
68
|
+
"type": "object",
|
|
69
|
+
"additionalProperties": true
|
|
70
|
+
},
|
|
71
|
+
"required_conditions": {
|
|
72
|
+
"type": [
|
|
73
|
+
"string",
|
|
74
|
+
"array"
|
|
75
|
+
],
|
|
76
|
+
"description": "Conditions that must hold for the evidence to apply."
|
|
77
|
+
},
|
|
78
|
+
"evidence_boundary": {
|
|
79
|
+
"type": "string"
|
|
80
|
+
},
|
|
81
|
+
"transfer_boundaries": {
|
|
82
|
+
"type": [
|
|
83
|
+
"string",
|
|
84
|
+
"array"
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"outcome_limits": {
|
|
88
|
+
"type": [
|
|
89
|
+
"string",
|
|
90
|
+
"array"
|
|
91
|
+
]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
3
|
"$id": "https://github.com/37chengshan/eduevidence/schemas/chart-spec.schema.json",
|
|
4
4
|
"title": "ChartSpec",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "One chart record inside a ReportSpec. Producer: visualization/eduevidence-report/scripts/build_report.py::build_report_spec. Kept in sync with the producer by scripts/check_protocol_alignment.py.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
8
8
|
"required": [
|
|
9
9
|
"chart_id",
|
|
10
10
|
"purpose",
|
|
11
11
|
"engine",
|
|
12
|
-
"
|
|
12
|
+
"data_ref"
|
|
13
13
|
],
|
|
14
14
|
"properties": {
|
|
15
15
|
"chart_id": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
},
|
|
29
29
|
"chart_type": {
|
|
30
30
|
"type": "string",
|
|
31
|
-
"
|
|
31
|
+
"description": "Optional chart family label. The Lieflat gallery carries its own type on result.visual_layout; this field is only used by callers that build chart specs by hand."
|
|
32
32
|
},
|
|
33
33
|
"semantic_basis": {
|
|
34
34
|
"type": "string"
|
|
@@ -83,6 +83,13 @@
|
|
|
83
83
|
]
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
|
+
},
|
|
87
|
+
"data_ref": {
|
|
88
|
+
"type": [
|
|
89
|
+
"string",
|
|
90
|
+
"null"
|
|
91
|
+
],
|
|
92
|
+
"description": "Pointer to the data the chart reads, or null when the chart is rendered inline from result.json by the report builder."
|
|
86
93
|
}
|
|
87
94
|
}
|
|
88
95
|
}
|