webscout 8.2.9__py3-none-any.whl → 2026.1.19__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.
- webscout/AIauto.py +524 -251
- webscout/AIbase.py +247 -319
- webscout/AIutel.py +68 -703
- webscout/Bard.py +1072 -1026
- webscout/Extra/GitToolkit/__init__.py +10 -10
- webscout/Extra/GitToolkit/gitapi/__init__.py +20 -12
- webscout/Extra/GitToolkit/gitapi/gist.py +142 -0
- webscout/Extra/GitToolkit/gitapi/organization.py +91 -0
- webscout/Extra/GitToolkit/gitapi/repository.py +308 -195
- webscout/Extra/GitToolkit/gitapi/search.py +162 -0
- webscout/Extra/GitToolkit/gitapi/trending.py +236 -0
- webscout/Extra/GitToolkit/gitapi/user.py +128 -96
- webscout/Extra/GitToolkit/gitapi/utils.py +82 -62
- webscout/Extra/YTToolkit/README.md +443 -375
- webscout/Extra/YTToolkit/YTdownloader.py +953 -957
- webscout/Extra/YTToolkit/__init__.py +3 -3
- webscout/Extra/YTToolkit/transcriber.py +595 -476
- webscout/Extra/YTToolkit/ytapi/README.md +230 -44
- webscout/Extra/YTToolkit/ytapi/__init__.py +22 -6
- webscout/Extra/YTToolkit/ytapi/captions.py +190 -0
- webscout/Extra/YTToolkit/ytapi/channel.py +302 -307
- webscout/Extra/YTToolkit/ytapi/errors.py +13 -13
- webscout/Extra/YTToolkit/ytapi/extras.py +178 -118
- webscout/Extra/YTToolkit/ytapi/hashtag.py +120 -0
- webscout/Extra/YTToolkit/ytapi/https.py +89 -88
- webscout/Extra/YTToolkit/ytapi/patterns.py +61 -61
- webscout/Extra/YTToolkit/ytapi/playlist.py +59 -59
- webscout/Extra/YTToolkit/ytapi/pool.py +8 -8
- webscout/Extra/YTToolkit/ytapi/query.py +143 -40
- webscout/Extra/YTToolkit/ytapi/shorts.py +122 -0
- webscout/Extra/YTToolkit/ytapi/stream.py +68 -63
- webscout/Extra/YTToolkit/ytapi/suggestions.py +97 -0
- webscout/Extra/YTToolkit/ytapi/utils.py +66 -62
- webscout/Extra/YTToolkit/ytapi/video.py +403 -232
- webscout/Extra/__init__.py +2 -3
- webscout/Extra/gguf.py +1298 -684
- webscout/Extra/tempmail/README.md +487 -487
- webscout/Extra/tempmail/__init__.py +28 -28
- webscout/Extra/tempmail/async_utils.py +143 -141
- webscout/Extra/tempmail/base.py +172 -161
- webscout/Extra/tempmail/cli.py +191 -187
- webscout/Extra/tempmail/emailnator.py +88 -84
- webscout/Extra/tempmail/mail_tm.py +378 -361
- webscout/Extra/tempmail/temp_mail_io.py +304 -292
- webscout/Extra/weather.py +196 -194
- webscout/Extra/weather_ascii.py +17 -15
- webscout/Provider/AISEARCH/PERPLEXED_search.py +175 -0
- webscout/Provider/AISEARCH/Perplexity.py +292 -333
- webscout/Provider/AISEARCH/README.md +106 -279
- webscout/Provider/AISEARCH/__init__.py +16 -9
- webscout/Provider/AISEARCH/brave_search.py +298 -0
- webscout/Provider/AISEARCH/iask_search.py +357 -410
- webscout/Provider/AISEARCH/monica_search.py +200 -220
- webscout/Provider/AISEARCH/webpilotai_search.py +242 -255
- webscout/Provider/Algion.py +413 -0
- webscout/Provider/Andi.py +74 -69
- webscout/Provider/Apriel.py +313 -0
- webscout/Provider/Ayle.py +323 -0
- webscout/Provider/ChatSandbox.py +329 -342
- webscout/Provider/ClaudeOnline.py +365 -0
- webscout/Provider/Cohere.py +232 -208
- webscout/Provider/DeepAI.py +367 -0
- webscout/Provider/Deepinfra.py +467 -340
- webscout/Provider/EssentialAI.py +217 -0
- webscout/Provider/ExaAI.py +274 -261
- webscout/Provider/Gemini.py +175 -169
- webscout/Provider/GithubChat.py +385 -369
- webscout/Provider/Gradient.py +286 -0
- webscout/Provider/Groq.py +556 -801
- webscout/Provider/HadadXYZ.py +323 -0
- webscout/Provider/HeckAI.py +392 -375
- webscout/Provider/HuggingFace.py +387 -0
- webscout/Provider/IBM.py +340 -0
- webscout/Provider/Jadve.py +317 -291
- webscout/Provider/K2Think.py +306 -0
- webscout/Provider/Koboldai.py +221 -384
- webscout/Provider/Netwrck.py +273 -270
- webscout/Provider/Nvidia.py +310 -0
- webscout/Provider/OPENAI/DeepAI.py +489 -0
- webscout/Provider/OPENAI/K2Think.py +423 -0
- webscout/Provider/OPENAI/PI.py +463 -0
- webscout/Provider/OPENAI/README.md +890 -952
- webscout/Provider/OPENAI/TogetherAI.py +405 -0
- webscout/Provider/OPENAI/TwoAI.py +255 -357
- webscout/Provider/OPENAI/__init__.py +148 -40
- webscout/Provider/OPENAI/ai4chat.py +348 -293
- webscout/Provider/OPENAI/akashgpt.py +436 -0
- webscout/Provider/OPENAI/algion.py +303 -0
- webscout/Provider/OPENAI/{exachat.py → ayle.py} +365 -444
- webscout/Provider/OPENAI/base.py +253 -249
- webscout/Provider/OPENAI/cerebras.py +296 -0
- webscout/Provider/OPENAI/chatgpt.py +870 -556
- webscout/Provider/OPENAI/chatsandbox.py +233 -173
- webscout/Provider/OPENAI/deepinfra.py +403 -322
- webscout/Provider/OPENAI/e2b.py +2370 -1414
- webscout/Provider/OPENAI/elmo.py +278 -0
- webscout/Provider/OPENAI/exaai.py +452 -417
- webscout/Provider/OPENAI/freeassist.py +446 -0
- webscout/Provider/OPENAI/gradient.py +448 -0
- webscout/Provider/OPENAI/groq.py +380 -364
- webscout/Provider/OPENAI/hadadxyz.py +292 -0
- webscout/Provider/OPENAI/heckai.py +333 -308
- webscout/Provider/OPENAI/huggingface.py +321 -0
- webscout/Provider/OPENAI/ibm.py +425 -0
- webscout/Provider/OPENAI/llmchat.py +253 -0
- webscout/Provider/OPENAI/llmchatco.py +378 -335
- webscout/Provider/OPENAI/meta.py +541 -0
- webscout/Provider/OPENAI/netwrck.py +374 -357
- webscout/Provider/OPENAI/nvidia.py +317 -0
- webscout/Provider/OPENAI/oivscode.py +348 -287
- webscout/Provider/OPENAI/openrouter.py +328 -0
- webscout/Provider/OPENAI/pydantic_imports.py +1 -172
- webscout/Provider/OPENAI/sambanova.py +397 -0
- webscout/Provider/OPENAI/sonus.py +305 -304
- webscout/Provider/OPENAI/textpollinations.py +370 -339
- webscout/Provider/OPENAI/toolbaz.py +375 -413
- webscout/Provider/OPENAI/typefully.py +419 -355
- webscout/Provider/OPENAI/typliai.py +279 -0
- webscout/Provider/OPENAI/utils.py +314 -318
- webscout/Provider/OPENAI/wisecat.py +359 -387
- webscout/Provider/OPENAI/writecream.py +185 -163
- webscout/Provider/OPENAI/x0gpt.py +462 -365
- webscout/Provider/OPENAI/zenmux.py +380 -0
- webscout/Provider/OpenRouter.py +386 -0
- webscout/Provider/Openai.py +337 -496
- webscout/Provider/PI.py +443 -429
- webscout/Provider/QwenLM.py +346 -254
- webscout/Provider/STT/__init__.py +28 -0
- webscout/Provider/STT/base.py +303 -0
- webscout/Provider/STT/elevenlabs.py +264 -0
- webscout/Provider/Sambanova.py +317 -0
- webscout/Provider/TTI/README.md +69 -82
- webscout/Provider/TTI/__init__.py +37 -7
- webscout/Provider/TTI/base.py +147 -64
- webscout/Provider/TTI/claudeonline.py +393 -0
- webscout/Provider/TTI/magicstudio.py +292 -201
- webscout/Provider/TTI/miragic.py +180 -0
- webscout/Provider/TTI/pollinations.py +331 -221
- webscout/Provider/TTI/together.py +334 -0
- webscout/Provider/TTI/utils.py +14 -11
- webscout/Provider/TTS/README.md +186 -192
- webscout/Provider/TTS/__init__.py +43 -10
- webscout/Provider/TTS/base.py +523 -159
- webscout/Provider/TTS/deepgram.py +286 -156
- webscout/Provider/TTS/elevenlabs.py +189 -111
- webscout/Provider/TTS/freetts.py +218 -0
- webscout/Provider/TTS/murfai.py +288 -113
- webscout/Provider/TTS/openai_fm.py +364 -129
- webscout/Provider/TTS/parler.py +203 -111
- webscout/Provider/TTS/qwen.py +334 -0
- webscout/Provider/TTS/sherpa.py +286 -0
- webscout/Provider/TTS/speechma.py +693 -580
- webscout/Provider/TTS/streamElements.py +275 -333
- webscout/Provider/TTS/utils.py +280 -280
- webscout/Provider/TextPollinationsAI.py +331 -308
- webscout/Provider/TogetherAI.py +450 -0
- webscout/Provider/TwoAI.py +309 -475
- webscout/Provider/TypliAI.py +311 -305
- webscout/Provider/UNFINISHED/ChatHub.py +219 -209
- webscout/Provider/{OPENAI/glider.py → UNFINISHED/ChutesAI.py} +331 -326
- webscout/Provider/{GizAI.py → UNFINISHED/GizAI.py} +300 -295
- webscout/Provider/{Marcus.py → UNFINISHED/Marcus.py} +218 -198
- webscout/Provider/UNFINISHED/Qodo.py +481 -0
- webscout/Provider/{MCPCore.py → UNFINISHED/XenAI.py} +330 -315
- webscout/Provider/UNFINISHED/Youchat.py +347 -330
- webscout/Provider/UNFINISHED/aihumanizer.py +41 -0
- webscout/Provider/UNFINISHED/grammerchecker.py +37 -0
- webscout/Provider/UNFINISHED/liner.py +342 -0
- webscout/Provider/UNFINISHED/liner_api_request.py +246 -263
- webscout/Provider/{samurai.py → UNFINISHED/samurai.py} +231 -224
- webscout/Provider/WiseCat.py +256 -233
- webscout/Provider/WrDoChat.py +390 -370
- webscout/Provider/__init__.py +115 -174
- webscout/Provider/ai4chat.py +181 -174
- webscout/Provider/akashgpt.py +330 -335
- webscout/Provider/cerebras.py +397 -290
- webscout/Provider/cleeai.py +236 -213
- webscout/Provider/elmo.py +291 -283
- webscout/Provider/geminiapi.py +343 -208
- webscout/Provider/julius.py +245 -223
- webscout/Provider/learnfastai.py +333 -325
- webscout/Provider/llama3mitril.py +230 -215
- webscout/Provider/llmchat.py +308 -258
- webscout/Provider/llmchatco.py +321 -306
- webscout/Provider/meta.py +996 -801
- webscout/Provider/oivscode.py +332 -309
- webscout/Provider/searchchat.py +316 -292
- webscout/Provider/sonus.py +264 -258
- webscout/Provider/toolbaz.py +359 -353
- webscout/Provider/turboseek.py +332 -266
- webscout/Provider/typefully.py +262 -202
- webscout/Provider/x0gpt.py +332 -299
- webscout/__init__.py +31 -39
- webscout/__main__.py +5 -5
- webscout/cli.py +585 -524
- webscout/client.py +1497 -70
- webscout/conversation.py +140 -436
- webscout/exceptions.py +383 -362
- webscout/litagent/__init__.py +29 -29
- webscout/litagent/agent.py +492 -455
- webscout/litagent/constants.py +60 -60
- webscout/models.py +505 -181
- webscout/optimizers.py +74 -420
- webscout/prompt_manager.py +376 -288
- webscout/sanitize.py +1514 -0
- webscout/scout/README.md +452 -404
- webscout/scout/__init__.py +8 -8
- webscout/scout/core/__init__.py +7 -7
- webscout/scout/core/crawler.py +330 -210
- webscout/scout/core/scout.py +800 -607
- webscout/scout/core/search_result.py +51 -96
- webscout/scout/core/text_analyzer.py +64 -63
- webscout/scout/core/text_utils.py +412 -277
- webscout/scout/core/web_analyzer.py +54 -52
- webscout/scout/element.py +872 -478
- webscout/scout/parsers/__init__.py +70 -69
- webscout/scout/parsers/html5lib_parser.py +182 -172
- webscout/scout/parsers/html_parser.py +238 -236
- webscout/scout/parsers/lxml_parser.py +203 -178
- webscout/scout/utils.py +38 -37
- webscout/search/__init__.py +47 -0
- webscout/search/base.py +201 -0
- webscout/search/bing_main.py +45 -0
- webscout/search/brave_main.py +92 -0
- webscout/search/duckduckgo_main.py +57 -0
- webscout/search/engines/__init__.py +127 -0
- webscout/search/engines/bing/__init__.py +15 -0
- webscout/search/engines/bing/base.py +35 -0
- webscout/search/engines/bing/images.py +114 -0
- webscout/search/engines/bing/news.py +96 -0
- webscout/search/engines/bing/suggestions.py +36 -0
- webscout/search/engines/bing/text.py +109 -0
- webscout/search/engines/brave/__init__.py +19 -0
- webscout/search/engines/brave/base.py +47 -0
- webscout/search/engines/brave/images.py +213 -0
- webscout/search/engines/brave/news.py +353 -0
- webscout/search/engines/brave/suggestions.py +318 -0
- webscout/search/engines/brave/text.py +167 -0
- webscout/search/engines/brave/videos.py +364 -0
- webscout/search/engines/duckduckgo/__init__.py +25 -0
- webscout/search/engines/duckduckgo/answers.py +80 -0
- webscout/search/engines/duckduckgo/base.py +189 -0
- webscout/search/engines/duckduckgo/images.py +100 -0
- webscout/search/engines/duckduckgo/maps.py +183 -0
- webscout/search/engines/duckduckgo/news.py +70 -0
- webscout/search/engines/duckduckgo/suggestions.py +22 -0
- webscout/search/engines/duckduckgo/text.py +221 -0
- webscout/search/engines/duckduckgo/translate.py +48 -0
- webscout/search/engines/duckduckgo/videos.py +80 -0
- webscout/search/engines/duckduckgo/weather.py +84 -0
- webscout/search/engines/mojeek.py +61 -0
- webscout/search/engines/wikipedia.py +77 -0
- webscout/search/engines/yahoo/__init__.py +41 -0
- webscout/search/engines/yahoo/answers.py +19 -0
- webscout/search/engines/yahoo/base.py +34 -0
- webscout/search/engines/yahoo/images.py +323 -0
- webscout/search/engines/yahoo/maps.py +19 -0
- webscout/search/engines/yahoo/news.py +258 -0
- webscout/search/engines/yahoo/suggestions.py +140 -0
- webscout/search/engines/yahoo/text.py +273 -0
- webscout/search/engines/yahoo/translate.py +19 -0
- webscout/search/engines/yahoo/videos.py +302 -0
- webscout/search/engines/yahoo/weather.py +220 -0
- webscout/search/engines/yandex.py +67 -0
- webscout/search/engines/yep/__init__.py +13 -0
- webscout/search/engines/yep/base.py +34 -0
- webscout/search/engines/yep/images.py +101 -0
- webscout/search/engines/yep/suggestions.py +38 -0
- webscout/search/engines/yep/text.py +99 -0
- webscout/search/http_client.py +172 -0
- webscout/search/results.py +141 -0
- webscout/search/yahoo_main.py +57 -0
- webscout/search/yep_main.py +48 -0
- webscout/server/__init__.py +48 -0
- webscout/server/config.py +78 -0
- webscout/server/exceptions.py +69 -0
- webscout/server/providers.py +286 -0
- webscout/server/request_models.py +131 -0
- webscout/server/request_processing.py +404 -0
- webscout/server/routes.py +642 -0
- webscout/server/server.py +351 -0
- webscout/server/ui_templates.py +1171 -0
- webscout/swiftcli/__init__.py +79 -95
- webscout/swiftcli/core/__init__.py +7 -7
- webscout/swiftcli/core/cli.py +574 -297
- webscout/swiftcli/core/context.py +98 -104
- webscout/swiftcli/core/group.py +268 -241
- webscout/swiftcli/decorators/__init__.py +28 -28
- webscout/swiftcli/decorators/command.py +243 -221
- webscout/swiftcli/decorators/options.py +247 -220
- webscout/swiftcli/decorators/output.py +392 -252
- webscout/swiftcli/exceptions.py +21 -21
- webscout/swiftcli/plugins/__init__.py +9 -9
- webscout/swiftcli/plugins/base.py +134 -135
- webscout/swiftcli/plugins/manager.py +269 -269
- webscout/swiftcli/utils/__init__.py +58 -59
- webscout/swiftcli/utils/formatting.py +251 -252
- webscout/swiftcli/utils/parsing.py +368 -267
- webscout/update_checker.py +280 -136
- webscout/utils.py +28 -14
- webscout/version.py +2 -1
- webscout/version.py.bak +3 -0
- webscout/zeroart/__init__.py +218 -135
- webscout/zeroart/base.py +70 -66
- webscout/zeroart/effects.py +155 -101
- webscout/zeroart/fonts.py +1799 -1239
- webscout-2026.1.19.dist-info/METADATA +638 -0
- webscout-2026.1.19.dist-info/RECORD +312 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/WHEEL +1 -1
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/entry_points.txt +1 -1
- webscout/DWEBS.py +0 -520
- webscout/Extra/Act.md +0 -309
- webscout/Extra/GitToolkit/gitapi/README.md +0 -110
- webscout/Extra/autocoder/__init__.py +0 -9
- webscout/Extra/autocoder/autocoder.py +0 -1105
- webscout/Extra/autocoder/autocoder_utiles.py +0 -332
- webscout/Extra/gguf.md +0 -430
- webscout/Extra/weather.md +0 -281
- webscout/Litlogger/README.md +0 -10
- webscout/Litlogger/__init__.py +0 -15
- webscout/Litlogger/formats.py +0 -4
- webscout/Litlogger/handlers.py +0 -103
- webscout/Litlogger/levels.py +0 -13
- webscout/Litlogger/logger.py +0 -92
- webscout/Provider/AI21.py +0 -177
- webscout/Provider/AISEARCH/DeepFind.py +0 -254
- webscout/Provider/AISEARCH/felo_search.py +0 -202
- webscout/Provider/AISEARCH/genspark_search.py +0 -324
- webscout/Provider/AISEARCH/hika_search.py +0 -186
- webscout/Provider/AISEARCH/scira_search.py +0 -298
- webscout/Provider/Aitopia.py +0 -316
- webscout/Provider/AllenAI.py +0 -440
- webscout/Provider/Blackboxai.py +0 -791
- webscout/Provider/ChatGPTClone.py +0 -237
- webscout/Provider/ChatGPTGratis.py +0 -194
- webscout/Provider/Cloudflare.py +0 -324
- webscout/Provider/ExaChat.py +0 -358
- webscout/Provider/Flowith.py +0 -217
- webscout/Provider/FreeGemini.py +0 -250
- webscout/Provider/Glider.py +0 -225
- webscout/Provider/HF_space/__init__.py +0 -0
- webscout/Provider/HF_space/qwen_qwen2.py +0 -206
- webscout/Provider/HuggingFaceChat.py +0 -469
- webscout/Provider/Hunyuan.py +0 -283
- webscout/Provider/LambdaChat.py +0 -411
- webscout/Provider/Llama3.py +0 -259
- webscout/Provider/Nemotron.py +0 -218
- webscout/Provider/OLLAMA.py +0 -396
- webscout/Provider/OPENAI/BLACKBOXAI.py +0 -766
- webscout/Provider/OPENAI/Cloudflare.py +0 -378
- webscout/Provider/OPENAI/FreeGemini.py +0 -283
- webscout/Provider/OPENAI/NEMOTRON.py +0 -232
- webscout/Provider/OPENAI/Qwen3.py +0 -283
- webscout/Provider/OPENAI/api.py +0 -969
- webscout/Provider/OPENAI/c4ai.py +0 -373
- webscout/Provider/OPENAI/chatgptclone.py +0 -494
- webscout/Provider/OPENAI/copilot.py +0 -242
- webscout/Provider/OPENAI/flowith.py +0 -162
- webscout/Provider/OPENAI/freeaichat.py +0 -359
- webscout/Provider/OPENAI/mcpcore.py +0 -389
- webscout/Provider/OPENAI/multichat.py +0 -376
- webscout/Provider/OPENAI/opkfc.py +0 -496
- webscout/Provider/OPENAI/scirachat.py +0 -477
- webscout/Provider/OPENAI/standardinput.py +0 -433
- webscout/Provider/OPENAI/typegpt.py +0 -364
- webscout/Provider/OPENAI/uncovrAI.py +0 -463
- webscout/Provider/OPENAI/venice.py +0 -431
- webscout/Provider/OPENAI/yep.py +0 -382
- webscout/Provider/OpenGPT.py +0 -209
- webscout/Provider/Perplexitylabs.py +0 -415
- webscout/Provider/Reka.py +0 -214
- webscout/Provider/StandardInput.py +0 -290
- webscout/Provider/TTI/aiarta.py +0 -365
- webscout/Provider/TTI/artbit.py +0 -0
- webscout/Provider/TTI/fastflux.py +0 -200
- webscout/Provider/TTI/piclumen.py +0 -203
- webscout/Provider/TTI/pixelmuse.py +0 -225
- webscout/Provider/TTS/gesserit.py +0 -128
- webscout/Provider/TTS/sthir.py +0 -94
- webscout/Provider/TeachAnything.py +0 -229
- webscout/Provider/UNFINISHED/puterjs.py +0 -635
- webscout/Provider/UNFINISHED/test_lmarena.py +0 -119
- webscout/Provider/Venice.py +0 -258
- webscout/Provider/VercelAI.py +0 -253
- webscout/Provider/Writecream.py +0 -246
- webscout/Provider/WritingMate.py +0 -269
- webscout/Provider/asksteve.py +0 -220
- webscout/Provider/chatglm.py +0 -215
- webscout/Provider/copilot.py +0 -425
- webscout/Provider/freeaichat.py +0 -285
- webscout/Provider/granite.py +0 -235
- webscout/Provider/hermes.py +0 -266
- webscout/Provider/koala.py +0 -170
- webscout/Provider/lmarena.py +0 -198
- webscout/Provider/multichat.py +0 -364
- webscout/Provider/scira_chat.py +0 -299
- webscout/Provider/scnet.py +0 -243
- webscout/Provider/talkai.py +0 -194
- webscout/Provider/typegpt.py +0 -289
- webscout/Provider/uncovr.py +0 -368
- webscout/Provider/yep.py +0 -389
- webscout/litagent/Readme.md +0 -276
- webscout/litprinter/__init__.py +0 -59
- webscout/swiftcli/Readme.md +0 -323
- webscout/tempid.py +0 -128
- webscout/webscout_search.py +0 -1184
- webscout/webscout_search_async.py +0 -654
- webscout/yep_search.py +0 -347
- webscout/zeroart/README.md +0 -89
- webscout-8.2.9.dist-info/METADATA +0 -1033
- webscout-8.2.9.dist-info/RECORD +0 -289
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/licenses/LICENSE.md +0 -0
- {webscout-8.2.9.dist-info → webscout-2026.1.19.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# webscout/server/__init__.py
|
|
2
|
+
from .exceptions import APIError
|
|
3
|
+
from .routes import Api
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
# Import server functions lazily to avoid module execution issues
|
|
7
|
+
def create_app():
|
|
8
|
+
from .server import create_app as _create_app
|
|
9
|
+
return _create_app()
|
|
10
|
+
|
|
11
|
+
def run_api(*args, **kwargs):
|
|
12
|
+
from .server import run_api as _run_api
|
|
13
|
+
return _run_api(*args, **kwargs)
|
|
14
|
+
|
|
15
|
+
def start_server(*args, **kwargs):
|
|
16
|
+
from .server import start_server as _start_server
|
|
17
|
+
return _start_server(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Lazy imports for config classes to avoid initialization issues
|
|
22
|
+
def get_server_config():
|
|
23
|
+
from .config import ServerConfig
|
|
24
|
+
return ServerConfig
|
|
25
|
+
|
|
26
|
+
def get_app_config():
|
|
27
|
+
from .config import AppConfig
|
|
28
|
+
return AppConfig
|
|
29
|
+
|
|
30
|
+
def initialize_provider_map():
|
|
31
|
+
from .providers import initialize_provider_map as _init_provider_map
|
|
32
|
+
return _init_provider_map()
|
|
33
|
+
|
|
34
|
+
def initialize_tti_provider_map():
|
|
35
|
+
from .providers import initialize_tti_provider_map as _init_tti_provider_map
|
|
36
|
+
return _init_tti_provider_map()
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"create_app",
|
|
40
|
+
"run_api",
|
|
41
|
+
"start_server",
|
|
42
|
+
"Api",
|
|
43
|
+
"get_server_config",
|
|
44
|
+
"get_app_config",
|
|
45
|
+
"APIError",
|
|
46
|
+
"initialize_provider_map",
|
|
47
|
+
"initialize_tti_provider_map"
|
|
48
|
+
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration management for the Webscout API server.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Any, Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from litprinter import ic
|
|
9
|
+
|
|
10
|
+
# Configuration constants
|
|
11
|
+
DEFAULT_PORT = 8000
|
|
12
|
+
DEFAULT_HOST = "0.0.0.0"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ServerConfig:
|
|
16
|
+
"""Centralized configuration management for the API server."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.provider_map: Dict[str, Any] = {}
|
|
20
|
+
self.default_provider: str = "ChatGPT"
|
|
21
|
+
self.base_url: Optional[str] = None
|
|
22
|
+
self.host: str = DEFAULT_HOST
|
|
23
|
+
self.port: int = DEFAULT_PORT
|
|
24
|
+
self.debug: bool = False
|
|
25
|
+
self.cors_origins: List[str] = ["*"]
|
|
26
|
+
self.max_request_size: int = 10 * 1024 * 1024 # 10MB
|
|
27
|
+
self.request_timeout: int = 300 # 5 minutes
|
|
28
|
+
self.auth_required: bool = False
|
|
29
|
+
self.rate_limit_enabled: bool = False
|
|
30
|
+
self.request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
|
|
31
|
+
|
|
32
|
+
def update(self, **kwargs) -> None:
|
|
33
|
+
"""Update configuration with provided values."""
|
|
34
|
+
for key, value in kwargs.items():
|
|
35
|
+
if hasattr(self, key) and value is not None:
|
|
36
|
+
setattr(self, key, value)
|
|
37
|
+
ic.configureOutput(prefix='INFO| ')
|
|
38
|
+
ic(f"Config updated: {key} = {value}")
|
|
39
|
+
|
|
40
|
+
def validate(self) -> None:
|
|
41
|
+
"""Validate configuration settings."""
|
|
42
|
+
if self.port < 1 or self.port > 65535:
|
|
43
|
+
raise ValueError(f"Invalid port number: {self.port}")
|
|
44
|
+
|
|
45
|
+
if self.default_provider not in self.provider_map and self.provider_map:
|
|
46
|
+
available_providers = list(set(v.__name__ for v in self.provider_map.values()))
|
|
47
|
+
ic.configureOutput(prefix='WARNING| ')
|
|
48
|
+
ic(f"Default provider '{self.default_provider}' not found. Available: {available_providers}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class AppConfig:
|
|
52
|
+
"""Legacy configuration class for backward compatibility."""
|
|
53
|
+
provider_map = {}
|
|
54
|
+
tti_provider_map = {} # Add TTI provider map
|
|
55
|
+
default_provider = "ChatGPT"
|
|
56
|
+
default_tti_provider = "PollinationsAI" # Add default TTI provider
|
|
57
|
+
base_url: Optional[str] = None
|
|
58
|
+
auth_required: bool = False
|
|
59
|
+
rate_limit_enabled: bool = False
|
|
60
|
+
request_logging_enabled: bool = os.getenv("WEBSCOUT_REQUEST_LOGGING", "true").lower() == "true" # Enable request logging by default
|
|
61
|
+
|
|
62
|
+
@classmethod
|
|
63
|
+
def set_config(cls, **data):
|
|
64
|
+
"""Set configuration values."""
|
|
65
|
+
# Filter out auth-related keys
|
|
66
|
+
auth_keys = {'api_key'}
|
|
67
|
+
filtered_data = {k: v for k, v in data.items() if k not in auth_keys}
|
|
68
|
+
|
|
69
|
+
for key, value in filtered_data.items():
|
|
70
|
+
setattr(cls, key, value)
|
|
71
|
+
# Sync with new config system
|
|
72
|
+
try:
|
|
73
|
+
from .server import get_config
|
|
74
|
+
config = get_config()
|
|
75
|
+
config.update(**filtered_data)
|
|
76
|
+
except ImportError:
|
|
77
|
+
# Handle case where server module is not available
|
|
78
|
+
pass
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom exceptions for the Webscout API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from fastapi.responses import JSONResponse
|
|
10
|
+
from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
|
|
11
|
+
|
|
12
|
+
from .request_models import ErrorDetail, ErrorResponse
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def clean_text(text):
|
|
16
|
+
"""Clean text by removing null bytes and control characters except newlines and tabs."""
|
|
17
|
+
if not isinstance(text, str):
|
|
18
|
+
return text
|
|
19
|
+
|
|
20
|
+
# Remove null bytes
|
|
21
|
+
text = text.replace('\x00', '')
|
|
22
|
+
|
|
23
|
+
# Keep newlines, tabs, and other printable characters, remove other control chars
|
|
24
|
+
# This regex matches control characters except \n, \r, \t
|
|
25
|
+
return re.sub(r'[\x01-\x08\x0b\x0c\x0e-\x1f\x7f]', '', text)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class APIError(Exception):
|
|
29
|
+
"""Custom exception for API errors."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, message: str, status_code: int = HTTP_500_INTERNAL_SERVER_ERROR,
|
|
32
|
+
error_type: str = "server_error", param: Optional[str] = None,
|
|
33
|
+
code: Optional[str] = None):
|
|
34
|
+
self.message = message
|
|
35
|
+
self.status_code = status_code
|
|
36
|
+
self.error_type = error_type
|
|
37
|
+
self.param = param
|
|
38
|
+
self.code = code
|
|
39
|
+
super().__init__(message)
|
|
40
|
+
|
|
41
|
+
def to_response(self) -> JSONResponse:
|
|
42
|
+
"""Convert to FastAPI JSONResponse."""
|
|
43
|
+
error_detail = ErrorDetail(
|
|
44
|
+
message=self.message,
|
|
45
|
+
type=self.error_type,
|
|
46
|
+
param=self.param,
|
|
47
|
+
code=self.code
|
|
48
|
+
)
|
|
49
|
+
error_response = ErrorResponse(error=error_detail)
|
|
50
|
+
return JSONResponse(
|
|
51
|
+
status_code=self.status_code,
|
|
52
|
+
content=error_response.model_dump(exclude_none=True)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def format_exception(e) -> str:
|
|
57
|
+
"""Format exception for JSON response."""
|
|
58
|
+
if isinstance(e, str):
|
|
59
|
+
message = e
|
|
60
|
+
else:
|
|
61
|
+
message = f"{e.__class__.__name__}: {str(e)}"
|
|
62
|
+
return json.dumps({
|
|
63
|
+
"error": {
|
|
64
|
+
"message": message,
|
|
65
|
+
"type": "server_error",
|
|
66
|
+
"param": None,
|
|
67
|
+
"code": "internal_server_error"
|
|
68
|
+
}
|
|
69
|
+
})
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Provider management and initialization for the Webscout API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import inspect
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Any, Dict, Optional, Tuple
|
|
8
|
+
|
|
9
|
+
from litprinter import ic
|
|
10
|
+
from starlette.status import HTTP_404_NOT_FOUND, HTTP_500_INTERNAL_SERVER_ERROR
|
|
11
|
+
|
|
12
|
+
from .config import AppConfig
|
|
13
|
+
from .exceptions import APIError
|
|
14
|
+
|
|
15
|
+
# Cache for provider instances to avoid reinitialization on every request
|
|
16
|
+
provider_instances: Dict[str, Any] = {}
|
|
17
|
+
tti_provider_instances: Dict[str, Any] = {}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def initialize_provider_map() -> None:
|
|
21
|
+
"""Initialize the provider map by discovering available providers."""
|
|
22
|
+
ic.configureOutput(prefix='INFO| ')
|
|
23
|
+
ic("Initializing provider map...")
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
from webscout.Provider.OPENAI.base import OpenAICompatibleProvider, SimpleModelList
|
|
27
|
+
module = sys.modules["webscout.Provider.OPENAI"]
|
|
28
|
+
|
|
29
|
+
provider_count = 0
|
|
30
|
+
model_count = 0
|
|
31
|
+
|
|
32
|
+
for name, obj in inspect.getmembers(module):
|
|
33
|
+
if (
|
|
34
|
+
inspect.isclass(obj)
|
|
35
|
+
and issubclass(obj, OpenAICompatibleProvider)
|
|
36
|
+
and obj.__name__ != "OpenAICompatibleProvider"
|
|
37
|
+
):
|
|
38
|
+
# Only include providers that don't require authentication
|
|
39
|
+
if hasattr(obj, 'required_auth') and not getattr(obj, 'required_auth', True):
|
|
40
|
+
provider_name = obj.__name__
|
|
41
|
+
AppConfig.provider_map[provider_name] = obj
|
|
42
|
+
provider_count += 1
|
|
43
|
+
|
|
44
|
+
# Register available models for this provider
|
|
45
|
+
available_models = getattr(obj, "AVAILABLE_MODELS", None)
|
|
46
|
+
if available_models is not None and isinstance(
|
|
47
|
+
available_models, (list, tuple, set)
|
|
48
|
+
):
|
|
49
|
+
for model in available_models:
|
|
50
|
+
if model and isinstance(model, str):
|
|
51
|
+
model_key = f"{provider_name}/{model}"
|
|
52
|
+
AppConfig.provider_map[model_key] = obj
|
|
53
|
+
model_count += 1
|
|
54
|
+
|
|
55
|
+
# Fallback to ChatGPT if no providers found
|
|
56
|
+
if not AppConfig.provider_map:
|
|
57
|
+
ic.configureOutput(prefix='WARNING| ')
|
|
58
|
+
ic("No providers found, using ChatGPT fallback")
|
|
59
|
+
try:
|
|
60
|
+
from webscout.Provider.OPENAI.chatgpt import ChatGPT
|
|
61
|
+
fallback_models = ["gpt-4", "gpt-4o", "gpt-4o-mini", "gpt-3.5-turbo"]
|
|
62
|
+
|
|
63
|
+
AppConfig.provider_map["ChatGPT"] = ChatGPT
|
|
64
|
+
|
|
65
|
+
for model in fallback_models:
|
|
66
|
+
model_key = f"ChatGPT/{model}"
|
|
67
|
+
AppConfig.provider_map[model_key] = ChatGPT
|
|
68
|
+
|
|
69
|
+
AppConfig.default_provider = "ChatGPT"
|
|
70
|
+
provider_count = 1
|
|
71
|
+
model_count = len(fallback_models)
|
|
72
|
+
except ImportError as e:
|
|
73
|
+
ic.configureOutput(prefix='ERROR| ')
|
|
74
|
+
ic(f"Failed to import ChatGPT fallback: {e}")
|
|
75
|
+
raise APIError("No providers available", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
76
|
+
|
|
77
|
+
ic.configureOutput(prefix='INFO| ')
|
|
78
|
+
ic(f"Initialized {provider_count} providers with {model_count} models")
|
|
79
|
+
|
|
80
|
+
except Exception as e:
|
|
81
|
+
ic.configureOutput(prefix='ERROR| ')
|
|
82
|
+
ic(f"Failed to initialize provider map: {e}")
|
|
83
|
+
raise APIError(f"Provider initialization failed: {e}", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def initialize_tti_provider_map() -> None:
|
|
87
|
+
"""Initialize the TTI provider map by discovering available TTI providers."""
|
|
88
|
+
ic.configureOutput(prefix='INFO| ')
|
|
89
|
+
ic("Initializing TTI provider map...")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
from webscout.Provider.TTI.base import TTICompatibleProvider
|
|
93
|
+
module = sys.modules["webscout.Provider.TTI"]
|
|
94
|
+
|
|
95
|
+
provider_count = 0
|
|
96
|
+
model_count = 0
|
|
97
|
+
|
|
98
|
+
for name, obj in inspect.getmembers(module):
|
|
99
|
+
if (
|
|
100
|
+
inspect.isclass(obj)
|
|
101
|
+
and issubclass(obj, TTICompatibleProvider)
|
|
102
|
+
and obj.__name__ != "TTICompatibleProvider"
|
|
103
|
+
and obj.__name__ != "BaseImages"
|
|
104
|
+
):
|
|
105
|
+
provider_name = obj.__name__
|
|
106
|
+
AppConfig.tti_provider_map[provider_name] = obj
|
|
107
|
+
provider_count += 1
|
|
108
|
+
|
|
109
|
+
# Register available models for this TTI provider
|
|
110
|
+
available_models = getattr(obj, "AVAILABLE_MODELS", None)
|
|
111
|
+
if available_models is not None and isinstance(
|
|
112
|
+
available_models, (list, tuple, set)
|
|
113
|
+
):
|
|
114
|
+
for model in available_models:
|
|
115
|
+
if model and isinstance(model, str):
|
|
116
|
+
model_key = f"{provider_name}/{model}"
|
|
117
|
+
AppConfig.tti_provider_map[model_key] = obj
|
|
118
|
+
model_count += 1
|
|
119
|
+
|
|
120
|
+
# Fallback to PollinationsAI if no TTI providers found
|
|
121
|
+
if not AppConfig.tti_provider_map:
|
|
122
|
+
ic.configureOutput(prefix='WARNING| ')
|
|
123
|
+
ic("No TTI providers found, using PollinationsAI fallback")
|
|
124
|
+
try:
|
|
125
|
+
from webscout.Provider.TTI.pollinations import PollinationsAI
|
|
126
|
+
fallback_models = ["flux", "turbo", "gptimage"]
|
|
127
|
+
|
|
128
|
+
AppConfig.tti_provider_map["PollinationsAI"] = PollinationsAI
|
|
129
|
+
|
|
130
|
+
for model in fallback_models:
|
|
131
|
+
model_key = f"PollinationsAI/{model}"
|
|
132
|
+
AppConfig.tti_provider_map[model_key] = PollinationsAI
|
|
133
|
+
|
|
134
|
+
AppConfig.default_tti_provider = "PollinationsAI"
|
|
135
|
+
provider_count = 1
|
|
136
|
+
model_count = len(fallback_models)
|
|
137
|
+
except ImportError as e:
|
|
138
|
+
ic.configureOutput(prefix='ERROR| ')
|
|
139
|
+
ic(f"Failed to import PollinationsAI fallback: {e}")
|
|
140
|
+
raise APIError("No TTI providers available", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
141
|
+
|
|
142
|
+
ic.configureOutput(prefix='INFO| ')
|
|
143
|
+
ic(f"Initialized {provider_count} TTI providers with {model_count} models")
|
|
144
|
+
|
|
145
|
+
except Exception as e:
|
|
146
|
+
ic.configureOutput(prefix='ERROR| ')
|
|
147
|
+
ic(f"Failed to initialize TTI provider map: {e}")
|
|
148
|
+
raise APIError(f"TTI Provider initialization failed: {e}", HTTP_500_INTERNAL_SERVER_ERROR)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def resolve_provider_and_model(model_identifier: str) -> Tuple[Any, str]:
|
|
152
|
+
"""Resolve provider class and model name from model identifier."""
|
|
153
|
+
provider_class = None
|
|
154
|
+
model_name = None
|
|
155
|
+
|
|
156
|
+
# Check for explicit provider/model syntax
|
|
157
|
+
if model_identifier in AppConfig.provider_map and "/" in model_identifier:
|
|
158
|
+
provider_class = AppConfig.provider_map[model_identifier]
|
|
159
|
+
_, model_name = model_identifier.split("/", 1)
|
|
160
|
+
elif "/" in model_identifier:
|
|
161
|
+
provider_name, model_name = model_identifier.split("/", 1)
|
|
162
|
+
provider_class = AppConfig.provider_map.get(provider_name)
|
|
163
|
+
else:
|
|
164
|
+
provider_class = AppConfig.provider_map.get(AppConfig.default_provider)
|
|
165
|
+
model_name = model_identifier
|
|
166
|
+
|
|
167
|
+
if not provider_class:
|
|
168
|
+
available_providers = list(set(v.__name__ for v in AppConfig.provider_map.values()))
|
|
169
|
+
raise APIError(
|
|
170
|
+
f"Provider for model '{model_identifier}' not found. Available providers: {available_providers}",
|
|
171
|
+
HTTP_404_NOT_FOUND,
|
|
172
|
+
"model_not_found",
|
|
173
|
+
param="model"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
# Validate model availability
|
|
177
|
+
if hasattr(provider_class, "AVAILABLE_MODELS") and model_name is not None:
|
|
178
|
+
available = getattr(provider_class, "AVAILABLE_MODELS", None)
|
|
179
|
+
# If it's a property, get from instance
|
|
180
|
+
if isinstance(available, property):
|
|
181
|
+
try:
|
|
182
|
+
available = getattr(provider_class(), "AVAILABLE_MODELS", [])
|
|
183
|
+
except Exception:
|
|
184
|
+
available = []
|
|
185
|
+
# If still not iterable, fallback to empty list
|
|
186
|
+
if not isinstance(available, (list, tuple, set)):
|
|
187
|
+
available = list(available) if available and hasattr(available, "__iter__") and not isinstance(available, str) else []
|
|
188
|
+
if available and model_name not in available:
|
|
189
|
+
raise APIError(
|
|
190
|
+
f"Model '{model_name}' not supported by provider '{provider_class.__name__}'. Available models: {available}",
|
|
191
|
+
HTTP_404_NOT_FOUND,
|
|
192
|
+
"model_not_found",
|
|
193
|
+
param="model"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
return provider_class, model_name
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def resolve_tti_provider_and_model(model_identifier: str) -> Tuple[Any, str]:
|
|
200
|
+
"""Resolve TTI provider class and model name from model identifier."""
|
|
201
|
+
provider_class = None
|
|
202
|
+
model_name = None
|
|
203
|
+
|
|
204
|
+
# Check for explicit provider/model syntax
|
|
205
|
+
if model_identifier in AppConfig.tti_provider_map and "/" in model_identifier:
|
|
206
|
+
provider_class = AppConfig.tti_provider_map[model_identifier]
|
|
207
|
+
_, model_name = model_identifier.split("/", 1)
|
|
208
|
+
elif "/" in model_identifier:
|
|
209
|
+
provider_name, model_name = model_identifier.split("/", 1)
|
|
210
|
+
provider_class = AppConfig.tti_provider_map.get(provider_name)
|
|
211
|
+
else:
|
|
212
|
+
provider_class = AppConfig.tti_provider_map.get(AppConfig.default_tti_provider)
|
|
213
|
+
model_name = model_identifier
|
|
214
|
+
|
|
215
|
+
if not provider_class:
|
|
216
|
+
available_providers = list(set(v.__name__ for v in AppConfig.tti_provider_map.values()))
|
|
217
|
+
raise APIError(
|
|
218
|
+
f"TTI Provider for model '{model_identifier}' not found. Available TTI providers: {available_providers}",
|
|
219
|
+
HTTP_404_NOT_FOUND,
|
|
220
|
+
"model_not_found",
|
|
221
|
+
param="model"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Validate model availability
|
|
225
|
+
if hasattr(provider_class, "AVAILABLE_MODELS") and model_name is not None:
|
|
226
|
+
available = getattr(provider_class, "AVAILABLE_MODELS", None)
|
|
227
|
+
# If it's a property, get from instance
|
|
228
|
+
if isinstance(available, property):
|
|
229
|
+
try:
|
|
230
|
+
available = getattr(provider_class(), "AVAILABLE_MODELS", [])
|
|
231
|
+
except Exception:
|
|
232
|
+
available = []
|
|
233
|
+
# If still not iterable, fallback to empty list
|
|
234
|
+
if not isinstance(available, (list, tuple, set)):
|
|
235
|
+
available = list(available) if available and hasattr(available, "__iter__") and not isinstance(available, str) else []
|
|
236
|
+
if available and model_name not in available:
|
|
237
|
+
raise APIError(
|
|
238
|
+
f"Model '{model_name}' not supported by TTI provider '{provider_class.__name__}'. Available models: {available}",
|
|
239
|
+
HTTP_404_NOT_FOUND,
|
|
240
|
+
"model_not_found",
|
|
241
|
+
param="model"
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
return provider_class, model_name
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def get_provider_instance(provider_class: Any):
|
|
248
|
+
"""Return a cached instance of the provider, creating it if necessary."""
|
|
249
|
+
key = provider_class.__name__
|
|
250
|
+
instance = provider_instances.get(key)
|
|
251
|
+
if instance is None:
|
|
252
|
+
try:
|
|
253
|
+
instance = provider_class()
|
|
254
|
+
except TypeError as e:
|
|
255
|
+
# Handle abstract class instantiation error
|
|
256
|
+
if "abstract class" in str(e):
|
|
257
|
+
from .exceptions import APIError
|
|
258
|
+
raise APIError(
|
|
259
|
+
f"Provider misconfiguration: Cannot instantiate abstract class '{provider_class.__name__}'. Please check the provider implementation.",
|
|
260
|
+
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
261
|
+
"provider_error"
|
|
262
|
+
)
|
|
263
|
+
raise
|
|
264
|
+
provider_instances[key] = instance
|
|
265
|
+
return instance
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def get_tti_provider_instance(provider_class: Any):
|
|
269
|
+
"""Return a cached instance of the TTI provider, creating it if needed."""
|
|
270
|
+
key = provider_class.__name__
|
|
271
|
+
instance = tti_provider_instances.get(key)
|
|
272
|
+
if instance is None:
|
|
273
|
+
try:
|
|
274
|
+
instance = provider_class()
|
|
275
|
+
except TypeError as e:
|
|
276
|
+
# Handle abstract class instantiation error
|
|
277
|
+
if "abstract class" in str(e):
|
|
278
|
+
from .exceptions import APIError
|
|
279
|
+
raise APIError(
|
|
280
|
+
f"Provider misconfiguration: Cannot instantiate abstract class '{provider_class.__name__}'. Please check the provider implementation.",
|
|
281
|
+
HTTP_500_INTERNAL_SERVER_ERROR,
|
|
282
|
+
"provider_error",
|
|
283
|
+
)
|
|
284
|
+
raise
|
|
285
|
+
tti_provider_instances[key] = instance
|
|
286
|
+
return instance
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Pydantic models for API requests and responses.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Literal, Optional, Union
|
|
6
|
+
|
|
7
|
+
try:
|
|
8
|
+
from webscout.Provider.OPENAI.pydantic_imports import BaseModel, Field
|
|
9
|
+
except ImportError:
|
|
10
|
+
from pydantic import BaseModel, Field
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Define Pydantic models for multimodal content parts, aligning with OpenAI's API
|
|
14
|
+
class TextPart(BaseModel):
|
|
15
|
+
"""Text content part for multimodal messages."""
|
|
16
|
+
type: Literal["text"]
|
|
17
|
+
text: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ImageURL(BaseModel):
|
|
21
|
+
"""Image URL configuration for multimodal messages."""
|
|
22
|
+
url: str # Can be http(s) or data URI
|
|
23
|
+
detail: Optional[Literal["auto", "low", "high"]] = Field(
|
|
24
|
+
"auto",
|
|
25
|
+
description="Specifies the detail level of the image."
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class ImagePart(BaseModel):
|
|
30
|
+
"""Image content part for multimodal messages."""
|
|
31
|
+
type: Literal["image_url"]
|
|
32
|
+
image_url: ImageURL
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
MessageContentParts = Union[TextPart, ImagePart]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Message(BaseModel):
|
|
39
|
+
"""Chat message model compatible with OpenAI API."""
|
|
40
|
+
role: Literal["system", "user", "assistant", "function", "tool"]
|
|
41
|
+
content: Optional[Union[str, List[MessageContentParts]]] = Field(
|
|
42
|
+
None,
|
|
43
|
+
description="The content of the message. Can be a string, a list of content parts (for multimodal), or null."
|
|
44
|
+
)
|
|
45
|
+
name: Optional[str] = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ChatCompletionRequest(BaseModel):
|
|
49
|
+
"""Request model for chat completions."""
|
|
50
|
+
model: str = Field(..., description="ID of the model to use. See the model endpoint for the available models.")
|
|
51
|
+
messages: List[Message] = Field(..., description="A list of messages comprising the conversation so far.")
|
|
52
|
+
temperature: Optional[float] = Field(None, description="What sampling temperature to use, between 0 and 2.")
|
|
53
|
+
top_p: Optional[float] = Field(None, description="An alternative to sampling with temperature, called nucleus sampling.")
|
|
54
|
+
n: Optional[int] = Field(1, description="How many chat completion choices to generate for each input message.")
|
|
55
|
+
stream: Optional[bool] = Field(False, description="If set, partial message deltas will be sent, like in ChatGPT.")
|
|
56
|
+
max_tokens: Optional[int] = Field(None, description="The maximum number of tokens to generate in the chat completion.")
|
|
57
|
+
presence_penalty: Optional[float] = Field(None, description="Number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the text so far.")
|
|
58
|
+
frequency_penalty: Optional[float] = Field(None, description="Number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency in the text so far.")
|
|
59
|
+
logit_bias: Optional[Dict[str, float]] = Field(None, description="Modify the likelihood of specified tokens appearing in the completion.")
|
|
60
|
+
user: Optional[str] = Field(None, description="A unique identifier representing your end-user.")
|
|
61
|
+
stop: Optional[Union[str, List[str]]] = Field(None, description="Up to 4 sequences where the API will stop generating further tokens.")
|
|
62
|
+
|
|
63
|
+
class Config:
|
|
64
|
+
extra = "ignore"
|
|
65
|
+
schema_extra = {
|
|
66
|
+
"example": {
|
|
67
|
+
"model": "Cloudflare/@cf/meta/llama-4-scout-17b-16e-instruct",
|
|
68
|
+
"messages": [
|
|
69
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
70
|
+
{"role": "user", "content": "Hello, how are you?"}
|
|
71
|
+
],
|
|
72
|
+
"temperature": 0.7,
|
|
73
|
+
"max_tokens": 150,
|
|
74
|
+
"stream": False
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class ImageGenerationRequest(BaseModel):
|
|
80
|
+
"""Request model for OpenAI-compatible image generation endpoint."""
|
|
81
|
+
prompt: str = Field(..., description="A text description of the desired image(s). The maximum length is 1000 characters.")
|
|
82
|
+
model: str = Field(..., description="The model to use for image generation.")
|
|
83
|
+
n: Optional[int] = Field(1, description="The number of images to generate. Must be between 1 and 10.")
|
|
84
|
+
size: Optional[str] = Field("1024x1024", description="The size of the generated images. Must be one of: '256x256', '512x512', or '1024x1024'.")
|
|
85
|
+
response_format: Optional[Literal["url", "b64_json"]] = Field("url", description="The format in which the generated images are returned.")
|
|
86
|
+
user: Optional[str] = Field(None, description="A unique identifier representing your end-user.")
|
|
87
|
+
style: Optional[str] = Field(None, description="Optional style for the image (provider/model-specific).")
|
|
88
|
+
aspect_ratio: Optional[str] = Field(None, description="Optional aspect ratio for the image (provider/model-specific).")
|
|
89
|
+
timeout: Optional[int] = Field(None, description="Optional timeout for the image generation request in seconds.")
|
|
90
|
+
image_format: Optional[str] = Field(None, description="Optional image format (e.g., 'png', 'jpeg').")
|
|
91
|
+
seed: Optional[int] = Field(None, description="Optional random seed for reproducibility.")
|
|
92
|
+
|
|
93
|
+
class Config:
|
|
94
|
+
extra = "ignore"
|
|
95
|
+
schema_extra = {
|
|
96
|
+
"example": {
|
|
97
|
+
"prompt": "A futuristic cityscape at sunset, digital art",
|
|
98
|
+
"model": "PollinationsAI/turbo",
|
|
99
|
+
"n": 1,
|
|
100
|
+
"size": "1024x1024",
|
|
101
|
+
"response_format": "url",
|
|
102
|
+
"user": "user-1234"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ModelInfo(BaseModel):
|
|
108
|
+
"""Model information for the models endpoint."""
|
|
109
|
+
id: str
|
|
110
|
+
object: str = "model"
|
|
111
|
+
created: int
|
|
112
|
+
owned_by: str
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class ModelListResponse(BaseModel):
|
|
116
|
+
"""Response model for the models list endpoint."""
|
|
117
|
+
object: str = "list"
|
|
118
|
+
data: List[ModelInfo]
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ErrorDetail(BaseModel):
|
|
122
|
+
"""Error detail structure compatible with OpenAI API."""
|
|
123
|
+
message: str
|
|
124
|
+
type: str = "server_error"
|
|
125
|
+
param: Optional[str] = None
|
|
126
|
+
code: Optional[str] = None
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class ErrorResponse(BaseModel):
|
|
130
|
+
"""Error response structure compatible with OpenAI API."""
|
|
131
|
+
error: ErrorDetail
|