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
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""query_enhance.py — 首轮查询增强(无 LLM):归一化 + 拆词变体 + 复杂度门控
|
|
3
|
+
|
|
4
|
+
目标:让「简单问题首轮就命中、复杂问题才走多轮」,避免简单问题被拖成多轮
|
|
5
|
+
浪费 token。纯规则,零外部依赖,中英双语。
|
|
6
|
+
|
|
7
|
+
设计(保持一致 Adaptive-RAG 的复杂度分流 + Algolia 的 query expansion/relaxation):
|
|
8
|
+
1. normalize_query 词形规范化:全角→半角、拆斜杠、压多余空格。
|
|
9
|
+
治「型号/日期/分隔符检索命中难」,如 LongCat-2.0/1.6 万亿。
|
|
10
|
+
2. retrieval_variants 生成有序检索变体(归一化主词 + 拆连字符型号词 + 概念/同义词)。
|
|
11
|
+
供「首轮多路召回」用(低复杂度时用少数变体并行补齐召回)。
|
|
12
|
+
3. complexity_gate 复杂度门控:把查询分为 low / medium / high。
|
|
13
|
+
low → 单轮多路召回即可,不放行多轮 recovery;
|
|
14
|
+
high → 允许多轮/全变体(复杂、多跳、对比)。
|
|
15
|
+
|
|
16
|
+
复杂度信号来源:query_understanding 的 intents/confidence/multi_intent_splits + 长度 +
|
|
17
|
+
多跳词(对比/因果/区别/为什么/vs 等)。与 query_understanding 互不相干,仅做启发式分级。
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import re
|
|
23
|
+
|
|
24
|
+
# ── 词形规范化 ──────────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
# 全角标点/空白 → 半角:中文输入常见,易导致精确源(版本号/路径/符号)漏检
|
|
27
|
+
_FULL_TO_HALF = str.maketrans(
|
|
28
|
+
",。!?:;()【】《》“”‘’%+-",
|
|
29
|
+
",.!?:;()[]<>\"\"''%+-",
|
|
30
|
+
)
|
|
31
|
+
# 全角字母数字/符号 → 半角:ABC123 常见于粘贴文本,与标点同批处理
|
|
32
|
+
_FULL_ALNUM = str.maketrans(
|
|
33
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
34
|
+
"abcdefghijklmnopqrstuvwxyz¥~",
|
|
35
|
+
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
36
|
+
"abcdefghijklmnopqrstuvwxyz¥~",
|
|
37
|
+
)
|
|
38
|
+
_FULL_SPACE = str.maketrans("\u3000", " ")
|
|
39
|
+
|
|
40
|
+
# 只拆「版本/模型号」的斜杠(如 LongCat-2.0/1.6 万亿 → LongCat-2.0 1.6 万亿)。
|
|
41
|
+
# 斜杠前必须是「点号分隔的数字」形态(2.0/),据此精准命中版本号,
|
|
42
|
+
# 不碰日期(2023/2024)、分数(1/2)、URL(https://…)、路径、比例、颜色选项等保留
|
|
43
|
+
# 斜杠语义的查询——全量拆斜杠会把 `https://example.com/a/b` 毁成
|
|
44
|
+
# `https: example.com a b`、把「1/2 怎么算」毁成「1 2 怎么算」。
|
|
45
|
+
_MODEL_SLASH = re.compile(r"(?<=[0-9A-Za-z]\.[0-9])\/")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def normalize_query(query: str) -> str:
|
|
49
|
+
"""词形规范化:全角→半角(标点+字母数字)、按「点号版本」拆斜杠、压缩多余空格。
|
|
50
|
+
保留连字符(避免 GPT-5 拆散);仅拆点号版本斜杠,不破坏日期/分数/URL/路径。"""
|
|
51
|
+
if not query or not isinstance(query, str):
|
|
52
|
+
return query
|
|
53
|
+
q = query.translate(_FULL_TO_HALF).translate(_FULL_ALNUM).translate(_FULL_SPACE)
|
|
54
|
+
q = _MODEL_SLASH.sub(" ", q)
|
|
55
|
+
q = re.sub(r"\s+", " ", q).strip()
|
|
56
|
+
return q
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# ── 检索变体(首轮多路召回用)────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
def retrieval_variants(query: str, max_n: int = 3) -> list[str]:
|
|
62
|
+
"""返回有序检索变体:归一化主词 + 拆连字符型号词 + 概念/同义词。
|
|
63
|
+
|
|
64
|
+
max_n 默认 3(低复杂度只取少量,控制成本)。去重、保持稳定顺序。
|
|
65
|
+
"""
|
|
66
|
+
if not query or not isinstance(query, str):
|
|
67
|
+
return []
|
|
68
|
+
base = normalize_query(query)
|
|
69
|
+
out = [base]
|
|
70
|
+
|
|
71
|
+
# 拆连字符型号:LongCat-2.0 → LongCat 2.0(仅字母后接数字,避免拆分普通词)
|
|
72
|
+
split_hyphen = re.sub(r"(?<=[A-Za-z])\-(?=[0-9])", " ", base)
|
|
73
|
+
split_hyphen = re.sub(r"\s+", " ", split_hyphen).strip()
|
|
74
|
+
if split_hyphen and split_hyphen != base:
|
|
75
|
+
out.append(split_hyphen)
|
|
76
|
+
|
|
77
|
+
# 概念/同义变体(复用 query_variants 的概念/缩写表,只取首个替代)
|
|
78
|
+
concept = _concept_variant(base)
|
|
79
|
+
if concept and concept not in out:
|
|
80
|
+
out.append(concept)
|
|
81
|
+
|
|
82
|
+
seen = set()
|
|
83
|
+
uniq = []
|
|
84
|
+
for v in out:
|
|
85
|
+
k = v.strip().lower()
|
|
86
|
+
if k not in seen:
|
|
87
|
+
seen.add(k)
|
|
88
|
+
uniq.append(v)
|
|
89
|
+
return uniq[:max_n]
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _concept_variant(base: str) -> str | None:
|
|
93
|
+
"""把命中的概念/缩写替换为首个等价表述;无命中返回 None。"""
|
|
94
|
+
try:
|
|
95
|
+
from query_variants import CONCEPT_MAP, ACRONYM_MAP
|
|
96
|
+
except ImportError:
|
|
97
|
+
return None
|
|
98
|
+
ql = base.lower()
|
|
99
|
+
for word, alts in CONCEPT_MAP.items():
|
|
100
|
+
if word in ql and alts:
|
|
101
|
+
target = alts[0]
|
|
102
|
+
if target.lower() == ql:
|
|
103
|
+
continue
|
|
104
|
+
return re.sub(r"\b" + re.escape(word) + r"\b", target, base, count=1,
|
|
105
|
+
flags=re.IGNORECASE)
|
|
106
|
+
for word, full in ACRONYM_MAP.items():
|
|
107
|
+
if word in ql:
|
|
108
|
+
return re.sub(r"\b" + re.escape(word) + r"\b", full, base, count=1,
|
|
109
|
+
flags=re.IGNORECASE)
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# ── 复杂度门控 ──────────────────────────────────────────────────────────────
|
|
114
|
+
|
|
115
|
+
# 多跳 / 对比 / 归因信号:这类问题往往需要多轮或全变体位
|
|
116
|
+
_MULTI_HOP = re.compile(
|
|
117
|
+
r"对比|比较|区别|差异|为什么|导致|原因|vs|versus|between|compared|"
|
|
118
|
+
r"impact\s+of|effect\s+of|influence|relationship|影响|关系|关联|如何影响|"
|
|
119
|
+
r"列举|有哪些|总结|回顾|evolution|timeline|历程|归因|走势|趋势",
|
|
120
|
+
re.I,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def complexity_gate(query: str, qu=None) -> str:
|
|
125
|
+
"""返回查询复杂度:low / medium / high。
|
|
126
|
+
|
|
127
|
+
low → 短、单意图、无多跳词、置信度较高 → 单轮多路召回即可,不放行多轮 recovery。
|
|
128
|
+
high → 多跳/对比词 或 多意图 或 较长 → 允许多轮/全变体。
|
|
129
|
+
medium → 其余。
|
|
130
|
+
"""
|
|
131
|
+
if not query or not isinstance(query, str):
|
|
132
|
+
return "low"
|
|
133
|
+
ql = query.strip()
|
|
134
|
+
length = len(ql)
|
|
135
|
+
|
|
136
|
+
multi_hop = bool(_MULTI_HOP.search(ql))
|
|
137
|
+
|
|
138
|
+
intent_cnt = 0
|
|
139
|
+
conf = 0.0
|
|
140
|
+
multi_splits = 0
|
|
141
|
+
if qu is not None:
|
|
142
|
+
intent_cnt = len(getattr(qu, "intents", []) or [])
|
|
143
|
+
conf = float(getattr(qu, "confidence", 0.0) or 0.0)
|
|
144
|
+
multi_splits = len(getattr(qu, "multi_intent_splits", []) or [])
|
|
145
|
+
|
|
146
|
+
if multi_hop or intent_cnt >= 2 or multi_splits >= 2 or length > 60:
|
|
147
|
+
return "high"
|
|
148
|
+
if length <= 24 and intent_cnt <= 1 and not multi_hop:
|
|
149
|
+
return "low"
|
|
150
|
+
if conf >= 0.6 and length <= 30 and not multi_hop:
|
|
151
|
+
return "low"
|
|
152
|
+
return "medium"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def low_complexity(query: str, qu=None) -> bool:
|
|
156
|
+
"""是否为「首轮即可解决」的低复杂度查询。"""
|
|
157
|
+
return complexity_gate(query, qu) == "low"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""query_operators.py — 查询级操作符契约层(2026-10-17,借鉴 DeepSeek Search)。
|
|
3
|
+
|
|
4
|
+
## 解决什么问题
|
|
5
|
+
|
|
6
|
+
Google 风格查询里的操作符(site:/-site:/-词/-"短语"/lang:/after:/before:)
|
|
7
|
+
此前散落四处:引号短语走 phrase_filter、-词走 query_understanding 的否定、
|
|
8
|
+
after/before 只认 CLI flag、lang 靠自动检测、site: 更是只以「域正则触发词」
|
|
9
|
+
的形态存在——结果是一条 `DeepSeek site:github.com` 把路由锁死进 code_search,
|
|
10
|
+
而引擎侧拿到的还是含字面 site: 的脏查询(2026-10-17 实测:github 引擎返回
|
|
11
|
+
GitHub Pages 通用页)。同一能力五种形态、没有一处是契约。
|
|
12
|
+
|
|
13
|
+
## 本层的契约(对照 DeepSeek Search 的透传语义)
|
|
14
|
+
|
|
15
|
+
Argo 侧选择**解析而非透传**:DeepSeek 把操作符原样甩给上游搜索服务,Argo 的
|
|
16
|
+
上游是 260 个异构引擎(多数不认识这些语法),原样透传=把字面 site: 喂给
|
|
17
|
+
不认识它的引擎。故本层抽取为结构化 operators,路由/缓存/查询理解只认
|
|
18
|
+
clean_query(剥掉指令后的意图核心),operators 走三个既有消费点:
|
|
19
|
+
|
|
20
|
+
- langs → route 的语言偏好覆盖(替代自动检测的选择)
|
|
21
|
+
- after/before → 时间窗归一化(--since/--until 的同一判据;CLI flag 优先)
|
|
22
|
+
- exclude_terms → query_understanding 的排除项汇合(融合后过滤)
|
|
23
|
+
- sites/exclude_sites/phrases → v1 只记录可观测(results 的 limitations 提示
|
|
24
|
+
未下推),不污染检索词;按引擎能力下推是后续增量(extra_params 的查询级
|
|
25
|
+
覆盖机制已就位)
|
|
26
|
+
|
|
27
|
+
## 语法(与 DeepSeek 对齐的子集)
|
|
28
|
+
|
|
29
|
+
site:github.com / -site:x.com 站点限定/排除(多个空格分隔按 OR 语义记录)
|
|
30
|
+
-词 / -"短语" 排除关键词/短语
|
|
31
|
+
"短语" 精确短语(保留在 clean_query——它是意图
|
|
32
|
+
本体不是指令,phrase_filter 照常提取)
|
|
33
|
+
lang:zh,en 语言限定(逗号分隔)
|
|
34
|
+
after:2026 / after:2026-01-01 发布时间下界
|
|
35
|
+
before:2026 / before:2026-06-01 发布时间上界
|
|
36
|
+
|
|
37
|
+
纯函数、零依赖、无全局状态。用法:
|
|
38
|
+
from query_operators import parse_query_operators
|
|
39
|
+
clean, ops = parse_query_operators('DeepSeek site:github.com lang:zh after:2026')
|
|
40
|
+
# clean == 'DeepSeek';ops['sites'] == ['github.com'];ops['langs'] == ['zh']
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import re
|
|
45
|
+
import urllib.parse
|
|
46
|
+
from typing import Any
|
|
47
|
+
|
|
48
|
+
# site: 的值:域名形态(字母数字点连字符),到空白/引号/结尾收边
|
|
49
|
+
# site: 值;尾部 OR 连接词仅在「后面还跟 site:」时消费(DeepSeek 文档的
|
|
50
|
+
# 多站点 OR 语义),否则查询里的裸 OR 原样保留。
|
|
51
|
+
_SITE_RE = re.compile(r"(?<![\w-])(-?)site:([a-zA-Z0-9.\-]+)(?:\s+OR(?=\s+site:))?")
|
|
52
|
+
# lang: 的值:逗号分隔的语言代码
|
|
53
|
+
_LANG_RE = re.compile(r"(?<![\w-])lang:([a-zA-Z,]+)")
|
|
54
|
+
# after:/before: 的值:年份或 YYYY-MM-DD
|
|
55
|
+
_DATE_RE = re.compile(r"(?<![\w-])(after|before):(\d{4}(?:-\d{2}-\d{2})?)")
|
|
56
|
+
# 排除短语:-"..."(先于单词排除处理)
|
|
57
|
+
_EXCL_PHRASE_RE = re.compile(r'-\s*"([^"]+)"')
|
|
58
|
+
# 排除单词:-词(字母数字或 CJK 串;连字符后紧跟非空白非连字符)
|
|
59
|
+
_EXCL_TERM_RE = re.compile(r"(?<![\w\"])-(\S+)")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def parse_query_operators(query: str) -> tuple[str, dict[str, Any]]:
|
|
63
|
+
"""抽取查询操作符 → (clean_query, operators)。
|
|
64
|
+
|
|
65
|
+
clean_query 剥掉全部指令(site/-site/-词/-"短语"/lang/after/before),
|
|
66
|
+
保留精确短语 "..."(它是意图本体)。operators 字段:
|
|
67
|
+
sites / exclude_sites: list[str]
|
|
68
|
+
exclude_terms: list[str](-词 与 -"短语" 合并,供排除过滤)
|
|
69
|
+
langs: list[str]
|
|
70
|
+
after / before: str | None
|
|
71
|
+
dropped: list[str](原操作符文本,可观测/limitations 提示用)
|
|
72
|
+
"""
|
|
73
|
+
if not query or not isinstance(query, str):
|
|
74
|
+
return (query or ""), _empty_ops()
|
|
75
|
+
|
|
76
|
+
ops = _empty_ops()
|
|
77
|
+
text = query
|
|
78
|
+
|
|
79
|
+
def _take(match: re.Match) -> str:
|
|
80
|
+
ops["dropped"].append(match.group(0).strip())
|
|
81
|
+
return " "
|
|
82
|
+
|
|
83
|
+
# 1) 站点限定/排除
|
|
84
|
+
def _site_sub(m: re.Match) -> str:
|
|
85
|
+
(ops["exclude_sites"] if m.group(1) else ops["sites"]).append(m.group(2))
|
|
86
|
+
return _take(m)
|
|
87
|
+
text = _SITE_RE.sub(_site_sub, text)
|
|
88
|
+
|
|
89
|
+
# 2) 语言
|
|
90
|
+
def _lang_sub(m: re.Match) -> str:
|
|
91
|
+
langs = [x.strip() for x in m.group(1).split(",") if x.strip()]
|
|
92
|
+
ops["langs"].extend(langs)
|
|
93
|
+
return _take(m)
|
|
94
|
+
text = _LANG_RE.sub(_lang_sub, text)
|
|
95
|
+
|
|
96
|
+
# 3) 时间窗
|
|
97
|
+
def _date_sub(m: re.Match) -> str:
|
|
98
|
+
ops[m.group(1)] = m.group(2)
|
|
99
|
+
return _take(m)
|
|
100
|
+
text = _DATE_RE.sub(_date_sub, text)
|
|
101
|
+
|
|
102
|
+
# 4) 排除短语(先于 -词,避免 " 被当词的一部分)
|
|
103
|
+
def _excl_phrase_sub(m: re.Match) -> str:
|
|
104
|
+
ops["exclude_terms"].append(m.group(1).strip())
|
|
105
|
+
return _take(m)
|
|
106
|
+
text = _EXCL_PHRASE_RE.sub(_excl_phrase_sub, text)
|
|
107
|
+
|
|
108
|
+
# 5) 排除单词
|
|
109
|
+
def _excl_term_sub(m: re.Match) -> str:
|
|
110
|
+
term = m.group(1).strip()
|
|
111
|
+
if term:
|
|
112
|
+
ops["exclude_terms"].append(term)
|
|
113
|
+
return _take(m)
|
|
114
|
+
text = _EXCL_TERM_RE.sub(_excl_term_sub, text)
|
|
115
|
+
|
|
116
|
+
clean = re.sub(r"\s+", " ", text).strip()
|
|
117
|
+
return clean, ops
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _host_matches(host: str, site: str) -> bool:
|
|
121
|
+
"""host 是否属于 site(含子域)。site 为空或非法时返回 False。"""
|
|
122
|
+
host = (host or "").lower().strip(".")
|
|
123
|
+
site = (site or "").lower().strip().strip(".")
|
|
124
|
+
if not host or not site:
|
|
125
|
+
return False
|
|
126
|
+
return host == site or host.endswith("." + site)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def operator_cache_suffix(operators: dict[str, Any] | None) -> str:
|
|
130
|
+
"""把影响结果集的操作符拼成缓存键后缀;无操作符返回空串。
|
|
131
|
+
|
|
132
|
+
缓存键的主体是剥掉指令后的 clean_query,若不把指令本身并入键,
|
|
133
|
+
`python asyncio` 与 `python asyncio -site:realpython.com` 会命中同一条
|
|
134
|
+
缓存:要么把受限结果回给无约束查询,要么相反——后者更隐蔽,
|
|
135
|
+
用户写了约束却拿到未过滤的结果。langs 同理(它改变路由与引擎选择)。
|
|
136
|
+
after/before 已归一化为 since/until,由调用方单独并入,不在这重复。
|
|
137
|
+
"""
|
|
138
|
+
ops = operators or {}
|
|
139
|
+
parts: list[str] = []
|
|
140
|
+
for key, tag in (("sites", "site"), ("exclude_sites", "xsite"),
|
|
141
|
+
("exclude_terms", "not"), ("langs", "lang")):
|
|
142
|
+
vals = [str(v).strip() for v in (ops.get(key) or [])
|
|
143
|
+
if isinstance(v, (str, int, float)) and str(v).strip()]
|
|
144
|
+
if vals:
|
|
145
|
+
parts.append(f"{tag}={','.join(vals)}")
|
|
146
|
+
return "".join(f"|{p}" for p in parts)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def filter_results_by_sites(
|
|
150
|
+
results: list[dict[str, Any]],
|
|
151
|
+
sites: list[str] | None,
|
|
152
|
+
exclude_sites: list[str] | None = None,
|
|
153
|
+
) -> tuple[list[dict[str, Any]], int]:
|
|
154
|
+
"""按 site:/-site: 后置过滤结果,返回 (保留项, 剔除数)。
|
|
155
|
+
|
|
156
|
+
为什么是后置过滤而不是下推检索词:argo 的上游是 260 个异构引擎,多数
|
|
157
|
+
不认识 site: 语法;把它留在检索串里只会降低召回。解析层已经把它从
|
|
158
|
+
clean_query 剥掉,若不再执行就等于「宣称支持但静默忽略」——实测
|
|
159
|
+
`DeepSeek site:github.com` 会返回 models.dev 的模型页(2026-10-17)。
|
|
160
|
+
|
|
161
|
+
子域算匹配:site:github.com 命中 gist.github.com。多个 site 之间是 OR。
|
|
162
|
+
无 url 的模态卡不参与站点判定(不因无法判定而被误删)。
|
|
163
|
+
"""
|
|
164
|
+
include = [s for s in (sites or []) if isinstance(s, str) and s.strip()]
|
|
165
|
+
exclude = [s for s in (exclude_sites or []) if isinstance(s, str) and s.strip()]
|
|
166
|
+
if not include and not exclude:
|
|
167
|
+
return results, 0
|
|
168
|
+
kept: list[dict[str, Any]] = []
|
|
169
|
+
dropped = 0
|
|
170
|
+
for r in results:
|
|
171
|
+
if not isinstance(r, dict):
|
|
172
|
+
kept.append(r)
|
|
173
|
+
continue
|
|
174
|
+
url = str(r.get("url") or "")
|
|
175
|
+
if not url:
|
|
176
|
+
kept.append(r)
|
|
177
|
+
continue
|
|
178
|
+
try:
|
|
179
|
+
host = urllib.parse.urlsplit(url).hostname or ""
|
|
180
|
+
except ValueError:
|
|
181
|
+
host = ""
|
|
182
|
+
if include and not any(_host_matches(host, s) for s in include):
|
|
183
|
+
dropped += 1
|
|
184
|
+
continue
|
|
185
|
+
if exclude and any(_host_matches(host, s) for s in exclude):
|
|
186
|
+
dropped += 1
|
|
187
|
+
continue
|
|
188
|
+
kept.append(r)
|
|
189
|
+
return kept, dropped
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def _empty_ops() -> dict[str, Any]:
|
|
193
|
+
return {
|
|
194
|
+
"sites": [], "exclude_sites": [], "exclude_terms": [],
|
|
195
|
+
"langs": [], "after": None, "before": None, "dropped": [],
|
|
196
|
+
}
|
|
@@ -28,9 +28,10 @@ REWRITE_MAP: dict[tuple[str, str], str] = {
|
|
|
28
28
|
# 科技公司/产品
|
|
29
29
|
("苹果", "finance"): "Apple 公司 AAPL 股票行情",
|
|
30
30
|
("苹果", "tech"): "Apple 公司 iPhone Mac iOS",
|
|
31
|
-
|
|
31
|
+
# 改写词不得触发 package_search 等域规则(禁止 pip/npm/pypi/cargo 等包管理词)
|
|
32
|
+
("Python", "tech"): "Python 编程语言 异步 开发",
|
|
32
33
|
("Java", "tech"): "Java 编程语言 JDK Spring JVM",
|
|
33
|
-
("Rust", "tech"): "Rust 编程语言
|
|
34
|
+
("Rust", "tech"): "Rust 编程语言 所有权 并发",
|
|
34
35
|
("小米", "tech"): "小米科技 手机 MIUI HyperOS",
|
|
35
36
|
("小米", "auto"): "小米汽车 SU7",
|
|
36
37
|
("华为", "tech"): "华为技术 鸿蒙 5G 芯片 Mate",
|
|
@@ -125,13 +126,56 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
125
126
|
# 延迟导入避免循环依赖
|
|
126
127
|
from clarify import AMBIGUOUS_TERMS, BRAND_COLLISIONS
|
|
127
128
|
|
|
129
|
+
# 长查询自带完整上下文,扩词只会引入噪声:research 子查询含「GPT-4o」时
|
|
130
|
+
# 曾被扩成「…大语言模型 OpenAI ChatGPT GPT-4 technical overview…」,
|
|
131
|
+
# 整条检索串从照片趋势题变成模型架构题。短查询才需要补召回信号。
|
|
132
|
+
if len(query) > 60:
|
|
133
|
+
return {
|
|
134
|
+
"original": query,
|
|
135
|
+
"rewritten": None,
|
|
136
|
+
"confidence": 0.0,
|
|
137
|
+
"reason": "长查询跳过改写(自带完整上下文)",
|
|
138
|
+
"type": "direct",
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
# 非中文查询跳过策略1(REWRITE_MAP 中文领域扩词):领域词全是中文
|
|
142
|
+
# (「编程语言 异步 开发」),拼进俄文/日文/泰文查询后检索串变成混合语,
|
|
143
|
+
# 实测俄文查询被污染成 9/9 中文结果(原文 0 中文)。
|
|
144
|
+
# 策略2(英文术语拆分)保留:追加的是查询自带的拉丁术语,无语言污染,
|
|
145
|
+
# 是非中文查询补召回信号的正途。
|
|
146
|
+
# 日语特殊:日文本身混用汉字(「比較」),不能靠汉字正则放行——
|
|
147
|
+
# detect_language 判 ja/ko 时直接按非中文处理。
|
|
148
|
+
try:
|
|
149
|
+
from lang_detect import detect_language as _detect_lang
|
|
150
|
+
_lang = _detect_lang(query)
|
|
151
|
+
_is_zh = _lang in ("zh", "mixed") or (
|
|
152
|
+
_lang not in ("ja", "ko") and re.search(r"[\u4e00-\u9fff]", query))
|
|
153
|
+
_skip_zh_expand = not _is_zh
|
|
154
|
+
except ImportError:
|
|
155
|
+
_skip_zh_expand = False
|
|
156
|
+
|
|
157
|
+
# P0-001:先做查询理解,用 clean_query(去否定片段)做消歧,
|
|
158
|
+
# 避免否定实体(如「除了百度」的百度)污染消歧信号。
|
|
159
|
+
understanding = None
|
|
160
|
+
disambig_source = query
|
|
161
|
+
try:
|
|
162
|
+
from query_understanding import _understand_cached as understand
|
|
163
|
+
understanding = understand(query)
|
|
164
|
+
if understanding.clean_query:
|
|
165
|
+
disambig_source = understanding.clean_query
|
|
166
|
+
except ImportError:
|
|
167
|
+
pass # query_understanding 不可用,退回原查询
|
|
168
|
+
except Exception:
|
|
169
|
+
pass # query_understanding 不可用或返回异常:退回原查询,不阻断改写
|
|
170
|
+
|
|
128
171
|
rewritten_parts: list[str] = []
|
|
129
172
|
reasons: list[str] = []
|
|
130
173
|
total_confidence = 0.0
|
|
131
174
|
match_count = 0
|
|
132
175
|
|
|
133
176
|
# ── 策略 1:歧义消解改写 ──
|
|
134
|
-
|
|
177
|
+
# 用 clean_query(去否定片段)做歧义定位,避免被排除实体干扰
|
|
178
|
+
query_lower = disambig_source.lower()
|
|
135
179
|
for term, info in AMBIGUOUS_TERMS.items():
|
|
136
180
|
# 在查询中定位歧义词(大小写不敏感)
|
|
137
181
|
term_lower = term.lower()
|
|
@@ -140,6 +184,12 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
140
184
|
and re.search(r"\b" + re.escape(term) + r"\b", query, re.I)
|
|
141
185
|
):
|
|
142
186
|
continue
|
|
187
|
+
# 型号后缀守卫:词后跟数字(GPT-4o / GPT5 / iPhone 15)是具体型号
|
|
188
|
+
# 提及而非品牌歧义词,展开只会把查询推向无关领域
|
|
189
|
+
if term.isascii() and re.search(
|
|
190
|
+
r"\b" + re.escape(term) + r"[-.\s]?\d", query, re.I
|
|
191
|
+
):
|
|
192
|
+
continue
|
|
143
193
|
|
|
144
194
|
best_domain = None
|
|
145
195
|
best_conf = 0.0
|
|
@@ -198,14 +248,23 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
198
248
|
if best_domain and best_conf >= min_confidence:
|
|
199
249
|
rewrite_key = (term, best_domain)
|
|
200
250
|
append = REWRITE_MAP.get(rewrite_key)
|
|
201
|
-
if append:
|
|
251
|
+
if append and not _skip_zh_expand:
|
|
202
252
|
rewritten_parts.append(append)
|
|
203
253
|
reasons.append(f"「{term}」→ {best_domain} 领域")
|
|
204
254
|
total_confidence += best_conf
|
|
205
255
|
match_count += 1
|
|
206
256
|
|
|
207
257
|
# ── 策略 2:混合语言拆分 ──
|
|
208
|
-
|
|
258
|
+
# 多语种(v2.7):主语言非英文(日/韩/西里尔/泰/阿等)且含拉丁术语时
|
|
259
|
+
# 也做混合拆分,抽出英文技术词补检索;中文+英文逻辑保持原样。
|
|
260
|
+
lang = "en"
|
|
261
|
+
try:
|
|
262
|
+
from lang_detect import detect_language
|
|
263
|
+
lang = detect_language(query)
|
|
264
|
+
except ImportError:
|
|
265
|
+
pass
|
|
266
|
+
mixed_latin = _has_chinese(query) or lang not in ("en", "latin")
|
|
267
|
+
if mixed_latin and _has_english(query):
|
|
209
268
|
eng_terms = _extract_english_terms(query)
|
|
210
269
|
technical = any(
|
|
211
270
|
t
|
|
@@ -214,7 +273,7 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
214
273
|
"npm", "pip", "git", "ssh", "rest", "graphql", "grpc",
|
|
215
274
|
"react", "vue", "node", "python", "java", "rust", "go",
|
|
216
275
|
"docker", "kubernetes", "linux", "nginx", "redis",
|
|
217
|
-
"async", "await", "thread", "process",
|
|
276
|
+
"async", "await", "thread", "process",
|
|
218
277
|
}
|
|
219
278
|
for t in eng_terms
|
|
220
279
|
)
|
|
@@ -227,6 +286,8 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
227
286
|
match_count += 1
|
|
228
287
|
|
|
229
288
|
# ── 构建结果 ──
|
|
289
|
+
understanding_dict = understanding.to_dict() if understanding is not None else None
|
|
290
|
+
|
|
230
291
|
if not rewritten_parts:
|
|
231
292
|
return {
|
|
232
293
|
"original": query,
|
|
@@ -234,10 +295,31 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
234
295
|
"confidence": 0.0,
|
|
235
296
|
"reason": "无需改写",
|
|
236
297
|
"type": "direct",
|
|
298
|
+
"understanding": understanding_dict,
|
|
237
299
|
}
|
|
238
300
|
|
|
239
301
|
avg_confidence = round(total_confidence / match_count, 2)
|
|
240
|
-
|
|
302
|
+
# 追加词与原查询词级去重:消歧/拆分追加只补「新增信息」。
|
|
303
|
+
# 此前 "Python async" + "Python 编程语言 异步 开发" 直接拼接成
|
|
304
|
+
# "Python async Python 编程语言 异步 开发",重复词污染检索串,
|
|
305
|
+
# 且混入子查询分解后产生 "Python async Python" 半改写残留。
|
|
306
|
+
append_str = _dedupe_append_words(query, rewritten_parts)
|
|
307
|
+
if not append_str:
|
|
308
|
+
# 去重后全空:中文查询的混合拆分场景(日文查询 + 原文已有的英文术语)
|
|
309
|
+
# 仍保留追加,旧行为不改写会丢失语言补充信号;
|
|
310
|
+
# 非中文查询的追加全为原文术语时,重复拼接是纯噪声
|
|
311
|
+
# (实测俄文查询末尾重复 "python asyncio" 无增益),直接不改写。
|
|
312
|
+
if _skip_zh_expand:
|
|
313
|
+
return {
|
|
314
|
+
"original": query,
|
|
315
|
+
"rewritten": None,
|
|
316
|
+
"confidence": 0.0,
|
|
317
|
+
"reason": "非中文查询追加词与原文重复,跳过改写",
|
|
318
|
+
"type": "direct",
|
|
319
|
+
"understanding": understanding_dict,
|
|
320
|
+
}
|
|
321
|
+
append_str = " ".join(rewritten_parts)
|
|
322
|
+
rewritten = query + " " + append_str
|
|
241
323
|
|
|
242
324
|
return {
|
|
243
325
|
"original": query,
|
|
@@ -245,9 +327,36 @@ def rewrite_query(query: str, min_confidence: float = 0.7) -> dict[str, Any]:
|
|
|
245
327
|
"confidence": avg_confidence,
|
|
246
328
|
"reason": ";".join(reasons),
|
|
247
329
|
"type": "disambiguate",
|
|
330
|
+
"understanding": understanding_dict,
|
|
248
331
|
}
|
|
249
332
|
|
|
250
333
|
|
|
334
|
+
def _dedupe_append_words(original: str, parts: list[str]) -> str:
|
|
335
|
+
"""追加词与原查询词级去重:已出现在原文里的词不再追加。
|
|
336
|
+
|
|
337
|
+
例:original="Python async",parts=["Python 编程语言 异步 开发"]
|
|
338
|
+
→ Python 已在原文 → 保留 "编程语言 异步 开发"。
|
|
339
|
+
以词块为单位(空格分词),中文词块与英文词均按字面比较。
|
|
340
|
+
"""
|
|
341
|
+
import re as _re
|
|
342
|
+
if not parts:
|
|
343
|
+
return ""
|
|
344
|
+
q_tokens = set(
|
|
345
|
+
t.lower()
|
|
346
|
+
for t in _re.findall(r"[\u4e00-\u9fff]+|[a-zA-Z0-9]+", original)
|
|
347
|
+
)
|
|
348
|
+
kept: list[str] = []
|
|
349
|
+
for part in parts:
|
|
350
|
+
fresh = [
|
|
351
|
+
w for w in part.split()
|
|
352
|
+
if _re.sub(r"[^a-zA-Z0-9\u4e00-\u9fff]", "", w).lower()
|
|
353
|
+
not in q_tokens
|
|
354
|
+
]
|
|
355
|
+
if fresh:
|
|
356
|
+
kept.append(" ".join(fresh))
|
|
357
|
+
return " ".join(kept)
|
|
358
|
+
|
|
359
|
+
|
|
251
360
|
# ── CLI 测试 ──────────────────────────────────────────────────────────────────
|
|
252
361
|
|
|
253
362
|
if __name__ == "__main__":
|