provider-guide 2.2.0a0__py3-none-any.whl

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 (484) hide show
  1. provider_guide-2.2.0a0.dist-info/METADATA +2344 -0
  2. provider_guide-2.2.0a0.dist-info/RECORD +484 -0
  3. provider_guide-2.2.0a0.dist-info/WHEEL +5 -0
  4. provider_guide-2.2.0a0.dist-info/entry_points.txt +2 -0
  5. provider_guide-2.2.0a0.dist-info/top_level.txt +1 -0
  6. src/__init__.py +6 -0
  7. src/bootstrap/__init__.py +5 -0
  8. src/bootstrap/app_factory.py +50 -0
  9. src/bootstrap/lifecycle.py +50 -0
  10. src/bootstrap/webui_bindings.py +94 -0
  11. src/core/__init__.py +30 -0
  12. src/core/auth/__init__.py +9 -0
  13. src/core/auth/session.py +28 -0
  14. src/core/config/__init__.py +62 -0
  15. src/core/config/base.py +4 -0
  16. src/core/config/manager.py +222 -0
  17. src/core/config/reload_policy.py +140 -0
  18. src/core/config/resolver.py +32 -0
  19. src/core/config/sections.py +260 -0
  20. src/core/dispatch/__init__.py +2 -0
  21. src/core/dispatch/candidate.py +168 -0
  22. src/core/dispatch/executors.py +428 -0
  23. src/core/dispatch/fncall_context.py +125 -0
  24. src/core/dispatch/gateway.py +110 -0
  25. src/core/dispatch/registry.py +218 -0
  26. src/core/dispatch/runtime_view.py +152 -0
  27. src/core/dispatch/selector.py +431 -0
  28. src/core/errors/__init__.py +121 -0
  29. src/core/errors/base.py +61 -0
  30. src/core/errors/business.py +65 -0
  31. src/core/errors/platform.py +158 -0
  32. src/core/fncall/__init__.py +19 -0
  33. src/core/fncall/base.py +11 -0
  34. src/core/fncall/parsers/__init__.py +9 -0
  35. src/core/fncall/parsers/stream.py +4 -0
  36. src/core/fncall/parsers/xml_parser.py +4 -0
  37. src/core/fncall/prompt/__init__.py +5 -0
  38. src/core/fncall/prompt/history.py +4 -0
  39. src/core/fncall/prompt/inject.py +43 -0
  40. src/core/fncall/prompt/templates.py +4 -0
  41. src/core/fncall/protocols/__init__.py +25 -0
  42. src/core/fncall/protocols/_echotools_shims.py +28 -0
  43. src/core/fncall/registry.py +70 -0
  44. src/core/fncall/shared/__init__.py +23 -0
  45. src/core/fncall/shared/coercion.py +4 -0
  46. src/core/fncall/shared/loop_detect.py +4 -0
  47. src/core/fncall/shared/normalization.py +4 -0
  48. src/core/fncall/shared/uuid7.py +6 -0
  49. src/core/fncall/shared/xml_helpers.py +4 -0
  50. src/core/http_errors.py +31 -0
  51. src/core/observability/__init__.py +17 -0
  52. src/core/observability/services.py +83 -0
  53. src/core/plugins/__init__.py +3 -0
  54. src/core/plugins/runtime.py +143 -0
  55. src/core/server/__init__.py +61 -0
  56. src/core/server/app.py +17 -0
  57. src/core/server/app_host.py +162 -0
  58. src/core/server/http_utils.py +80 -0
  59. src/core/server/infra/connector.py +51 -0
  60. src/core/server/infra/ipc/__init__.py +5 -0
  61. src/core/server/infra/reload/__init__.py +26 -0
  62. src/core/server/infra/reload/classifier.py +125 -0
  63. src/core/server/infra/reload/coordinator.py +117 -0
  64. src/core/server/infra/reload/file_watcher.py +403 -0
  65. src/core/server/infra/reload/internal/__init__.py +17 -0
  66. src/core/server/infra/reload/internal/connection_drain.py +51 -0
  67. src/core/server/infra/reload/internal/pre_restart.py +55 -0
  68. src/core/server/infra/reload/internal/runtime_state.py +23 -0
  69. src/core/server/infra/reload/restart.py +48 -0
  70. src/core/server/infra/reload/service.py +145 -0
  71. src/core/server/infra/shutdown.py +20 -0
  72. src/core/server/infra/terminal_sessions.py +533 -0
  73. src/core/server/infra/watcher.py +10 -0
  74. src/core/server/infra/win_asyncio.py +37 -0
  75. src/core/server/keys.py +10 -0
  76. src/core/server/middleware.py +140 -0
  77. src/core/server/proxy.py +91 -0
  78. src/core/server/runner.py +242 -0
  79. src/core/server/worker.py +428 -0
  80. src/core/utils/__init__.py +2 -0
  81. src/core/utils/compat/models_cache.py +184 -0
  82. src/core/utils/compat/proxy_selector.py +50 -0
  83. src/core/utils/compat/scriptgen.py +142 -0
  84. src/core/utils/compat/tools.py +103 -0
  85. src/core/utils/files.py +4 -0
  86. src/core/utils/ids.py +4 -0
  87. src/core/utils/io_utils.py +4 -0
  88. src/core/utils/retry.py +4 -0
  89. src/core/utils/scheduler.py +4 -0
  90. src/logger.py +482 -0
  91. src/paths.py +57 -0
  92. src/platforms/__init__.py +6 -0
  93. src/platforms/aitianhu2/__init__.py +5 -0
  94. src/platforms/aitianhu2/accounts.py +56 -0
  95. src/platforms/aitianhu2/adapter.py +7 -0
  96. src/platforms/aitianhu2/core/__init__.py +8 -0
  97. src/platforms/aitianhu2/core/adaptercore.py +160 -0
  98. src/platforms/aitianhu2/core/auth.py +244 -0
  99. src/platforms/aitianhu2/core/chat.py +771 -0
  100. src/platforms/aitianhu2/core/client.py +749 -0
  101. src/platforms/aitianhu2/core/constants.py +38 -0
  102. src/platforms/aitianhu2/core/headers.py +50 -0
  103. src/platforms/aitianhu2/core/models.py +93 -0
  104. src/platforms/aitianhu2/core/persistence.py +214 -0
  105. src/platforms/aitianhu2/core/pow.py +134 -0
  106. src/platforms/aitianhu2/core/sentinel.py +85 -0
  107. src/platforms/aitianhu2/core/upload.py +358 -0
  108. src/platforms/aitianhu2/util.py +25 -0
  109. src/platforms/apiairforce/__init__.py +5 -0
  110. src/platforms/apiairforce/accounts.py +9 -0
  111. src/platforms/apiairforce/adapter.py +7 -0
  112. src/platforms/apiairforce/core/__init__.py +6 -0
  113. src/platforms/apiairforce/core/adaptercore.py +96 -0
  114. src/platforms/apiairforce/core/client.py +193 -0
  115. src/platforms/apiairforce/core/constants.py +13 -0
  116. src/platforms/apiairforce/core/headers.py +24 -0
  117. src/platforms/apiairforce/core/payloads.py +36 -0
  118. src/platforms/apiairforce/core/sse.py +21 -0
  119. src/platforms/apiairforce/util.py +25 -0
  120. src/platforms/azuretranslate/__init__.py +6 -0
  121. src/platforms/azuretranslate/accounts.py +23 -0
  122. src/platforms/azuretranslate/adapter.py +8 -0
  123. src/platforms/azuretranslate/core/__init__.py +1 -0
  124. src/platforms/azuretranslate/core/adaptercore.py +103 -0
  125. src/platforms/azuretranslate/core/client.py +324 -0
  126. src/platforms/azuretranslate/core/constants.py +30 -0
  127. src/platforms/azuretranslate/util.py +55 -0
  128. src/platforms/base.py +267 -0
  129. src/platforms/caiyuesbk/__init__.py +5 -0
  130. src/platforms/caiyuesbk/accounts.py +8 -0
  131. src/platforms/caiyuesbk/adapter.py +7 -0
  132. src/platforms/caiyuesbk/core/__init__.py +8 -0
  133. src/platforms/caiyuesbk/core/adaptercore.py +180 -0
  134. src/platforms/caiyuesbk/core/client.py +389 -0
  135. src/platforms/caiyuesbk/core/constants.py +41 -0
  136. src/platforms/caiyuesbk/core/headers.py +31 -0
  137. src/platforms/caiyuesbk/core/payloads.py +43 -0
  138. src/platforms/caiyuesbk/core/sse.py +21 -0
  139. src/platforms/caiyuesbk/util.py +25 -0
  140. src/platforms/capabilities.py +143 -0
  141. src/platforms/cerebras/__init__.py +5 -0
  142. src/platforms/cerebras/accounts.py +19 -0
  143. src/platforms/cerebras/adapter.py +7 -0
  144. src/platforms/cerebras/core/__init__.py +8 -0
  145. src/platforms/cerebras/core/adaptercore.py +186 -0
  146. src/platforms/cerebras/core/client.py +309 -0
  147. src/platforms/cerebras/core/constants.py +32 -0
  148. src/platforms/cerebras/core/extract.py +61 -0
  149. src/platforms/cerebras/core/payloads.py +54 -0
  150. src/platforms/cerebras/util.py +25 -0
  151. src/platforms/chatmoe/__init__.py +5 -0
  152. src/platforms/chatmoe/accounts.py +11 -0
  153. src/platforms/chatmoe/adapter.py +7 -0
  154. src/platforms/chatmoe/core/__init__.py +8 -0
  155. src/platforms/chatmoe/core/adaptercore.py +211 -0
  156. src/platforms/chatmoe/core/client.py +364 -0
  157. src/platforms/chatmoe/core/constants.py +36 -0
  158. src/platforms/chatmoe/core/headers.py +35 -0
  159. src/platforms/chatmoe/core/payloads.py +52 -0
  160. src/platforms/chatmoe/core/sse.py +23 -0
  161. src/platforms/chatmoe/util.py +25 -0
  162. src/platforms/chutes/__init__.py +5 -0
  163. src/platforms/chutes/accounts.py +38 -0
  164. src/platforms/chutes/adapter.py +7 -0
  165. src/platforms/chutes/core/__init__.py +8 -0
  166. src/platforms/chutes/core/adaptercore.py +192 -0
  167. src/platforms/chutes/core/client.py +354 -0
  168. src/platforms/chutes/core/constants.py +26 -0
  169. src/platforms/chutes/core/headers.py +18 -0
  170. src/platforms/chutes/core/payloads.py +49 -0
  171. src/platforms/chutes/core/sse.py +24 -0
  172. src/platforms/chutes/util.py +25 -0
  173. src/platforms/codebuddy/__init__.py +5 -0
  174. src/platforms/codebuddy/accounts.py +26 -0
  175. src/platforms/codebuddy/adapter.py +7 -0
  176. src/platforms/codebuddy/core/__init__.py +8 -0
  177. src/platforms/codebuddy/core/adaptercore.py +193 -0
  178. src/platforms/codebuddy/core/client.py +322 -0
  179. src/platforms/codebuddy/core/constants.py +24 -0
  180. src/platforms/codebuddy/core/headers.py +59 -0
  181. src/platforms/codebuddy/core/payloads.py +33 -0
  182. src/platforms/codebuddy/core/sse.py +27 -0
  183. src/platforms/codebuddy/util.py +66 -0
  184. src/platforms/cursor/__init__.py +5 -0
  185. src/platforms/cursor/accounts.py +9 -0
  186. src/platforms/cursor/adapter.py +7 -0
  187. src/platforms/cursor/core/__init__.py +3 -0
  188. src/platforms/cursor/core/adaptercore.py +203 -0
  189. src/platforms/cursor/core/client.py +425 -0
  190. src/platforms/cursor/core/constants.py +72 -0
  191. src/platforms/cursor/core/conversation.py +156 -0
  192. src/platforms/cursor/core/extract.py +318 -0
  193. src/platforms/cursor/core/headers.py +45 -0
  194. src/platforms/cursor/core/payloads.py +31 -0
  195. src/platforms/cursor/core/refusal.py +148 -0
  196. src/platforms/cursor/core/sanitize.py +304 -0
  197. src/platforms/cursor/core/sse.py +54 -0
  198. src/platforms/cursor/util.py +84 -0
  199. src/platforms/deepl/__init__.py +6 -0
  200. src/platforms/deepl/accounts.py +16 -0
  201. src/platforms/deepl/adapter.py +8 -0
  202. src/platforms/deepl/core/__init__.py +1 -0
  203. src/platforms/deepl/core/adaptercore.py +103 -0
  204. src/platforms/deepl/core/client.py +304 -0
  205. src/platforms/deepl/core/constants.py +32 -0
  206. src/platforms/deepl/util.py +55 -0
  207. src/platforms/deepseek/__init__.py +10 -0
  208. src/platforms/deepseek/accounts.py +54 -0
  209. src/platforms/deepseek/adapter.py +8 -0
  210. src/platforms/deepseek/core/__init__.py +6 -0
  211. src/platforms/deepseek/core/adaptercore.py +234 -0
  212. src/platforms/deepseek/core/client.py +530 -0
  213. src/platforms/deepseek/core/constants.py +89 -0
  214. src/platforms/deepseek/core/headers.py +65 -0
  215. src/platforms/deepseek/core/hif.py +115 -0
  216. src/platforms/deepseek/core/modelcache.py +149 -0
  217. src/platforms/deepseek/core/payloads.py +43 -0
  218. src/platforms/deepseek/core/pow.py +263 -0
  219. src/platforms/deepseek/core/runtime.py +8 -0
  220. src/platforms/deepseek/core/session.py +319 -0
  221. src/platforms/deepseek/core/sessionapi.py +320 -0
  222. src/platforms/deepseek/core/stream.py +477 -0
  223. src/platforms/deepseek/core/streamparser.py +454 -0
  224. src/platforms/deepseek/core/user.py +422 -0
  225. src/platforms/deepseek/core/userapi.py +423 -0
  226. src/platforms/deepseek/util.py +170 -0
  227. src/platforms/edgetts/__init__.py +5 -0
  228. src/platforms/edgetts/accounts.py +32 -0
  229. src/platforms/edgetts/adapter.py +4 -0
  230. src/platforms/edgetts/core/__init__.py +1 -0
  231. src/platforms/edgetts/core/adaptercore.py +157 -0
  232. src/platforms/edgetts/core/client.py +514 -0
  233. src/platforms/edgetts/core/constants.py +61 -0
  234. src/platforms/edgetts/core/drm.py +143 -0
  235. src/platforms/edgetts/core/tts.py +108 -0
  236. src/platforms/edgetts/core/websocket.py +235 -0
  237. src/platforms/edgetts/util.py +55 -0
  238. src/platforms/googletranslate/__init__.py +6 -0
  239. src/platforms/googletranslate/accounts.py +12 -0
  240. src/platforms/googletranslate/adapter.py +8 -0
  241. src/platforms/googletranslate/core/__init__.py +1 -0
  242. src/platforms/googletranslate/core/adaptercore.py +103 -0
  243. src/platforms/googletranslate/core/client.py +212 -0
  244. src/platforms/googletranslate/core/constants.py +32 -0
  245. src/platforms/googletranslate/util.py +55 -0
  246. src/platforms/gtts/__init__.py +5 -0
  247. src/platforms/gtts/accounts.py +29 -0
  248. src/platforms/gtts/adapter.py +7 -0
  249. src/platforms/gtts/core/__init__.py +1 -0
  250. src/platforms/gtts/core/adaptercore.py +161 -0
  251. src/platforms/gtts/core/client.py +134 -0
  252. src/platforms/gtts/core/constants.py +22 -0
  253. src/platforms/gtts/core/headers.py +26 -0
  254. src/platforms/gtts/core/payloads.py +33 -0
  255. src/platforms/gtts/core/sse.py +31 -0
  256. src/platforms/gtts/core/tts.py +186 -0
  257. src/platforms/gtts/util.py +76 -0
  258. src/platforms/n1n/__init__.py +5 -0
  259. src/platforms/n1n/accounts.py +11 -0
  260. src/platforms/n1n/adapter.py +7 -0
  261. src/platforms/n1n/core/__init__.py +1 -0
  262. src/platforms/n1n/core/adaptercore.py +134 -0
  263. src/platforms/n1n/core/client.py +227 -0
  264. src/platforms/n1n/core/constants.py +21 -0
  265. src/platforms/n1n/core/headers.py +30 -0
  266. src/platforms/n1n/core/models.py +649 -0
  267. src/platforms/n1n/core/payloads.py +37 -0
  268. src/platforms/n1n/core/sse.py +21 -0
  269. src/platforms/n1n/util.py +66 -0
  270. src/platforms/noobkeys/__init__.py +6 -0
  271. src/platforms/noobkeys/accounts.py +10 -0
  272. src/platforms/noobkeys/adapter.py +8 -0
  273. src/platforms/noobkeys/core/__init__.py +1 -0
  274. src/platforms/noobkeys/core/adaptercore.py +150 -0
  275. src/platforms/noobkeys/core/client.py +424 -0
  276. src/platforms/noobkeys/core/constants.py +34 -0
  277. src/platforms/noobkeys/core/headers.py +22 -0
  278. src/platforms/noobkeys/core/payloads.py +36 -0
  279. src/platforms/noobkeys/core/sse.py +49 -0
  280. src/platforms/noobkeys/util.py +58 -0
  281. src/platforms/nvidia/__init__.py +6 -0
  282. src/platforms/nvidia/accounts.py +10 -0
  283. src/platforms/nvidia/adapter.py +8 -0
  284. src/platforms/nvidia/core/__init__.py +1 -0
  285. src/platforms/nvidia/core/adaptercore.py +185 -0
  286. src/platforms/nvidia/core/client.py +324 -0
  287. src/platforms/nvidia/core/constants.py +22 -0
  288. src/platforms/nvidia/core/headers.py +28 -0
  289. src/platforms/nvidia/core/payloads.py +48 -0
  290. src/platforms/nvidia/core/sse.py +54 -0
  291. src/platforms/nvidia/util.py +55 -0
  292. src/platforms/ollama/__init__.py +6 -0
  293. src/platforms/ollama/accounts.py +150 -0
  294. src/platforms/ollama/adapter.py +25 -0
  295. src/platforms/ollama/core/__init__.py +6 -0
  296. src/platforms/ollama/core/adaptercore.py +235 -0
  297. src/platforms/ollama/core/cache.py +85 -0
  298. src/platforms/ollama/core/client.py +954 -0
  299. src/platforms/ollama/core/constants.py +40 -0
  300. src/platforms/ollama/core/detect.py +46 -0
  301. src/platforms/ollama/core/discover.py +218 -0
  302. src/platforms/ollama/core/payloads.py +94 -0
  303. src/platforms/ollama/core/registry.py +36 -0
  304. src/platforms/ollama/core/sse.py +67 -0
  305. src/platforms/ollama/util.py +126 -0
  306. src/platforms/openaifm/__init__.py +6 -0
  307. src/platforms/openaifm/accounts.py +12 -0
  308. src/platforms/openaifm/adapter.py +8 -0
  309. src/platforms/openaifm/core/__init__.py +1 -0
  310. src/platforms/openaifm/core/adaptercore.py +156 -0
  311. src/platforms/openaifm/core/client.py +152 -0
  312. src/platforms/openaifm/core/constants.py +100 -0
  313. src/platforms/openaifm/core/headers.py +30 -0
  314. src/platforms/openaifm/core/tts.py +49 -0
  315. src/platforms/openaifm/util.py +65 -0
  316. src/platforms/opencode/__init__.py +6 -0
  317. src/platforms/opencode/accounts.py +15 -0
  318. src/platforms/opencode/adapter.py +8 -0
  319. src/platforms/opencode/core/__init__.py +3 -0
  320. src/platforms/opencode/core/adaptercore.py +143 -0
  321. src/platforms/opencode/core/client.py +801 -0
  322. src/platforms/opencode/core/constants.py +58 -0
  323. src/platforms/opencode/core/headers.py +18 -0
  324. src/platforms/opencode/core/payloads.py +40 -0
  325. src/platforms/opencode/core/proxypool.py +537 -0
  326. src/platforms/opencode/core/proxyscore.py +363 -0
  327. src/platforms/opencode/core/sse.py +48 -0
  328. src/platforms/opencode/util.py +62 -0
  329. src/platforms/openrouter/__init__.py +6 -0
  330. src/platforms/openrouter/accounts.py +20 -0
  331. src/platforms/openrouter/adapter.py +8 -0
  332. src/platforms/openrouter/core/__init__.py +1 -0
  333. src/platforms/openrouter/core/adaptercore.py +155 -0
  334. src/platforms/openrouter/core/client.py +349 -0
  335. src/platforms/openrouter/core/constants.py +53 -0
  336. src/platforms/openrouter/core/headers.py +24 -0
  337. src/platforms/openrouter/core/payloads.py +36 -0
  338. src/platforms/openrouter/core/sse.py +49 -0
  339. src/platforms/openrouter/util.py +60 -0
  340. src/platforms/perplexity/__init__.py +6 -0
  341. src/platforms/perplexity/accounts.py +10 -0
  342. src/platforms/perplexity/adapter.py +7 -0
  343. src/platforms/perplexity/core/__init__.py +1 -0
  344. src/platforms/perplexity/core/adaptercore.py +135 -0
  345. src/platforms/perplexity/core/client.py +206 -0
  346. src/platforms/perplexity/core/constants.py +13 -0
  347. src/platforms/perplexity/core/headers.py +40 -0
  348. src/platforms/perplexity/core/models.py +59 -0
  349. src/platforms/perplexity/core/payloads.py +159 -0
  350. src/platforms/perplexity/core/sse.py +45 -0
  351. src/platforms/perplexity/util.py +49 -0
  352. src/platforms/qwen/__init__.py +24 -0
  353. src/platforms/qwen/accounts.py +10026 -0
  354. src/platforms/qwen/core/__init__.py +47 -0
  355. src/platforms/qwen/core/adaptercore.py +163 -0
  356. src/platforms/qwen/core/auth.py +387 -0
  357. src/platforms/qwen/core/bxumid.py +10 -0
  358. src/platforms/qwen/core/cdn.py +18 -0
  359. src/platforms/qwen/core/chat_session.py +164 -0
  360. src/platforms/qwen/core/client.py +452 -0
  361. src/platforms/qwen/core/constants.py +129 -0
  362. src/platforms/qwen/core/cookies.py +31 -0
  363. src/platforms/qwen/core/crypto.py +108 -0
  364. src/platforms/qwen/core/endpoints.py +74 -0
  365. src/platforms/qwen/core/errors.py +11 -0
  366. src/platforms/qwen/core/files.py +48 -0
  367. src/platforms/qwen/core/headers.py +108 -0
  368. src/platforms/qwen/core/logs.py +52 -0
  369. src/platforms/qwen/core/media.py +294 -0
  370. src/platforms/qwen/core/mimes.py +79 -0
  371. src/platforms/qwen/core/models.py +55 -0
  372. src/platforms/qwen/core/oss.py +34 -0
  373. src/platforms/qwen/core/password.py +10 -0
  374. src/platforms/qwen/core/payloads.py +211 -0
  375. src/platforms/qwen/core/persistence.py +75 -0
  376. src/platforms/qwen/core/proxy.py +28 -0
  377. src/platforms/qwen/core/runtime.py +141 -0
  378. src/platforms/qwen/core/settings.py +79 -0
  379. src/platforms/qwen/core/shared.py +155 -0
  380. src/platforms/qwen/core/sse.py +147 -0
  381. src/platforms/qwen/core/storage.py +78 -0
  382. src/platforms/qwen/core/stream.py +139 -0
  383. src/platforms/qwen/core/tts.py +135 -0
  384. src/platforms/qwen/core/upload.py +200 -0
  385. src/platforms/qwen/core/video.py +151 -0
  386. src/platforms/qwen/mvp/chat.py +162 -0
  387. src/platforms/qwen/mvp/env.py +27 -0
  388. src/platforms/qwen/util.py +44 -0
  389. src/platforms/sse_common.py +91 -0
  390. src/platforms/yandextranslate/__init__.py +6 -0
  391. src/platforms/yandextranslate/accounts.py +14 -0
  392. src/platforms/yandextranslate/adapter.py +8 -0
  393. src/platforms/yandextranslate/core/__init__.py +1 -0
  394. src/platforms/yandextranslate/core/adaptercore.py +103 -0
  395. src/platforms/yandextranslate/core/client.py +299 -0
  396. src/platforms/yandextranslate/core/constants.py +29 -0
  397. src/platforms/yandextranslate/util.py +53 -0
  398. src/platforms/zen/__init__.py +6 -0
  399. src/platforms/zen/accounts.py +10 -0
  400. src/platforms/zen/adapter.py +8 -0
  401. src/platforms/zen/core/__init__.py +3 -0
  402. src/platforms/zen/core/adaptercore.py +143 -0
  403. src/platforms/zen/core/client.py +342 -0
  404. src/platforms/zen/core/constants.py +33 -0
  405. src/platforms/zen/core/headers.py +20 -0
  406. src/platforms/zen/core/payloads.py +40 -0
  407. src/platforms/zen/core/sse.py +47 -0
  408. src/platforms/zen/util.py +59 -0
  409. src/routes/__init__.py +10 -0
  410. src/routes/anthropic/__init__.py +8 -0
  411. src/routes/anthropic/convert.py +408 -0
  412. src/routes/anthropic/handlers.py +293 -0
  413. src/routes/anthropic/messages.py +46 -0
  414. src/routes/anthropic/streaming.py +414 -0
  415. src/routes/main/__init__.py +8 -0
  416. src/routes/main/function_call.py +43 -0
  417. src/routes/main/health.py +19 -0
  418. src/routes/main/models.py +30 -0
  419. src/routes/main/routes.py +21 -0
  420. src/routes/main/static.py +187 -0
  421. src/routes/openai/__init__.py +8 -0
  422. src/routes/openai/chat.py +567 -0
  423. src/routes/openai/helpers.py +280 -0
  424. src/routes/openai/media.py +548 -0
  425. src/routes/openai/routes.py +265 -0
  426. src/routes/openai/stubs/__init__.py +62 -0
  427. src/routes/openai/stubs/assistants.py +118 -0
  428. src/routes/openai/stubs/files.py +210 -0
  429. src/routes/openai/stubs/jobs.py +158 -0
  430. src/routes/openai/stubs/runs.py +142 -0
  431. src/routes/openai/stubs/stores.py +140 -0
  432. src/routes/openai/stubs/threads.py +157 -0
  433. src/webui/__init__.py +12 -0
  434. src/webui/bootstrap/__init__.py +15 -0
  435. src/webui/bootstrap/app.py +65 -0
  436. src/webui/bootstrap/config_schema.py +58 -0
  437. src/webui/bootstrap/dependencies.py +39 -0
  438. src/webui/bootstrap/routes.py +102 -0
  439. src/webui/bootstrap/server.py +152 -0
  440. src/webui/core/__init__.py +15 -0
  441. src/webui/core/async_tasks.py +131 -0
  442. src/webui/core/auth.py +46 -0
  443. src/webui/core/local_store.py +68 -0
  444. src/webui/core/logs_ws.py +161 -0
  445. src/webui/core/security.py +111 -0
  446. src/webui/middleware/__init__.py +10 -0
  447. src/webui/middleware/auth.py +75 -0
  448. src/webui/middleware/error_handler.py +31 -0
  449. src/webui/middleware/static_nocache.py +21 -0
  450. src/webui/middleware/stats.py +12 -0
  451. src/webui/routers/__init__.py +113 -0
  452. src/webui/routers/admin/__init__.py +33 -0
  453. src/webui/routers/admin/admin.py +225 -0
  454. src/webui/routers/admin/admin_auth.py +70 -0
  455. src/webui/routers/admin/autoupdate.py +309 -0
  456. src/webui/routers/admin/plugins.py +329 -0
  457. src/webui/routers/admin/system.py +30 -0
  458. src/webui/routers/api/__init__.py +43 -0
  459. src/webui/routers/api/files/__init__.py +35 -0
  460. src/webui/routers/api/files/_common.py +128 -0
  461. src/webui/routers/api/files/handlers/__init__.py +26 -0
  462. src/webui/routers/api/files/handlers/meta.py +43 -0
  463. src/webui/routers/api/files/handlers/mutate.py +173 -0
  464. src/webui/routers/api/files/handlers/read.py +215 -0
  465. src/webui/routers/api/files/handlers/search.py +122 -0
  466. src/webui/routers/api/files/handlers/transfer.py +121 -0
  467. src/webui/routers/api/files/handlers/upload.py +126 -0
  468. src/webui/routers/api/files.py +1002 -0
  469. src/webui/routers/api/stats.py +87 -0
  470. src/webui/routers/api/summary.py +35 -0
  471. src/webui/routers/session/__init__.py +21 -0
  472. src/webui/routers/session/chat_media.py +97 -0
  473. src/webui/routers/session/pages.py +115 -0
  474. src/webui/routers/session/terminal.py +98 -0
  475. src/webui/routers/session/terminal_output_bridge.py +46 -0
  476. src/webui/routers/session/terminal_session.py +414 -0
  477. src/webui/routers/session/terminal_ws_handlers.py +220 -0
  478. src/webui/routers/session/websocket.py +36 -0
  479. src/webui/services/__init__.py +8 -0
  480. src/webui/services/export_util.py +12 -0
  481. src/webui/services/request_log.py +374 -0
  482. src/webui/services/stats.py +68 -0
  483. src/webui/services/summaries.py +30 -0
  484. src/webui/services/summary_schema.py +20 -0
@@ -0,0 +1,2344 @@
1
+ Metadata-Version: 2.4
2
+ Name: provider-guide
3
+ Version: 2.2.0a0
4
+ Summary: Unified AI model gateway aggregating 17+ platforms behind a single OpenAI/Anthropic-compatible API
5
+ License: MIT
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: aiohttp>=3.9.0
9
+ Requires-Dist: aiohttp-socks>=0.8.0
10
+ Requires-Dist: beautifulsoup4>=4.12.0
11
+ Requires-Dist: requests>=2.31.0
12
+ Requires-Dist: pycryptodome>=3.20.0
13
+ Requires-Dist: loguru>=0.7.0
14
+ Requires-Dist: tomlkit>=0.12.0
15
+ Requires-Dist: watchdog>=4.0.0
16
+ Requires-Dist: watchfiles>=0.21.0
17
+ Requires-Dist: pydantic>=2.0.0
18
+ Requires-Dist: typing-extensions>=4.7.0
19
+ Requires-Dist: echotools>=2.0.0
20
+ Requires-Dist: provider-sdk>=0.3.0
21
+ Requires-Dist: tomli>=2.0.0; python_version < "3.11"
22
+ Provides-Extra: deepseek
23
+ Requires-Dist: wasmtime>=0.40.0; extra == "deepseek"
24
+ Provides-Extra: cerebras
25
+ Requires-Dist: cerebras-cloud-sdk>=1.0.0; extra == "cerebras"
26
+ Provides-Extra: ssh
27
+ Requires-Dist: paramiko>=3.0.0; extra == "ssh"
28
+ Provides-Extra: async-perf
29
+ Requires-Dist: uvloop>=0.19.0; extra == "async-perf"
30
+ Provides-Extra: all
31
+ Requires-Dist: provider-guide[async-perf,cerebras,deepseek,ssh]; extra == "all"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
34
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
35
+ Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
36
+ Requires-Dist: coverage>=7.0.0; extra == "dev"
37
+
38
+ # Provider-V2
39
+
40
+ > 统一 AI 模型网关,聚合多个 AI 平台,提供 OpenAI 和 Anthropic 兼容 API
41
+
42
+ ## 分支说明
43
+
44
+ | 分支 | 定位 | 说明 |
45
+ |------|------|------|
46
+ | **main** | 稳定发布 | 经过验证的稳定版本,适合生产部署与日常使用 |
47
+ | **dev** | 活跃开发 | 相比 main 包含更多最新更改与功能,但可能不够稳定,建议仅在需要新特性时使用 |
48
+ | **classical** | 历史快照 | 重构前的冻结版本,仅供对比与回退参考,不再接受更新 |
49
+
50
+ > ⚠️ **重要**
51
+ > - **禁止直接提交到 `main` 和 `classical` 分支**
52
+ > - 所有新功能开发和 Bug 修复请基于 `dev` 分支创建功能分支
53
+ > - PR 目标必须为 `dev`,由作者审核后合并
54
+ > - `main` 仅用于稳定发布;`classical` 为只读存档
55
+
56
+ <div align="center">
57
+
58
+ ![Status](https://img.shields.io/badge/status-v2.2.266-blue)
59
+ ![Version](https://img.shields.io/badge/version-v2.2.266-blue)
60
+ ![Python](https://img.shields.io/badge/python-3.8+-blue)
61
+ ![License](https://img.shields.io/badge/license-MIT-green)
62
+ ![Platforms](https://img.shields.io/badge/platforms-24-orange)
63
+
64
+ [![GitHub stars](https://img.shields.io/github/stars/nichengfuben/provider-v2)](https://github.com/nichengfuben/provider-v2/stargazers)
65
+ [![GitHub issues](https://img.shields.io/github/issues/nichengfuben/provider-v2)](https://github.com/nichengfuben/provider-v2/issues)
66
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)](https://github.com/nichengfuben/provider-v2/pulls)
67
+
68
+ </div>
69
+
70
+ ---
71
+
72
+ ## 📋 目录
73
+
74
+ - [项目简介](#-项目简介)
75
+ - [功能特性](#-功能特性)
76
+ - [效果展示](#-效果展示)
77
+ - [快速开始](#-快速开始)
78
+ - [安装指南](#-安装指南)
79
+ - [使用说明](#-使用说明)
80
+ - [项目结构](#-项目结构)
81
+ - [配置说明](#-配置说明)
82
+ - [API 文档](#-api-文档)
83
+ - [测试指南](#-测试指南)
84
+ - [开发指南](#-开发指南)
85
+ - [路线图](#-路线图)
86
+ - [常见问题](#-常见问题)
87
+ - [贡献指南](#-贡献指南)
88
+ - [许可证](#-许可证)
89
+ - [联系方式](#-联系方式)
90
+
91
+ ---
92
+
93
+ ## 🎯 项目简介
94
+
95
+ ### 项目背景
96
+
97
+ 随着 AI 大模型技术的飞速发展,市场上涌现出众多优秀的 AI 平台(如 Qwen、DeepSeek、GLM、Cerebras 等)。每个平台都提供各自独立的 API 接口,格式各异,调用方式不同。开发者在集成多个平台时,面临以下痛点:
98
+
99
+ - **接口不统一**:每个平台的 API 格式各异,需要编写大量适配代码
100
+ - **切换成本高**:更换平台需要修改核心业务逻辑
101
+ - **缺乏容错机制**:单平台故障导致服务不可用
102
+ - **工具调用差异**:Function Calling 格式各平台实现不一致
103
+ - **推理增强不兼容**:thinking、search 等高级功能调用方式各异
104
+
105
+ **Provider-V2 应运而生**——一个高性能的 AI 模型网关,通过统一的 API 接口,让开发者可以轻松访问多个主流 AI 平台,无需关心各平台的差异性实现细节。
106
+
107
+ ### 核心功能
108
+
109
+ - 🔌 **多平台聚合**:统一接入 Qwen、DeepSeek、Cerebras、Ollama、OpenRouter、OpenCode 等 24 个平台适配器
110
+ - 🔄 **API 兼容性**:同时兼容 OpenAI 和 Anthropic API 规范,无缝迁移现有项目
111
+ - ⚡ **并发竞速**:智能选择最快响应的候选项,显著降低延迟
112
+ - 🛠️ **工具调用支持**:完整的 function calling 功能,支持 7 种协议(xml/original/antml/bracket/custom/nous/dsml),中英文模板自定义
113
+ - 🧠 **推理增强**:支持 thinking 和 search 参数增强推理能力
114
+ - 📡 **流式响应**:完整支持 SSE 流式输出,实时展示生成内容
115
+ - 🔐 **鉴权中间件**:可配置的 API Key 鉴权机制,保障服务安全
116
+ - 🔄 **配置热加载**:修改配置文件后自动重载,无需重启服务
117
+ - 🌐 **代理支持**:全局代理配置,自动禁用 SSL 验证
118
+ - 🔁 **平台热重载**:平台代码变更时自动重载,无需重启进程
119
+
120
+ ### 技术栈
121
+
122
+ | 类别 | 技术 | 版本要求 |
123
+ |------|------|----------|
124
+ | Web 框架 | aiohttp | >= 3.9.0 |
125
+ | HTTP 客户端 | aiohttp | >= 3.9.0 |
126
+ | 数据验证 | pydantic | >= 2.0.0 |
127
+ | 平台 SDK | cerebras-cloud-sdk | >= 1.0.0 |
128
+ | WASM 运行时 | wasmtime | >= 0.40.0 |
129
+ | HTML 解析 | beautifulsoup4 | >= 4.12.0 |
130
+ | 加密库 | pycryptodome | >= 3.20.0 |
131
+
132
+ ### 为什么选择 Provider-V2
133
+
134
+ | 特性 | Provider-V2 | 直接接入各平台 |
135
+ |------|:-----------:|:-------------:|
136
+ | 统一 API | ✅ OpenAI/Anthropic 兼容 | ❌ 各平台 API 不同 |
137
+ | 并发竞速 | ✅ 自动选择最快响应 | ❌ 需自行实现 |
138
+ | 多平台切换 | ✅ 配置文件切换 | ❌ 需修改代码 |
139
+ | 工具调用 | ✅ 统一 fncall 接口,XML 模板可定制 | ❌ 格式各异 |
140
+ | 推理增强 | ✅ thinking/search 支持 | ❌ 仅部分支持 |
141
+ | 代理支持 | ✅ 全局代理配置 | ❌ 需逐个配置 |
142
+ | 热加载 | ✅ 配置和平台代码均可热重载 | ❌ 不支持 |
143
+ | 账号池 | ✅ 多账号轮询 | ❌ 需自行管理 |
144
+
145
+ ---
146
+
147
+ ## ✨ 功能特性
148
+
149
+ ### 核心功能
150
+
151
+ | 功能 | 状态 | 说明 |
152
+ |------|:----:|------|
153
+ | **多平台聚合** | ✅ | 24 个平台适配器(`src/platforms/*/core/adaptercore.py`) |
154
+ | **OpenAI 兼容 API** | ✅ | 完整支持 `/v1/chat/completions`、`/v1/models` 等接口 |
155
+ | **Anthropic 兼容 API** | ✅ | 完整支持 `/v1/messages`、`/v1/models` 等接口 |
156
+ | **并发竞速模式** | ✅ | 同时请求多个候选项,选择最快响应 |
157
+ | **工具调用(fncall)** | ✅ | 7 种协议(xml/original/antml/bracket/custom/nous/dsml),中英文模板,支持多行参数 |
158
+ | **推理增强** | ✅ | 支持 thinking 和 search 参数增强 |
159
+ | **流式响应** | ✅ | 完整支持 SSE 流式输出 |
160
+ | **鉴权中间件** | ✅ | 可配置的 API Key 鉴权 |
161
+ | **配置热加载** | ✅ | 修改 `config/main_config.toml` 自动重载,无需重启 |
162
+ | **平台热重载** | ✅ | 修改平台代码自动重载适配器 |
163
+ | **代理支持** | ✅ | 全局代理配置,自动禁用 SSL 验证 |
164
+
165
+ ### 高级功能
166
+
167
+ | 功能 | 状态 | 说明 |
168
+ |------|:----:|------|
169
+ | **智能 Token 回退** | ✅ | 平台返回 usage 为 0 时自动估算 |
170
+ | **模型映射** | ✅ | 支持自定义模型名称映射 |
171
+ | **健康检查** | ✅ | 内置 `/health` 健康检查端点 |
172
+ | **WASM PoW** | ✅ | DeepSeek 平台自动管理 PoW 求解 |
173
+ | **账号池管理** | ✅ | 支持多账号轮询和自动登录 |
174
+ | **TAS 选择器** | ✅ | Thompson Sampling + 冷却机制,智能选择候选项 |
175
+ | **文件管理** | ✅ | 支持文件上传和管理 API(内存存储) |
176
+ | **Assistants API** | ✅ | 基础实现(内存存储) |
177
+
178
+ ### 已支持平台
179
+
180
+ | 平台 | 状态 | 能力 |
181
+ |------|:----:|------|
182
+ | Qwen | ✅ | chat, tools, vision, search |
183
+ | DeepSeek | ✅ | chat, thinking, search |
184
+ | Cerebras | ✅ | chat |
185
+ | Ollama | ✅ | chat, embedding |
186
+ | ChatMoe | ✅ | chat |
187
+ | Cursor | ✅ | chat |
188
+ | CodeBuddy | ✅ | chat |
189
+ | NVIDIA | ✅ | chat |
190
+ | NoobKeys | ✅ | chat |
191
+ | OpenCode | ✅ | chat, tools, vision, native_tools |
192
+ | OpenRouter | ✅ | chat |
193
+ | Chutes | ✅ | chat |
194
+ | N1N / Zen / Caiyuesbk 等 | ✅ | chat 或专项能力 |
195
+ | 翻译 / TTS(DeepL、Google、Edge TTS 等) | ✅ | translate, tts |
196
+ | 更多平台 | 🚧 | 持续开发中(当前共 24 个 adaptercore 适配器) |
197
+
198
+ ---
199
+
200
+ ## 🖼️ 效果展示
201
+
202
+ ### 服务启动
203
+
204
+ ```bash
205
+ $ python main.py
206
+
207
+ 2026-04-05 10:30:15 | INFO | main | Provider-V2 已启动: http://0.0.0.0:1337
208
+ 2026-04-05 10:30:15 | INFO | registry | 发现 24 个平台适配器,开始注册
209
+ 2026-04-05 10:30:16 | INFO | registry | 平台 [qwen] 已注册 (3 模型)
210
+ 2026-04-05 10:30:16 | INFO | registry | 平台 [deepseek] 已注册 (2 模型)
211
+ 2026-04-05 10:30:16 | INFO | registry | 注册完成: ['qwen', 'deepseek', 'openrouter', ...]
212
+ ```
213
+
214
+ ### API 调用示例
215
+
216
+ ```bash
217
+ # OpenAI 兼容调用
218
+ curl -X POST http://localhost:1337/v1/chat/completions \
219
+ -H "Authorization: Bearer sk-xxx" \
220
+ -H "Content-Type: application/json" \
221
+ -d '{
222
+ "model": "qwen-plus",
223
+ "messages": [{"role": "user", "content": "你好"}]
224
+ }'
225
+ ```
226
+
227
+ **响应示例:**
228
+ ```json
229
+ {
230
+ "id": "chatcmpl-xxx",
231
+ "object": "chat.completion",
232
+ "created": 1234567890,
233
+ "model": "qwen-plus",
234
+ "choices": [
235
+ {
236
+ "index": 0,
237
+ "message": {
238
+ "role": "assistant",
239
+ "content": "你好!我是 AI 助手,很高兴为你服务。"
240
+ },
241
+ "finish_reason": "stop"
242
+ }
243
+ ],
244
+ "usage": {
245
+ "prompt_tokens": 10,
246
+ "completion_tokens": 20,
247
+ "total_tokens": 30
248
+ }
249
+ }
250
+ ```
251
+
252
+ ### 内置 WebUI
253
+
254
+ 启动服务后直接访问 `http://localhost:1337/` 即可打开内置 WebUI,包含聊天、终端、文件管理、统计与配置等面板;启用鉴权时需先访问 `/login`。
255
+
256
+ ---
257
+
258
+ ## 🚀 快速开始
259
+
260
+ ### 环境要求
261
+
262
+ | 依赖 | 版本要求 |
263
+ |------|----------|
264
+ | Python | >= 3.8 |
265
+ | pip | >= 21.0 |
266
+ | 操作系统 | Windows / macOS / Linux |
267
+
268
+ ### 30 秒快速体验
269
+
270
+ ```bash
271
+ # 1. 克隆项目
272
+ git clone https://github.com/nichengfuben/provider-v2.git
273
+ cd provider-v2
274
+
275
+ # 2. 安装依赖
276
+ pip install -r requirements.txt
277
+
278
+ # 3. 配置平台账号
279
+ # 编辑 config/main_config.toml(首次启动可由 template/template_config.toml 自动生成)
280
+
281
+ # 4. 启动服务
282
+ python main.py
283
+ ```
284
+
285
+ ### 验证安装
286
+
287
+ 访问以下地址验证服务是否正常运行:
288
+
289
+ | 地址 | 说明 |
290
+ |------|------|
291
+ | http://localhost:1337/health | 健康检查,返回 `{"status": "healthy"}` |
292
+ | http://localhost:1337/ | 内置 WebUI 管理界面 |
293
+ | http://localhost:1337/v1/models | 模型列表接口 |
294
+
295
+ ---
296
+
297
+ ## 📦 安装指南
298
+
299
+ ### 方式一:pip 安装(推荐)
300
+
301
+ ```bash
302
+ # 克隆仓库
303
+ git clone https://github.com/nichengfuben/provider-v2.git
304
+ cd provider-v2
305
+
306
+ # 安装依赖
307
+ pip install -r requirements.txt
308
+ ```
309
+
310
+ ### 方式二:使用虚拟环境
311
+
312
+ ```bash
313
+ # 创建虚拟环境
314
+ python -m venv venv
315
+
316
+ # 激活虚拟环境
317
+ # Windows
318
+ .\venv\Scripts\activate
319
+ # macOS / Linux
320
+ source venv/bin/activate
321
+
322
+ # 安装依赖
323
+ pip install -r requirements.txt
324
+ ```
325
+
326
+ ### 方式三:Docker 安装
327
+
328
+ **Dockerfile:**
329
+ ```dockerfile
330
+ FROM python:3.11-slim
331
+
332
+ WORKDIR /app
333
+ COPY requirements.txt .
334
+ RUN pip install --no-cache-dir -r requirements.txt
335
+
336
+ COPY . .
337
+ EXPOSE 1337
338
+
339
+ CMD ["python", "main.py"]
340
+ ```
341
+
342
+ ```bash
343
+ # 构建镜像
344
+ docker build -t provider-v2:latest .
345
+
346
+ # 运行容器
347
+ docker run -d -p 1337:1337 -v $(pwd)/config:/app/config provider-v2:latest
348
+ ```
349
+
350
+ ### 系统特定说明
351
+
352
+ #### Windows
353
+
354
+ ```powershell
355
+ # 使用虚拟环境
356
+ python -m venv venv
357
+ .\venv\Scripts\activate
358
+ pip install -r requirements.txt
359
+ python main.py
360
+ ```
361
+
362
+ > **注意**:Windows 平台使用 `asyncio` 事件循环,其他平台优先使用 `uvloop`。
363
+
364
+ #### macOS
365
+
366
+ ```bash
367
+ # 安装 Homebrew(如已安装可跳过)
368
+ /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
369
+
370
+ # 安装 Python 3.11
371
+ brew install python@3.11
372
+
373
+ # 创建虚拟环境并安装
374
+ python3.11 -m venv venv
375
+ source venv/bin/activate
376
+ pip install -r requirements.txt
377
+ python main.py
378
+ ```
379
+
380
+ #### Linux (Ubuntu/Debian)
381
+
382
+ ```bash
383
+ # 安装 Python 和依赖
384
+ sudo apt-get update
385
+ sudo apt-get install python3 python3-pip python3-venv
386
+
387
+ # 创建虚拟环境并安装
388
+ python3 -m venv venv
389
+ source venv/bin/activate
390
+ pip install -r requirements.txt
391
+ python main.py
392
+
393
+ # 使用 systemd 管理(可选)
394
+ sudo systemctl enable python3
395
+ ```
396
+
397
+ ---
398
+
399
+ ## 💻 使用说明
400
+
401
+ ### 基础用法
402
+
403
+ #### OpenAI API 调用
404
+
405
+ ```python
406
+ import aiohttp
407
+ import asyncio
408
+
409
+ async def chat_with_openai():
410
+ async with aiohttp.ClientSession() as session:
411
+ headers = {
412
+ "Authorization": "Bearer your-api-key",
413
+ "Content-Type": "application/json"
414
+ }
415
+ data = {
416
+ "model": "qwen-plus",
417
+ "messages": [
418
+ {"role": "user", "content": "你好,请介绍一下你自己"}
419
+ ],
420
+ "stream": False
421
+ }
422
+
423
+ async with session.post(
424
+ "http://localhost:1337/v1/chat/completions",
425
+ headers=headers,
426
+ json=data
427
+ ) as response:
428
+ result = await response.json()
429
+ print(result["choices"][0]["message"]["content"])
430
+
431
+ asyncio.run(chat_with_openai())
432
+ ```
433
+
434
+ #### Anthropic API 调用
435
+
436
+ ```python
437
+ import aiohttp
438
+ import asyncio
439
+
440
+ async def chat_with_anthropic():
441
+ async with aiohttp.ClientSession() as session:
442
+ headers = {
443
+ "x-api-key": "your-api-key",
444
+ "Content-Type": "application/json",
445
+ "anthropic-version": "2023-06-01"
446
+ }
447
+ data = {
448
+ "model": "claude-3-sonnet-20240229",
449
+ "max_tokens": 1024,
450
+ "messages": [
451
+ {"role": "user", "content": "你好,请介绍一下你自己"}
452
+ ]
453
+ }
454
+
455
+ async with session.post(
456
+ "http://localhost:1337/v1/messages",
457
+ headers=headers,
458
+ json=data
459
+ ) as response:
460
+ result = await response.json()
461
+ print(result["content"][0]["text"])
462
+
463
+ asyncio.run(chat_with_anthropic())
464
+ ```
465
+
466
+ ### 高级用法
467
+
468
+ #### 工具调用(Function Calling)
469
+
470
+ Provider-V2 使用 XML 标签格式将工具描述注入到提示中。模型需要按照以下格式返回函数调用:
471
+
472
+ ```xml
473
+ <function=函数名>
474
+ <参数名1>
475
+ 参数值
476
+ </参数名1>
477
+ <参数名2>
478
+ 参数值
479
+ </参数名2>
480
+ </function>
481
+ ```
482
+
483
+ **重要格式要求:**
484
+ - XML 标签名必须与参数名完全一致
485
+ - 标签和值之间**必须**有换行符
486
+ - 必须包含所有必需参数
487
+ - 支持多行参数(如代码块)
488
+
489
+ **示例代码:**
490
+
491
+ ```python
492
+ import aiohttp
493
+ import asyncio
494
+
495
+ async def function_calling_example():
496
+ async with aiohttp.ClientSession() as session:
497
+ headers = {
498
+ "Authorization": "Bearer your-api-key",
499
+ "Content-Type": "application/json"
500
+ }
501
+ data = {
502
+ "model": "qwen-plus",
503
+ "messages": [
504
+ {"role": "user", "content": "北京今天的天气怎么样?"}
505
+ ],
506
+ "tools": [
507
+ {
508
+ "type": "function",
509
+ "function": {
510
+ "name": "get_weather",
511
+ "description": "获取指定城市的天气",
512
+ "parameters": {
513
+ "type": "object",
514
+ "properties": {
515
+ "city": {
516
+ "type": "string",
517
+ "description": "城市名称"
518
+ }
519
+ },
520
+ "required": ["city"]
521
+ }
522
+ }
523
+ }
524
+ ]
525
+ }
526
+
527
+ async with session.post(
528
+ "http://localhost:1337/v1/chat/completions",
529
+ headers=headers,
530
+ json=data
531
+ ) as response:
532
+ result = await response.json()
533
+ tool_call = result["choices"][0]["message"]["tool_calls"][0]
534
+ print(f"模型调用了工具: {tool_call['function']['name']}")
535
+ print(f"参数: {tool_call['function']['arguments']}")
536
+
537
+ asyncio.run(function_calling_example())
538
+ ```
539
+
540
+ **自定义模板:**
541
+
542
+ 可以在 `config/main_config.toml` 中自定义中英文提示模板:
543
+
544
+ ```toml
545
+ [fncall.templates]
546
+ en = """
547
+ You are a helpful AI assistant that can interact with tools to solve tasks.
548
+
549
+ <chat_history>
550
+ {chat_history}
551
+ </chat_history>
552
+
553
+ # Available Tools
554
+
555
+ <tools>
556
+ {tool_descs}
557
+ </tools>
558
+
559
+ To call a function, wrap each parameter value in XML tags named after the parameter:
560
+
561
+ <function=function_name>
562
+ <first_parameter>
563
+ value
564
+ </first_parameter>
565
+ <second_parameter>
566
+ value
567
+ </second_parameter>
568
+ </function>
569
+
570
+ <IMPORTANT>
571
+ - The XML tag name MUST exactly match the parameter name defined above.
572
+ - There MUST be a line break between the opening tag and the value, and between the value and the closing tag.
573
+ - Required parameters MUST be included.
574
+ - Only provide reasoning BEFORE the function call, never after.
575
+ - If no function call is needed, answer normally without mentioning tools.
576
+ </IMPORTANT>"""
577
+
578
+ zh = """
579
+ 你是一个乐于助人的AI助手,可以使用工具来解决任务。
580
+
581
+ <chat_history>
582
+ {chat_history}
583
+ </chat_history>
584
+
585
+ # 可用工具
586
+
587
+ <tools>
588
+ {tool_descs}
589
+ </tools>
590
+
591
+ 调用函数时,将每个参数值用与参数名同名的XML标签包裹:
592
+
593
+ <function=函数名>
594
+ <第一个参数名>
595
+
596
+ </第一个参数名>
597
+ <第二个参数名>
598
+
599
+ </第二个参数名>
600
+ </function>
601
+
602
+ <IMPORTANT>
603
+ - XML标签名必须与参数名完全一致,不要添加前缀。
604
+ - 标签和值之间必须有换行符。
605
+ - 必须包含所有必需参数。
606
+ - 只能在函数调用之前提供推理说明,不能在之后。
607
+ - 如果不需要调用函数,请正常回答问题。
608
+ </IMPORTANT>"""
609
+ ```
610
+
611
+ #### 流式响应
612
+
613
+ ```python
614
+ import aiohttp
615
+ import asyncio
616
+ import json
617
+
618
+ async def streaming_example():
619
+ async with aiohttp.ClientSession() as session:
620
+ headers = {
621
+ "Authorization": "Bearer your-api-key",
622
+ "Content-Type": "application/json"
623
+ }
624
+ data = {
625
+ "model": "qwen-plus",
626
+ "messages": [
627
+ {"role": "user", "content": "请写一首关于春天的诗"}
628
+ ],
629
+ "stream": True
630
+ }
631
+
632
+ async with session.post(
633
+ "http://localhost:1337/v1/chat/completions",
634
+ headers=headers,
635
+ json=data
636
+ ) as response:
637
+ async for line in response.content:
638
+ line = line.decode('utf-8').strip()
639
+ if line.startswith('data: '):
640
+ data_str = line[6:]
641
+ if data_str == '[DONE]':
642
+ break
643
+ try:
644
+ chunk = json.loads(data_str)
645
+ if chunk['choices'][0]['delta'].get('content'):
646
+ print(chunk['choices'][0]['delta']['content'], end='', flush=True)
647
+ except json.JSONDecodeError:
648
+ continue
649
+
650
+ asyncio.run(streaming_example())
651
+ ```
652
+
653
+ #### Thinking 推理增强
654
+
655
+ ```python
656
+ import aiohttp
657
+ import asyncio
658
+
659
+ async def thinking_example():
660
+ async with aiohttp.ClientSession() as session:
661
+ headers = {
662
+ "Authorization": "Bearer your-api-key",
663
+ "Content-Type": "application/json"
664
+ }
665
+ data = {
666
+ "model": "deepseek-reasoner",
667
+ "messages": [
668
+ {"role": "user", "content": "解释量子纠缠现象"}
669
+ ],
670
+ "extra_body": {
671
+ "thinking": True # 启用 thinking 模式
672
+ }
673
+ }
674
+
675
+ async with session.post(
676
+ "http://localhost:1337/v1/chat/completions",
677
+ headers=headers,
678
+ json=data
679
+ ) as response:
680
+ result = await response.json()
681
+ if result.get("reasoning_content"):
682
+ print("推理过程:", result["reasoning_content"])
683
+ print("最终答案:", result["choices"][0]["message"]["content"])
684
+
685
+ asyncio.run(thinking_example())
686
+ ```
687
+
688
+ ### 请求参数说明
689
+
690
+ | 参数名 | 类型 | 默认值 | 说明 |
691
+ |--------|------|--------|------|
692
+ | `model` | string | - | 模型名称,如 `qwen-plus`、`deepseek-chat` |
693
+ | `messages` | array | - | 消息列表,格式为 `[{role, content}]` |
694
+ | `stream` | boolean | `false` | 是否启用流式输出 |
695
+ | `max_tokens` | integer | - | 最大生成 Token 数 |
696
+ | `temperature` | float | `1.0` | 温度参数,控制随机性(0-2) |
697
+ | `top_p` | float | `1.0` | 核采样参数(0-1) |
698
+ | `tools` | array | - | 工具定义列表(fncall) |
699
+ | `extra_body` | object | - | 额外参数,如 `thinking`、`search` |
700
+
701
+ ---
702
+
703
+ ## 🏗️ 项目结构
704
+
705
+ ```
706
+ provider-v2/
707
+ ├── 📁 config/ # 运行时配置(main_config.toml,通常不提交)
708
+ ├── 📁 template/ # 配置模板 template_config.toml
709
+ ├── 📁 persist/ # 持久化数据(账号状态、WebUI、缓存等)
710
+ ├── 📁 src/
711
+ │ ├── 📁 bootstrap/ # 单一组合根(app_factory、lifecycle、webui_bindings)
712
+ │ ├── 📁 core/
713
+ │ │ ├── 📁 auth/ # 会话 cookie 常量(core 不依赖 webui)
714
+ │ │ ├── 📁 config/ # 配置管理(sections/manager)
715
+ │ │ ├── 📁 dispatch/ # 网关分发(gateway/executors/fncall_context)
716
+ │ │ ├── 📁 errors/ # 分层异常 + classify_http_error
717
+ │ │ ├── 📁 fncall/ # 工具调用(protocols/parsers/prompt)
718
+ │ │ ├── 📁 observability/ # 观测 Protocol + AppKey(stats/logs/terminal)
719
+ │ │ ├── 📁 server/ # HTTP 服务(app/runner/worker/middleware)
720
+ │ │ │ └── 📁 infra/ # connector、reload、terminal_sessions
721
+ │ │ └── 📁 utils/
722
+ │ │ └── 📁 compat/ # models_cache、tools、scriptgen 等兼容层
723
+ │ ├── 📁 platforms/ # 平台适配器(*/core/adaptercore.py)
724
+ │ ├── 📁 routes/
725
+ │ │ ├── 📁 main/ # health、models、function_call
726
+ │ │ ├── 📁 openai/ # chat、media、stubs
727
+ │ │ └── 📁 anthropic/ # messages、streaming、convert
728
+ │ └── 📁 webui/
729
+ │ ├── 📁 bootstrap/ # app、routes、server
730
+ │ ├── 📁 routers/ # admin、api、session
731
+ │ ├── 📁 static/ # 内置 WebUI 前端资源
732
+ │ └── 📁 services/ # stats、request_log、summaries
733
+ ├── 📄 main.py # Runner-Worker 双进程入口(<60 行,委托 runner/worker)
734
+ ├── 📄 pyproject.toml # 依赖与打包 SSOT
735
+ ├── 📄 requirements.txt # 核心依赖(与 pyproject 同步)
736
+ └── 📄 README.md
737
+ ```
738
+
739
+ ### 核心目录说明
740
+
741
+ | 目录 | 说明 |
742
+ |------|------|
743
+ | `src/core/` | 核心网关逻辑,包括配置管理、请求分发、TAS 候选项选择、文件监视热重载 |
744
+ | `src/platforms/` | 各平台适配器实现,自动发现机制,支持热重载 |
745
+ | `src/routes/` | OpenAI / Anthropic 兼容 HTTP 路由 |
746
+ | `src/core/server/infra/` | 连接器、IPC、热重载、文件监视、终端会话存储 |
747
+ | `src/webui/` | 内置 WebUI(bootstrap 应用、session/api/admin 路由、静态资源) |
748
+
749
+ ---
750
+
751
+ ## ⚙️ 配置说明
752
+
753
+ ### 配置文件位置
754
+
755
+ Provider-V2 默认从 `config/main_config.toml` 加载配置(`config/` 目录通常不纳入版本库)。
756
+
757
+ - 若文件不存在,会基于 `template/template_config.toml` 自动生成并提示补全后重启
758
+ - 可通过 ConfigCenter / 环境变量等方式指定其他配置文件路径
759
+
760
+ ### 完整配置示例
761
+
762
+ ```toml
763
+ # Provider-V2 配置文件
764
+
765
+ # ============================================================
766
+ # 服务器配置
767
+ # ============================================================
768
+ [server]
769
+ host = "0.0.0.0" # 监听地址
770
+ port = 1337 # 监听端口
771
+ debug = false # 调试模式
772
+
773
+ [server.anthropic]
774
+ api_version = "2023-06-01"
775
+
776
+ [server.anthropic.model_mapping]
777
+ # 可选:自定义模型映射
778
+ # "claude-3-sonnet" = "qwen-plus"
779
+
780
+ # ============================================================
781
+ # 鉴权配置
782
+ # ============================================================
783
+ [auth]
784
+ enabled = false # 是否启用鉴权
785
+ keys = [
786
+ "sk-your-api-key-1",
787
+ "sk-your-api-key-2"
788
+ ]
789
+
790
+ # ============================================================
791
+ # 网关配置
792
+ # ============================================================
793
+ [gateway]
794
+ concurrent_enabled = true # 是否启用并发竞速
795
+ concurrent_count = 3 # 并发请求数
796
+ min_tokens = 10 # 选择胜者的最小 Token 阈值
797
+
798
+ # ============================================================
799
+ # 代理配置
800
+ # ============================================================
801
+ [proxy]
802
+ proxy_server = "http://127.0.0.1:40000"
803
+ proxy_enabled = false
804
+
805
+ # ============================================================
806
+ # 工具调用配置
807
+ # ============================================================
808
+ [fncall]
809
+ call_start_tag = "<function="
810
+ call_end_tag = "</function>"
811
+ tools_start_tag = "<tools>"
812
+ tools_end_tag = "</tools>"
813
+
814
+ [fncall.templates]
815
+ en = """
816
+ You are a helpful AI assistant that can interact with tools to solve tasks.
817
+
818
+ <chat_history>
819
+ {chat_history}
820
+ </chat_history>
821
+
822
+ # Available Tools
823
+
824
+ <tools>
825
+ {tool_descs}
826
+ </tools>
827
+
828
+ To call a function, wrap each parameter value in XML tags named after the parameter:
829
+
830
+ <function=function_name>
831
+ <first_parameter>
832
+ value
833
+ </first_parameter>
834
+ <second_parameter>
835
+ value
836
+ </second_parameter>
837
+ </function>
838
+
839
+ <IMPORTANT>
840
+ - The XML tag name MUST exactly match the parameter name defined above.
841
+ - There MUST be a line break between the opening tag and the value, and between the value and the closing tag.
842
+ - Required parameters MUST be included.
843
+ - Only provide reasoning BEFORE the function call, never after.
844
+ - If no function call is needed, answer normally without mentioning tools.
845
+ </IMPORTANT>"""
846
+
847
+ zh = """
848
+ 你是一个乐于助人的AI助手,可以使用工具来解决任务。
849
+
850
+ <chat_history>
851
+ {chat_history}
852
+ </chat_history>
853
+
854
+ # 可用工具
855
+
856
+ <tools>
857
+ {tool_descs}
858
+ </tools>
859
+
860
+ 调用函数时,将每个参数值用与参数名同名的XML标签包裹:
861
+
862
+ <function=函数名>
863
+ <第一个参数名>
864
+
865
+ </第一个参数名>
866
+ <第二个参数名>
867
+
868
+ </第二个参数名>
869
+ </function>
870
+
871
+ <IMPORTANT>
872
+ - XML标签名必须与参数名完全一致,不要添加前缀。
873
+ - 标签和值之间必须有换行符。
874
+ - 必须包含所有必需参数。
875
+ - 只能在函数调用之前提供推理说明,不能在之后。
876
+ - 如果不需要调用函数,请正常回答问题。
877
+ </IMPORTANT>"""
878
+ ```
879
+
880
+ ### 配置项详解
881
+
882
+ #### 服务器配置
883
+
884
+ ```toml
885
+ [server]
886
+ host = "0.0.0.0" # 监听地址,0.0.0.0 表示监听所有网卡
887
+ port = 1337 # 监听端口
888
+ debug = false # 调试模式,开启后输出更多日志
889
+ ```
890
+
891
+ #### Anthropic 兼容层配置
892
+
893
+ ```toml
894
+ [server.anthropic]
895
+ api_version = "2023-06-01" # Anthropic API 版本
896
+
897
+ [server.anthropic.model_mapping]
898
+ # 模型名称映射,将 Anthropic 模型名映射到实际平台模型
899
+ # "claude-3-sonnet" = "qwen-plus"
900
+ ```
901
+
902
+ #### 鉴权配置
903
+
904
+ ```toml
905
+ [auth]
906
+ enabled = true # 是否启用鉴权
907
+ keys = ["key1", "key2", "key3"] # 允许的 API Key 列表
908
+ ```
909
+
910
+ **鉴权方式**:
911
+ - 优先从 `Authorization: Bearer {key}` 头提取
912
+ - 失败后从 `x-api-key` 头提取
913
+ - 以下路径无条件跳过 API 鉴权:`/login`、`/logout`、`/health`、`/static/*`
914
+ - 启用鉴权后,`/`、`/v1/*`(含 `/v1/models`)需有效 API Key 或 WebUI 会话 Cookie
915
+
916
+ #### 网关配置
917
+
918
+ ```toml
919
+ [gateway]
920
+ concurrent_enabled = true # 是否启用并发竞速
921
+ concurrent_count = 3 # 并发请求数(同时请求多少个候选项)
922
+ min_tokens = 10 # 选择胜者的最小 Token 阈值
923
+ ```
924
+
925
+ **并发竞速机制**:
926
+ - 同时向 `concurrent_count` 个候选项发送请求
927
+ - 当任一候选项累积 Token 数 ≥ `min_tokens` 时立即选择该候选项
928
+ - 适用于低延迟场景,会消耗更多配额
929
+
930
+ #### 代理配置
931
+
932
+ ```toml
933
+ [proxy]
934
+ proxy_server = "http://proxy.example.com:8080" # 代理服务器地址
935
+ proxy_enabled = true # 是否启用代理
936
+ proxy_list_type = "whitelist" # 列表类型:whitelist(白名单)或 blacklist(黑名单)
937
+ proxy_urls = [ # URL 正则匹配列表
938
+ ".*qwen\\.ai.*",
939
+ ".*deepseek\\.com.*"
940
+ ]
941
+ ```
942
+
943
+ **说明:**
944
+ - `whitelist` 模式:仅匹配 `proxy_urls` 正则的请求走代理,其余直连
945
+ - `blacklist` 模式:匹配 `proxy_urls` 正则的请求直连,其余走代理
946
+ - 直接 IP 地址始终 bypass 代理
947
+ - `proxy_urls` 为空时:whitelist 模式全部直连,blacklist 模式全部走代理
948
+
949
+ > **注意**:代理在 `src.core.proxy` 模块导入时即生效,修改后需重启服务。
950
+
951
+ #### 工具调用配置
952
+
953
+ ```toml
954
+ [fncall]
955
+ call_start_tag = "<function=" # 函数调用开始标签
956
+ call_end_tag = "</function>" # 函数调用结束标签
957
+ tools_start_tag = "<tools>" # 工具列表开始标签
958
+ tools_end_tag = "</tools>" # 工具列表结束标签
959
+
960
+ [fncall.templates]
961
+ en = "..." # 英文模板,支持 {chat_history} 和 {tool_descs} 占位符
962
+ zh = "..." # 中文模板
963
+ ```
964
+
965
+ ### 配置热加载
966
+
967
+ - 修改 `config/main_config.toml` 后自动重载,无需重启服务
968
+ - 配置监听器每 2 秒检查一次变更
969
+ - 代理配置修改后需要重启服务
970
+
971
+ ---
972
+
973
+ ## 🔌 API 文档
974
+
975
+ ### 接口概览
976
+
977
+ | 方法 | 端点 | 说明 |
978
+ |------|------|------|
979
+ | GET | `/health` | 健康检查 |
980
+ | GET | `/v1/models` | 获取模型列表 |
981
+ | POST | `/v1/chat/completions` | OpenAI 聊天补全 |
982
+ | POST | `/v1/messages` | Anthropic 聊天接口 |
983
+ | POST | `/v1/completions` | Legacy 文本补全 |
984
+ | POST | `/v1/embeddings` | 嵌入向量生成 |
985
+ | POST | `/v1/images/generations` | 图片生成 |
986
+ | POST | `/v1/audio/speech` | 语音合成 |
987
+ | POST | `/v1/audio/transcriptions` | 语音转录 |
988
+ | POST | `/v1/moderations` | 内容审核 |
989
+
990
+ ### 接口详情
991
+
992
+ #### 健康检查
993
+
994
+ ```http
995
+ GET /health
996
+ ```
997
+
998
+ **响应示例:**
999
+ ```json
1000
+ {
1001
+ "status": "healthy",
1002
+ "timestamp": 1234567890
1003
+ }
1004
+ ```
1005
+
1006
+ #### 获取模型列表
1007
+
1008
+ ```http
1009
+ GET /v1/models
1010
+ Authorization: Bearer <api-key>
1011
+ ```
1012
+
1013
+ **响应示例:**
1014
+ ```json
1015
+ {
1016
+ "object": "list",
1017
+ "data": [
1018
+ {
1019
+ "id": "qwen-plus",
1020
+ "object": "model",
1021
+ "created": 1234567890,
1022
+ "owned_by": "qwen",
1023
+ "capabilities": {
1024
+ "chat": true,
1025
+ "tools": true,
1026
+ "vision": true,
1027
+ "search": true
1028
+ }
1029
+ },
1030
+ {
1031
+ "id": "deepseek-chat",
1032
+ "object": "model",
1033
+ "created": 1234567890,
1034
+ "owned_by": "deepseek",
1035
+ "capabilities": {
1036
+ "chat": true,
1037
+ "thinking": true,
1038
+ "search": true
1039
+ }
1040
+ }
1041
+ ]
1042
+ }
1043
+ ```
1044
+
1045
+ #### OpenAI 聊天补全
1046
+
1047
+ ```http
1048
+ POST /v1/chat/completions
1049
+ Authorization: Bearer <api-key>
1050
+ Content-Type: application/json
1051
+ ```
1052
+
1053
+ **请求示例:**
1054
+ ```json
1055
+ {
1056
+ "model": "qwen-plus",
1057
+ "messages": [
1058
+ {"role": "system", "content": "你是一个乐于助人的助手"},
1059
+ {"role": "user", "content": "你好"}
1060
+ ],
1061
+ "stream": false,
1062
+ "max_tokens": 1024,
1063
+ "temperature": 0.7
1064
+ }
1065
+ ```
1066
+
1067
+ **响应示例:**
1068
+ ```json
1069
+ {
1070
+ "id": "chatcmpl-xxx",
1071
+ "object": "chat.completion",
1072
+ "created": 1234567890,
1073
+ "model": "qwen-plus",
1074
+ "choices": [
1075
+ {
1076
+ "index": 0,
1077
+ "message": {
1078
+ "role": "assistant",
1079
+ "content": "你好!我是 AI 助手,很高兴为你服务。"
1080
+ },
1081
+ "finish_reason": "stop",
1082
+ "logprobs": null
1083
+ }
1084
+ ],
1085
+ "usage": {
1086
+ "prompt_tokens": 10,
1087
+ "completion_tokens": 20,
1088
+ "total_tokens": 30
1089
+ },
1090
+ "system_fingerprint": null
1091
+ }
1092
+ ```
1093
+
1094
+ #### Anthropic 聊天接口
1095
+
1096
+ ```http
1097
+ POST /v1/messages
1098
+ x-api-key: <api-key>
1099
+ anthropic-version: 2023-06-01
1100
+ Content-Type: application/json
1101
+ ```
1102
+
1103
+ **请求示例:**
1104
+ ```json
1105
+ {
1106
+ "model": "claude-3-sonnet-20240229",
1107
+ "max_tokens": 1024,
1108
+ "messages": [
1109
+ {"role": "user", "content": "你好"}
1110
+ ],
1111
+ "stream": false
1112
+ }
1113
+ ```
1114
+
1115
+ **响应示例:**
1116
+ ```json
1117
+ {
1118
+ "id": "msg-xxx",
1119
+ "type": "message",
1120
+ "role": "assistant",
1121
+ "content": [
1122
+ {
1123
+ "type": "text",
1124
+ "text": "你好!我是 Claude,很高兴为你服务。"
1125
+ }
1126
+ ],
1127
+ "stop_reason": "end_turn",
1128
+ "stop_sequence": null,
1129
+ "usage": {
1130
+ "input_tokens": 10,
1131
+ "output_tokens": 20
1132
+ }
1133
+ }
1134
+ ```
1135
+
1136
+ ### 错误码说明
1137
+
1138
+ | HTTP 状态码 | 错误类型 | 说明 |
1139
+ |------------|---------|------|
1140
+ | 400 | `invalid_request_error` | 请求参数错误 |
1141
+ | 401 | `authentication_error` | API Key 无效或缺失 |
1142
+ | 404 | `not_found_error` | 资源不存在 |
1143
+ | 429 | `rate_limit_error` | 请求频率超限 |
1144
+ | 500 | `server_error` | 服务器内部错误 |
1145
+ | 503 | `overloaded_error` | 服务过载,无可用候选项 |
1146
+
1147
+ ### 完整 API 文档
1148
+
1149
+ 启动服务后访问 `http://localhost:1337/` 通过内置 WebUI 的「模型」与「配置」选项卡查看接口清单与能力矩阵。
1150
+
1151
+ ---
1152
+
1153
+ ## 🧪 测试指南
1154
+
1155
+ ### 运行测试
1156
+
1157
+ ```bash
1158
+ # 运行所有测试
1159
+ pytest tests/
1160
+
1161
+ # 运行特定测试文件
1162
+ pytest tests/test_gateway.py
1163
+
1164
+ # 运行特定测试函数
1165
+ pytest tests/test_gateway.py::test_dispatch
1166
+
1167
+ # 生成覆盖率报告
1168
+ pytest --cov=src tests/
1169
+ ```
1170
+
1171
+ ### 平台独立测试
1172
+
1173
+ 每个平台都提供独立的 `test.py` 测试脚本:
1174
+
1175
+ ```bash
1176
+ # 测试 Qwen 平台
1177
+ cd src/platforms/qwen
1178
+ python test.py
1179
+
1180
+ # 测试 DeepSeek 平台
1181
+ cd src/platforms/deepseek
1182
+ python test.py
1183
+ ```
1184
+
1185
+ ### 测试覆盖率要求
1186
+
1187
+ | 类型 | 最低覆盖率 |
1188
+ |------|:---------:|
1189
+ | 语句覆盖 | 70% |
1190
+ | 分支覆盖 | 60% |
1191
+ | 函数覆盖 | 80% |
1192
+
1193
+ ---
1194
+
1195
+ ## 📝 开发指南
1196
+
1197
+ ### 开发状态
1198
+
1199
+ > ⚠️ **项目状态:Alpha 2.1 - 活跃开发中**
1200
+ >
1201
+ > 本项目正处于**活跃开发阶段**,代码和文档持续更新中。
1202
+ >
1203
+ > **贡献者请注意**:
1204
+ > - 请将 Pull Request 提交到 **`dev` 分支**
1205
+ > - `main` 分支仅用于稳定版本发布
1206
+ > - 新功能开发请基于 `dev` 分支创建功能分支
1207
+
1208
+ ### 开发环境搭建
1209
+
1210
+ ```bash
1211
+ # 1. Fork 并克隆项目
1212
+ git clone https://github.com/你的用户名/provider-v2.git
1213
+ cd provider-v2
1214
+
1215
+ # 2. 添加上游仓库
1216
+ git remote add upstream https://github.com/nichengfuben/provider-v2.git
1217
+
1218
+ # 3. 切换到 dev 分支
1219
+ git checkout dev
1220
+
1221
+ # 4. 创建功能分支
1222
+ git checkout -b feature/你的功能名
1223
+
1224
+ # 5. 安装开发依赖
1225
+ pip install -r requirements.txt
1226
+ pip install pytest pytest-cov black isort mypy
1227
+
1228
+ # 6. 运行测试确保环境正常
1229
+ pytest tests/
1230
+ ```
1231
+
1232
+ ### 代码规范
1233
+
1234
+ - 遵循 PEP 8 代码风格
1235
+ - 使用 `black` 进行格式化:`black src/`
1236
+ - 使用 `isort` 进行导入排序:`isort src/`
1237
+ - 使用 `mypy` 进行类型检查:`mypy src/`
1238
+ - 添加类型注解
1239
+ - 编写中文注释和文档字符串
1240
+ - **不使用 f-string**,使用 `str.format()` 保持 Python 3.8 兼容
1241
+
1242
+ ### Git 提交规范
1243
+
1244
+ 采用 [Conventional Commits](https://conventionalcommits.org/) 规范:
1245
+
1246
+ ```
1247
+ <type>(<scope>): <description>
1248
+
1249
+ [optional body]
1250
+
1251
+ [optional footer]
1252
+ ```
1253
+
1254
+ **Type 类型:**
1255
+ | 类型 | 说明 |
1256
+ |------|------|
1257
+ | `feat` | 新功能 |
1258
+ | `fix` | Bug 修复 |
1259
+ | `docs` | 文档更新 |
1260
+ | `style` | 代码格式调整 |
1261
+ | `refactor` | 重构 |
1262
+ | `test` | 测试相关 |
1263
+ | `chore` | 构建/工具相关 |
1264
+
1265
+ **示例:**
1266
+ ```
1267
+ feat(qwen): 添加 qwen3.5-122b 模型支持
1268
+
1269
+ - 更新 MODELS 列表
1270
+ - 调整 max_tokens 限制
1271
+
1272
+ fix(gateway): 修复并发竞速时 token 计数错误
1273
+ ```
1274
+
1275
+ ### 分支命名规范
1276
+
1277
+ | 分支类型 | 命名格式 | 示例 |
1278
+ |----------|----------|------|
1279
+ | 功能分支 | `feature/xxx` | `feature/qwen-new-model` |
1280
+ | 修复分支 | `fix/xxx` | `fix/gateway-timeout` |
1281
+ | 文档分支 | `docs/xxx` | `docs/api-update` |
1282
+ | 重构分支 | `refactor/xxx` | `refactor/selector` |
1283
+
1284
+ ### 添加新平台适配器
1285
+
1286
+ 参考 [平台适配器开发指南](docs-src/src/platforms/guide.md) 了解如何添加新平台支持。
1287
+
1288
+ ---
1289
+
1290
+ ## 🗺️ 路线图
1291
+
1292
+ ### 当前版本:v2.2.266
1293
+
1294
+ ✅ 已完成(v2.2.234):
1295
+ - fix(webui): rewrite log panel with structured rendering, module colors, mobile layout, and smart auto-scroll
1296
+
1297
+ ✅ 已完成(v2.2.232):
1298
+ - fix(terminal): add legacy base64 data URL to server-side file migration
1299
+ - test(core): fix ModelsCache test to match ListCache init behavior (fallback only, explicit load required)
1300
+ - refactor(dispatch): update Registry tests to match new plugin-based API
1301
+
1302
+ ✅ 已完成(v2.2.231):
1303
+ - refactor(webui): migrate request logs from JSON to SQLite persistence
1304
+ - fix(webui): terminal tab add button always visible regardless of tab count
1305
+ - fix(webui): close-all button repositioned to avoid overlapping the add button
1306
+ - feat(terminal): add custom background image support with opacity control and pseudo-element rendering
1307
+ - fix(terminal): resolve panel positioning issue when switching tabs (remove display:flex !important)
1308
+ - fix(terminal): enhance DEC response stripping for cross-message handling
1309
+
1310
+ ✅ 已完成(v2.2.221):
1311
+ - refactor(webui): replace native prompt/confirm dialogs with custom input/confirm components for terminal rename, restart confirmation, and new folder creation
1312
+
1313
+ ✅ 已完成(v2.2.220):
1314
+ - fix(terminal): resolve terminal height overflow with precise flex layout and viewport-constrained sizing
1315
+ - fix(qwen): add `supported_models` property to QwenAdapter for runtime model count reporting
1316
+ - feat(terminal): improve session recovery with read-only reattach and offline output consumption
1317
+ - feat(terminal): add background mode toggle button for terminal UI
1318
+ - refactor(terminal): optimize terminal container layout with flex-based sizing
1319
+
1320
+ ✅ 已完成(v2.2.213):
1321
+ - feat(terminal): migrate terminal system to T3 Code architecture
1322
+ - Added shell fallback chains for cross-platform compatibility (Windows: pwsh.exe → PowerShell → cmd.exe; POSIX: $SHELL → zsh → bash → sh)
1323
+ - Added output sanitization for clean history management
1324
+ - Added subprocess monitoring with metadata events
1325
+ - Added history management with 5000-line limit
1326
+ - Added clear history and restart terminal operations
1327
+ - Added subprocess indicator showing running command name in tab title
1328
+ - Added terminal configuration options (max_history_lines, subprocess_monitor_interval, enable_subprocess_monitoring)
1329
+ - Updated frontend with Clear History and Restart buttons in context menu
1330
+ - Updated WebSocket protocol with clear, restart, and metadata messages
1331
+ - All tests passing, documentation updated
1332
+
1333
+ ✅ 已完成(v2.2.209):
1334
+ - fix(routes): accept enable_thinking from extra_body for thinking mode
1335
+ - Updated thinking parameter handling in src/routes/openai_chat.py to accept both "thinking" and "enable_thinking" from extra_body
1336
+
1337
+ ✅ 已完成(v2.2.207):
1338
+ - feat(opencode): enable proxy fetch and update RECORD.md
1339
+ - Changed PROXY_FETCH_ENABLED from False to True in opencode constants
1340
+ - Updated RECORD.md with brotli entry from previous session
1341
+
1342
+ ✅ 已完成(v2.2.206):
1343
+ - fix(deps): add brotli for brotli decompression support
1344
+ - aiohttp requires brotli to decode brotli-compressed responses from opencode and zen APIs
1345
+
1346
+ ✅ 已完成(v2.2.205):
1347
+
1348
+ - refactor(qwen): extract crypto/file_objects/io_utils/headers modules from shared.py
1349
+ - refactor(fncall): restructure parsers, protocols, prompt, and shared subpackages
1350
+ - refactor(core): extract utils subpackage (files/ids/io_utils/retry/scheduler)
1351
+ - refactor(config): extract base config module
1352
+ - refactor(dispatch): extract dispatch __init__ facade
1353
+ - refactor(webui): extract request_log service module
1354
+
1355
+ ✅ 已完成(v2.2.204):
1356
+ - refactor(errors): add to_dict() serialization to all ProviderError subclasses
1357
+ - refactor(errors): fix ServerError to pass actual upstream status code instead of hardcoded 502
1358
+ - refactor(errors): centralize context-length detection keywords as bilingual frozenset
1359
+ - refactor(config): remove __getattr__ proxy from ConfigManager, deduplicate color-setting code
1360
+
1361
+ ✅ 已完成(v2.2.203):
1362
+ - refactor(core): remove shims and slim down core public API
1363
+ - Add pyproject.toml for project packaging configuration
1364
+ - Remove src/core/shims.py backward-compatibility layer
1365
+ - Slim src/core/__init__.py from 203 to 25 lines, exposing only 7 core symbols
1366
+ - Mirror changes in docs-src/src/core/
1367
+
1368
+ ✅ 已完成(v2.2.202):
1369
+ - refactor(main): Extract _setup_signal_handlers, _create_background_tasks, _shutdown from _run()
1370
+ - refactor(main): Narrow broad except Exception to specific exception types
1371
+ - refactor(base): Extract DEFAULT_CONTEXT_LENGTH, MODERATION_CATEGORIES, MODERATION_CATEGORY_SCORES constants
1372
+ - fix(webui): Fix import order (from __future__ import annotations after docstring)
1373
+
1374
+ ✅ 已完成(v2.2.201):
1375
+ - fix(opencode): MAX_RETRIES 从 50 降至 3,避免无效重试消耗
1376
+
1377
+ ✅ 已完成(v2.2.200):
1378
+ - fix(opencode): 修复 `_do_request()` 中 `record_success` 在异步生成器中断时不执行的问题,改用 `_request_ok` 标志 + `finally` 块确保评分记录
1379
+ - fix(opencode): 更新常量定义
1380
+
1381
+ ✅ 已完成(v2.2.198):
1382
+ - fix(ollama): 修复 `detect_capabilities()` 和 `_verify_server()` 中 `.get()` 返回 `None` 导致 `'NoneType' object is not iterable` 的问题
1383
+
1384
+ ✅ 已完成(v2.2.197):
1385
+ - refactor(routes): 拆分 `routes/openai.py`(2153 行)为 5 个聚焦子模块
1386
+ - refactor(qwen): 通过 mixin 模式拆分 QwenClient(2613 行)为 5 个模块
1387
+ - refactor(logging): 迁移 49 个平台适配器文件从 stdlib logging 到 loguru
1388
+ - feat(selector): 新增 stale candidate 自动清理功能(默认 30 天阈值)
1389
+ - fix(webui): 修复剪贴板 API 在 HTTP 环境下的兼容性(3 个 JS 文件)
1390
+ - chore(deps): 移除未使用的 tqdm 和 brotli 依赖
1391
+ - docs: 新增 ADR-006 至 ADR-013,修复 README 技术栈表(移除错误的 uvicorn 条目)
1392
+
1393
+ ✅ 已完成(v2.2.196):
1394
+ - refactor(core): 将 `server/` 子包合并为单个 `server.py` 文件,更新全部导入路径
1395
+ - fix(main): asyncio WindowsSelectorEventLoopPolicy 添加 Python 版本守卫,兼容 3.8-3.14
1396
+ - fix(opencode): `fetch_remote_models` 改为直连,不再通过代理获取模型列表
1397
+
1398
+ ✅ 已完成(v2.2.194):
1399
+ - fix(core): 在 `__init__.py` 中添加 sys.modules 向后兼容别名,修复旧模块路径导入失败
1400
+
1401
+ ✅ 已完成(v2.2.193):
1402
+ - fix(core): 更新 `__init__.py` 导入 — `http_request/stream_request` 替换为 `clean_fncall/safe_flush`,`with_retry` 替换为 `retry_with_backoff`
1403
+
1404
+ ✅ 已完成(v2.2.192):
1405
+ - feat(fncall): 新增 DSML 工具调用协议支持(echotools 升级至 1.0.32)
1406
+ - refactor(core): 合并 16 个兼容 shim 文件至 shims.py 模块,减少 59% 文件数量
1407
+
1408
+ ✅ 已完成(v2.2.191):
1409
+ - Ollama 平台新增 FILTER_CLOUD_MODELS 常量(默认 True),服务器扫描时过滤 :cloud 后缀的付费云端模型,过滤发生在注册表构建前
1410
+
1411
+ ✅ 已完成(v2.2.190):
1412
+ - WebUI 文件面板懒加载:后端 offset/limit 分页 + os.scandir 性能优化,前端滚动触发按批次加载(每批 200 条),CSS 加载指示器动画
1413
+
1414
+ ✅ 已完成(v2.2.189):
1415
+ - opencode HTTP 429 改为可重试,触发 TAS 代理切换重试
1416
+ - opencode 支持从 accounts.py 注入本地代理(LOCAL_PROXIES),启动和每次刷新时合并到代理池参与 TAS 选择
1417
+
1418
+ ✅ 已完成(v2.2.187):
1419
+ - opencode 连接重试修复:连接断开/负载截断时不再中止,所有连接错误均触发 TAS 选择新代理重试;仅 PlatformError 为不可重试异常
1420
+ - echotools 升级到 1.0.30:自动更新合并冲突自修复(merge abort 检测)、hard reset 回退、暂存恢复(stash pop)、祖先检查(ancestry check)
1421
+
1422
+ ✅ 已完成(v2.2.186):
1423
+ - opencode 平台改为单候选项模式,代理选择完全由平台内部 TAS 处理(包括首次尝试)
1424
+ - 代理池抓取日志全部从 INFO 降为 DEBUG,减少运行时日志噪音
1425
+
1426
+ ✅ 已完成(v2.2.185):
1427
+ - opencode 平台重试逻辑优化:每次重试用 TAS 选择器轮换到不同代理,排除上次失败的代理
1428
+
1429
+ ✅ 已完成(v2.2.184):
1430
+ - opencode 平台重试逻辑重构:50 次重试、1 秒固定延迟、远程断开/负载截断时立即中止,MAX_RETRIES 常量移至 constants.py
1431
+
1432
+ ✅ 已完成(v2.2.183):
1433
+ - 新增 opencode 平台适配器 — proxy-pool 架构,无需 API Key,TAS 式代理评分选择器,6 个 free 模型,native_tools 支持
1434
+
1435
+ ✅ 已完成(v2.2.182):
1436
+ - 终端 WebSocket 修复:本地/SSH 终端启动失败时向客户端发送明确错误信息
1437
+
1438
+ ✅ 已完成(v2.2.181):
1439
+ - 自动更新修复:应用更新时改用镜像源拉取并支持本地未提交变更自动暂存恢复
1440
+ - 日志级别调整:zen 平台初始化与 Worker 启动日志降为 DEBUG
1441
+
1442
+ ✅ 已完成(v2.2.180):
1443
+ - 架构审计修复:裸 except 块添加 debug 日志 + 创建 PROJECT_DECISIONS.md(5 条 ADR)
1444
+ - 异常处理加固:terminal_sessions/terminal router/request_log 共 12 处裸 except 块缩窄为具体异常类型,Unexpected 兜底升为 WARNING
1445
+ - 聊天面板 JS 重构:工具调用 HTML 提取为 _buildToolCallsHtml、模型协议选择独立持久化、loadChatState try/finally 守卫
1446
+
1447
+ ✅ 已完成(v2.2.178):
1448
+ - echotools 升级到 1.0.29:终端会话生命周期日志降级为 debug(ConPTY started/attached/killed)
1449
+
1450
+ ✅ 已完成(v2.2.177):
1451
+ - 聊天面板持久化修复:工具调用参数刷新后可展开、模型与协议选择刷新后保留
1452
+
1453
+ ✅ 已完成(v2.2.176):
1454
+ - echotools 升级到 1.0.28:修复 Linux/macOS 终端 WebSocket 连接失败(Unix PTY 启动移到线程池 + shell 候选链 + select 超时读取)
1455
+
1456
+ ✅ 已完成(v2.2.175):
1457
+ - 终端 + 按钮重新设计:点击立即创建 chooser 标签页(类 Chrome 新标签),页内选本地/远程终端
1458
+
1459
+ ✅ 已完成(v2.2.174):
1460
+ - 终端标签恢复加速:_restPreFetch 并行探测 + _discoveryProcessed 防竞态;每 tab attach 延迟去除
1461
+ - 终端 CSI 泄漏防御:_stripDecResponses 加 null-safe 守卫,覆盖所有 xterm.write 入口
1462
+ - 光标错位/双光标修复:_initTerminal 顶部完整 dispose 守卫 + 孤儿 DOM 清理 + fitAddon.dispose
1463
+ - 文件管理器 confirm() 替换为 showConfirmDialog 自定义对话框(_deleteEntries、_cancelEdit 两处)
1464
+
1465
+ ✅ 已完成(v2.2.173):
1466
+ - 修复页面刷新后标签消失:终端从 existing_sessions 重建并 attach;文件标签用 activeTabIndex 鲁棒恢复
1467
+
1468
+ ✅ 已完成(v2.2.172):
1469
+ - 修复终端字符交错双写与 CSI 响应泄漏:删除 terminal.py 重复回调,过滤 DEC private mode 响应
1470
+ - 恢复侧边栏压缩按钮为迁移前原始设计:全宽按钮置顶,替换 24x24 绝对定位小方块
1471
+ - 终端与文件侧边栏联动压缩:onToggleCollapsed 回调同步 window._tabBars 所有实例
1472
+
1473
+ ✅ 已完成(v2.2.171):
1474
+ - 终端会话持久化与进程保活(Phase 0):WS 断连不杀进程,主动关闭才终止;多客户端同时挂载;重启自动恢复存活会话
1475
+ - echotools 1.0.27:LocalTerminal 新增 detach/attach/kill API,离线输出缓冲,recover_sessions 重启挂载
1476
+ - 文件管理器迁移到统一 TabBar(Phase 1b),压缩态显示文件夹图标(修圆点痛点)
1477
+
1478
+ ✅ 已完成(v2.2.170):
1479
+ - WebUI 文件管理器默认目录改为项目根(main.py 所在目录),保留任意目录导航能力
1480
+
1481
+ ✅ 已完成(v2.2.169):
1482
+ - WebUI 终端前端全量重写:自定义 DOM ANSI 渲染器迁移到 @xterm/xterm 5.5.0 + @xterm/addon-fit
1483
+ - 删除 ~720 行自写 ANSI 解析、SGR、光标、DOM 渲染代码;删除 LOCAL_ECHO 与行回显剥离
1484
+ - xterm.js 通过 CDN 懒加载(lazy.js 注入 TAB_RESOURCES.terminal)
1485
+ - 键盘输入由 xterm.js 内部 textarea 原生捕获,不再依赖 document 级 keydown 捕获
1486
+
1487
+ ✅ 已完成(v2.2.168):
1488
+ - WebUI 终端升级 ConPTY:echotools 1.0.26 新增 Windows ConPTY 后端,支持 cls/color/ANSI/resize
1489
+ - 终端前后端模式协商:后端发送 mode 消息,前端动态开关本地回显
1490
+ - 文件管理器 UI 全中文化:64 处英文标签统一为中文
1491
+
1492
+ ✅ 已完成(v2.2.167):
1493
+ - WebUI 终端禁用路径超链接渲染:输出纯文本,与原生终端体验一致
1494
+
1495
+ ✅ 已完成(v2.2.166):
1496
+ - Qwen 平台登录失败日志聚合:60 秒窗口缓冲,单条 WARNING 输出 "first*** and N other account(s)"
1497
+ - Qwen 登录端点不可达时熔断:跳过本批次剩余账号,避免刷屏
1498
+
1499
+ ✅ 已完成(v2.2.165):
1500
+ - WebUI 文件管理器完全解锁:支持浏览整个文件系统,Windows 枚举盘符,Linux 直达根目录,新增项目根快捷按钮
1501
+ - WebUI 终端全量本地回显:修复 Windows PIPE I/O 下输入不可见,修正 Enter 行终止为 \r\n,修复提示符误染蓝色
1502
+
1503
+ ✅ 已完成(v2.2.164):
1504
+ - 修正 zen 平台日志 format 字符串({} → %d/%s),修复占位符原样输出 bug
1505
+ - ollama 平台空网络发现日志从 WARNING 降级为 DEBUG
1506
+
1507
+ ✅ 已完成(v2.2.163):
1508
+ - 新增 native_tools 能力 — 平台可声明原生工具调用支持,跳过 inject_fncall 直接传递 tools
1509
+ - zen 平台声明 native_tools=True,流式 tool_calls delta 累积,语法高亮文件预览
1510
+
1511
+ ✅ 已完成(v2.2.162):
1512
+ - WebUI 文件管理器全面增强:文件编辑(行号/Ctrl+S/脏跟踪)、上传(按钮+拖拽)、复制移动(剪贴板 Ctrl+C/X/V)、目录搜索(300ms 防抖)
1513
+ - 终端与文件管理器联动:右键在终端打开、终端输出路径可点击跳转文件管理器
1514
+
1515
+ ✅ 已完成(v2.2.161):
1516
+ - 新增 zen 平台适配器 — OpenCode.ai API,Bearer 认证,SSE 流式,reasoning→thinking 映射,自动模型获取
1517
+
1518
+ ✅ 已完成(v2.2.160):
1519
+ - 恢复静态资源 no-cache 缓存策略,撤销 max-age=3600 以避免浏览器缓存旧版 JS/CSS
1520
+
1521
+ ✅ 已完成(v2.2.159):
1522
+ - persist_get 文件不存在时返回 200 null 而非 404,消除控制台报错
1523
+ - index.html 添加 console.warn 过滤器抑制 Tailwind CDN 生产环境警告
1524
+
1525
+ ✅ 已完成(v2.2.158):
1526
+ - echotools 1.0.25:修复 Windows asyncio ProactorEventLoop ConnectionResetError(终端关闭时 [WinError 10054] 日志噪音)
1527
+
1528
+ ✅ 已完成(v2.2.157):
1529
+ - 终端键盘捕获改为 document 级别 capture phase,不依赖元素焦点,只要终端标签页可见即捕获
1530
+
1531
+ ✅ 已完成(v2.2.156):
1532
+ - 修复终端键盘输入:移除 textarea 输入层,改用 wrapper div keydown 直接捕获所有按键
1533
+
1534
+ ✅ 已完成(v2.2.155):
1535
+ - 修复终端 Enter 键在 Windows 下不执行命令的问题(CRLF 兼容)
1536
+
1537
+ ✅ 已完成(v2.2.154):
1538
+ - Qwen 重试日志改为缓冲聚合输出(DEBUG 级别)
1539
+ - WebUI 终端重写:移除 xterm.js,自研轻量终端渲染器
1540
+ - echotools 新增 terminal 模块(v1.0.24),终端逻辑下沉为通用组件
1541
+
1542
+ ✅ 已完成(v2.2.153):
1543
+ - WebUI 静态资源懒加载:按标签页延迟加载 JS/CSS,初始负载从 ~335KB 降至 ~100KB
1544
+ - 新增 LazyLoader 模块,xterm.js CDN 从预加载改为按需加载
1545
+ - 静态资源缓存策略优化(no-cache → 1 小时缓存)
1546
+
1547
+ ✅ 已完成(v2.2.152):
1548
+ - 聊天历史加载添加调试日志和逐条错误处理,定位首条助手消息消失问题
1549
+
1550
+ ✅ 已完成(v2.2.151):
1551
+ - 修复聊天历史加载竞态:sendChatMessage 等待 loadChatState 完成后再读取历史
1552
+
1553
+ ✅ 已完成(v2.2.150):
1554
+ - 聊天工具定义持久化(保存到 persist/webui/tools.json,页面加载自动恢复)
1555
+
1556
+ ✅ 已完成(v2.2.149):
1557
+ - 代码块渲染重构:JS Map 存储避免属性转义截断、独立 preview 容器、折叠/展开按钮、完整 CSS 样式
1558
+
1559
+ ✅ 已完成(v2.2.148):
1560
+ - 代码块选项卡默认 code(仅 HTML 显示 preview)、流式代码块解析支持未闭合反引号、请求日志持久化
1561
+
1562
+ ✅ 已完成(v2.2.147):
1563
+ - WebUI 日志 ANSI 颜色、聊天加载动画与 Send→Stop、工具调用持久化修复、流式渲染优化、便携面板交互改进
1564
+
1565
+ ✅ 已完成(v2.2.146):
1566
+ - WebUI 便携设置持久化从 config.json 迁移到 config.toml,后端 persist API 支持 TOML 格式
1567
+
1568
+ ✅ 已完成(v2.2.145):
1569
+ - Qwen 排队重登日志聚合:60 秒缓冲窗口,多账号 token 无效时合并为单条日志输出
1570
+
1571
+ ✅ 已完成(v2.2.144):
1572
+ - Windows 终端改用 asyncio subprocess(非阻塞 I/O);移除 WebUI 自构日志(仅保留后端 WebSocket 日志转发)
1573
+
1574
+ ✅ 已完成(v2.2.142):
1575
+ - 修复运行日志行号顺序(最新条目 #1 在顶部,向下递增)
1576
+
1577
+ ✅ 已完成(v2.2.141):
1578
+ - 终端退格键修复(DEL→BS 转换)、全部关闭浮动按钮(标签>5时显示)、Windows 终端改用 cmd.exe + UTF-8
1579
+
1580
+ ✅ 已完成(v2.2.140):
1581
+ - Windows 终端改用 PowerShell + 块读取 + 修复 write_input lambda 闭包;竖向标签栏加号 sticky 底部
1582
+
1583
+ ✅ 已完成(v2.2.139):
1584
+ - 修复竖向标签栏展开/压缩模式加号按钮纵向位置不一致
1585
+
1586
+ ✅ 已完成(v2.2.138):
1587
+ - 修复标签栏侧边切换按钮消失(renderTabBar 保留 toggle 按钮)、竖向布局加号居中、终端点击聚焦输入
1588
+
1589
+ ✅ 已完成(v2.2.137):
1590
+ - 修复 STT/TTS 模型下拉菜单无法识别模型(TTS 使用 audio_gen 能力、STT 使用 chat+vision 多模态能力)
1591
+
1592
+ ✅ 已完成(v2.2.136):
1593
+ - 修复终端选项卡不显示(switchTab 未移除 hidden 类);修复 Markdown 渲染多余空行(块级元素后不插入 br)
1594
+
1595
+ ✅ 已完成(v2.2.135):
1596
+ - Ollama 服务器发现使用代理获取、网络故障时保留缓存不覆盖、发现日志降为 DEBUG、WebUI 日志渲染匹配控制台格式
1597
+
1598
+ ✅ 已完成(v2.2.134):
1599
+ - 新增标签栏布局切换(横向顶部/竖向侧边),竖向支持展开/压缩形态,设置持久化到 persist/webui/config.json
1600
+
1601
+ ✅ 已完成(v2.2.133):
1602
+ - 终端选项卡 UI 文本全面汉化(标签名、右键菜单、SSH 对话框、验证提示、终端输出消息、空状态)
1603
+
1604
+ ✅ 已完成(v2.2.132):
1605
+ - 日志面板性能优化(O(1) DOM 更新替代全量重建);录音设备面板打开时刷新设备列表
1606
+
1607
+ ✅ 已完成(v2.2.131):
1608
+ - 新增文件管理器选项卡:目录浏览、多标签管理、面包屑导航、右键菜单、文件预览、下载、会话持久化
1609
+
1610
+ ✅ 已完成(v2.2.130):
1611
+ - 模型下拉菜单按能力过滤:聊天面板仅显示 chat 模型,STT/TTS 下拉仅显示对应能力模型
1612
+
1613
+ ✅ 已完成(v2.2.129):
1614
+ - 修复 record_prompt 不生效(gateway 导入路径修正)、日志双重时间戳、toast 恢复右下角、Qwen 定时任务持久化执行时间
1615
+
1616
+ ✅ 已完成(v2.2.128):
1617
+ - WebUI 10 项修复:复选框渲染、镜像源拖拽排序、配置下拉菜单自定义组件、diff 左右对比+分支下拉、主题切换分离、语音按钮亮色模式、静音自动停止录音、录音停止修复
1618
+
1619
+ ✅ 已完成(v2.2.127):
1620
+ - 修复更新面板复选框不显示(CSS input reset 排除 checkbox)、toast 移至左下角、zip 脚本自动替换 color=false、deepseek/ollama 初始化不阻塞
1621
+
1622
+ ✅ 已完成(v2.2.126):
1623
+ - 请求检查器详情弹窗新增复制按钮(响应内容 + 请求消息),支持流式更新时保留
1624
+
1625
+ ✅ 已完成(v2.2.125):
1626
+ - 通用逻辑下沉到 echotools 1.0.23(KeyState/KeyPool、translate 工具、ProxySelector、异步生成器重试),provider-self 代码复用 ~400 行精简
1627
+
1628
+ ✅ 已完成(v2.2.124):
1629
+ - 新增 4 个翻译平台:DeepL、Google Translate(免费公共 API)、Yandex Translate、Azure Translator
1630
+
1631
+ ✅ 已完成(v2.2.123):
1632
+ - 聊天文件附件改为文件卡片显示(图标+名称+大小);统计数据服务端持久化(30秒定时+启停保存,修复运行时间等数据丢失)
1633
+
1634
+ ✅ 已完成(v2.2.122):
1635
+ - 新增终端选项卡:本地终端(pty/cmd)+ SSH 远程终端(paramiko),xterm.js 多标签管理,右键菜单,SSH 连接持久化
1636
+
1637
+ ✅ 已完成(v2.2.121):
1638
+ - WebUI 五项修复:录音设备选择器、日志 ANSI 颜色+行号、统计/聊天持久化、Markdown 渲染+原始复制、信息框边框移除
1639
+
1640
+ ✅ 已完成(v2.2.120):
1641
+ - 更新面板修复:文件复选框正常显示、确认/取消按钮、搜索框(>5 文件时自动出现)、已选计数
1642
+
1643
+ ✅ 已完成(v2.2.119):
1644
+ - 修复 Windows 下 Runner 进程日志无颜色(colorama 包装 stderr 确保 VTP 启用 + isatty 检测改为 stderr)
1645
+
1646
+ ✅ 已完成(v2.2.118):
1647
+ - 请求日志每页调整为 7 条,搜索框始终显示
1648
+
1649
+ ✅ 已完成(v2.2.117):
1650
+ - 请求日志分页(每页 5 条、页码输入框 + /{n}、搜索框 >5 条时自动显示)
1651
+
1652
+ ✅ 已完成(v2.2.116):
1653
+ - 修复 Qwen 平台热重载失败(echotools 插件发现支持 __getattr__ 懒加载 + watcher 平台分类修复)
1654
+
1655
+ ✅ 已完成(v2.2.115):
1656
+ - 新增智能代理选择器(src/core/proxy_selector.py),TAS 式 4 维评分(失败次数、最近成功、延迟 EMA、总调用数),Qwen 平台集成,代理状态独立持久化
1657
+
1658
+ ✅ 已完成(v2.2.114):
1659
+ - 所有平台启动日志从 INFO 降为 DEBUG(config manager + 9 个平台客户端/适配器)
1660
+
1661
+ ✅ 已完成(v2.2.113):
1662
+ - Qwen 平台登录逻辑重写:统一轮询式登录替代并发批量登录,智能选择算法(最旧100→随机15-30→取10顺序登录),login+settings 统一,代理支持
1663
+
1664
+ ✅ 已完成(v2.2.112):
1665
+ - 修复端口占用检测竞态条件(echotools 升级至 1.0.20,添加 taskkill 后重试等待)及 main.py 错误信息与实际配置不一致
1666
+
1667
+ ✅ 已完成(v2.2.111):
1668
+ - 请求检查器卡片支持点击弹出详情对话框(完整消息 JSON、响应内容、元数据),卡片显示截断摘要
1669
+
1670
+ ✅ 已完成(v2.2.110):
1671
+ - WebUI 聊天错误消息与正常助手消息完全一致(无任何视觉区分),输入框始终固定在聊天面板底部
1672
+
1673
+ ✅ 已完成(v2.2.109):
1674
+ - WebUI 聊天错误消息改为助手消息气泡样式,不再使用独立错误提示框
1675
+
1676
+ ✅ 已完成(v2.2.108):
1677
+ - 修复非流式补全路径 NameError: resp 未定义(移除错误的 resp._platform 赋值)
1678
+
1679
+ ✅ 已完成(v2.2.107):
1680
+ - 配置保存防覆写改用时间戳守卫(5 秒窗口),比单次 boolean 更可靠
1681
+
1682
+ ✅ 已完成(v2.2.106):
1683
+ - 修复配置保存后被 refreshAll 覆写回原样的问题(configDirty 检查 + _skipNextConfigRender 标记)
1684
+
1685
+ ✅ 已完成(v2.2.105):
1686
+ - 新增 MappingEditor 键值对编辑器组件(替代 JSON textarea),用于 fncall_mapping 和 model_mapping
1687
+
1688
+ ✅ 已完成(v2.2.104):
1689
+ - 配置组件溢出修复:输入框弹性宽度 + _fieldBlock 纵向布局(列表/JSON 编辑器全宽显示)
1690
+
1691
+ ✅ 已完成(v2.2.103):
1692
+ - 配置 JSON 编辑器修复:textarea 只在失焦时提交(不再每次按键 parse),防止事件重复绑定
1693
+
1694
+ ✅ 已完成(v2.2.102):
1695
+ - 配置标签页重写为组件化架构:Toggle/Number/Text/Select/StringList/JsonEditor 六种组件,每个配置段独立渲染器
1696
+
1697
+ ✅ 已完成(v2.2.101):
1698
+ - config_get 添加 tomli 回退兼容 Python < 3.11
1699
+
1700
+ ✅ 已完成(v2.2.100):
1701
+ - 聊天工具参数切换改用 document 级全局事件委托,彻底修复点击无效
1702
+
1703
+ ✅ 已完成(v2.2.99):
1704
+ - GET /v1/config 改为直接读取 config.toml(tomllib.load),返回与文件一致的结构
1705
+
1706
+ ✅ 已完成(v2.2.98):
1707
+ - 聊天工具调用「查看参数/收起参数」改用事件委托 + style.display 切换(修复点击无效)
1708
+
1709
+ ✅ 已完成(v2.2.97):
1710
+ - 请求检查器 Request Messages 折叠切换改用 addEventListener 事件委托(修复 inline onclick 失效)
1711
+
1712
+ ✅ 已完成(v2.2.96):
1713
+ - WebUI 配置标签页改为从 /v1/config 加载真实配置(与 config.toml 结构一致),不再使用摘要 API 的简化版本
1714
+
1715
+ ✅ 已完成(v2.2.95):
1716
+ - WebUI 聊天调试日志:发送时显示消息数量和角色
1717
+ - reasoning_content 捕获并保存到对话历史,防止思考内容丢失导致上下文断裂
1718
+
1719
+ ✅ 已完成(v2.2.94):
1720
+ - openaifm 导入修复:util.py 移除已删除的 DEFAULT_MODEL 引用,恢复平台加载
1721
+
1722
+ ✅ 已完成(v2.2.93):
1723
+ - openaifm 候选项构建移除对 accounts.py(gitignored)的依赖,始终生成 1 个候选项
1724
+
1725
+ ✅ 已完成(v2.2.92):
1726
+ - openaifm 平台修复:无需认证(单占位候选项)、表单字段修正(input/prompt/voice/vibe)、multipart/form-data、移除无效 auth
1727
+
1728
+ ✅ 已完成(v2.2.91):
1729
+ - chatmoe 平台增强:配置 UUID Key + Authorization: Key 格式
1730
+ - SSE 解析重写:支持 event/data/id 行 + X-Stream-Id 捕获
1731
+ - 新增 abort_stream(停止生成)和 resume_stream(继续生成)
1732
+ - 定时 Key 重新生成(每 24 小时,类似 qwen 定时登录)
1733
+ - 新增 flash-lite 模型
1734
+
1735
+ ✅ 已完成(v2.2.90):
1736
+ - 移除自动更新中错误的嵌套协议检查,允许代理镜像 URL 正常拼接
1737
+
1738
+ ✅ 已完成(v2.2.89):
1739
+ - 侧栏标签文字简化:「自动更新」→「更新」,「聊天测试」→「聊天」
1740
+
1741
+ ✅ 已完成(v2.2.88):
1742
+ - 请求检查器流式内容改为路由层直接推送到 broker(绕过中间件收集器)
1743
+ - 中间件 request_start 事件新增 messages 字段(原始请求消息)
1744
+ - 请求详情新增可折叠的 Request Messages 区域显示原始请求 JSON
1745
+ - echotools 升级至 1.0.19
1746
+
1747
+ ✅ 已完成(v2.2.87):
1748
+ - 修复自动更新 URL 污染 bug:_fetch_from_mirrors 改为 try/finally 保存恢复原始 remote URL
1749
+ - 新增 _extract_repo_path() 函数清理嵌套协议前缀(regex 去重)
1750
+ - 构造的 URL 含多个 :// 时直接跳过
1751
+
1752
+ ✅ 已完成(v2.2.86):
1753
+ - echotools 升级至 1.0.18:中间件跳过 GET 请求 + request 级别 chunk 收集器修复请求检查器
1754
+ - requirements.txt 更新为 echotools>=1.0.18
1755
+
1756
+ ✅ 已完成(v2.2.85):
1757
+ - 新增 SortableList 可排序列表组件(上下箭头 + 删除,来自 echotools)
1758
+ - 自动更新镜像源列表改用 SortableList 组件替代旧的数字优先级输入
1759
+
1760
+ ✅ 已完成(v2.2.84):
1761
+ - 自动更新检查结果支持文件 diff 预览:点击文件名弹出彩色 diff 对话框
1762
+ - 新增 POST /v1/admin/autoupdate/diff 接口返回单文件变更
1763
+ - 自动更新应用后自动热重载配置
1764
+
1765
+ ✅ 已完成(v2.2.83):
1766
+ - 请求检查器修复:流式响应内容改用 request 级别 chunk 收集器,路由层推入中间件广播
1767
+ - 跳过 GET 请求(/v1/models 等),仅捕获 POST API 调用
1768
+
1769
+ ✅ 已完成(v2.2.82):
1770
+ - 便携设置 STT/TTS 模型改为下拉选择菜单(从 /v1/models 加载)
1771
+ - TTS 引导 Prompt 新增「恢复默认」按钮,从 prompts/tts_default.prompt 加载模板
1772
+ - 新增 /prompts/ 静态路由服务 prompt 模板文件
1773
+
1774
+ ✅ 已完成(v2.2.81):
1775
+ - 语音输入波形 GIF 改为 base64 内嵌(与 echotools 一致),移除外部 waveform_64x64.gif 文件依赖
1776
+
1777
+ ✅ 已完成(v2.2.80):
1778
+ - 语音输入录音时按钮替换为波形 GIF 动画,录音结束后恢复原按钮内容
1779
+
1780
+ ✅ 已完成(v2.2.79):
1781
+ - 批量测试统计:首包延迟、总耗时、Tokens、TPS 实时显示
1782
+ - 结果卡片点击展开详情对话框,支持实时流式内容更新(200ms 轮询)
1783
+ - 批量测试空 prompt 时使用默认 prompt 而非报错
1784
+
1785
+ ✅ 已完成(v2.2.78):
1786
+ - 配置 Tab 补全:新增 fncall 和 autoupdate 段,debug 段补充 color/access_log
1787
+ - WebUI 自动滑动修复:.webui-content 改为滚动容器,切换 Tab 时滚动到顶部
1788
+
1789
+ ✅ 已完成(v2.2.77):
1790
+ - 批量测试工具定义生效:自动收集工具定义并注入请求
1791
+ - 批量测试结果卡片可点击展开详情对话框(显示完整 Prompt + 响应)
1792
+ - chatTestReport 移入 batchTestSection 内部,收起时一并隐藏
1793
+
1794
+ ✅ 已完成(v2.2.76):
1795
+ - WebUI 批量测试改为点击展开模式(默认隐藏参数表单)
1796
+ - Tab 切换滚动位置修复:滚动到右侧内容容器底部而非页面顶部
1797
+
1798
+ ✅ 已完成(v2.2.75):
1799
+ - WebUI 批量测试重设计为 OpenAI Batch 风格:参数表单(Temperature/MaxTokens/SystemPrompt)+ 实时流式结果展示
1800
+ - 每个 prompt 独立卡片,流式内容实时更新
1801
+ - 工具调用检测 + 最终汇总
1802
+
1803
+ ✅ 已完成(v2.2.74):
1804
+ - 通用逻辑下沉到 echotools:RequestStats、RequestBroker、stats_middleware 迁移到 echotools.web
1805
+ - provider-self 改为 echotools 薄包装(re-export),减少约 400 行重复代码
1806
+ - echotools 升级至 1.0.17
1807
+
1808
+ ✅ 已完成(v2.2.73):
1809
+ - WebUI 批量测试:多行文本框输入多个 prompt,顺序提交并展示每个结果
1810
+ - 配置 Tab:过滤内部字段(group_list_set 等),界面更简洁
1811
+ - 自动更新差异文件选择:每个变更文件显示勾选框 + 全选/取消全选 + 选择性覆盖
1812
+ - 请求检查器流式捕获:中间件包装 response.write 捕获流式 chunks 并广播
1813
+
1814
+ ✅ 已完成(v2.2.72):
1815
+ - 自动更新镜像源改进:默认使用基础 URL(如 `https://github.com/`),后端自动拼接仓库路径
1816
+ - 镜像源 UI 添加优先级数字输入框(带上下箭头),支持拖拽排序
1817
+
1818
+ ✅ 已完成(v2.2.71):
1819
+ - InputBox 通用组件集成:替换旧聊天输入区,支持文件上传、语音输入、长文本自动转文件
1820
+ - 便携设置新增 STT/TTS 模型配置和 TTS 引导 Prompt(localStorage 持久化)
1821
+ - echotools 新增 web/input_box 组件(input-box.js + input-box.css)
1822
+
1823
+ ✅ 已完成(v2.2.70):
1824
+ - 自动更新功能完善:差异更新(仅覆盖变更文件)、镜像源优先级配置、变更文件列表展示
1825
+ - 新增 `diff_update` 和 `mirrors` 配置字段
1826
+ - 新增 `POST /v1/admin/autoupdate/apply` API(差异 checkout / 全量 pull)
1827
+ - WebUI 自动更新 Tab 重写:toggle 开关 + 镜像源增删 + 检查结果面板 + 应用按钮
1828
+
1829
+ ✅ 已完成(v2.2.69):
1830
+ - WebUI 配置 Tab 表单化:布尔值显示开关、字符串/数字显示输入框、列表/字典显示 JSON 文本域
1831
+ - WebUI 请求检查器:实时请求日志列表 + 详情面板,WebSocket 推送 request_start/end 事件
1832
+ - 新增 RequestBroker 服务(环形缓冲 100 条 + WebSocket 广播)
1833
+ - stats_middleware 增强:请求开始/结束时广播事件
1834
+ - 新增 API:GET /v1/webui/ws/requests(WebSocket)、GET /v1/webui/requests(REST)
1835
+
1836
+ ✅ 已完成(v2.2.68):
1837
+ - 修复工具调用标签泄露到流式输出的 bug(safe_flush 未检测 buffer 本身是 trigger tag 前缀)
1838
+ - 修复 clean_fncall/safe_flush 使用 echotools 而非项目 wrapper 的 get_protocol(忽略 config 配置)
1839
+ - 修复非流式路径未传递 protocol_id 到 gateway 和 clean_fncall
1840
+ - 修复 Runner 进程重复加载配置(proxy._init 仅在 Worker 中加载)
1841
+ - ConfigManager.exit_after_merge=False 消除模板合并后的多余重启
1842
+ - echotools 升级到 1.0.14(NousProtocol 重写为真正的 Nous/Hermes 格式)
1843
+
1844
+ ✅ 已完成(v2.2.67):
1845
+ - 修复工具协议选择未生效 bug:`get_protocol()` 现在自动从 `[fncall]` 配置读取默认协议和平台映射
1846
+ - `gateway.py` 改用项目薄包装版 `get_protocol`(自动读 config),不再直接用 echotools 版本
1847
+
1848
+ ✅ 已完成(v2.2.66):
1849
+ - WebUI 新增「统计」Tab:请求量、错误率、延迟分位数、Token 用量、请求趋势 sparkline、状态码分布、Top 平台/模型排行、系统资源
1850
+ - 新增请求统计收集器(内存环形缓冲 + 时间桶聚合,零外部依赖)
1851
+ - 新增统计 API:`GET /v1/webui/stats` + `POST /v1/webui/stats/reset`
1852
+ - 新增统计中间件:自动记录 API 请求(platform/model/status/latency)
1853
+ - 前端模块化架构:`js/core/`(api.js, router.js)+ `js/features/`(stats.js)
1854
+
1855
+ ✅ 已完成(v2.2.65):
1856
+ - 核心重构:`src/core/` 全面迁移至 `echotools` 包,减少约 6000 行重复代码
1857
+ - 新增 `echotools>=1.0.1` 依赖
1858
+ - 修复 `registry.py` 持久化路径计算错误(`.parent` 层级不足导致写入 `src/persist/`)
1859
+ - `[debug]` 启用彩色日志(`color = true`)
1860
+
1861
+ ✅ 已完成(v2.2.64):
1862
+ - `src/logger.py` 新增 `_resolve_log_name()` 函数,日志文件名和默认 `module_name` 改为从 `config.toml` 的 `debug.log_name` 读取(默认 `provider-v2`)
1863
+ - `[debug]` 新增 `log_name` 配置项,支持自定义日志文件名前缀
1864
+ - 新增 `noobkeys` 平台适配器:OpenAI 兼容协议的纯文本对话中转,支持 Claude / GPT-OSS / Qwen / Kimi 共 9 个模型;SSE 解析兼容 `delta.reasoning` 与 `delta.reasoning_content`;按上游错误消息区分余额不足、鉴权失败与限速并分类处理
1865
+
1866
+ ✅ 已完成(v2.2.62):
1867
+ - 修复并发竞速模式下 `inject_fncall()` 重复写入 prompt 文件:`_race()` 中 worker 启动前统一转储一次,worker 内部传 `dump_prompt=False` 抑制重复
1868
+
1869
+ ✅ 已完成(v2.2.61):
1870
+ - TAS 候选项自动调优重构:基于 4 个核心变量(错误时间、调用时间、token 速度、首包延迟)的自适应评分系统,持久化到 persist/gateway/,权重自动学习
1871
+
1872
+ ✅ 已完成(v2.2.60):
1873
+ - 移除 `_format_conversation_history()` 中的 `is_webui` 检测逻辑,始终渲染 assistant tool_calls 和 tool results,所有协议所有客户端统一处理;跳过无内容的空 assistant 块
1874
+
1875
+ ✅ 已完成(v2.2.59):
1876
+ - 修复 `dispatch()` 无 tools 时 system 消息处理:将 `role=system` 消息折叠到第一条 user 消息,确保所有平台(包括不原生支持 system role 的平台如 Qwen)都能收到系统指令
1877
+
1878
+ ✅ 已完成(v2.2.58):
1879
+ - 修复 `create_embeddings()` 路由:改用 `get_candidates(model=, capability=)` 双重过滤,优先选择有请求模型的服务器,回退到仅 capability 过滤保证可用性
1880
+
1881
+ ✅ 已完成(v2.2.57):
1882
+ - `DYNAMIC_DISCOVERY` 设为 `False`,Ollama 使用已重建的持久化缓存(480 台服务器),不再执行网络抓取和定时刷新
1883
+
1884
+ ✅ 已完成(v2.2.56):
1885
+ - 修复 `background_setup()` 在 `DYNAMIC_DISCOVERY=False` 时直接 return,不再调用 `_do_refresh(force=True)` 覆盖持久化缓存(此 bug 导致 480 台服务器缓存被清空)
1886
+ - `DYNAMIC_DISCOVERY` 临时恢复为 `True` 以重建被清空的缓存
1887
+ - `collect_servers()` 的 `skip_network` 参数保留供未来使用
1888
+
1889
+ ✅ 已完成(v2.2.55):
1890
+ - Ollama candidates() 运行时 embedding 检测:添加模型名关键词检测覆盖缓存中过期的 `embedding: false`
1891
+ - Ollama collect_servers() 添加 skip_network 参数跳过网络抓取
1892
+ - Ollama background_setup() DYNAMIC_DISCOVERY=False 时仍验证 ACCOUNTS 本地服务器
1893
+
1894
+ ✅ 已完成(v2.2.54):
1895
+ - Ollama 动态服务器发现关闭:`DYNAMIC_DISCOVERY` 设为 `False`,Ollama 平台仅使用持久化缓存中的服务器数据,不再执行网络抓取和定时刷新
1896
+
1897
+ ✅ 已完成(v2.2.53):
1898
+ - 代理 localhost 直连修复:`_should_proxy_url()` 新增 `_LOCAL_HOSTS` 正则匹配 `localhost` 和 `*.localhost`,启用全局代理时本地地址不再被误判为需要代理,修复 Ollama 等本地服务连接失败问题
1899
+ - 日志颜色初始化修复:`_setup_handlers()` 尽早通过 tomllib 读取 `config.toml` 的 `debug.color`,确保首行日志就尊重 `color=false` 设置
1900
+ - Ollama 动态发现开关:新增 `DYNAMIC_DISCOVERY` 常量,为 False 时跳过网络服务器发现和定时刷新,仅使用持久化缓存
1901
+
1902
+ ✅ 已完成(v2.2.52):
1903
+ - Ollama embedding 支持:OllamaAdapter/OllamaClient 新增 `create_embedding()` 调用 `/api/embed`;增强 `detect_capabilities()` embedding 模型名称关键词检测;修复 `_verify_server()` URL 格式 bug;添加 `EMBED_PATH` 常量和 CAPS embedding 声明
1904
+ - 日志颜色 bug 修复:`logger.py set_color()` 重建 console handler 使颜色变更立即生效;`main.py` Runner 读取 `config.toml` 决定 `CLICOLOR_FORCE`
1905
+
1906
+ ✅ 已完成(v2.2.51):
1907
+ - 全平台合规审计与修正:18 个平台逐一对照 platform-guide 规范检查并修复偏差
1908
+ - `core/constants.py` 补齐:apiairforce、caiyuesbk、cerebras、chatmoe 四个平台提取内联常量至标准 `constants.py`(BASE_URL、MODELS、CAPS 等)
1909
+ - n1n 门面重写:`adapter.py` 从 783 行实现改为 5 行纯门面 re-export,`util.py` 引入 `__getattr__` 懒加载,常量统一从 `core/constants.py` 导入
1910
+ - qwen 常量集中化:`BASE_URL`/`USER_AGENT` 从 `shared.py` 移至 `core/constants.py`,消除重复定义
1911
+ - openaifm `util.py` 补齐 `MODELS`/`CAPS` 导出
1912
+ - ollama、perplexity 清理顶层死代码兼容 shim
1913
+
1914
+ ✅ 已完成(v2.2.48):
1915
+ - fncall 全协议健康检查:6 个协议逐一审计插值完整性、参数一致性、schema 字段完整性、prompt 结构一致性、测试覆盖;`format_tool_descs` 已支持嵌套 object/array/oneOf/anyOf/allOf/additionalProperties 递归渲染(max_depth=4),无需改动;上游 tools 透传链路(openai/anthropic/gateway)完整无裁剪
1916
+ - original 协议 `render_prompt` 修复:`loop_warning` 参数此前被静默丢弃,现以 `<loop_warning>` XML 包装注入 prompt;该参数是工具调用死循环安全告警,所有协议都应无条件渲染
1917
+
1918
+ ✅ 已完成(v2.2.47):
1919
+ - nous fncall 协议 `render_prompt` 修复:`'{tool_descs}\n\n'` 由普通字符串字面量改为拼接,工具定义(参数名/类型/必填/schema)现可正确注入 prompt;默认配置 `qwen = "nous"` 下所有工具调用请求此前均命中此 bug,模型只能靠报错摸索调用方式
1920
+
1921
+ ✅ 已完成(v2.2.46):
1922
+ - aitianhu2 SSE 解析重写:移植上游 `_V1DeltaAccumulator`(228 行状态机),支持短键/长键双向规范化、字段继承、多通道并行、完整 JSONPatch op 集(add/replace/append/patch/truncate/remove)。**根因修复**:gpt-5.5 上游使用长键或省略重复字段时,旧解析器静默丢弃整段 antml 标签导致工具调用失效
1923
+ - `openai.py::_stream_chat` 标签检测改用 `proto.detect_start(buffer)`,兼容 `<antml:function_calls >` / 带属性等变体;不支持该方法的协议回退到严格 `trigger_tags` 匹配
1924
+ - 路由守卫放宽:delta 事件只要含 `o`/`op`/`p`/`path`/`v`/`value`/`c`/`channel` 任一即交状态机,仅有 `v` 靠继承的 delta 不再被丢弃
1925
+
1926
+ ✅ 已完成(v2.2.45):
1927
+ - fncall `record_prompt` 字段激活:原字段定义在 `FncallCfg` 但全代码未读取,现作为 `print_prompt` 别名生效,两者任一为 `true` 都会把注入后的 prompt 落盘到 `logs/prompts/<uuid7>.txt`
1928
+
1929
+ ✅ 已完成(v2.2.44):
1930
+ - antml fncall 解析:`_BLOCK_RE` 放宽以容忍 `<antml:function_calls >` / 带属性 / 换行等变体;`detect_start()` 同步放宽前缀匹配
1931
+ - 非流式 chat completions:即使网关未产出 `tool_calls` 块(请求未带 `tools` 或流式解析器漏掉),也对整段 content 再跑一次协议解析提取 tool_calls
1932
+ - aitianhu2 `chat.py::stream()` 增加 429 限流感知:解析 `please wait N seconds` 并抛结构化 `RateLimitError`;`client.py::complete()` 重试循环尊重上游冷却秒数
1933
+ - aitianhu2 `/conversation` 旧端点 fallback 补齐 `openai-sentinel-chat-requirements-token` 头
1934
+
1935
+ ✅ 已完成(v2.2.43):
1936
+ - aitianhu2 多账号重构:每个 Account → 独立 `aiohttp.ClientSession` + cookie jar + device_id + 持久化条目;N 账号暴露 N 个 Candidate,限速失败时自动切换账号
1937
+ - aitianhu2 动态 `chatgpt-account-id`:`fetch_account_id()` 从 `/backend-api/accounts/check/v4-2023-04-27` 按 plan 优先级(team/plus/pro/business/enterprise)挑选;每个账号独立缓存 22h
1938
+ - aitianhu2 持久化 schema 升级:`state.json` 形如 `{accounts: {...}, carids: {...}}`;旧扁平格式按 `api_key_hint` 透明迁移
1939
+ - aitianhu2 CRITICAL bug 修复:`/f/conversation` 500 / 连接异常回退到旧 `/conversation` 端点;SSE 解析增加旧格式 `data["message"]`;`resolve_image` URL 检查放宽为 `not startswith("http")`
1940
+ - aitianhu2 models fetch 移除多余 `chatgpt-account-id` header
1941
+ - 所有服务(sentinel/chat/upload)的 `chatgpt-account-id` 改为方法参数,由账号级 `account_id` 驱动
1942
+
1943
+ ✅ 已完成(v2.2.42):
1944
+ - `[gateway].group_list` 语义修正:名单决定"谁允许并发竞速",不决定"谁能路由";非名单内平台仍可路由但请求强制单发(n=1),竞速池需 ≥ 2 才启用并发
1945
+
1946
+ ✅ 已完成(v2.2.41):
1947
+ - `[gateway]` 新增 `group_list_type`(whitelist/blacklist)+ `group_list`:并发竞速平台黑白名单
1948
+ - `[platforms_proxy]` 新增 `group_list_type`:让 `enabled_platforms` 在白名单(默认,向后兼容)与黑名单之间切换
1949
+ - Qwen / DeepSeek 代理切换统一走 `platforms_proxy.is_platform_enabled()`
1950
+ - runtime 摘要导出新字段
1951
+
1952
+ ✅ 已完成(v2.2.40):
1953
+ - `_auth_middleware` 移除 `/health` 与 `/v1/models` 的无条件放行:必须持有 `pv2_session` Cookie 或有效 Bearer/X-API-Key 才能访问;浏览器无凭证自动 302 到 `/login`,API 客户端 JSON 401
1954
+
1955
+ ✅ 已完成(v2.2.39):
1956
+ - `_auth_middleware` 不再放行 `/`:未登录浏览器访问管理面板自动 302 到 `/login`,登录后 Cookie 驱动回到 `/`
1957
+
1958
+ ✅ 已完成(v2.2.38):
1959
+ - WebUI 新增 `/login` 表单页(自包含内联 CSS,light/dark 自适应)
1960
+ - `_auth_middleware` 接受 `pv2_session` Cookie 作为 Bearer 等价凭证
1961
+ - `_auth_middleware` 放行 `/login` 与 `/static/`,解决未登录时 CSS/JS 401 导致 UI 风格丢失
1962
+ - 浏览器访问受保护路由时无凭证自动 302 到 `/login`,API 客户端仍保持 JSON 401 协议
1963
+ - 新增 `/logout` 路由清除 `pv2_session` Cookie
1964
+
1965
+ ✅ 已完成(v2.2.37):
1966
+ - aitianhu2 认证失败 / 401 重认证时强制刷新 carids(清空模块缓存 + 绕过 48h 阈值)
1967
+ - aitianhu2 落地页 200 但 carids 正则未匹配时记录页面片段便于排查
1968
+
1969
+ ✅ 已完成(v2.2.36):
1970
+ - aitianhu2 流式对话:`system_hints` 非空时透传 `client_prepare_state: "success"`
1971
+ - aitianhu2 流式对话:`prepare` 响应的 `conduit_token` 注入 stream `x-conduit-token` 头
1972
+
1973
+ ✅ 已完成(v2.2.35):
1974
+ - aitianhu2 平台对齐上游:动态 carids 抓取 + 48h 刷新 + 持久化
1975
+ - aitianhu2 图像生成透传 `system_hints=["picture_v2"]`
1976
+ - aitianhu2 补全 sentinel/upload 的 `Authorization: Bearer` 头
1977
+ - aitianhu2 认证加固:200 响应 `wrong-email-credentials` / 登录页检测
1978
+ - aitianhu2 会话 22h 软刷新 + 24h 硬过期双保险
1979
+
1980
+ ✅ 已完成(v2.2.34):
1981
+ - 非 WebUI 客户端一律不渲染工具调用历史
1982
+
1983
+ ✅ 已完成(v2.2.33):
1984
+ - agent CLI 消息跳过渲染工具调用历史,避免双重渲染
1985
+
1986
+ ✅ 已完成(v2.2.32):
1987
+ - 修复 LLM 工具调用死循环:历史中工具调用格式与协议指令格式不一致
1988
+
1989
+ ✅ 已完成(v2.2.31):
1990
+ - 修复自动更新检查 Path 未导入导致的 NameError
1991
+
1992
+ ✅ 已完成(v2.2.30):
1993
+ - 修复聊天发送按钮不可见:Tailwind backgroundColor 缺少 accent 定义
1994
+
1995
+ ✅ 已完成(v2.2.29):
1996
+ - 修复 Anthropic 路由协议硬编码导致工具调用标签泄漏到客户端
1997
+
1998
+ ✅ 已完成(v2.2.28):
1999
+ - 修复助手消息编辑按钮在浅色背景上不可见的问题
2000
+
2001
+ ✅ 已完成(v2.2.27):
2002
+ - 消息编辑对话框按钮统一为「确定」+「取消」
2003
+
2004
+ ✅ 已完成(v2.2.26):
2005
+ - 修复 qwen 平台 enabled_platforms=[] 时仍能使用代理的 bug
2006
+
2007
+ ✅ 已完成(v2.2.25):
2008
+ - WebUI CustomDropdown 组件添加搜索筛选功能,选项超过5项时自动显示搜索框,列表最多显示5项
2009
+
2010
+ ✅ 已完成(v2.2.24):
2011
+ - 废除 `/docs` 端点与 `/webui` 页面路由,根路径 `/` 直接服务内置 WebUI
2012
+ - 静态资源前缀由 `/webui/static/` 简化为 `/static/`
2013
+ - 删除 `DocLink`/`DocSection`、`build_doc_sections`、`render_page`、`render_webui` 等未使用的兼容导出
2014
+ - 鉴权跳过集合仅保留 `/`、`/health`、`/v1/models`
2015
+
2016
+ ✅ 已完成(v2.2.11):
2017
+ - 新增自动更新功能(autoupdate.enabled/branch/interval,git pull + exit 42 自动重启)
2018
+
2019
+ ✅ 已完成(v2.2.10):
2020
+ - 修复工具调用 fncall 协议不一致问题(_single 缺少注入、_race 缺少 fncall_lang 参数、FncallStreamParser 协议错误)
2021
+
2022
+ ✅ 已完成(v2.2.5):
2023
+ - 修复 antml 协议 prompt 缺少 Function Invocation Syntax
2024
+ - 延迟 `_build_param_schema_index` 构建至确认有 function_calls 块后
2025
+ - 修复 ServerCfg 缺少 version 字段
2026
+ - 消除 silent `except Exception: pass` 模式(10 处)
2027
+ - 补全缺失的 `from __future__ import annotations`(4 文件)
2028
+ - 补全核心函数/类 docstring
2029
+
2030
+ ✅ 已完成(v2.2.1):
2031
+ - 修复平台 accounts 模块导入路径(遵循平台开发规范)
2032
+
2033
+ ✅ 已完成(v2.2.0):
2034
+ - 核心网关架构重构
2035
+ - OpenAI 和 Anthropic API 兼容
2036
+ - 11+ 平台适配器
2037
+ - 并发竞速机制
2038
+ - 工具调用(fncall)XML 模板可配置
2039
+ - 配置热加载
2040
+ - 文件监视热重载(平台代码热重载)
2041
+ - TAS 选择器(Thompson Sampling + 冷却)
2042
+ - 错误类命名修复(RequestTimeoutError)
2043
+ - 热重载 BUG 修复(清理 sys.modules)
2044
+
2045
+ ### v2.1(计划中)
2046
+
2047
+ - [ ] 更多平台适配器
2048
+ - [ ] 请求缓存机制
2049
+ - [ ] 负载均衡策略优化
2050
+ - [ ] 更完善的监控指标
2051
+ - [ ] WebSocket 实时通信支持
2052
+
2053
+ ### v2.2(规划中)
2054
+
2055
+ - [ ] 分布式部署支持
2056
+ - [ ] 请求限流和熔断
2057
+ - [x] 可视化控制台
2058
+ - [ ] 插件化架构
2059
+
2060
+ ### 长期目标
2061
+
2062
+ - 成为最全面的 AI 模型网关
2063
+ - 支持 30+ AI 平台
2064
+ - 毫秒级延迟
2065
+ - 99.99% 可用性
2066
+
2067
+ 📢 欢迎在 [Issues](https://github.com/nichengfuben/provider-v2/issues) 中提出功能建议!
2068
+
2069
+ ---
2070
+
2071
+ ## ❓ 常见问题
2072
+
2073
+ <details>
2074
+ <summary><b>Q1: 启动时提示 "ModuleNotFoundError: No module named 'xxx'" 怎么办?</b></summary>
2075
+
2076
+ **问题描述**:运行 `python main.py` 时提示缺少依赖模块
2077
+
2078
+ **解决方案:**
2079
+ ```bash
2080
+ # 确保已安装所有依赖
2081
+ pip install -r requirements.txt
2082
+
2083
+ # 或使用虚拟环境
2084
+ python -m venv venv
2085
+ source venv/bin/activate # Windows: .\venv\Scripts\activate
2086
+ pip install -r requirements.txt
2087
+ ```
2088
+ </details>
2089
+
2090
+ <details>
2091
+ <summary><b>Q2: 如何配置代理服务器?</b></summary>
2092
+
2093
+ **解决方案:**
2094
+
2095
+ 编辑 `config/main_config.toml` 文件:
2096
+ ```toml
2097
+ [proxy]
2098
+ proxy_server = "http://proxy.example.com:8080"
2099
+ proxy_enabled = true
2100
+ proxy_list_type = "blacklist"
2101
+ proxy_urls = []
2102
+ ```
2103
+
2104
+ > **注意**:代理配置在 `src.core.proxy` 模块导入时即生效,修改后需重启服务。
2105
+ </details>
2106
+
2107
+ <details>
2108
+ <summary><b>Q3: 并发竞速模式下如何控制配额消耗?</b></summary>
2109
+
2110
+ **解决方案:**
2111
+
2112
+ 编辑 `config/main_config.toml` 调整参数:
2113
+ ```toml
2114
+ [gateway]
2115
+ concurrent_enabled = true # 启用并发竞速
2116
+ concurrent_count = 2 # 减少并发数(降低配额消耗)
2117
+ min_tokens = 50 # 增加 Token 阈值(减少早期切换)
2118
+ ```
2119
+
2120
+ **建议:**
2121
+ - 配额紧张时设置 `concurrent_count = 1`(禁用竞速)
2122
+ - 低延迟优先时设置 `concurrent_count = 3`,`min_tokens = 10`
2123
+ </details>
2124
+
2125
+ <details>
2126
+ <summary><b>Q4: DeepSeek 平台提示 "WASM 下载失败" 怎么办?</b></summary>
2127
+
2128
+ **可能原因:**
2129
+ 1. 代理配置不正确
2130
+ 2. 网络连接问题
2131
+ 3. `persist/deepseek/` 目录无写入权限
2132
+
2133
+ **排查步骤:**
2134
+ ```bash
2135
+ # 检查目录权限
2136
+ ls -la persist/deepseek/
2137
+
2138
+ # 手动创建目录
2139
+ mkdir -p persist/deepseek/
2140
+
2141
+ # 检查代理配置
2142
+ # 确保 config/main_config.toml 中 [proxy] 配置正确
2143
+ ```
2144
+
2145
+ **WASM 文件位置:**
2146
+ - 保存路径:`persist/deepseek/sha3_wasm_bg.7b9ca65ddd.wasm`
2147
+ - 自动下载 URL:`https://fe-static.deepseek.com/chat/static/sha3_wasm_bg.7b9ca65ddd.wasm`
2148
+ - 更新检查频率:24 小时
2149
+ </details>
2150
+
2151
+ <details>
2152
+ <summary><b>Q5: 如何添加新的平台适配器?</b></summary>
2153
+
2154
+ **解决方案:**
2155
+
2156
+ 1. 在 `src/platforms/<name>/` 下创建目录结构
2157
+ 2. 在 `core/adaptercore.py` 实现平台适配器(导出 `Adapter` / `*Adapter`)
2158
+ 3. 按需添加 `accounts.py`、`core/client.py` 等模块
2159
+ 4. 确保注册表能自动发现 `src/platforms/<name>/__init__.py` 中的 `Adapter`
2160
+
2161
+ 详细步骤请参考 [平台适配器开发指南](docs-src/src/platforms/guide.md)。
2162
+ </details>
2163
+
2164
+ <details>
2165
+ <summary><b>Q6: 工具调用(fncall)中 XML 标签格式有何要求?</b></summary>
2166
+
2167
+ **解决方案:**
2168
+
2169
+ Provider-V2 要求模型返回的 XML 遵循以下格式:
2170
+
2171
+ ```xml
2172
+ <function=函数名>
2173
+ <参数名1>
2174
+ 参数值
2175
+ </参数名1>
2176
+ <参数名2>
2177
+ 参数值
2178
+ </参数名2>
2179
+ </function>
2180
+ ```
2181
+
2182
+ **关键要求:**
2183
+ - XML 标签名必须与工具定义中的参数名完全一致
2184
+ - 标签和值之间**必须**有换行符
2185
+ - 支持多行参数(如代码块),换行符会被保留
2186
+ - 必须包含所有必需参数
2187
+
2188
+ 如果模型没有按此格式返回,Provider-V2 将无法正确解析工具调用。
2189
+ </details>
2190
+
2191
+ <details>
2192
+ <summary><b>Q7: 配置文件修改后不生效怎么办?</b></summary>
2193
+
2194
+ **解决方案:**
2195
+
2196
+ ```bash
2197
+ # 方法一:等待自动重载(最多延迟 2 秒)
2198
+
2199
+ # 方法二:重启服务
2200
+ # Ctrl+C 停止,然后重新运行
2201
+ python main.py
2202
+
2203
+ # 方法三:检查配置文件语法
2204
+ python -c "import tomllib; print(tomllib.load(open('config/main_config.toml', 'rb')))"
2205
+ ```
2206
+
2207
+ **代理配置需要重启**:
2208
+ - `config/main_config.toml` 中的 `[proxy]` 配置在模块导入时生效
2209
+ - 修改代理配置后必须重启服务
2210
+ </details>
2211
+
2212
+ <details>
2213
+ <summary><b>Q8: 如何禁用鉴权?</b></summary>
2214
+
2215
+ **解决方案:**
2216
+
2217
+ 编辑 `config/main_config.toml`:
2218
+ ```toml
2219
+ [auth]
2220
+ enabled = false # 设置为 false
2221
+ keys = []
2222
+ ```
2223
+
2224
+ **无条件跳过鉴权的路径**:
2225
+ - `/login`、`/logout`、`/health`、`/static/*`
2226
+ </details>
2227
+
2228
+ ### 更多问题
2229
+
2230
+ - 🐛 在 [Issues](https://github.com/nichengfuben/provider-v2/issues) 中报告问题
2231
+ - 💬 加入社区讨论
2232
+
2233
+ ---
2234
+
2235
+ ## 🤝 贡献指南
2236
+
2237
+ 我们欢迎所有形式的贡献!
2238
+
2239
+ ### 如何贡献
2240
+
2241
+ 1. **报告 Bug**:通过 [Issues](https://github.com/nichengfuben/provider-v2/issues) 提交详细报告
2242
+ 2. **功能建议**:在 [Discussions](https://github.com/nichengfuben/provider-v2/discussions) 中提出想法
2243
+ 3. **代码贡献**:提交 Pull Request 到 **`dev` 分支**
2244
+ 4. **文档改进**:帮助完善 README 和 API 文档
2245
+ 5. **分享经验**:在社区分享使用心得
2246
+
2247
+ ### Pull Request 流程
2248
+
2249
+ ```bash
2250
+ # 1. 确保在 dev 分支基础上创建
2251
+ git checkout dev
2252
+ git pull upstream dev
2253
+ git checkout -b feature/your-feature
2254
+
2255
+ # 2. 提交更改
2256
+ git add .
2257
+ git commit -m "feat: add your feature"
2258
+
2259
+ # 3. 运行测试
2260
+ pytest tests/
2261
+
2262
+ # 4. 推送到你的 fork
2263
+ git push origin feature/your-feature
2264
+
2265
+ # 5. 创建 Pull Request(目标分支:dev)
2266
+ ```
2267
+
2268
+ ### 代码规范检查
2269
+
2270
+ ```bash
2271
+ # 格式化代码
2272
+ black src/
2273
+
2274
+ # 排序导入
2275
+ isort src/
2276
+
2277
+ # 类型检查
2278
+ mypy src/
2279
+
2280
+ # 运行测试
2281
+ pytest tests/
2282
+ ```
2283
+
2284
+ ### 贡献者名单
2285
+
2286
+ 感谢所有贡献者!
2287
+
2288
+ <a href="https://github.com/nichengfuben/provider-v2/graphs/contributors">
2289
+ <img src="https://contrib.rocks/image?repo=nichengfuben/provider-v2" />
2290
+ </a>
2291
+
2292
+ ---
2293
+
2294
+ ## 📜 许可证
2295
+
2296
+ 本项目采用 [MIT 许可证](./LICENSE)。
2297
+
2298
+ ```
2299
+ MIT License
2300
+
2301
+ Copyright (c) 2026 Provider-V2 Contributors
2302
+
2303
+ Permission is hereby granted, free of charge, to any person obtaining a copy
2304
+ of this software and associated documentation files (the "Software"), to deal
2305
+ in the Software without restriction, including without limitation the rights
2306
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
2307
+ copies of the Software, and to permit persons to whom the Software is
2308
+ furnished to do so, subject to the following conditions:
2309
+
2310
+ The above copyright notice and this permission notice shall be included in all
2311
+ copies or substantial portions of the Software.
2312
+
2313
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
2314
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
2315
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
2316
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
2317
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2318
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2319
+ SOFTWARE.
2320
+ ```
2321
+
2322
+ ---
2323
+
2324
+ ## 📮 联系方式
2325
+
2326
+ - **作者**:nightpoem
2327
+ - **邮箱**:nichengfuben@outlook.com
2328
+ - **主页**:https://github.com/nichengfuben/provider-v2
2329
+
2330
+ ### 技术支持
2331
+
2332
+ - 📧 技术支持邮箱:nichengfuben@outlook.com
2333
+ - 🐛 [问题反馈](https://github.com/nichengfuben/provider-v2/issues)
2334
+ - 💬 [社区讨论](https://github.com/nichengfuben/provider-v2/discussions)
2335
+
2336
+ ---
2337
+
2338
+ <div align="center">
2339
+
2340
+ **如果这个项目对你有帮助,请给一个 ⭐️ Star!**
2341
+
2342
+ Made with ❤️ by [nightpoem](https://github.com/nichengfuben)
2343
+
2344
+ </div>