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.
Files changed (472) hide show
  1. package/LICENSE +201 -21
  2. package/README.en.md +735 -0
  3. package/README.es.md +777 -0
  4. package/README.ja.md +777 -0
  5. package/README.ko.md +777 -0
  6. package/README.md +778 -320
  7. package/SKILL.md +135 -0
  8. package/assets/readme/hero.svg +121 -0
  9. package/assets/readme/made-with-beautify.svg +10 -0
  10. package/assets/readme/proof-routes.svg +73 -0
  11. package/assets/readme/why-better.svg +104 -0
  12. package/assets/readme/workflow.svg +83 -0
  13. package/backends/domain_profiles.json +2356 -96
  14. package/backends/engine_registry.yaml +3428 -503
  15. package/backends/query_synonyms_cn.json +35 -0
  16. package/backends/quota_profiles.json +2488 -160
  17. package/backends/source_types_cn.json +109 -0
  18. package/backends/text_lexicon.json +44 -0
  19. package/bin/argo +772 -0
  20. package/bin/argo.js +102 -39
  21. package/config.yaml +5139 -367
  22. package/cordis.patch.yml +21 -0
  23. package/data/countries.json +40 -0
  24. package/data/lang_matrix/lang_capability.json +842 -0
  25. package/data/lang_matrix/matrix_2026-09-10.json +11921 -0
  26. package/data/regions_cn.json +1 -0
  27. package/docs/ADDING_NEW_ENGINE.md +184 -0
  28. package/docs/ARGO_INTRO.md +124 -0
  29. package/docs/DATA_SOURCE_GAPS.md +81 -0
  30. package/docs/DESIGN_LOCAL_DATA_FUSION.md +90 -0
  31. package/docs/ENGINE_CATALOG.md +575 -0
  32. package/docs/MCP_SETUP.md +108 -0
  33. package/docs/PLATFORM_PATHS.md +111 -0
  34. package/docs/RELEASE_NOTES_v2.8.0.md +71 -0
  35. package/docs/RELEASE_NOTES_v2.8.2.md +63 -0
  36. package/docs/RELEASE_NOTES_v2.8.3.md +55 -0
  37. package/docs/RELEASE_NOTES_v2.8.4.md +79 -0
  38. package/docs/RELEASE_NOTES_v2.8.5.md +102 -0
  39. package/docs/RELEASE_NOTES_v2.8.6.md +66 -0
  40. package/docs/RELEASE_NOTES_v2.8.7.md +65 -0
  41. package/docs/RELEASE_NOTES_v2.8.8.md +67 -0
  42. package/docs/RELEASE_NOTES_v2.8.9.md +67 -0
  43. package/docs/RELEASE_NOTES_v2.9.0.md +64 -0
  44. package/docs/RELEASE_NOTES_v2.9.8.md +73 -0
  45. package/docs/SEARCH_ARCHIVE.md +115 -0
  46. package/docs/assets/architecture.svg +104 -0
  47. package/docs/assets/hero.svg +98 -0
  48. package/docs/engines/bangumi.md +27 -0
  49. package/docs/engines/biorxiv.md +27 -0
  50. package/docs/engines/clawhub.md +27 -0
  51. package/docs/engines/crt_sh.md +27 -0
  52. package/docs/engines/datacite.md +27 -0
  53. package/docs/engines/deps_dev.md +27 -0
  54. package/docs/engines/douban_movie.md +27 -0
  55. package/docs/engines/endoflife.md +27 -0
  56. package/docs/engines/flk_law.md +27 -0
  57. package/docs/engines/fxtwitter.md +27 -0
  58. package/docs/engines/google_news.md +27 -0
  59. package/docs/engines/gov_regulations.md +27 -0
  60. package/docs/engines/guqian.md +27 -0
  61. package/docs/engines/hackernews.md +27 -0
  62. package/docs/engines/iplant.md +27 -0
  63. package/docs/engines/keenable.md +27 -0
  64. package/docs/engines/met_museum.md +27 -0
  65. package/docs/engines/nasa_images.md +27 -0
  66. package/docs/engines/netease_music.md +27 -0
  67. package/docs/engines/npm.md +27 -0
  68. package/docs/engines/nvd.md +27 -0
  69. package/docs/engines/openreview.md +27 -0
  70. package/docs/engines/openstd.md +27 -0
  71. package/docs/engines/parallel_free.md +27 -0
  72. package/docs/engines/people_daily.md +27 -0
  73. package/docs/engines/realtime_index.md +27 -0
  74. package/docs/engines/sec_edgar.md +27 -0
  75. package/docs/engines/seltz.md +27 -0
  76. package/docs/engines/skillsmp.md +27 -0
  77. package/docs/engines/sspai.md +27 -0
  78. package/docs/engines/std_samr.md +27 -0
  79. package/docs/engines/train.md +27 -0
  80. package/docs/engines/un_comtrade.md +27 -0
  81. package/docs/engines/wikisource.md +27 -0
  82. package/docs/engines/zdic.md +27 -0
  83. package/docs/engines/zenodo.md +27 -0
  84. package/docs/engines/zhihu_hot_app.md +27 -0
  85. package/docs//344/270/272/344/273/200/344/271/210/351/200/211/346/213/251argo.md +176 -0
  86. package/engines/README.md +10 -0
  87. package/engines/_template_cli.yaml +39 -0
  88. package/engines/_template_http.yaml +37 -0
  89. package/engines/plugins/_template_plugin.py +50 -0
  90. package/engines/specs/_cisa_kev.yaml +54 -0
  91. package/engines/specs/_osv.yaml +63 -0
  92. package/engines/specs/aljazeera.yaml +43 -0
  93. package/engines/specs/antara.yaml +43 -0
  94. package/engines/specs/artic.yaml +20 -0
  95. package/engines/specs/aviation_weather.yaml +39 -0
  96. package/engines/specs/bangumi.yaml +21 -0
  97. package/engines/specs/biorxiv.yaml +24 -0
  98. package/engines/specs/carbon_intensity.yaml +32 -0
  99. package/engines/specs/cfets.yaml +41 -0
  100. package/engines/specs/cinii.yaml +44 -0
  101. package/engines/specs/clawhub.yaml +49 -0
  102. package/engines/specs/cleveland.yaml +19 -0
  103. package/engines/specs/cn_ai_news.yaml +43 -0
  104. package/engines/specs/core.yaml +35 -0
  105. package/engines/specs/crt_sh.yaml +38 -0
  106. package/engines/specs/ct24.yaml +43 -0
  107. package/engines/specs/datacite.yaml +27 -0
  108. package/engines/specs/datagov_uk.yaml +41 -0
  109. package/engines/specs/ddgs_images.yaml +45 -0
  110. package/engines/specs/deepseek_search.yaml +74 -0
  111. package/engines/specs/deezer.yaml +20 -0
  112. package/engines/specs/defillama.yaml +38 -0
  113. package/engines/specs/deps_dev.yaml +21 -0
  114. package/engines/specs/digup.yaml +36 -0
  115. package/engines/specs/doi.yaml +39 -0
  116. package/engines/specs/douban_movie.yaml +22 -0
  117. package/engines/specs/dw_news.yaml +47 -0
  118. package/engines/specs/egov_law.yaml +21 -0
  119. package/engines/specs/elpais.yaml +43 -0
  120. package/engines/specs/endoflife.yaml +21 -0
  121. package/engines/specs/energy_charts.yaml +21 -0
  122. package/engines/specs/factcheck_org.yaml +52 -0
  123. package/engines/specs/faz.yaml +43 -0
  124. package/engines/specs/federal_register.yaml +57 -0
  125. package/engines/specs/firecrawl.yaml +53 -0
  126. package/engines/specs/flk_law.yaml +24 -0
  127. package/engines/specs/folha.yaml +43 -0
  128. package/engines/specs/france24.yaml +44 -0
  129. package/engines/specs/full_fact.yaml +47 -0
  130. package/engines/specs/fxtwitter.yaml +36 -0
  131. package/engines/specs/gbfs_nyc.yaml +32 -0
  132. package/engines/specs/gdacs.yaml +21 -0
  133. package/engines/specs/gdelt.yaml +64 -0
  134. package/engines/specs/gleif.yaml +55 -0
  135. package/engines/specs/google_news.yaml +22 -0
  136. package/engines/specs/gov_regulations.yaml +38 -0
  137. package/engines/specs/guardian_rss.yaml +59 -0
  138. package/engines/specs/hkex.yaml +40 -0
  139. package/engines/specs/hurriyet.yaml +43 -0
  140. package/engines/specs/iplant.yaml +25 -0
  141. package/engines/specs/jikan.yaml +24 -0
  142. package/engines/specs/jolpica.yaml +20 -0
  143. package/engines/specs/k10plus.yaml +20 -0
  144. package/engines/specs/keenable.yaml +44 -0
  145. package/engines/specs/keenable_free.yaml +36 -0
  146. package/engines/specs/lefigaro.yaml +43 -0
  147. package/engines/specs/listenbrainz.yaml +19 -0
  148. package/engines/specs/matichon.yaml +43 -0
  149. package/engines/specs/met_museum.yaml +21 -0
  150. package/engines/specs/nasa_images.yaml +45 -0
  151. package/engines/specs/netease_music.yaml +38 -0
  152. package/engines/specs/nhk.yaml +43 -0
  153. package/engines/specs/nhtsa_vpic.yaml +20 -0
  154. package/engines/specs/noaa_swpc.yaml +20 -0
  155. package/engines/specs/nvd.yaml +38 -0
  156. package/engines/specs/obis.yaml +20 -0
  157. package/engines/specs/opencitations.yaml +52 -0
  158. package/engines/specs/openf1.yaml +21 -0
  159. package/engines/specs/openfoodfacts.yaml +45 -0
  160. package/engines/specs/openligadb.yaml +20 -0
  161. package/engines/specs/openreview.yaml +37 -0
  162. package/engines/specs/openstd.yaml +21 -0
  163. package/engines/specs/people_daily.yaml +22 -0
  164. package/engines/specs/realtime_index.yaml +34 -0
  165. package/engines/specs/ror.yaml +20 -0
  166. package/engines/specs/satnogs.yaml +20 -0
  167. package/engines/specs/sec_edgar.yaml +46 -0
  168. package/engines/specs/shfe.yaml +39 -0
  169. package/engines/specs/skillsmp.yaml +50 -0
  170. package/engines/specs/sogou_weixin_browser.yaml +30 -0
  171. package/engines/specs/soilgrids.yaml +25 -0
  172. package/engines/specs/sspai.yaml +36 -0
  173. package/engines/specs/stackexchange.yaml +46 -0
  174. package/engines/specs/std_samr.yaml +21 -0
  175. package/engines/specs/tass.yaml +43 -0
  176. package/engines/specs/tavily_free.yaml +56 -0
  177. package/engines/specs/tle_mirror.yaml +21 -0
  178. package/engines/specs/train.yaml +37 -0
  179. package/engines/specs/tvmaze.yaml +20 -0
  180. package/engines/specs/twitter_syndication.yaml +31 -0
  181. package/engines/specs/un_comtrade.yaml +23 -0
  182. package/engines/specs/unpaywall.yaml +54 -0
  183. package/engines/specs/vnpress.yaml +43 -0
  184. package/engines/specs/weather.yaml +32 -0
  185. package/engines/specs/who_don.yaml +21 -0
  186. package/engines/specs/who_gho.yaml +20 -0
  187. package/engines/specs/wikimedia_commons.yaml +34 -0
  188. package/engines/specs/wikisource.yaml +22 -0
  189. package/engines/specs/worms.yaml +19 -0
  190. package/engines/specs/x_trending.yaml +34 -0
  191. package/engines/specs/yna.yaml +43 -0
  192. package/engines/specs/ynet.yaml +43 -0
  193. package/engines/specs/yuanbao.yaml +93 -0
  194. package/engines/specs/zbmath.yaml +40 -0
  195. package/engines/specs/zdic.yaml +22 -0
  196. package/engines/specs/zenodo.yaml +27 -0
  197. package/engines/specs/zhihu_hot_app.yaml +35 -0
  198. package/mcp/clients.yaml +106 -0
  199. package/package.json +55 -12
  200. package/packages/dsh-plugin/README.md +152 -0
  201. package/packages/dsh-plugin/cordis.patch.yml +48 -0
  202. package/packages/dsh-plugin/dsh/client.js +1533 -0
  203. package/packages/dsh-plugin/dsh/diagnostics.js +54 -0
  204. package/packages/dsh-plugin/dsh/index.js +527 -0
  205. package/packages/dsh-plugin/dsh/native-tools.mjs +749 -0
  206. package/packages/dsh-plugin/dsh/runner.js +666 -0
  207. package/packages/dsh-plugin/dsh/wide-research.js +878 -0
  208. package/packages/dsh-plugin/package.json +38 -0
  209. package/packages/dsh-plugin/scripts/sync-local.sh +70 -0
  210. package/packages/dsh-plugin/test/client.test.js +406 -0
  211. package/packages/dsh-plugin/test/config.test.js +30 -0
  212. package/packages/dsh-plugin/test/diagnostics.test.js +28 -0
  213. package/packages/dsh-plugin/test/index.test.js +1382 -0
  214. package/references/query-templates.md +96 -0
  215. package/references/query-templates.yaml +118 -0
  216. package/scripts/ab_eval.py +219 -0
  217. package/scripts/ab_eval_p0p1.py +266 -0
  218. package/scripts/adaptive.py +294 -70
  219. package/scripts/answer.py +154 -0
  220. package/scripts/archive_run.py +591 -0
  221. package/scripts/argo_engine_registry.py +172 -0
  222. package/scripts/argo_paths.py +728 -0
  223. package/scripts/article.py +136 -0
  224. package/scripts/article_meta.py +131 -0
  225. package/scripts/arxiv_source.py +201 -0
  226. package/scripts/badcase_ledger.py +252 -0
  227. package/scripts/batch_probe.py +241 -0
  228. package/scripts/benchmark.py +19 -0
  229. package/scripts/bounded_run.py +144 -0
  230. package/scripts/browser_auth.py +323 -0
  231. package/scripts/budget.py +247 -0
  232. package/scripts/budget_cli.py +93 -0
  233. package/scripts/build_release_assets.sh +65 -0
  234. package/scripts/burstiness_detector.py +147 -0
  235. package/scripts/cache.py +1066 -67
  236. package/scripts/cache_guard.py +271 -0
  237. package/scripts/cache_key_vdom.py +56 -0
  238. package/scripts/cache_similarity.py +170 -0
  239. package/scripts/caliber_isolation.py +195 -0
  240. package/scripts/calibrate_lowquality.py +210 -0
  241. package/scripts/candidate_envelope.py +366 -0
  242. package/scripts/chrome_cdp.py +871 -0
  243. package/scripts/ci_drift_gate.py +90 -0
  244. package/scripts/circuit_breaker.py +389 -0
  245. package/scripts/citations.py +334 -0
  246. package/scripts/cite_cli.py +59 -0
  247. package/scripts/cjk_tokens.py +95 -0
  248. package/scripts/clarify.py +47 -24
  249. package/scripts/cli_io.py +240 -0
  250. package/scripts/config.py +683 -45
  251. package/scripts/config_cache.py +402 -0
  252. package/scripts/conn_pool.py +220 -0
  253. package/scripts/content_boundary.py +46 -0
  254. package/scripts/content_security.py +685 -0
  255. package/scripts/content_signals.py +751 -0
  256. package/scripts/crawl.py +74 -35
  257. package/scripts/cross_lingual_trace.py +293 -0
  258. package/scripts/daemon.py +866 -0
  259. package/scripts/digup_bridge.py +214 -0
  260. package/scripts/dispatch_result.py +98 -0
  261. package/scripts/engine_admission.py +366 -0
  262. package/scripts/engine_dispatch.py +982 -0
  263. package/scripts/engine_env.py +539 -0
  264. package/scripts/engine_failure.py +411 -0
  265. package/scripts/engine_families.py +738 -0
  266. package/scripts/engine_policy.py +218 -0
  267. package/scripts/engine_requires.py +190 -0
  268. package/scripts/engine_status.py +322 -0
  269. package/scripts/engine_validate.py +638 -0
  270. package/scripts/engines.py +799 -294
  271. package/scripts/engines_base.py +1521 -0
  272. package/scripts/engines_builders.py +310 -0
  273. package/scripts/engines_builders_batch10.py +204 -0
  274. package/scripts/engines_builders_batch11.py +174 -0
  275. package/scripts/engines_builders_batch12.py +103 -0
  276. package/scripts/engines_builders_batch13.py +340 -0
  277. package/scripts/engines_builders_batch14.py +656 -0
  278. package/scripts/engines_builders_batch15.py +249 -0
  279. package/scripts/engines_builders_batch9.py +1468 -0
  280. package/scripts/engines_builders_cn.py +2283 -0
  281. package/scripts/engines_builders_data.py +2923 -0
  282. package/scripts/engines_builders_data_macro.py +775 -0
  283. package/scripts/engines_builders_feeds.py +255 -0
  284. package/scripts/engines_builders_intl.py +1239 -0
  285. package/scripts/engines_builders_keyless.py +111 -0
  286. package/scripts/engines_builders_search.py +440 -0
  287. package/scripts/engines_builders_tech.py +1359 -0
  288. package/scripts/evidence.py +620 -166
  289. package/scripts/evidence_loop.py +624 -0
  290. package/scripts/evidence_tier.py +298 -0
  291. package/scripts/except_sets.py +25 -0
  292. package/scripts/extract.py +55 -16
  293. package/scripts/fact_align.py +207 -0
  294. package/scripts/fact_crosscheck.py +230 -0
  295. package/scripts/fetch.py +25 -12
  296. package/scripts/fetch_browser.py +87 -0
  297. package/scripts/fetch_jsrun.py +122 -0
  298. package/scripts/fetch_quality.py +225 -0
  299. package/scripts/fetch_render_tinyfish.py +117 -0
  300. package/scripts/fetch_v3.py +1804 -0
  301. package/scripts/focus_extract.py +350 -0
  302. package/scripts/fulltext_store.py +169 -0
  303. package/scripts/fxtwitter_api.py +325 -0
  304. package/scripts/gen_engine_catalog.py +364 -0
  305. package/scripts/gen_native_tools.py +91 -0
  306. package/scripts/gen_routing_snapshot.py +195 -0
  307. package/scripts/health_check.py +140 -402
  308. package/scripts/health_probe.py +23 -8
  309. package/scripts/hot_state.py +134 -0
  310. package/scripts/http_client.py +1053 -0
  311. package/scripts/image_ops.py +430 -0
  312. package/scripts/image_vision/vision_probe.swift +126 -0
  313. package/scripts/install.ps1 +166 -0
  314. package/scripts/install.sh +221 -0
  315. package/scripts/job.py +1186 -0
  316. package/scripts/jump_resolver.py +235 -0
  317. package/scripts/lang_capability.py +150 -0
  318. package/scripts/lang_detect.py +289 -0
  319. package/scripts/lang_matrix_probe.py +329 -0
  320. package/scripts/lang_pref.py +311 -0
  321. package/scripts/link_source.py +340 -0
  322. package/scripts/local_image.py +970 -0
  323. package/scripts/local_seek.py +402 -0
  324. package/scripts/macro_countries.py +97 -0
  325. package/scripts/matrix_search_eval.py +1187 -0
  326. package/scripts/mcp_compact.py +234 -0
  327. package/scripts/mcp_diag.py +8 -3
  328. package/scripts/mcp_handlers.py +492 -0
  329. package/scripts/mcp_handlers_surface.py +157 -0
  330. package/scripts/mcp_launch.sh +124 -0
  331. package/scripts/mcp_local.py +241 -0
  332. package/scripts/mcp_server.py +125 -419
  333. package/scripts/mcp_setup.py +431 -0
  334. package/scripts/mcp_tools.py +331 -0
  335. package/scripts/mcp_transport.py +191 -0
  336. package/scripts/mcp_warm.py +127 -0
  337. package/scripts/min_version_scan.py +335 -0
  338. package/scripts/net_proxy.py +258 -0
  339. package/scripts/network_aware.py +152 -0
  340. package/scripts/output_transparency.py +200 -0
  341. package/scripts/paid_policy.py +98 -0
  342. package/scripts/paper.py +274 -0
  343. package/scripts/pdf_extract.py +518 -0
  344. package/scripts/phrase_filter.py +66 -0
  345. package/scripts/plan.py +422 -0
  346. package/scripts/postprocess_signals.py +150 -0
  347. package/scripts/pre-commit +48 -0
  348. package/scripts/query_enhance.py +157 -0
  349. package/scripts/query_operators.py +196 -0
  350. package/scripts/query_rewriter.py +116 -7
  351. package/scripts/query_signals.py +300 -0
  352. package/scripts/query_templates.py +167 -0
  353. package/scripts/query_understanding.py +411 -0
  354. package/scripts/query_variants.py +298 -0
  355. package/scripts/quota.py +633 -76
  356. package/scripts/rank_signals.py +775 -0
  357. package/scripts/ranking_eval.py +319 -0
  358. package/scripts/readability_extract.py +433 -0
  359. package/scripts/recompute.py +415 -0
  360. package/scripts/recovery.py +616 -0
  361. package/scripts/recovery_l0.py +160 -0
  362. package/scripts/recovery_runner.py +150 -0
  363. package/scripts/redskill/redskill_engine.py +332 -0
  364. package/scripts/regression_p0p1.py +318 -0
  365. package/scripts/replay_eval.py +658 -0
  366. package/scripts/rerank_tfidf.py +225 -0
  367. package/scripts/research.py +607 -403
  368. package/scripts/research_cli.py +361 -0
  369. package/scripts/research_dossier.py +492 -0
  370. package/scripts/research_expand.py +169 -0
  371. package/scripts/research_gates.py +272 -0
  372. package/scripts/research_report.py +209 -0
  373. package/scripts/research_strategy.py +37 -0
  374. package/scripts/research_work_packages.py +195 -0
  375. package/scripts/result_lang.py +311 -0
  376. package/scripts/retry_policy.py +76 -0
  377. package/scripts/robots_guard.py +259 -0
  378. package/scripts/route.py +921 -254
  379. package/scripts/route_bangs.py +40 -0
  380. package/scripts/route_cache.py +243 -0
  381. package/scripts/route_combo.py +917 -0
  382. package/scripts/route_domains.py +341 -0
  383. package/scripts/route_lang.py +605 -0
  384. package/scripts/route_log.py +63 -0
  385. package/scripts/route_policy.py +250 -0
  386. package/scripts/rrf_classic.py +66 -0
  387. package/scripts/screenshot.py +73 -0
  388. package/scripts/search.py +886 -406
  389. package/scripts/search_attribution.py +185 -0
  390. package/scripts/search_benchmark.py +407 -0
  391. package/scripts/search_cli.py +640 -0
  392. package/scripts/search_entry.py +395 -0
  393. package/scripts/search_output.py +690 -0
  394. package/scripts/search_pipeline.py +926 -0
  395. package/scripts/search_rank.py +991 -0
  396. package/scripts/search_types.py +129 -16
  397. package/scripts/seek_locator.py +68 -0
  398. package/scripts/semantic_evidence.py +391 -0
  399. package/scripts/serp_guard.py +336 -0
  400. package/scripts/serp_kill_log.py +74 -0
  401. package/scripts/single_flight.py +120 -0
  402. package/scripts/social_engines/__init__.py +10 -0
  403. package/scripts/social_engines/bilibili_engine.py +131 -0
  404. package/scripts/social_engines/hackernews_engine.py +113 -0
  405. package/scripts/social_engines/reddit_engine.py +172 -0
  406. package/scripts/social_engines/twitter_engine.py +397 -0
  407. package/scripts/social_engines/v2ex_engine.py +101 -0
  408. package/scripts/social_engines/weibo_engine.py +128 -0
  409. package/scripts/social_engines/xiaohongshu_engine.py +137 -0
  410. package/scripts/social_engines/zhihu_engine.py +139 -0
  411. package/scripts/social_research.py +201 -0
  412. package/scripts/sogou_weixin_browser.py +318 -0
  413. package/scripts/source_ledger.py +249 -0
  414. package/scripts/stage_timing.py +58 -0
  415. package/scripts/stats_cli.py +107 -0
  416. package/scripts/straggler_policy.py +60 -0
  417. package/scripts/stylometry_detector.py +231 -0
  418. package/scripts/sync_backends.py +353 -0
  419. package/scripts/sync_installs.py +29 -0
  420. package/scripts/template_cli.py +127 -0
  421. package/scripts/text_lexicon.py +72 -0
  422. package/scripts/tfidf_router.py +109 -37
  423. package/scripts/time_utils.py +233 -0
  424. package/scripts/topic_research_profiles.py +643 -0
  425. package/scripts/train.py +737 -0
  426. package/scripts/tweet.py +596 -0
  427. package/scripts/url_canon.py +217 -0
  428. package/scripts/url_optimize.py +56 -0
  429. package/scripts/url_safety.py +236 -0
  430. package/scripts/usage_log.py +116 -0
  431. package/scripts/usage_log_clean.py +113 -0
  432. package/scripts/v2ex_nodes.py +311 -0
  433. package/scripts/variant_recall.py +188 -0
  434. package/scripts/watch.py +205 -0
  435. package/scripts/wechat_article.py +258 -0
  436. package/scripts/wide_research.py +447 -0
  437. package/scripts/wx.py +349 -0
  438. package/scripts/x_article_fetch.py +141 -0
  439. package/scripts/x_search.py +234 -0
  440. package/scripts/x_trending.py +189 -0
  441. package/scripts/yaml_load.py +67 -0
  442. package/scripts/zhihu_quota.py +139 -0
  443. package/sub-skills/ego-search/SKILL.md +253 -0
  444. package/sub-skills/ego-search/scripts/ego_search.py +800 -0
  445. package/sub-skills/ego-search/scripts/merge.py +226 -0
  446. package/sub-skills/ego-search/scripts/quality.py +64 -0
  447. package/sub-skills/ego-search/scripts/runtime.py +172 -0
  448. package/sub-skills/ego-search/scripts/safety.py +109 -0
  449. package/sub-skills/ego-search/scripts/serp_spec.py +109 -0
  450. package/sub-skills/ego-search/scripts/webbridge_adapter.py +374 -0
  451. package/sub-skills/js-run/SKILL.md +67 -0
  452. package/sub-skills/js-run/scripts/jsrun.py +112 -0
  453. package/sub-skills/js-run/scripts/shim.js +188 -0
  454. package/sub-skills/local-search/SKILL.md +50 -53
  455. package/sub-skills/local-search/bm25.py +186 -0
  456. package/sub-skills/local-search/cli_runner.py +331 -0
  457. package/sub-skills/local-search/config.yaml +152 -55
  458. package/sub-skills/local-search/engine_registry.py +91 -26
  459. package/sub-skills/local-search/{health_check.py → local_health_check.py} +140 -10
  460. package/sub-skills/local-search/local_search_adapter.py +13 -2
  461. package/sub-skills/local-search/parse_maps.yaml +40 -42
  462. package/sub-skills/local-search/parsers.py +328 -0
  463. package/sub-skills/local-search/search_v3.py +716 -242
  464. package/sub-skills/local-search/smart_router.py +47 -7
  465. package/sub-skills/local-seek/SKILL.md +110 -0
  466. package/sub-skills/local-seek/config/domains.yaml +20 -0
  467. package/sub-skills/local-seek/scripts/eval_rank.py +216 -0
  468. package/sub-skills/local-seek/scripts/eval_seek.py +224 -0
  469. package/sub-skills/local-seek/scripts/seek.py +1465 -0
  470. package/sub-skills/local-seek/scripts/seek_frecency.py +126 -0
  471. package/sub-skills/local-seek/scripts/seek_fts.py +430 -0
  472. package/sub-skills/local-seek/scripts/seek_rank.py +197 -0
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ """engines_builders — 聚合专用构建器导出(兼容 from engines_builders import *)。"""
3
+
4
+ from engines_builders_batch9 import (
5
+ _build_who_don_engine,
6
+ _build_who_gho_engine,
7
+ _build_pubmed_engine,
8
+ _build_core_engine,
9
+ _build_gdacs_engine,
10
+ _build_obis_engine,
11
+ _build_worms_engine,
12
+ _build_energy_charts_engine,
13
+ _build_jolpica_engine,
14
+ _build_openf1_engine,
15
+ _build_openligadb_engine,
16
+ _build_artic_engine,
17
+ _build_cleveland_engine,
18
+ _build_tvmaze_engine,
19
+ _build_jikan_engine,
20
+ _build_deezer_engine,
21
+ _build_listenbrainz_engine,
22
+ _build_egov_law_engine,
23
+ _build_k10plus_engine,
24
+ _build_ror_engine,
25
+ _build_soilgrids_engine,
26
+ _build_noaa_swpc_engine,
27
+ _build_satnogs_engine,
28
+ _build_tle_mirror_engine,
29
+ _build_nhtsa_vpic_engine,
30
+ )
31
+ from engines_builders_keyless import (
32
+ _build_keenable_free_engine,
33
+ )
34
+ from engines_builders_tech import (
35
+ _build_exa_engine,
36
+ _build_anysearch_engine,
37
+ _build_wechat_sogou_engine,
38
+ _build_hackernews_engine,
39
+ _build_stackoverflow_engine,
40
+ _build_google_scholar_engine,
41
+ _build_deps_dev_engine,
42
+ _build_endoflife_engine,
43
+ _build_v2ex_engine,
44
+ _build_github_engine,
45
+ _build_tineye_engine,
46
+ _build_bing_rss_engine,
47
+ )
48
+ from engines_builders_cn import (
49
+ _build_em_miaoxiang_engine,
50
+ _build_cninfo_engine,
51
+ _build_sina_quote_engine,
52
+ _build_tencent_quote_engine,
53
+ _build_em_flow_engine,
54
+ _build_ths_hot_engine,
55
+ _build_cls_telegraph_engine,
56
+ _build_em_global_news_engine,
57
+ _build_eastmoney_engine,
58
+ _build_itotii_engine,
59
+ _build_baidu_hot_engine,
60
+ _build_toutiao_hot_engine,
61
+ _build_bilibili_hot_engine,
62
+ _build_zhihu_global_engine,
63
+ _build_zhihu_user_engine,
64
+ _build_bocha_engine,
65
+ _build_bocha_ai_engine,
66
+ _build_std_samr_engine,
67
+ _build_openstd_engine,
68
+ _build_bangumi_engine,
69
+ _build_douban_movie_engine,
70
+ _build_zdic_engine,
71
+ _build_iplant_engine,
72
+ _build_people_daily_engine,
73
+ _build_flk_law_engine,
74
+ _build_wikisource_engine,
75
+ _build_weibo_hot_engine,
76
+ _build_douyin_hot_engine,
77
+ _build_csdn_engine,
78
+ _build_wallstreetcn_engine,
79
+ _build_weather_cn_engine,
80
+ )
81
+ from engines_builders_batch12 import (
82
+ _build_so_engine,
83
+ _build_shenma_engine,
84
+ )
85
+
86
+ from engines_builders_batch13 import (
87
+ _build_yahoo_finance_engine,
88
+ _build_sec_companyfacts_engine,
89
+ _build_frankfurter_engine,
90
+ )
91
+ from engines_builders_batch14 import (
92
+ _build_hkex_engine,
93
+ _build_shfe_engine,
94
+ _build_cfets_engine,
95
+ _build_defillama_engine,
96
+ )
97
+ from engines_builders_batch15 import (
98
+ _build_yuanbao_engine,
99
+ )
100
+ from engines_builders_data import (
101
+ _build_open_library_engine,
102
+ _build_weread_engine,
103
+ _build_douban_book_engine,
104
+ _build_pubchem_engine,
105
+ _build_gbif_engine,
106
+ _build_rfc_editor_engine,
107
+ _build_uniprot_engine,
108
+ _build_biorxiv_engine,
109
+ _build_un_comtrade_engine,
110
+ _build_rcsb_pdb_engine,
111
+ _build_courtlistener_engine,
112
+ _build_gutenberg_engine,
113
+ _build_wayback_cdx_engine,
114
+ _build_usgs_engine,
115
+ _build_nasa_cmr_engine,
116
+ _build_free_dictionary_engine,
117
+ _build_baidu_baike_engine,
118
+ _build_pypi_engine,
119
+ _build_clinicaltrials_engine,
120
+ _build_openfda_engine,
121
+ _build_juejin_engine,
122
+ _build_models_dev_engine,
123
+ _build_finviz_engine,
124
+ _build_seeking_alpha_engine,
125
+ _build_qweather_engine,
126
+ _build_wenshu_engine,
127
+ _build_jin10_engine,
128
+ _build_octen_engine,
129
+ _build_imdb_engine,
130
+ _build_thesportsdb_engine,
131
+ _build_itunes_engine,
132
+ _build_opencorporates_engine,
133
+ _build_google_patents_engine,
134
+ _build_marginalia_engine,
135
+ _build_wiby_engine,
136
+ )
137
+ from engines_builders_data_macro import (
138
+ _build_fred_engine,
139
+ _build_fx_rate_engine,
140
+ _build_worldbank_engine,
141
+ _build_nbs_stats_engine,
142
+ _build_eurostat_engine,
143
+ )
144
+ from engines_builders_search import (
145
+ _build_parallel_engine,
146
+ _build_parallel_free_engine,
147
+ _build_seltz_engine,
148
+ _build_you_engine,
149
+ _build_qwant_engine,
150
+ _build_ecosia_engine,
151
+ )
152
+ from engines_builders_intl import (
153
+ _build_cnii_engine,
154
+ _build_ndl_engine,
155
+ _build_kor_law_engine,
156
+ _build_hatena_bookmark_engine,
157
+ _build_dnb_engine,
158
+ _build_doaj_engine,
159
+ _build_europeana_engine,
160
+ _build_hal_engine,
161
+ _build_eu_opendata_engine,
162
+ _build_open_meteo_engine,
163
+ _build_twitter_syndication_engine,
164
+ _build_searchmysite_engine,
165
+ _build_lieu_engine,
166
+ _build_opensky_engine,
167
+ _build_electricity_maps_engine,
168
+ _build_usda_engine,
169
+ _build_tatoeba_engine,
170
+ _build_figshare_engine,
171
+ _build_tencent_kline_engine,
172
+ _build_qq_music_engine,
173
+ _build_google_news_engine,
174
+ _build_met_museum_engine,
175
+ )
176
+ from engines_builders_feeds import (
177
+ _build_rss_feed_engine,
178
+ )
179
+
180
+ __all__ = [
181
+ "_build_em_miaoxiang_engine",
182
+ "_build_cninfo_engine",
183
+ "_build_sina_quote_engine",
184
+ "_build_tencent_quote_engine",
185
+ "_build_em_flow_engine",
186
+ "_build_exa_engine",
187
+ "_build_wechat_sogou_engine",
188
+ "_build_hackernews_engine",
189
+ "_build_stackoverflow_engine",
190
+ "_build_std_samr_engine",
191
+ "_build_openstd_engine",
192
+ "_build_bangumi_engine",
193
+ "_build_douban_movie_engine",
194
+ "_build_zdic_engine",
195
+ "_build_iplant_engine",
196
+ "_build_people_daily_engine",
197
+ "_build_flk_law_engine",
198
+ "_build_wikisource_engine",
199
+ "_build_weibo_hot_engine",
200
+ "_build_douyin_hot_engine",
201
+ "_build_csdn_engine",
202
+ "_build_wallstreetcn_engine",
203
+ "_build_weather_cn_engine",
204
+ "_build_google_news_engine",
205
+ "_build_met_museum_engine",
206
+ "_build_google_scholar_engine",
207
+ "_build_v2ex_engine",
208
+ "_build_github_engine",
209
+ "_build_ths_hot_engine",
210
+ "_build_cls_telegraph_engine",
211
+ "_build_em_global_news_engine",
212
+ "_build_eastmoney_engine",
213
+ "_build_itotii_engine",
214
+ "_build_baidu_hot_engine",
215
+ "_build_toutiao_hot_engine",
216
+ "_build_bilibili_hot_engine",
217
+ "_build_open_library_engine",
218
+ "_build_weread_engine",
219
+ "_build_douban_book_engine",
220
+ "_build_fred_engine",
221
+ "_build_fx_rate_engine",
222
+ "_build_worldbank_engine",
223
+ "_build_nbs_stats_engine",
224
+ "_build_pubchem_engine",
225
+ "_build_eurostat_engine",
226
+ "_build_gbif_engine",
227
+ "_build_rfc_editor_engine",
228
+ "_build_uniprot_engine",
229
+ "_build_rcsb_pdb_engine",
230
+ "_build_courtlistener_engine",
231
+ "_build_gutenberg_engine",
232
+ "_build_wayback_cdx_engine",
233
+ "_build_usgs_engine",
234
+ "_build_nasa_cmr_engine",
235
+ "_build_free_dictionary_engine",
236
+ "_build_baidu_baike_engine",
237
+ "_build_pypi_engine",
238
+ "_build_clinicaltrials_engine",
239
+ "_build_openfda_engine",
240
+ "_build_juejin_engine",
241
+ "_build_models_dev_engine",
242
+ "_build_finviz_engine",
243
+ "_build_yahoo_finance_engine",
244
+ "_build_sec_companyfacts_engine",
245
+ "_build_frankfurter_engine",
246
+ "_build_seeking_alpha_engine",
247
+ "_build_qweather_engine",
248
+ "_build_wenshu_engine",
249
+ "_build_jin10_engine",
250
+ "_build_octen_engine",
251
+ "_build_imdb_engine",
252
+ "_build_thesportsdb_engine",
253
+ "_build_itunes_engine",
254
+ "_build_opencorporates_engine",
255
+ "_build_google_patents_engine",
256
+ "_build_marginalia_engine",
257
+ "_build_wiby_engine",
258
+ "_build_cnii_engine",
259
+ "_build_ndl_engine",
260
+ "_build_kor_law_engine",
261
+ "_build_hatena_bookmark_engine",
262
+ "_build_dnb_engine",
263
+ "_build_doaj_engine",
264
+ "_build_europeana_engine",
265
+ "_build_hal_engine",
266
+ "_build_eu_opendata_engine",
267
+ "_build_open_meteo_engine",
268
+ "_build_twitter_syndication_engine",
269
+ "_build_searchmysite_engine",
270
+ "_build_searchmysite_engine",
271
+ "_build_lieu_engine",
272
+ "_build_opensky_engine",
273
+ "_build_electricity_maps_engine",
274
+ "_build_usda_engine",
275
+ "_build_tatoeba_engine",
276
+ "_build_figshare_engine",
277
+ "_build_tencent_kline_engine",
278
+ "_build_qq_music_engine",
279
+ "_build_parallel_engine",
280
+ "_build_parallel_free_engine",
281
+ "_build_seltz_engine",
282
+ "_build_you_engine",
283
+ "_build_who_don_engine",
284
+ "_build_who_gho_engine",
285
+ "_build_pubmed_engine",
286
+ "_build_core_engine",
287
+ "_build_gdacs_engine",
288
+ "_build_obis_engine",
289
+ "_build_worms_engine",
290
+ "_build_energy_charts_engine",
291
+ "_build_jolpica_engine",
292
+ "_build_openf1_engine",
293
+ "_build_openligadb_engine",
294
+ "_build_artic_engine",
295
+ "_build_cleveland_engine",
296
+ "_build_tvmaze_engine",
297
+ "_build_jikan_engine",
298
+ "_build_deezer_engine",
299
+ "_build_listenbrainz_engine",
300
+ "_build_egov_law_engine",
301
+ "_build_k10plus_engine",
302
+ "_build_ror_engine",
303
+ "_build_soilgrids_engine",
304
+ "_build_noaa_swpc_engine",
305
+ "_build_satnogs_engine",
306
+ "_build_tle_mirror_engine",
307
+ "_build_nhtsa_vpic_engine",
308
+ "_build_rss_feed_engine",
309
+ ]
310
+
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env python3
2
+ """批次十构建器:zhihu_global 补强。
3
+
4
+ 为什么不在 engines_builders_cn.py 里加(任务原指定位置):该文件被
5
+ test_module_size_gate.py 祖父清单冻结在 2319 行(当前恰好 2319,只能减不能增),
6
+ 且 tests/ 门禁只放行新建测试文件——门禁自身文档写明「想加功能,正解是拆模块,
7
+ 不是把数字往上调」,故按 batch9 命名先例落新文件(新增文件天然合规)。
8
+
9
+ zhihu_global 说明:cn 里的旧实现与本文件同名实现并存,engines.py 注册表已
10
+ 指向本文件版本(候选池取满 + 错误文案行动提示两处补强);cn 旧版仅在
11
+ engines_builders 聚合层保留转出,属被取代代码。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import re
18
+ import sys
19
+ import time
20
+ import urllib.error
21
+ import urllib.parse
22
+ import urllib.request
23
+ from datetime import datetime
24
+ from typing import Any
25
+
26
+ from engine_env import get_env
27
+
28
+ from engines_base import (
29
+ http_open,
30
+ rank_score,
31
+ safe_search,
32
+ )
33
+ from engines_builders_cn import _parse_site_filter
34
+
35
+
36
+ class _LazyLogger:
37
+ """延迟创建 logger,避免 import logging 的重量级 import 链(traceback→dataclasses→inspect)。"""
38
+ _logger = None
39
+
40
+ def __getattr__(self, name: str):
41
+ if _LazyLogger._logger is None:
42
+ import logging
43
+ _LazyLogger._logger = logging.getLogger("unified_search.engines")
44
+ if not _LazyLogger._logger.handlers:
45
+ _LazyLogger._logger.setLevel(logging.WARNING)
46
+ _LazyLogger._logger.addHandler(logging.StreamHandler(sys.stderr))
47
+ return getattr(_LazyLogger._logger, name)
48
+
49
+
50
+ logger = _LazyLogger()
51
+
52
+
53
+ # ── zhihu_global 补强版(取代 engines_builders_cn 同名实现)───────────────────
54
+
55
+ # 端点 Count 上限:官方 global_search 上限 20(cn 旧版 min(n, 20) 同源口径)
56
+ _ZHIHU_COUNT_MAX = 20
57
+
58
+ # 非成功响应 → 可行动提示。时钟偏差类按 Message 文案识别(错误码表未公开,
59
+ # 不猜码值):签名带 X-Request-Timestamp,本机时钟漂移会让服务端判签名过期,
60
+ # 现象是「密钥没错却全挂」——提示必须指向校时,防止用户误判 key 失效反复换 key
61
+ _ZHIHU_CLOCK_HINT_RE = re.compile(r"timestamp|时间戳|签名|时钟|clock", re.I)
62
+ _ZHIHU_CLOCK_HINT = "本机时钟可能偏差导致签名过期:先校准系统时间再重试,不必反复换密钥"
63
+
64
+
65
+ def _zhihu_error_item(code: Any, message: Any) -> dict[str, Any]:
66
+ """非成功响应的 error item:保留原始 code/message,时钟类追加行动提示。"""
67
+ text = f"zhihu_global Code={code} {str(message or '')[:100]}"
68
+ if _ZHIHU_CLOCK_HINT_RE.search(str(message or "")):
69
+ text = f"{text};hint: {_ZHIHU_CLOCK_HINT}"
70
+ return {"error": text, "source": "zhihu_global"}
71
+
72
+
73
+ def _zhihu_epoch(value: Any) -> int | None:
74
+ """ISO 时间窗 → 秒级时间戳;空/不可解析返回 None(不阻断检索)。"""
75
+ raw = str(value or "").strip()
76
+ if not raw:
77
+ return None
78
+ try:
79
+ return int(datetime.fromisoformat(raw.replace("Z", "+00:00")).timestamp())
80
+ except (TypeError, ValueError):
81
+ return None
82
+
83
+
84
+ _DB_OPERATOR_RE = re.compile(r"(?:^|\s)db:(all|realtime|static)(?=\s|$)", re.I)
85
+
86
+
87
+ def _parse_db_operator(query: str) -> tuple[str, str]:
88
+ """解析 `db:realtime|static|all` 查询操作符,返回 (db, 剩余查询)。"""
89
+ match = _DB_OPERATOR_RE.search(query or "")
90
+ if not match:
91
+ return "all", query
92
+ db = match.group(1).lower()
93
+ rest = _DB_OPERATOR_RE.sub(" ", query, count=1).strip()
94
+ return db, rest or (query or "")
95
+
96
+
97
+ def _build_zhihu_publish_filter(since: Any, until: Any) -> str:
98
+ """把 --since/--until 下推成官方 Filter(publish_time 支持 >=/<=)。"""
99
+ since_ts = _zhihu_epoch(since)
100
+ until_ts = _zhihu_epoch(until)
101
+ parts = []
102
+ if since_ts is not None:
103
+ parts.append(f"publish_time>={since_ts}")
104
+ if until_ts is not None:
105
+ parts.append(f"publish_time<={until_ts}")
106
+ return " AND ".join(parts)
107
+
108
+
109
+ def _build_zhihu_global_engine(spec: dict[str, Any]) -> Any:
110
+ """知乎开放平台全网搜索(developer.zhihu.com global_search)。
111
+
112
+ 在 cn 旧版语义之上的两处微机制补强(2026-09-26):
113
+ 1. 候选池取满再截断:Filter host== 是服务端在候选池上后筛,只取 n 条
114
+ 会把「过滤后恰好命中」的候选一并筛掉,空结果会被模型读成「知乎没有
115
+ 相关内容」;带站点限定或时间下限(since)时取满端点 Count 上限,
116
+ 客户端再截断到 n。since 的时间 Filter 语法官方未文档化,先只承担
117
+ 「取满候选池」语义。
118
+ 2. 错误码 → 行动提示:时钟偏差类错误给「校时」提示而非裸错误。
119
+ 查询语法与 cn 版一致:site:/host: → Filter host==,普通查询 SearchDB=all。
120
+ """
121
+ timeout = spec.get("timeout", 10)
122
+
123
+ @safe_search
124
+ def _engine(query: str, n: int = 5, _timeout: float | None = None, **kwargs) -> list[dict[str, Any]]:
125
+ to = _timeout or timeout
126
+ secret = get_env(["ARGO_ZHIHU_ACCESS_SECRET", "ZHIHU_ACCESS_SECRET"])
127
+ if not secret:
128
+ return []
129
+
130
+ # 解析 site:/host: 站点限定语法 → Filter: host=="..."(实现单点在 cn)
131
+ filter_expr, search_query = _parse_site_filter(query)
132
+ # db:realtime|static|all:官方 SearchDB 选择(默认 all)
133
+ db, search_query = _parse_db_operator(search_query or query)
134
+ # --since/--until → 官方 publish_time 过滤(文档 2026-10 正式定义)
135
+ _time_filter = _build_zhihu_publish_filter(kwargs.get("since"),
136
+ kwargs.get("until"))
137
+ if _time_filter:
138
+ filter_expr = (f"{filter_expr} AND {_time_filter}"
139
+ if filter_expr else _time_filter)
140
+
141
+ filtered = bool(filter_expr or db != "all")
142
+ params: dict[str, Any] = {
143
+ "Query": search_query or query,
144
+ "Count": str(_ZHIHU_COUNT_MAX if filtered else min(int(n or 5), _ZHIHU_COUNT_MAX)),
145
+ "SearchDB": db,
146
+ }
147
+ if filter_expr:
148
+ params["Filter"] = filter_expr
149
+ url = "https://developer.zhihu.com/api/v1/content/global_search?" + urllib.parse.urlencode(params)
150
+ headers = {
151
+ "Authorization": f"Bearer {secret}",
152
+ "X-Request-Timestamp": str(int(time.time())),
153
+ "Content-Type": "application/json",
154
+ "User-Agent": "argo-search/2.6 (unified-search@local)",
155
+ }
156
+ try:
157
+ with http_open(urllib.request.Request(url, headers=headers), timeout=to, engine=spec.get("_name", "")) as resp:
158
+ data = json.loads(resp.read().decode("utf-8", "replace"))
159
+ except urllib.error.HTTPError as e:
160
+ # 401/403 等必须暴露为 error item 而非静默空——调用侧把
161
+ # 「没配置」「鉴权失败」「没结果」区分开才可行动
162
+ return [{"error": f"zhihu_global API HTTP {e.code}", "source": "zhihu_global"}]
163
+ except Exception as e:
164
+ logger.warning(f"zhihu_global 失败: {e}")
165
+ return [{"error": f"zhihu_global {type(e).__name__}: {e}", "source": "zhihu_global"}]
166
+ if data.get("Code") not in (0, None):
167
+ # 30001=频率限制 30002=配额限制,显式暴露供配额状态机归类
168
+ logger.warning(f"zhihu_global 返回码异常: {data.get('Code')} {data.get('Message')}")
169
+ return [_zhihu_error_item(data.get("Code"), data.get("Message"))]
170
+ items = (data.get("Data") or {}).get("Items") or []
171
+ results = []
172
+ for _rk3, item in enumerate(items[:n]):
173
+ if not isinstance(item, dict):
174
+ continue
175
+ title = (item.get("Title") or "").strip()
176
+ # API 标题统一带「 - 知乎」尾巴:截断前剥掉(先剥再切,尾巴不占正文)
177
+ if title.endswith(" - 知乎"):
178
+ title = title[: -len(" - 知乎")].rstrip()
179
+ url_ = item.get("Url") or ""
180
+ snippet = item.get("ContentText") or ""
181
+ # 去 <em> 高亮标签
182
+ snippet = re.sub(r"<[^>]+>", "", snippet).strip()
183
+ if not title and not url_:
184
+ continue
185
+ # 结构化信号:权威等级 / 互动 / 时效
186
+ social_meta = {
187
+ "author": item.get("AuthorName") or "",
188
+ "content_type": item.get("ContentType") or "",
189
+ "vote_up": item.get("VoteUpCount") or 0,
190
+ "comment_count": item.get("CommentCount") or 0,
191
+ "authority_level": item.get("AuthorityLevel") or "",
192
+ "edit_time": item.get("EditTime") or 0,
193
+ }
194
+ results.append({
195
+ "title": title[:200],
196
+ "url": url_,
197
+ "snippet": snippet[:300],
198
+ "source": "zhihu_global",
199
+ "score": rank_score(0.7, _rk3),
200
+ "authority_level": social_meta["authority_level"],
201
+ "social_meta": social_meta,
202
+ })
203
+ return results
204
+ return _engine
@@ -0,0 +1,174 @@
1
+ #!/usr/bin/env python3
2
+ """批次十一构建器:图片检索能力的两个补强源(2026-09-26)。
3
+
4
+ 本批只收图源,目标是修「搜到图源却拿不到图」这一类缺陷的通用面:
5
+
6
+ wikimedia_commons Wikimedia Commons(免 key,通用图库,带 CC 许可与宽高)
7
+
8
+ 为什么单独建一个 builder 而不是声明式 spec:Commons 的 `query.pages` 是
9
+ **以 pageid 为键的字典**,不是数组——`_extract_items` 遇到 dict 会把它当成
10
+ 「单条结果」包成 `[{pageid: {...}}]`,声明式 output_map 取不到 title/url
11
+ (实测返回空列表)。这属于查询形状不兼容,只能用代码展开。
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import sys
18
+ import urllib.error
19
+ import urllib.parse
20
+ import urllib.request
21
+ from typing import Any
22
+
23
+ from engines_base import (
24
+ finalize_image_fields,
25
+ rank_score,
26
+ safe_search,
27
+ http_open,
28
+ )
29
+
30
+
31
+ class _LazyLogger:
32
+ """延迟创建 logger,避免 import logging 的重量级 import 链(traceback→dataclasses→inspect)。"""
33
+ _logger = None
34
+
35
+ def __getattr__(self, name: str):
36
+ if _LazyLogger._logger is None:
37
+ import logging
38
+ _LazyLogger._logger = logging.getLogger("unified_search.engines")
39
+ if not _LazyLogger._logger.handlers:
40
+ _LazyLogger._logger.setLevel(logging.WARNING)
41
+ _LazyLogger._logger.addHandler(logging.StreamHandler(sys.stderr))
42
+ return getattr(_LazyLogger._logger, name)
43
+
44
+
45
+ logger = _LazyLogger()
46
+
47
+ _UA = "argo-search/2.9 (+batch11; wikimedia_commons)"
48
+ _API = "https://commons.wikimedia.org/w/api.php"
49
+
50
+ # Commons 的 extmetadata 是一层「字段 → {value, source, hidden}」的包装,
51
+ # 取值要过两道:raw["LicenseShortName"]["value"]。漏掉里层取键会拿到 dict
52
+ # 本身,_coerce_field 会把它丢成空串——表现为「许可字段时有时无」。
53
+ #
54
+ # 许可取两个字段:`LicenseShortName` 是给人看的(`CC BY-SA 4.0`),
55
+ # `License` 是机器码(`cc-by-sa-4.0`)。两者都要——实测有约 8% 的文件
56
+ # shortname 只写「Attribution」(Commons 在无法确定具体协议时的泛称),
57
+ # 而机器码为空;反过来也有机器码齐全而 shortname 缺失的。任一可用即可判定。
58
+ _META_FIELDS = ("LicenseShortName", "License", "Artist", "ImageDescription",
59
+ "LicenseUrl")
60
+
61
+
62
+ def _meta(ext: dict[str, Any], key: str) -> str:
63
+ """取 extmetadata 里的纯文本值(内层是 {value, source, hidden})。"""
64
+ node = ext.get(key)
65
+ if isinstance(node, dict):
66
+ node = node.get("value")
67
+ if node is None:
68
+ return ""
69
+ text = str(node)
70
+ # 上游描述字段带 HTML(<a href=...>、<span>),剥标签留文本——
71
+ # 交付出去的 snippet 里带标签既占字数又不可读。
72
+ if "<" in text and ">" in text:
73
+ import re
74
+ text = re.sub(r"<[^>]+>", " ", text)
75
+ text = re.sub(r"\s+", " ", text)
76
+ return text.strip()
77
+
78
+
79
+ def _build_wikimedia_commons_engine(spec: dict[str, Any]) -> Any:
80
+ """Wikimedia Commons 通用图库(免 key、CC 素材、字段齐全)。"""
81
+ timeout = spec.get("timeout", 20)
82
+ eng = spec.get("_name", "wikimedia_commons")
83
+
84
+ @safe_search
85
+ def _engine(query: str, n: int = 5, _timeout: float | None = None, **kwargs) -> list[dict[str, Any]]:
86
+ to = _timeout or timeout
87
+ q = (query or "").strip()
88
+ if not q:
89
+ return []
90
+ # filetype:bitmap 排除 PDF/DjVu/SVG 等非位图文件——不筛的话「猫」这类
91
+ # 查询会混进大量古籍扫描 PDF,它们没有可直接展示的位图。
92
+ params = {
93
+ "action": "query",
94
+ "generator": "search",
95
+ "gsrsearch": f"filetype:bitmap {q}",
96
+ "gsrnamespace": "6", # namespace 6 = File
97
+ "gsrlimit": str(max(1, min(int(n), 50))),
98
+ "prop": "imageinfo",
99
+ "iiprop": "url|size|extmetadata",
100
+ "iiurlwidth": "480", # 缩略图宽度,直接可嵌入
101
+ "format": "json",
102
+ "formatversion": "2",
103
+ }
104
+ url = f"{_API}?{urllib.parse.urlencode(params)}"
105
+ req = urllib.request.Request(url, headers={"User-Agent": _UA, "Accept": "application/json"})
106
+ try:
107
+ with http_open(req, timeout=to, engine=eng) as resp:
108
+ data = json.loads(resp.read().decode("utf-8", "replace"))
109
+ except (urllib.error.URLError, OSError, ValueError) as e:
110
+ logger.warning(f"Wikimedia Commons 失败: {e}")
111
+ return []
112
+
113
+ pages: Any = ((data or {}).get("query") or {}).get("pages")
114
+ # formatversion=2 返回 list,=1 返回 dict。两种都收——上游改默认值
115
+ # 是常事,只认一种会让引擎在版本切换时静默归零。
116
+ if isinstance(pages, dict):
117
+ pages = list(pages.values())
118
+ if not isinstance(pages, list):
119
+ return []
120
+
121
+ results: list[dict[str, Any]] = []
122
+ for rk, page in enumerate(pages[: max(1, int(n))]):
123
+ if not isinstance(page, dict):
124
+ continue
125
+ info = page.get("imageinfo")
126
+ if isinstance(info, list):
127
+ info = info[0] if info else None
128
+ if not isinstance(info, dict):
129
+ continue
130
+ title = str(page.get("title") or "").strip()
131
+ # File: 前缀是 MediaWiki 的命名空间标记,不是文件名的一部分
132
+ title = title[5:].strip() if title.startswith("File:") else title
133
+ img = str(info.get("url") or "").strip()
134
+ if not title or not img:
135
+ continue
136
+
137
+ ext = info.get("extmetadata")
138
+ ext = ext if isinstance(ext, dict) else {}
139
+ license_label = _meta(ext, "LicenseShortName")
140
+ # shortname 是泛称(Commons 对无法确定具体协议的件写「Attribution」)
141
+ # 或缺失时,退到机器码 `cc-by-sa-4.0`——它虽然不好读,但能被
142
+ # finalize_image_fields 归一成标准标注,比一个认不出的泛称有用。
143
+ if not license_label or license_label.strip().lower() in (
144
+ "attribution", "copyrighted", "unknown"):
145
+ license_label = _meta(ext, "License") or license_label
146
+ artist = _meta(ext, "Artist")
147
+ desc = _meta(ext, "ImageDescription")
148
+ page_url = str(page.get("descriptionurl") or "").strip()
149
+ if not page_url:
150
+ page_url = f"https://commons.wikimedia.org/wiki/File:{urllib.parse.quote(title.replace(' ', '_'))}"
151
+
152
+ snippet_bits = [b for b in (artist and f"作者 {artist}", desc) if b]
153
+ snippet = " · ".join(snippet_bits) or "Wikimedia Commons 开放素材"
154
+
155
+ row: dict[str, Any] = {
156
+ "title": title[:200],
157
+ "url": page_url,
158
+ "snippet": snippet[:300],
159
+ "source": eng,
160
+ "score": rank_score(0.85, rk),
161
+ # 图片直链用 imageinfo.url(原图),thumburl 是缩略图——
162
+ # 交付时给原图,需要小图的一方自己缩(反过来则拿不到原图)。
163
+ "image_url": img,
164
+ "image_license": license_label,
165
+ "image_license_url": _meta(ext, "LicenseUrl"),
166
+ "image_width": info.get("width"),
167
+ "image_height": info.get("height"),
168
+ }
169
+ results.append(row)
170
+ # 尺寸转 int / 许可归一 / 算商用判据——与声明式路径同一个收尾函数,
171
+ # 保证两类引擎交付的字段契约完全一致。
172
+ return finalize_image_fields(results, spec.get("image_license") or "")
173
+
174
+ return _engine