argo-search 1.0.1 → 2.9.8
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 +735 -0
- package/README.es.md +777 -0
- package/README.ja.md +777 -0
- package/README.ko.md +777 -0
- package/README.md +778 -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/why-better.svg +104 -0
- package/assets/readme/workflow.svg +83 -0
- package/backends/domain_profiles.json +2356 -96
- package/backends/engine_registry.yaml +3428 -503
- package/backends/query_synonyms_cn.json +35 -0
- package/backends/quota_profiles.json +2488 -160
- package/backends/source_types_cn.json +109 -0
- package/backends/text_lexicon.json +44 -0
- package/bin/argo +772 -0
- package/bin/argo.js +102 -39
- package/config.yaml +5139 -367
- 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 +575 -0
- package/docs/MCP_SETUP.md +108 -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/RELEASE_NOTES_v2.9.8.md +73 -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/cfets.yaml +41 -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/defillama.yaml +38 -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 +36 -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/gleif.yaml +55 -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/hkex.yaml +40 -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/keenable_free.yaml +36 -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/shfe.yaml +39 -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/tavily_free.yaml +56 -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/yuanbao.yaml +93 -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 +106 -0
- package/package.json +55 -12
- package/packages/dsh-plugin/README.md +152 -0
- package/packages/dsh-plugin/cordis.patch.yml +48 -0
- package/packages/dsh-plugin/dsh/client.js +1533 -0
- package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
- package/packages/dsh-plugin/dsh/index.js +527 -0
- package/packages/dsh-plugin/dsh/native-tools.mjs +749 -0
- package/packages/dsh-plugin/dsh/runner.js +666 -0
- package/packages/dsh-plugin/dsh/wide-research.js +878 -0
- package/packages/dsh-plugin/package.json +38 -0
- package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
- package/packages/dsh-plugin/test/client.test.js +406 -0
- package/packages/dsh-plugin/test/config.test.js +30 -0
- package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
- package/packages/dsh-plugin/test/index.test.js +1382 -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 +294 -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/article_meta.py +131 -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/budget.py +247 -0
- package/scripts/budget_cli.py +93 -0
- package/scripts/build_release_assets.sh +65 -0
- package/scripts/burstiness_detector.py +147 -0
- package/scripts/cache.py +1066 -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/ci_drift_gate.py +90 -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_boundary.py +46 -0
- package/scripts/content_security.py +685 -0
- package/scripts/content_signals.py +751 -0
- package/scripts/crawl.py +74 -35
- package/scripts/cross_lingual_trace.py +293 -0
- package/scripts/daemon.py +866 -0
- package/scripts/digup_bridge.py +214 -0
- package/scripts/dispatch_result.py +98 -0
- package/scripts/engine_admission.py +366 -0
- package/scripts/engine_dispatch.py +982 -0
- package/scripts/engine_env.py +539 -0
- package/scripts/engine_failure.py +411 -0
- package/scripts/engine_families.py +738 -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 +799 -294
- package/scripts/engines_base.py +1521 -0
- package/scripts/engines_builders.py +310 -0
- package/scripts/engines_builders_batch10.py +204 -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_batch14.py +656 -0
- package/scripts/engines_builders_batch15.py +249 -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_keyless.py +111 -0
- package/scripts/engines_builders_search.py +440 -0
- package/scripts/engines_builders_tech.py +1359 -0
- package/scripts/evidence.py +620 -166
- package/scripts/evidence_loop.py +624 -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 +1804 -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 +195 -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.swift +126 -0
- package/scripts/install.ps1 +166 -0
- package/scripts/install.sh +221 -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_image.py +970 -0
- package/scripts/local_seek.py +402 -0
- package/scripts/macro_countries.py +97 -0
- package/scripts/matrix_search_eval.py +1187 -0
- package/scripts/mcp_compact.py +234 -0
- package/scripts/mcp_diag.py +8 -3
- package/scripts/mcp_handlers.py +492 -0
- package/scripts/mcp_handlers_surface.py +157 -0
- package/scripts/mcp_launch.sh +124 -0
- package/scripts/mcp_local.py +241 -0
- package/scripts/mcp_server.py +125 -419
- package/scripts/mcp_setup.py +431 -0
- package/scripts/mcp_tools.py +331 -0
- package/scripts/mcp_transport.py +191 -0
- package/scripts/mcp_warm.py +127 -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/output_transparency.py +200 -0
- package/scripts/paid_policy.py +98 -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 +633 -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/recovery_runner.py +150 -0
- package/scripts/redskill/redskill_engine.py +332 -0
- package/scripts/regression_p0p1.py +318 -0
- package/scripts/replay_eval.py +658 -0
- package/scripts/rerank_tfidf.py +225 -0
- package/scripts/research.py +607 -403
- package/scripts/research_cli.py +361 -0
- package/scripts/research_dossier.py +492 -0
- package/scripts/research_expand.py +169 -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/retry_policy.py +76 -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 +243 -0
- package/scripts/route_combo.py +917 -0
- package/scripts/route_domains.py +341 -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 +886 -406
- package/scripts/search_attribution.py +185 -0
- package/scripts/search_benchmark.py +407 -0
- package/scripts/search_cli.py +640 -0
- package/scripts/search_entry.py +395 -0
- package/scripts/search_output.py +690 -0
- package/scripts/search_pipeline.py +926 -0
- package/scripts/search_rank.py +991 -0
- package/scripts/search_types.py +129 -16
- package/scripts/seek_locator.py +68 -0
- package/scripts/semantic_evidence.py +391 -0
- package/scripts/serp_guard.py +336 -0
- package/scripts/serp_kill_log.py +74 -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 +107 -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 +447 -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/scripts/zhihu_quota.py +139 -0
- package/sub-skills/ego-search/SKILL.md +253 -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/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/local-search/SKILL.md +50 -53
- package/sub-skills/local-search/bm25.py +186 -0
- package/sub-skills/local-search/cli_runner.py +331 -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/parsers.py +328 -0
- package/sub-skills/local-search/search_v3.py +716 -242
- 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/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 +197 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""route_domains.py — 域规则的唯一实现:声明、匹配、精度守卫。
|
|
3
|
+
|
|
4
|
+
## 为什么单独成模块
|
|
5
|
+
|
|
6
|
+
「查询 → 命中哪些域」是本仓最容易被写坏的一步:域命中会锁死整条引擎组合,
|
|
7
|
+
而多数垂直域的 engines_combo **不含通用保底源**,误命中时结果与查询无关且
|
|
8
|
+
无从纠正(历史上出现过「monetary policy」→ 艺术馆、「川菜 做法」→ OpenStreetMap)。
|
|
9
|
+
这类缺陷的共同形态是「一条正则悄悄吃掉了一整类查询」,因此:
|
|
10
|
+
|
|
11
|
+
- 规则**全部是声明式数据**(config.yaml 的 domains 段),本模块只做求值;
|
|
12
|
+
- 精度机制**只有一套**(见下),不允许在别处再长出第二套;
|
|
13
|
+
- tests/test_domain_rule_schema.py 把「写坏」变成可见失败。
|
|
14
|
+
|
|
15
|
+
## 域规则的字段契约(唯一真源:config.yaml)
|
|
16
|
+
|
|
17
|
+
| 字段 | 语义 |
|
|
18
|
+
|------|------|
|
|
19
|
+
| `patterns` | 触发词列表,每项是字符串或 `{match, unless}` |
|
|
20
|
+
| `patterns[].match` | 正向触发词:命中即候选 |
|
|
21
|
+
| `patterns[].unless` | **该触发词**的否决词:命中则这一条不算(**不要**手写 `^(?!.*...)`) |
|
|
22
|
+
| `intent_required` | 点查域意图豁免词:长主题句下必须命中其一,否则让位 |
|
|
23
|
+
| `engines_combo` / `primary` / `parallel` / `no_early_stop` | 命中之后怎么执行(route_combo 消费) |
|
|
24
|
+
|
|
25
|
+
`unless` 与 `intent_required` 是同一件事的两面,都在回答「什么情况下这条命中
|
|
26
|
+
是假的」:前者是「查询里出现了别的语义」,后者是「查询里缺了本域的语义」。
|
|
27
|
+
历史上它们一个写成内联负向前瞻(藏在 patterns 里)、一个写成 Python 字典表
|
|
28
|
+
(`_POINTED_INTENT_RE`),两处都不在 config 里,改域规则要看三个地方。现在
|
|
29
|
+
统一为声明式字段。
|
|
30
|
+
|
|
31
|
+
**`unless` 的作用域必须与被否决的触发词一样窄**:挂在整个域上会把「行政区划
|
|
32
|
+
代码」(真地理查询,但含「代码」)一起毙掉。2026-09-21 用 3709 条查询的路由
|
|
33
|
+
快照实测到这条漂移,因此否决是逐触发词的,不是逐域的。
|
|
34
|
+
|
|
35
|
+
## 求值顺序(三阶段,顺序本身是契约)
|
|
36
|
+
|
|
37
|
+
1. `_match_raw`:按 config 顺序扫描,正向命中 ∧ ¬`exclude_if`,最多 max_n 个;
|
|
38
|
+
2. `_social_domain_first`:结构化平台语法命中时把 social 域提到首位;
|
|
39
|
+
3. `_intent_gate`:点查域在「长主题句 + 无意图词」时让位(面查信号优先)。
|
|
40
|
+
|
|
41
|
+
阶段 1 的 max_n 截断发生在阶段 3 之前是**既有语义**(被让位的域仍占名额),
|
|
42
|
+
改动它属于行为变更,需单独评估——本模块刻意保持逐位一致,由
|
|
43
|
+
`tests/golden` + 路由快照把关。
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from __future__ import annotations
|
|
47
|
+
|
|
48
|
+
import re
|
|
49
|
+
from typing import Any
|
|
50
|
+
|
|
51
|
+
from config import get_domains
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# ── 中文内容域语言门 ──────────────────────────────────────────────────────────
|
|
55
|
+
# 这些域以中文内容为主,明确的非中文查询(ja/ko/en/latin 等主语言)不应命中。
|
|
56
|
+
# 日文/韩文查询常含汉字或谚文字符,易被中文泛内容域的 `[一-\u9fff]` 或单音节
|
|
57
|
+
# 子串正则误爆(如韩语「비교」命中天气的「비」、日语汉字命中 chinese_general),
|
|
58
|
+
# 在匹配层按主语言跳过即可修正。2026-08 修复。
|
|
59
|
+
#
|
|
60
|
+
# 留在代码里而不是 config 字段:它对所有成员语义相同、没有逐域差异,加字段只是
|
|
61
|
+
# 把同一句话抄 16 遍。判断它该不该动的标准是「成员是否各自有理由」——有则上
|
|
62
|
+
# config,无则留表。
|
|
63
|
+
_ZH_LANG_GATED_DOMAINS = frozenset({
|
|
64
|
+
"chinese_general", "local_chinese", "chinese_tech_deep",
|
|
65
|
+
"cn_tech_community", "zhihu_content", "zhihu_hot_list",
|
|
66
|
+
"wechat_search", "cn_encyclopedia", "cn_ai_news",
|
|
67
|
+
"moegirl", "juejin", "bilibili", "weibo",
|
|
68
|
+
# 中文政策/百科/医疗域:日韩查询不应进(如日文「政策金利」命中 gov_policy)
|
|
69
|
+
"gov_policy", "baidu_baike", "medical",
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# ── 域预编译(内容指纹缓存) ──────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
_compiled_domains: list[dict[str, Any]] | None = None
|
|
76
|
+
_compiled_domains_id: tuple | None = None # (name, patterns/exclude_if 内容指纹)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _ensure_domain_compiled(domain: dict[str, Any]) -> list[tuple[re.Pattern, re.Pattern | None]]:
|
|
80
|
+
"""按需编译单个域的触发词与意图正则(扫到哪个域才编译哪个域)。"""
|
|
81
|
+
rules = domain.get("_rules")
|
|
82
|
+
if rules is not None:
|
|
83
|
+
return rules
|
|
84
|
+
rules = []
|
|
85
|
+
for p in _pattern_entries(domain):
|
|
86
|
+
match_src, unless_src = _split_pattern_entry(p)
|
|
87
|
+
match_re = _try_compile(match_src)
|
|
88
|
+
if match_re is None:
|
|
89
|
+
continue # 触发词写坏 → 丢这一条(与旧行为一致)
|
|
90
|
+
rules.append((match_re, _try_compile(unless_src)))
|
|
91
|
+
intent_src = domain.get("intent_required")
|
|
92
|
+
domain["_intent"] = (
|
|
93
|
+
_try_compile(intent_src) if isinstance(intent_src, str) and intent_src else None
|
|
94
|
+
)
|
|
95
|
+
domain["_rules"] = rules
|
|
96
|
+
return rules
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def _compile_domain_patterns(domains: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
100
|
+
"""把域声明编译成可执行形态(`_rules` = (触发词, 该词的否决词|None) 列表)。
|
|
101
|
+
|
|
102
|
+
写坏的正则**静默丢弃**(既有语义:一个域的某条正则写错不该让整条路由崩),
|
|
103
|
+
代价是该域从此永不命中——由 tests/test_domain_rule_schema.py 把这种静默
|
|
104
|
+
失效变成可见失败。
|
|
105
|
+
"""
|
|
106
|
+
compiled = []
|
|
107
|
+
for idx, domain in enumerate(domains):
|
|
108
|
+
entry = {**domain, "_idx": idx, "_rules": None, "_intent": None}
|
|
109
|
+
_ensure_domain_compiled(entry)
|
|
110
|
+
compiled.append(entry)
|
|
111
|
+
return compiled
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _try_compile(src: Any) -> re.Pattern | None:
|
|
115
|
+
if not isinstance(src, str) or not src:
|
|
116
|
+
return None
|
|
117
|
+
try:
|
|
118
|
+
return re.compile(src)
|
|
119
|
+
except re.error:
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _pattern_entries(domain: dict[str, Any]) -> list[Any]:
|
|
124
|
+
patterns = domain.get("patterns", [])
|
|
125
|
+
if isinstance(patterns, str):
|
|
126
|
+
return []
|
|
127
|
+
return list(patterns) if isinstance(patterns, list) else []
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _split_pattern_entry(entry: Any) -> tuple[Any, Any]:
|
|
131
|
+
"""触发词条目的两种形态:字符串,或 `{match, unless}`。"""
|
|
132
|
+
if isinstance(entry, dict):
|
|
133
|
+
return entry.get("match"), entry.get("unless")
|
|
134
|
+
return entry, None
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _domain_fires(domain: dict[str, Any], query: str) -> bool:
|
|
138
|
+
"""一个域是否成立:任一触发词命中,且它自己的否决词没命中。"""
|
|
139
|
+
for match_re, unless_re in _ensure_domain_compiled(domain):
|
|
140
|
+
if unless_re is not None and unless_re.search(query):
|
|
141
|
+
continue
|
|
142
|
+
if match_re.search(query):
|
|
143
|
+
return True
|
|
144
|
+
return False
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _get_compiled_domains(domains: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
148
|
+
global _compiled_domains, _compiled_domains_id
|
|
149
|
+
# 用内容指纹替代 id(domains),避免 GC 后 id 复用导致缓存失效/错误。
|
|
150
|
+
# 指纹含规则本体(只记长度会漏「改正则不改条数」的编辑),且必须覆盖
|
|
151
|
+
# exclude_if / intent_required——它们和 patterns 一样会改变命中结果。
|
|
152
|
+
# 拼接后 hash 而非直接进 tuple:长正则列表构造开销大,hash 一次 O(n) 可控。
|
|
153
|
+
import hashlib
|
|
154
|
+
dom_fp = tuple(
|
|
155
|
+
(d.get("name", ""),
|
|
156
|
+
hashlib.sha1(
|
|
157
|
+
"\x00".join(
|
|
158
|
+
p if isinstance(p, str) else str(p)
|
|
159
|
+
for p in _rule_sources(d)
|
|
160
|
+
).encode("utf-8", "replace")).hexdigest()[:12])
|
|
161
|
+
for d in domains
|
|
162
|
+
)
|
|
163
|
+
if _compiled_domains is not None and _compiled_domains_id == dom_fp:
|
|
164
|
+
return _compiled_domains
|
|
165
|
+
# 槽位懒初始化:实际正则在 _domain_fires 扫到该域时才按需编译,
|
|
166
|
+
# 冷启动路由省去未扫到域(及被语言门跳过的域)的正则编译税。
|
|
167
|
+
_compiled_domains = [
|
|
168
|
+
{**domain, "_idx": idx, "_rules": None, "_intent": None}
|
|
169
|
+
for idx, domain in enumerate(domains)
|
|
170
|
+
]
|
|
171
|
+
_compiled_domains_id = dom_fp
|
|
172
|
+
return _compiled_domains
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def warm_compiled_domains() -> None:
|
|
176
|
+
"""全量预编译域正则(长驻进程的后台预热入口,如 MCP initialize 后)。
|
|
177
|
+
|
|
178
|
+
match_domains 懒编译让「扫到哪个域才编译哪个域」;长驻进程在这里一次
|
|
179
|
+
付清全部域的编译税,首包 tools/call 不再带路由冷启动。失败静默——
|
|
180
|
+
懒编译与全量回退都会自愈,预热是纯优化。
|
|
181
|
+
"""
|
|
182
|
+
try:
|
|
183
|
+
for d in _get_compiled_domains(get_domains()):
|
|
184
|
+
_ensure_domain_compiled(d)
|
|
185
|
+
except Exception:
|
|
186
|
+
pass
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def _rule_sources(domain: dict[str, Any]) -> list[str]:
|
|
190
|
+
"""参与指纹的全部规则源(新增字段必须加进来,否则编辑它不会让缓存失效)。"""
|
|
191
|
+
out: list[str] = []
|
|
192
|
+
for entry in _pattern_entries(domain):
|
|
193
|
+
match_src, unless_src = _split_pattern_entry(entry)
|
|
194
|
+
out.append(str(match_src))
|
|
195
|
+
if unless_src:
|
|
196
|
+
out.append(str(unless_src))
|
|
197
|
+
intent = domain.get("intent_required")
|
|
198
|
+
if isinstance(intent, str):
|
|
199
|
+
out.append(intent)
|
|
200
|
+
return out
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
# ── 阶段 2:结构化平台语法 ────────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
def _social_domain_first(hits: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
206
|
+
"""命中列表里有 social 域 → 提到首位(无则原样)。
|
|
207
|
+
|
|
208
|
+
排位在 social 之前的更具体域保持原序不抢占:「小红书技能排行」的
|
|
209
|
+
redskill_search(小红书技能垂直域)、「我的收藏/我的回答」的
|
|
210
|
+
zhihu_user_data(个人数据意图,收藏/关注是社交平台通用功能词,
|
|
211
|
+
泛 social 域语义更宽)。
|
|
212
|
+
"""
|
|
213
|
+
social = [h for h in hits if h.get("name") == "social"]
|
|
214
|
+
if not social:
|
|
215
|
+
return hits
|
|
216
|
+
idx_first_social = hits.index(social[0])
|
|
217
|
+
# 「热搜/热榜」是比泛 social 更具体的意图:查询里带微博/抖音这类平台名时,
|
|
218
|
+
# social 会被提前,把 hot_trending 顶掉——用户问的是榜单,不是社交帖子。
|
|
219
|
+
_SPECIFIC_BEFORE_SOCIAL = ("redskill_search", "zhihu_user_data", "hot_trending")
|
|
220
|
+
idx_specific = next(
|
|
221
|
+
(i for i, h in enumerate(hits)
|
|
222
|
+
if h.get("name") in _SPECIFIC_BEFORE_SOCIAL), None
|
|
223
|
+
)
|
|
224
|
+
if idx_specific is not None and idx_specific < idx_first_social:
|
|
225
|
+
return hits
|
|
226
|
+
return social + [h for h in hits if h.get("name") != "social"]
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ── 阶段 3:点查域意图门 ──────────────────────────────────────────────────────
|
|
230
|
+
|
|
231
|
+
# 面查信号:查询在研究/排障/对比/学用一个主题,而非定位一个对象
|
|
232
|
+
_DIFFUSE_SIGNAL_RE = re.compile(
|
|
233
|
+
r"(?i)\b(issue|bug|regression|reinstall|stale|not.?work|broken|crash"
|
|
234
|
+
r"|how (to|does|do)|why (is|does|do)|difference|vs\.?)\b"
|
|
235
|
+
r"|报错|失效|不生效|不更新|出错|排查|区别|对比"
|
|
236
|
+
# 学用类信号(2026-10-06 补):实体名只是教程/文档的上下文,不是查询目标。
|
|
237
|
+
# 「claude code mcp tutorial」(4 token)被 ai_model 锁死返回模型规格——
|
|
238
|
+
# 阈值从 5 降到 4 后这类查询进意图门,靠这组词判定让位。
|
|
239
|
+
r"|\b(tutorial|guide|documentation|docs|getting started|how to use)\b"
|
|
240
|
+
r"|教程|文档|指南|怎么用|如何用|入门|上手|使用说明|用法"
|
|
241
|
+
)
|
|
242
|
+
# 长主题句门槛:去重 token 少于此值不设卡(短查询大概率是点查)。
|
|
243
|
+
# 5 → 4(2026-10-06):4 token 的「claude code mcp tutorial」不过门,
|
|
244
|
+
# 实体名(Claude)+ 主题词(tutorial)被 ai_model 锁死,返回模型规格
|
|
245
|
+
# 还进了 L2 缓存。4 token 已足够构成主题句式;真点查("GPT-4o pricing")
|
|
246
|
+
# 普遍 ≤3 token,且 4 token 点查有意图词豁免保护。
|
|
247
|
+
_INTENT_MIN_TOKENS = 4
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _intent_gate(hits: list[dict[str, Any]], query: str) -> list[dict[str, Any]]:
|
|
251
|
+
"""点查域在「主题句式查询」下让位。
|
|
252
|
+
|
|
253
|
+
点查域(声明了 `intent_required` 的域)语义是「查询主体即目标对象」:
|
|
254
|
+
「pnpm add lodash」「GPT-4o」该走结构化源。但当查询是长主题句
|
|
255
|
+
(去重 token ≥5)且不含该域意图词时,实体词(pnpm/DeepSeek)只是
|
|
256
|
+
上下文,域命中属词面误抢——2026-09-06 实测:「pnpm file: directory
|
|
257
|
+
dependency no content hash reinstall」(无意图词)被锁死 pypi,
|
|
258
|
+
返回单字垃圾包且骗过覆盖守卫早停;「DeepSeek Harness DSH 插件开发」
|
|
259
|
+
被锁死 models_dev。让位后主域由次域 / TF-IDF / 通用组合接管。
|
|
260
|
+
|
|
261
|
+
判别优先级:面查信号 > 意图豁免 > token 门槛。面查信号命中即让位
|
|
262
|
+
(「npm 包 安装 报错」意图是解决报错,不是找包);意图词命中则豁免
|
|
263
|
+
(「python 环境安装 requests 库」确实是包查询)。
|
|
264
|
+
"""
|
|
265
|
+
if not hits:
|
|
266
|
+
return hits
|
|
267
|
+
if not any(d.get("_intent") is not None for d in hits):
|
|
268
|
+
return hits # 没有点查域参与:连分词都不必算(本函数最常见的路径)
|
|
269
|
+
try:
|
|
270
|
+
from tfidf_router import tokenize
|
|
271
|
+
n_tokens = len(set(tokenize(query)))
|
|
272
|
+
except Exception:
|
|
273
|
+
return hits # 分词不可用不设卡(fail-open,同覆盖守卫口径)
|
|
274
|
+
if n_tokens < _INTENT_MIN_TOKENS:
|
|
275
|
+
return hits
|
|
276
|
+
diffuse_hit = re.search(_DIFFUSE_SIGNAL_RE, query) is not None
|
|
277
|
+
kept: list[dict[str, Any]] = []
|
|
278
|
+
for d in hits:
|
|
279
|
+
intent_re = d.get("_intent")
|
|
280
|
+
if intent_re is None:
|
|
281
|
+
kept.append(d)
|
|
282
|
+
continue
|
|
283
|
+
if diffuse_hit:
|
|
284
|
+
continue # 面查信号压过意图豁免:报错/排查/对比类走通用
|
|
285
|
+
if intent_re.search(query):
|
|
286
|
+
kept.append(d)
|
|
287
|
+
continue
|
|
288
|
+
continue # 长主题句 + 无意图词:实体词只是上下文,让位
|
|
289
|
+
return kept
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
# ── 对外入口 ──────────────────────────────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
def match_domains(query: str, domains: list[dict[str, Any]] | None = None,
|
|
295
|
+
max_n: int = 3,
|
|
296
|
+
primary_lang: str | None = None) -> list[dict[str, Any]]:
|
|
297
|
+
"""按 config.yaml domains 顺序返回全部命中域(多意图,主域 1 + 次域 max_n-1)。
|
|
298
|
+
|
|
299
|
+
旧 match_domain 单射只取首个命中域,多意图查询(如「北京 AI 公司融资」同时
|
|
300
|
+
命中 geo/finance/tech)只走一个域。本函数返回命中列表供 route 主域执行 +
|
|
301
|
+
次域按预算补充。catch-all(无 patterns)只做垫底:有命中时不掺入。
|
|
302
|
+
max_n 限制命中数,防止正则宽泛的域批量命中稀释主域。
|
|
303
|
+
|
|
304
|
+
primary_lang:查询主语言(来自 extract_features)。当为明确的非中文语言
|
|
305
|
+
(ja/ko/en/latin/cyrillic/thai 等)时,跳过中文内容域白名单,避免韩/日查询
|
|
306
|
+
被中文泛内容域误捕获。传 None 时不做门控(兼容旧调用方)。
|
|
307
|
+
|
|
308
|
+
三阶段顺序(截断 → 结构化域提前 → 意图门)是契约,见模块 docstring。
|
|
309
|
+
"""
|
|
310
|
+
if domains is None:
|
|
311
|
+
domains = get_domains()
|
|
312
|
+
compiled = _get_compiled_domains(domains)
|
|
313
|
+
hits: list[dict[str, Any]] = []
|
|
314
|
+
catch_all: dict[str, Any] | None = None
|
|
315
|
+
non_zh = bool(primary_lang and primary_lang not in ("zh", "mixed", "other"))
|
|
316
|
+
for domain in compiled:
|
|
317
|
+
if not domain.get("patterns", []):
|
|
318
|
+
catch_all = domain
|
|
319
|
+
continue
|
|
320
|
+
if non_zh and domain.get("name") in _ZH_LANG_GATED_DOMAINS:
|
|
321
|
+
continue
|
|
322
|
+
if _domain_fires(domain, query):
|
|
323
|
+
hits.append(domain)
|
|
324
|
+
if len(hits) >= max_n:
|
|
325
|
+
break
|
|
326
|
+
hits = _social_domain_first(hits)
|
|
327
|
+
hits = _intent_gate(hits, query)
|
|
328
|
+
return hits if hits else ([catch_all] if catch_all else [])
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def match_domain(query: str, domains: list[dict[str, Any]] | None = None) -> dict[str, Any] | None:
|
|
332
|
+
"""兼容旧接口:返回首个命中域(或 catch-all)。"""
|
|
333
|
+
hits = match_domains(query, domains, max_n=1)
|
|
334
|
+
return hits[0] if hits else None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def intent_gated_domains(domains: list[dict[str, Any]] | None = None) -> set[str]:
|
|
338
|
+
"""声明了 `intent_required` 的域名集合(回归门用:这份名单必须保持最小)。"""
|
|
339
|
+
if domains is None:
|
|
340
|
+
domains = get_domains()
|
|
341
|
+
return {d.get("name", "") for d in domains if d.get("intent_required")}
|