From 869f7f6ff4a8aa4fe5822a4d7ed27c6bec84ade1 Mon Sep 17 00:00:00 2001 From: joshua Date: Sun, 4 Feb 2024 09:49:22 +0000 Subject: [PATCH] GITBOOK-102: No subject --- zh_CN/.gitbook/assets/Feb 4,2.png | Bin 0 -> 83708 bytes zh_CN/SUMMARY.md | 3 + .../model-configuration/customizable-model.md | 300 +++++++++ .../model-configuration/new-provider.md | 588 ++++++++++++++++++ .../model-configuration/predefined-model.md | 197 ++++++ 5 files changed, 1088 insertions(+) create mode 100644 zh_CN/.gitbook/assets/Feb 4,2.png create mode 100644 zh_CN/guides/model-configuration/customizable-model.md create mode 100644 zh_CN/guides/model-configuration/new-provider.md create mode 100644 zh_CN/guides/model-configuration/predefined-model.md diff --git a/zh_CN/.gitbook/assets/Feb 4,2.png b/zh_CN/.gitbook/assets/Feb 4,2.png new file mode 100644 index 0000000000000000000000000000000000000000..d2bd2dbe98a58fe9f26d6916526823d594b62d25 GIT binary patch literal 83708 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

)w2Xr)dvShLxY1d zJE7^7+{-H!)^pLZDdxk08NjIpyuMucF%UT5WF0RS$*lLziPR*4G<31owJA=k$S1evqt%94SIZjl8yN5wN) zj$qfdh<6~Vj#l&D{W6WIBYWk2?AHhSGG6mZ*W*hu`gz=2+1ae8H4Udq@AC8&9km&N z9Tr{u$%AwNwG+Y< zp{88dEjN2wlQPrQaB&jrVYk!&&#HbBCwKJ7WZAjAnYV18n8|VfHivvaxhai9+f4&V(&{& z!g{Bpn2$S_2WZy4#gKgcty!Ns1Ryg)k=bCdQNr_eT#^Tw42M9Cvf1ee;8Ij{SgjtY zs@}%}-YW0E@HV}^<&RSJycCULw=!Kgdv#;`Y%5yGB&VQWSU*&~>0H5Bt&vDYl)(PfOrWUDNZ1k96gv(;i=qKCv=MwSNEX%jQ(QFA17- z!G^f&uU=#A1WE5R-uZ;!V)UJb_r1?iicL#$+Tuyzrx^wc{)*RB9AH5p( zot;V>!MSL^J_ysVw+nILM`x9+P|)D@tviP+BVAVN2wk0z4Z*}5e|SPENkT?M<9vnGtvh6+ijO(wTElvZ~QA@o9Cq=pP?fF3A$XWJ?g6AGHHEA(Db)#`_vUU5C!{F4^ zYfVjKyR#Q0#r*|)U32SU{+zo~rP5cp|D{huk4P&1WlxQDdojL7JfvLTYhqqmwMPxD zOG`^gR(}f`S;LtOx=L=E&|f_t*RZto2+o5Xh0H+@uLEK*L-*v;i?pH)fDvwXpSQrf z5SL##W=4F+IpW9OSLZ(x4pBQfz$KYMKq=6 z{9J}upQa8X*$x4)N9ovT+L1x~|(SDbr@gMDtY!ip|MzR(=&1r`(N zvVf9W^4Ib|@mfu6mZmv}&9a9`HFECI6UrPpmdy$Rh_eo74+N zrQv^1VrBv`R_#l6#qb3^3x3~EqMs2BGqn!?)k|skWd)36k<6q zQqa)02Mc0mV~(j3MExuR$vxn2Q@B0IW)__DdP+b^5u*)?dA7w-Th9_QXxK!lmEZGd& zKPk{SV4CTIy$t)*;6gFP12?GrI%+uHdX$$KCs45bO~;Su2B18?S=w=oi+mra=1n3{ zj8FHJ#<2Uzv-aQmUY!U0`K&jEe6ahH;WmMPvMiw0_cq=9{N+}+x0iNS+M!eBzsv>l zz(^5~&qP8{y8VOkeXxY?VZ=P6qWs48rrwZt|K^;wSoH%&Q^E%lBErMoi}7VSE|(je z>QJca6#*=gULh^3D22>Pw0%WR&E~Z0m1>GGx~4{AexqRXPgT}#%!kFTe(-@r6fHDRRxY}k?js5Tb z;zxLIBGY$WY9XI3{*xAyQGjOf3Mg@GW)4qyC*$NksU?h5VgvI*p|_^UC8V>}ZD z2H3@k!(Iy78GzP#;+wg)Q}VPaAMIY0_h`L~!q~ApRwaQ+YZ}D(CMA>H4%CSl)yoUl z(z5a?ezuxYdYQ}Gi7b2AzH3`M>eF!?aKA(sdYqb!?I}L61P^%6+H0Z*1Hr9r^|f6 zWFK2Ve8s?c`1qvgDHDY`fI=M5(ywJ{(wzZXz$id3X}oi);YI_P0djzd=%GT2uEgKo-gS0F#LrO1z#G~jgj0x z1=hcO{6l#}MFZizxo&cdk;Z^Q0%H#Y1ARbM&GJ=|RsRIMl%}YQb5|h{@auvqdpMUu zas&F(ghV7#x$@h&1v+zB!TVR+uoC&+3V7};_~rO0m*z8a92|~$_=WGk{!8C7FPpbH zb~UACf=`?xt>!EN06u5b+X@!n4Xqb zOa^&heR8x=HVOI%IM}DUh6(8A9#$j6dzL$3_Q&{BA)z4yUZ;<&!QQvPq-F{w4SBpj zUcUu3bhQ;>$4h9b_63aFGv#~RKMHEWW@_geeHTIVUn1ImN$-L8b=b8sGco;>9knfu z5=bPF8L!Y1m(PI^&CLHC$exB%RUt?)>W`lWUEJ}9_XbWHT^~`~ol>D=As;V!fqY=W zT@0Dwwt3(S*;!tGQ@=YAbg_MW-@LDE-{@+M3DiH}`1qSw<(zjg%oeLoj6dsC z0Kydv2=x9g6JkI@Fj(9tFFCU>$B`!wP2?6hDoSnaKW_Ni}f z=^6wX%b$~%KaagSZ!lR)dnc8emW3dE9AdKrT-w&x=hq6LkVVj2Jc|#$MPP3Hts8p` z7&2r|PVAIS`Sp0o79fGcJIz=glk86+9}|<2VR%RZ@R4*O26?*hXm<{NH&H8~dsw?u zC)X1ANy!2hTgH@IF))akgQIQ1cIIe`Cd@x=zA>cRv82#+$1o^Rn`8+Bo$>>dpf}>! zSi7=va$tKuWq5!AV^)icJ+^OPXi|+a66bXh3tx9ZdTH)rzJfTQ_f8$lG&AbiOA4hS zQ~|2g&qCVBX&cmIveRmVK^)4qD#_W~Dmt2ve+0Ad%q<>%;;`VgRLb$G+8lTM)N%il zg^P`SH)l38TgIQoUe8|!983(UCY#T03u+3;(?|T(n{zgSG6I2L7xE3cj3+Xc&6b6b zz^33@5@bdO25RUfQ2vxP`BVD-u5)18Td4Y*S!CeTQDNaC@vr(E?MIf0AgPd=TWY4t zQ4a`g(EvW}h0Rl^2~LAL)Kk(p;FtM8DNr9|4WwObuS;1{PCY>q2f}6&zkQ$l!+l-IN76||_PAd65JpYnQgr{q`x0-a zz}G4v&hHz{Cv^jF@3&=cL3(}tQTXJ;@2^E^5E-Fb5Kbagdzs&aMGE-?oVRsxjvF5>EdlX*kfdefZ( z8dGVr`-qlSuqA`-Cg_;^z9bOaP=^RTdgnp7rsbuNkD`xO-Di?;^(T}#3kC)a2-_y6 z#E0zI!swTB#*2qPr2jjrD~<*7I~dIcOwB72*NfDO3?Z~0))KE?4f86*RqeQlmX`7F zv&~$C^Aa5YW=pXeES7MF+O6%qIuMFrJB|bknLk6o9X@PEThKf8z zSnAWEiNHmS&WEuj@XHAtEE)dU+Ulp)1y_ezMl{jfPP*sLJ@X6P+j9JR>Wlu212O|F z{ARxER5SMQ#jI+K*d;Q&sAyg z2bDQEG*lfDvpi+x;qg7d{o=D!(+eO9?O&<1m<~Mn!-!xU`1C_w@S?vR7`ImT|9i*C z#cpHP#M-JiSx)gh@kO+v+I8T1wxHvySA;_I2KQbN5#0Qh;9!iE={HZ_68ow=AKBRp zau;-2eduS*BPiN^PV3cGwIBOar}b}MJ`(JK<>idRlkefyUs}_%m9|CNTU%kaPb)<% zImddB<1HtZ@^`GEr2I~@gYLHV?aHVY{nsT{e3Ztg#j4XDTO|CJ`rvI0g$m(KmUoan z`lRQIoGaI5-CZ*WY-jO4&2`uChBAZZSh)&oB%}K7pJSKht5C9g-)#EB`f6-AUrd_-l_-H~%wm-v+Bq&^PBJ#7+jr(JFP2$0B#i}_2nN?jqt#ud|<9L~2o@Au!1 zD@#5BCaHr12;wx^auTHTQs4g8Pie5pj7F2Fc*(J9#w_BTFOVoP0GnGykP#c4K9V9> z@i!|QQPHP++VPjw5OgyiR?#)!`_D9Hd)29UKLw7&h35Bw!Kcf_-^=l2QyGjDgKhDr!q3oW+2{?a46n2yz}kW-0Ypp0K~0=(fy7rDRts-Ea&p{ zvxM<-6>+2A4q<(%eJib4BVB>t>*~0^-yepZSUmjRI5Et2+IuDH2k##h==i7gU8L@E zYHbV5#QL6k%aXmPw3KgPdO}K0jxBjB&!Mzp;H^>3pL%C~eSK0gb>gS8g@0Xky9ljy zq4-G8EeQI}6ASva@pjb+$iQ2j_JmTrIlr@eM8cr zi}4eOyc7c^ty8}qz zbxl=4I^+}}*5DAeyQ^U5dq=NGrbx?eb)x32-7|MnkaCXt8jDmfCq*Su{>b+Y*cuqtv9 zuySim3tmk{i3fB=)71-Iu8_$~1$1)n{p$<4Gq(p^%8|ojn2X+-tLM2!4fnC84k0Ru zdJToFVHGTxgWFxR$K(W%ck6sZ@%Fcsbk4HcPkj2l5P> z9f4Bs-}C0Y%h&CYIYJ=!|5L0$xajY6>&WB8X6>>)qk!|WbSz7 zt`!X$)wJ-35fI}zM(@L*u$#btWEMr_2HEkpu7-fq&Ca0|z2?}#mpmV59H*Aixdbdn0_x71`M1v4PPGmTb^8^Kf#DL5zy zs``e3fc$r|CqKH}y(?0gbeMmkG10$v{yu8LQB>|MR^}smUJJNnllU`a!$6zu9tEGFLwC~LvsEO!J~ zU>_z_QIT{#oFSK`;V`f#jPA``R$MRL4Z~Er`iDt~bu{+dEQw70{sU(wWQh^#X;^yT z;fnJOCl6y7_Mo7hcj1%?&2#FJN|$=ExTZYBr$8U+v9r5!v57er0!I%iZ)P*AsspOZ z49ZAM@ZY05FatR@cuB7CGRigCh}w?9uvi)Jwm>5%PW|vJWqavIbCX;)o_QlaWmu@J5um9tZcXJH;mysyLb%8_Pod=l=Ah=G@QRCf~=}(C+?{ zcXV09yynENFD{We-6-Y2k3>#R$#{9i1o-vuomny&In^+XI^Xv?R5f7*0>RAP*xObb6^(Hm%=LF81CVay~tErw#A< z2yJj8nYrvutUWwK;|W`bwX-z#Fx`E}3d%MM1O@W9PXm6aM?J{I!$-9+Z;X|Pv0%%y>=*!ydX5erzrwRHYU2^dyD1)d|D`9;-+m%5a zy~LAlxy zY4POW%cE)~#zdISCl^Vg%{iN&Tki#ihU$_Ac5%ck1OF5(Ffkl4# z_P~Cim_ekesd=%^>5UB^+yF~KFKCm}MQ;8q^9MJ}RX3tf!7`yd|dBAp+r95)E$!jesn^DPNCUp}N0M;w1Uv>G}q ztG&zo*nAfoFZ8IcmDNp!wU3X|b!rM+ypj#ne_Xe#?6aQm&6Z+<90$ivDy zYLhg5`1-Z)eSE^c&GHn^wCk}aPdudUj+qPs2Shwu$}PcBE%N3i!NDQ+2Q4gK7YOgJ zifNbV(4&!7aaqvylf`m2X#}e4*WZ&Yc&+2p5pVddog!$|fn0SGM5`CVVNQ8bKkwb6 zfvttJNzTOew6EZE9468uULfAmKK&HTncQ0rzjIqcj4ft=)N4(Io`|J4rJq`?MlePQ%gup6Pdpe=y z;8^}FKy+&1Uef$KX-~{|qxX)ER0X3!-~ee!a5C5G=BM{NS9qyfzz##tjqmf8SzcR| ze*tC32{T|a+U?jquRZZ7$t26HSp&V%>Z(P$x6MzJCE=0bk)5{sSl#iGi!4`)YEURd zu|Lzk%4su4@j*~BY_YLP5`T^3qR_C+Ea{lHQ5mnEp45V=CKQOc1f2|k0UpVQfGFjo zRw6(g`}w&6PYDKPYc3*QF}Z4plXk2FZ!Zt!Jf zPjAG5{$~IIwMF0XQNDRctc?0#2AnHbl3;$0*N=P2$Y{{B&$jhUe!_ce_X`&PiBF1; zGBrOx=4gcU-4Vpp#4d1P0C)R<@TNs6+Yq?PBm+l21ox!58`(9aJ zK+t8)!B1LF)#h?}D?Z=^yXl<_j&d0qpO#`KZ&|SQSkVHx7&8ZhmBtnS&gDmqgUnNP z2lH0o=~vpEKJq@*!A2?~$yaIT3#Ko>PGqyz(hxp=jMq1JQ~oO<>{B(dD?bVe-SlLn z%u{loZ-8M%cpx@KqH<^V*Vmc5*_%{-rAaI``khGU?&U=lp9VoBZ;~6nbe;RBpLl%Q zoOR9Aujz?CViKbN4Uf*&&4HJ_N0jst4z$Eh$gm)jNLgb1OB?}dfgv!LJP6xMmpHX& zL=wrTy#^@Ky8h~sCA$hf0m0CW#6{y(yng6&0V^Fw91t0|U;hXE1P^H9G@jjSA9DkG z+NxjE$s#cTD>u2t-CbP1yzVar+c0f4fnAr)0K-0$IR_1jp3&+4-2C6Mb9+ehZK1XpeM>|@)f33aB+uTJc{p3N$HEHC#6kQ0=^k%Ob|aqs}m)vdVOo9{w} z37G>gec7 zyv!g%;^LjFt79*XMos2j=pOuh^YRDkzrjvo(*;%T?Y16~(sKSD3VRSDFuJ~_laFx- z^a!hTih-F9SBFFczJdqqxx6BMPVq>$%_SiQ6A+4jYjLFOcnrsWr~7I-sf(0VXG>4y&t?U7k!6FK6o&6FfLE=r zV#X~*3D`>GtXJWd+*MPhj?HsT_EJyBi~OJf2FmD z!^6lPGj*+K7rth2jx4+3Sc#|VFzpZ@t|+~K zA^G1xDK09L(a@fo2yF_9!OaHd8>9oQJ~xi=k(lN9R}%~Bu~GE7LEs17Y=bpSc}90z zca($Z=o_=(m3dliE@4q%F2-;81Hsbi0Ry+=b zaUmM_-;w?!8?KpZYn_=Khlej{ulHuSd4$`5MB%+7CxI;R{)+WJ zW(Su%EYN{Fg3_+cTo+w-uahO#gl=7auwi$G=(us-I=qk4+}01mfI zaWw)^)-tc>gl(e;o-kGI=?@MY!k3m)|0hT4YhNV_PGOrY2R_x4%ldfucNd7_p_p&k z+<NnVK zgtr@7+>`vxNRyeK=NmLmb?WhLpNwPQ-Frv+stJ3c^3H)X(eV<3-Yq*XX>f@Jq)~A3 zvT|K-YpGdM{zj-o#EaQ|SM}}0#Yp<_uQTm2DjP-0Alz?EKr13i7yd_|gO`g-3hcJh z&ns>kK_M4W)<8O)+EM&27&WT&ZOrSJNZ5FkqmM@CX>X(~31YNlaho@N;Ngu!45lW4 zLZt&A+As7nfZ}%ys4X|W_lKdAN)hs=rqtjD>DM{=NTuFj0HpfXX zl!~AI$rHz2tmLH(kc~Rw2K?j*&VUUr_Wq&8Hrvd zr=*nR+yLa1N%+HrcZ|uMX|ay?)L*l~lZ5TVL8du<`)dy2t|BK;Lmx+8?R^&oTgr9B}O}I-=u57Ixe@KJm`Ntb3=F~`n`C_8_DfA zcQQrt2!~!lW)HIMoMAah9K@iw#Pc1zSAYL@4ergjr^qu#B#C$jAgpxwRl9(&K50FtHP)_fHkso}w#?)GlEW-4F2v>@12DPD_Q4T2gy!_3Up+f#q4uyX z$E7ai!E`%d@`k?%obD|2Re3FtrZE>AVB)5j$!+U)Fb6SwXXSy)=pQR0F) zHW&=C(3U6Sadc`7O!hQ-rFWT(Z+y+6$%3dYk-D#q<$2G?iyU8m8O=>7%X{&VIq*BZ z!m6LRrm{A%Z$iS!U7ocY)S;Y|B|=i5Bb&oV8m-6>H9JpsgsxUi)olUEW$m{2EEDWy zCuVM9UQ4Z*;4n2VzDw-1TmSCn&-)H4D3v1YLmsP$&LxAO^ex-}A#Kjz9 z=Xt}`T_B{;R)+DbL}tlE98X{WxNAh!#LrF_&L!e(wWQ;{a}+D=J)BPT@XVaDQ+bHq z$R_{%a&)4Y%Gd8Mf_CmCfHzL5Wm1(`Ml@S^|McO>rNww{C@?b=3uXS)8|3Rvs%iAT z`KZZ;(=VlEWtA$QZx)&n?^H9!{`-rsa(<7KiRw&8Ov$#9EQxmn7*jm1PanGDI30KJ z>EL!7%a$`Q?&AOb5yHdm=K_q*mzGXDv@Sc7=tbW!J99sP!+ezgk?-v1U7iR^V z{xhhmZHpR3LBT*$5d?`!k_?h1s^lCOIY`bRIfJ5tfPiEH0m-mPk|m1>i;U!)Ge`!J zd^>xebMCFWRqw~EdR6boH-A{0t;{vom}B(Ndh4xo4fT+jGe#`z##o;=_e{mxr`tu_TQC`GXlzt+zjt^ejo%NXbBpfE0U5F;WZwOu%a| z@b_>(hZq%$U3`B4m_!6MVRGB;5DCaI9km`s$T_a7v!#&z^@lLVaGYll;ZR-8zoJ!) zV=rDJa~p$3+Gu?V7n*+_3qO`FR$Dl2?f^wmV5X!N$6EZXVhynS*lhwLJ_|Fc5bw`C z;I*H8rc4OcvR_O|f6yopnLv7|t8sHYUARy8DErd!5^ec>$R)LzDhtzO_GfOhSmX#Y ziL~ambDAj?)vfaMnX(4^X0Bx}09K$MB8`QgG?(%K;ZlzkXBmQTEan%YFi?v zxWBlxp{#{r-2FEp#h4)P@DR-EIJJOrRhr1=HC7H@}$hP zW1Uzr%{eBPcV-gP@0;d6kj5t+Dq3nUC;cqwlx)HQeZTZXtopN^qapb^HNoz`?MiJR zryl3ApSdizg)08yW&e3XN%18sD*-5jN(LGNrFd4fp@0`aapiA^+cYqX=h19*8)CWh zJnQ$XPJHyOkaJ-B7Z~Efw-$`M!RW~=v9@K;cG8K6)QR6&tsyX{d*X?}VVk$6O5cLnW9yrTnL9=E}3l0C%wnku^B z*wBj7uyGc3w`uDv!WVz*u4J;z7!mMy?izN5t+BSc`0mSBS0NF)?)TRi5FvJF^6+6% zRZ;Sj=`obS#o|_9?^Jtc$FTeIt2zq=d;qqFgh-0!*n%02+0TY%!BGYE&Q&!{12p^@7C)5<=xJaOC593s&=~Ojixt*LczfDP9 zTEUz#cCpqh zH+mv3j|)|mLj8s|7@NfhEt94NA82IXUO}(0(eEkg{-cH3_-!#yd0y8bP89*Q-l(4C zqy17g;`=Tc!ovkR^53^WKmfEqaiNK{d0}CoQw-`fw~{DABz{01ldyHsS9#PM?)T&S z_uByWolb3C+*xMTHR{MZaAsToS;lgFa{YMRc}Jaq_fF~h*@PYp-)N+=&@QO#&;CBrt#V8{u+~rp(GD@HwwS!(C(eKQU~v0_;}aUK9~=d=HFSz7`QgIJ zg}-f_U})I+I9kFl_Osp3>Saog`dDt%Tza3*7N~1V-ngO`Xy_C8lSv56K$o}GYH#Z> zUsIoLXO|@Y4@uArRm&HMJKIcFNr@L0+MIw^7!2_&JI5Nq1`MSKi+sJP?)Zu|$kC;y z;%UWd4GlIHo$ZbTTOcx}eh(W))S*U3Fe<%Hj?P3*9~+Q8>6Z0!E4E#i;2+bWTuZ+e zR&fsCI*9LNsWJEV_n$p&l#ewPQ#L9L5-1vela}*7=9O%|`^}@;sX(i^>FH<0c<)AP z9mrX_Lcy-FYU3YBKk+#h1T!l^ApT?aHh|FidZqZ|B^Fjz4~TC7mmTc}=2hUQeYGCu zfvh`eEWyh0$)ksddxs*6GenXMRhRdk4ErtCc%P0M0(Vb#`ulAWufwb0>T+^( z)2ebK>dzlS+mSK=x81Hr2q&-g%d&%ae}w7Hlpa|nrYN?1ZA7xICAAjT3RU@0ohBeI z0thnBUuDE%YQ1%j|9p1$%boT?Onb_k??$S8I15-p4MKiQj&bK{4)%BTO!=goH$KdY zWKc*Jd_yBmOz44>qkE%21c@Y|4cmX*rc8PYZJi1W^uyaLx=8;PozHG<7hOzlIc>Dq z25Sc;R&D~ryStGsY-T6tQ&zujRM#TAvw5{$a|!GDanUJ0>2SeA=M+KH2Q%3wXQ2>N7DEvJ zXha+JM%Npb&f)-$$i2*$*Lg;Dlb`3lWe)y()-7~4A;T{l0szZpH3brv2%};tBaX)G z6!N*uI)U7xyodUha`!M^FJ(!bhcp}vGxbFULT9HL1bPc?oVxX%Niaq0!;njocj{BR zDD(#uA;mBxUf;*#7BERt+C3zx4C@ak&j@5`!&3M{ZO1PY4UobS7H}sX{FaYohxu%(=B;m5YyPhcT<{SP}*4CUCGh65PXyxZ2z9HDfgq2yGR}^_ffVDeh z&6ZAY$2fACoUOk)u|4~o%6LVHh05MWomg&UBA&gCW!I;k{wV^j!da%aTA%J-Swx0; z;sd5Q^6ApXMnk*9gZ^P&F@uZC7;aXIqNzChOU$Uy#&>8Yp-vWgD zy^Psgf9q-+gCxGy<~ofG)gkhOe21j`1v7?tL{M|uPov)J6fn3RB+FqwvAN)Lin#iE zbfA9ogyk=8jMshUcd?r~+eFPD+{O!p%D1>OfwLZTEIOR`5yC=W4BuDi7)G#+IIiY`;Vc!9Koza05V* zS*2GchMfdLB}h;Q7WWOf$&vj)<%=dW^$i~3lt3l+S2WTg$K=T}Sx{ugo~pmy7A2L{ zyBT$l;5Nts$2}kve~qfa@$ctu@e#QBBY%CgZ{nwQQSE!w{Os?ekcrKWWra)&7dC)& z_wzNo!!~qA>P@efmk-{ung5&(N;=#S85_Cpk3$<)Fw?LdJ0BNU_H+?yJu|Nq1hn}_ zicRmH==;1rt{%Id#cZlYM}}4Gwyf@~hiX|`D<{q9(}EI&q0w%#u_v_EU_#?hW?^+O z=Q5kavcfcVQEbn0dU<9L)HwZ7>5eO{4zSqNDIHODsD8}@_Z(z!u}g=UN+WhYKCjhLb z72nA7tOgO3yYPyP7lRi=IF#3clHhy?16?cjvzJ3ZjRrkG5-o;%L5qh7nxRvb(v;{g zY4D79@i3vvAs!-+p)98PCBsABh5NF3Zet8_iB^Ei{QDOJs!*2+jO`&k))9mp{Nn%t zF#Wyi3sFYfhJa5-DX>YJpG7jO5Qg;k_O=|C=Xp46DL@O+Gy1C=8z3E(H#IeGo~p_w7=TRc}+S)y!&!V2o@NJ+{LEnJULSQm_n zQk__(ocTQ}d)cB$%4-jtx(Jh?S3Qc>muv{;<(g^VHz4NzOG%`1Ix(&FXWE@9_YG5i zfiVl4d`SZFYmyRerX@hiNH9hz_D>A7W`(X_B3XG}vAqKWK&o+!jE-4&9rZyoVV7vs zCV8FLZfEDUDDk2DcR@UG%Z3~gKY3VMTIl#(cX5K$j*G)p5ro-Z9@l#j;E=p-xXveqZV!+Vug>=PRyPIsn zR4F~w@2?E*@IflMH(XztLS!%^PETwy+-PbCM5(R;$u2GOY+CugTuuo zUiU%U4n&~lid#i12{F(ikn0Iv1R)J8Yby&oyPr-5{+#$X`-TEaCnf&N1sLm07!14B zg^ZcB8T%G%s<+{NJo*s0V$Z;CONgKMN`Gn3*B#JK=FBvOoOR*JUxh;euz z3hM{Go{JqdW&`(s%m(I(cK*(8b#^I(!h6KaPeZZyuAGmi2z8X0Wjq&6MS~lfL6iWl z6u2tyyc-!lNc@2A){M&*ESNm8T+EEp(IF5Q)bHt>?+2mB(uqd<6R+k@zXA=n3~Tb>t4I2leJmw1Sn?=^X$ zEK3z;D@ey=Os`7glgIeK+KN{^M`)l{SM^~~$U`7UPW<}%<;+`{4T8|xKWia>eT1P( ze;ecV^HeF0nE3Jy=hB^Iz46>aWLgC>SQMqe{`hfFR?K60>}M~t3hMN4k#E@ko+^$Z zALlL2&-3ZFo^v1I`I=S}B1Q9ddJ9zBdrqcukus8AOw&h0>z6e?7=_Hv#|H$(X7xgB@Sq0w$Aa4_{H3aM!b2^y9u0paYD!{c|_b8Zgk)TPO~IQ2U=ccR-(emh=y z@)t1XzeE>nhZHL-2K3rmC@vSdrst8gCR)#Y{1>EQ@!9IWCN^BS`9@V&lh{peE{6N< z-<>*Z4g??7KQYnTa87iAO*&lExV+cp@xiFL6>zA(;UcH15W~w}%-n7^x(QvTc2+Nz z|FE2P+h5>nlDm%W?{*_3M4@mwsd3`&f_JGUja{kQ!nq2)>$* z{)4#%%N4$VF^+LXm}>UHtfZ8z2(I(wg9w?PR>Pn)DO<$n7vC>BqmDriwA-Y&!!-X9 zPNVp%NN=SQa94sX8k?hQm^ zwfgp^$M)wvUo8`WcmW{ik@pk>#v&H{0GSvtdS24M9bRu-KS;}Izbc+u#g1dV48!X&8}RB7(Q(p*xBq_oAE5&A%<8P0xV20`(S}f z3h&+wd@s@TRx~yKaF`uOWff;6Y|dX2ar4?$=!Xlm?3vV^iods$!>C{6Hsocgb??X< zy+AkFvOQRW>Q;fz!ZeE$| zeEX^?wFz}J3vfqZimw7sl<9C-!Rb`P*>sS2`Yrdq(TY$z;^<&nSf~t3s2oA6TwI1LGEvG(1ce(WT;1!qDfMUGAuRMr!FU|tZ!8bF#ZM&E(sL_tynlM_lS;Ap@+Z$HkSx@8sSSTm!mFB5eA zUq2P6J0Im91aQLJ3FwhcTygr)r zM)5TQ7vJ7SWn_X@h0wR6aTyRY$?%lf?TEYkf1v2j?`z)0BG0_F_9eR}BrMDo2tt>N zyt%~`TI%b@mH+6zN#5Kv-~rc0U~J4l4G7n})jgt-Kp z^;QIkmaRje(omAz=tlKh%{by~tbGtu(;B`{n)0;2or`Cn7*gTOiszF$`WsYGIX^zo z!3hsbjdOu2rt8K;`w$DjJ9gZY9W~o0Th~x&^nFSODD0XFWyTnH$aSB%o#V&1FXz-1 z#Kaj)$U8aBQ%8NigcMoBYLg`qW;9_O;1N~shcH(g;%|E?N53OP+zW=?by;4G@UDMv zNRoq;p&|ahwZ|Il_5sQzX}40WPB`2J*QoD`I^Ds0@~+?=bXAvgJIQKJ3Ec`WrJFwF zpuI^zEA?RJ5k-hH;LxfoyY6IcDJXd=>BzS|%T4PC_@Qrea|_e*Nj0;A(&%%L{%L}~ z4|QTqVS>xn;hmd-^l{b<>-@pY>B!)_aT89RQZ%(Q#)=&x{P-o$jC$!!^-vu0YT?0> z2=iur;Ku`1z1284O^Th^-Q;creK3Sk_s;F6CyUl7a!aeedE~_by8LqESiap9#pZb! zR&ZI4LI(b8OC_NC85TFgfc}tTx|ze-gB(R~5kVqgj|{o&zN4yoen-Clw)l{FwLc&F;&lfjwIr4i9(1nw#$t(Z|AVv~EdB81)RLY3J=qf)-Ox{$S~_mxq3H#_FEOXQu@9FWW*U+I}SBvAMbW-}=JU zlyAKx{qPSDv;Nz=nq>mzw;yluxCFiTm#a5%GHo@XvGrL@b_>4o>{hK z!M8x8qQlOdsRGOTLgy$pxG_$C{(vk?W~C|bXL8qTdZmb)xNZhMSNO;#ob>1h<{M^% zq3+7VhOqOuD)EXT&>+H8EwH2Rzwm0hr#;yYYI#B8O|_>#?2LL_sqb=^FoRG&G;1|- zu@H!xi63%6^G~3QwMPGdrpU6da7tW7h1t~91&MGRRJ5?DMx%a1I&I$b%VoeUmUA0 zyKlDqwi%#7_VPJUExqj|dP%kzSJ1tMi#QQVeGtoke&QFzhDVw1_u}@emX>R=E-o|5 zgP4TVV#?m%MaM2t7>7IJ%k}*Dct+zq?_k&PJ}!BJ)AaQ4Xxl>wYG^b;ie!nJ6+S&& zl@f(7HT*}HcWy&U>M1n+ZRt&sXOa(D)+5|yrZ4}Hm%j-zT^3Ol>=fX`H9n8l?{6 z!b>rjp7df& zKISf*{w7aDVm963MMQHal^(0BEA+Hr`mgCF9cZGyABYVDLu&nFW54NqFZVaaRV~*c z8HDmDnXER#;Ak0BnM@90gCM2viuA-!BTTazl8JVO1Xx@ON$DVRuqk{hY#>%d)2xPg zcn^;)STG9$Aq7cEQZHA#`Q?Vc@4X-PT)xKBK`-E-d_`s_c6Ylbkdm@9zbP)hR6T0B ztaNbjt20yw!{I3QTy*3ugc_H}l3WKGDm|>zfg>tYmq*HUmjH|8^{Y4laVj~;@xqA` zKgGU-QF~4vB}Z2>Rqo&Uh=_+r6eN*|jnVS8{Kp8xABCt%A3-(>w?iXsr9MM0Lx;Hl z81LbNH_hG}oqX<8U>QA9sxlWU&rwiLRg9L?K8NuI*q?n|z)-#|{`#_LgfF37?_=U_&pd8sV?zdB9%YQ3 z@cC13A{#-(Wp;Bsa+f^7P>DJAvZRgJuAX!UrUAWfcun>j+c&lXst)g-2idSH*020c zWr$q)>*`J*nI@&oSn>`(lD<3N!EK&%nXfukpx!Wnx_sfMEzscZ?w{PaPA+n5c(1*A zaTU_Dv#X+~+ahQ8DSe$^0fIn%<4U8_#~0tXTH`&{Y{uieJ1)l{TDEJ)zoh?3TGY^4 zIW>j~D@c6bY@{fk`UChRZ`U0T7acZd<;xu7;aLim#D>X59*g5EMa$TvG13QrplZ97 zgCU2a3xM7Qy3CTW!kAvB;-Cj37~aCqIr}C{bjqCmsZg>y2yNDr*>&vjqDjAfb-kKI ztTy;*>vdahDqacZ<44SPidveAn)NZSK2d5TY5A?hhbm-ken)FgKm5`fLh8*QsVnTM z&g`|+!*WC6#RbA+4B9HnRNkM`QDI%dJ#Yyx?}=LJnhs~~z~)(Xz5K*@Qc3=?RXZZg z%hDhrW`V9mKW^^ zPA$H2@C{P{dNXf`E9VsDE$Z0U%g~U`4Xio=!z)CMPS%JqNSz{aR9)S~|23LC?g8}H zmw0PEvakQcVLiNmI|U`%b7^k!epUrGFa_ndax_N=v1@X1m*%9s>VZs!oT1Ju(y<)G(a`Yn49)NBDOkY^5-I<=AGYLW#|jm} z{@rb_$MTYE+WqYorf*6dG>-d9^5cD=DiMjHrscYjr-lj4AL!Je+Xr))jJxChA!)qw ztM3LQPE$sPB>|Q=WLf9IT%U)R#UltIQW~B=?Yg9_Gjodpi+2xtEk4aYFxAV>u#E2D zPuP^}lSk3@W7e*Yh((x~|4=||d#VXi5K2eiZeBG@7x#7e&-2MrKv5r8EJv;zllF!? zhS9^<9~DhJ-pC_gL@+%swn<-o!N*bd9yEhqDVFbeiT}`le+?5FyPg$MqgF2+@Z~QwLZC^#!ti;0>TAUJ z04p95FD^I0*|T+YL5cQ_s0#VS{zpfQrAGmmEh=Jz&#^ShXsY%TQ(J41QO`T)Skrd% zqunSYGztKX2=D?4@VvbI1;V1HIonfkM_JCpuf8Ju&Q2p55Ixy6QX+SBR&PTpCmFWh z>f(rSt11o}#+%>@^)S90BIF9Qz4fTSd9~jU7(Om3)I~D!E^KIDhgsL|3x8q^r-@!H za>}c|ewF+56_Ou~DbA3%qy(J|4efjScSlK>8~^Y11?~0(`hR~w!+bS^_rD+L(JV8uKI|>HLsXk< zM*Q38Hr9d1I1DRprdn0hOSecUd|sIow@iF%c3G_M{D_XW&Gk}`x1pq!z4r05XY5N$ zYPyek(9vcr=c?^|Ct>hKJI8X&yBWKiQ)qib1`H{*F>lF^n71@~95Q!45h#j`t;3Rp zEAUf?D#+&keoo%>G5Af`m7y+4lF^%`k^;=!T!#sAAK+u0<4=C`mKgh^dRoJS;OY8X zNE5$t(8LEYq}9|&zI3~et^QROOS6!X+Uc&#eI1g|`|7h-(Ox}L6=XRGm2HDtcSN^S zg*|`W>}4vl=D}!uj&yYtKtoGrtm-*Gnt%QL1+o5z6+%poridZ(JWLSaW7 z{amK-I?&>)9>aIRsko$aB^p^&+EgZGC!h6>Ua3_N3OWB;_c}u$2qj*r<~o=lekRBh;DwkxhIA zXs-x+Tr!;{s)cFvRujEyspc&7^dus_n8fSHOrDAhKMD8T?c{=)Vhl~&$W5|qH1NT_ zWT-q7PMo%qPXFABj>kA{?Uj46WT@06URY@MVHeZ!uxLHCh0v=Mhlm0>47WUXM8c_Fs8`HIVV8+Nu) zN8iRw6H1tiFyGDd9f_c!&9s$nmrk`_b9=|#Y)l-$+!1N{X-X`4enH0^9rk&MhTazJ zr!E%Omb%%bn$U@4?kdObsu`Fl8I-pqBbmpc}-j-vTQ(n}pf`nZp6*YnZ&k{y7R^_;em`GGN43k&Knx3zJ^?X_Wd)4T( ztP%rU@sNzcnEST&TwGCHT=y%v?ni~WN|k4-{CZSADE+K~{yHYw3ry+E%&Et#&T-E9 zq6xvWu@8@!HAvU`t@7G(_!ipDwK#B`o>?4zyC3i{#dPs7!huXuf#(;~+e>3|XnthbOv&61E#Q(4b>0k!5L`w}Hb_5jQzc@|gslt_K+5qiw|>$c|{VVh=P6Z^90Kk z{=f|TLidv)g%;Z3h|{bx&dqDzEM&Y)JL~`hBl5&i(kEAS*2=$hQF`yGH8^mKFe}7Ke_wBT=|oy(sJu6w>>X({?)cdz5oWuruyX=bbtxdlGf_*Qx42#qyHV)orU6f;vLD#=6k zr0qFLMs?u=-;^e8RPkcLD6a`gl{K%rTnGy|DY~jJ-42cj*Fqc*gtdO=WV?;%V)gEr zyE%$(Z;OSuP}}k~m|Dd*x-XXbrqa@mQl^DdczLuKlF-zzFm7&sXUCcRZ0ngfLfh6E z;Gyzs3t2Lv{5?+nyCZ_I+{NWr(K&-`+lN#W36jK^7W2&d$GMK*^J+r~q#rgc94fn9 zhtO!QTBYTWf7NJ4Wy<8r2QJ}xOb^#T=~L_($B`ej^K1%=6!Qw`(s$pNq$b5D(d%g1 zYP8~rj=t!wQ6H};E#Ryh+z7L7cb@Rr5XRu3?One59qcCYRqsTr z99oTQS59U^dF?0?-J@RF`FhW>aI|kwf3DGJp#v*GQne#(r#siqWZ{_fo4DDMTQc9XGx@22Yg;T-#DqHR`jHW00Lkg2*1`2wr9fUFFTXH}@Ve5jIZheM{W#9#?EZ2r`7Ca6%OlNs=CO3mRK`w+x6) z^vV6m`zdRNV`_ukf%|L9Nyn}th6s;r39suxoQe>$LEIBSvQ{AOJE&2(5aP-B8rY@8 z9KD`w$JEfzFi9ICVa>1^- z4J>kcRNiq9qQT3UKs75@T;SGhBlWi$SvR}H&}yc||8fCV+Z}%YwsLjT|G3N;6B|&1 zKJ_<0bM^t-1^LETwZ%z)#QNx0R*j&zlaq-rs_lm%UFSZt)`INa6vg)@?h6FY>s3J~>}n z_I=9>3whU~Sl71rm!0omQpBW&+3cayGxF5`d@co@f6a;WUb0(N1KT-jT++-pQ|}*p z79K#<*>!8@gAR{oeZR2lfAcbTV(;*iE9(4B>gl>hF&Ho9)dQrp;}lR~^}YZU~?3nqJ0B5-{KKfjsW}8_u|@ zE_;volhM5@)9(`aJ$v0V!zt;dUC?}Xv@w^7!B>4SJj_SPSewlKhvIp5YNB=@_YKT zmoV^km8Jek?YsemVb_g)y*l4bv$+U9`|sjV`qxOBRM~T0kieZaJG$m>Z2bGn+u!a? z2+`HTPlEawF8}^Ndainu54s`~9rpjSTlSQrZ*2em?LUA1KU3J-|DRIW|9l(xrT?F2 z%m3%2{(V7D|G$MB{?7vbp9Mtwf1^Y`}?rjj1Mg=L`SwoP#18ZQ%qIHU7UeGx zLHTGsZe?pbw5d0a&{nCnP@3lpgH|BtwN4McDo;J}*I)E~7N3CF6dNB%^F2?Y;RlukSkD<5!6vlwoFO&i&*`CVUM9LOrLtS|wGLdR;nYCyk-;^^RCv=H9v56PE@uV4wFGjPrCeEK|-4ApyU%1aWOTCf5| zL$0~S%SrH7dmU7Y_!@|u%}~cgN9R;j?8eR%`n^!PSerf-(A%m_1%fNGwpOgUxmVzt z8!U9Z94NFfCbdlr*WC|DRZ@;f>wQnd_NCNTC(=Yq7!VYAKMLl~GadH(L?XvMkr{qF zJr`2bJ|_n!c$={BBY)ot|6Nh_Iqd*M<}AV6Hw9=?e;U+aI?&ZykWI#{S38nDx#GbIy>`)|8A@L934Dt#+Tlh zu90=&M*P>|qoKV}2A4XjB2T90QNT?;T(xmjxc<)DjepaOle3h6dyx0|fap3r#+Y7` zSLsW>{H@xZ;Pd8`(^Och@83UO3kaZrIrX?mdbTK2Ek`oRyLVOBes4z5$jiwyIHjbf z(%|BTueA0?9YfF5;NW0ESkdv)uY3IUm(#2sUhLc|^V_m}*Q6iPsz#S?Ld786 zZemka=w(neDZ-v})xa+=<#b=v#npu=S&E(jkAVztb$4)Gh9;`Rw{U{ubs|r`s=WL` z8VZI0t^ifxcz3-ixOpzPc|N$=C?P4-+H44W584O_6=_V&ZM1}Cp}HlLsS^AM|7bb> zbAr0LMkSEmV-zw8gWi^#8k7Ft;fW@G<0LgZ>>2y;&%YWj@-AoubY3r2t8m1qFyD)t zznrvk{5LLTHH7UJD<5x6w}p}S^RW4xZ2PAo7c+N{uA*x#>9S03CXF9#3fME>*ktZd z@$kr1FS6VH!JHGaCN7Qv|E6qYUv61T%Ofs6E@o+DL9RLi@eY26 zomffUC6jCz!JG^d=*#rVo#GRJ zp+%Q8G&1td_v}c6u{xyg98O2OjI0`2DC_-+&~U|-m7V@$KhVuPLg9#V;1rSP7!vQE zcw6NZsY**1Q$@Uomew+l{%eD38jC&mNlAxcSo7|~tm!Qcd6yWD_z$pRaWJ8(@k?;9 zKgWm6H9+tGy}n1io!PttBS8}4;|E)Rwfdhe*%eA8e*WAyF!19eUFCGb0;W39%quaT z%-_8mjJk_W!4xL3zqHO~jrzlqkQ^VeUVVHEhD#nCG&YeWe)`3Ea=5a=YcM!eKuROs zB$|G_9o4g+-f$!!-RCytjc7P)aw80VYkI_th>a8Z#A_T6Hr3_th{M@v%7s+69g_HS za{OSt6A?|*z@&+G zwnwN2zM8bio4}7W`i4k@%yGwa)wn&=S(s*N%yXOy|jH<^g(50U@8j=e*Xv4s+2pXRYFMI3$mILf= za@Y$!zUL8HW@c&{s_8ynZpfTXO*O2Nb=DhQ@d<=R;E@ntuhok8ZKrpkbH%CY8<(DAN8zt zya_y5)Ce4y@|9mz({4wDd8!zIVaF3pMh$)z#IJ z%PHg87iWHFTZBPk1UCs8qKaM4=S<1}u6+b9vzop>vOtIT(y9cD{n$P7#x87gB|Er< zv$n8Wdi1osveLls&&64}Tt{^ALU$nc**34AXx`fTLMb!ye+4XetlJZl>F$Dsv$M*$ zcwPmxdK%ReQxQdBq@;Oo!=OSg0D$J7918%9>vKIbu6=Eg`Q@|P@|a2_yU^2*9+{D4lOPJ^%YG8yV}%(AH; zW9o!8W?x2}`m&i|UAr3mwuzB7%(Le)xiUL(YYOEJ41!PzI5kV6=@aKLtQ5Qih$1?l z{~+{n+5U=0DO89}R76j}*jMdhU1r-sCsgVd7aT&}fmiH#e9igv=Y4Q|52YD{B+Bi3 z@DP(~%De_SIa2Fm+6V+9Rf;C4(YhwU}J zoRKw^Y&x0dRy_BHccRSgyz0V?I~%Reon^8Ld6t=E+Jl^1E(A<1SD>j$AAn!oZZ(9JFL$3k0g(JRipz}jzk1kglSR!to zA!`?>dH-QJzX(gq+^lSKHD&!mZWej%x@zf6TH4XNVRbv&*m=GZTb(u&8S*5(-#w3{z%57_ePtugnviw;u@UO4eA4^=X=cgI3Sll~O zGcd@TKAx?y(wZz=@CY!ZC&ObR!&|0IObVTD7OLWRJ^cWZ3SXQVS)gLe62?eUzen*3 zb|_f(DGy;;^acapi``AYc>cZey)oPl>F?c7^&f9g`YMX=P|>O~&_+oUV!rX59j$}tO64nkPJ6H;I9677@`mYb%G$%QtgI}A zflk4kh1!n0uA|=3nJ+|D@k{O4Te`!8e}87M>RAn&U&mV;h~KOBt&;^#f=qTFBOB8o z2$3I59Zn4at%cymwaqo*a58~ZMPZ>Y{pM`+_rk6F16+=~NsBCe#!1A}djlO6r}P6j zj~DvcoEKVe9$odd)t%0wJfG34+dr)rNcP(vH$MKoSG0eSN(}~D1A)jq^}Ptjg9^^S z*Z18!aCTm>=OA3l$Esn%1VfC1wX?HaTy_?%G;Qzu1QJp;U9y-SbIZvwb%_Z z6}ecsx~d0>sm$MvN{CO2G1Vrye;>hxufNS7WFiC7FYOteYt_94Ov8nFIrKS^b4KMb z8_?eS5Lf&Mc29hAva*|E3ZAc*TJa|hmZj=K(7ZvWrrv43E1kseqY$>fme_=%^(YWh zOcqY`3lj|tZ*w3Mr8zK3^uEoiXE!`+oV^d0zWU`(Fqjf90a8^O{GnJD z(sBpUL~R{)YYPi8O7Hz#Z{d_=FSAOXe1q(<1SD-!z9w@a>7)>>+12BvU&;_s3=R*i zy<7gX-ZiYR2K&}q*t%_JWJ(hBP>5qQxO}bc?H&6|q(v*`aVh;31&Nz~lRpc)DX%(1od#*@5@nUgq;5k0 zzsPA57ceV!9MMeL=CN|?JdoU{m4?~y{2oi63(E|kW?*HJGYtCcX)P@XtZQ$Og{&GL zTDkRqcRxb#s*&*dbL3*_0w+s+R#pD6i=M`PR76r+4zlEVk4TLoG)wf)eluU#qFX54?8Ic2?CEFD--p^t}vN&S1}7{uJJp4I zVW@dnQd?`c$-T4qg^g!%QZ%&EZE7+VwjF~7s7aWmU73^ZrvyzV)>|j!I{Q;K_UnUj z$p{yh%(6mN=)Ljvdps%`cwQeIL&+3Gl^0LMK0z=l=N)NA@?M*Fyr8N+<$u+ zOdl_18(3I*O1PAB!|I+L_lq1o8vE*7z-+xLQ}a1_O4!F~C1>hlJI?7AR3s`)%{la+ z@;%AyssHQwY=-}CBEnR>3bxl5NJ?QUd&cx>qubzEU;azJjNDaAz1o37zq5W1P}`}_Hw z{`l8FaaN1mKXp1Ov+q&BeDh3NMP5DzntZ)vnR>hs+S*9-!;QX+{pqt|My$Yr3C$uG zTWd7~jdTc@UOdRbT_4}i_S+2{EAPHs-7BTK{@P( zgY{t~30-Kg&nY@Y-ldI+iM1Gyl^-4Y+zk0w;XGrKg0+#O3^jFnkj=X*bd(85&^vN0B{57)R<)lnN%4NZ<0T{WN_3OKNZDOr_e zrCcnsh!{fca-=#UremC!_4an2-%Cy5l+5u3S4hgOY_$q@OE^X=3dA^aVUDrS(cr_& z6p93Ibulh^kn_aV5}{W>WBKBxX2r(Bn;Mox-WAHTnVMjUbPgJDL2x=5;^n5!63c$p zmF3$Fi5$<`UpiVx z{II46v#69caL63FW!mbztD(dCedG4^X=h$;?d)0TneAEGS#_pVLT4gvl%>!~r#_)1^;m;j+}KbBYhHU#F?m;a zH!?q8>>zKA0P7mUKqD`5Ze_AA_rLr0g*cQ<6d?)rHhQk7H@UW&Y#lS&Kbfa!e7($e zolczEut2N7jZa8W84jg@K{fED%N87jQxto1*bMIQaPVir zs2RxNA!H%IvOMgz9V_t=^$iz>5VWK(^3*9hHaaJ__!%v2^U-<*xukKrafuvLgT`me z1Z^`q*N`+SwlCG>VAx!23$!OEzSMcSt`wawp^itdQ(AukJL0oz?X%xjv^Q+19h*S1 zcF?U;w0`Kjd@@#R*;Io3y`)o{%HJ>W7rbz}nMFJ5?#-a*-)7hP=iq z&6oXM<6#mYBP$*gF^(0&XM_B(zhmd*3|UavS3fC0h=V~8@<>fjFTThX7(D-8ir1kK z5-mmJ^5-`uE*|dmZfV=<{j-DVQ%yj&5QrE+4Z1IwqZHOpk2s{j_~klPE7Qzdn$=mynR4&PS%=qZhhnl}D&=_Tp1iUad||g?Lf@ZY|_l;C+IF zDSW@1B*a2qTIL~|Sg1e#rSWyDw@Z4Dd2U6PHe3C6zP{fg=EhhR#5LS*W0hWkNXB-UZa!3A(6+S~CzEYD9$(|xJ?@~V0UD%h8>C9+8sUYm z&TBF!+_*S6fRjKDR)8!Tgbtu`LjiLh54ALP;Dk(#!bc1v^=nyI(2na|>3@{*FLkb8;ISfNMpcEHI1kRec*ewa?Q8F~KkL2LUMnk08rXW5fKnVtQW11$oy>`tVMfi}>#|drTDtU%t#ATLdVeXE{Y!_Cgzo) zw>{}fgnvGz+Xc)lkdna6dM^F&`;u5pC?jz=a03t(;FBSby8ACN4F8Ih85pUv`2{oF znc1l}aNqWNLUx<{&(n~xErYv1N(vUOP3nb{+wmX(lzIwF04}cEe3G!bOtO0h< zlVL12kB^dBSI_6|<5HuN#Kf0Dn{(x01Jo4}`CTSrIu^e!ZQ{~eaOv%*8HkB6|qy=dm#~xEd+an018OQXtTu zjX$Szs+ReB`N_bI7)0k>PerVJdxJV!xI6Ot?nKc3`Zsq*P&M5PyP|u726cCQv}Tya z5Mg0Y)YR1;y?@S2G6b4U1T|gix-WwEOcH+n?ZF;(FN{pS6m$~-Vrslihw<`S^Dh){ zbg=_lTf)MxS3in{XdwauoKs$)G{Mj^I91|ftanmF0ZeGI6$e~jlGpN$<8`pH(Cq?U zum1ui1n4FD!@lEWy@g5eTe<&DyG>8l`X(U0USj>+w|$FwH0 z6XFM>(PJyvjTX+HuU=(&FF^gK5g;?zId5yM0Hq)!gV(vrVzEK1zc(T5z`( z+|R|h&&|`2lTq|jPWrIG01p~cmbXikih*AwVG;3NoBL~GvReIB{_K6xupE~ z|8fD!##U2oyo_<9-#0%dWJ;C*x8pyGEolwRUKp;iaoSJ5P(2gV^=u7ZS~PsT=L|=1 z^y_s24iBJ&o@t;i7K?-pbVCgca(avJOeExuKYtD`ik^Bb%rxW*Q5#07DFN zj`wz&x!dCoWFw{Ys8?=+5t(6zy1KSHIKZpH`6r;0JjL@?;i^bbU37JI9cp$F zI+%t0447MG;!R-@;i8ffCBSF4iSZ|^Ei%EUQUc3DWj=#XtW7tl-CDRs56C&FL%Dlg z!dFyVa2)mr{ck$<{}Il!q6mt>ApIVVPbt<0{=d6yYuvH)UGrOnJ%p#y;CVV;Oq+1= za*hSGZZTbgwaIdC05fK`o4@KCgV+i{PSz^g^76>XF8XzLUdBK}GmO>n7&zZ1jj9I4 zjR;SJrRqZ7Y8U65`nqI;TJyA0()8CHFIqCWINxx*?^`icDm-6x4K6k3|IE^SlH$ES z4hNn4R6tweD9{TfA7d7v)tfP?2dd!PEp$JYYVt`{efG@7FB9w@XNw`H5|_|Gys7a% zCyRPo0cR(~O8FI&o_DR3HSlJE^ap#*hS+y7KXiOHLT~PuZJY!maPY!94Vj}OrAD{k zFz)`Jppsm5j&m94L_dm=;ppVA4mK$E5;-h+~?T-TXg&D(EpvN0Mft6!<(9t)eD6M z#J=1E-vsJyk}7rj?|oSwt!&hy<*niKAKV#QM<=XhKuw^8W9y;bKo_!B^| zz+$d~_O0TZiz0$*FBmX2#Wi2VSbhQSTjuD9mN_YG4d|bC4U?69udILn{{1?gDHT)Y zy)g#9k_G=%Fsi_|JHSi8MXERhO<2`vaBeQUw#IZkGIN^AZjVWDzVrg+f$kM7TU@jB zsTyh#EuswGdY0MhyI9);lp1d3jnJj(8Xf=sasSUmPO533xCHMVnP%cJC*ANc67Je`B0|7X26q%mCnt?ceslLH4{anZhywQi0TM7ZRTY>L zc;^{F(Bx$P&XNJg$HM%grFQO~?Oy@heuhQZ*xG_f4NX1gCTSGxa_u6wCZECKrJBHs z1eui4>!C}&=BV%O)qtAuudodw@XB0c&`TUZp*N)grLs-INTaU%U4{_C4|?{tQwg9P zVHqC@yVpPG!wh2a2&aG9-aYsaq-X5-8f$3#@s!e(Y62RHVg=<``Ol<^zrZ(gl_Gc z-9}a>nB7`hT5i1_2;+tV*SA@tqoc{crU-m660R#i1F9q@VJ+8Lg7-(5$}fGjS{H1+ zof`%eE{Rsvl~8dy(zYqim_LgZq>*TLFk#741{L` zZi&XAR|95(OwO&jV-yElB*j6_`D&1|5a@kx604@sZ@<8g-7 zn8tkj_779GQld`~qU3jR+NotW%OUs-quDDNWQIYCMpu{C8Q;Z1NS@ECsoCzwXz1Q6 zMm_25z3=jkU7`t~U1ttxs{lk#ZzVVK04uWh;~eYLERBMoF*2WHX{XR4{6){j%ba~I6z@wG@G68FAacteWRn} ztU%WY3+I78U_c_l8yZxkXOJ}{g#VLdkBI-T`zb3MxXx_E{jPta**^2<@494ttJcO# z0i6ljr^yuQh?i+rC<7Fl{wWI-D9~=(cwucF_1M+098?@rkncD?7I(IDG}*1e)YYt@ z^-MJ{+J?7X2E7(V6A)gWtBOA5?DlFn2lSZEBgTdxYm0=TNs`QwFobPk7}Bg z{7if~@a^R)SlC}4F&UyYHU8r*FG4}ca=gwb71VxfSQ!!*cU?&`3(7eN*?Kl{$Eb$9 zq&nch8&GXiK*d$R2mwxx24sJ4->4i^I+{;et~U|gJ=;L$9ZUxWOkh?U5g^s#d=Yde z*tNY_=ew`cy3={En<3+?gqs}WKJi=kG>LMUnPvjACJA1`m5N>x{Gf{SUnaj){($eP z`~+GFkcsZ5mJ7`yWl-JC!DtHJuh+aR;jx};8oYIfv;MSC>VJ=>LJ(x(-QAIAsvcRZt;IC-*Udr0KxH|Jzmh2nRkMBR~CvrXF z`bRVYKdh~Ce;W9=VdkHZ0giy;*LbEw-dG$a1;R9^;Fz6M#pKJ&XvFNWTQFt&mtO-}o*)M$ zg{$u(WPd$RB~en1@?^mqoMItULAc1GXtkowiB zzwA~^)j1VHHI{e z=MJe;w%H+XhNLgHKa2&TJWb?VFUKziR|r4GKY8tSlhzUl`vnZwPXQ=rltHD&pi@0e!BRr!jN;?aV$2bz`lZs0fB4d zSDy(_SeTv0^6dIE(KS*`)lT;rggyQ5y z;R^A_!kH4~)^AW_UFLAtQqq4HFWTZ0sXatZKF_pMBluK)B|409CWJC%r>?cD^`zv2 zvMJ!?aJ9shKXZ_(O8_q{hI0Lh7WFsxxOVf`u33_uzJft(L;TDd{L(l6F^R#6e;iu= zzJr6FXYN+L4T%dmq&%mpwMM=w`7sl;Pk8BSL?F*1^rqkTa~p;8+HR^9dYmrRna%QD zteLTq$USwBTf+t&28Zfag}n{lly*R`rlkeJ;T@IF^;3=%_mlp8#;;$y@Dcfle20*E zPV$hjX_o2gK2c9*S4NRJo-wcP(KAoCf`)?UH>A)0d3d;ab}+ngz#FKchSDs7ZV}5| z5U?~~FiW5RruuclHn+Wj^&on}!FIHPmhrmL}DV}^bCOso_vV4JWLCvP3J&F_7 zA4A(taKiEXe`=LuhFye5>_*Kd_h0nAlAb$nzwsc6^D|zpeXd8{8_H=g<`^hM!z1Qg}D*K7*n*(O)(1q~|XNJ~`-$S46>0#VGd?8z$(;>Tfdx}hO zxExZ^4ON8M49GsEmO0u|I>g$V%@3WQ&dh=a=B?c!#~K#`=bbX=c~BD1gZ10z`BUvI z7bmUJRP$;4LDVi_EUWR~)O?NEb{aUawvGC?2vv&bB; z$giZEM!&)Cf54!H$=7tRU(Rct5*jCGm(PY}#*OObn0CJ4b0OcXn28?|xp?s0`52i~ zs_ekidxMC3{~D>gxkA*OUrrG|^CkkI-v@f(As)M7*@MR-V-^Mv3aZ3xs>Je>?dpt* zbbqDlI+-Y{UK`pGV2l@_i*s8ZBsxY;JD8>?-;^Vv?kcG(Y~}<##Gt}OaDWyn*;NEG z2P>+Yi1Ajur|PR}uBo9+8|0iNv&{J!+YhelaPdyA-9ARHe;6CsVMI+ZcFvJ#QFppK z{X$lG*>9$>Ut98EiI}4@NV&@KR-sH+p-M{$X&M3iiTb~f8g%jMjE$4|(iL{r$WW@v)1Vvkna0t7J;ar%TCx>tg)le=XmznENVgN(7+4itV0hJ`Rki3`j5P#INmMe%TY6${XKqNMFnGSO2|!e6-WvP``UOEF538;X(zz(oL}X5#8ipS2-e~u6)hlHG1aG zG?on5ds)@q;lJm4i(X2U8s2BPQ<~qc(G7p%Y483tZ>P6^U(bw)w(4s+)_&3@c9GC_ ztRt27T|NR*HdbGOoB#dL$icgJH>0S&UthXk9((yFIo6@;%ABSx{Qte@#)yP!pPx!VCOv+Noe$VVtG?zurhARC1@1)3NP5r~&K7Dr|HEq?wn!kqf zZe!c4X(n5&2}?wiV`Al>p}v3q9;OvWZ8?%&jzt>qj2~ZPBk(Qz>!NRD-q%WCyoyS8 zv~&Ys9A35d*PMFI%f|j_-8Aa*DNZS?_O_J%^{AgNn@4Z2g3EDk9{>9@rU_|%UIO(^ z53W7v`upvv@3jSM0|@_LV)2fBkXLuHeX;mj)bgsJ45h`2_;XZfF2~FsBFHvB8BBrx z?d^|rM5cRg%AD_M2_vaAX$aE`Fns%zPRQ={1J3?e%A7g& zt96w#+N#u1Am^W-j48dq#6F{cFA)-ja2yt9O82gkIjLyg&nLKC+gyCux?B#{NCIkE zndK_(10&T2UyZ$Z(}=n>D)6mONUxdj4V^hl-JyE?rUzvFJJi!)Nz8bt zB+k#;MdosRW)O$d5M#Y>MW17h>8-zOex}05GheZu^~mBLdT%SXoePe47Fs&O*247W37tn`%khbbXP!! z-bl+S5oT@^B~xmWPLiaww=m02cW!;~PV{Gv(B#l${Hlf+$dJWvamJo#`}E0%e|l%GZPv4k(`eka}k=wv_cYmkJCkP{NI0=9-gls-)lTe}%UwRfZF} z`PdhjKZ_S@lU$wMl+wqY*_z3?d4y3fsDDxB}F#k!O3EIlDnHupfr z!f>A$1j!U{{b3;!pi9UBWifqs@iwqCfoo^VKdEDSEG4ni^3+{L7{|k|LYZhwBTQg6 zS$5D7!9p*!O8OPN>8223BL=DJ^gYbYV-6k)5u>ZrbH zcMESlA6Uk7d)ex+Or+;$Y7WVo&sdX+Q+!dB4l@frP7>;Id3>;$8nS0Z!Z~6uya|-4 zKXTva_Mn;7ou|^lm9bdlJ-jwbY2VO2Q-%b~*8LMD_wb<>e^{?ow5UpsBM{{?9ZUYS z1Kk7DZ~5+hc}o58qo4HB3l>yE%)p*>W99?ngCpDcdAjV7GqNxid^|2hghuf3TwP1+ zzgA>-t-(aY<+l-yqQH^|rTZsY)oUnzO?M`{8Li=aPYp&q-6T8QAXo4pIGXx|AQ!h( zcCm2&@MU5!hwRg*zXw|xBuxC0r|SYkr=Ide)vxT}D;PYfuL~jI zyzgle5fITM-v27DPzcObNxpZ>V$vtu*O-4jlCN-;Xuc(9F8XP9(=L=ly>CA_7M5d8 zLZf)Y+nYilbKr)efYX#*&3p%BK%~*tleeO|3s{2OJXWu+Jn0uOed;r z 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

%{#Bq_c&3{NS7px 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 Ug8`7RZ}T+0U~#_a?6rIU0ec$c761SM literal 0 HcmV?d00001 diff --git a/zh_CN/SUMMARY.md b/zh_CN/SUMMARY.md index 09293f1..6e70436 100644 --- a/zh_CN/SUMMARY.md +++ b/zh_CN/SUMMARY.md @@ -40,6 +40,9 @@ * [基于 APIs 开发](guides/application-publishing/developing-with-apis.md) * [基于前端组件再开发](guides/application-publishing/based-on-frontend-templates.md) * [模型配置](guides/model-configuration/README.md) + * [增加新供应商](guides/model-configuration/new-provider.md) + * [预定义模型接入](guides/model-configuration/predefined-model.md) + * [自定义预定义模型接入](guides/model-configuration/customizable-model.md) * [接入 Hugging Face 上的开源模型](guides/model-configuration/hugging-face.md) * [接入 Replicate 上的开源模型](guides/model-configuration/replicate.md) * [接入 Xinference 部署的本地模型](guides/model-configuration/xinference.md) diff --git a/zh_CN/guides/model-configuration/customizable-model.md b/zh_CN/guides/model-configuration/customizable-model.md new file mode 100644 index 0000000..f8f1322 --- /dev/null +++ b/zh_CN/guides/model-configuration/customizable-model.md @@ -0,0 +1,300 @@ +# 自定义预定义模型接入 + +### 介绍 + +供应商集成完成后,接下来为供应商下模型的接入,为了帮助理解整个接入过程,我们以`Xinference`为例,逐步完成一个完整的供应商接入。 + +需要注意的是,对于自定义模型,每一个模型的接入都需要填写一个完整的供应商凭据。 + +而不同于预定义模型,自定义供应商接入时永远会拥有如下两个参数,不需要在供应商yaml中定义。 + +
+ +在前文中,我们已经知道了供应商无需实现`validate_provider_credential`,Runtime会自行根据用户在此选择的模型类型和模型名称调用对应的模型层的`validate_credentials`来进行验证。 + +#### 编写供应商yaml + +我们首先要确定,接入的这个供应商支持哪些类型的模型。 + +当前支持模型类型如下: + +* `llm` 文本生成模型 +* `text_embedding` 文本 Embedding 模型 +* `rerank` Rerank 模型 +* `speech2text` 语音转文字 +* `tts` 文字转语音 +* `moderation` 审查 + +`Xinference`支持`LLM`和`Text Embedding`和Rerank,那么我们开始编写`xinference.yaml`。 + +```yaml +provider: xinference #确定供应商标识 +label: # 供应商展示名称,可设置 en_US 英文、zh_Hans 中文两种语言,zh_Hans 不设置将默认使用 en_US。 + en_US: Xorbits Inference +icon_small: # 小图标,可以参考其他供应商的图标,存储在对应供应商实现目录下的 _assets 目录,中英文策略同 label + en_US: icon_s_en.svg +icon_large: # 大图标 + en_US: icon_l_en.svg +help: # 帮助 + title: + en_US: How to deploy Xinference + zh_Hans: 如何部署 Xinference + url: + en_US: https://github.com/xorbitsai/inference +supported_model_types: # 支持的模型类型,Xinference同时支持LLM/Text Embedding/Rerank +- llm +- text-embedding +- rerank +configurate_methods: # 因为Xinference为本地部署的供应商,并且没有预定义模型,需要用什么模型需要根据Xinference的文档自己部署,所以这里只支持自定义模型 +- customizable-model +provider_credential_schema: + credential_form_schemas: +``` + +随后,我们需要思考在Xinference中定义一个模型需要哪些凭据 + +* 它支持三种不同的模型,因此,我们需要有`model_type`来指定这个模型的类型,它有三种类型,所以我们这么编写 + +```yaml +provider_credential_schema: + credential_form_schemas: + - variable: model_type + type: select + label: + en_US: Model type + zh_Hans: 模型类型 + required: true + options: + - value: text-generation + label: + en_US: Language Model + zh_Hans: 语言模型 + - value: embeddings + label: + en_US: Text Embedding + - value: reranking + label: + en_US: Rerank +``` + +* 每一个模型都有自己的名称`model_name`,因此需要在这里定义 + +```yaml + - variable: model_name + type: text-input + label: + en_US: Model name + zh_Hans: 模型名称 + required: true + placeholder: + zh_Hans: 填写模型名称 + en_US: Input model name +``` + +* 填写Xinference本地部署的地址 + +```yaml + - variable: server_url + label: + zh_Hans: 服务器URL + en_US: Server url + type: text-input + required: true + placeholder: + zh_Hans: 在此输入Xinference的服务器地址,如 https://example.com/xxx + en_US: Enter the url of your Xinference, for example https://example.com/xxx +``` + +* 每个模型都有唯一的model\_uid,因此需要在这里定义 + +```yaml + - variable: model_uid + label: + zh_Hans: 模型UID + en_US: Model uid + type: text-input + required: true + placeholder: + zh_Hans: 在此输入您的Model UID + en_US: Enter the model uid +``` + +现在,我们就完成了供应商的基础定义。 + +#### 编写模型代码 + +然后我们以`llm`类型为例,编写`xinference.llm.llm.py` + +在 `llm.py` 中创建一个 Xinference LLM 类,我们取名为 `XinferenceAILargeLanguageModel`(随意),继承 `__base.large_language_model.LargeLanguageModel` 基类,实现以下几个方法: + +* LLM 调用 + + 实现 LLM 调用的核心方法,可同时支持流式和同步返回。 + + ```python + def _invoke(self, model: str, credentials: dict, + prompt_messages: list[PromptMessage], model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, stop: Optional[List[str]] = None, + stream: bool = True, user: Optional[str] = None) \ + -> Union[LLMResult, Generator]: + """ + Invoke large language model + + :param model: model name + :param credentials: model credentials + :param prompt_messages: prompt messages + :param model_parameters: model parameters + :param tools: tools for tool calling + :param stop: stop words + :param stream: is stream response + :param user: unique user id + :return: full response or stream response chunk generator result + """ + ``` + + 在实现时,需要注意使用两个函数来返回数据,分别用于处理同步返回和流式返回,因为Python会将函数中包含 `yield` 关键字的函数识别为生成器函数,返回的数据类型固定为 `Generator`,因此同步和流式返回需要分别实现,就像下面这样(注意下面例子使用了简化参数,实际实现时需要按照上面的参数列表进行实现): + + ```python + def _invoke(self, stream: bool, **kwargs) \ + -> Union[LLMResult, Generator]: + if stream: + return self._handle_stream_response(**kwargs) + return self._handle_sync_response(**kwargs) + + def _handle_stream_response(self, **kwargs) -> Generator: + for chunk in response: + yield chunk + def _handle_sync_response(self, **kwargs) -> LLMResult: + return LLMResult(**response) + ``` +* 预计算输入 tokens + + 若模型未提供预计算 tokens 接口,可直接返回 0。 + + ```python + def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], + tools: Optional[list[PromptMessageTool]] = None) -> int: + """ + Get number of tokens for given prompt messages + + :param model: model name + :param credentials: model credentials + :param prompt_messages: prompt messages + :param tools: tools for tool calling + :return: + """ + ``` + + 有时候,也许你不需要直接返回0,所以你可以使用`self._get_num_tokens_by_gpt2(text: str)`来获取预计算的tokens,这个方法位于`AIModel`基类中,它会使用GPT2的Tokenizer进行计算,但是只能作为替代方法,并不完全准确。 +* 模型凭据校验 + + 与供应商凭据校验类似,这里针对单个模型进行校验。 + + ```python + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + ``` +* 模型参数Schema + + 与自定义类型不同,由于没有在yaml文件中定义一个模型支持哪些参数,因此,我们需要动态时间模型参数的Schema。 + + 如Xinference支持`max_tokens` `temperature` `top_p` 这三个模型参数。 + + 但是有的供应商根据不同的模型支持不同的参数,如供应商`OpenLLM`支持`top_k`,但是并不是这个供应商提供的所有模型都支持`top_k`,我们这里举例A模型支持`top_k`,B模型不支持`top_k`,那么我们需要在这里动态生成模型参数的Schema,如下所示: + + ```python + def get_customizable_model_schema(self, model: str, credentials: dict) -> AIModelEntity | None: + """ + used to define customizable model schema + """ + rules = [ + ParameterRule( + name='temperature', type=ParameterType.FLOAT, + use_template='temperature', + label=I18nObject( + zh_Hans='温度', en_US='Temperature' + ) + ), + ParameterRule( + name='top_p', type=ParameterType.FLOAT, + use_template='top_p', + label=I18nObject( + zh_Hans='Top P', en_US='Top P' + ) + ), + ParameterRule( + name='max_tokens', type=ParameterType.INT, + use_template='max_tokens', + min=1, + default=512, + label=I18nObject( + zh_Hans='最大生成长度', en_US='Max Tokens' + ) + ) + ] + + # if model is A, add top_k to rules + if model == 'A': + rules.append( + ParameterRule( + name='top_k', type=ParameterType.INT, + use_template='top_k', + min=1, + default=50, + label=I18nObject( + zh_Hans='Top K', en_US='Top K' + ) + ) + ) + + """ + some NOT IMPORTANT code here + """ + + entity = AIModelEntity( + model=model, + label=I18nObject( + en_US=model + ), + fetch_from=FetchFrom.CUSTOMIZABLE_MODEL, + model_type=model_type, + model_properties={ + ModelPropertyKey.MODE: ModelType.LLM, + }, + parameter_rules=rules + ) + + return entity + ``` +* 调用异常错误映射表 + + 当模型调用异常时需要映射到 Runtime 指定的 `InvokeError` 类型,方便 Dify 针对不同错误做不同后续处理。 + + Runtime Errors: + + * `InvokeConnectionError` 调用连接错误 + * `InvokeServerUnavailableError` 调用服务方不可用 + * `InvokeRateLimitError` 调用达到限额 + * `InvokeAuthorizationError` 调用鉴权失败 + * `InvokeBadRequestError` 调用传参有误 + + ```python + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + Map model invoke error to unified error + The key is the error type thrown to the caller + The value is the error type thrown by the model, + which needs to be converted into a unified error type for the caller. + + :return: Invoke error mapping + """ + ``` + +接口方法说明见:Interfaces,具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model\_providers/anthropic/llm/llm.py)。 diff --git a/zh_CN/guides/model-configuration/new-provider.md b/zh_CN/guides/model-configuration/new-provider.md new file mode 100644 index 0000000..b1c3e5a --- /dev/null +++ b/zh_CN/guides/model-configuration/new-provider.md @@ -0,0 +1,588 @@ +# 增加新供应商 + +供应商支持三种模型配置方式: + +* `predefined-model`预定义模型 + + 表示用户只需要配置统一的供应商凭据即可使用供应商下的预定义模型。 +* `customizable-model`自定义模型 + + 用户需要新增每个模型的凭据配置,如 Xinference,它同时支持 LLM 和 Text Embedding,但是每个模型都有唯一的 **model\_uid**,如果想要将两者同时接入,就需要为每个模型配置一个 **model\_uid**。 +* `fetch-from-remote`从远程获取 + + 与 `predefined-model`配置方式一致,只需要配置统一的供应商凭据即可,模型通过凭据信息从供应商获取。 + + 如OpenAI,我们可以基于 gpt-turbo-3.5 来 Fine Tune 多个模型,而他们都位于同一个 **api\_key** 下,当配置为`fetch-from-remote`时,开发者只需要配置统一的 **api\_key** 即可让 Dify Runtime 获取到开发者所有的微调模型并接入 Dify。 + +这三种配置方式**支持共存**,即存在供应商支持`predefined-model` + `customizable-model` 或 `predefined-model` + `fetch-from-remote`等,也就是配置了供应商统一凭据可以使用预定义模型和从远程获取的模型,若新增了模型,则可以在此基础上额外使用自定义的模型。 + +### 开始 + +#### 介绍 + +**名词解释** + +* `module`: 一个`module`即为一个Python Package,或者通俗一点,称为一个文件夹,里面包含了一个`__init__.py`文件,以及其他的`.py`文件。 + +**步骤** + +新增一个供应商主要分为几步,这里简单列出,帮助大家有一个大概的认识,具体的步骤会在下面详细介绍。 + +* 创建供应商 yaml 文件,根据 [Provider Schema](#user-content-fn-1)[^1] 编写。 +* 创建供应商代码,实现一个`class`。 +* 根据模型类型,在供应商`module`下创建对应的模型类型 `module`,如`llm`或`text_embedding`。 +* 根据模型类型,在对应的模型`module`下创建同名的代码文件,如`llm.py`,并实现一个`class`。 +* 如果有预定义模型,根据模型名称创建同名的yaml文件在模型`module`下,如`claude-2.1.yaml`,根据 [AI Model Entity](#user-content-fn-2)[^2] 编写。 +* 编写测试代码,确保功能可用。 + +#### 开始吧 + +增加一个新的供应商需要先确定供应商的英文标识,如 `anthropic`,使用该标识在 `model_providers` 创建以此为名称的 `module`。 + +在此 `module` 下,我们需要先准备供应商的 YAML 配置。 + +**准备供应商 YAML** + +此处以 `Anthropic` 为例,预设了供应商基础信息、支持的模型类型、配置方式、凭据规则。 + +```YAML +provider: anthropic # 供应商标识 +label: # 供应商展示名称,可设置 en_US 英文、zh_Hans 中文两种语言,zh_Hans 不设置将默认使用 en_US。 + en_US: Anthropic +icon_small: # 供应商小图标,存储在对应供应商实现目录下的 _assets 目录,中英文策略同 label + en_US: icon_s_en.png +icon_large: # 供应商大图标,存储在对应供应商实现目录下的 _assets 目录,中英文策略同 label + en_US: icon_l_en.png +supported_model_types: # 支持的模型类型,Anthropic 仅支持 LLM +- llm +configurate_methods: # 支持的配置方式,Anthropic 仅支持预定义模型 +- predefined-model +provider_credential_schema: # 供应商凭据规则,由于 Anthropic 仅支持预定义模型,则需要定义统一供应商凭据规则 + credential_form_schemas: # 凭据表单项列表 + - variable: anthropic_api_key # 凭据参数变量名 + label: # 展示名称 + en_US: API Key + type: secret-input # 表单类型,此处 secret-input 代表加密信息输入框,编辑时只展示屏蔽后的信息。 + required: true # 是否必填 + placeholder: # PlaceHolder 信息 + zh_Hans: 在此输入您的 API Key + en_US: Enter your API Key + - variable: anthropic_api_url + label: + en_US: API URL + type: text-input # 表单类型,此处 text-input 代表文本输入框 + required: false + placeholder: + zh_Hans: 在此输入您的 API URL + en_US: Enter your API URL +``` + +如果接入的供应商提供自定义模型,比如`OpenAI`提供微调模型,那么我们就需要添加`model_credential_schema`,以`OpenAI`为例: + +```yaml +model_credential_schema: + model: # 微调模型名称 + label: + en_US: Model Name + zh_Hans: 模型名称 + placeholder: + en_US: Enter your model name + zh_Hans: 输入模型名称 + credential_form_schemas: + - variable: openai_api_key + label: + en_US: API Key + type: secret-input + required: true + placeholder: + zh_Hans: 在此输入您的 API Key + en_US: Enter your API Key + - variable: openai_organization + label: + zh_Hans: 组织 ID + en_US: Organization + type: text-input + required: false + placeholder: + zh_Hans: 在此输入您的组织 ID + en_US: Enter your Organization ID + - variable: openai_api_base + label: + zh_Hans: API Base + en_US: API Base + type: text-input + required: false + placeholder: + zh_Hans: 在此输入您的 API Base + en_US: Enter your API Base +``` + +也可以参考`model_providers`目录下其他供应商目录下的[ YAML ](#user-content-fn-3)[^3]配置信息。 + +**实现供应商代码** + +我们需要在`model_providers`下创建一个同名的python文件,如`anthropic.py`,并实现一个`class`,继承`__base.provider.Provider`基类,如`AnthropicProvider`。 + +**自定义模型供应商** + +当供应商为Xinference等自定义模型供应商时,可跳过该步骤,仅创建一个空的`XinferenceProvider`类即可,并实现一个空的`validate_provider_credentials`方法,该方法并不会被实际使用,仅用作避免抽象类无法实例化。 + +```python +class XinferenceProvider(Provider): + def validate_provider_credentials(self, credentials: dict) -> None: + pass +``` + +**预定义模型供应商** + +供应商需要继承 `__base.model_provider.ModelProvider` 基类,实现 `validate_provider_credentials` 供应商统一凭据校验方法即可,可参考 [AnthropicProvider](https://github.com/langgenius/dify-runtime/blob/main/lib/model\_providers/anthropic/anthropic.py)。 + +```python +def validate_provider_credentials(self, credentials: dict) -> None: + """ + Validate provider credentials + You can choose any validate_credentials method of model type or implement validate method by yourself, + such as: get model list api + + if validate failed, raise exception + + :param credentials: provider credentials, credentials form defined in `provider_credential_schema`. + """ +``` + +当然也可以先预留 `validate_provider_credentials` 实现,在模型凭据校验方法实现后直接复用。 + +**增加模型** + +**增加预定义模型 👈🏻** + +对于预定义模型,我们可以通过简单定义一个 yaml,并通过实现调用代码来接入。 + +**增加自定义模型 👈🏻** + +对于自定义模型,我们只需要实现调用代码即可接入,但是它需要处理的参数可能会更加复杂。 + +*** + +#### 测试 + +为了保证接入供应商/模型的可用性,编写后的每个方法均需要在 `tests` 目录中编写对应的集成测试代码。 + +依旧以 `Anthropic` 为例。 + +在编写测试代码前,需要先在 `.env.example` 新增测试供应商所需要的凭据环境变量,如:`ANTHROPIC_API_KEY`。 + +在执行前需要将 `.env.example` 复制为 `.env` 再执行。 + +**编写测试代码** + +在 `tests` 目录下创建供应商同名的 `module`: `anthropic`,继续在此模块中创建 `test_provider.py` 以及对应模型类型的 test py 文件,如下所示: + +```shell +. +├── __init__.py +├── anthropic +│   ├── __init__.py +│   ├── test_llm.py # LLM 测试 +│   └── test_provider.py # 供应商测试 +``` + +针对上面实现的代码的各种情况进行测试代码编写,并测试通过后提交代码。 + +[^1]: ## 配置规则 + + * 供应商规则基于 Provider 实体。 + * 模型规则基于 AIModelEntity 实体。 + + 以下所有实体均基于 `Pydantic BaseModel`,可在 `entities` 模块中找到对应实体。 + + #### Provider + + * `provider` (string) 供应商标识,如:`openai` + * `label` (object) 供应商展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名,`zh_Hans` 不设置将默认使用 `en_US`。 + * `en_US` (string) 英文标签名 + * `description` (object) \[optional] 供应商描述,i18n + * `zh_Hans` (string) \[optional] 中文描述 + * `en_US` (string) 英文描述 + * `icon_small` (string) \[optional] 供应商小 ICON,存储在对应供应商实现目录下的 `_assets` 目录,中英文策略同 `label` + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `icon_large` (string) \[optional] 供应商大 ICON,存储在对应供应商实现目录下的 \_assets 目录,中英文策略同 label + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `background` (string) \[optional] 背景颜色色值,例:#FFFFFF,为空则展示前端默认色值。 + * `help` (object) \[optional] 帮助信息 + * `title` (object) 帮助标题,i18n + * `zh_Hans` (string) \[optional] 中文标题 + * `en_US` (string) 英文标题 + * `url` (object) 帮助链接,i18n + * `zh_Hans` (string) \[optional] 中文链接 + * `en_US` (string) 英文链接 + * `supported_model_types` (array\[ModelType]) 支持的模型类型 + * `configurate_methods` (array\[ConfigurateMethod]) 配置方式 + * `provider_credential_schema` (ProviderCredentialSchema) 供应商凭据规格 + * `model_credential_schema` (ModelCredentialSchema) 模型凭据规格 + + #### AIModelEntity + + * `model` (string) 模型标识,如:`gpt-3.5-turbo` + * `label` (object) \[optional] 模型展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名 + * `en_US` (string) 英文标签名 + * `model_type` (ModelType) 模型类型 + * `features` (array\[ModelFeature]) \[optional] 支持功能列表 + * `model_properties` (object) 模型属性 + * `mode` (LLMMode) 模式 (模型类型 `llm` 可用) + * `context_size` (int) 上下文大小 (模型类型 `llm` `text-embedding` 可用) + * `max_chunks` (int) 最大分块数量 (模型类型 `text-embedding moderation` 可用) + * `file_upload_limit` (int) 文件最大上传限制,单位:MB。(模型类型 `speech2text` 可用) + * `supported_file_extensions` (string) 支持文件扩展格式,如:mp3,mp4(模型类型 `speech2text` 可用) + * `default_voice` (string) 缺省音色,可选:alloy,echo,fable,onyx,nova,shimmer(模型类型 `tts` 可用) + * `word_limit` (int) 单次转换字数限制,默认按段落分段(模型类型 `tts` 可用) + * `audio_type` (string) 支持音频文件扩展格式,如:mp3,wav(模型类型 `tts` 可用) + * `max_workers` (int) 支持文字音频转换并发任务数(模型类型 `tts` 可用) + * `max_characters_per_chunk` (int) 每块最大字符数 (模型类型 `moderation` 可用) + * `parameter_rules` (array\[ParameterRule]) \[optional] 模型调用参数规则 + * `pricing` (PriceConfig) \[optional] 价格信息 + * `deprecated` (bool) 是否废弃。若废弃,模型列表将不再展示,但已经配置的可以继续使用,默认 False。 + + #### ModelType + + * `llm` 文本生成模型 + * `text-embedding` 文本 Embedding 模型 + * `rerank` Rerank 模型 + * `speech2text` 语音转文字 + * `tts` 文字转语音 + * `moderation` 审查 + + #### ConfigurateMethod + + * `predefined-model` 预定义模型 + + 表示用户只需要配置统一的供应商凭据即可使用供应商下的预定义模型。 + * `customizable-model` 自定义模型 + + 用户需要新增每个模型的凭据配置。 + * `fetch-from-remote` 从远程获取 + + 与 `predefined-model` 配置方式一致,只需要配置统一的供应商凭据即可,模型通过凭据信息从供应商获取。 + + #### ModelFeature + + * `agent-thought` Agent 推理,一般超过 70B 有思维链能力。 + * `vision` 视觉,即:图像理解。 + + #### FetchFrom + + * `predefined-model` 预定义模型 + * `fetch-from-remote` 远程模型 + + #### LLMMode + + * `completion` 文本补全 + * `chat` 对话 + + #### ParameterRule + + * `name` (string) 调用模型实际参数名 + * `use_template` (string) \[optional] 使用模板 + + 默认预置了 5 种变量内容配置模板: + + * `temperature` + * `top_p` + * `frequency_penalty` + * `presence_penalty` + * `max_tokens` + + 可在 use\_template 中直接设置模板变量名,将会使用 entities.defaults.PARAMETER\_RULE\_TEMPLATE 中的默认配置 不用设置除 `name` 和 `use_template` 之外的所有参数,若设置了额外的配置参数,将覆盖默认配置。 可参考 `openai/llm/gpt-3.5-turbo.yaml`。 + * `label` (object) \[optional] 标签,i18n + * `zh_Hans`(string) \[optional] 中文标签名 + * `en_US` (string) 英文标签名 + * `type`(string) \[optional] 参数类型 + * `int` 整数 + * `float` 浮点数 + * `string` 字符串 + * `boolean` 布尔型 + * `help` (string) \[optional] 帮助信息 + * `zh_Hans` (string) \[optional] 中文帮助信息 + * `en_US` (string) 英文帮助信息 + * `required` (bool) 是否必填,默认 False。 + * `default`(int/float/string/bool) \[optional] 默认值 + * `min`(int/float) \[optional] 最小值,仅数字类型适用 + * `max`(int/float) \[optional] 最大值,仅数字类型适用 + * `precision`(int) \[optional] 精度,保留小数位数,仅数字类型适用 + * `options` (array\[string]) \[optional] 下拉选项值,仅当 `type` 为 `string` 时适用,若不设置或为 null 则不限制选项值 + + #### PriceConfig + + * `input` (float) 输入单价,即 Prompt 单价 + * `output` (float) 输出单价,即返回内容单价 + * `unit` (float) 价格单位,如:每 100K 的单价为 `0.000001` + * `currency` (string) 货币单位 + + #### ProviderCredentialSchema + + * `credential_form_schemas` (array\[CredentialFormSchema]) 凭据表单规范 + + #### ModelCredentialSchema + + * `model` (object) 模型标识,变量名默认 `model` + * `label` (object) 模型表单项展示名称 + * `en_US` (string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `placeholder` (object) 模型提示内容 + * `en_US`(string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `credential_form_schemas` (array\[CredentialFormSchema]) 凭据表单规范 + + #### CredentialFormSchema + + * `variable` (string) 表单项变量名 + * `label` (object) 表单项标签名 + * `en_US`(string) 英文 + * `zh_Hans` (string) \[optional] 中文 + * `type` (FormType) 表单项类型 + * `required` (bool) 是否必填 + * `default`(string) 默认值 + * `options` (array\[FormOption]) 表单项为 `select` 或 `radio` 专有属性,定义下拉内容 + * `placeholder`(object) 表单项为 `text-input` 专有属性,表单项 PlaceHolder + * `en_US`(string) 英文 + * `zh_Hans` (string) \[optional] 中文 + * `max_length` (int) 表单项为`text-input`专有属性,定义输入最大长度,0 为不限制。 + * `show_on` (array\[FormShowOnObject]) 当其他表单项值符合条件时显示,为空则始终显示。 + + #### FormType + + * `text-input` 文本输入组件 + * `secret-input` 密码输入组件 + * `select` 单选下拉 + * `radio` Radio 组件 + * `switch` 开关组件,仅支持 `true` 和 `false` + + #### FormOption + + * `label` (object) 标签 + * `en_US`(string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `value` (string) 下拉选项值 + * `show_on` (array\[FormShowOnObject]) 当其他表单项值符合条件时显示,为空则始终显示。 + + #### FormShowOnObject + + * `variable` (string) 其他表单项变量名 + * `value` (string) 其他表单项变量值 + +[^2]: ## 配置规则 + + * 供应商规则基于 Provider 实体。 + * 模型规则基于 AIModelEntity 实体。 + + 以下所有实体均基于 `Pydantic BaseModel`,可在 `entities` 模块中找到对应实体。 + + #### Provider + + * `provider` (string) 供应商标识,如:`openai` + * `label` (object) 供应商展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名,`zh_Hans` 不设置将默认使用 `en_US`。 + * `en_US` (string) 英文标签名 + * `description` (object) \[optional] 供应商描述,i18n + * `zh_Hans` (string) \[optional] 中文描述 + * `en_US` (string) 英文描述 + * `icon_small` (string) \[optional] 供应商小 ICON,存储在对应供应商实现目录下的 `_assets` 目录,中英文策略同 `label` + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `icon_large` (string) \[optional] 供应商大 ICON,存储在对应供应商实现目录下的 \_assets 目录,中英文策略同 label + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `background` (string) \[optional] 背景颜色色值,例:#FFFFFF,为空则展示前端默认色值。 + * `help` (object) \[optional] 帮助信息 + * `title` (object) 帮助标题,i18n + * `zh_Hans` (string) \[optional] 中文标题 + * `en_US` (string) 英文标题 + * `url` (object) 帮助链接,i18n + * `zh_Hans` (string) \[optional] 中文链接 + * `en_US` (string) 英文链接 + * `supported_model_types` (array\[ModelType]) 支持的模型类型 + * `configurate_methods` (array\[ConfigurateMethod]) 配置方式 + * `provider_credential_schema` (ProviderCredentialSchema) 供应商凭据规格 + * `model_credential_schema` (ModelCredentialSchema) 模型凭据规格 + + #### AIModelEntity + + * `model` (string) 模型标识,如:`gpt-3.5-turbo` + * `label` (object) \[optional] 模型展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名 + * `en_US` (string) 英文标签名 + * `model_type` (ModelType) 模型类型 + * `features` (array\[ModelFeature]) \[optional] 支持功能列表 + * `model_properties` (object) 模型属性 + * `mode` (LLMMode) 模式 (模型类型 `llm` 可用) + * `context_size` (int) 上下文大小 (模型类型 `llm` `text-embedding` 可用) + * `max_chunks` (int) 最大分块数量 (模型类型 `text-embedding moderation` 可用) + * `file_upload_limit` (int) 文件最大上传限制,单位:MB。(模型类型 `speech2text` 可用) + * `supported_file_extensions` (string) 支持文件扩展格式,如:mp3,mp4(模型类型 `speech2text` 可用) + * `default_voice` (string) 缺省音色,可选:alloy,echo,fable,onyx,nova,shimmer(模型类型 `tts` 可用) + * `word_limit` (int) 单次转换字数限制,默认按段落分段(模型类型 `tts` 可用) + * `audio_type` (string) 支持音频文件扩展格式,如:mp3,wav(模型类型 `tts` 可用) + * `max_workers` (int) 支持文字音频转换并发任务数(模型类型 `tts` 可用) + * `max_characters_per_chunk` (int) 每块最大字符数 (模型类型 `moderation` 可用) + * `parameter_rules` (array\[ParameterRule]) \[optional] 模型调用参数规则 + * `pricing` (PriceConfig) \[optional] 价格信息 + * `deprecated` (bool) 是否废弃。若废弃,模型列表将不再展示,但已经配置的可以继续使用,默认 False。 + + #### ModelType + + * `llm` 文本生成模型 + * `text-embedding` 文本 Embedding 模型 + * `rerank` Rerank 模型 + * `speech2text` 语音转文字 + * `tts` 文字转语音 + * `moderation` 审查 + + #### ConfigurateMethod + + * `predefined-model` 预定义模型 + + 表示用户只需要配置统一的供应商凭据即可使用供应商下的预定义模型。 + * `customizable-model` 自定义模型 + + 用户需要新增每个模型的凭据配置。 + * `fetch-from-remote` 从远程获取 + + 与 `predefined-model` 配置方式一致,只需要配置统一的供应商凭据即可,模型通过凭据信息从供应商获取。 + + #### ModelFeature + + * `agent-thought` Agent 推理,一般超过 70B 有思维链能力。 + * `vision` 视觉,即:图像理解。 + + #### FetchFrom + + * `predefined-model` 预定义模型 + * `fetch-from-remote` 远程模型 + + #### LLMMode + + * `completion` 文本补全 + * `chat` 对话 + + #### ParameterRule + + * `name` (string) 调用模型实际参数名 + * `use_template` (string) \[optional] 使用模板 + + 默认预置了 5 种变量内容配置模板: + + * `temperature` + * `top_p` + * `frequency_penalty` + * `presence_penalty` + * `max_tokens` + + 可在 use\_template 中直接设置模板变量名,将会使用 entities.defaults.PARAMETER\_RULE\_TEMPLATE 中的默认配置 不用设置除 `name` 和 `use_template` 之外的所有参数,若设置了额外的配置参数,将覆盖默认配置。 可参考 `openai/llm/gpt-3.5-turbo.yaml`。 + * `label` (object) \[optional] 标签,i18n + * `zh_Hans`(string) \[optional] 中文标签名 + * `en_US` (string) 英文标签名 + * `type`(string) \[optional] 参数类型 + * `int` 整数 + * `float` 浮点数 + * `string` 字符串 + * `boolean` 布尔型 + * `help` (string) \[optional] 帮助信息 + * `zh_Hans` (string) \[optional] 中文帮助信息 + * `en_US` (string) 英文帮助信息 + * `required` (bool) 是否必填,默认 False。 + * `default`(int/float/string/bool) \[optional] 默认值 + * `min`(int/float) \[optional] 最小值,仅数字类型适用 + * `max`(int/float) \[optional] 最大值,仅数字类型适用 + * `precision`(int) \[optional] 精度,保留小数位数,仅数字类型适用 + * `options` (array\[string]) \[optional] 下拉选项值,仅当 `type` 为 `string` 时适用,若不设置或为 null 则不限制选项值 + + #### PriceConfig + + * `input` (float) 输入单价,即 Prompt 单价 + * `output` (float) 输出单价,即返回内容单价 + * `unit` (float) 价格单位,如:每 100K 的单价为 `0.000001` + * `currency` (string) 货币单位 + + #### ProviderCredentialSchema + + * `credential_form_schemas` (array\[CredentialFormSchema]) 凭据表单规范 + + #### ModelCredentialSchema + + * `model` (object) 模型标识,变量名默认 `model` + * `label` (object) 模型表单项展示名称 + * `en_US` (string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `placeholder` (object) 模型提示内容 + * `en_US`(string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `credential_form_schemas` (array\[CredentialFormSchema]) 凭据表单规范 + + #### CredentialFormSchema + + * `variable` (string) 表单项变量名 + * `label` (object) 表单项标签名 + * `en_US`(string) 英文 + * `zh_Hans` (string) \[optional] 中文 + * `type` (FormType) 表单项类型 + * `required` (bool) 是否必填 + * `default`(string) 默认值 + * `options` (array\[FormOption]) 表单项为 `select` 或 `radio` 专有属性,定义下拉内容 + * `placeholder`(object) 表单项为 `text-input` 专有属性,表单项 PlaceHolder + * `en_US`(string) 英文 + * `zh_Hans` (string) \[optional] 中文 + * `max_length` (int) 表单项为`text-input`专有属性,定义输入最大长度,0 为不限制。 + * `show_on` (array\[FormShowOnObject]) 当其他表单项值符合条件时显示,为空则始终显示。 + + #### FormType + + * `text-input` 文本输入组件 + * `secret-input` 密码输入组件 + * `select` 单选下拉 + * `radio` Radio 组件 + * `switch` 开关组件,仅支持 `true` 和 `false` + + #### FormOption + + * `label` (object) 标签 + * `en_US`(string) 英文 + * `zh_Hans`(string) \[optional] 中文 + * `value` (string) 下拉选项值 + * `show_on` (array\[FormShowOnObject]) 当其他表单项值符合条件时显示,为空则始终显示。 + + #### FormShowOnObject + + * `variable` (string) 其他表单项变量名 + * `value` (string) 其他表单项变量值 + +[^3]: Provider + + * `provider` (string) 供应商标识,如:`openai` + * `label` (object) 供应商展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名,`zh_Hans` 不设置将默认使用 `en_US`。 + * `en_US` (string) 英文标签名 + * `description` (object) \[optional] 供应商描述,i18n + * `zh_Hans` (string) \[optional] 中文描述 + * `en_US` (string) 英文描述 + * `icon_small` (string) \[optional] 供应商小 ICON,存储在对应供应商实现目录下的 `_assets` 目录,中英文策略同 `label` + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `icon_large` (string) \[optional] 供应商大 ICON,存储在对应供应商实现目录下的 \_assets 目录,中英文策略同 label + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `background` (string) \[optional] 背景颜色色值,例:#FFFFFF,为空则展示前端默认色值。 + * `help` (object) \[optional] 帮助信息 + * `title` (object) 帮助标题,i18n + * `zh_Hans` (string) \[optional] 中文标题 + * `en_US` (string) 英文标题 + * `url` (object) 帮助链接,i18n + * `zh_Hans` (string) \[optional] 中文链接 + * `en_US` (string) 英文链接 + * `supported_model_types` (array\[ModelType]) 支持的模型类型 + * `configurate_methods` (array\[ConfigurateMethod]) 配置方式 + * `provider_credential_schema` (ProviderCredentialSchema) 供应商凭据规格 + * `model_credential_schema` (ModelCredentialSchema) 模型凭据规格 diff --git a/zh_CN/guides/model-configuration/predefined-model.md b/zh_CN/guides/model-configuration/predefined-model.md new file mode 100644 index 0000000..6dfa60e --- /dev/null +++ b/zh_CN/guides/model-configuration/predefined-model.md @@ -0,0 +1,197 @@ +# 预定义模型接入 + +供应商集成完成后,接下来为供应商下模型的接入。 + +我们首先需要确定接入模型的类型,并在对应供应商的目录下创建对应模型类型的 `module`。 + +当前支持模型类型如下: + +* `llm` 文本生成模型 +* `text_embedding` 文本 Embedding 模型 +* `rerank` Rerank 模型 +* `speech2text` 语音转文字 +* `tts` 文字转语音 +* `moderation` 审查 + +依旧以 `Anthropic` 为例,`Anthropic` 仅支持 LLM,因此在 `model_providers.anthropic` 创建一个 `llm` 为名称的 `module`。 + +对于预定义的模型,我们首先需要在 `llm` `module` 下创建以模型名为文件名称的 YAML 文件,如:`claude-2.1.yaml`。 + +#### 准备模型 YAML + +```yaml +model: claude-2.1 # 模型标识 +# 模型展示名称,可设置 en_US 英文、zh_Hans 中文两种语言,zh_Hans 不设置将默认使用 en_US。 +# 也可不设置 label,则使用 model 标识内容。 +label: + en_US: claude-2.1 +model_type: llm # 模型类型,claude-2.1 为 LLM +features: # 支持功能,agent-thought 为支持 Agent 推理,vision 为支持图片理解 +- agent-thought +model_properties: # 模型属性 + mode: chat # LLM 模式,complete 文本补全模型,chat 对话模型 + context_size: 200000 # 支持最大上下文大小 +parameter_rules: # 模型调用参数规则,仅 LLM 需要提供 +- name: temperature # 调用参数变量名 + # 默认预置了 5 种变量内容配置模板,temperature/top_p/max_tokens/presence_penalty/frequency_penalty + # 可在 use_template 中直接设置模板变量名,将会使用 entities.defaults.PARAMETER_RULE_TEMPLATE 中的默认配置 + # 若设置了额外的配置参数,将覆盖默认配置 + use_template: temperature +- name: top_p + use_template: top_p +- name: top_k + label: # 调用参数展示名称 + zh_Hans: 取样数量 + en_US: Top k + type: int # 参数类型,支持 float/int/string/boolean + help: # 帮助信息,描述参数作用 + zh_Hans: 仅从每个后续标记的前 K 个选项中采样。 + en_US: Only sample from the top K options for each subsequent token. + required: false # 是否必填,可不设置 +- name: max_tokens_to_sample + use_template: max_tokens + default: 4096 # 参数默认值 + min: 1 # 参数最小值,仅 float/int 可用 + max: 4096 # 参数最大值,仅 float/int 可用 +pricing: # 价格信息 + input: '8.00' # 输入单价,即 Prompt 单价 + output: '24.00' # 输出单价,即返回内容单价 + unit: '0.000001' # 价格单位,即上述价格为每 100K 的单价 + currency: USD # 价格货币 +``` + +建议将所有模型配置都准备完毕后再开始模型代码的实现。 + +同样,也可以参考 `model_providers` 目录下其他供应商对应模型类型目录下的 YAML 配置信息,完整的 YAML 规则见:Schema[^1]。 + +#### 实现模型调用代码 + +接下来需要在 `llm` `module` 下创建一个同名的 python 文件 `llm.py` 来编写代码实现。 + +在 `llm.py` 中创建一个 Anthropic LLM 类,我们取名为 `AnthropicLargeLanguageModel`(随意),继承 `__base.large_language_model.LargeLanguageModel` 基类,实现以下几个方法: + +* LLM 调用 + + 实现 LLM 调用的核心方法,可同时支持流式和同步返回。 + + ```python + def _invoke(self, model: str, credentials: dict, + prompt_messages: list[PromptMessage], model_parameters: dict, + tools: Optional[list[PromptMessageTool]] = None, stop: Optional[List[str]] = None, + stream: bool = True, user: Optional[str] = None) \ + -> Union[LLMResult, Generator]: + """ + Invoke large language model + + :param model: model name + :param credentials: model credentials + :param prompt_messages: prompt messages + :param model_parameters: model parameters + :param tools: tools for tool calling + :param stop: stop words + :param stream: is stream response + :param user: unique user id + :return: full response or stream response chunk generator result + """ + ``` + + 在实现时,需要注意使用两个函数来返回数据,分别用于处理同步返回和流式返回,因为Python会将函数中包含 `yield` 关键字的函数识别为生成器函数,返回的数据类型固定为 `Generator`,因此同步和流式返回需要分别实现,就像下面这样(注意下面例子使用了简化参数,实际实现时需要按照上面的参数列表进行实现): + + ```python + def _invoke(self, stream: bool, **kwargs) \ + -> Union[LLMResult, Generator]: + if stream: + return self._handle_stream_response(**kwargs) + return self._handle_sync_response(**kwargs) + + def _handle_stream_response(self, **kwargs) -> Generator: + for chunk in response: + yield chunk + def _handle_sync_response(self, **kwargs) -> LLMResult: + return LLMResult(**response) + ``` +* 预计算输入 tokens + + 若模型未提供预计算 tokens 接口,可直接返回 0。 + + ```python + def get_num_tokens(self, model: str, credentials: dict, prompt_messages: list[PromptMessage], + tools: Optional[list[PromptMessageTool]] = None) -> int: + """ + Get number of tokens for given prompt messages + + :param model: model name + :param credentials: model credentials + :param prompt_messages: prompt messages + :param tools: tools for tool calling + :return: + """ + ``` +* 模型凭据校验 + + 与供应商凭据校验类似,这里针对单个模型进行校验。 + + ```python + def validate_credentials(self, model: str, credentials: dict) -> None: + """ + Validate model credentials + + :param model: model name + :param credentials: model credentials + :return: + """ + ``` +* 调用异常错误映射表 + + 当模型调用异常时需要映射到 Runtime 指定的 `InvokeError` 类型,方便 Dify 针对不同错误做不同后续处理。 + + Runtime Errors: + + * `InvokeConnectionError` 调用连接错误 + * `InvokeServerUnavailableError` 调用服务方不可用 + * `InvokeRateLimitError` 调用达到限额 + * `InvokeAuthorizationError` 调用鉴权失败 + * `InvokeBadRequestError` 调用传参有误 + + ```python + @property + def _invoke_error_mapping(self) -> dict[type[InvokeError], list[type[Exception]]]: + """ + Map model invoke error to unified error + The key is the error type thrown to the caller + The value is the error type thrown by the model, + which needs to be converted into a unified error type for the caller. + + :return: Invoke error mapping + """ + ``` + +接口方法说明见:[Interfaces](https://github.com/langgenius/dify/blob/main/api/core/model\_runtime/docs/zh\_Hans/interfaces.md),具体实现可参考:[llm.py](https://github.com/langgenius/dify-runtime/blob/main/lib/model\_providers/anthropic/llm/llm.py)。 + +[^1]: #### Provider + + * `provider` (string) 供应商标识,如:`openai` + * `label` (object) 供应商展示名称,i18n,可设置 `en_US` 英文、`zh_Hans` 中文两种语言 + * `zh_Hans` (string) \[optional] 中文标签名,`zh_Hans` 不设置将默认使用 `en_US`。 + * `en_US` (string) 英文标签名 + * `description` (object) \[optional] 供应商描述,i18n + * `zh_Hans` (string) \[optional] 中文描述 + * `en_US` (string) 英文描述 + * `icon_small` (string) \[optional] 供应商小 ICON,存储在对应供应商实现目录下的 `_assets` 目录,中英文策略同 `label` + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `icon_large` (string) \[optional] 供应商大 ICON,存储在对应供应商实现目录下的 \_assets 目录,中英文策略同 label + * `zh_Hans` (string) \[optional] 中文 ICON + * `en_US` (string) 英文 ICON + * `background` (string) \[optional] 背景颜色色值,例:#FFFFFF,为空则展示前端默认色值。 + * `help` (object) \[optional] 帮助信息 + * `title` (object) 帮助标题,i18n + * `zh_Hans` (string) \[optional] 中文标题 + * `en_US` (string) 英文标题 + * `url` (object) 帮助链接,i18n + * `zh_Hans` (string) \[optional] 中文链接 + * `en_US` (string) 英文链接 + * `supported_model_types` (array\[ModelType]) 支持的模型类型 + * `configurate_methods` (array\[ConfigurateMethod]) 配置方式 + * `provider_credential_schema` (ProviderCredentialSchema) 供应商凭据规格 + * `model_credential_schema` (ModelCredentialSchema) 模型凭据规格