From 6f1b6ade697fdea436492d67274ae86f8fa6f56d Mon Sep 17 00:00:00 2001 From: Mason Date: Mon, 8 Sep 2025 05:08:41 +0800 Subject: [PATCH] [FEAT]: Model provider SubModel integration --- .vscode/settings.json | 1 + README.md | 1 + docker/.env.example | 4 + .../LLMSelection/SubModelLLMOptions/index.jsx | 154 ++++++++++ frontend/src/hooks/useGetProvidersModels.js | 1 + frontend/src/media/llmprovider/submodel.png | Bin 0 -> 8561 bytes .../GeneralSettings/LLMPreference/index.jsx | 10 + .../Steps/DataHandling/index.jsx | 9 + .../Steps/LLMPreference/index.jsx | 9 + .../AgentConfig/AgentLLMSelection/index.jsx | 1 + locales/README.fa-IR.md | 1 + locales/README.ja-JP.md | 1 + locales/README.tr-TR.md | 1 + locales/README.zh-CN.md | 1 + server/.env.example | 4 + server/endpoints/utils.js | 3 + server/models/systemSettings.js | 4 + server/utils/AiProviders/submodel/index.js | 270 ++++++++++++++++++ server/utils/agents/aibitat/index.js | 2 + .../agents/aibitat/providers/ai-provider.js | 8 + .../utils/agents/aibitat/providers/index.js | 2 + .../agents/aibitat/providers/submodel.js | 115 ++++++++ server/utils/agents/index.js | 10 + server/utils/helpers/customModels.js | 20 ++ server/utils/helpers/index.js | 8 + server/utils/helpers/updateENV.js | 10 + 26 files changed, 650 insertions(+) create mode 100644 frontend/src/components/LLMSelection/SubModelLLMOptions/index.jsx create mode 100644 frontend/src/media/llmprovider/submodel.png create mode 100644 server/utils/AiProviders/submodel/index.js create mode 100644 server/utils/agents/aibitat/providers/submodel.js diff --git a/.vscode/settings.json b/.vscode/settings.json index e6b76c9e9de..7bb6acd469d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -57,6 +57,7 @@ "Weaviate", "XAILLM", "Zilliz" + "SubModel", ], "eslint.experimental.useFlatConfig": true, "docker.languageserver.formatter.ignoreMultilineInstructions": true diff --git a/README.md b/README.md index 34a723859b5..bff0774edcd 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,7 @@ AnythingLLM divides your documents into objects called `workspaces`. A Workspace - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) - [Moonshot AI](https://www.moonshot.ai/) +- [SubModel](https://submodel.ai) **Embedder models:** diff --git a/docker/.env.example b/docker/.env.example index dca22fa0493..7cc37e10349 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -138,6 +138,10 @@ GID='1000' # MOONSHOT_AI_API_KEY='your-moonshot-api-key-here' # MOONSHOT_AI_MODEL_PREF='moonshot-v1-32k' +# LLM_PROVIDER='submodel' +# SUBMODEL_INSTAGEN_ACCESS_KEY='your-submodel-instagen-access-key-here' +# SUBMODEL_MODEL_PREF=deepseek-ai/DeepSeek-V3-0324 + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/frontend/src/components/LLMSelection/SubModelLLMOptions/index.jsx b/frontend/src/components/LLMSelection/SubModelLLMOptions/index.jsx new file mode 100644 index 00000000000..8c6afec26d1 --- /dev/null +++ b/frontend/src/components/LLMSelection/SubModelLLMOptions/index.jsx @@ -0,0 +1,154 @@ +import System from "@/models/system"; +import { useState, useEffect } from "react"; + +export default function SubModelLLMOptions({ settings }) { + return ( +
+
+
+ + +
+ {!settings?.credentialsOnly && ( + + )} +
+
+ ); +} + +function SubModelModelSelection({ settings }) { + const [groupedModels, setGroupedModels] = useState({}); + const [loading, setLoading] = useState(true); + const [selectedModelId, setSelectedModelId] = useState(settings?.SubModelModelPref); + + useEffect(() => { + async function fetchModels() { + setLoading(true); + const { models } = await System.customModels("submodel"); + if (models?.length > 0) { + const modelsByOrganization = models.reduce((acc, model) => { + acc[model.organization] = acc[model.organization] || []; + acc[model.organization].push(model); + return acc; + }, {}); + setGroupedModels(modelsByOrganization); + } + setLoading(false); + } + fetchModels(); + }, []); + + // Update selected model when settings change + useEffect(() => { + setSelectedModelId(settings?.SubModelModelPref); + }, [settings?.SubModelModelPref]); + + if (loading || Object.keys(groupedModels).length === 0) { + return ( +
+ + +
+ ); + } + + return ( +
+ + + + + +
+ ); +} + +function FreeQuotaInfo({ groupedModels, selectedModelId }) { + // Find the currently selected model + const selectedModel = Object.values(groupedModels) + .flat() + .find(model => model.id === selectedModelId); + + // Only show models with free_quota structure + if (!selectedModel?.free_quota) { + return null; + } + + return ( +
+
+
+

+ Free Quota Available +

+
+
+
+ + Daily Tokens: + + + {selectedModel.free_quota.day_token?.toLocaleString() || 'N/A'} + +
+
+ + Daily Requests: + + + {selectedModel.free_quota.day_request?.toLocaleString() || 'N/A'} + +
+
+
+ ); +} diff --git a/frontend/src/hooks/useGetProvidersModels.js b/frontend/src/hooks/useGetProvidersModels.js index 82ef427cfd5..caa08bdafca 100644 --- a/frontend/src/hooks/useGetProvidersModels.js +++ b/frontend/src/hooks/useGetProvidersModels.js @@ -52,6 +52,7 @@ const groupedProviders = [ "novita", "openrouter", "ppio", + "submodel", ]; export default function useGetProviderModels(provider = null) { const [defaultModels, setDefaultModels] = useState([]); diff --git a/frontend/src/media/llmprovider/submodel.png b/frontend/src/media/llmprovider/submodel.png new file mode 100644 index 0000000000000000000000000000000000000000..e54e601b9c82933e36c46eabaa14fba944d500bd GIT binary patch literal 8561 zcmaiZXH-+q^S6qMs31mqM-oZsgc|9BK!UVTqzNh@Qeu>Vln4rZEp!N>BfW?SC?Jt8 zN=qP!6qOdF1yG9A5R$+@zn9Oe=bpXip4~G$d#8PN&P=S0l^OS0k+a8+9pi?Zn;?%J zJC69@dxnF(a)U+I?E1Ujhx;Cd|mp*usagpPc$uTwyO;=_pWVMwf-NTqA_JwifE#2SbQ03*`O*^{v&3$J_Ixd$fML1A-vMctL;b=n_=w}L$ zn0=3#7lxT02!<;%%mC_^xWiE?ce_*NNkTVoeX<+p_ou7(F+c;-48qE=;mB zlj6>V2JNtwCenQ%SFjIkpsOU(_y!p*_AJ3ZMw1;2ZE&O-wL5u>sT5DUIK=SPI}+?? zi1adKJ{`QVW{tZs6U>gzv*l_%P)eg+siW5;S-iu{`?^Q%4lG6dew7WY%aO^JsK$mx zbYenE=wBRJ1SD(TgZa~yDc#NxBr{~14&+PrRi5sSxG?#Km`P?wl5LDKD;BWvK&Iuu zPvy*M%#{9g zz(-_=5*eyNJH?iV!X!ql;St{S2vSWihaaZF4=XJW|2i|p+YaP0J6Wbj?<@}gIWnUR zk0RiQllIJ|Tg()*quERDjNz|A0}mEEy3DHn3H zE(?`Z{H@7#+A>i82RGODH30wViVZ<-EdBVJ9ai+r^WW+yImoH?<+b^Ncdw>@5OS^l zt^R&jkm}CZouBDMv&c0l*3b5L#W|T_hxDxJJ)E$K4Ck?9;zYQKk$vdI>evUrFNR{h zo0mAHEvBvwY4DzPzPda_F48w#7R_=#W7XNIdXFnf95hnod0LkL>H)GXKIiX8bIH#e zK7AZd=tqGk*#GMDgz_d#=YKY6cIQoG65Vnk@jz!Vylxud zoUs^rRB4~CmdFvPKOForHO@)0PN5|uPYbdEQbuM@X@RQdocOF(xtKp5l@N%2ZSFsJ zXEOrJK{VW&ZAP<~t(XI8dXVd6UzuHDT{vPG}cf6ZYrt*n3SQ1m$ zpg=PHhQ0Z%Iy^1xW_hV5-_X>395w7PSicg#*Gjh4gedQ|S|Tdshmt8HJ1)aH1Xg%e za>G-6x>8e1`c!~>ciQ%eBN>Mvgk4?Z63TtUz<|`a(6jQ&XOG-2+|Y3Gx@{=(d%%Au zwOpVBZ`TK|K`vfA`O(XE8F^FVwttXqk$)9ew9rE}{?jL}-HhU`!)TrAJbU;dUl>rE zmK)ZjW}Gcp&dK7CTx!K`h#}uNy&gPAl)X&HuJ)~5m}6bT-QeO+7?AU6UsKo7N5s*% z)kDD$O2CD=^8cVZT|S7hdugfXCu9CJyS^CktNj@ry%{OerSpk%bH#EqQVf*+@rj@oR%#_1y0udtTE7yJ zs%JNdr#->xQobd4_3iK^(u~Umf-t?C=GOwk89@Hc_4*aPliA&&OY%u0F~5n#O?_k! zBehq@LT9mV@fheSSlSdhM*XKI>j}IAe>F#zKs*h%m=J5v^NBC@h1paIPQ`#JQ85?i zP&|owhOKHLsu`%ssFT=~R&s0zb}7F5)ElR~!S;|32%z*HzcGcXyY+JGniZ7L-ch%C z>fk)8wnkBCjcYUV-lhNnb{AJKRcR7-3?=MRwR$xH7=|38?wC!AE$b~)i&}r+LFQMD@rtwA#%g9m)IRuIGoIAeA8DY}^DGH`DLEC#6uC#zECS+WWku>v z!f&bu+M}#)4N-wI!gmoVqbc6q!M+##h!bMw>|@{ngcU9qFdUGS8Daf)Uc7^xnsySg z+t~-tf9?CxL#c%iZIo-g+2g@x&<7_~Xcg%)Zr_(lGnbplGx$E4mj#rbHRi$%wQ15q zJ9mMxG|N^^Gjph4E7xM%m6=$9>Xgt+L@!iQ3NSuoxl_oTV73(mPu|FM$SfzkC)1@L zU>5Qd#L+3bdJ*XRQVMwHZTl|QDJl>s>_x+g~*NS?aZGQ9#_xobv$G|j942tIU5t4ogR zuzm22)_KVUiN0mvQUvZhkLlJz3b4Vy^bNGzRkh8bgIp;;ktYN!%G!L<4AIX##bywv z_VPyMA&Q4DYC&P!%gyd_%k*e||J0|K0K}ml_yo~IdNV_5sn#~ql_b3KW-IfP8uv#? z+}wWC1Ms%)H)Co-HZ%>wH~iIskE9{{RpyC`d`&4HNi70nUY!r4Kdg-J9gC1ulD6Z! z46VfkpqqZcd^XxvG0a8A-=7B?ANLM7B5JxQICY<9{~Q@pgc96j#U(9BTTm(rSeU0l zI_n(zN*az?t`;Cddh}1j8{00HNpT%03nWLkIs5XAtDMEoQ(RvI3+<;=NP;Bi#>0M- z(csDPr#CR=*m77LkoI>1TV2~G{=6y51nXY`>tFC41igcwhLi2iBk_gVMXtKg-|s?l z>J3?_LGIx;VGW|>AcUBG>&yDwS^!oSIPDtPl35OO0iHtmu3EywC$U$+3r1~J=a1@U z`kdd@&5}!}DB;(+&Tb23Ng%gosJvMDv?&OIF#J%-{4&B6DuK+wxLoBdY8!*E7m(_- z`erDFHag#KyXVB*SqrM2bz3{>+0owiQ*J_3U8XCjSZPDoJfEs!4jmCQz^UE)Pawfe z)O=WM5AtG8f>RKY_LY9ag!pUwUwmM07bSrJd#ll8cMmOs^~@NY$pqI%*-gO-77G)1Yp%X(Kj&vogr4;`VNgX~?w#2mh0pg$r z2FtHQQDTE2OiD%D%h7rvxsVfU)%tmM*nZoyIQnsqA8)`P2@2%OTi>DAaUFN?0wAWr z<99jyrOg2B-vOg%bv4~rAmW4cz4Gf^z~oaRahVb!dj-RJ8T?Ja>$X6~^EHHC8e z25op2=3WqWiK^_$O%^^<=TzPpDSjtD`=ivbGN0PlLYSf~jaXAp{|pWH_IOQx*wOF< z`lW_==~v*1Gyc9bed;g9uleY5N5iFVr`Hxb+TVeb#aD-;Uy%w5-Xqgm!NQo31MsPVb`%gq!3_s|%l15d) zSYUE6=J{WW7s_E4+0%?CB5}h;u&b{i5#aqvN9duV%nQtz7RhsmMzIA7H@Ce`-^#Fr zqVKu~_qPT-)v5F8Pj?AH3R_mdQcgb4rrxFr$b$+8TE_ZxiRP?QL*H*1=E%UfPQB81 z%Y|O$FgL8HtyPdCf;Y+7lyG%(u1+`C5Zl7Wg~KP|z2;KIdS~G4ixFh*62%JewpjN7 zIyAiZ-264Tw$y~YB_;*v!xwkH0{pRE<%{ItnoN=F3DBDlkMJ^-uK4tbU~ti9*+*#J zrJ2SAZkbmD)IXx83Z&js$B@g8&LcpJpVrc(FRMQjTH0>SP#YSA^5Vc_Aqi{I}z${h-MiK=WTb#*tmZ%&2?9pd{qYEEDjbmp=D6~ zW{bQkV9fOh2~KGf;jgGqA2=Ef(gz|3q~OGA0RfT~Y1;MB)VTsX6BN!9ogi#*|L;nn zNtrv-+zn%m=@aw`{)uQk>NU7<8SJpe;tjXrA?bd-<7|I^87AQI&~nYnFFPlXr)Dg0 z;au61udnu$s9Ay>`1BG-UXH7BT&l_FoEX8fWXFYp^gIHLy2cHCQmh&z7$okuITGp1 z6M3f;=DGjp;^Ul{JyTrL6?N6gV5Q55hS0lF6O(y>jK*f)^Gku){+Ad(w25!yMgkZJ z4jE_k_CN6Dp%075oY+{%q23s_!Z^yKg^YX!vDUA;kCn|{()KFcr3G9G%w4L15T078+;!JEaud zJFhflV`6F)jjWB-eZACd^MDvuGUev~#ss@PEQSEslC%=ck?+-o(|4`9r)}M*(pe4b zTe@^XpMI0zy_wkxnAR;pMA6kNy(B&mBuJSd?u07_qVXEYUi~R<83Xch9N|7q;4t4t z^YaIQt-Bj?pXxs+CJmPy)VevNX60Q98<-U%P#VCV9p*^|w*%j=X760)Li8Bu-=fkZ zRG4!CT4ON@udfV?orG9oo-K44tcv$n!wtt(4DsfG1|WTQ+AdJSq(j@)R53I_euZ)# z+rM#F3eIaZPLU!Vz!Av8R)QB1H~N=gEm++4NV_)~_Dix_-p=+;ae!!ePFiGWt|l7D zbyf%@00QsMt&OZm6D9I6FS`Jx zwF>0lSsi0*jIJMmPYUrA8}sAcBd2CIo>_+3(21JQPS5b>!#{Qi4v`LH9YBgMEueV(zdBVt6||{#k)xHKf@cjx0s9I{XWy3YonFM9L1o{d}x* z>zy44B5ISbsnx)N_KEPcUhe4y2M5!djNpyjhxd7ai+h^M$Oq`5a?9Mfj3;`8Ay#i& zro}25foVP;*GvsB@uPDunGbKL0N|&e;RcQH!1oFV?f<+7m3dQ(nH&opZRDu9uK!+t z-dUlnR9!gxtn?a9GpSDBXvGT9z_8jHnas@W)AWIT&=LUA9&Z@VJZZC5tbGn;{WIzm z0}3mVfYEIG+;-miGuiR?R8>M&>H3D$hX^PqAAtGZAMkmCR|%r zxa7iFk0!W>1E*TFj!;&T7H@4wDSObcTwFrf4`5-Jv8I|?#~Gw4z)2utoVGRWA!;f; z9O$N%ur*JS%ZScljRh70ll8#IaG2E6cVKdfeFLF7O?HEC7t5)jx0a~*QZ3KZlJpn) zlakbw3KS^Qf*blmQF zFjj}AP0NLLS^dO^Ne;w_T)Xb!A#={MTe$7i(z%B`K-mJA=T@;r?ycwD8kH{ic*Eql zgGjv-DlE-}1a+T6%A(CN7>6TqGGu8WI|j%f7s!9cyha(lhb*jUGlBt)qJd`|2kyMT z8=iOfnUk*-zlBA~*2vH&Ex|K5k*`h0TLY}O_dK)_0tj9Zm&=oXgT=}}J@je6RO*KF zqMAc8{G$jLz7@n=hrrpFaS7PCbRX@WGtxV3 zKRy|9E+8c#A9MP)BiMV-r2|PTzrMoOUgSomL5HDc0vp~Z2<0Sn{)QRA2#M1qnZA(B zeN~Jf&lLbI`(S+l6ZU-1X9eXPxD@YK$4A@S*!G*6!L~s%m6wEa-l*Hxn*+go!9{2O^o-o--2*n!rYJvd1{zebz8%eA z))_NZ)=4jutJgVIZHadpVB7Sfvqg|of~ElUXVi+<_gJkj=VT0Zvop4cmb}~6M?Som zebnID_{7``xSZh`XU~-~ zf$eI(jf9lXYd`L*H}7DNrV9)@AWw4D?BJyE!RHq%v~Fjv9L;Kr%)Pb;e0r=S($;ph zlB$@3+huD)0|G99ueKCf_1pMQQ$zk%` zr45_DJj7W`m_2)gJRnxrh0ZcNsziT|j74MYGm0QZ$12E1b`zSEV0ERHjgzRPe}yKH z5yT0f<0?TKJ{E{|=UQcRVOZ(qDs9)Gnca2O%BP~80+ED5p$hk)Om}u*2i1#)88*eX)nJ;9H@`qwYwyRB;7!#0P^3j z_>9emoUn_l2$2Rwm6#<*=R=YjfdKiZtb<*Re&ySXBNdk{_U(KgOLgEH$el5OhOW39 zOZT|A1RG!jY=b*PqC^cXEG$&^BUL(oV{R7@k8VDi5KACP7I-gro#OJ*T|XraZ>_`` zT7jbC;p?(tIo{D_&nA2QFm*DVrSBd)%X}^lk$oY);ZL7alm&(3GSLOYJ1=HFo%Jo- z>(}YjFnf4sKyt(A8r5Noj@bcnCfB|!7oB@uK_<#^Jq_PHQ4=P9x6Ac?9>=LU?u9t5 za2%SXIIx0}E$ebH$9!REqj-j-^ufp90yWsot<$aR$kGVSNp`v`C^6j+lY{7j9nuxJ z=|U(TEA+A2tIZJSUzswLfpaBuJ3Wf8V*BNjv|!+o^3^TL$5X+;G$(rj+2iZS+leIiyKR1YVHpxuW z@mHHxaA#DaSd~@jiJGM^ulkz|;ojDcbA7333>0xdWVyuj9;MHgp_QlQMsZLdW0N;@L5_FJz%MukFtW?6NnEk@Q2OnGL~8ViZVfy)A$ zZ{?4_DGkVt*?jQ?lZiO|-6Ye-X$xu^alJ)E)GGgG*W%s)g|8k|wgpL)ElA)8LgnS1 z|5TnkrM+=?D3oKh=xfL3ee4w0ArmKYLFpODTY}bmV5lcY48TCkfm-5g0iXET)wv+p zQm<>|T!Q~qz}xMbM?XhDwNaW=E*RXk_hr+)t252Pfo3^H%C%Iz&!unC`?-*a_J~Oe zB@$@-tX)>|0Y>DdobSlZ#Dplxv)>Ac_89LjWCB3zS33^v3a9Y#Tof_iSzY_GAnt{9}~5a1-~) z3IUEq@z}g!2W5s!$(Or$>@H$GFkg*A_Wl8eDnGG?QtaVT5D!v#T`oBLZzAt>e&xywWNm9~WB8YJ9#dAxrV`_8GM3Srs6&{mEDL6qeUh=VvP~ zmn9@p!X({m;6e5Zne7}GMZEnArbasQN@Tm2N|RPBEn_!o5-*?o(%aW}d@BGy7nfS* zrK1XR0Q`o;PX7X{Mfzr`wmlC)n>On-PfmJYKs0lvuAnZ&(L(!3c7Pk{7&(wn!Aa}542&NlkoNdStKG$9$)7{i_ut?mXi0Rq9VmPA zhc(LB4F#(}wx5>+bN%B%gwqZ;Ms&W;dG;=v@~D=DH>*;lN_$qg2i_k_)>uBmBU$N~ zf29leco2wg?8_&u-|?&HpdiUi?yr$!aS>LeVBR-ghbwCdF;J|VTzlXk%`4Y=TtB%;zSEtn)^a>HbR#8+z&g?*J%x<($!=yG zz1q~j31^|KsNV9ND#5;PbLkinbU9A^&BFWg5YNH5M{$d1s0y)LY|q100b9u9a%0rb z=h}IS_V%7-oaOYk34GkKyuO^0NaI<#uynlQJlf0HAMYkqhR5emzbW0*?0vB3#B(>o z7=JX|x}zVP@6`aS!=|kLr?KoIpx$t|b&=x7MJh-Gmz8UBDkn6`{3z^nF?IF!fN|R6 z?H^$F1hOca4HT=dAEiW6A7F$oEosQX0H`FrX>S zxd=?e7!r54@=tbcQYG;`Eg?&ugMa$6Pi#g$EnvG>1^M_wI4nw?*_S+sGf*ns~zWAzwb~U;OLWg3v_pZXI?Z*$K$m3lL6If{ftyl(a|6 ztL}*j6$%94Pf}3uLYqy8w#`@@ ze!(#Ffo(m=LwMHG)5*WhZk2%7(5dLgio7hP!B zDm^*UOI4~w5EXVx+ci16c5VuOKaKC&KA~4xO+HSlit~Y3*PphmoT3heX5Qo#P-L2{gt=`%8Gm_Eb=&){y+CB0!%paTXiM4Spzp$ zq>T94Z_xd#5rv*F73RPGStwKSJ!$&z^yf89gsC&l)${_~|P>D_ww z-(>htv~*@ghn7HQ3s4=hO_uL4w$34?m5^;?pKVFJo}S)qVq%)qD1UeJt8;9mI#4c7PnHDTe`txRgJ-H!f0u(tZE literal 0 HcmV?d00001 diff --git a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx index 9ef12944c2b..78f3d3274a1 100644 --- a/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx +++ b/frontend/src/pages/GeneralSettings/LLMPreference/index.jsx @@ -31,6 +31,7 @@ import APIPieLogo from "@/media/llmprovider/apipie.png"; import XAILogo from "@/media/llmprovider/xai.png"; import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import PPIOLogo from "@/media/llmprovider/ppio.png"; +import SubModelLogo from "@/media/llmprovider/submodel.png"; import DellProAiStudioLogo from "@/media/llmprovider/dpais.png"; import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png"; @@ -61,6 +62,7 @@ import ApiPieLLMOptions from "@/components/LLMSelection/ApiPieOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions"; +import SubModelLLMOptions from "@/components/LLMSelection/SubModelLLMOptions"; import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions"; import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions"; @@ -325,6 +327,14 @@ export const AVAILABLE_LLM_PROVIDERS = [ description: "Run xAI's powerful LLMs like Grok-2 and more.", requiredConfig: ["XAIApiKey", "XAIModelPref"], }, + { + name: "SubModel", + value: "submodel", + logo: SubModelLogo, + options: (settings) => , + description: "Powerful AI Cloud for Startups.", + requiredConfig: ["SubModelLLMAccessKey"], + }, ]; export default function GeneralLLMPreference() { diff --git a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx index a6e4ab025c1..1b11a414ec9 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/DataHandling/index.jsx @@ -36,6 +36,7 @@ import QDrantLogo from "@/media/vectordbs/qdrant.png"; import MilvusLogo from "@/media/vectordbs/milvus.png"; import VoyageAiLogo from "@/media/embeddingprovider/voyageai.png"; import PPIOLogo from "@/media/llmprovider/ppio.png"; +import SubModelLogo from "@/media/llmprovider/submodel.png"; import PGVectorLogo from "@/media/vectordbs/pgvector.png"; import DPAISLogo from "@/media/llmprovider/dpais.png"; import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png"; @@ -252,6 +253,14 @@ export const LLM_SELECTION_PRIVACY = { ], logo: MoonshotAiLogo, }, + submodel: { + name: "SubModel", + description: [ + "Your chats will not be used for training", + "Your prompts and document text used in response creation are visible to SubModel", + ], + logo: SubModelLogo, + }, }; export const VECTOR_DB_PRIVACY = { diff --git a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx index 4ce2745d041..7f52ff5721b 100644 --- a/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx +++ b/frontend/src/pages/OnboardingFlow/Steps/LLMPreference/index.jsx @@ -26,6 +26,7 @@ import XAILogo from "@/media/llmprovider/xai.png"; import NvidiaNimLogo from "@/media/llmprovider/nvidia-nim.png"; import CohereLogo from "@/media/llmprovider/cohere.png"; import PPIOLogo from "@/media/llmprovider/ppio.png"; +import SubModelLogo from "@/media/llmprovider/submodel.png"; import DellProAiStudioLogo from "@/media/llmprovider/dpais.png"; import MoonshotAiLogo from "@/media/llmprovider/moonshotai.png"; @@ -55,6 +56,7 @@ import NovitaLLMOptions from "@/components/LLMSelection/NovitaLLMOptions"; import XAILLMOptions from "@/components/LLMSelection/XAiLLMOptions"; import NvidiaNimOptions from "@/components/LLMSelection/NvidiaNimOptions"; import PPIOLLMOptions from "@/components/LLMSelection/PPIOLLMOptions"; +import SubModelLLMOptions from "@/components/LLMSelection/SubModelLLMOptions"; import DellProAiStudioOptions from "@/components/LLMSelection/DPAISOptions"; import MoonshotAiOptions from "@/components/LLMSelection/MoonshotAiOptions"; @@ -272,6 +274,13 @@ const LLMS = [ options: (settings) => , description: "Run Moonshot AI's powerful LLMs.", }, + { + name: "SubModel", + value: "submodel", + logo: SubModelLogo, + options: (settings) => , + description: "Powerful AI Cloud for Startups.", + }, ]; export default function LLMPreference({ diff --git a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx index 31b7327ba63..aa69e8a1758 100644 --- a/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx +++ b/frontend/src/pages/WorkspaceSettings/AgentConfig/AgentLLMSelection/index.jsx @@ -32,6 +32,7 @@ const ENABLED_PROVIDERS = [ "nvidia-nim", "gemini", "moonshotai", + "submodel", // TODO: More agent support. // "cohere", // Has tool calling and will need to build explicit support // "huggingface" // Can be done but already has issues with no-chat templated. Needs to be tested. diff --git a/locales/README.fa-IR.md b/locales/README.fa-IR.md index c28abf648ae..c5bb4a3fd4b 100644 --- a/locales/README.fa-IR.md +++ b/locales/README.fa-IR.md @@ -104,6 +104,7 @@ AnythingLLM اسناد شما را به اشیایی به نام `workspaces` ت - [xAI](https://x.ai/) - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) +- [SubModel](https://submodel.ai)
diff --git a/locales/README.ja-JP.md b/locales/README.ja-JP.md index 8920b218fda..cd4725aea8a 100644 --- a/locales/README.ja-JP.md +++ b/locales/README.ja-JP.md @@ -91,6 +91,7 @@ AnythingLLMは、ドキュメントを`ワークスペース`と呼ばれるオ - [Cohere](https://cohere.com/) - [KoboldCPP](https://github.com/LostRuins/koboldcpp) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) +- [SubModel](https://submodel.ai) **埋め込みモデル:** diff --git a/locales/README.tr-TR.md b/locales/README.tr-TR.md index 9f539779503..df2577d10e1 100644 --- a/locales/README.tr-TR.md +++ b/locales/README.tr-TR.md @@ -101,6 +101,7 @@ AnythingLLM, belgelerinizi **"çalışma alanları" (workspaces)** adı verilen - [xAI](https://x.ai/) - [Novita AI (chat models)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [PPIO](https://ppinfra.com?utm_source=github_anything-llm) +- [SubModel](https://submodel.ai) **Embedder modelleri:** diff --git a/locales/README.zh-CN.md b/locales/README.zh-CN.md index e3c63225a2f..4312de1c959 100644 --- a/locales/README.zh-CN.md +++ b/locales/README.zh-CN.md @@ -100,6 +100,7 @@ AnythingLLM将您的文档划分为称为`workspaces` (工作区)的对象。工 - [xAI](https://x.ai/) - [Novita AI (聊天模型)](https://novita.ai/model-api/product/llm-api?utm_source=github_anything-llm&utm_medium=github_readme&utm_campaign=link) - [PPIO (聊天模型)](https://ppinfra.com?utm_source=github_anything-llm) +- [SubModel](https://submodel.ai) **支持的嵌入模型:** diff --git a/server/.env.example b/server/.env.example index 0d3d1ecd0e0..6f3bb38043e 100644 --- a/server/.env.example +++ b/server/.env.example @@ -136,6 +136,10 @@ SIG_SALT='salt' # Please generate random string at least 32 chars long. # MOONSHOT_AI_API_KEY='your-moonshot-api-key-here' # MOONSHOT_AI_MODEL_PREF='moonshot-v1-32k' +# LLM_PROVIDER='submodel' +# SUBMODEL_INSTAGEN_ACCESS_KEY='your-submodel-instagen-access-key-here' +# SUBMODEL_MODEL_PREF=deepseek-ai/DeepSeek-V3-0324 + ########################################### ######## Embedding API SElECTION ########## ########################################### diff --git a/server/endpoints/utils.js b/server/endpoints/utils.js index e971290755d..b4c0d9fd586 100644 --- a/server/endpoints/utils.js +++ b/server/endpoints/utils.js @@ -145,6 +145,9 @@ function getModelTag() { case "moonshotai": model = process.env.MOONSHOT_AI_MODEL_PREF; break; + case "submodel": + model = process.env.SUBMODEL_MODEL_PREF; + break; default: model = "--"; break; diff --git a/server/models/systemSettings.js b/server/models/systemSettings.js index f0796be0431..7c7dbdc7b77 100644 --- a/server/models/systemSettings.js +++ b/server/models/systemSettings.js @@ -610,6 +610,10 @@ const SystemSettings = { DellProAiStudioModelPref: process.env.DPAIS_LLM_MODEL_PREF, DellProAiStudioTokenLimit: process.env.DPAIS_LLM_MODEL_TOKEN_LIMIT ?? 4096, + + // SubModel InstaGen Access keys + SubModelLLMAccessKey: !!process.env.SUBMODEL_INSTAGEN_ACCESS_KEY, + SubModelModelPref: process.env.SUBMODEL_MODEL_PREF, }; }, diff --git a/server/utils/AiProviders/submodel/index.js b/server/utils/AiProviders/submodel/index.js new file mode 100644 index 00000000000..80c4a10b61b --- /dev/null +++ b/server/utils/AiProviders/submodel/index.js @@ -0,0 +1,270 @@ +const { NativeEmbedder } = require("../../EmbeddingEngines/native"); +const { + handleDefaultStreamResponseV2, +} = require("../../helpers/chat/responses"); +const fs = require("fs"); +const path = require("path"); +const { safeJsonParse } = require("../../http"); +const { + LLMPerformanceMonitor, +} = require("../../helpers/chat/LLMPerformanceMonitor"); +const cacheFolder = path.resolve( + process.env.STORAGE_DIR + ? path.resolve(process.env.STORAGE_DIR, "models", "submodel") + : path.resolve(__dirname, `../../../storage/models/submodel`) +); + +class SubModelLLM { + constructor(embedder = null, modelPreference = null) { + if (!process.env.SUBMODEL_INSTAGEN_ACCESS_KEY) + throw new Error("No SubModel InstaGen Access key was set."); + + const { OpenAI: OpenAIApi } = require("openai"); + this.basePath = "https://llm.submodel.ai/v1/"; + this.openai = new OpenAIApi({ + baseURL: this.basePath, + apiKey: process.env.SUBMODEL_INSTAGEN_ACCESS_KEY ?? null, + defaultHeaders: { + "HTTP-Referer": "https://anythingllm.com", + "X-API-Source": "anythingllm", + }, + }); + this.model = + modelPreference || + process.env.SUBMODEL_MODEL_PREF || + "deepseek-ai/DeepSeek-V3-0324"; + this.limits = { + history: this.promptWindowLimit() * 0.15, + system: this.promptWindowLimit() * 0.15, + user: this.promptWindowLimit() * 0.7, + }; + + this.embedder = embedder ?? new NativeEmbedder(); + this.defaultTemp = 0.7; + + if (!fs.existsSync(cacheFolder)) + fs.mkdirSync(cacheFolder, { recursive: true }); + this.cacheModelPath = path.resolve(cacheFolder, "models.json"); + this.cacheAtPath = path.resolve(cacheFolder, ".cached_at"); + + this.log(`Loaded with model: ${this.model}`); + } + + log(text, ...args) { + console.log(`\x1b[36m[${this.constructor.name}]\x1b[0m ${text}`, ...args); + } + + async #syncModels() { + if (fs.existsSync(this.cacheModelPath) && !this.#cacheIsStale()) + return false; + + this.log( + "Model cache is not present or stale. Fetching from SubModel API." + ); + await fetchSubModelModels(); + return; + } + + #cacheIsStale() { + const MAX_STALE = 6.048e8; // 1 Week in MS + if (!fs.existsSync(this.cacheAtPath)) return true; + const now = Number(new Date()); + const timestampMs = Number(fs.readFileSync(this.cacheAtPath)); + return now - timestampMs > MAX_STALE; + } + + #appendContext(contextTexts = []) { + if (!contextTexts || !contextTexts.length) return ""; + return ( + "\nContext:\n" + + contextTexts + .map((text, i) => { + return `[CONTEXT ${i}]:\n${text}\n[END CONTEXT ${i}]\n\n`; + }) + .join("") + ); + } + + models() { + if (!fs.existsSync(this.cacheModelPath)) return {}; + return safeJsonParse( + fs.readFileSync(this.cacheModelPath, { encoding: "utf-8" }), + {} + ); + } + + streamingEnabled() { + return "streamGetChatCompletion" in this; + } + + promptWindowLimit() { + const model = this.models()[this.model]; + if (!model) return 4096; // Default to 4096 if we cannot find the model + return model?.maxLength || 4096; + } + + async isValidChatCompletionModel(model = "") { + await this.#syncModels(); + const availableModels = this.models(); + return Object.prototype.hasOwnProperty.call(availableModels, model); + } + + /** + * Generates appropriate content array for a message + attachments. + * @param {{userPrompt:string, attachments: import("../../helpers").Attachment[]}} + * @returns {string|object[]} + */ + #generateContent({ userPrompt, attachments = [] }) { + if (!attachments.length) { + return userPrompt; + } + + const content = [{ type: "text", text: userPrompt }]; + for (let attachment of attachments) { + content.push({ + type: "image_url", + image_url: { + url: attachment.contentString, + detail: "auto", + }, + }); + } + return content.flat(); + } + + constructPrompt({ + systemPrompt = "", + contextTexts = [], + chatHistory = [], + userPrompt = "", + // attachments = [], - not supported + }) { + const prompt = { + role: "system", + content: `${systemPrompt}${this.#appendContext(contextTexts)}`, + }; + return [prompt, ...chatHistory, { role: "user", content: userPrompt }]; + } + + async getChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `SubModel chat: ${this.model} is not valid for chat completion!` + ); + + const result = await LLMPerformanceMonitor.measureAsyncFunction( + this.openai.chat.completions + .create({ + model: this.model, + messages, + temperature, + }) + .catch((e) => { + throw new Error(e.message); + }) + ); + + if ( + !Object.prototype.hasOwnProperty.call(result.output, "choices") || + result.output.choices.length === 0 + ) + return null; + + return { + textResponse: result.output.choices[0].message.content, + metrics: { + prompt_tokens: result.output.usage.prompt_tokens || 0, + completion_tokens: result.output.usage.completion_tokens || 0, + total_tokens: result.output.usage.total_tokens || 0, + outputTps: result.output.usage.completion_tokens / result.duration, + duration: result.duration, + }, + }; + } + + async streamGetChatCompletion(messages = null, { temperature = 0.7 }) { + if (!(await this.isValidChatCompletionModel(this.model))) + throw new Error( + `SubModel chat: ${this.model} is not valid for chat completion!` + ); + + const measuredStreamRequest = await LLMPerformanceMonitor.measureStream( + this.openai.chat.completions.create({ + model: this.model, + stream: true, + messages, + temperature, + }), + messages + ); + return measuredStreamRequest; + } + + handleStream(response, stream, responseProps) { + return handleDefaultStreamResponseV2(response, stream, responseProps); + } + + async embedTextInput(textInput) { + return await this.embedder.embedTextInput(textInput); + } + async embedChunks(textChunks = []) { + return await this.embedder.embedChunks(textChunks); + } + + async compressMessages(promptArgs = {}, rawHistory = []) { + const { messageArrayCompressor } = require("../../helpers/chat"); + const messageArray = this.constructPrompt(promptArgs); + return await messageArrayCompressor(this, messageArray, rawHistory); + } +} + +async function fetchSubModelModels() { + return await fetch(`https://llm.submodel.ai/v1/models`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }) + .then((res) => res.json()) + .then(({ data = [] }) => { + const models = {}; + data.forEach((model) => { + const organization = model.id?.split("/")?.[0] || "SubModel"; + models[model.id] = { + id: model.id, + name: model.display_name || model.title || model.id, + organization, + free_quota: model?.free_quota || null, + pricing: model?.pricing || null, + maxLength: model.context_length || 4096, + }; + }); + + if (!fs.existsSync(cacheFolder)) + fs.mkdirSync(cacheFolder, { recursive: true }); + fs.writeFileSync( + path.resolve(cacheFolder, "models.json"), + JSON.stringify(models), + { + encoding: "utf-8", + } + ); + fs.writeFileSync( + path.resolve(cacheFolder, ".cached_at"), + String(Number(new Date())), + { + encoding: "utf-8", + } + ); + return models; + }) + .catch((e) => { + console.error(e); + return {}; + }); +} + +module.exports = { + SubModelLLM, + fetchSubModelModels, +}; diff --git a/server/utils/agents/aibitat/index.js b/server/utils/agents/aibitat/index.js index d6b22d3a9af..ff4c3ffac03 100644 --- a/server/utils/agents/aibitat/index.js +++ b/server/utils/agents/aibitat/index.js @@ -830,6 +830,8 @@ ${this.getHistory({ to: route.to }) return new Providers.GeminiProvider({ model: config.model }); case "dpais": return new Providers.DellProAiStudioProvider({ model: config.model }); + case "submodel": + return new Providers.SubModelProvider({ model: config.model }); default: throw new Error( `Unknown provider: ${config.provider}. Please use a valid provider.` diff --git a/server/utils/agents/aibitat/providers/ai-provider.js b/server/utils/agents/aibitat/providers/ai-provider.js index 07867e4c6e4..50ee493c318 100644 --- a/server/utils/agents/aibitat/providers/ai-provider.js +++ b/server/utils/agents/aibitat/providers/ai-provider.js @@ -192,6 +192,14 @@ class Provider { apiKey: process.env.MOONSHOT_AI_API_KEY ?? null, ...config, }); + case "submodel": + return new ChatOpenAI({ + configuration: { + baseURL: "https://llm.submodela.ai/v1", + }, + apiKey: process.env.SUBMODEL_INSTAGEN_ACCESS_KEY ?? null, + ...config, + }); // OSS Model Runners // case "anythingllm_ollama": // return new ChatOllama({ diff --git a/server/utils/agents/aibitat/providers/index.js b/server/utils/agents/aibitat/providers/index.js index 859ad9de9d5..3e78428280e 100644 --- a/server/utils/agents/aibitat/providers/index.js +++ b/server/utils/agents/aibitat/providers/index.js @@ -21,6 +21,7 @@ const XAIProvider = require("./xai.js"); const NovitaProvider = require("./novita.js"); const NvidiaNimProvider = require("./nvidiaNim.js"); const PPIOProvider = require("./ppio.js"); +const SubModelProvider = require("./submodel.js"); const GeminiProvider = require("./gemini.js"); const DellProAiStudioProvider = require("./dellProAiStudio.js"); const MoonshotAiProvider = require("./moonshotAi.js"); @@ -49,6 +50,7 @@ module.exports = { NovitaProvider, NvidiaNimProvider, PPIOProvider, + SubModelProvider, GeminiProvider, DellProAiStudioProvider, MoonshotAiProvider, diff --git a/server/utils/agents/aibitat/providers/submodel.js b/server/utils/agents/aibitat/providers/submodel.js new file mode 100644 index 00000000000..f19e0795a68 --- /dev/null +++ b/server/utils/agents/aibitat/providers/submodel.js @@ -0,0 +1,115 @@ +const OpenAI = require("openai"); +const Provider = require("./ai-provider.js"); +const InheritMultiple = require("./helpers/classes.js"); +const UnTooled = require("./helpers/untooled.js"); + +/** + * The agent provider for the SubModel AI provider. + */ +class SubModelProvider extends InheritMultiple([Provider, UnTooled]) { + model; + + constructor(config = {}) { + const { model = "deepseek-ai/DeepSeek-V3-0324" } = config; + super(); + const client = new OpenAI({ + baseURL: "https://llm.submodel.ai/v1/", + apiKey: process.env.SUBMODEL_INSTAGEN_ACCESS_KEY, + maxRetries: 3, + defaultHeaders: { + "HTTP-Referer": "https://anythingllm.com", + "X-API-Source": "anythingllm", + }, + }); + + this._client = client; + this.model = model; + this.verbose = true; + } + + get client() { + return this._client; + } + + async #handleFunctionCallChat({ messages = [] }) { + return await this.client.chat.completions + .create({ + model: this.model, + temperature: 0, + messages, + }) + .then((result) => { + if (!Object.prototype.hasOwnProperty.call(result, "choices")) + throw new Error("SubModel chat: No results!"); + if (result.choices.length === 0) + throw new Error("SubModel chat: No results length!"); + return result.choices[0].message.content; + }) + .catch((_) => { + return null; + }); + } + + /** + * Create a completion based on the received messages. + * + * @param messages A list of messages to send to the API. + * @param functions + * @returns The completion. + */ + async complete(messages, functions = null) { + let completion; + if (functions.length > 0) { + const { toolCall, text } = await this.functionCall( + messages, + functions, + this.#handleFunctionCallChat.bind(this) + ); + + if (toolCall !== null) { + this.providerLog(`Valid tool call found - running ${toolCall.name}.`); + this.deduplicator.trackRun(toolCall.name, toolCall.arguments); + return { + result: null, + functionCall: { + name: toolCall.name, + arguments: toolCall.arguments, + }, + cost: 0, + }; + } + completion = { content: text }; + } + + if (!completion?.content) { + this.providerLog("Will assume chat completion without tool call inputs."); + const response = await this.client.chat.completions.create({ + model: this.model, + messages: this.cleanMsgs(messages), + }); + completion = response.choices[0].message; + } + + // The UnTooled class inherited Deduplicator is mostly useful to prevent the agent + // from calling the exact same function over and over in a loop within a single chat exchange + // _but_ we should enable it to call previously used tools in a new chat interaction. + this.deduplicator.reset("runs"); + return { + result: completion.content, + cost: 0, + }; + } + + /** + * Get the cost of the completion. + * + * @param _usage The completion to get the cost for. + * @returns The cost of the completion. + * Stubbed since SubModel has no cost basis. + */ + getCost() { + return 0; + } +} + +module.exports = SubModelProvider; diff --git a/server/utils/agents/index.js b/server/utils/agents/index.js index 4527ee783b4..d2f273786c0 100644 --- a/server/utils/agents/index.js +++ b/server/utils/agents/index.js @@ -203,6 +203,12 @@ class AgentHandler { if (!process.env.MOONSHOT_AI_MODEL_PREF) throw new Error("Moonshot AI model must be set to use agents."); break; + case "submodel": + if (!process.env.SUBMODEL_INSTAGEN_ACCESS_KEY) + throw new Error( + "SubModel InstaGen Access Key must be provided to use agents." + ); + break; default: throw new Error( @@ -274,6 +280,10 @@ class AgentHandler { return process.env.GEMINI_LLM_MODEL_PREF ?? "gemini-2.0-flash-lite"; case "dpais": return process.env.DPAIS_LLM_MODEL_PREF; + case "submodel": + return ( + process.env.SUBMODEL_MODEL_PREF ?? "deepseek-ai/DeepSeek-V3-0324" + ); default: return null; } diff --git a/server/utils/helpers/customModels.js b/server/utils/helpers/customModels.js index e0a1fb820e4..c6d33106e02 100644 --- a/server/utils/helpers/customModels.js +++ b/server/utils/helpers/customModels.js @@ -8,6 +8,7 @@ const { parseLMStudioBasePath } = require("../AiProviders/lmStudio"); const { parseNvidiaNimBasePath } = require("../AiProviders/nvidiaNim"); const { fetchPPIOModels } = require("../AiProviders/ppio"); const { GeminiLLM } = require("../AiProviders/gemini"); +const { fetchSubModelModels } = require("../AiProviders/submodel"); const SUPPORT_CUSTOM_MODELS = [ "openai", @@ -33,6 +34,7 @@ const SUPPORT_CUSTOM_MODELS = [ "ppio", "dpais", "moonshotai", + "submodel", // Embedding Engines "native-embedder", ]; @@ -88,6 +90,8 @@ async function getCustomModels(provider = "", apiKey = null, basePath = null) { return await getDellProAiStudioModels(basePath); case "moonshotai": return await getMoonshotAiModels(apiKey); + case "submodel": + return await getSubModelModels(apiKey); case "native-embedder": return await getNativeEmbedderModels(); default: @@ -710,6 +714,22 @@ async function getMoonshotAiModels(_apiKey = null) { return { models, error: null }; } +async function getSubModelModels() { + const submodelModels = await fetchSubModelModels(); + if (!Object.keys(submodelModels).length === 0) + return { models: [], error: null }; + const models = Object.values(submodelModels).map((model) => { + return { + id: model.id, + organization: model.organization, + name: model.name, + free_quota: model.free_quota, + pricing: model.pricing, + }; + }); + return { models, error: null }; +} + module.exports = { getCustomModels, SUPPORT_CUSTOM_MODELS, diff --git a/server/utils/helpers/index.js b/server/utils/helpers/index.js index bff2873b526..f58de7bb405 100644 --- a/server/utils/helpers/index.js +++ b/server/utils/helpers/index.js @@ -212,6 +212,9 @@ function getLLMProvider({ provider = null, model = null } = {}) { case "dpais": const { DellProAiStudioLLM } = require("../AiProviders/dellProAiStudio"); return new DellProAiStudioLLM(embedder, model); + case "submodel": + const { SubModelLLM } = require("../AiProviders/submodel"); + return new SubModelLLM(embedder, model); default: throw new Error( `ENV: No valid LLM_PROVIDER value found in environment! Using ${process.env.LLM_PROVIDER}` @@ -362,6 +365,9 @@ function getLLMProviderClass({ provider = null } = {}) { case "moonshotai": const { MoonshotAiLLM } = require("../AiProviders/moonshotAi"); return MoonshotAiLLM; + case "submodel": + const { SubModelLLM } = require("../AiProviders/submodel"); + return SubModelLLM; default: return null; } @@ -430,6 +436,8 @@ function getBaseLLMProviderModel({ provider = null } = {}) { return process.env.DPAIS_LLM_MODEL_PREF; case "moonshotai": return process.env.MOONSHOT_AI_MODEL_PREF; + case "submodel": + return process.env.SUBMODEL_MODEL_PREF; default: return null; } diff --git a/server/utils/helpers/updateENV.js b/server/utils/helpers/updateENV.js index 124b4b4e77f..c841c2d973a 100644 --- a/server/utils/helpers/updateENV.js +++ b/server/utils/helpers/updateENV.js @@ -704,6 +704,15 @@ const KEY_MAPPING = { envKey: "MOONSHOT_AI_MODEL_PREF", checks: [isNotEmpty], }, + // SubModel Options + SubModelKey: { + envKey: "SUBMODEL_INSTAGEN_ACCESS_KEY", + checks: [isNotEmpty], + }, + SubModelModelPref: { + envKey: "SUBMODEL_MODEL_PREF", + checks: [isNotEmpty], + }, }; function isNotEmpty(input = "") { @@ -813,6 +822,7 @@ function supportedLLM(input = "") { "ppio", "dpais", "moonshotai", + "submodel", ].includes(input); return validSelection ? null : `${input} is not a valid LLM provider.`; }