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,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
network_aware.py — 网络环境感知(v2.7 新增)
|
|
4
|
+
|
|
5
|
+
利用 adaptive 引擎表现数据 + 运行时实测,推断当前网络环境并给出
|
|
6
|
+
自适应策略建议。核心洞察:
|
|
7
|
+
- adaptive.get_score 已经综合了 success × latency × cost,
|
|
8
|
+
是天然的「网络传感器」(慢网 → 延迟高 → 分数低)
|
|
9
|
+
- 不同网络环境应使用不同策略:慢网放大超时预算、快网收紧、
|
|
10
|
+
受限网优先本地引擎
|
|
11
|
+
|
|
12
|
+
纯 stdlib,无外部依赖。任何失败都返回中性建议,不阻断搜索。
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
# 引擎延迟采样窗口(秒)
|
|
21
|
+
_LATENCY_WINDOW = 24 * 3600
|
|
22
|
+
|
|
23
|
+
# 网络环境判定阈值(平均延迟,毫秒)
|
|
24
|
+
_FAST_THRESHOLD_MS = 800.0 # 平均 < 0.8s → 快网
|
|
25
|
+
_SLOW_THRESHOLD_MS = 3000.0 # 平均 > 3s → 慢网
|
|
26
|
+
|
|
27
|
+
# 超时缩放范围
|
|
28
|
+
_TIMEOUT_SCALE_MIN = 0.8
|
|
29
|
+
_TIMEOUT_SCALE_MAX = 1.8
|
|
30
|
+
|
|
31
|
+
_cache: dict[str, Any] = {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _avg_latency(learner: Any, engines: list[str]) -> float | None:
|
|
35
|
+
"""从 adaptive 数据估计引擎平均延迟(无数据返回 None)。"""
|
|
36
|
+
latencies: list[float] = []
|
|
37
|
+
for e in engines:
|
|
38
|
+
try:
|
|
39
|
+
# 用 score 反推延迟不可靠;直接查 adaptive 的 per-engine 平均延迟
|
|
40
|
+
score = learner.get_score(e)
|
|
41
|
+
# score 已含 latency_factor,但需原始延迟 → 查 db
|
|
42
|
+
lat = _latency_from_db(e)
|
|
43
|
+
if lat is not None:
|
|
44
|
+
latencies.append(lat)
|
|
45
|
+
except Exception:
|
|
46
|
+
continue
|
|
47
|
+
if not latencies:
|
|
48
|
+
return None
|
|
49
|
+
return sum(latencies) / len(latencies)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _latency_from_db(engine: str) -> float | None:
|
|
53
|
+
"""直接查 adaptive.db 的 AVG(latency_ms)(绕过 score 的合成因子)。"""
|
|
54
|
+
try:
|
|
55
|
+
from adaptive import DB_PATH, WINDOW_DAYS
|
|
56
|
+
import sqlite3
|
|
57
|
+
cutoff = time.time() - WINDOW_DAYS * 86400
|
|
58
|
+
conn = sqlite3.connect(str(DB_PATH), timeout=2)
|
|
59
|
+
try:
|
|
60
|
+
row = conn.execute(
|
|
61
|
+
"SELECT AVG(latency_ms) FROM engine_perf "
|
|
62
|
+
"WHERE engine = ? AND created_at > ?",
|
|
63
|
+
(engine, cutoff),
|
|
64
|
+
).fetchone()
|
|
65
|
+
lat = row[0] if row and row[0] is not None else None
|
|
66
|
+
return float(lat) if lat else None
|
|
67
|
+
finally:
|
|
68
|
+
conn.close()
|
|
69
|
+
except Exception:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def network_profile(engines: list[str]) -> dict[str, Any]:
|
|
74
|
+
"""推断当前网络环境,返回策略建议。
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
{
|
|
78
|
+
"network": "fast" | "normal" | "slow" | "unknown",
|
|
79
|
+
"avg_latency_ms": float | None,
|
|
80
|
+
"timeout_scale": float, # 超时缩放(0.8 ~ 1.8)
|
|
81
|
+
"prefer_local": bool, # 受限网偏好本地引擎
|
|
82
|
+
}
|
|
83
|
+
"""
|
|
84
|
+
now = time.time()
|
|
85
|
+
cached = _cache.get("profile")
|
|
86
|
+
if cached and now - cached["_ts"] < 60: # 60s 缓存
|
|
87
|
+
return cached["data"]
|
|
88
|
+
|
|
89
|
+
profile: dict[str, Any] = {
|
|
90
|
+
"network": "unknown",
|
|
91
|
+
"avg_latency_ms": None,
|
|
92
|
+
"timeout_scale": 1.0,
|
|
93
|
+
"prefer_local": False,
|
|
94
|
+
}
|
|
95
|
+
try:
|
|
96
|
+
from adaptive import get_learner
|
|
97
|
+
learner = get_learner()
|
|
98
|
+
avg = _avg_latency(learner, engines or [])
|
|
99
|
+
if avg is None:
|
|
100
|
+
# 无数据:用健康探针的延迟
|
|
101
|
+
avg = _probe_latency(engines)
|
|
102
|
+
if avg is not None:
|
|
103
|
+
profile["avg_latency_ms"] = round(avg, 1)
|
|
104
|
+
if avg < _FAST_THRESHOLD_MS:
|
|
105
|
+
profile["network"] = "fast"
|
|
106
|
+
profile["timeout_scale"] = _TIMEOUT_SCALE_MIN
|
|
107
|
+
elif avg > _SLOW_THRESHOLD_MS:
|
|
108
|
+
profile["network"] = "slow"
|
|
109
|
+
profile["timeout_scale"] = _TIMEOUT_SCALE_MAX
|
|
110
|
+
profile["prefer_local"] = True
|
|
111
|
+
else:
|
|
112
|
+
profile["network"] = "normal"
|
|
113
|
+
profile["timeout_scale"] = 1.0
|
|
114
|
+
except Exception:
|
|
115
|
+
pass
|
|
116
|
+
|
|
117
|
+
_cache["profile"] = {"_ts": now, "data": profile}
|
|
118
|
+
return profile
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def _probe_latency(engines: list[str]) -> float | None:
|
|
122
|
+
"""无 adaptive 数据时,用 health_probe 的 last_latency_ms 估计。"""
|
|
123
|
+
try:
|
|
124
|
+
from health_probe import get_engine_status
|
|
125
|
+
lats = []
|
|
126
|
+
for e in engines:
|
|
127
|
+
st = get_engine_status(e)
|
|
128
|
+
lat = st.get("last_latency_ms")
|
|
129
|
+
if isinstance(lat, (int, float)) and lat > 0:
|
|
130
|
+
lats.append(float(lat))
|
|
131
|
+
if lats:
|
|
132
|
+
return sum(lats) / len(lats)
|
|
133
|
+
except Exception:
|
|
134
|
+
pass
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def adjusted_timeout(base_timeout: int, engines: list[str]) -> int:
|
|
139
|
+
"""按网络环境调整超时预算。
|
|
140
|
+
|
|
141
|
+
慢网放大(最多 1.8×,避免误杀慢网下正常的引擎),
|
|
142
|
+
快网收紧(0.8×,更快响应),正常网不变。
|
|
143
|
+
"""
|
|
144
|
+
profile = network_profile(engines)
|
|
145
|
+
scale = profile.get("timeout_scale", 1.0)
|
|
146
|
+
return max(1, int(base_timeout * scale))
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def should_prefer_local(engines: list[str]) -> bool:
|
|
150
|
+
"""受限网偏好本地引擎(零成本、少外部依赖)。"""
|
|
151
|
+
profile = network_profile(engines)
|
|
152
|
+
return bool(profile.get("prefer_local"))
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""output_transparency.py — 搜索输出的可信度评估、结构化失败清单与精简档透明修饰。
|
|
3
|
+
|
|
4
|
+
从 search_output.py 拆出(2026-10-21):
|
|
5
|
+
1. 单一职责:search_output 管「结果成形与人读格式」,本模块管「可信度/失败分类/
|
|
6
|
+
不可信边界」三件透明化信号;拆开后 search_output 保持在 ~650 行,远离千行硬限。
|
|
7
|
+
2. 口径合一:`_FAILURE_NON_ERROR` 与 `_NON_ERROR_OUTCOME` 同源派生,不再维护
|
|
8
|
+
两份独立 frozenset。
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
# 本次调用不算失败的基础状态(引擎正常交付、无结果、或被早停主动取消)
|
|
17
|
+
_FAILURE_NON_ERROR: frozenset[str] = frozenset({
|
|
18
|
+
"ok", "ok-cached", "partial", "no-results", "no-results-cached", "cancelled",
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
# errors[] 与 uncertainty 排除的 outcome 状态:在基础非错误集之上再排除
|
|
22
|
+
# skipped-quota-exhausted(持续一天的配额跳过状态,不作为单次检索错误刷屏)
|
|
23
|
+
_NON_ERROR_OUTCOME: frozenset[str] = _FAILURE_NON_ERROR | {"skipped-quota-exhausted"}
|
|
24
|
+
|
|
25
|
+
# 二手/聚合平台(转载与洗稿高发地)。**刻意不复用 evidence 的词表**:
|
|
26
|
+
# evidence 属热路径禁入名单(tests/test_hotpath_import_lint.py 的 HEAVY_TOP,
|
|
27
|
+
# 它会拖进 content_signals/stylometry/burstiness 约 9ms),而本函数在每次
|
|
28
|
+
# 搜索的收口处无条件执行。两处词表需同步——改这里时看 evidence.
|
|
29
|
+
# _SECONDARY_PLATFORM_PATTERNS。
|
|
30
|
+
_AGGREGATOR_HOSTS: frozenset[str] = frozenset({
|
|
31
|
+
"baijiahao.baidu.com", # 百家号(自媒体洗稿重灾区)
|
|
32
|
+
"toutiao.com", # 头条号(聚合+洗稿)
|
|
33
|
+
"k.sina.com.cn", # 新浪博客/聚合
|
|
34
|
+
"guba.eastmoney.com", # 股吧(UGC 讨论,非事实来源)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
# 可信度等级(与 dsh-free-search 的 meta.uncertainty 同形)
|
|
38
|
+
_UNCERTAINTY_HIGH = "high"
|
|
39
|
+
_UNCERTAINTY_MEDIUM = "medium"
|
|
40
|
+
_UNCERTAINTY_LOW = "low"
|
|
41
|
+
# 严重度序:用于「多信号取最差」——等级只能降不升。
|
|
42
|
+
_SEVERITY = {_UNCERTAINTY_HIGH: 0, _UNCERTAINTY_MEDIUM: 1, _UNCERTAINTY_LOW: 2}
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _hostname(url: Any) -> str:
|
|
46
|
+
"""URL → 归一化主机名(小写、去 www.);解析失败返回空串。"""
|
|
47
|
+
try:
|
|
48
|
+
from urllib.parse import urlparse
|
|
49
|
+
host = urlparse(str(url or "")).hostname or ""
|
|
50
|
+
except Exception:
|
|
51
|
+
return ""
|
|
52
|
+
return host.lower().removeprefix("www.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# 失败类别 → (归一类别, 可执行建议, 是否可重试)。类别名与 dsh-free-search
|
|
56
|
+
# 的 failureClass 对齐(quota/auth/bot-wall/transient/rate-limit/unknown),
|
|
57
|
+
# 但归类依据是 argo 自己的 outcome.status(retry_policy.classify_error_status
|
|
58
|
+
# 的唯一真源),不另写一套错误词表。
|
|
59
|
+
_FAILURE_META: dict[str, tuple[str, str, bool]] = {
|
|
60
|
+
"timeout": ("transient", "超时;可重试或换源", True),
|
|
61
|
+
"quota-exhausted": ("quota", "配额耗尽;换源或等窗口重置", False),
|
|
62
|
+
"rate-limited": ("rate-limit", "被限流;稍后重试或换源", True),
|
|
63
|
+
"blocked": ("bot-wall", "被反爬拦截;稍后重试或换源", True),
|
|
64
|
+
"auth-failed": ("auth", "检查 API Key 是否有效、是否有该接口权限", False),
|
|
65
|
+
"error": ("unknown", "源端错误;可重试或换源", True),
|
|
66
|
+
"skipped-missing-env": ("missing-key", "未配置密钥;配置后即可参与", False),
|
|
67
|
+
"skipped-quota-exhausted": ("quota", "配额耗尽,本轮跳过", False),
|
|
68
|
+
"skipped-circuit-open": ("cooldown", "熔断冷却中,稍后自动恢复", True),
|
|
69
|
+
"skipped-budget": ("budget", "本轮预算不足,未执行", True),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def build_failures(result: dict[str, Any], limit: int = 6) -> list[dict[str, Any]]:
|
|
74
|
+
"""把引擎 outcome 压成 Agent 可行动的结构化失败清单。
|
|
75
|
+
|
|
76
|
+
与 errors 的分工:errors 是给人看的整行文本(兼容旧契约),本字段是
|
|
77
|
+
给模型/宿主判断「要不要重试、换哪个源、是不是缺 key」的分类结果。
|
|
78
|
+
只收录没交出结果的 outcome;ok/no-results 不算失败(后者是「查了没有」,
|
|
79
|
+
不是「没查成」),与 uncertainty 的降级判据同源。
|
|
80
|
+
"""
|
|
81
|
+
out: list[dict[str, Any]] = []
|
|
82
|
+
seen: set[tuple[str, str]] = set()
|
|
83
|
+
for o in (result.get("engine_outcomes") or []):
|
|
84
|
+
if not isinstance(o, dict):
|
|
85
|
+
continue
|
|
86
|
+
engine = str(o.get("engine") or "")
|
|
87
|
+
status = str(o.get("status") or "")
|
|
88
|
+
if not engine or status in _FAILURE_NON_ERROR:
|
|
89
|
+
continue
|
|
90
|
+
key = (engine, status)
|
|
91
|
+
if key in seen:
|
|
92
|
+
continue
|
|
93
|
+
seen.add(key)
|
|
94
|
+
category, action, retryable = _FAILURE_META.get(
|
|
95
|
+
status, ("unknown", "源端异常;可重试或换源", True))
|
|
96
|
+
item: dict[str, Any] = {
|
|
97
|
+
"engine": engine,
|
|
98
|
+
"status": status,
|
|
99
|
+
"category": category,
|
|
100
|
+
"retryable": retryable,
|
|
101
|
+
"action": action,
|
|
102
|
+
}
|
|
103
|
+
# skipped-* 是「持续状态」而不是本次尝试失败:配额窗口/熔断冷却在
|
|
104
|
+
# 周期边界前不会因重试改变。标出来,避免 Agent 立刻重试同一个源。
|
|
105
|
+
if status.startswith("skipped-"):
|
|
106
|
+
item["persistent"] = True
|
|
107
|
+
detail = str(o.get("detail") or "").strip()
|
|
108
|
+
if detail:
|
|
109
|
+
item["detail"] = detail[:160]
|
|
110
|
+
out.append(item)
|
|
111
|
+
if len(out) >= max(1, limit):
|
|
112
|
+
break
|
|
113
|
+
return out
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def decorate_compact_payload(out: dict[str, Any],
|
|
117
|
+
result: dict[str, Any]) -> None:
|
|
118
|
+
"""精简响应(MCP/Agent 档)的收尾修饰:边界清洗 + 三件透明信息 + sources 投影。"""
|
|
119
|
+
try:
|
|
120
|
+
from content_boundary import (CONTENT_TRUST_UNTRUSTED,
|
|
121
|
+
sanitize_results_boundary)
|
|
122
|
+
sanitize_results_boundary(out.get("results"))
|
|
123
|
+
out["content_trust"] = CONTENT_TRUST_UNTRUSTED
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
failures = result.get("failures")
|
|
127
|
+
if isinstance(failures, list) and failures:
|
|
128
|
+
out["failures"] = failures[:6]
|
|
129
|
+
meta = result.get("meta")
|
|
130
|
+
if isinstance(meta, dict) and meta.get("uncertainty"):
|
|
131
|
+
out["meta"] = {"uncertainty": meta["uncertainty"]}
|
|
132
|
+
sources = result.get("sources")
|
|
133
|
+
if sources:
|
|
134
|
+
out["sources"] = [
|
|
135
|
+
{
|
|
136
|
+
"ref": s.get("ref"),
|
|
137
|
+
"title": s.get("title"),
|
|
138
|
+
"url": s.get("url"),
|
|
139
|
+
"engine": s.get("engine") or s.get("source"),
|
|
140
|
+
}
|
|
141
|
+
for s in sources
|
|
142
|
+
if isinstance(s, dict) and s.get("url")
|
|
143
|
+
][:20]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def build_uncertainty(result: dict[str, Any]) -> dict[str, Any]:
|
|
147
|
+
"""一次调用的综合可信度:`{"level": ..., "notes": [...]}`。
|
|
148
|
+
|
|
149
|
+
与 `limitations` 的分工:limitations 是**逐条**的局限声明(早停、降级、
|
|
150
|
+
缓存复用、站点过滤),本字段是**整体**等级——消费者拿到一个 level 就能
|
|
151
|
+
决定「直接引用」还是「先核验」,不必自己交叉比对五六个字段。
|
|
152
|
+
"""
|
|
153
|
+
notes: list[str] = []
|
|
154
|
+
results = [r for r in (result.get("results") or []) if isinstance(r, dict)]
|
|
155
|
+
|
|
156
|
+
if not results:
|
|
157
|
+
return {"level": _UNCERTAINTY_LOW, "notes": ["没有返回结果"]}
|
|
158
|
+
|
|
159
|
+
level = _UNCERTAINTY_HIGH
|
|
160
|
+
|
|
161
|
+
def _worse(candidate: str) -> None:
|
|
162
|
+
nonlocal level
|
|
163
|
+
if _SEVERITY[candidate] > _SEVERITY[level]:
|
|
164
|
+
level = candidate
|
|
165
|
+
|
|
166
|
+
if len(results) == 1:
|
|
167
|
+
_worse(_UNCERTAINTY_LOW)
|
|
168
|
+
notes.append("只有 1 个来源,无法交叉验证")
|
|
169
|
+
elif len(results) == 2:
|
|
170
|
+
_worse(_UNCERTAINTY_MEDIUM)
|
|
171
|
+
notes.append("只有 2 个来源")
|
|
172
|
+
|
|
173
|
+
hosts = {_hostname(r.get("url")) for r in results}
|
|
174
|
+
hosts.discard("")
|
|
175
|
+
if 0 < len(hosts) == 1:
|
|
176
|
+
_worse(_UNCERTAINTY_MEDIUM)
|
|
177
|
+
notes.append("所有结果来自同一站点")
|
|
178
|
+
|
|
179
|
+
# 尊重 search_output._AGGREGATOR_HOSTS 的猴子补丁(test_output_uncertainty 门禁锁)
|
|
180
|
+
so_mod = sys.modules.get("search_output")
|
|
181
|
+
agg_hosts = getattr(so_mod, "_AGGREGATOR_HOSTS", _AGGREGATOR_HOSTS)
|
|
182
|
+
aggregators = sorted(h for h in hosts if h in agg_hosts
|
|
183
|
+
or any(h.endswith("." + a) for a in agg_hosts))
|
|
184
|
+
if aggregators:
|
|
185
|
+
_worse(_UNCERTAINTY_MEDIUM)
|
|
186
|
+
notes.append(f"含聚合站内容({'/'.join(aggregators)}),建议二次核对")
|
|
187
|
+
|
|
188
|
+
if result.get("time_filter_warning"):
|
|
189
|
+
_worse(_UNCERTAINTY_MEDIUM)
|
|
190
|
+
notes.append("时间窗未实际过滤,结果可能不在请求的时间范围内")
|
|
191
|
+
|
|
192
|
+
outcomes = [o for o in (result.get("engine_outcomes") or [])
|
|
193
|
+
if isinstance(o, dict)]
|
|
194
|
+
failed = [o for o in outcomes
|
|
195
|
+
if str(o.get("status") or "") not in _NON_ERROR_OUTCOME]
|
|
196
|
+
if failed:
|
|
197
|
+
_worse(_UNCERTAINTY_LOW)
|
|
198
|
+
notes.append(f"{len(failed)} 个引擎未交出结果,覆盖可能比计划的窄")
|
|
199
|
+
|
|
200
|
+
return {"level": level, "notes": notes}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""paid_policy.py — 付费搜索的自动对冲位(2026-10-21,方案一)。
|
|
3
|
+
|
|
4
|
+
只解决一件事:在**已证明有优势的域**里,把付费引擎放到 combo 第二位当对冲,
|
|
5
|
+
而不是让它进通用候选池(那会被 TF-IDF/兜底带到任意查询并扣费)。
|
|
6
|
+
|
|
7
|
+
策略:
|
|
8
|
+
- `ARGO_PAID_SEARCH=off` → 完全关闭;缺省 `auto`。
|
|
9
|
+
- 该域在 `PAID_DOMAIN_HEDGES` 里,且引擎 key 就绪、月度预算未用完
|
|
10
|
+
→ 返回该引擎;预算用完/无 key → 回落 `tinyfish`(免费、带日期),
|
|
11
|
+
再不行返回空,由原 combo 继续。
|
|
12
|
+
- `fast`/`budget` 模式不参与(与 quota 的成本纪律一致)。
|
|
13
|
+
|
|
14
|
+
当前只开两个域:`english_tech`、`intl_news_flash`。依据 2026-10-21 多引擎
|
|
15
|
+
实测:DeepSeek 在英文技术/英文新闻上 p50 515ms、日期覆盖 23/39,明显优于
|
|
16
|
+
anysearch/Exa/Tavily;中文宽泛新闻与金融原文反而更差,故不纳入。
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
|
|
23
|
+
__all__ = ["PAID_DOMAIN_HEDGES", "hedge_for", "insert_hedge"]
|
|
24
|
+
|
|
25
|
+
PAID_DOMAIN_HEDGES: dict[str, str] = {
|
|
26
|
+
"english_tech": "deepseek_search",
|
|
27
|
+
"intl_news_flash": "deepseek_search",
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
_FREE_FALLBACK = "tinyfish"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _enabled() -> bool:
|
|
34
|
+
try:
|
|
35
|
+
from engine_env import get_env
|
|
36
|
+
raw = get_env("ARGO_PAID_SEARCH", "auto")
|
|
37
|
+
except Exception:
|
|
38
|
+
raw = os.environ.get("ARGO_PAID_SEARCH", "auto")
|
|
39
|
+
return raw.strip().lower() not in ("off", "0", "false", "no")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _key_ready(engine: str) -> bool:
|
|
43
|
+
try:
|
|
44
|
+
from engine_env import missing_env_for
|
|
45
|
+
return not missing_env_for(engine)
|
|
46
|
+
except Exception:
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _budget_ok(engine: str) -> bool:
|
|
51
|
+
try:
|
|
52
|
+
from quota import get_quota_manager
|
|
53
|
+
return not get_quota_manager().is_hard_down(engine)
|
|
54
|
+
except Exception:
|
|
55
|
+
# 预算层读失败时 fail-closed:宁可不自动花钱
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _healthy_and_enabled(engine: str, enabled: set[str] | None) -> bool:
|
|
60
|
+
if enabled is not None and engine not in enabled:
|
|
61
|
+
return False
|
|
62
|
+
try:
|
|
63
|
+
from route_combo import _breaker_blocked
|
|
64
|
+
if _breaker_blocked(engine):
|
|
65
|
+
return False
|
|
66
|
+
except Exception:
|
|
67
|
+
pass
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def hedge_for(domain: str, mode: str = "auto",
|
|
72
|
+
enabled: set[str] | None = None) -> list[str]:
|
|
73
|
+
"""返回该域应插入的付费/回落引擎(0 或 1 个)。永不抛异常。"""
|
|
74
|
+
if not _enabled() or mode in ("fast", "budget"):
|
|
75
|
+
return []
|
|
76
|
+
engine = PAID_DOMAIN_HEDGES.get(str(domain or ""))
|
|
77
|
+
if not engine:
|
|
78
|
+
return []
|
|
79
|
+
try:
|
|
80
|
+
if (_healthy_and_enabled(engine, enabled)
|
|
81
|
+
and _key_ready(engine) and _budget_ok(engine)):
|
|
82
|
+
return [engine]
|
|
83
|
+
# 付费位不可用:TinyFish 是免费且带发布日期的回落位
|
|
84
|
+
if (_healthy_and_enabled(_FREE_FALLBACK, enabled)
|
|
85
|
+
and _key_ready(_FREE_FALLBACK) and _budget_ok(_FREE_FALLBACK)):
|
|
86
|
+
return [_FREE_FALLBACK]
|
|
87
|
+
except Exception:
|
|
88
|
+
return []
|
|
89
|
+
return []
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def insert_hedge(combo: list[str], domain: str, mode: str = "auto",
|
|
93
|
+
enabled: set[str] | None = None) -> list[str]:
|
|
94
|
+
"""把付费/回落引擎插到 combo 第二位(原地改并返回,便于链式调用)。"""
|
|
95
|
+
for engine in reversed(hedge_for(domain, mode, enabled=enabled)):
|
|
96
|
+
if engine not in combo:
|
|
97
|
+
combo.insert(min(1, len(combo)), engine)
|
|
98
|
+
return combo
|