argo-search 1.0.1 → 2.9.7
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/LICENSE +201 -21
- package/README.en.md +822 -0
- package/README.es.md +864 -0
- package/README.ja.md +864 -0
- package/README.ko.md +864 -0
- package/README.md +867 -320
- package/SKILL.md +135 -0
- package/assets/readme/hero.svg +121 -0
- package/assets/readme/made-with-beautify.svg +10 -0
- package/assets/readme/proof-routes.svg +73 -0
- package/assets/readme/settings-keys.png +0 -0
- package/assets/readme/settings-tools.png +0 -0
- package/assets/readme/why-better.svg +104 -0
- package/assets/readme/workflow.svg +83 -0
- package/backends/domain_profiles.json +2308 -96
- package/backends/engine_registry.yaml +3314 -503
- package/backends/query_synonyms_cn.json +35 -0
- package/backends/quota_profiles.json +2409 -161
- package/backends/source_types_cn.json +83 -0
- package/backends/text_lexicon.json +44 -0
- package/bin/argo +703 -0
- package/bin/argo.js +71 -39
- package/config.yaml +4976 -363
- package/cordis.patch.yml +21 -0
- package/data/countries.json +40 -0
- package/data/lang_matrix/lang_capability.json +842 -0
- package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
- package/data/regions_cn.json +1 -0
- package/docs/ADDING_NEW_ENGINE.md +184 -0
- package/docs/ARGO_INTRO.md +124 -0
- package/docs/DATA_SOURCE_GAPS.md +81 -0
- package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
- package/docs/ENGINE_CATALOG.md +564 -0
- package/docs/MCP_SETUP.md +106 -0
- package/docs/PLATFORM_PATHS.md +111 -0
- package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
- package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
- package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
- package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
- package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
- package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
- package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
- package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
- package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
- package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
- package/docs/SEARCH_ARCHIVE.md +115 -0
- package/docs/assets/architecture.svg +104 -0
- package/docs/assets/hero.svg +98 -0
- package/docs/engines/bangumi.md +27 -0
- package/docs/engines/biorxiv.md +27 -0
- package/docs/engines/clawhub.md +27 -0
- package/docs/engines/crt_sh.md +27 -0
- package/docs/engines/datacite.md +27 -0
- package/docs/engines/deps_dev.md +27 -0
- package/docs/engines/douban_movie.md +27 -0
- package/docs/engines/endoflife.md +27 -0
- package/docs/engines/flk_law.md +27 -0
- package/docs/engines/fxtwitter.md +27 -0
- package/docs/engines/google_news.md +27 -0
- package/docs/engines/gov_regulations.md +27 -0
- package/docs/engines/guqian.md +27 -0
- package/docs/engines/hackernews.md +27 -0
- package/docs/engines/iplant.md +27 -0
- package/docs/engines/keenable.md +27 -0
- package/docs/engines/met_museum.md +27 -0
- package/docs/engines/nasa_images.md +27 -0
- package/docs/engines/netease_music.md +27 -0
- package/docs/engines/npm.md +27 -0
- package/docs/engines/nvd.md +27 -0
- package/docs/engines/openreview.md +27 -0
- package/docs/engines/openstd.md +27 -0
- package/docs/engines/parallel_free.md +27 -0
- package/docs/engines/people_daily.md +27 -0
- package/docs/engines/realtime_index.md +27 -0
- package/docs/engines/sec_edgar.md +27 -0
- package/docs/engines/seltz.md +27 -0
- package/docs/engines/skillsmp.md +27 -0
- package/docs/engines/sspai.md +27 -0
- package/docs/engines/std_samr.md +27 -0
- package/docs/engines/train.md +27 -0
- package/docs/engines/un_comtrade.md +27 -0
- package/docs/engines/wikisource.md +27 -0
- package/docs/engines/zdic.md +27 -0
- package/docs/engines/zenodo.md +27 -0
- package/docs/engines/zhihu_hot_app.md +27 -0
- package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
- package/engines/README.md +10 -0
- package/engines/_template_cli.yaml +39 -0
- package/engines/_template_http.yaml +37 -0
- package/engines/plugins/_template_plugin.py +50 -0
- package/engines/specs/_cisa_kev.yaml +54 -0
- package/engines/specs/_osv.yaml +63 -0
- package/engines/specs/aljazeera.yaml +43 -0
- package/engines/specs/antara.yaml +43 -0
- package/engines/specs/artic.yaml +20 -0
- package/engines/specs/aviation_weather.yaml +39 -0
- package/engines/specs/bangumi.yaml +21 -0
- package/engines/specs/biorxiv.yaml +24 -0
- package/engines/specs/carbon_intensity.yaml +32 -0
- package/engines/specs/cinii.yaml +44 -0
- package/engines/specs/clawhub.yaml +49 -0
- package/engines/specs/cleveland.yaml +19 -0
- package/engines/specs/cn_ai_news.yaml +43 -0
- package/engines/specs/core.yaml +35 -0
- package/engines/specs/crt_sh.yaml +38 -0
- package/engines/specs/ct24.yaml +43 -0
- package/engines/specs/datacite.yaml +27 -0
- package/engines/specs/datagov_uk.yaml +41 -0
- package/engines/specs/ddgs_images.yaml +45 -0
- package/engines/specs/deepseek_search.yaml +74 -0
- package/engines/specs/deezer.yaml +20 -0
- package/engines/specs/deps_dev.yaml +21 -0
- package/engines/specs/digup.yaml +36 -0
- package/engines/specs/doi.yaml +39 -0
- package/engines/specs/douban_movie.yaml +22 -0
- package/engines/specs/dw_news.yaml +47 -0
- package/engines/specs/egov_law.yaml +21 -0
- package/engines/specs/elpais.yaml +43 -0
- package/engines/specs/endoflife.yaml +21 -0
- package/engines/specs/energy_charts.yaml +21 -0
- package/engines/specs/factcheck_org.yaml +52 -0
- package/engines/specs/faz.yaml +43 -0
- package/engines/specs/federal_register.yaml +57 -0
- package/engines/specs/firecrawl.yaml +53 -0
- package/engines/specs/flk_law.yaml +24 -0
- package/engines/specs/folha.yaml +43 -0
- package/engines/specs/france24.yaml +44 -0
- package/engines/specs/full_fact.yaml +47 -0
- package/engines/specs/fxtwitter.yaml +31 -0
- package/engines/specs/gbfs_nyc.yaml +32 -0
- package/engines/specs/gdacs.yaml +21 -0
- package/engines/specs/gdelt.yaml +64 -0
- package/engines/specs/google_news.yaml +22 -0
- package/engines/specs/gov_regulations.yaml +38 -0
- package/engines/specs/guardian_rss.yaml +59 -0
- package/engines/specs/hurriyet.yaml +43 -0
- package/engines/specs/iplant.yaml +25 -0
- package/engines/specs/jikan.yaml +24 -0
- package/engines/specs/jolpica.yaml +20 -0
- package/engines/specs/k10plus.yaml +20 -0
- package/engines/specs/keenable.yaml +44 -0
- package/engines/specs/lefigaro.yaml +43 -0
- package/engines/specs/listenbrainz.yaml +19 -0
- package/engines/specs/matichon.yaml +43 -0
- package/engines/specs/met_museum.yaml +21 -0
- package/engines/specs/nasa_images.yaml +45 -0
- package/engines/specs/netease_music.yaml +38 -0
- package/engines/specs/nhk.yaml +43 -0
- package/engines/specs/nhtsa_vpic.yaml +20 -0
- package/engines/specs/noaa_swpc.yaml +20 -0
- package/engines/specs/nvd.yaml +38 -0
- package/engines/specs/obis.yaml +20 -0
- package/engines/specs/opencitations.yaml +52 -0
- package/engines/specs/openf1.yaml +21 -0
- package/engines/specs/openfoodfacts.yaml +45 -0
- package/engines/specs/openligadb.yaml +20 -0
- package/engines/specs/openreview.yaml +37 -0
- package/engines/specs/openstd.yaml +21 -0
- package/engines/specs/people_daily.yaml +22 -0
- package/engines/specs/realtime_index.yaml +34 -0
- package/engines/specs/ror.yaml +20 -0
- package/engines/specs/satnogs.yaml +20 -0
- package/engines/specs/sec_edgar.yaml +46 -0
- package/engines/specs/skillsmp.yaml +50 -0
- package/engines/specs/sogou_weixin_browser.yaml +30 -0
- package/engines/specs/soilgrids.yaml +25 -0
- package/engines/specs/sspai.yaml +36 -0
- package/engines/specs/stackexchange.yaml +46 -0
- package/engines/specs/std_samr.yaml +21 -0
- package/engines/specs/tass.yaml +43 -0
- package/engines/specs/tle_mirror.yaml +21 -0
- package/engines/specs/train.yaml +37 -0
- package/engines/specs/tvmaze.yaml +20 -0
- package/engines/specs/twitter_syndication.yaml +31 -0
- package/engines/specs/un_comtrade.yaml +23 -0
- package/engines/specs/unpaywall.yaml +54 -0
- package/engines/specs/vnpress.yaml +43 -0
- package/engines/specs/weather.yaml +32 -0
- package/engines/specs/who_don.yaml +21 -0
- package/engines/specs/who_gho.yaml +20 -0
- package/engines/specs/wikimedia_commons.yaml +34 -0
- package/engines/specs/wikisource.yaml +22 -0
- package/engines/specs/worms.yaml +19 -0
- package/engines/specs/x_trending.yaml +34 -0
- package/engines/specs/yna.yaml +43 -0
- package/engines/specs/ynet.yaml +43 -0
- package/engines/specs/zbmath.yaml +40 -0
- package/engines/specs/zdic.yaml +22 -0
- package/engines/specs/zenodo.yaml +27 -0
- package/engines/specs/zhihu_hot_app.yaml +35 -0
- package/mcp/clients.yaml +74 -0
- package/package.json +51 -12
- package/packages/dsh-plugin/README.md +134 -0
- package/packages/dsh-plugin/cordis.patch.yml +48 -0
- package/packages/dsh-plugin/dsh/client.js +1244 -0
- package/packages/dsh-plugin/dsh/index.js +2290 -0
- package/packages/dsh-plugin/dsh/native-tools.mjs +743 -0
- package/packages/dsh-plugin/package.json +38 -0
- package/packages/dsh-plugin/test/client.test.js +232 -0
- package/packages/dsh-plugin/test/config.test.js +30 -0
- package/packages/dsh-plugin/test/index.test.js +1338 -0
- package/references/query-templates.md +96 -0
- package/references/query-templates.yaml +118 -0
- package/scripts/ab_eval.py +219 -0
- package/scripts/ab_eval_p0p1.py +266 -0
- package/scripts/adaptive.py +271 -70
- package/scripts/answer.py +154 -0
- package/scripts/archive_run.py +591 -0
- package/scripts/argo_engine_registry.py +172 -0
- package/scripts/argo_paths.py +728 -0
- package/scripts/article.py +136 -0
- package/scripts/arxiv_source.py +201 -0
- package/scripts/badcase_ledger.py +252 -0
- package/scripts/batch_probe.py +241 -0
- package/scripts/benchmark.py +19 -0
- package/scripts/bounded_run.py +144 -0
- package/scripts/browser_auth.py +323 -0
- package/scripts/burstiness_detector.py +147 -0
- package/scripts/cache.py +1078 -67
- package/scripts/cache_guard.py +271 -0
- package/scripts/cache_key_vdom.py +56 -0
- package/scripts/cache_similarity.py +170 -0
- package/scripts/caliber_isolation.py +195 -0
- package/scripts/calibrate_lowquality.py +210 -0
- package/scripts/candidate_envelope.py +366 -0
- package/scripts/chrome_cdp.py +871 -0
- package/scripts/circuit_breaker.py +389 -0
- package/scripts/citations.py +334 -0
- package/scripts/cite_cli.py +59 -0
- package/scripts/cjk_tokens.py +95 -0
- package/scripts/clarify.py +47 -24
- package/scripts/cli_io.py +240 -0
- package/scripts/config.py +683 -45
- package/scripts/config_cache.py +402 -0
- package/scripts/conn_pool.py +220 -0
- package/scripts/content_security.py +685 -0
- package/scripts/content_signals.py +730 -0
- package/scripts/crawl.py +74 -35
- package/scripts/daemon.py +866 -0
- package/scripts/digup_bridge.py +214 -0
- package/scripts/engine_admission.py +366 -0
- package/scripts/engine_dispatch.py +1019 -0
- package/scripts/engine_env.py +534 -0
- package/scripts/engine_failure.py +411 -0
- package/scripts/engine_families.py +732 -0
- package/scripts/engine_policy.py +218 -0
- package/scripts/engine_requires.py +190 -0
- package/scripts/engine_status.py +322 -0
- package/scripts/engine_validate.py +638 -0
- package/scripts/engines.py +778 -294
- package/scripts/engines_base.py +1521 -0
- package/scripts/engines_builders.py +298 -0
- package/scripts/engines_builders_batch10.py +159 -0
- package/scripts/engines_builders_batch11.py +174 -0
- package/scripts/engines_builders_batch12.py +103 -0
- package/scripts/engines_builders_batch13.py +340 -0
- package/scripts/engines_builders_batch9.py +1468 -0
- package/scripts/engines_builders_cn.py +2283 -0
- package/scripts/engines_builders_data.py +2923 -0
- package/scripts/engines_builders_data_macro.py +775 -0
- package/scripts/engines_builders_feeds.py +255 -0
- package/scripts/engines_builders_intl.py +1239 -0
- package/scripts/engines_builders_search.py +440 -0
- package/scripts/engines_builders_tech.py +1360 -0
- package/scripts/evidence.py +588 -162
- package/scripts/evidence_loop.py +592 -0
- package/scripts/evidence_tier.py +298 -0
- package/scripts/except_sets.py +25 -0
- package/scripts/extract.py +55 -16
- package/scripts/fact_align.py +207 -0
- package/scripts/fact_crosscheck.py +230 -0
- package/scripts/fetch.py +25 -12
- package/scripts/fetch_browser.py +87 -0
- package/scripts/fetch_jsrun.py +122 -0
- package/scripts/fetch_quality.py +225 -0
- package/scripts/fetch_render_tinyfish.py +117 -0
- package/scripts/fetch_v3.py +1779 -0
- package/scripts/focus_extract.py +350 -0
- package/scripts/fulltext_store.py +169 -0
- package/scripts/fxtwitter_api.py +325 -0
- package/scripts/gen_engine_catalog.py +364 -0
- package/scripts/gen_native_tools.py +91 -0
- package/scripts/gen_routing_snapshot.py +184 -0
- package/scripts/health_check.py +140 -402
- package/scripts/health_probe.py +23 -8
- package/scripts/hot_state.py +134 -0
- package/scripts/http_client.py +1053 -0
- package/scripts/image_ops.py +430 -0
- package/scripts/image_vision/vision_probe +0 -0
- package/scripts/image_vision/vision_probe.swift +126 -0
- package/scripts/install.ps1 +148 -0
- package/scripts/install.sh +206 -0
- package/scripts/job.py +1186 -0
- package/scripts/jump_resolver.py +235 -0
- package/scripts/lang_capability.py +150 -0
- package/scripts/lang_detect.py +289 -0
- package/scripts/lang_matrix_probe.py +329 -0
- package/scripts/lang_pref.py +311 -0
- package/scripts/link_source.py +340 -0
- package/scripts/local_engines.py +321 -0
- package/scripts/local_image.py +970 -0
- package/scripts/local_seek.py +366 -0
- package/scripts/macro_countries.py +82 -0
- package/scripts/matrix_search_eval.py +1105 -0
- package/scripts/mcp_diag.py +8 -3
- package/scripts/mcp_handlers.py +1074 -0
- package/scripts/mcp_handlers_surface.py +157 -0
- package/scripts/mcp_launch.sh +122 -0
- package/scripts/mcp_server.py +118 -417
- package/scripts/mcp_setup.py +407 -0
- package/scripts/mcp_tools.py +330 -0
- package/scripts/mcp_transport.py +191 -0
- package/scripts/min_version_scan.py +335 -0
- package/scripts/net_proxy.py +258 -0
- package/scripts/network_aware.py +152 -0
- package/scripts/paper.py +274 -0
- package/scripts/pdf_extract.py +518 -0
- package/scripts/phrase_filter.py +66 -0
- package/scripts/plan.py +422 -0
- package/scripts/postprocess_signals.py +150 -0
- package/scripts/pre-commit +48 -0
- package/scripts/query_enhance.py +157 -0
- package/scripts/query_operators.py +196 -0
- package/scripts/query_rewriter.py +116 -7
- package/scripts/query_signals.py +300 -0
- package/scripts/query_templates.py +167 -0
- package/scripts/query_understanding.py +411 -0
- package/scripts/query_variants.py +298 -0
- package/scripts/quota.py +488 -76
- package/scripts/rank_signals.py +775 -0
- package/scripts/ranking_eval.py +319 -0
- package/scripts/readability_extract.py +433 -0
- package/scripts/recompute.py +415 -0
- package/scripts/recovery.py +616 -0
- package/scripts/recovery_l0.py +160 -0
- package/scripts/redskill/redskill_engine.py +332 -0
- package/scripts/regression_p0p1.py +318 -0
- package/scripts/replay_eval.py +640 -0
- package/scripts/rerank_tfidf.py +225 -0
- package/scripts/research.py +591 -403
- package/scripts/research_cli.py +361 -0
- package/scripts/research_dossier.py +492 -0
- package/scripts/research_expand.py +146 -0
- package/scripts/research_gates.py +272 -0
- package/scripts/research_report.py +209 -0
- package/scripts/research_strategy.py +37 -0
- package/scripts/research_work_packages.py +195 -0
- package/scripts/result_lang.py +311 -0
- package/scripts/robots_guard.py +259 -0
- package/scripts/route.py +921 -254
- package/scripts/route_bangs.py +40 -0
- package/scripts/route_cache.py +225 -0
- package/scripts/route_combo.py +910 -0
- package/scripts/route_domains.py +328 -0
- package/scripts/route_lang.py +605 -0
- package/scripts/route_log.py +63 -0
- package/scripts/route_policy.py +250 -0
- package/scripts/rrf_classic.py +66 -0
- package/scripts/screenshot.py +73 -0
- package/scripts/search.py +897 -405
- package/scripts/search_attribution.py +88 -0
- package/scripts/search_benchmark.py +407 -0
- package/scripts/search_cli.py +627 -0
- package/scripts/search_entry.py +389 -0
- package/scripts/search_output.py +606 -0
- package/scripts/search_pipeline.py +1077 -0
- package/scripts/search_rank.py +991 -0
- package/scripts/search_types.py +27 -12
- package/scripts/seek_locator.py +68 -0
- package/scripts/semantic_evidence.py +215 -0
- package/scripts/serp_guard.py +228 -0
- package/scripts/serp_kill_log.py +51 -0
- package/scripts/single_flight.py +120 -0
- package/scripts/social_engines/__init__.py +10 -0
- package/scripts/social_engines/bilibili_engine.py +131 -0
- package/scripts/social_engines/hackernews_engine.py +113 -0
- package/scripts/social_engines/reddit_engine.py +172 -0
- package/scripts/social_engines/twitter_engine.py +397 -0
- package/scripts/social_engines/v2ex_engine.py +101 -0
- package/scripts/social_engines/weibo_engine.py +128 -0
- package/scripts/social_engines/xiaohongshu_engine.py +137 -0
- package/scripts/social_engines/zhihu_engine.py +139 -0
- package/scripts/social_research.py +201 -0
- package/scripts/sogou_weixin_browser.py +318 -0
- package/scripts/source_ledger.py +249 -0
- package/scripts/stage_timing.py +58 -0
- package/scripts/stats_cli.py +95 -0
- package/scripts/straggler_policy.py +60 -0
- package/scripts/stylometry_detector.py +231 -0
- package/scripts/sync_backends.py +353 -0
- package/scripts/sync_installs.py +29 -0
- package/scripts/template_cli.py +127 -0
- package/scripts/text_lexicon.py +72 -0
- package/scripts/tfidf_router.py +109 -37
- package/scripts/time_utils.py +233 -0
- package/scripts/topic_research_profiles.py +643 -0
- package/scripts/train.py +737 -0
- package/scripts/tweet.py +596 -0
- package/scripts/url_canon.py +217 -0
- package/scripts/url_optimize.py +56 -0
- package/scripts/url_safety.py +236 -0
- package/scripts/usage_log.py +116 -0
- package/scripts/usage_log_clean.py +113 -0
- package/scripts/v2ex_nodes.py +311 -0
- package/scripts/variant_recall.py +188 -0
- package/scripts/watch.py +205 -0
- package/scripts/wechat_article.py +258 -0
- package/scripts/wide_research.py +402 -0
- package/scripts/wx.py +349 -0
- package/scripts/x_article_fetch.py +141 -0
- package/scripts/x_search.py +234 -0
- package/scripts/x_trending.py +189 -0
- package/scripts/yaml_load.py +67 -0
- package/sub-skills/ego-search/SKILL.md +253 -0
- package/sub-skills/ego-search/references/browser-runtime.md +43 -0
- package/sub-skills/ego-search/references/install.md +67 -0
- package/sub-skills/ego-search/references/original-ego-upgrade.md +51 -0
- package/sub-skills/ego-search/scripts/ego_search.py +800 -0
- package/sub-skills/ego-search/scripts/merge.py +226 -0
- package/sub-skills/ego-search/scripts/quality.py +64 -0
- package/sub-skills/ego-search/scripts/runtime.py +172 -0
- package/sub-skills/ego-search/scripts/safety.py +109 -0
- package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
- package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
- package/sub-skills/ego-search/tests/test_query_normalize.py +68 -0
- package/sub-skills/js-run/SKILL.md +67 -0
- package/sub-skills/js-run/scripts/jsrun.py +112 -0
- package/sub-skills/js-run/scripts/shim.js +188 -0
- package/sub-skills/js-run/tests/fixtures/challenge_like.js +27 -0
- package/sub-skills/js-run/tests/test_jsrun.py +165 -0
- package/sub-skills/local-search/SKILL.md +50 -53
- package/sub-skills/local-search/bm25.py +162 -0
- package/sub-skills/local-search/config.yaml +152 -55
- package/sub-skills/local-search/engine_registry.py +91 -26
- package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
- package/sub-skills/local-search/local_search_adapter.py +13 -2
- package/sub-skills/local-search/parse_maps.yaml +40 -42
- package/sub-skills/local-search/search_v3.py +1182 -65
- package/sub-skills/local-search/smart_router.py +47 -7
- package/sub-skills/local-seek/SKILL.md +110 -0
- package/sub-skills/local-seek/config/domains.yaml +20 -0
- package/sub-skills/local-seek/references/auto-behaviors.md +24 -0
- package/sub-skills/local-seek/references/fd-mdfind-recipes.md +62 -0
- package/sub-skills/local-seek/references/git-integration.md +54 -0
- package/sub-skills/local-seek/references/remote-handoff.md +46 -0
- package/sub-skills/local-seek/references/rg-recipes.md +96 -0
- package/sub-skills/local-seek/references/strategies.md +52 -0
- package/sub-skills/local-seek/references/structural-search.md +59 -0
- package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
- package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
- package/sub-skills/local-seek/scripts/seek.py +1465 -0
- package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
- package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
- package/sub-skills/local-seek/scripts/seek_rank.py +193 -0
- package/sub-skills/local-seek/tests/test_content_rank.py +222 -0
- package/sub-skills/local-seek/tests/test_determinism_20261006.py +145 -0
- package/sub-skills/local-seek/tests/test_noise_tier.py +101 -0
- package/sub-skills/local-seek/tests/test_pcre2_probe.py +136 -0
- package/sub-skills/local-seek/tests/test_rank_pinyin.py +77 -0
- package/sub-skills/local-seek/tests/test_regression_20261003.py +169 -0
- package/sub-skills/local-seek/tests/test_regression_20261006.py +146 -0
- package/sub-skills/local-seek/tests/test_seek_degradation.py +111 -0
- package/sub-skills/local-seek/tests/test_seek_fts.py +313 -0
package/scripts/plan.py
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
plan.py — 离线搜索计划(吸纳 yichen-unified-search 契约)
|
|
4
|
+
|
|
5
|
+
目标:
|
|
6
|
+
- 不联网,只产出 status / authorization / steps / limitations
|
|
7
|
+
- 分流 input_kind:keyword | url-seed | known-url
|
|
8
|
+
- 与 route_query 保持一致,供 deep/research 路径挂载元数据
|
|
9
|
+
|
|
10
|
+
执行分层(产品纪律,禁止日常「先确认再搜」):
|
|
11
|
+
- daily:fast/auto/budget + 非 deep depth → 直搜,不挂 plan,不等用户确认
|
|
12
|
+
- professional:mode=deep 或 depth=deep → 直搜同时附加 plan 元数据 + 核验提示
|
|
13
|
+
- deep_research:research.py → 先离线 plan 一次(非循环),再拆子查询采集
|
|
14
|
+
|
|
15
|
+
反死循环保证:
|
|
16
|
+
- build_plan 永不 import/调用 super_search 或 deep_research
|
|
17
|
+
- 仅依赖 route_query(纯决策,无网络)
|
|
18
|
+
- 调用方最多 build_plan 一次,不得 plan→search→plan 递归
|
|
19
|
+
|
|
20
|
+
用法:
|
|
21
|
+
python3 scripts/plan.py "检索词" --mode auto --json
|
|
22
|
+
python3 scripts/plan.py "https://example.com/a" --input-kind known-url --json
|
|
23
|
+
python3 scripts/plan.py "搜索引用 https://example.com/a 的报道" --input-kind url-seed --json
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import hashlib
|
|
30
|
+
import os
|
|
31
|
+
import re
|
|
32
|
+
import sys
|
|
33
|
+
from typing import Any, Literal
|
|
34
|
+
from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse
|
|
35
|
+
from cli_io import dumps
|
|
36
|
+
|
|
37
|
+
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
38
|
+
sys.path.insert(0, SCRIPT_DIR)
|
|
39
|
+
|
|
40
|
+
_URL_RE = re.compile(r"https?://[^\s<>\"']+", re.I)
|
|
41
|
+
|
|
42
|
+
ExecutionTier = Literal["daily", "professional", "deep_research"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def execution_tier(
|
|
46
|
+
mode: str = "auto",
|
|
47
|
+
depth: str = "fast",
|
|
48
|
+
context: str = "search",
|
|
49
|
+
) -> ExecutionTier:
|
|
50
|
+
"""判定执行分层。
|
|
51
|
+
|
|
52
|
+
context:
|
|
53
|
+
- search:普通 argo_search / search.py
|
|
54
|
+
- research:deep_research / argo_research
|
|
55
|
+
"""
|
|
56
|
+
ctx = (context or "search").lower()
|
|
57
|
+
if ctx in ("research", "deep_research", "argo_research"):
|
|
58
|
+
return "deep_research"
|
|
59
|
+
m = (mode or "auto").lower()
|
|
60
|
+
d = (depth or "fast").lower()
|
|
61
|
+
if m == "deep" or d == "deep":
|
|
62
|
+
return "professional"
|
|
63
|
+
return "daily"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def should_attach_plan(
|
|
67
|
+
mode: str = "auto",
|
|
68
|
+
depth: str = "fast",
|
|
69
|
+
context: str = "search",
|
|
70
|
+
plan_only: bool = False,
|
|
71
|
+
) -> bool:
|
|
72
|
+
"""是否在 super_search 响应中附加完整 plan 对象。
|
|
73
|
+
|
|
74
|
+
- plan_only:显式只要计划 → True
|
|
75
|
+
- daily:False(避免热路径噪音与「先确认」暗示)
|
|
76
|
+
- professional(mode/depth=deep):True(元数据,不阻断执行)
|
|
77
|
+
- deep_research:False(由 research.py 顶层挂一次 plan,子查询不重复)
|
|
78
|
+
"""
|
|
79
|
+
if plan_only:
|
|
80
|
+
return True
|
|
81
|
+
tier = execution_tier(mode, depth, context)
|
|
82
|
+
return tier == "professional"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def requires_user_confirmation(
|
|
86
|
+
mode: str = "auto",
|
|
87
|
+
depth: str = "fast",
|
|
88
|
+
context: str = "search",
|
|
89
|
+
input_kind: str = "keyword",
|
|
90
|
+
plan_status: str | None = None,
|
|
91
|
+
) -> bool:
|
|
92
|
+
"""日常搜索永不要求用户确认。
|
|
93
|
+
|
|
94
|
+
仅下列情况可能为 True(且仍非交互阻塞,只是 status 提示):
|
|
95
|
+
- needs_authorization(显式站内登录敏感查询)
|
|
96
|
+
known-url handoff 是工具分流,不是「请确认后搜索」。
|
|
97
|
+
"""
|
|
98
|
+
if plan_status == "needs_authorization":
|
|
99
|
+
return True
|
|
100
|
+
# daily / professional / research 的 keyword 与 url-seed 均不弹确认
|
|
101
|
+
_ = (mode, depth, context, input_kind)
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def extract_urls(text: str) -> list[str]:
|
|
106
|
+
if not text:
|
|
107
|
+
return []
|
|
108
|
+
return list(dict.fromkeys(_URL_RE.findall(text)))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def is_pure_url(text: str) -> bool:
|
|
112
|
+
t = (text or "").strip()
|
|
113
|
+
if not t:
|
|
114
|
+
return False
|
|
115
|
+
return bool(re.fullmatch(r"https?://[^\s]+", t, flags=re.I))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _is_single_tweet(url: str) -> bool:
|
|
119
|
+
"""该 URL 是否单条推文(有免登录抽取通道)。
|
|
120
|
+
|
|
121
|
+
推文 ID 提取复用引擎侧实现(engines_builders_intl.extract_tweet_id),
|
|
122
|
+
不在这里重写一套 URL 正则——两处解析必然漂移。懒加载,只在 known-url
|
|
123
|
+
交接分支里发生(罕见路径,约 39ms)。
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
from engines_builders_intl import extract_tweet_id
|
|
127
|
+
return extract_tweet_id(url) is not None
|
|
128
|
+
except Exception:
|
|
129
|
+
return False # engines_builders_intl 不可用或解析失败:安全退回「非 tweet URL」
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def canonicalize_url(url: str) -> str:
|
|
133
|
+
"""URL 归一化(薄转发到 url_canon 唯一来源)。
|
|
134
|
+
|
|
135
|
+
本函数曾自带一份追踪参数表与归一规则,与 search/candidate_envelope/
|
|
136
|
+
research_dossier 的实现互不一致;现统一到 url_canon,保证同一条链接
|
|
137
|
+
在计划层与融合层归一成同一个键。
|
|
138
|
+
"""
|
|
139
|
+
from url_canon import canonical_url as _impl
|
|
140
|
+
return _impl(url)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def classify_input_kind(query: str, explicit: str = "auto") -> str:
|
|
144
|
+
"""keyword | url-seed | known-url。
|
|
145
|
+
|
|
146
|
+
auto 规则(安全默认):
|
|
147
|
+
- 纯 URL → known-url(应 fetch/归档,不进多引擎热搜)
|
|
148
|
+
- 含 URL 且含「搜索/引用/讨论/关联」等发现意图 → url-seed
|
|
149
|
+
- 含 URL 其余 → known-url(安全 handoff)
|
|
150
|
+
- 无 URL → keyword
|
|
151
|
+
"""
|
|
152
|
+
if explicit and explicit != "auto":
|
|
153
|
+
return explicit
|
|
154
|
+
urls = extract_urls(query)
|
|
155
|
+
if not urls:
|
|
156
|
+
return "keyword"
|
|
157
|
+
if is_pure_url(query):
|
|
158
|
+
return "known-url"
|
|
159
|
+
discover = re.search(
|
|
160
|
+
r"(搜索|检索|引用|讨论|关联|提到|关于该|关于此|related|discuss|mention|site:)",
|
|
161
|
+
query or "",
|
|
162
|
+
re.I,
|
|
163
|
+
)
|
|
164
|
+
return "url-seed" if discover else "known-url"
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def build_plan(
|
|
168
|
+
query: str,
|
|
169
|
+
mode: str = "auto",
|
|
170
|
+
depth: str = "fast",
|
|
171
|
+
max_results: int = 5,
|
|
172
|
+
engine: str = "auto",
|
|
173
|
+
input_kind: str = "auto",
|
|
174
|
+
login_approved: bool = False,
|
|
175
|
+
context: str = "search",
|
|
176
|
+
) -> dict[str, Any]:
|
|
177
|
+
"""离线计划。永不调用搜索后端;永不回调 super_search/research。"""
|
|
178
|
+
kind = classify_input_kind(query, input_kind)
|
|
179
|
+
urls = extract_urls(query)
|
|
180
|
+
limitations: list[str] = []
|
|
181
|
+
steps: list[dict[str, Any]] = []
|
|
182
|
+
tier = execution_tier(mode, depth, context)
|
|
183
|
+
|
|
184
|
+
# ── known-url:交接 fetch,不联网搜索 ──
|
|
185
|
+
if kind == "known-url":
|
|
186
|
+
target = urls[0] if urls else query.strip()
|
|
187
|
+
# 单条推文有免登录抽取通道(twitter_syndication 走 syndication 接口):
|
|
188
|
+
# 只提示 argo_fetch 会让用户以为得靠浏览器/登录才拿得到,而这条通道
|
|
189
|
+
# 恰好是为「按 URL 取单条」设计的。能力可发现性属于交接内容的一部分。
|
|
190
|
+
suggested = ["argo_fetch", "argo_pdf"]
|
|
191
|
+
extra_limits: list[str] = []
|
|
192
|
+
if _is_single_tweet(target):
|
|
193
|
+
# 两条免登录通道分工:`argo tweet` 吸收整份内容(正文全文 + 串/引用/
|
|
194
|
+
# 转发 + 可选媒体下载),twitter_syndication 只要一条轻量结果。
|
|
195
|
+
# 只提 argo_fetch 会让人以为得靠浏览器/登录才拿得到。
|
|
196
|
+
suggested.insert(0, "argo_tweet")
|
|
197
|
+
suggested.insert(1, "argo_search(engine=twitter_syndication)")
|
|
198
|
+
extra_limits.append(
|
|
199
|
+
"Single tweet: `argo tweet <url|id>` packages it fully without login "
|
|
200
|
+
"(full text, thread/quotes/reposts, optional --media download); "
|
|
201
|
+
"argo_search with engine=twitter_syndication returns a lightweight "
|
|
202
|
+
"single-post result."
|
|
203
|
+
)
|
|
204
|
+
return {
|
|
205
|
+
"schema_version": "1.0",
|
|
206
|
+
"status": "handoff_required",
|
|
207
|
+
"authorization": "not_applicable",
|
|
208
|
+
"input_kind": kind,
|
|
209
|
+
"query": query,
|
|
210
|
+
"execution_tier": tier,
|
|
211
|
+
"requires_confirmation": False,
|
|
212
|
+
"handoff": {
|
|
213
|
+
"action": "fetch_or_archive",
|
|
214
|
+
"url": target,
|
|
215
|
+
"canonical_url": canonicalize_url(target),
|
|
216
|
+
"suggested_tools": suggested,
|
|
217
|
+
},
|
|
218
|
+
"route": None,
|
|
219
|
+
"steps": [
|
|
220
|
+
{
|
|
221
|
+
"action": "handoff_fetch",
|
|
222
|
+
"url": target,
|
|
223
|
+
"reason": "known-url is not a multi-engine search query",
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
"limitations": [
|
|
227
|
+
"Known URL reading/download/archive is outside multi-engine search.",
|
|
228
|
+
"Use argo_fetch / argo_pdf for content; use --input-kind url-seed only to discover related public pages.",
|
|
229
|
+
*extra_limits,
|
|
230
|
+
],
|
|
231
|
+
"mode": mode,
|
|
232
|
+
"depth": depth,
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
# ── url-seed:URL 仅作发现线索 ──
|
|
236
|
+
search_query = query
|
|
237
|
+
if kind == "url-seed":
|
|
238
|
+
limitations.append(
|
|
239
|
+
"URL is a discovery seed only; this plan does not read/download/archive the seed URL itself."
|
|
240
|
+
)
|
|
241
|
+
# 若用户未写 site: 且是纯发现句,可提示但默认保留原 query 交给引擎
|
|
242
|
+
if urls and "site:" not in query.lower():
|
|
243
|
+
host = urlparse(urls[0]).netloc
|
|
244
|
+
if host:
|
|
245
|
+
limitations.append(
|
|
246
|
+
f"Consider site-index discovery around host={host}; not equal to native full-site index."
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# ── 社交/登录敏感词:仅声明授权需求,不阻断公共引擎 ──
|
|
250
|
+
ql = (query or "").lower()
|
|
251
|
+
needs_login = any(
|
|
252
|
+
s in ql for s in ("小红书站内", "抖音站内", "chrome 登录", "登录态")
|
|
253
|
+
)
|
|
254
|
+
if needs_login and not login_approved:
|
|
255
|
+
return {
|
|
256
|
+
"schema_version": "1.0",
|
|
257
|
+
"status": "needs_authorization",
|
|
258
|
+
"authorization": "explicit_current_turn_login_required",
|
|
259
|
+
"input_kind": kind,
|
|
260
|
+
"query": query,
|
|
261
|
+
"execution_tier": tier,
|
|
262
|
+
"requires_confirmation": True,
|
|
263
|
+
"route": {
|
|
264
|
+
"platform": "social_native",
|
|
265
|
+
"backend": "social_engines",
|
|
266
|
+
"mode": mode,
|
|
267
|
+
"reason": "native_login_gated_search",
|
|
268
|
+
"login_state_used": True,
|
|
269
|
+
},
|
|
270
|
+
"steps": [],
|
|
271
|
+
"limitations": [
|
|
272
|
+
"State platform, exact query, and limit before requesting login authorization.",
|
|
273
|
+
"One approval does not extend to other platforms or later turns.",
|
|
274
|
+
],
|
|
275
|
+
"mode": mode,
|
|
276
|
+
"depth": depth,
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
# ── 正常 keyword / url-seed:保持一致 route_query(离线,无网络)──
|
|
280
|
+
try:
|
|
281
|
+
from route import route_query
|
|
282
|
+
decision = route_query(
|
|
283
|
+
search_query, engine_override=engine, mode=mode,
|
|
284
|
+
depth=depth, context=context,
|
|
285
|
+
)
|
|
286
|
+
except Exception as e:
|
|
287
|
+
return {
|
|
288
|
+
"schema_version": "1.0",
|
|
289
|
+
"status": "invalid_request",
|
|
290
|
+
"authorization": "not_applicable",
|
|
291
|
+
"input_kind": kind,
|
|
292
|
+
"query": query,
|
|
293
|
+
"execution_tier": tier,
|
|
294
|
+
"requires_confirmation": False,
|
|
295
|
+
"route": None,
|
|
296
|
+
"steps": [],
|
|
297
|
+
"limitations": [f"route_query failed offline: {type(e).__name__}"],
|
|
298
|
+
"mode": mode,
|
|
299
|
+
"depth": depth,
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
engines = decision.get("engines_combo") or decision.get("engines") or []
|
|
303
|
+
primary = decision.get("engine") or (engines[0] if engines else "auto")
|
|
304
|
+
domain = decision.get("domain")
|
|
305
|
+
|
|
306
|
+
steps.append({
|
|
307
|
+
"action": "route",
|
|
308
|
+
"engine": primary,
|
|
309
|
+
"engines_combo": engines,
|
|
310
|
+
"domain": domain,
|
|
311
|
+
"reason": decision.get("reason"),
|
|
312
|
+
})
|
|
313
|
+
steps.append({
|
|
314
|
+
"action": "search",
|
|
315
|
+
"query": search_query,
|
|
316
|
+
"max_results": max_results,
|
|
317
|
+
"mode": mode,
|
|
318
|
+
"depth": depth,
|
|
319
|
+
"engines": engines,
|
|
320
|
+
})
|
|
321
|
+
# 仅 professional / deep_research 追加核验步骤(日常不暗示「先确认」)
|
|
322
|
+
if tier in ("professional", "deep_research"):
|
|
323
|
+
steps.append({
|
|
324
|
+
"action": "optional_verify_top_k",
|
|
325
|
+
"k": min(3, max_results),
|
|
326
|
+
"note": "snippet is clue only; fetch before hard claims",
|
|
327
|
+
})
|
|
328
|
+
if tier == "deep_research":
|
|
329
|
+
steps.insert(0, {
|
|
330
|
+
"action": "decompose_then_collect",
|
|
331
|
+
"note": "research path: offline plan once → sub-queries → synthesize; no plan loop",
|
|
332
|
+
})
|
|
333
|
+
limitations.append(
|
|
334
|
+
"Deep research: plan is offline metadata only; execution continues without user confirmation."
|
|
335
|
+
)
|
|
336
|
+
else:
|
|
337
|
+
limitations.append(
|
|
338
|
+
"Daily tier: direct search; plan attach skipped; no user confirmation gate."
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
# 常见局限
|
|
342
|
+
if any(e.startswith("local_") for e in engines):
|
|
343
|
+
limitations.append("Local engines parse public pages; may be blocked by CAPTCHA/anti-bot.")
|
|
344
|
+
if "octen" in engines:
|
|
345
|
+
limitations.append("octen requires OCTEN_API_KEY; missing key yields empty results for that engine.")
|
|
346
|
+
if decision.get("parallel"):
|
|
347
|
+
limitations.append("Parallel multi-engine; early-stop may skip later engines when primary is sufficient.")
|
|
348
|
+
if max_results > 10:
|
|
349
|
+
limitations.append("Some backends clamp max_results ≤10; request may be truncated per engine.")
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
"schema_version": "1.0",
|
|
353
|
+
"status": "ready",
|
|
354
|
+
"authorization": "not_required",
|
|
355
|
+
"input_kind": kind,
|
|
356
|
+
"query": query,
|
|
357
|
+
"search_query": search_query,
|
|
358
|
+
"execution_tier": tier,
|
|
359
|
+
"requires_confirmation": False,
|
|
360
|
+
"route": {
|
|
361
|
+
"platform": "web",
|
|
362
|
+
"backend": primary,
|
|
363
|
+
"engines_combo": engines,
|
|
364
|
+
"domain": domain,
|
|
365
|
+
"mode": mode,
|
|
366
|
+
"depth": depth,
|
|
367
|
+
"reason": decision.get("reason"),
|
|
368
|
+
"login_state_used": False,
|
|
369
|
+
"confidence": decision.get("confidence"),
|
|
370
|
+
"parallel": decision.get("parallel"),
|
|
371
|
+
"tfidf_scores": decision.get("tfidf_scores") or [],
|
|
372
|
+
},
|
|
373
|
+
"steps": steps,
|
|
374
|
+
"limitations": limitations,
|
|
375
|
+
"decision": {
|
|
376
|
+
"engine": primary,
|
|
377
|
+
"engines_combo": engines,
|
|
378
|
+
"domain": domain,
|
|
379
|
+
"elapsed_ms": decision.get("elapsed_ms"),
|
|
380
|
+
},
|
|
381
|
+
"mode": mode,
|
|
382
|
+
"depth": depth,
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def main() -> int:
|
|
387
|
+
p = argparse.ArgumentParser(description="Argo 离线搜索计划(不联网)")
|
|
388
|
+
p.add_argument("query", help="查询词或 URL")
|
|
389
|
+
p.add_argument("--mode", default="auto", choices=("fast", "auto", "deep", "budget"))
|
|
390
|
+
p.add_argument("--depth", default="fast", choices=("fast", "balanced", "deep"))
|
|
391
|
+
p.add_argument("--max-results", "-n", type=int, default=5)
|
|
392
|
+
p.add_argument("--engine", "-e", default="auto")
|
|
393
|
+
p.add_argument(
|
|
394
|
+
"--input-kind",
|
|
395
|
+
default="auto",
|
|
396
|
+
choices=("auto", "keyword", "url-seed", "known-url"),
|
|
397
|
+
)
|
|
398
|
+
p.add_argument("--login-approved", action="store_true")
|
|
399
|
+
p.add_argument(
|
|
400
|
+
"--context",
|
|
401
|
+
default="search",
|
|
402
|
+
choices=("search", "research"),
|
|
403
|
+
help="search=日常/专业分层;research=深度研究分层",
|
|
404
|
+
)
|
|
405
|
+
p.add_argument("--json", action="store_true", dest="json_output", default=True)
|
|
406
|
+
args = p.parse_args()
|
|
407
|
+
plan = build_plan(
|
|
408
|
+
args.query,
|
|
409
|
+
mode=args.mode,
|
|
410
|
+
depth=args.depth,
|
|
411
|
+
max_results=args.max_results,
|
|
412
|
+
engine=args.engine,
|
|
413
|
+
input_kind=args.input_kind,
|
|
414
|
+
login_approved=args.login_approved,
|
|
415
|
+
context=args.context,
|
|
416
|
+
)
|
|
417
|
+
print(dumps(plan))
|
|
418
|
+
return 0 if plan.get("status") in ("ready", "handoff_required", "needs_authorization") else 1
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
if __name__ == "__main__":
|
|
422
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""postprocess_signals.py — 加工段两个可选信号:TF-IDF 语义抛光 + 数字交叉验证。
|
|
3
|
+
|
|
4
|
+
2026-10-16 从 search_pipeline.postprocess 提取(方案二梳理的「真源收敛」一步)。
|
|
5
|
+
留在主管线里的代价是实测过的:+37 行把 search_pipeline 顶破祖父上限 1033,
|
|
6
|
+
且每个特性自带 try/except + 环境开关,主管线的异常面被无谓放大。提取后:
|
|
7
|
+
|
|
8
|
+
- 主管线每个特性只留 3~4 行调用点(与 search_attribution / phrase_filter
|
|
9
|
+
同一形态,本文件也只留调用点的那条纪律在这里是反过来的应用);
|
|
10
|
+
- 开关与它守护的功能同文件:ARGO_TFIDF_RERANK / ARGO_NUMBER_CROSSCHECK
|
|
11
|
+
不再散在主管线里,读代码的人在同一处看到「功能+门控+降级」;
|
|
12
|
+
- 失败各自降级:本模块的函数不向主管线抛异常,异常只留一行日志。
|
|
13
|
+
|
|
14
|
+
两个入口都是「纯加工」签名:吃主管线的中间态,吐改写后的结果。
|
|
15
|
+
"""
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from typing import Any, Callable
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def apply_tfidf_polish(
|
|
22
|
+
query: str,
|
|
23
|
+
merged: list[dict[str, Any]],
|
|
24
|
+
max_results: int,
|
|
25
|
+
mode: str,
|
|
26
|
+
reranker_status: str,
|
|
27
|
+
rank_method: str,
|
|
28
|
+
log: Callable[[str], None] | None = None,
|
|
29
|
+
) -> tuple[list[dict[str, Any]], str, bool]:
|
|
30
|
+
"""精排降级时的零依赖 TF-IDF 语义抛光(防 SERP 壳链靠 RRF 高分回顶)。
|
|
31
|
+
|
|
32
|
+
返回 (merged, rank_method, ran):
|
|
33
|
+
- ran=True:确实重排过,rank_method 已带 "+tfidf"/"tfidf" 标记;
|
|
34
|
+
- ran=False:未触发(开关关 / fast 档 / 结果不足 / 非降级态)或执行
|
|
35
|
+
失败。两者对调用方含义相同——本轮排序没有 TF-IDF 贡献——由调用方
|
|
36
|
+
决定 none 兜底;异常只留一行日志,不外抛。
|
|
37
|
+
|
|
38
|
+
mode 透传给 rerank_tfidf:fast/budget 档其内部直接跳过(零延迟)。
|
|
39
|
+
"""
|
|
40
|
+
from search_rank import _RERANK_DEGRADED_STATUSES
|
|
41
|
+
|
|
42
|
+
# fast/budget 档先拦:rerank_tfidf 内部虽也会跳过,但那样 ran 会被误置
|
|
43
|
+
# True、rank_method 被误标 "+tfidf"——本轮实际没有任何重排贡献
|
|
44
|
+
# (2026-10-16 本模块测试锁住该语义)。
|
|
45
|
+
if mode in ("fast", "budget"):
|
|
46
|
+
return merged, rank_method, False
|
|
47
|
+
if not merged or len(merged) <= 1:
|
|
48
|
+
return merged, rank_method, False
|
|
49
|
+
if reranker_status not in _RERANK_DEGRADED_STATUSES:
|
|
50
|
+
return merged, rank_method, False
|
|
51
|
+
try:
|
|
52
|
+
from engine_env import env_flag
|
|
53
|
+
if not env_flag("ARGO_TFIDF_RERANK", True):
|
|
54
|
+
return merged, rank_method, False
|
|
55
|
+
from rerank_tfidf import rerank as _tfidf_rerank
|
|
56
|
+
merged = _tfidf_rerank(query, merged, top_k=max_results, mode=mode)
|
|
57
|
+
rank_method = (rank_method + "+tfidf") if rank_method != "none" else "tfidf"
|
|
58
|
+
return merged, rank_method, True
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if log:
|
|
61
|
+
log(f"TF-IDF rerank 跳过: {type(e).__name__}")
|
|
62
|
+
return merged, rank_method, False
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def detect_number_conflict(
|
|
66
|
+
query: str,
|
|
67
|
+
merged: list[dict[str, Any]],
|
|
68
|
+
mode: str,
|
|
69
|
+
depth: str,
|
|
70
|
+
log: Callable[[str], None] | None = None,
|
|
71
|
+
) -> dict[str, Any] | None:
|
|
72
|
+
"""多源同指标数值分歧信号(与 fact_alignment 并列的轻量核验提示)。
|
|
73
|
+
|
|
74
|
+
仅 deep/auto 且结果 ≥3 时启用;只对比前 10 条(O(n) 正则扫描,<1ms)。
|
|
75
|
+
有分歧返回 {has_conflict, indicator, values, sources, suggestion},
|
|
76
|
+
无分歧 / 未启用 / 执行失败返回 None(异常只留一行日志不外抛)。
|
|
77
|
+
values[i] 与 sources[i] 同序配对。
|
|
78
|
+
"""
|
|
79
|
+
if not merged or len(merged) < 3 or mode in ("fast", "budget") or depth == "fast":
|
|
80
|
+
return None
|
|
81
|
+
try:
|
|
82
|
+
from engine_env import env_flag
|
|
83
|
+
if not env_flag("ARGO_NUMBER_CROSSCHECK", True):
|
|
84
|
+
return None
|
|
85
|
+
from fact_crosscheck import crosscheck_numbers
|
|
86
|
+
nc = crosscheck_numbers(query, merged[:10])
|
|
87
|
+
if nc.get("has_conflict"):
|
|
88
|
+
return {
|
|
89
|
+
"has_conflict": True,
|
|
90
|
+
"indicator": nc.get("indicator", ""),
|
|
91
|
+
"values": nc.get("values", []),
|
|
92
|
+
"sources": nc.get("sources", []),
|
|
93
|
+
"suggestion": nc.get("suggestion", ""),
|
|
94
|
+
}
|
|
95
|
+
except Exception as e:
|
|
96
|
+
if log:
|
|
97
|
+
log(f"数字交叉验证跳过: {type(e).__name__}")
|
|
98
|
+
return None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ── 查询词回声(2026-10-17 实锤)──────────────────────────────────────────────
|
|
102
|
+
# 上游把整句当单词去查时,返回的是查询中某一个词的词典释义/同主题页。
|
|
103
|
+
# 判据与理由在 query_signals.is_query_echo;这里只做「后处理侧」的接线:
|
|
104
|
+
# 主管线(search_pipeline)只留调用点,逻辑不落在那里。
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def echo_recovery_needed(results: list[dict[str, Any]], query: str) -> bool:
|
|
108
|
+
"""恢复链是否该因「查询词回声」触发——有结果、但整批是查询某一个词的
|
|
109
|
+
词典释义(判据见 query_signals.is_query_echo)。
|
|
110
|
+
|
|
111
|
+
零结果走 `if not merged` 的老路;这里补的是「有结果却是垃圾」:anysearch
|
|
112
|
+
超时后仅剩的 local_bing 回声会被原样交付(2026-10-17 实锤)。
|
|
113
|
+
"""
|
|
114
|
+
try:
|
|
115
|
+
from query_signals import is_query_echo
|
|
116
|
+
except ImportError:
|
|
117
|
+
return False # 判据不可用不拦(fail-open)
|
|
118
|
+
return bool(results) and is_query_echo(results, query)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def drop_echo_items(results: list[dict[str, Any]],
|
|
122
|
+
query: str) -> list[dict[str, Any]]:
|
|
123
|
+
"""过滤查询词回声条目,让恢复链继续往「换引擎」走。
|
|
124
|
+
|
|
125
|
+
恢复链的 L1 放宽查询后,原引擎仍会返回同一批词典释义;若把它们算作
|
|
126
|
+
「恢复成功」,L3 换引擎永不执行(2026-10-17 实测 level_used=L1 而交付的
|
|
127
|
+
仍是回声)。
|
|
128
|
+
|
|
129
|
+
两段判据,缺一不可:
|
|
130
|
+
1. 整批先过 is_query_echo——它要求「≥3 条」正是为了不误伤一两条短标题
|
|
131
|
+
的正确答案(实测 "Paris capital" 对 "what is the capital of France"
|
|
132
|
+
只命中 capital 一个词,但那就是答案);
|
|
133
|
+
2. 整批判定为回声后,再逐条剔除「只命中 1 个查询词」的条目——一批里
|
|
134
|
+
混着的好结果不该被连坐。
|
|
135
|
+
"""
|
|
136
|
+
if not results:
|
|
137
|
+
return results
|
|
138
|
+
try:
|
|
139
|
+
from query_signals import is_query_echo
|
|
140
|
+
from tfidf_router import tokenize
|
|
141
|
+
except ImportError:
|
|
142
|
+
return results # 判据不可用不过滤(fail-open)
|
|
143
|
+
if not is_query_echo(results, query):
|
|
144
|
+
return results
|
|
145
|
+
q_set = set(tokenize(query))
|
|
146
|
+
return [
|
|
147
|
+
r for r in results
|
|
148
|
+
if not (isinstance(r, dict)
|
|
149
|
+
and 0 < len(q_set & set(tokenize(r.get("title") or ""))) <= 1)
|
|
150
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# argo 提交前快门禁(pre-commit hook)。
|
|
3
|
+
#
|
|
4
|
+
# 为什么需要(2026-10-16 流程洞实证):并发代理往主管线接线时没跑测试——
|
|
5
|
+
# 6 道门禁红 + 阻塞全量收集,故障在树里躺了幾小时才被人工发现。「改了没
|
|
6
|
+
# 验证就提交」没有机制拦截,只靠自觉。本 hook 把拦截点放在提交层。
|
|
7
|
+
#
|
|
8
|
+
# 只跑快的六道(实测合计 ~12s):static_lint(语法/重复键/未声明 env)、
|
|
9
|
+
# module_size_gate(1000 行门禁与祖父登记)、usage_doc(ARGO_* 开关总表)、
|
|
10
|
+
# consistency(版本/徽章/工具数五语对账)、routing_snapshot(路由决策漂移)、
|
|
11
|
+
# context_budget + engine_catalog(常驻文档预算、源文档/死源/声明一致)。
|
|
12
|
+
# 后两道是 2026-10-18 补的:f702ee2 把 SKILL.md 压到 8049B、4a71693 让
|
|
13
|
+
# deepseek_search 成死源,两笔都过了 hook 却在全量回归里红——正是「改完没跑
|
|
14
|
+
# 到那条门」的同一个洞。全量回归 ~5 分钟留给 CI 或手动(python3 -m pytest
|
|
15
|
+
# tests/ -q),不进 hook——hook 慢于 20s 就会被用户 --no-verify 绕过,那是更
|
|
16
|
+
# 坏的结局。
|
|
17
|
+
#
|
|
18
|
+
# 安装:bash scripts/install.sh 自动装(链式:不覆盖 privacy-guard 等既有
|
|
19
|
+
# hook,装成 hooks/pre-commit-argo 并追一行调用)。手动:
|
|
20
|
+
# cp scripts/pre-commit "$(git rev-parse --git-common-dir)/hooks/pre-commit-argo"
|
|
21
|
+
# 跳过(不推荐):git commit --no-verify
|
|
22
|
+
set -e
|
|
23
|
+
|
|
24
|
+
# 仓库根:git 运行时 cwd 即工作树根;装在 hooks/ 下或被拷贝到别处时,
|
|
25
|
+
# 脚本相对推导会指向 .git 目录(2026-10-17 实测踩中),故以 show-toplevel
|
|
26
|
+
# 为准、脚本相对为兜底(原产地 scripts/ 形态手动跑也成立)。
|
|
27
|
+
root=$(git rev-parse --show-toplevel 2>/dev/null || true)
|
|
28
|
+
if [ -z "$root" ] || [ ! -d "$root/tests" ]; then
|
|
29
|
+
here=$(cd "$(dirname "$0")" && pwd)
|
|
30
|
+
root=$(cd "$here/.." && pwd)
|
|
31
|
+
fi
|
|
32
|
+
cd "$root" || exit 1
|
|
33
|
+
|
|
34
|
+
PY=$(command -v python3 || command -v python || true)
|
|
35
|
+
if [ -z "$PY" ]; then
|
|
36
|
+
echo "[pre-commit] 找不到 python3/python,跳过了门禁(应视为失败)" >&2
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
echo "[pre-commit] argo 快门禁(~12s;全量回归请手动跑 tests/)..."
|
|
41
|
+
"$PY" -m pytest -q \
|
|
42
|
+
tests/test_static_lint_gate.py \
|
|
43
|
+
tests/test_module_size_gate.py \
|
|
44
|
+
tests/test_usage_doc_gates.py \
|
|
45
|
+
tests/test_consistency_gates.py \
|
|
46
|
+
tests/test_routing_snapshot.py \
|
|
47
|
+
tests/test_context_budget.py \
|
|
48
|
+
tests/test_engine_catalog.py
|