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
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
根据查询特征将请求路由到最优本地引擎组合,覆盖:
|
|
5
5
|
- 中文通用 / 中文新闻 / 代码 / 学术 / 参考百科 / 事实问答 / 垂直实体
|
|
6
|
-
- 与 unified-search 的 route.py
|
|
6
|
+
- 与 unified-search 的 route.py 拆开,local-search 内部使用
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
9
|
from __future__ import annotations
|
|
@@ -22,6 +22,8 @@ if not logger.handlers:
|
|
|
22
22
|
|
|
23
23
|
# 查询特征正则
|
|
24
24
|
_RE_CHINESE = re.compile(r"[一-鿿]")
|
|
25
|
+
_RE_KANA = re.compile(r"[\u3040-\u30ff]")
|
|
26
|
+
_RE_HANGUL = re.compile(r"[\uac00-\ud7af]")
|
|
25
27
|
_RE_ACADEMIC = re.compile(
|
|
26
28
|
r"\b(paper|arxiv|preprint|doi|citation|abstract|journal|conference|"
|
|
27
29
|
r"peer[-\s]?review|research|survey|review|thesis|dissertation)\b|"
|
|
@@ -60,10 +62,23 @@ CATEGORY_PRIORITY = {
|
|
|
60
62
|
"academic": ["local_arxiv", "local_semantic_scholar", "local_crossref", "local_pubmed"],
|
|
61
63
|
"code": ["local_github", "local_stackoverflow", "local_gitlab", "local_npm"],
|
|
62
64
|
"news": ["local_bing_news", "local_google_news", "local_duckduckgo_news"],
|
|
63
|
-
|
|
65
|
+
# chinese 与主路径 route_combo._select_sub_engines 中文支对齐(2026-10-06 实测):
|
|
66
|
+
# baidu 直连 0 结果移出;sogou(~1s 相关)/360(~1s)实测可用;yandex 殿后。
|
|
67
|
+
"chinese": ["local_sogou", "local_360", "local_yandex", "local_bing", "local_duckduckgo"],
|
|
68
|
+
# ja:yandex 实测覆盖最好;bing text 后端已被 ddgs 9.14.4 移除(auto 降级)
|
|
69
|
+
"japanese": ["local_yandex", "local_duckduckgo", "local_bing"],
|
|
70
|
+
# ko 同 ja
|
|
71
|
+
"korean": ["local_yandex", "local_duckduckgo"],
|
|
64
72
|
"reference": ["local_wikipedia", "local_wiktionary", "local_wikiquote"],
|
|
65
73
|
"vertical": ["local_openstreetmap", "local_imdb", "local_goodreads"],
|
|
66
|
-
|
|
74
|
+
# web_general 顺序 = 实测可用性(2026-10-06 重排):
|
|
75
|
+
# ddgs 9.14.4 起 text 后端仅 brave/duckduckgo/google/grokipedia/
|
|
76
|
+
# mojeek/startpage/wikipedia/yahoo/yandex——没有 bing,local_bing 的
|
|
77
|
+
# `-b bing` 已静默降级 auto(1.6-6s 随机波动,拖早停第二引擎的尾),
|
|
78
|
+
# 故移出 web_general(chinese 域仍保留作第 4 备选)。
|
|
79
|
+
# brave 对真实查询稳定 0 结果;yandex 是唯一稳定后端(~1.9s)置首,
|
|
80
|
+
# ddg/yahoo 次之(限流时 1s 级快速失败,不拖墙钟)。
|
|
81
|
+
"web_general": ["local_yandex", "local_duckduckgo", "local_yahoo", "local_brave", "local_bing"],
|
|
67
82
|
}
|
|
68
83
|
|
|
69
84
|
|
|
@@ -71,9 +86,29 @@ def extract_features(query: str) -> dict[str, Any]:
|
|
|
71
86
|
"""提取查询特征向量。"""
|
|
72
87
|
total = max(len(query), 1)
|
|
73
88
|
chinese = len(_RE_CHINESE.findall(query))
|
|
89
|
+
# 主语言判定:优先复用 scripts/lang_detect 单来源
|
|
90
|
+
primary_lang = "en"
|
|
91
|
+
try:
|
|
92
|
+
from lang_detect import detect_language
|
|
93
|
+
primary_lang = detect_language(query)
|
|
94
|
+
except ImportError:
|
|
95
|
+
kana = len(_RE_KANA.findall(query))
|
|
96
|
+
hangul = len(_RE_HANGUL.findall(query))
|
|
97
|
+
if kana / total >= 0.15 or (kana and chinese / total >= 0.3):
|
|
98
|
+
primary_lang = "ja"
|
|
99
|
+
elif hangul / total >= 0.15:
|
|
100
|
+
primary_lang = "ko"
|
|
101
|
+
elif chinese / total >= 0.3:
|
|
102
|
+
primary_lang = "zh"
|
|
103
|
+
else:
|
|
104
|
+
primary_lang = "en"
|
|
74
105
|
return {
|
|
75
106
|
"chinese_ratio": chinese / total,
|
|
76
|
-
|
|
107
|
+
# is_chinese:仅当真主语言为 zh 时标中文(日文汉字查询不再误入 chinese 分类)
|
|
108
|
+
"is_chinese": primary_lang == "zh" or (
|
|
109
|
+
primary_lang not in ("ja", "ko") and chinese / total > 0.3
|
|
110
|
+
),
|
|
111
|
+
"primary_lang": primary_lang,
|
|
77
112
|
"is_academic": bool(_RE_ACADEMIC.search(query)),
|
|
78
113
|
"is_code": bool(_RE_CODE.search(query)),
|
|
79
114
|
"is_news": bool(_RE_NEWS.search(query)),
|
|
@@ -116,6 +151,11 @@ def route_query(
|
|
|
116
151
|
|
|
117
152
|
# 特征 → 候选分类
|
|
118
153
|
candidates: list[tuple[str, float]] = []
|
|
154
|
+
# 日/韩主语言:优先对应语言的本地引擎分类
|
|
155
|
+
if features.get("primary_lang") == "ja":
|
|
156
|
+
candidates.append(("japanese", 1.0))
|
|
157
|
+
elif features.get("primary_lang") == "ko":
|
|
158
|
+
candidates.append(("korean", 1.0))
|
|
119
159
|
if features["is_academic"]:
|
|
120
160
|
candidates.append(("academic", 1.0))
|
|
121
161
|
if features["is_code"]:
|
|
@@ -130,13 +170,13 @@ def route_query(
|
|
|
130
170
|
# 事实查询优先百科与通用搜索
|
|
131
171
|
candidates.append(("reference", 0.85))
|
|
132
172
|
candidates.append(("web_general", 0.7))
|
|
133
|
-
if features["is_chinese"]:
|
|
173
|
+
if features["is_chinese"] and features.get("primary_lang") != "ja":
|
|
134
174
|
candidates.append(("chinese", 0.85))
|
|
135
175
|
# 中文新闻类查询增强
|
|
136
176
|
if features["is_news"]:
|
|
137
177
|
candidates.append(("news", 0.9))
|
|
138
178
|
|
|
139
|
-
#
|
|
179
|
+
# 保底
|
|
140
180
|
if not candidates:
|
|
141
181
|
candidates.append(("web_general", 0.5))
|
|
142
182
|
|
|
@@ -168,7 +208,7 @@ def route_query(
|
|
|
168
208
|
if len(selected) >= max_engines:
|
|
169
209
|
break
|
|
170
210
|
|
|
171
|
-
#
|
|
211
|
+
# 保底:如果都没选到,返回前几个启用的可用引擎
|
|
172
212
|
if not selected:
|
|
173
213
|
selected = reg.list_engines(available_only=require_available, enabled_only=True)[:max_engines]
|
|
174
214
|
reasons = [f"{e}(fallback)" for e in selected]
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: local-seek
|
|
3
|
+
version: 1.6.0
|
|
4
|
+
description: 本机文件与代码内容搜索(rg/fd/mdfind/grep 统一入口 seek.py,工具输出即答案)。与 argo 联网搜索互补:搜本机目录、代码符号、文件名、文档全文、文件结构。触发:搜本地、找文件、找代码、本机搜 xx、代码在哪、统计本地命中。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# local-seek — 本地高效搜索(v1.6.0)
|
|
8
|
+
|
|
9
|
+
搜本机文件和内容就从这里开始。**工具的输出就是答案**,不用再自己翻一遍。
|
|
10
|
+
与 Argo(联网搜索)分工:Argo 搜网络,本技能搜本机。
|
|
11
|
+
|
|
12
|
+
## 三层递进:先定位、再看上下文、最后精读
|
|
13
|
+
|
|
14
|
+
不要一上来就读文件。一层层往里走,每层的结果都不多:
|
|
15
|
+
|
|
16
|
+
| 层 | 干什么 | 命令 | 输出量 |
|
|
17
|
+
|----|--------|------|--------|
|
|
18
|
+
| L1 定位 | 找到「在哪个文件」 | seek.py "词" --count 或 --filename | 每文件一行 |
|
|
19
|
+
| L2 上下文 | 看命中位置附近 | seek.py "词" --context 2 | 每命中 3 行 |
|
|
20
|
+
| L3 精读 | 读关键段落 | read_file 按行号局部读取 | 按需 |
|
|
21
|
+
|
|
22
|
+
每次搜索先想清楚停在哪一层,默认只做第一层。
|
|
23
|
+
|
|
24
|
+
## 统一入口
|
|
25
|
+
|
|
26
|
+
统一走 `scripts/seek.py`(相对 argo 根:`sub-skills/local-seek/scripts/seek.py`),
|
|
27
|
+
由它选工具,不要自己拼 rg/fd 参数:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# 以下以 argo 安装根为 cwd;或写死 $ARGO_HOME/sub-skills/local-seek/scripts/seek.py
|
|
31
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" # 默认:当前目录全文
|
|
32
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --path ~/notes # 指定目录
|
|
33
|
+
python3 sub-skills/local-seek/scripts/seek.py "词" --path ~/.agents --dot # 连 . 开头目录与软链一起搜;搜 ~/.agents、~/.zcode 要加(Spotlight 收不到)
|
|
34
|
+
python3 sub-skills/local-seek/scripts/seek.py "词" --path . --include-noise # repos/、tests/ 等与真源平权(默认排真源之后,仍搜得到)
|
|
35
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --count # 先数命中,再决定是否深入
|
|
36
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --filename # 按文件名
|
|
37
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --spotlight # 全盘兜底(PDF/邮件/笔记)
|
|
38
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --type py,ts
|
|
39
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --json # 结构化输出
|
|
40
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --exact # 关闭中文扩展,精确匹配
|
|
41
|
+
python3 sub-skills/local-seek/scripts/seek.py "查询词" --exclude 某文件 # 额外排除(可重复)
|
|
42
|
+
python3 sub-skills/local-seek/scripts/seek.py --outline 文件路径 # 文件结构(def/class/标题/顶层key)
|
|
43
|
+
python3 sub-skills/local-seek/scripts/seek.py --lines 10-50 文件路径 # 按行读取,替代 read_file 全文
|
|
44
|
+
python3 sub-skills/local-seek/scripts/seek.py "裸except" --structural # 结构搜索(空catch/裸except/装饰函数等)
|
|
45
|
+
python3 sub-skills/local-seek/scripts/seek.py --git-log 文件路径 # 文件的最近提交历史
|
|
46
|
+
python3 sub-skills/local-seek/scripts/seek.py --git-blame 12 文件路径 # 第 12 行的提交归属
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
内置智能行为(固定字符串回退、中文 2-gram 扩展、PCRE2 检测、结构搜索、
|
|
50
|
+
git 联动等)自动生效,明细见 references/auto-behaviors.md。
|
|
51
|
+
|
|
52
|
+
## 工具选择(何时换工具)
|
|
53
|
+
|
|
54
|
+
| 场景 | 用 | 为什么 |
|
|
55
|
+
|------|-----|--------|
|
|
56
|
+
| 搜代码/正文关键词 | rg(默认) | 毫秒级,尊重 .gitignore,自动排除 node_modules 等 |
|
|
57
|
+
| 只记得文件名 | --filename(fd) | 按文件名模糊匹配 |
|
|
58
|
+
| 搜 PDF/邮件/已归档内容 | --spotlight(mdfind) | 用系统已建好的索引,不额外花时间(Windows/Linux 无此工具,改用 rg) |
|
|
59
|
+
| 找代码模式(裸except/空catch等) | --structural | 按语义不按字符串 |
|
|
60
|
+
| 追文件历史/单行归属 | --git-log / --git-blame | 免开终端敲 git |
|
|
61
|
+
| 当前目录搜不到 | 先扩大 --path,再 --spotlight | 先窄后宽 |
|
|
62
|
+
| 大仓库担心输出爆炸 | --count + --max 20 | 先看分布再深入 |
|
|
63
|
+
|
|
64
|
+
## 执行纪律
|
|
65
|
+
|
|
66
|
+
1. **先窄后宽**:先限定目录/扩展名,搜不到再扩大。禁止一开始就全盘扫。
|
|
67
|
+
2. **先数后看**:--count 看分布,--context 0(默认)看命中行,最后才读文件。
|
|
68
|
+
3. **指定类型**:代码场景加 --type(py,ts,go…),文档场景用 --scope doc。
|
|
69
|
+
4. **绝对路径优先**:涉及读文件时用 read_file + 绝对路径。
|
|
70
|
+
5. **不读整个文件**:L3 精读用行号偏移局部读取,命中上下文不够再扩。
|
|
71
|
+
6. **结果为空先换词**:换同义词/拆词/去大小写,再换工具(fd→mdfind),
|
|
72
|
+
不要重复同一查询。
|
|
73
|
+
7. **先结构后正文**:大文件先 --outline 看结构,再 --lines N-M 按需读取,
|
|
74
|
+
禁止直接 read_file 整个文件。
|
|
75
|
+
8. **结构搜索按语义**:找「是不是有裸 except」这类问题用 --structural,
|
|
76
|
+
不要手写正则碰运气;可用语义清单见 references/structural-search.md。
|
|
77
|
+
9. **git 只读标题**:--git-log 只给标题,够用就停;要看具体改动再手动 git show。
|
|
78
|
+
|
|
79
|
+
## 排序与性能(v1.6.0)
|
|
80
|
+
|
|
81
|
+
内容命中按 **BM25F 相关度重排**(实现文件不再被文档顺带提及淹没),池子按
|
|
82
|
+
**文件轮转配额**(每文件 ≤8 行);文件名场景用 **fzf FuzzyMatchV2** 评分。
|
|
83
|
+
确定性不变:与 rg 发射序无关,并列分按 (路径,行号)。
|
|
84
|
+
|
|
85
|
+
大树上的内容搜索可开 **FTS5 索引快路**:`ARGO_LOCAL_FTS=1`(默认关),
|
|
86
|
+
首次对该目录先跑 `scripts/seek_fts.py rebuild <目录>` 预热,之后毫秒级返回;
|
|
87
|
+
rg 永远兜底(索引未建成/带正则/零命中时自动回退)。明细见
|
|
88
|
+
references/auto-behaviors.md。
|
|
89
|
+
|
|
90
|
+
逃生门(默认行为已是最优,仅在回退/实验时用):
|
|
91
|
+
- `ARGO_SEEK_CONTENT_RERANK=0` 关闭内容重排,恢复 (路径,行号) 原序
|
|
92
|
+
- `ARGO_SEEK_FRECENCY=1` 开启访问频次加成(实验项,默认关)
|
|
93
|
+
- 排序质量用 `scripts/eval_rank.py` 评测(MRR/耗时,支持 --compare)
|
|
94
|
+
|
|
95
|
+
## 配置
|
|
96
|
+
|
|
97
|
+
排除规则与知识域:`sub-skills/local-seek/config/domains.yaml`
|
|
98
|
+
查看当前规则:`python3 sub-skills/local-seek/scripts/seek.py --domains`
|
|
99
|
+
|
|
100
|
+
## 参考
|
|
101
|
+
|
|
102
|
+
按场景的完整命令配方(进阶,非常规操作再读):
|
|
103
|
+
|
|
104
|
+
- references/rg-recipes.md — rg 分场景配方(函数定义/跨文件引用/多词/正则)
|
|
105
|
+
- references/auto-behaviors.md — 自动行为明细(重排/FTS/中文扩展/PCRE2/git)
|
|
106
|
+
- references/fd-mdfind-recipes.md — fd 与 Spotlight 配方
|
|
107
|
+
- references/strategies.md — 三层做法的详细说明,以及怎么少花 token
|
|
108
|
+
- references/structural-search.md — 结构搜索手册(语义规则表/别名/扩展新规则)
|
|
109
|
+
- references/git-integration.md — git 联动用法、场景与边界
|
|
110
|
+
- references/remote-handoff.md — 本地搜不到时的扩大与联网交接清单
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# local-seek 搜索配置
|
|
2
|
+
# 修改后即时生效,无需重启。
|
|
3
|
+
|
|
4
|
+
# 附加排除规则(追加到内置默认排除之后)
|
|
5
|
+
# 内置默认排除:node_modules .git __pycache__ .venv dist build target 等
|
|
6
|
+
exclude:
|
|
7
|
+
- "*.min.js"
|
|
8
|
+
- "*.map"
|
|
9
|
+
- "*.lock"
|
|
10
|
+
- ".turbo"
|
|
11
|
+
- ".nyc_output"
|
|
12
|
+
- "*.egg-info"
|
|
13
|
+
|
|
14
|
+
# 知识域:经常要搜的目录,填在这里可被 --domains 查询
|
|
15
|
+
# 示例:- ~/Documents/notes
|
|
16
|
+
knowledge_domains: []
|
|
17
|
+
|
|
18
|
+
# 输出控制
|
|
19
|
+
max_results: 30 # 默认最大结果数
|
|
20
|
+
snippet_chars: 120 # 命中行截断长度
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""eval_rank.py — local-seek 排序质量分级评测 + 计时基准(离线,零依赖)。
|
|
3
|
+
|
|
4
|
+
与 eval_seek.py 的分工:eval_seek 检查「有没有命中」(hit/no-hit 契约),
|
|
5
|
+
本文件回答「命中的**排序**对不对」——内容重排(BM25F)与文件名 fzf V2 的
|
|
6
|
+
改动前后用同一批用例对比,产出两类指标:
|
|
7
|
+
|
|
8
|
+
质量 每个用例记录 top-N (path, line) 实况,并按期望文件集算
|
|
9
|
+
topk_hit(期望文件进 top-K 的用例占比)与 MRR(倒数排名均值)。
|
|
10
|
+
速度 固定查询集在固定语料上跑 run_query 的中位耗时(毫秒)。
|
|
11
|
+
|
|
12
|
+
期望值来自「实现所在文件」这一弱真值(改动前用 rg -l 逐一核实过),
|
|
13
|
+
不是人工标注的全序金标——它的用途是**守护不退步 + 观察位移**,
|
|
14
|
+
不是证明绝对质量。绝对质量请配合 eval_seek.py 的命中契约一起读。
|
|
15
|
+
|
|
16
|
+
用法:
|
|
17
|
+
python3 eval_rank.py # 人读报告(stdout)
|
|
18
|
+
python3 eval_rank.py --json out.json # 同时写 JSON(供 A/B 对比)
|
|
19
|
+
python3 eval_rank.py --compare a.json b.json # 对比两份报告,给出逐用例位移
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import argparse
|
|
25
|
+
import json
|
|
26
|
+
import statistics
|
|
27
|
+
import sys
|
|
28
|
+
import time
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
|
|
31
|
+
SCRIPTS = Path(__file__).resolve().parent
|
|
32
|
+
sys.path.insert(0, str(SCRIPTS))
|
|
33
|
+
|
|
34
|
+
import seek as s # noqa: E402
|
|
35
|
+
|
|
36
|
+
# 语料根从本文件位置推导(scripts/ → local-seek/ → sub-skills/ → 仓根),
|
|
37
|
+
# 此前硬编码 ~/.agents/skills/argo:换机器/克隆路径不同就全 miss(MRR 全 0
|
|
38
|
+
# 却看不出是路径病)。SKILLS_ROOT 保留给「跨仓搜索」类用例的常见安装位,
|
|
39
|
+
# 不存在时相关用例自然 0 命中(诚实反映环境,不伪造)。
|
|
40
|
+
_ARGO_ROOT = Path(__file__).resolve().parents[3]
|
|
41
|
+
ARGO_ROOT = str(_ARGO_ROOT)
|
|
42
|
+
SKILLS_ROOT = str(_ARGO_ROOT.parent)
|
|
43
|
+
|
|
44
|
+
# ── 分级用例 ─────────────────────────────────────────────────────────────────
|
|
45
|
+
# expect:文件路径后缀集合,任一命中即算该用例 top 命中(MRR 取最高排名)。
|
|
46
|
+
# 期望文件在改动前经 rg -l 核实确实包含查询词。
|
|
47
|
+
CASES = [
|
|
48
|
+
# 锚点用例(topk=5):实现文件是该查询的明确最优,排名恶化即回归
|
|
49
|
+
{"name": "文件级-噪声档实现", "argv": ["噪声档 降权", "--path", ARGO_ROOT],
|
|
50
|
+
"expect": ["sub-skills/local-seek/scripts/seek.py"], "topk": 8},
|
|
51
|
+
{"name": "文件级-pinyin实现", "argv": ["pinyin_initials 拼音", "--path", ARGO_ROOT],
|
|
52
|
+
"expect": ["sub-skills/local-seek/scripts/seek.py"], "topk": 5},
|
|
53
|
+
{"name": "文件级-rg_search定义", "argv": ["def rg_search", "--path", ARGO_ROOT],
|
|
54
|
+
"expect": ["sub-skills/local-seek/scripts/seek.py"], "topk": 5},
|
|
55
|
+
{"name": "文件级-cache键域隔离", "argv": ["cache_key_vdom", "--path", ARGO_ROOT],
|
|
56
|
+
"expect": ["scripts/cache_key_vdom.py"], "topk": 5},
|
|
57
|
+
{"name": "文件级-hash_token实现", "argv": ["def _hash_token", "--path", ARGO_ROOT],
|
|
58
|
+
"expect": ["scripts/cache.py"], "topk": 5},
|
|
59
|
+
# 守卫用例(topk=8):行级语义下文档密集行同样合法,只守「实现文件仍在
|
|
60
|
+
# top-8 内可被看见」,防实现被彻底挤出视野的回归
|
|
61
|
+
{"name": "守卫-语义缓存minhash", "argv": ["语义缓存 minhash 软命中", "--path", ARGO_ROOT],
|
|
62
|
+
"expect": ["scripts/cache.py", "scripts/search_rank.py"], "topk": 8},
|
|
63
|
+
{"name": "守卫-BM25实现", "argv": ["BM25Okapi k1", "--path", SKILLS_ROOT],
|
|
64
|
+
"expect": ["sub-skills/local-search/bm25.py"], "topk": 8},
|
|
65
|
+
{"name": "守卫-中文聚合", "argv": ["本地搜索 聚合", "--path", SKILLS_ROOT],
|
|
66
|
+
"expect": ["sub-skills/local-search/search_v3.py"], "topk": 8},
|
|
67
|
+
{"name": "守卫-混排查询", "argv": ["性能 asyncio 教程", "--path", ARGO_ROOT],
|
|
68
|
+
"expect": ["sub-skills"], "topk": 8},
|
|
69
|
+
# 行级用例:期望「定义块」而非首次提及出现在 top 行
|
|
70
|
+
{"name": "行级-NOISE_TIER定义", "argv": ["NOISE_TIER = [", "--path", ARGO_ROOT],
|
|
71
|
+
"expect_line_range": ("sub-skills/local-seek/scripts/seek.py", 70, 130), "topk": 5},
|
|
72
|
+
{"name": "行级-K1常数", "argv": ["_K1 = 1.5", "--path", SKILLS_ROOT],
|
|
73
|
+
"expect_line_range": ("bm25.py", 40, 90),
|
|
74
|
+
"expect_alt_line_range": ("seek_rank.py", 40, 90), "topk": 5},
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# ── 计时用例(中位数,含 1 次预热不计入)─────────────────────────────────────
|
|
78
|
+
TIMING = [
|
|
79
|
+
{"name": "中文短词", "argv": ["噪声档", "--path", ARGO_ROOT]},
|
|
80
|
+
{"name": "英文高频词", "argv": ["import", "--path", ARGO_ROOT]},
|
|
81
|
+
{"name": "多词混排", "argv": ["def run_query timeout", "--path", ARGO_ROOT]},
|
|
82
|
+
{"name": "skills根宽查询", "argv": ["search", "--path", SKILLS_ROOT, "--dot"]},
|
|
83
|
+
{"name": "文件名查询", "argv": ["seek", "--path", ARGO_ROOT, "--filename"]},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
N_RUNS = 5
|
|
87
|
+
TOP_N = 8
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _run_case(argv: list[str]) -> tuple[list[dict], int]:
|
|
91
|
+
"""跑一次 run_query --json,返回 (results 原序, elapsed_ms)。
|
|
92
|
+
|
|
93
|
+
自污染防护(与 eval_seek.py 同款):用例词会命中本文件与 eval_seek.py
|
|
94
|
+
的用例定义行,统一注入 --exclude 排除自身。
|
|
95
|
+
"""
|
|
96
|
+
text, rc = s.run_query(argv + ["--exclude", "eval_rank.py",
|
|
97
|
+
"--exclude", "eval_seek.py",
|
|
98
|
+
"--json", "--max", str(TOP_N)], time_budget=10)
|
|
99
|
+
try:
|
|
100
|
+
payload = json.loads(text)
|
|
101
|
+
return payload.get("results", []), int(payload.get("elapsed_ms", 0))
|
|
102
|
+
except (json.JSONDecodeError, TypeError):
|
|
103
|
+
return [], rc
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def run_cases() -> dict:
|
|
107
|
+
out = {}
|
|
108
|
+
for case in CASES:
|
|
109
|
+
results, _ = _run_case(case["argv"])
|
|
110
|
+
top = [(r.get("path", ""), r.get("line", 0)) for r in results[:TOP_N]]
|
|
111
|
+
entry: dict = {"top": top}
|
|
112
|
+
if "expect" in case:
|
|
113
|
+
rank = 0
|
|
114
|
+
for i, (p, _ln) in enumerate(top, 1):
|
|
115
|
+
if any(p.endswith(e) or e in p for e in case["expect"]):
|
|
116
|
+
rank = i
|
|
117
|
+
break
|
|
118
|
+
entry["expect_rank"] = rank # 0 = 未进 top-N
|
|
119
|
+
elif "expect_line_range" in case:
|
|
120
|
+
ranges = [case["expect_line_range"]]
|
|
121
|
+
alt = case.get("expect_alt_line_range")
|
|
122
|
+
if alt:
|
|
123
|
+
ranges.append(alt)
|
|
124
|
+
rank = 0
|
|
125
|
+
for i, (p, ln) in enumerate(top, 1):
|
|
126
|
+
if any(p.endswith(fp) and lo <= int(ln or 0) <= hi
|
|
127
|
+
for fp, lo, hi in ranges):
|
|
128
|
+
rank = i
|
|
129
|
+
break
|
|
130
|
+
entry["expect_rank"] = rank
|
|
131
|
+
out[case["name"]] = entry
|
|
132
|
+
return out
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def run_timing() -> dict:
|
|
136
|
+
out = {}
|
|
137
|
+
for case in TIMING:
|
|
138
|
+
_run_case(case["argv"]) # 预热(页缓存/解释器路径)
|
|
139
|
+
samples = []
|
|
140
|
+
for _ in range(N_RUNS):
|
|
141
|
+
t0 = time.perf_counter()
|
|
142
|
+
_run_case(case["argv"])
|
|
143
|
+
samples.append((time.perf_counter() - t0) * 1000)
|
|
144
|
+
out[case["name"]] = round(statistics.median(samples), 1)
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def summarize(cases: dict) -> dict:
|
|
149
|
+
"""topk_hit 按每用例自己的 topk 门槛计(锚点 5 / 守卫 8)。"""
|
|
150
|
+
by_name = {c["name"]: c for c in CASES}
|
|
151
|
+
hits, total, mrr = 0, 0, 0.0
|
|
152
|
+
for name, v in cases.items():
|
|
153
|
+
rank = v.get("expect_rank", 0)
|
|
154
|
+
topk = by_name.get(name, {}).get("topk", 5)
|
|
155
|
+
total += 1
|
|
156
|
+
if 0 < rank <= topk:
|
|
157
|
+
hits += 1
|
|
158
|
+
if rank:
|
|
159
|
+
mrr += 1.0 / rank
|
|
160
|
+
return {"topk_hit": hits, "n_cases": total, "MRR": round(mrr / total, 4) if total else 0.0}
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def compare(a: dict, b: dict) -> None:
|
|
164
|
+
"""对比两份 JSON 报告:b 相对 a 的逐用例位移(负 = 排名变好)。"""
|
|
165
|
+
ca, cb = a.get("cases", {}), b.get("cases", {})
|
|
166
|
+
print(f"{'用例':24s} {'before':>8s} {'after':>8s} 位移")
|
|
167
|
+
for name in ca:
|
|
168
|
+
ra = ca[name].get("expect_rank", 0)
|
|
169
|
+
rb = cb.get(name, {}).get("expect_rank", 0)
|
|
170
|
+
delta = (rb - ra) if (ra and rb) else 0
|
|
171
|
+
mark = "↑" if rb and (not ra or rb < ra) else ("↓" if rb and ra and rb > ra
|
|
172
|
+
else ("=" if rb == ra else "!"))
|
|
173
|
+
print(f"{name:24s} {ra or '—':>8} {rb or '—':>8} {mark} {delta:+d}")
|
|
174
|
+
ta, tb = a.get("timing", {}), b.get("timing", {})
|
|
175
|
+
print("\n计时(中位 ms,after / before,比值):")
|
|
176
|
+
for k in ta:
|
|
177
|
+
va, vb = ta[k], tb.get(k, 0)
|
|
178
|
+
ratio = f"{vb / va:.2f}x" if va else "—"
|
|
179
|
+
print(f" {k:16s} {vb:8.1f} / {va:8.1f} ({ratio})")
|
|
180
|
+
print(f"\n质量汇总 before: {summarize(ca)}")
|
|
181
|
+
print(f"质量汇总 after : {summarize(cb)}")
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def main() -> int:
|
|
185
|
+
ap = argparse.ArgumentParser(description="local-seek 排序质量分级评测")
|
|
186
|
+
ap.add_argument("--json", metavar="PATH", help="同时写 JSON 报告")
|
|
187
|
+
ap.add_argument("--compare", nargs=2, metavar=("A", "B"),
|
|
188
|
+
help="对比两份 JSON 报告后退出")
|
|
189
|
+
args = ap.parse_args()
|
|
190
|
+
if args.compare:
|
|
191
|
+
a = json.loads(Path(args.compare[0]).read_text(encoding="utf-8"))
|
|
192
|
+
b = json.loads(Path(args.compare[1]).read_text(encoding="utf-8"))
|
|
193
|
+
compare(a, b)
|
|
194
|
+
return 0
|
|
195
|
+
cases = run_cases()
|
|
196
|
+
timing = run_timing()
|
|
197
|
+
report = {"cases": cases, "timing": timing, "summary": summarize(cases),
|
|
198
|
+
"top_n": TOP_N, "n_runs": N_RUNS}
|
|
199
|
+
print(f"质量汇总: {report['summary']}")
|
|
200
|
+
for name, v in cases.items():
|
|
201
|
+
rank = v.get("expect_rank", 0)
|
|
202
|
+
print(f" [{rank if rank else '—':>2}] {name}")
|
|
203
|
+
for p, ln in v["top"][:5]:
|
|
204
|
+
print(f" {p}:{ln}")
|
|
205
|
+
print("\n计时(中位 ms):")
|
|
206
|
+
for k, v in timing.items():
|
|
207
|
+
print(f" {k:16s} {v:8.1f}")
|
|
208
|
+
if args.json:
|
|
209
|
+
Path(args.json).write_text(
|
|
210
|
+
json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
211
|
+
print(f"\nJSON 已写 {args.json}")
|
|
212
|
+
return 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
if __name__ == "__main__":
|
|
216
|
+
sys.exit(main())
|