From b2c5a4580272061d84a62d2a209eaec0bdd5cba5 Mon Sep 17 00:00:00 2001 From: christy Date: Mon, 27 Nov 2023 10:25:10 -0800 Subject: [PATCH] Cleanup notebooks Signed-off-by: christy --- images/db_insert.png | Bin 0 -> 72601 bytes .../langchain/readthedocs_rag_zilliz.ipynb | 338 +++++++++++++----- .../text/imdb_search_milvus_client.ipynb | 7 +- 3 files changed, 257 insertions(+), 88 deletions(-) create mode 100644 images/db_insert.png diff --git a/images/db_insert.png b/images/db_insert.png new file mode 100644 index 0000000000000000000000000000000000000000..7f8f808913a2da22497f61b26b609dfc33439dc6 GIT binary patch literal 72601 zcma%i1ymea)94I?ySs)Y5Zr>xAOR9II0Sch*TD(Fg9c4-cXtVH!QI{6>?y%g9|`W%nZ0a@b}|i6%}@~av`X(dexvHQfuE3A~G^`T_W>m?IkUQkg8^V=E*yg zXCFples3TF;3e@Isy2{|V$C10qO2KRhY9!*%ayD^8>6r08&3$2cq;N{IA)#h&2ZFu zGh6bF8h+OyfHvQjdyN60#4Mbq-8JM#T=0m=hwKJJ1u%CE@j5%FIi-{ho*|wKp^V>S zXY(HIyGu6iE_)sP^=;UuLo{ACK-iZylKt>my62hc)-8sLBEGD^@QbK^wnLCxV-nvR z=}=|LBOGHfap5AQQv{%#F$U|0zW9Kby@ww`)cONuqD7QiiMd?v)#lH z*>4836VFc;54U2SYAVo`s+E#OR|BcEjid8ReWg_wug9O7l{iTymOmdsjPV4Yi zCEi`4xt+5ob{B5Q&==Ux(U^rI2l7!Q1UY3ypHhZGNh`3V^s`TsMM(z;;O_@6%YR7Q z$rp9BFtqBLw#;l<0{IktuL+EMf_u;Xs~vbMZdNtRq&%tE^(2FJqDTcf2*`yfxM8k9v+x{IX5l z!8pW>kqA2Q9Y7RV0B=;=H{feuQp%kJivxd`voy*!pneqGO`CQ*YPe*~CF1H^bXElb$Od4Mj@%P#85>#R#QpoQ?fz`MNnVNt=!dN90dk<7E%<+a)Yi3zxB9y zbl`{(tWvEKe^0ix_>m#@pcsplL9&PN#yU|63#C(H?RzaDlSK;m93Puf|axCpIC*?)Yya0hlr1E zXNC5I56*Q-t}0*EVDdpy+hujP%c#|;GYpE=aHf38;?`CtFV$)qf|`Ezuk{7;`uW_D zxL1rx<8x2(Uh#TIU*nzSepvnmdHbG2hM~8gDT{rTz*o)$r5-(VM>axs7$Diod^SCu zOgQTT`n7vAJai8N98a~kw|6erkE#H!>N^jYA5trg+vI}T04B-x*mlt=o!JC1o>F=3 zsW}K?5Tdf6b2}L8Hk2D*FedU3Fux=aNB3gZHuXz8E(>T7E<1yi0aV!TUWQ}|$Jb6? zM#14ny#mXP#sraCA+7_d%3-(y@7noJh=eh*Mc$c)kn4x3Q96gxEjcY2{tLrm2TJHLlEnM&l5;sA5*hmM#`u=IED`+V~r=4Qu@rDZ~ zP8VE8?TS4UU?_sGhx|QOUU){9TZ|j-S^;B~ELw7cN&{?zfBkL}!zyRPbst^4kZ- zET->M@^T8tib8o~N@Pm21^kn<1#tyv1qsSJ1<{iX6N%G!hdn|JwZf|psaoS>{F*kC5ey?Xo z?$>gj&xclg&iRD+uunaTl>P*Npk79*eH2A)zHLo2H5DSlNhVPwxnA zk7?X5)=X`KZ{?BYZLV>4g{KY;oT=|eGE1{-$M0oOrA4G)zWyZ5DO?G-M`WoeO z<>E;#i-PLXOT&Hh{X$MLk|3FqA^2pen!Msds~W59!*_fW!$Y#Vwe2MqxO%ZYw5xf0 zAGr>c50b9lT~%LUkUElfkbNK}Cex1Kq)6p;zujTaY~^X--Qux!Xkr(4klsm*VP!PX z=9}fUOQlOK;Z;oaPch-+;%T)ncUWz-bgbX~;1F)BwKBJLH#xK@bD*+4wQqftcBH-O z?f?B}h%tk4$|a@ijmO{>_uA>N@uA95ht_Q!?JMni?Z&3G@M_`#ibksgY8yqHh->^~ zYl;TBh7sd*YaJY&2%G9-Q6YArv}NbkTxk+xbO)~U?AFZIndhiyk9Zo3*ZZyjd`wb# z{86GjqD0I(!4vN&GkFzxD|w$Xi<66$b*g}-eeSSnbN#mX~P#23HRgr<>taR(9NkSP+KzjT&92%BNI ztE&5K*SNOy^S*~Uy!ESYD&>1qDJ3assiOCu>9|ohK{H)$z}o-N(T&7^p6Z|G{Mr1uD?40a!`)Lz7%bF*nA zyniv8FEJrjHN7-8J#Cp`0(T2}o`y)~8*jdpAq^d#liXEGle>K@w{%l8;0>Y9s8SxB z&j#PisTiG?7@?k_bYBFe->2B?7IZ7EPDGg1jN{9AXpkyB%sYDWy137yekF1+;ZN*K zsW-xBqc>2Qx2&8!H@O}}N~?5WGO|8eSe7D{Qi*L!P2easYFT~YIj4BUevq=tH_xxT z=!PkUOU7SAfkWXV;9=IN-T6dRdf-`a;P&n|;pf87Z6C7p{W%?k1Vg4>&Al4Ob+$FO z)r$QAuY_aG@8v&Se`dyE(UYkBQjIHiZ**VVbHHQ5OP-RSlALlZMbnwD8unbQaMGDF zDQ#8R)lt_mlCRF&dxT#K8X$S(oU&qDP-@v7H*Vib{^VE7q4Y^b)$)}!e|12F6R?%T14V z?Y^b$`>|`SWr~{CL%=$CQTU6$aIe56Zu zT^o{{9}hF7B0ok9331M7Eb!WDIyN1PO?{TI!kIrxkrs5T7RYHnSDMMM%=MQ|=kMen zcb~dHh;~TZ{x-rs%%yGHI6AsIXSd@ry^P`1;KZrv*gE6bx2_;HJyT+k;KiI?hi$ug zp?o!oGX0{|NlUKI`&@i3{3y98dBl2i(cP(W=W3hJ;RyREeo?Vi)oyvk`;vmwXTqh? ziNXu>Bx7A~p_4DNE>fGH$@L-IbmzePO0hed~7I&E(4dPn(OS1K0ht8Y@com8ZrBm-OWXuhILmbL#c=B)RwL zY2ImPffq)Hs>!8{%N;F?WP=o>xOQEnv!wud`fo`{ZTn;Zc||}*`#F(O%EJQ8Ujnv*7%g6xepk+h=91t4- zf|h{LKLAJ!fcv8i0DJ(F{8d%}(*D^71^@^)1Hk^-Mgw~P{fmNLQ0zbNpqL;40`v(R zdIe;_{7-9Gw+68rWDd=^5JS8!@?B+5V;j;CJPP7OjjN^(bAfEUg`QT?JnJ z(SjFR{tae+LHS1$M+<=$YBKVaqBizMl$=Z~Oe`-1ktr!D`Rxsjc@@OO|3rsA3A`|M zbhPDVW_EFLVRB(-vavT|X651GVP;`tW@BT7wqSH{vv$;TWwdsn{tuD=p(AGGU|?@% z>u6?UP5GOyp1zHfqri(7zdQQt_n&bZxtjgmleNR2(}GTr`F9O7D-#Rze?xOLGyZ>| z{jT{B+8^`!Pj~#kiSf#txf)rjiJ4hJml`@WK~@%a4*oy-`M;|E9_fEjl^u-iMQyC0 zn2v&fZ`Pm4|E&Bs;vapg|J^4WC+9!A{720{Fn`a1SHZ}^#?tBc8Y)|xISR7zGyga6 zzp>Q+MiXS^fKKlZq<=#HjiL5mG5!htH-?KlAUs{|7Jp zhtd9kLOD$knVUI;PZC}mt> zburKj7^lLinimy$O3!lz%AC)|j8*1Vg?Y-HAoJo$4Ru&J{hw&>YNB98U}n`Sq5}KU zq+g%&jnrQ^9Y40*w)AZzzpCWtN;*E8KTq>m_81npZc%154+uiU1ik>nh5)cAVZ|vUUVilWJp}My zR{}cZ49XrqrvJJ~kL12{MUsLlHg@9|3kZWv~ znUc9|YM74ZstX4i?tGrFea`ynOVw^^Qf&PO6In9{_VnAuGDu-Q)mjhjkX0=w3@$Z$ zRJNdz@m4WAg?mx`@0}F`W9@JX3%h)OT=B=~?~kJ|Zupfcp(sD9)mBOVU#7W(gLo3* zCwNLYi}YV=DPdCm!Rh0)JEUrA$p2;{u?&|Nqhn()Zv5YDG7+dKE5F*MIIHc6bmZ_t z0U{&cO`RLs{G0W50udo@HrCehUiVjI7Zhi!HqTFwvpOfiCtF*X+JX#Ss;a6ePv++4 z9XgJxg{>^;yCTGL$2Hr<#l>;{kNbg<#Uq^cim-5>YAmBo(!HjR;M*Pnlt&*k#rqcO z9X6{=y^2xE`Rf(|M@!A0yssOV`_4y%YzT5z73vxqc!@YI6Qxsmm9@{mP4{NK|BBo% znp&t*Tv28F$)G!|+$7Y# zDS|-}31P=#w!(<0`8(4JxAg+I1C>z-@)G57XXzVznCfVb7N7fl{Vg$_;wH zkqF80p9V~#aFumef}Gp1eOx&~@Te>rFf#GP%)Rs4{sdTa5#9|vcRQJfJoOZhovOHy zzV2`mxEuWuVE7z~yWRzJ z-y~lZEaI;5V@zox82F_L$AEy!cLZ-S2SGmip-u0l_o_ssK|m4E2`4Bnk|b|ms))7x zs!L~&i6Dq$1=voqg0U|;qsB~)(I0GQ67?k_dHAX0DxZ86>GNPvf826!IU4%`@imIs(nStepU%^!4zV8PBALqqRix09 zYaxT~;3{1Y#v!u zz-7rT7wth|_aqzM(;7YOEiBb0>fQ69jZdSp2UV7x!_8{&eC|zKFFpy2K`#avB|+|H*xe;cxw6T@!9h5Fd(w$95YDxf$XeAK@_87I@aXv_@|i_^sx$QP z`8NGInydA8=q$peiJIi$r`c4Amc^k)rLl}%?Co*$wKmV=#rUQ_#e?q8kpUF=$0hgu zVz@_e%6j-Va)bNw{Sr-EGb6~DFfrSYAxo!RKk(O%oX?$N{lnRSOkI3DXzMm7*}C5^ zrF{e@lyv!a*aeob-SZ-oB2GyjQjWNaiaLiPjB^)Epl+V#eP?z%JK}A;4#%z1k+=Ty z68g3d1>k6N!qV$LEb`xOd{q&Gb=*W1WW9~AZF;x#c;u2G)nWS&E^(xXQRCCAq&^r9 zPIWEvq6D<%7@SgYUQtrE1<_9R+fZc z&|(^Vo!DG=Y>5ObWcMb7M!R@k8 zM~h9y1i=p}2or##^~Rsm*X&}%ApV0ti=^^#!#-28RdZOFMay@Y1o@vBy~(2!oV`J^)_l=`3OMn?;FP_U>9zxgy4QsfNlUJ8OUSr&^zT zuG<8kTY}^Z{-#39uuq7ap@FbwUXcPe_8!0(vlnddxf)0i|Dl8BpA0|*qekG@-ouM$L0U9h9$jg@$XiJT+eS>#0qin3 z919aN$|{WAMd(BhA`5#0XpO9tTA#2Gy{!=@-U}YkU3^ta!lCB&%IGc*i5#}kx(v0V zj23m2rRaNDeJvlX-wEYGj)X4^X2`y_&U1W?9fEKGijgmS?K62^7~hY}9!)x2B=7s7 zsfd;!hMKp&*<8X9B)~)|;gJOQ105<0m7hKw70eUlcu64i*jktL!S;BrUbM#AN(nf~ z8LoQZ!m&5UYHFN(BEl-}g&78*ct9g$k)IwzW3%s)B+4f~8nq9GXXYF!yM2pD*dv4} zYuZmOU!DPIT*;3D#H6~NZ}lC_R)+rqI#fXw5=EyTK<5gbNt?|Ba3mVGJ&2t6wg-HM>-eHC+|KNMigIi8;$<5p zH}q_?x+XGOON0*~GG#}78IL*adHO|%WB1$iG9Kq%!(q)l8x`=S>v~TlR}N8Prkrk! zB2_zU52?Y(HB@BP0@12n_|Gez@+QKfK~vTB4O%x{ zSsS~P!<4!S60Fcug$Q?AZKToY;O%xg^KVsbhzyen=Bv^?g28i&!*IFD-safrzf~`= z2^GYNaLQ8mVZ4=*4tA! zkC2kmAk^aNA#inY-rd8fo;!m_h#4T`ush8p#bb%2D7oz$-+fw7)giKUbMBj=1rJM; z8O)%zwt}qU-c0#50wH5c^E?F`%jN@%x!jI`)c$qLgj8ow>bYOd9t07N2Q2L{0VsTL z54Yk94Xris4ugF$_Ha8~a-*OM#sXvU{_ zpr^~ww&NS!`3n{irJpFTs}Z>Fw>YcsWRlV;A;7ilS^3IqH#;Fr807FZEjy@zd-!W7 zc#=pYKp!+|j%rQFGM9(Z6-~?%kSP&alWH4aGgs9|sE(=RF?6;$z;2A_CU}`IhsUCs zhcif%r72Go>k>kD(T4>d z4JBn%h!9vjc(wIJBtjQ1-8(8wJBO=TljOEnM#R2vcVI`_cVSKD6`}lplUgoz*ZMf$ zT5Ynu=_d#Qa-g^Ebb{;7Xnf9SicR~@V=I28zwqh*Nd>3G?Yu_ClO} zgFoq>`EP$_g#m_;FeAW_9h7x4XgAGdHZBCUZIwRlm9lAw24bFp>w-*xO{}skg7eh0 zkhTpeojV22CwcOdU|WW_i9(c7w&!WrO+5i&M?KsUUZ%^-kBiGhG7zRTw=E)`qq=o| zBCCJMI8s~~H96hlU?;YztNrQHS|_>%Ojs}?1P!IA^)$E|bE@cV0UBVdrA4nwI+ax_ zD3Z{~r*n_QFskGfJ|7;y0h2)lS={s8>mKGzR5WqM5vaQ#EiQ<3Ob#;wxPXJXz+z`{ro1JBMg}9BAx!rZivkwg6)C zm7@hlc*J=2#}%*F(dZpO0NiO%CRnN08ALoHUpj*OfPVufo$THsUl_IiX>8Tsc~M34 zYxXc-QXT-0iv5<0yH)21hR2i$lHXI}%2VmMJH~)Ks9he_Mm_#zoyCZ$j_@p&}^gMi&+?(NSUj8@d_cf^J!PW!PsF_^|4Aq^)I_EGz_Z&#l2ETYN&y z!%xe1D!#Tbbn7gmH{8cf=hB!Tqv}xjeXdM=j2k!2pgK#zM_-h)r-zYeewmp1_8kzy zC_ZW$)YyC={)k3s}ws#7(+G9z-gkinF$-J2MH zUgFX`z(nH+gtuj;p=~SiqI)Sh;9KK|g6v5n6n>1%<$TKAaI&1PDx3Xw3hXDi+yVkt=mtfPI>(I_>WHm$*LqyZ+Uue_Dm7e6&z?1E|a zL(@2kn1Su0n40jKRC-C<24`t)YetTLZbg*$$sfqTrB=2d!ogy7sJX`AX+p>{VUp&S zgBKXAHN0}x7D0GGn7aklUm76ayBC4^p}dGB+v8G1EKk;D``%;kY3ggU!WY%^tUwkI zdI^oig7FL+lprc2e{kLm_(L*stSyq!r>)du&s?%c+inuRk;JI+MUw+(QIyOSVEk_# znSBVEzcDWHwBo6_Gqy#qg;+FiOI#`R=nrgBtL@7xZ1EAl#38Me6Sj z!}PtPguHz@#i*8C*WLDoxrO$|AId(KoKt9i&TY0tLT2C086s;c4}_%vB0wXU%b7P- zh_>IgWvuF8%L)p_-BelfxGj!->Hf*cyH>|JVx2)j$~KHx0lP-bH-c94U@^SMd0$__jS+7Yz-~|Vpwg?vFv6w}0pv4Y+3zjL6= zpq2Guc~anOOjK(Ulq4?COiim4Ogs}{bWRWk?|@KW(`xP^^x;kvU`4iwdQf7PY!6yh z^~szr!jY%Vm5_s!T@yKczLl^qVq(YYTPpGjKaWwr$lmLb$>tu>wq&VqY%fP8%{Lt= z8k>Ju%y>>599Z4oU67p*yCSEhNbB=WLwWtR=5@IK!RS&6XU0H{^juEO9B3$fzocGUO&WpkIpvr4JDm5v)k9LPCrCk^ChekvOd5@&-&*xj4wp^T~pPB9!gYm~3?;2H(-IFSDkO1o|DItzwb~0grkiJc(0zHTF8DTDh=a@LjuWQkNsL+1 zWzJ$jiq+{(kIgE_oSok8puALP<;Bm#4-U)&Uzm&H38so-6DG8O5YPx-&8!y$Dj4aM z`v)_Jcu)IzhDAR4|s5FCk?B4HC>LVQ`dwBnPI*iWK=x=i^K6AbC#?;I8Rk zUNhg8FMz2-HF{A|K+nhTmnc-gsU)?Y^)E2g5^_;I4mAB6`Qt`o8@mOJEK5v8u<3r( zu#^2wRE$q!9x-=J<_GE~lmPeg%nGP=R}C`H)?1MjNMqB8ES8SrksveZ!FS$oMqsv) zm$@OBN<7i+yd${W>DcOBeiRq-pQV(@!{@0o{w{+qh~pPvPhy_>>uQ)b6DmY_x9JXl zez~^~H@A>qavYa|6M)$~YZhyEvuUz!3BEUetTwQhZ8jgb0 z+Y}yd-X#b5BieqXcxrpl^#h5*4I#}ii2+&HDxFL*pbRDk#7UWsd|x5(dU2J}#rg@n znUQ+IUbe=VO<$|92{i-T%Mi<7esU7V|t?j496ORZ+a4L={@f<4` zET(*>v$A5Xf~%mamXmT$Igk;^a>0VBv4YrPB6ywz`XDVV8dY}Z_3Gy3 zFXl@aB~P@jbOfedX(6&XWiS2uzvMRfni?=9YwEkkePX{T?IHeLhClz^!cW-!b_KfZ z8;LmDCd43aDM9@WTCG+Lb}OJwF&UYB3&_Hi_{At0Vpf$(_Wmq!v16Tgw_Tk#&-#hF z!zFvN`}-f-Avb4t)df~%AGGCH+xI3d9Q`c=AFjAshQsAPu~z zuy8g$_wz%kOTH_7LnWtrNTJ@g+J`+tPvwDrDyN@2uZXK=TPseWruIB*6mIRB{ZZqw z;QXUFcT3IaY)+juf9)DeDb5H(Y7u`Ey%p)MWTX?MTR2yDPw%AZ#BAc?3jw#WzYwA4 z&o#N{3%Q}xC)8i_L8ngGo^YE3I!_hvgrkB^=cf{A$JloMwuId|VO(q1>ET96168P= z%9esvRw2IrMhji}Y)*4}Y+gA97Lln@i>wf*A2vHq*K@fh)csRrA7GKqSP=_@(8+R$ z2QC{uOP_U^u)GGJX)vN96i?nLB-x{mz!_WG=-#)+OyF+JmIk6obc4kZx z{Z$$Y3s+Z8ii+LV#&iNT<#29S5QNk!9(7%zKCO}%J4ETp4WJm0FKDl$cqjjjC!v|N zZ9=FVbD=05%id-fd-CSmJPx`z5=M^~|w1n7p+SxToT>U>6KdROkon zLr;ZZb=(NpEUvTM8T|x&wb(r&XRCR4V|6$LTga~XZ3GVo4JFH)>}KB|U`1==;gtNC zI@>JZ96=i_Gn%gLh`_a>2&`QCoY3>_2)nTm9$S`Kf4$vm2!#z$T-Sa8iAyD)So6*Z zbsGDPs`IPWHo$rZoC>D?N}_Z*usC<5g}a6Ck>aKeL3*Gq@FTY$WPtY(5heg2$Yk4n z0u2N((348{z}-Qb{#Jm%$Kn7O7*jk^7RXS~=0HNeieaXAM~SeD8cnjU?p#%;ksmod zVUf@g+^`y5&9Skw#4oskBkK?;K8x(#dar zrYunMfB&)dvjMDg7F1zFwmL?S|IS zLpu!ixe4~6#cTrKQ#Nbi_`xs}Y%V&_TfuF&VZnbsB51w&DZNOd7e_n+7mmiYvwb-$1~g7`x#K)5iE-!bZxWBp!cVF} zv3%3bRl&I;`A+q~QB*(8`#T^Vi$PEuV&~k6#x-NzSbHo#7v-JP!GP#$3)>Fol3{a! z0&gDl{B-oBmtKU|J3^}a03c@tiW@IuGsTPO$e zD46AY6yPqk%w11nQYHQD$3!=q_0gkrT@fccE{^cjrj035A3z zsB3tc{XNQXMdSML9RVmaI3FxCYqpLXLc$Gd-qKiX%#92 zYdFN;wv7GDRU=R|Z~#XU>d2gKK609k;8D7;yCuDYMT1}9^0gnU$-1St^IsLx6hd3u zd&ofXoyeu19q#Dab&sM ztp{9uJSqtQ zEbs11zRrM}m84sqr-+I1B)9K8eg-U0>hh+Ty8LU3!MZoUya~E|pMD44NXYb?Aox^D zFkU$#SYadtERM#X)balcw2oFI>M9~yBtio|T~lRh82Va#vegJnC*5B!aO`RN6YX#g z6u66;H5|i>@X@e##t{jNYXg!wgLf0Kh%c=D-`w$Z780qia3UqNz3Z!u491mJaxzH94P4)Y!J8*DVStleDQoi3cH^BvGOb^}=8)_3-{ z&D7=Pg2B0Uhi%vwK_jMjl`uF+?)2{*FvT)bJ`hUf-TaP7;dzpE64~-!DamAcrm{d&G15Or}RBQg71kgX}Q~z?nSUdtil>0ycUN9J*taj z?D+XBSoQo0gqrwuO1PbW>*?g7079ywx2F+3#zdU8(D?PG6x_JcQK4+q?428rXS?nS z*i%Ql8ve%3kNocCY*C3gfF$asGibIBNzt$>;)_9q(EE5sEru^?H<{kTdf8fMk2{%> zz4^H<+Rw7pXuBU=Q?IVFw~i;Oxa{nB4@z2|E=#$@Ne89M?^p!i2A@x)-*nNyZr8K& zwCVmzFBtC(uP95_(S&dN#_LqTivQdDVuk=F-fi61eNe1+%z8%%i#RHBqNRgKZ(9q4 znkbpKnki_K$SC5|X1WSfEyYwXwl$P&l0ECSUz-4CLb z8A?2(v~D9FOVo8coS?JuWoAAPl`P=Xfk25`j#-M6$Ar9IMC&0Q9Uap-` z0M;nBFEFBw9WRy=b%b2xb=Y@YzAR94n%C9r8B`lv*6zw+Ck&GrZ6-ks(9{j=%f2Cu z$n{5)0kn={6H|>2+k()D2ozS;B=INsfS5Pin0!aae6U;At;7T=(WP* z83Kscyl4E){B!?MTqz+ge4Na-$9Ms-^EJn@oe_!DxlOAwXLszwQ(qP%CaYIzaY(_E zV5kELn!GaA?;ngB*QaRGyIQ$DmkY*fv|iMh6xY$8n;U)Iv*o2)91RU=?Nmi01tB*+ z&|+^ljSrlkStq#o<_BwFC$h8n?rkh?t@d?a_AkPuZ@t+R=le)8k@Aru zk#0hD@WSuCn3s0|FwEu^d~ zJ7&9rn1dZg42w0YNqgk+t)+6I4Jg6XX1hiN<1*n>hRw=Pz%ty1i%JvGq@*D0aR+w@ zHvu;&kD;_dpY4A&p~{4|*W)0OPNnIui!kVF!$}xScs|O~MzKS<4s+hqK1|h2giw?E zXHpDU2MpMEGsP0^B=Ga=?k+Bn^{ONF8vIO0%dH?DL0r5)1cW1@@cO=d0K(cKhAqhY zaLnFjN9t*8HyLVFVY-@mwovhkjxw*@10=_@sl@kUaTdlO`i+kx8aG4SzY}lLA^@Bm z8Sg^|I_#QkZ$f_A`=!+iZrx1JY@-zxS}1`>n+Yl|9S3IwUyN$QmL&zD)u`53#?Vms zs6E|``Zt_5sBlfw%&2x{plR*GyXSMt%gZC<1^qkyXw(|kRuk_!L|77%nWLCXL6w_! z^}0EDMQyT!R17HG7;Px>ViX@nxxnkwtNU+P{pOEFYkuj=R3keA@asyVj}PY~3GA2H z7sJ^2&bmkvvUq`X#>GBFXTWRF1?>Fe)WwMpTBEBpV)9 z9xBjNxu``iAmw)J-0>lmUYU-&&15oxNz442NJ+)K_nok?#HfMxKSCy$z!#%(SZ&jt zC$nd&7X$DT(-B~Hi-e*00OeBb6HBE%}hmH-x`JyfEcIRWOT6TgFB zrBVOyv2}Ng&$f6qq7v~+i-(gMpdwd82gO=8F8~kRIY(7lU5ug<-Hc=WWVqfPj#nG* z=NJ0jK>T0(AcQs3;+1?bSFN}QkH%@`S3p;R_xKfiW}iF|k|so`rb<%{+BdvTTXZg` zI>O2CvK3SHBR+@j;GbOmTaNmAhXGtMG&5?fEHV#fT9mI`R>rElQK=hcVR`X<3kv9F z>kV=bW}6i;V3cPGi>qLek?Gz=-2QbO|3M5aB%ym>BR*SY*Q}M+&4+x)!&-JP0Pw|c z=G(xVt@jE>?}=C>Qkb7?b~`BV+3>wTPN2bOik&P}o##PHF`KKlNLcI$NhYKU@6s_I z&4oexOek+^)Hn2tPL4cPJkBxKw^!xh8bY{4*I-CKb&Qzh`WyDDJPZk$ky&u&r_sxD zGo|+~#JzX#pMPGoot9>b&}@WA(Yt<#Nb&^a>`FC^x5XI!gbh^D7VhJBbJXyJHcpI% z`^^8Nz{31;!76+rpLm(FInSc27mLk&f;LIetAP}1in7@(@&C@_NL7_2`2G53P*6eE z;YVrJn$LKW+-6AgNxq< zi|6D1a?~p+{2n;t7Gi%}YSOq9mKm0IootIS7#`seRNr_mh7}DCW!eE4g93SRTDrPV zgQ3n9N;s?=Log&)G{UI@Ix4;eEJz90}s?V;GFAfrzds;soJV|q)k@P4lw zQ^Ch3(Z?CGaPu8kcO^`z*l}E zVj=#^@kXZv@7cdPa>V1s+kTrG7#rJn z3qHPC+E1uOHFQ}>DxM)4!|SG#?K-dCzSVs6*B^|0hXOM>`Sxnk-zvPcSKmlh+uNM&fyPwBJ4HbLP({{(#J2MUL1Fx;eaqVCf?WeG(H4GS@mPV})Q z8Thl`%B*oe?*$5fs_m2ET4tDjTz9H)8!(2$U{1jo2s#1QU&eoW!(h6;I6w=Pg6>=} ze`F0p?d2Gtzr?`I=~ttn&|IVOAOfSg!OqPp_K|3!;QdrM_gX2MGU8?u+L|;(M=Tgj zY)G*B)1adLvVah$-r^0?_{PoGk(hUpPa2%XI@Ae1JEHbb!%ww}d^a`|0qUHM8*H^| z5r(`PtG5wQO;f)M^ITi^Aq#|6Qx`WF?ka8Hp_HD1dXz7?#6LyVD;se$i;JGYaL9y- z?nz5VO(d99ot=C&k+bOkfwF;GL;=BiP6?Cr=>sUNt=tS4lMD+bPK3Wcv3O0vO&${^ zcbww0PeQz5^R#7SxXUiJS-joy?jFwfOYeitNJ3$03-3M+*&m|6B^1yyE4ZJBv+0zZ zPJ@$6MpdXPK!dSxkK?xXYq^l+GR`vXxbV@WHKgr37$D-ecxMNpA%FCLI5?u{|E4_B zAJE6Xd1>1+Qr>Huph716t8S+jCNaO$-eUzmB8#=)5@ExcTyOTxctu)u5-J%w>@=LGw&FnT(2>~(w zKH|X3?{wSRc#~N733%++i4iuq3}pW}4)J_h`bZg~yK(6P{JN?mGk%&@;0 zbwZrs9?QNCLfhXUxL$ta@-X7EaXU$Gbf@RA8Rm8zPb5K_iE@cvc{AmU9_Lp|;;F`L z>-PbRFyMU2J#E2pJ=|{qH#DiR%ov@IF$t0Kgn-H{7Bx#TB<%|#t%a^*H67Qw8VB&q z_?A(0Yk)}!m8!f7M#?~+=*d*w2bLC$qg$Rqs=9`uW#^`_(V1x5<@6UeK}PFZ)8 z12#*S`&kSE!vT!_VK+Ixm98NW>9HQpRcPG)2~@q$cR)>X8{&n`?Fec8V|N8ZA;Tle z4Nnw8WWe*4v%XuSe4O1u77bpHJeT{G)puL_<65q|CBvrmku%9*Bf7$D{Izm^d2>M_LO@DPHEC!e9avmZA7u9 zZnUOy#1>UCVsZmy*MJj zoj2`@ei_xf&OtgD7LIGd)}K{d7i?#@I#a%`!Ca)zXN9!4FK^}Zh0W}9Oo_uCl@n{L zj$iP59QSfl>6mH&)l{i1w|rOnMbBx^Eln~e3)js?#*ra*wft}^+{p%WJU}Tz^XaMm zS7Tvmy~fj4>2vqV!`*bN^_+{FcEca)l^22U<6SkOrargF(R()UHAgq+-Svu_&&&Ot zt@qjd=My4H4in{P1?#dg`Qzx7r5V0hBILhU2nI%&8&iew{@XXh!#M)OXk^4R%^Qi? zCbvvf3PFuPJA&SiJt+)OJ$pJkxkOk=5*ng_rW2_^ zTRv>Op?rB_`UNq1^+{OAtv=rOzB1rf^kLNfT>K>gq7JFaFS`&jV$d0@(F-tcto=h=)wWFIv5UriJF@jvYP z8DfD9Zd|GRDw|-3lha?3OA$9YBJe75f74=w$w$L@h-WSU{w7x6tJC%|MZdU8UU!sc z*EiLi28NEKaH@4{%XqfjE7^Rl>E%EIQ+#d$KlEb{tJnr%Q6=sZjU(fd0=}$J;aR2x8p*LAY;q|) zUm+YzexjOdhL7ll^T@K`&o?A5vxLH4yp^R491c_C;3m@Q#SFvwI{QA9kX+7tetDqprX5KQ4s_p*p)ov*O|tJwsb}-*#$t0n zfaQzVC8&8L#$>xgVOpN>hy~1bx*(ea?Q@kRDAUIeI1Hr4Y)|vk@VOks24Q7x?BFa> z1`qM7^!(zO?1CCA5!?}@w;Hg!?0IVPWwxswYO--D-g6{pY?XnUTfH-3{9Q!9ibD8X z>q9v}WrJsfcF_q&WwwzMqk~C26&d^imn8TR+4D$waNm%+gmMe@i%@$5D%ec6C?DTw z0c%LHB7{0S%=)Y%TA>0t>3P|9&g?a9+xhTv(yV`u|8IxEC^IIY6661H_0?ffec#ul zba!`1cZzfgNK1Ezbk~pq(k+ODfOK~bB_JUk(%s!Tyca*;`n`|;;XE+++;jHXd+oK> zwk&cOQ%UE$U`!XV`7!*~w!BQ|EV<0jBhr?DC1oeeUw2eb8Nhc0)GUWH3x>xuiYp!C zb=%v}P_dF1yZ3XA#QNS}JHzP17&|?Wg2z=e4iz|`_8U;eWi`+ zv-o-cYj{xlfSGC)dx@le(cseBrFi07*Orhg;YsDkZ?@Q6bWhHY&XBc75+eeWPp6); z=N02Qe%Av_2tLolSt881-@$jn+$i7wdEQFQD()$v^mX;mIEAG2W>*3*Y$m?WBV0Pw zhw|ReS1uOu{Js{@c0O`}qa;N% zdbCau@%0#DdqN&iU@uF+8)ZwGW=wVbE&6n$yK)GjCt;ly{UAP9qbSG7s(U zqiXHih=`y@37i7^30G_f`uBt{NkP-|%X`U(&HPPV;y zah~X~C=R_Ms?_&yOHG_0D|~gNf6hB3RLuDDYk`dDYhjm50f1Q-ZRmGv>+QXS;t}3e zvV&jRxeBjSAY2L?ASgz!l!ecOrs2f#erNjATAq14mpVriQirg|hlj}v6C+^^Xha0D z9c|RZ#b)&e$0ceKzB1mmPUPu)oyKc#JmSOg)>oef5y?xqjyoA&EP+N{Xb%Z##vp`|j{@2H#U%^81 zc(xIAq10t#nB)OTfph3H%8#zPG*2d>p+-Z5k=xiTS{bYb9)Q3{;z zy&X+xM`kGkje>i^`QZX_ll%Jml2`v}=3e^3DH`Xr=5qkDo@99+K@uJ`!vd@7*-E|g z8$y;8f)DK6DTmX{*(u^71OwLXYyOYDuemJdQQH@cTcRmMbv~hz{UD$UCPMi{j1UWV zbS%*shusuI7yqv6P@?e<(wBfCAnD|0m?{ly~Y11fFh_LhGNP89zoP-2XMN+kB23(a;#T9}RYEnlLVbE9xxIxOVU?p1SOsU17xn=j;AER1udVok-Rr3%^sq5I!| z&8Xh37k83`d@Nj`j?WYLcnVvmA+%9EaaqKP4_ipAq^V+(z>nX3k0-tExS2SiPDwUF zA%p!=y8i|{vmrX-qecba;5}|Fi+b@>^TMt9UeTNVYcT{kU?3v5E#e{OhpRRX2R^+C z3Cl$UrM>x7IVhyk6)Eo?l_qIo~QQtyGk{PqsF$kvuY*dl|TKX zJ@sLO`2n9}*I@g!FK)1e*`8(`!NU`s`fJj1FSA9t9*yr$`z!WJkT_ra<99CWubV%2 zX6FjGPJa{U6OWDw5DGg4FO!QJ$^eoa+bP0!0+Tv&Eo+XLkj&8mXT(P|ONR^~I=$8v zdlnC0^=))qp^wqy`-T6s0IZG@$;^R2fT|7sNV8}}uD(8T(B z>PP8JCvK}2Y?{}42h0>KafsM(n5hWf&S!Xds2)O&`ffV3R|SwzI`wOm@EG3wxS`jL zG)#9D7O4WMc>$X-3(FrAqs3Ur5$-KJ2lqICbgcf4L=3JP|%vNHuVL?-SVi`ixLP3BYOxZ{Xp zN;wlF6wS?GD%xOMdC~~U?5{pF5)F7iJbdB%qq{CHeXy*k8oP3@79K#j1Q?s|Io7%` zpV&mU?GfopN=ox|1>X+s)`GOz$@~t|^BAaASa3UC`zqc-I@|b5jiGd*F=50At5n%3 zL!E%Cu}`aEmjLAwv4dQslQV6NrhjlM-8JP$U#V{6-f;P#qU8r!bJEzmOmCt>AN|}Zz zBy>bos306foxz7>`dx=anuEbl5>ywqFt&rSw3NR8$LZQW-YZQ=Pp#hgw)rV91-i> ztTS0W&M;3lN@d`SI(((Q-K6fw;n0X<5PL9=wk2O3fPq6o>ya(_+_dbeBCO(=Ls67n zgmOa;6_3BffhOGD9(8u$j8UsVl5Z#EkFYTWZM=4D0`jZ#yE8q4o*{V2}^A^&93b4wAHC7gvt!-aX!t3)e9^K z8f+1Z*jgSSf4it|QE|X}W@UNU@`X}yiygPxJdCwNX!0ln&*uJW)Cz-YrQ(>c?#a49 zw}3p)J_rBX3&2RV910PJ@4%nntEK?d^YKz^%3HBFtn`@Owc#qp$+qQUX2vYX?Oy~~ z5no+?U*ESC(M24=!IKEx$+Dw$dyn8N^O_73w-s?QVqFE#;y zth#Vju$gw`yIR&sx8(WQpbm`J-a&_e+ZyItRZfPQUr5m`wcmI>5j>*a1IkVPKet#^y(hkO#NeJjgx=ank?DW+w6zTQ5w zDjTWo-)~bQ_IbPq5~LgVjGtl&&PF}s)Dx+mpwVJQ1LG&8kGCK5Y8jXJGeOKg&v4x^ zp9Gc0rT=Nlg(E^EVwTxjqfHT-2`I*~8prnWprxGAFNH1V$r4=eHy)duEY}-&*sJFT zA!9UrTq?8O|2pL`ReL~k$o=MUJc7_GBwkWfl+i+z}2@a@_(1<8b z(Z(lP?KPFn^6)}Ml0;qxt*bgyQ|KxtO5dV2jsk6ZWWVMhc7HbkfD6AAsCih}s|fjT zebD&pa~*BWYC7_u@82tzqD>BVm6FR60R^Ri8HV)e1ga zs?g>q9ZH#xyZzHN>ZFo{B1SM2Bn<8(qlB{>L14Pj*5p<5_qvcQZ~Md66ruvfzskRd z1#eAtsPYQGb*|O3gb&7}dTllj)!5KkxqGI3L1V*l`kfJG7>pS6$l`N3Z{lX4b3?uj-8daZ zWzX`DyuwkmCeK`(<$*L(bna3Rx|?mT#YVTTj0Ey{$ig1mc8Z$anjql$%{4Kbg4d_p zDg})jl+(QzQK0NK2q!~*VWvI_KXz5Z5I>bsR@Dl_#mj`jk8ML;#9dE)nFp0$8=b+* z+-vbSiSNsUHt&-n2JQK`(|m{n#-yLg^!4MP6^aJSchC~OTrrQfudKfdZLAm3vkG-_OpIB z$09$tFsP(zxu%-jS3G_`p<&;B4mnn1D#1-E z2C8&T>^rpngg1ePBf!Mhrl3}LXoYk?a$*en(S`1btsQc!{wgRI3AxicXpe;};A3V#E(eX=*IhwhT@r*fBK)$# zwrCaUQ~0-9;bvVqzev_Bd<}$-c^Tx9Zu$z-*xn85>B?4qg!cv-GFlWIzE zhMI@FuBua=Ued%Vli|EGf1)?NpP&`Fgyj^skY&&6>He_02j!MFhv9ePoQ^T{CVbYp zcOv1s@}oRxFHpskG|u1uG3;B+^*BhbG^%{wY1=*Lm>6w*De2QEA0lCoGR3caO0mMn z0)IPDw;OTFPz8SzH8D7JI+)*IS_~HPhff6N1C{rKEL=L^QPGz}EUUCN%yw>aXD~j^ zPCYyS5aj@SY#VK8Xc-#iaiX$o_!f3M()GH;OPs)Q-~}vVV8S6;e&wLjpFymDa59oG zzumx!e1ocsEPes?6zII*V=68%h}r#QU`dR+OTj}}lZUSQK$W@?5r(v!U!cyo@MG4NEvIqh>pRJi9zm784&gJLMuFs(wCl83@K~n2oryTVSKx8 zc;7|uP=DxS;}ZP~sLY~UhnQY2wTU6MgI+&U%&NZdfHOg7BSFv1@F4ymxvj!j65a)c zm^X31qf^equ66#PFvE_ljs!%A0`VIImJUFmkkln9zJkt}Y=V3W zkJ0zC68&3{f{)z~W(I34hPE2PPvw5k4=ZUvqqf#%vAX}4tmw6B^X~U3w^<7@KFta7 ze{NP0jG*K@kuNjwKlC&Y?9l18G8!&h=I70ZRT^>zym3PtxeA7CQpy-R-^Ga*;hYGh z=;IcAZ)Bhfj1&D}4HJU$&P8i8;i@Z~nj0yJD*)d7N7is9`lw@brp!mIOnz2{Y=mOw zP#*}u$Tx}&m>zez+e)*N2##N5`lUuQe2HZ$8G^_Bfq9pS66k;K|0bnk^SYYt;T3ez zu87A&nzn|y3OA<;zG6o2T`3B=L?B!btaip;;v5dw;`9y1x33>=Z%&60jEe5 zuxlj+eTMBf%Auu+6e9?F|Gkx9e5IR#V9+AXz~KZ8kAklazWtZL&>st$V=e_EJrer; z@spZ94qWd9uS9XnyENAvk1+Reu-U_U?VhiHiFO`Tse|v-JME75p1Da2MAbi`^7&Kz zM^?S#>D|q^1v&P;i?(X#c~C?3wS&S8E}HGO?hh&&yFxMtxXS{*WIi`E{@N?drUC>I zsb@I!0|RTf)U+(mvHdiFl#P0?p=H!5=*1xY6|hH-nRICwoE~Z}U_5Wuau+M&;55hPud<)XmZ4 z=$o}4)=!6%v1G#es*YoZjRECAdtZ^If;Ov&DHnYb8zLyd+- z`5$5rlUUAr9)j`265)ns*29E(#c;M2m)3ENw71ZZ>7fCqUYb@OYP3(q)8dW`RZyzu z?>{>O69o!eN!j=2xY#+??EIBgwfnMy;Wf`?*|$j$KTOlIG%nI-lWi2R*noaBfAF?I zu~sCL6p8!KTY;Hjf^DOGswY82B1LKReqMw{;1fS?+;ZD6{f!^POF14VK@T?GfGg0} zp|_Ql`-;mDD-z0z4ru@#a0JJm%i$C*`}d(U%4m8M_Qyf~AY6Q<&TBQR~G?=-I|r zmhZ{*&FaI(q8i<-5vaYLA?hqA`+pdK2ogD_;^GZ{p6Id!#_2dL?~lj%iWu(o4rP3K zhSI#Ky8W}*hH~~K^i#WQ`1U+yE1T9U6-TgbM&J5oE&3g>-j67`$xnJXKkbUIakcwC zjNV6z?y;xixbbXDW^IT<_gFIBc+NZy-4$B>7WH~wav3Uj`Q7Gv*t}s{i~+i(x#!Jk z_cH&lIs*#Jn*U|{sz|mdhL1iq7<;K4Kh1NZ=GlI$ukYYtCf+RgE~ypNO?z(p7i_|$uZDOB_bvWJ_vv~ ztZJ!uz=*Irpz#gQjm`biO5}@>ER4tb*$#(FRLKACn|4GEHpVgObk>2>4FWu=KGup^ zx3v;zP8Pd(Y0K9v0;3L*wfV<#l4}8`Ya2DzOjCWRXoFSDwEv{-IxdwY>}n@Bt&=GYi*p@H1QMr3;aeTTw@%?j~TcmCTUkMcu}5 zr|{3!^0(LLL8;Q5(}^;wb=dc2QyaTA+I5_W)$ zBp_?6b@d~GoTPvIT|$}gzO2Oz@uT*Fgo2Lf8N-wN)9ntBG56f@cZAM+J@cSerU895 zN?(c<&lP_{q5t#t31G;uapA5BuXbycM6}!fz@Urcr{8@a?sQ;29r)5$-iB%(UhI+r z!Nh~9Ukqyzh6!PMFS_COG;(3d>tXUF&z_fb<+%eqHBm|u1Qj_}G#1Q(Ta(arx3U~n zm>uBbg?AwAveq3&Ec1W#>QrM?;z1{5+jd;8VHve}dZ!um;wlVdfrLc7=)(xPVyfgt z$@FxVTNQ@w^NU5G>%%`(N{qDmU5=_c-*iBR@=ora&pIF~m|i15M8b^t%f9Q(h(k`` zIp|F$<9CC#he9ar!WMEN$Z5c3bJxn(pGSrNdkPCMcLQ@M4c``_&>97FrLat+?AP*S zso;dE&yeTHKV^!#(1mFuUopJ40GNgY#uh-Mi7N6S>wdh~SVHomp&GI|E+}V7B7>ex zzdvv>ib4^4C&qn2fG$9jfi1TdoPcJuQDSB{fxKmDu6JFD(cq(2WlZTuHAlm9Bz z|Gx7EOrXCQU)^g`Za&0TCYM;C)gXqHAos=S65(Uu7hcEgSK40)5=lh{a3Xp5_$-^l z`B58-R6g8HW!KX!UhkWte&&Br(wdqg6|k( zh?%Rm)A}JTWIhn@P$|mO+oHaCdEIAxlM$2sdk>^qu#Kka9abg^O*cUWY&+-2Kd4xR zk?(elev3+9l={HtV`(D{v|DZzKJ2mkiykjF59!JLSK~?vgCO-13@gwB;p1KJi@7me z(-y2#no;o85Z%@5xFamSSbeaXTy9SXJFHTUY$p8kiK3s2cr$U2=>T?~$ZfSfpqmz6 z70?rWi@@bViv67lRV^Dw*+dHUMvj%z)iRca5rgW9=%pFkD@r|f{rI&DE*v^t<-_(c zX+9Wrnp9A-fA9AZOUuP+Woc=xn(7K*ZdgOS``^njqB97yNM9vBC7MpIX5v@o@c@Cy zo(XK+&v%o`ggFCMZNp}@9B9bSEvq)R5>@FL(Dk+qd)$S>Lf`<6!;eTAc!pZ@xEN+n zu~JqdARsT>GlI{ja##v`$p74o?; zUbwpDLMu)z$f&sNF*TazWR<^6$6L%@Y#N)+QAre9jr}<@Lt2DlpaQ<4tz%;q3B`T> z@mi?(xlD8;T+J;i0RzGVe>N)fflx+99zo=9`XDejF^(7Vxr_(C-^eo#oI#A2zd%!z z`1Ei?AVT8vKM9Tsc@V7g9}Cw8+BDqIpUMqzE=Xc^7bu4++S+`!y6*W31H%Layxun6 zC72)RMjpOuqELluy-vgFz97Bx5Vbvgyf2^nFkO$lTLY;L2y4JwEQg5zi%Z-qGv=vp zQ@lisWv=Hhm}5IZb>oz#|7W8HiTw%qA9eM@fnELtCh)$sFg`y3n!5Vw*w<^6k*0n=n%a{)X z6nU<2$$Zh2F2t`-6CtQ2z!?Fan(rB*ytP}9i-Prk-9vM5UqUkN8$Zsk6zqQ{3gq>_ zZ7O(tlo_@*3B#J{dxNNpa$IZiQJezZ{b`w>XHajC4E^hj!2)O|I|=s8lchV5<_Q`j zRy3RD{0v$fCPrJ^Bos@PeCqn~z=B3S10i4{J9o7*1dbwbvi(*|yf*KuM+WwVulw&! zuYw7UBdBp z#NZQ7#{1&TN;8tZd4-Khu`k_?NnGQm{G?l)uEKqQK-f#c7!R@MfipeLB|74G@1iT8 zhegoG)pr%tCps92MEA*P!bq%lj1nS4ksu}qdfNuW^ho!g6gWbx6}Tc~f+}P_ihs`T zY-luG?dQ{0Q?Q4g`+Bq09+trnI@ckRxs7I__v4EQyR^X?Scc;BeYvJJ48XsXsaR5Y z&Ifax3qS;(ogs}{Kft85H&$P04(SR^uHVIo*Zt3GJ5jzMV-eB|`!sCggJn`V-zjc% zW15vm(c!`%n50d|b(l?Np9xWiW+kn~5#Li|X`2hRLI4LD44w$^xKt=r0Y;NN=$kM^ z-A@n__^Lm99IoF;hzEHoU8SAl%Uf!(|Ku3Sk~y%!!A(pG&DYrVltTH@6k?%;#^MBT z8c6govj|fCnW~Me)#;tHM)VD|K&sLfZDlJIo>rpIT!94eIG}Y3OXLZPzTlB|cy+5& z(#J*@+ykUWK4g#O`UH+}9q5&SqQtDCj?*mT)%Mr1~wnAjQDvd0A#@AYXozkL7IByha zpk}jfpTds9SX9%QL}xS9^|$gMqWYDikSQNbZPgXee2PoQa-%+8N@`|{3@SN$*BQXATG;cZ9qzpK?-_2dyx_du7%>f!y%IV&xI3Y7zafC;3_-V ze3q}NEEh9)c8numk)g;Sj!!C7n~6twm@DZ6d- zX881dh_Ua@@ls8_b7`4g>61mFJbb8ik#ScPpiFK%76 zEY)A-QRQmhEIkJfe-qYX13yZh(}u!D4Blf+7K`SiW6_&OprL&jHs1R#jGuTV;u?w^ ztOXL$uca#7x~7iDMVg{W3_}DEGE7h&`UHh>1Z-cQyjwp9h2b9olp^WMJ|CF))6Cgo4jf1y~XM=bY@4AZm2L zW6pFm%w4$GyvAT;b3ll28^?jIdgM&;{x<|Pi9aq6KtR$-GV|-$k&nSY-hgKhJkq95 zp2i#WeUIv0o~~x?4uYFtK9*DPDK2|J?p*GP3WpALYR$VtBe_g_5s7%bs!%IPXn6rQ znGc#mrK;~u%*@y&<0Kzq`Ug10pQFybS|&UUQ=2atj2A!bzrj$0H$pXg0w||iKBvj3 zBmIfZm*E@i*|kwrDArqFs5&U4FO<_pMJH-jmMupfGAxX^d62JntDDJclEgS7W14|S`XT^y=t>|GaoXnVyH8KMG=JK7W93lOeV&t85mLG`I~-o}TxOKr_SR<| zWZg%x7Y9_nv+x(nn)U6uq5z;ZVh&Nl1o*)SNx zAD~e2TnI`9)P*yiu2FuFi|n*CLmzpEmc1W|jsmZ+{}apG5rRI)ksBu*_syB_WQkOD zq6@*8$m3A6_^QbuUb*c@p?(oetWiOOv;~+=V6Mbm<+T8Yuk^@Bt9A*l%Pz5ihD_~l zia}I|mR79ohWW%v_C5|<)v7`6Q{&fv5DbhwN_3G}(P0S-)EzL##ZCDg==Sv0-Bh6{ z0E`B_`~7z3kb^K8#-ZcRvUxkhZg31H3-+h)Q@D~g4xly)wY;vw{ zsVEl}iy#+=%}q9W`dQ?*4b)X)Y5(F)R1Vy}U_9B&W(347z4t8#l}QNXUO8UGKmJ=? z?n=La-ve_u(F|9ZmxgJ|Is%XTkq~^+G2RW22&e4AAuekjDmDd^t%CZn^9f9)1?j5| z{M|VHGpuQAM7-gzo(;D6i)GmfCGSfPTkh3&k<<2;CSTAEDK%QiPcyFQUN(y7-jzW5 zrv!*!?^;+I_#8KFF;9L9=jq!ygq0I=3TeHY(c1)ULXngv?kk$skpbP3U+h&2u{2O>}W69^Os)xpsdFi^2lc^yz(^Oy#kvTT3(q<<+Y* z&w)tMS5~B{FX0n21k&HMJ9XnY{-~~JkayCvVBRa&J}QQ`oaALR@$V~_5~dv|Xb>LW zdCg?$Jv-_cRLo}?E$sW17g>;GO*~mR)_IDoCecVpyu{Ghb5}WdN&8u=<6QHjTha4i zX{B1df{4Rd%#~iYWIs*S5|9;F#%14z2iZxc122&RL8>mAC0as^#DL?u-={kY-n}=_ zx*Edq2F>qh?-`=UJ;?;wf%6aqzr4v*J+leAZ8q_A`n=hvCIw#)!X( zwEU#uJ}Bx+r}5mCPzU7s@4}F__b&XRav~^b?EcAh&mwKGDp!)zU=X;#HUU;R2S4aP zX=BiGnAlb@%!yTpX^lJVRM|()m|I2uCCB!edTDJN-{anf;O_aJ^FDXP#STh&HLKqa zWzQWC$3yoXfT^)u$nrhud}?ER>wR|DT+GubhZe#?93=pr1z*NhL*oiSTCE^Ek9obX zFnv}ZxdHnMKL|X5`N`nJ3D}6_-G4LmOFlRuT1Lb}=$0~O{43)nQxpL>9rNudE?D96 zqXf=P48Ob)*MI}3c)9#db+}F4XsGx?{<4DcE3JOP*hPq z2};L9x+9$4xGY%rB1z-8A3p7DT`!*T2Xu$p8{02db&)9k7g6~q^b{4xmXaHk9%_9I ztTK1D=!pxnt$ zq~LK_%0NXH`u}2b@P0-{S=`9T19gaQC%w6%dxVt!8y3tYiwwk|p&CGf3~!n`tW|E2*>Nf%2<$=2M$(|tE{ z;)MAuM@5zgn#iW^JR@L8lT#Zf_>_Us6x=C*K^1$MrX(%a190qP+~8P?Vs5gj4kOWY zLTRCkXM@is{ckaTKByi@iXOH{UjI)gp&j`J8RJdQ$$6J?!@KqET3Wg0`!!`_!rWp< zg|jeofwdJW3gu|odxhhG+NBM6D_-qL@&gb2gR7R0#gg_W@%KCz9Y6e2VVvFFVI^&u zd0qmluIFY@!zA8{f)cvWbzvbIcp;QOGeM~*V{I{d}gyN5A1PHR# zQNP8n15mR>T5rq;{@t?tXLkaf5mZt_A`1he!{sDIYCFy5~zl% zx`7uA>wGI>0pChK=`wwi?3)%yU455fzp|Jta91lc0x z&h{8D^;-@CC15RXJP_ZdbK!Zsn9!^z?3W(;#k@FQ-_h95UC>1S`)JuJy2a11AFsj^ zgIDUgCMWb}$@YhPgg*cyJ08jzrv!8ONZWUR-?u;8RBD4DZVIhxp+eV>k)oKf8$7|^ zzAHQ`P{mVA-49uMxC_vc9X?_tlW8_8P*HSG;P!%BPs9666yE4ngX!A%heT=mFoPsR zpk~|-X3CRNn6(B!v@7(?dWHYsE&FTWFm{1iR9+|F6CO8)Xjz? zK;x7VgOcgz*rjeAT7f=Em)RfMS(Srw=3SyL9NCxDht<-UTVaye>!JFbM63-q zQv|3Cq@X{Hh$_9!)yzAfSTO2EmcVo20+hx{gKAec;8IhE_%g3Vr^BzAMW;pzFdC%c zw)&l%7c2Dc#UB1*c6u5q6!~w1A_(&7r3~DgEF6-=c=~;++hCubdDq7;^9fqK#jU#D z^tUd)mswThD*%eyM7NQoX5rE=AxYAG*QCDTOjTyLkHI3Si>7rbWiRD9l|2KLt02!w zpjS2w)%k5B4*16|OZkn&KG_-y?k@gjCy!oPnE-279ucs*cHjQ}P1pj^zMKP=W&9hBrkvZqHd-nmW1rdp_6u=*xQD@$H(4$K z*shBK%SiOv=@I&~)35KQSgUfounAOS+eyLlbV&Sf7P3I?_Ov5WNETHAIp4M61hD-u zGgmWV2__C4$){S+Zf;ytY(Uoi))8VDvQXVHh-f($zE+bLTv0d|J zPKZvM7v~B0?%HU=2w)VE18i~ayw#U6L(G*e6LpV(k@fqp8V3jAHbdQ93=d&FGt&1- z-|z;~BzE6b@W#`{ZWU2@=VjPv8`{J(0D@KM&#FnStjHhF>LLz|pK@O%o2C53!S()T zU?6H6964PZ;M>+Kni~-<1QSCj7Q`YP1d#{vJqkczP+vIPyV{JR&!*_XUfHr3>w&q` zc7W!qlUHZlTHo!q7f1P}j6Y-xv6=kw3$VCgeW|P!GX+%7D+8}G*lKeBX0;wvKulQ$ zK3o^+@@u`&{Gs8<<$|?wfB_hSR29Lt%@}Q0O<|{Tuv?W6)e{mX-2ff5N?Vy0=#{TP zbfIFYf#_fmL@266d!`(;(OO(xHZ&k3Iu-{bZ9qCOLNS zeoU{~@}~OU!IXx3h2^r+cH4HZ`|XIbiL!nihG8beCB`pOZClN%D5b#5`&gk~ygXJm z*SNaZa<9sp5$GY+b036o)>ysOn}ChDE7c?H`dIKY31|YmAW`T%&$kaHm4B3!l=^r~ z`_nUU+E0LS8s$b{!;G(A7pA=k2q`(UM9$Z#dtZFOcu<^xIe>t%pnR@)E2afCfG_$8 zy<}qgerG?=3qW^?WnKEg3YF%HZ!7+Z zUwt2f1q1#XPrV(P+TXmSbA>vIsY-$@Hx>!uD?ry-( z?PTD?qo=*NqkaRprd|#a+c9#|HzLQ}bM3y3W%|u(Gi5q77^06=is@WGdC-8PZqoBG2Dn3M{iPObz{!FSUJ>U7iT(gP`)k35aI!Td!{HL3Mzj6GYfXO#i(U!eR{Okun% z+}&B|ots`I&p&b+UKu2EIVS0HyB0oFYV>6LuxRSr-!*r(DtoFqb>pr9phffSXlq-uKp7;c|C1$?IzwnQR3PfzjcKay%{V#rgjHe_ z&q5pHOy$1D{(arZ(4Jw6!cIK><5bpkjMO(>F#h&dHKbeCa5ZW)>vrc7CT;S)O#5eb z8Ch@hz?zt|a5$X~nAN=STPs&Fbmp_s>WC>|1}yNOl++VOZwDV4vrIQRESj!1&(i7} zG-!xq;O^hT+y=uO^X(RKd_SR?J|h>J>1=IjkwLlqQ&UrOHmnkxLXd4@LC2{ji+Q@E z8?&Q(telOLrFn~zEu;kRbyN(#^-e2io*3)p2cvQfz70;8puqTqooBNXA*iyF>T7Z1 zUBC#k040LDo$-3P8rs8P)^N7wzS3gJ;PAWY2|NmuvEj@APLsLrxi(dpg(q5q^U}h%gZA)*vky zcWa?zXLjwX91Xzx^<6Tfv-O8_%*yE&_k&A~5Wwr_SXIej0oR=TT^d`tE*u&Op9U)o zh^d>rE2CVh)#E6cE((7rW14wtSxQ$Ix>&9L6E3TvOB|u#=PP~(eFg;r)?`*azT&5Z z&nYGH=`LGT0s~kLul25m-kV$KcTwh>DGYZV3p8y1&SE>5Ef?2Je2$Gb8BJpcZ;oag zArri&FL;-h5*+qf%*iRV;hV4wj#;9t4U1m)^>Xmrq)dF!!bFaeQIU#?-^GyJ88#gq zJL;E2nHe{1bUkbHs?D-Y5BaQ>CndX*kLwzWOVl zlHy)#61kLygNPZHeO7~)pzO+~_3azwl?_K!*tq zil-4M)cL~48`0|?oHt>Bt3a`-T(@?(3}qM`;DM9^7}ik`;|UGhc11n`@q`ip+`p+9 z$M5gwSRh+x2NsgzGHJ-!&Xm1o)oVZvgGE)~1^1v{lUA}3{WJoRvr&K_J_C1zT3l({ zecwaA`b`qm05hu3+;yXXK3`?E4&(#I(M89#}+608+_;PME~%xG+scMS_~ z$X%$EJYg}oM^P=vNVeZo{>BmgKKt?dqOrzT%cTq9AOP-mzz=hMGd#Y?x_Vs=tg0?} zKSw^%rs<1!dMIP~i}0>ZPS|yx)_d!&pYqF>S50Hki30I3D;Y);=Y{gQ-O~BGBSJ22 z;SLkk7m;cuiaG@e(eD#kFm6{#v2d$W?HO(H-HsP^Dw?;6(?xt6K4g6|UG}?ReX6y_ z-2H+Cw?IqSA3G~E>Lp8ie$VMGz-`b&-}LKCsk7zhXsfGG^^78;P>IkSv3V0Y7GQWs zuGe+X=5VxY%-bj^uHHEK2Hd3#?|X94%5!FQNeGP(pV_O?edqhnF^PX}tw=>7k2kUp zpPwO?cMm;?;Y}O8ZC+>V;a;p5NBiRrP+H{b&L6}&@7N|ai&Vsw1+B-whiR04Q0kdu zXj^DRRMA$i_xZRb`JsIEHNNoH=n5$L2kX3i0n;?^n`!JVaU<bnMyENX?4+Dc+ zLr#MO*YrJBOyE_yE39R#j@M-!CqL8dm&vjB-aYfb`8$B;tFW{966TK#q9GCg9(#7S$;PMyLNX zxXKhB2Zgp_zEsuN#w6;%hKPpq=WvAV$X-1RnmA2a(Tr3Sa3ZzqKrB>V;Sza6-2E1Z z3qi4vGqV-RbqlT`TUs5Ud19^E`-}~k+-JBZi!kc6Tp#cGKIWmXKN$Uv1&KmT-#S7b zHo#!{r)e6e9Lmm>f#EMO9e@MVPT~}0?gv0S?5-)9>gm8Mr=T!| z&+U^l^{m1q+x|-Sok$rq`xj9KKF4`RoIVJ>0zP{@a!QH00!rXfoAI$?I`S*0gVd3h zcPnjIoNDzlAD3uAl%{=L?2-oA&nuGW8=gk*42xDz#>#mf@6Sgs*I=s;by9xKedVbB zjL*up#rz@FPkY3(6mWWC6RB`}Xeb{;)deD%zuGQEq)Dg#|yzh6Fn-? zS=X>l%0`o$;%WiW0ym8%I^`86IWDsyGN&p136iskufs}%(ho^@(0H(*%Ek(G zOek^7yWt}QKTr-AXwik!YDQyi&isS4}-q70@7hz6j;a*pl7rBf1zPi}? zotQw=k(xfq5Inm`eTY(t{@Caszq@#3F3+sZNbgTw_Wtb?j9|mYnWcgsrZ_zOTz3^t z|GL9c3w&_V-T8v@${+!@=q`v%m z^ATZqD7J$(9ndR8x$iRu-%)i>NPGW10yxld;1iH#=-pYkW7sp~_T;4F{%Nhs41^NE zJ93f$_JKp!hbs#Vj51hMQ>(Ytv{`Jb9^;^DB*JCY)zCu5SR^LRqRU%*+n_UP`LPN< zzoAUOT9HV~fns;8fFBNCpXm3zv$Qv>#wbI~g8`qZcv6LUo~6XcFu$roj~rwq#gQgT z9}Y)uj{1SacP1(uKgtVCw`_^#y8-iwljc9&#&MCZ`&>4gjB1}&fUBRQeoj1cVe(EC|V$2lkRe={JFj^+R}vQUP% zMUS2z67xno7Rk(J=8e9Y92fj}C*ogM@R2DFlo)&TkWDr$!%Ogf+#502v0|b)C zn|=_e_Us`~H0kC*r5vKGQyld&X&niS7$mi+?A}DDc!Up&fNye{G7xo67mzBQnfZVD2EoxL9y%;Rr{g zN+#t28x~jGLKcp#=#J*m+1T+d>#SC#8Lyt3fK(kB_O zWO)+R$!~5`0|D4@e~x(%rqa}Zw@C$1+egT^nJ-z9yAsE2j-q`&GJHPD8p0pi zj5Moq&mrSMM>ZeSKfj6gUF?UP)t24bRdeM}m&_(umdEy8CrNPH5T!kRdb2vu%WOX0 zeOH68NDU89r;rjv_t-KRK@KhB!r!P_*!lmMddr};y0&c?cfF9}+5$z2ODPbf#hv2r zZl$;cDeh3*ixqcwXmNKB?(T$;eChqoJM;X_p3Lmo%g%Mkd9d>UcyhlMluAhFHut_h ze8GB?ay=ubXQ?FM@WXoh~XybZ3$E{gzNj7GBz#^h+dhYn69T~1cy-n_p zR-jC`R@FNa*8V{A0Jpn5g}K>gPezuyg}wCAikHX3iHr|fwZI+*90@InfJP1iC*GDw zx9!RLoA~{nr+UiTez*@~J>ed5ku_ChzO*@8X_*j3Wd!z;vsikj_{jwUOB8A--~U7Jz4Y^Cv$8Z8CWk}8V!@eL7VKmA}0jE1_MXa7!8 zMb8@$Ee1p<(%rt zyd%zT0QLeIeLR){bcu(`RYx-8z=Gd=RkH#bY+~9-QNCNw$FZNrX3Slpl5oA*P3?hG zqMYCi&$bzif7a9rsXNSdhkhUq6#w--UqKX!m{%^!( z`0o?24_8jn0OKAu2`l@8iDI4p)LijKonf&-Xw+{sO_1j>J_s+7{$Kw>1yV_0vu2)Q zzDB{V>ki9l?D+TRyi*;L!kz%k07jdw5xleg^@H#*(l>m1fwY4}>?HyLl_ z2yK?;%NN9j?}c<%X_t_tKaE)=c&gIXQc0#rOaVU31)`%OX>Yo;IP}|kI^$B%5e>yl z2GnM#7Nzq!E$U5nJlWY!*JjXgrdLvbXe;u44 z$?7Tze*HA5UZ37(8OnxY!sN8XoZ zIv*0I=W+f+9v3Yk@jFi`O56hfg5niVLw*eA009#10jA0mgG4|^mjZz()@V8x&h?!r zv>S#h^*0^>dzH|}7?Qd})ii#ZiNT(_;Wn4FyLf+_vh$ecT6U2ksA8nZa=X)c0#R?3 zCDg>`)UM8+PB*wcm?(yaM2~J+>b|AzV@yCnq_Na#c364q?0?l4hM>7Vrv1CpciJ{D zaQtQbIN4$`CQ==jt=r(e%3!Sq0+;F8CD#u@DUKEGN&`*3oyL$~e-(}%$L#X1es|aO zco~a%0~4GymNT}NyZ`EBL z*YJ1z{?nLTO#Jz}g4pX3zbIh{v+mXBXd~vFoZ?hDE!6pkf%qe10x@<&9%I+{SV3Pm zz_F%t{WfM6DMNi%P&yJ~GMkU@A`SlVxm^t4Ippx%c?oR$@CU@2FlM^sh%ktFsT;?L z2hKeI_rc?|U+kB&taW~mx+hXqws_+60R_8_xoJ7 zS6qsg1bMEcA$)dz@)C&!|KlXo{7-~vce!D`ym@zL{-k`!QGtGZm+v-Ev8_hNrbMeH zgE_JUPgp%Sd~l1M1{e#3jmb$LJVz^!llvlI1!$z+5K}!p`eWd0j!s`q$;-Jf6%ef% zFrNHAS^AXF|M_NrDVbu_q(d?QL+^Cnu}BXILS01K55LtF>mf8)DKGjzeThyP;l<@}!DDe7_G&o>sv+O$J%O1<}M0ELKkcmka1wl4iAL8Sq zMsi@fs)~%k6dQsHnIUf+2l>wU*m|8t=XxgPCA~mN-(Smh%sT6k1}7*@fRB zQvv-*)J^So90^REE*3sph-vW@d2?rpBh!z@W(*upOeF9TbgLXCADgduVL}OR&ILiE zn%c%34OSD9bkxQ?|50DCWWlZR(??yA`z15wT;#KM5TTwy+$+H{rRsxG0u?}i+LkxO z9~Mo^IAgusG$rD>CEIBnK=DCJ%pd&$neTVKJT+ zjavWhkQSqa>v`35UX3tGW&>Ast_VP}`3UFjzK>9)1V z+0#9@ud01F-;7`Xvthv@hot&JN7j}<)DlZ3s7TzhkjZn9fZ*kmDx8A?4_^??MR@d!ZP^E^sM#EaR7=~Y^>laEX)0o1`Vp$B&s}%RW#yY z!}L{plepx!Yp>4Jz+Ky}oVKal^EZ+vXi*>7wF13G{;eCI{O~!|^&rlBy5%~5^c$Uu zdoXGyIbcAXK}Gy`6p`ShP)p6vaK|4J=(5t=TbKZNb>e+XQxGzwTs5*+F_)za;4?A^ zsqHmi{jk>Q|W?1<8J_`(031XIpu`ib^S+Q*Rrd_asf@6A)U z%M8P}lM&`I*CF?xVB8ahzfmN$uxjF>jL;&JpkOjS?|lqEvPk_1LxUXtPw%XZxqy@e z^E0^hGmUQ%leS-`lL=s4Wv8x5=_Z1`bJj0I?z;$G2$W)>386IcJ(Am7g!!r4sq|5l zTQ7N+zyQep;-k#rm1^?F2w;I_x#aLyjed(!6PDF@`Ul1Uh@2QLq|D(@u73@RFogtEh2XkpyN+aB0W4$ovmei#6_saO`9a?dv&^lVQf zJRV~E6eoaAI+;~`a)V1}BUJxIpKCo-Q?}Rp#+d8FaEd$6xy9yMBS5*j<5|LT_PtH5 zG0trhco{Y zgW1c=(N4FZ$o-v%bm4o7vF9{{QhgMgcoSZ&N8s}@<XL5zh4K`P5<5%RFj`YkW2u`D@pu3iM=3>#;sFDev_q#*EQ3 zaf=nwk{|0hf*-I0`n94Cmg8I@a)-aSBp5%vIgm5`tnJX0KPXkq%75>T-91-{9ZNnK zeED|r3#ay@Lho?VzGt(!!k+^rN}l4UlV0Y%*A`=(HX6zd{bt>?oKG1;(oNQV!h-fQ zXpWu@RC8r%b}e=O_#~dHSXk^GqteSw=4n)kg9O!p8C1n`pYy%VPILA`maT){i0@3* zN`0Gz;DrzbXgC1JS^#V!={2DV&g^MR_};_zGN!=URfOpG0a2j~?mla|yIgu>ji6D? z#BO^OEyoPN4Ff@i7eZX_hbkbm~$L&-BxD=mBMv zO1|K2(0e`U#u7M%2*bke?GsY3P26y<4L5&Iv8S2FO_YNj98A->9t5dKT2c2W+0KRU z0$u+&sppHEZm{N~q%hsCy|bi06M`1Gkz|c5r`92@A_1WfUEdLS6&=GrSF<*oW&3#0 z7X59;{FxP}Pk;3A=2p>EAu_2rRseIf&h1dZ@+$I8CTl^V2MSEx6_w7^c4XS1V!k71iYNjU-VXQ_W(+a39yAy_7RCg;)97yES%M7dEWmAfnI%~ zst&(>tRb5e>bY;$w>+7*oh_8%6gQpzmkA!)6Tj(=B7zjojAS9Jkn(}8ssL<6CBt-c z7Z|x4pnw&>ms+$JUL2c2B?aN9x>aMtRnN0H zRB0YapR>9F-k&O_(Rnm|_TPpQf}V>wWS9CV#yfmXE+?NqVqpGWFrnt&G?qltN3)j+ znqtSkR@6ctG^M22iNvw1}Z3#j;lmhM}=pKr{y7%m-G z)fvv)%$_;In73S?d4_i{uR+WpV-O7Tq;U26-nbyx?|FfsZ6|7MziG+Hnllm3Eb9n+ zUa-Rf<2h^K^}qE>i!zK+Lc&s1O6x)k&RnSN=<%X=K#x&F4&5Us3m<1+xE_*_t)*y2 z9Tr(EUsFBVmK{JsjX6Nf0K+kN6Mp|l3hjL?(b)e!7`J~`Ay5cAAxw{V*6GpfSs^z2 zMgpA3B(XcC1CUMQs_q*H?PA(82gXSgVvGCAKNH+Ay2kBu7`TQG#-j2{LKJc3 z<`t_SUo$24INm|k!#ntb8#=04zD%$JbD!qh^>zoCgVc)rmL z*1Z37NmM*?&ehE3C_XLz!Em$lU|6ouF)o0OrI^kwRM&%J=md-w0gS_3D(C>slJ((!YF_6Ahu zmnPLpj%$?u@2IJI41=a^oUBcZv35>yryiP)tq$yj1G5w^=1MJY?Z`gxA%8OTc{q6= zVnl0k+hZcn=h@eHgWp(Z?hhYVSq zya2YYc0nF&0O$kgF}%FEX3Nv%xW30?dAcC|3L6Jv^e&lM36H6&j&TX_zf~xNWKv}N zyM{aNE=$lFtr1acw3WMO*ws)aAvm;77>_9(5|gn4ouk;h;$gL$I}*P? z9EE`DsVU#2S5fZsBUAk!E1QfU+Qr^D!LHl?MtX(1nrb$lD6C$(scW}+&GZ07uAu@h zOf?22_9(%%`>tN|RQI5d%qRvU z=P#Fe|9%|;VxSGd2)%EIDTNqXzaFboh}j>od!H{X^$Tt8{;Q$HFcK2k)XE$A`i@a8 zz8_old(pL!pZ&o^vzYV@n%QN&g0%52Jcy^*@taEJHxVNeQA0BsXyP9W5k$>fNVRpCvb$(C|U6;ZUIb&)_;hLzVE5ex&1)7QDDw4BykU3+_Eh_t_Kh@EVR2pKgv|3st` z?A%oWtNOpA~NAkV$ye-qO2+hi~9?o7p65VJlqOV$$?e zrd~%qH6LxIcPp3oPO6gOm;CsMC|z6@N8G>vJmt=V=HiDIde8GuuUjxsUdQjG(CI-i zVtN1+0r^F;90dGjwL9zlqJ89!wEjrpu=#QG3b#dl$oe3j;?6)nId^Ud|>jsHd(=e@=Fg&w7e z^yqNYlY+kp;4g?vdV!<;(lE^NQgiRgs1hR@VmPfxH&L4_=Qa_NOxJuYfUVVp5cg^C zJ<|8c@?1gMQ;Y6lxrX&2&czx-?82nEuUvn`Bwld7M8ClYLK}>!uyE5^sU?YQSj0`H zNtsor#Rm2w3%`nU0XK0(CF>OXu*&We&)%5j75_tB`R(Tu5*cM!WqC`c{;Z4`Zs7}; z;9+!Qi575TVQ#K=J6vWK%AK;#+ifaVvI)7W^G(c;e7p%$=2qLY#Ap3=h-Qz+oLB0c)EEFZ`HErM+Ld0?;=wni^P z%MbbIciR0ftu*e#pv-wHROIJ;UGZatSPoKF0~8}Wl*`#FLXmg*bZ5k$8+i{DCVoI{ zP39$1uW=jlxQ!ezltlxtyZVE)5J0XKf5UL??=3>c$$YiEH&z$y#)9@)zyAK6%AG0` zRqT;3QfG9AEK+C2-yB#ZMfS&>ZUOBJMcjyD*D+6(x)1on0y~Rgi2=)7wQ(1Ai;Vm! zi_AVm=uQ5*&vc`11HmF8AT|(`AsTLU8cF8s*%0JEHgn)XI_LbW$obS^EZ+?2MTn>DbRV(q1F~6>C-m^4T1@FwcVx zSDoBu`o+$1{2s1PdA6>7_wUo7Aq%H7?LHG;57DP3bpZ!*$Z^Fkm5ki^Yqh4LCB z4Dy{v9{-TW%)PUMFwL3#DwJU2G;*wd$n8XMis>o@m_oeX4N!gZJRt)o&mClC7dn5# za>0sdZ)5%tPHBF5a(PT-8V^vb?Hb)APhD=~_Jd*fI9x5~{yce{iU&M!MQ0v6I$hqp zauOnakbH51xSuS)Palqo$ukKMBubeK|KABOfMl^_w<{!79$LEnHU!QEjC8i~T8tz; z^Vkm(uXLJ*P!NjL>r$4f*2$wGhps27;*{ckE&qzk_Kf-y-ucQmYm(c2g?L) z7dE^+l_#i>8*n4$y){{}cMJqECl9{j@dqpm2g3bFc|ZF`kUQs;@`MS;Uxe?O+o^(l zuJ%Hw#C)XCL}9Hs;sJA|%3RE}bP`W%xh&lM!>w9ndK&T3Z;?AthTcwQieWxbjF(|Y zCb`l=!R{57gN>0~-5AI>zpabQ`SU}2Ii@aJMAc4SDWwf;qjz2kLV{jsP4$}`)+B7A z8JNeW3Yp*ZKK$JYy$wvP3sFUr%=q=CHvJS8^`(@N&v`~NQ-EI&6|VaH`34>DvqZM3 zS(nccf=3j&vd4Dh`9WXuW;SLlW%T9M5M;es@cGl%O6|*!GC;U0jsCUWo$^Q1s`ec& zHL6Vy{MfKg_Ju_DL&<>fYcTY12-J7|S_VSsiMQ%CtB(UIU5X&l1Pzb|nQ;v+)!*0I z6(x+n-j(f|RG7q!qab&ZRNRr!qiofMd{yu~Bqgo&TlNI~wqCbS#d1N6G371(bTGlPV=#&R6)U4 zcBZk^>r0F(Kq%#(?V93f%i+??VPEGix>Nc<`T?UZE5qaUR|`0~ixWi#5r6yMVGzPQ z`&O)|1w-$>vHwH$$|i!ah*|-jgAOs9nuy#5y7rYsnVNV+O~*yM3xdea)j+-9a}-^gPAN5RJTxDb=E)ShS-{POXh z0Zm+u`2KIyFcIxgrd{aH@xT6!@)0{*$P)=@gg|dwrrFc0W!CxaR@LF<5YBg@kvQZs z-RS1o-*VVZ+O;I5=x{W%83%U4$Q2&;n4c)}io7eE*%J?u4)cEwsx(rPitM;+6`_#d zlNSWN3G|T4dZ4}+l6ErIi6tTo^8+_4W}3uoC$8`-)_e?3tN;&$b;j>LlG+AvxPCW| z)w?(Knjymc(}X^fBEktae<91l-l!%&c*q$bUQ)uZj^kZ$Bc9^2!~V9hvwv*4us^A-6aIb=>Tf zK|cfeIJciw14ulC-sa=LZg!`QJ|`bI7V*ceSOABkl$~}`G`uNu(y~V#X%E~+ z-lYxi#sk6%k*rOfrzqN5RcN2>h6z#v-=9{B`25lPBZAiVvjw#i$KUi=$>4Tey2N5c zXcPH;4pH7EUu@(Lww=EOF}^tUV;^4KPh;+qMilxZqE5eMLR;GkE$+@hMfP*M#U6is zFxe(4xzR-r4(bJyG2M)UyI*4!DgN2IEXE z>sq>eFB|d;_$bSth%8oSLvL=};>$;$xv70*V>9oIOtvMtc8V`&-B5c&?akuKx|9O+c?S67V~+j60Bqzs-M5z{at%H!|bbaL65`4e`Px5 zH>Ucq^%LNVBP>Vtok53f>`#t3H`fr!{lTZPtB|@pFs#1m$n)LIBPGfmj6)&vSO)EfF(P!_Dn%DnF&z(Y6ifdM6 zrI*L|T9!L|{CZ}{W1$Ot*$IY+esE(}JSgmS3FfAm8vErVg+rcadykxxQpVCu7rc`X zWa#_gBE?eHOWVmvv~gbA8wa=V)*s0g&t9CnSlG1LlV^Hj6ONOIcE0}jmVWpK5xG-} z+*5DHT9j3@EC^DaqBYIJHAH+9t~382PK`lOvi&qPA~V<$D~=Y zlyo~IVCb7&B2?dnhw2HR2N(hIG@^Ue9T!RAb@zc6a40I}okH61X-&@u5>}K!fnj%> zp59PnRAFC16=4D#DGF(;x3@!$TIUExnk|_1m>Gnz(P@+88=w19a;bjxs5}fzaM%OV7{4( z>4MS}n)a;DkXN^$*@90DlCT0jpuk1YJS>JktM;?$u?SQ`fe_=>W!DVod7h)TC3LK~ z|0wtg1PfL@ zQB;=6a_uji02qIUuw|vgpGXvln_&QZT=U>Du64F===a|1I8>J?_|+xxchZ#q^`U{+ zyJjZ#!RByh%`bUtxXt1mzzK=K65_)B6a$<6xGhi(^0mvbfbwy{CXA;;VfY6v z1L;hPwA6e*j!a(sj|zQc0dlT7)FKV>I7f2JX5hV;l|KM=Q=b!mqa88Ja7j(0APjZ& z^IJG++CReEC}QMeDVLJ{hQi0eW4^BIi)q2^k$*O$o!LWQwH_2t(LbKK9fEyhn6r;BDa35G;wz zb6M8(k)C(A7Yt^Kp5^Jwaw5pA(_sJnIFW&}6Jh2p4kK9yy^YhhCVg`~g$uggx;=j2x2o}8j zf@zq@&?9clhZ0$Wf62|Z>hbz-zPWq!d=>R~Jh?KvTK0UsH0jT?4%0}O&^5L@j0vyx ziT@`Mz+e!!YNz^o&)E%;wgnY*IDqPKE4opO-Pd+wgnh8wnF{5CB(n-TWH->1EEy_~ zRsLc@cS7Cj_D!0UKgyfj=bS(ry{D1Ti2vzQf~URcgWk#9d)djl!Thi>IDVf1;3{-o zEXEN1y80=iH=_eK^i8OGEvIxOb)k~hnV3SWs}XLu8}S3se!+SKw1?)PYYrUbSU43e zB}V+!BsJuc0TqMaeksk~xt+EGR66?S8Yr30rKEZ635RR3_;oJOVoij)E%hr$wN+X{ z0gq00ls{9VUp7%VqKfM&B}1$@R(qW!@oH09&IpBawiRupsQex9_i&M8<*2S%0vRcz z->{Y4vm4d_%)RQZC-lVP)>*WOyR=)fXL+seQHT#7m*-04bPmW4Oe|Tx*~6lctJG=B zEMlZ(Aw3Xor6&8f%zlZ27#D$5kROT-!BvcbLcJsWbadt1o zPk=8SFkiS86J}GqF5IEAAokMnZ%TPF^qM2;OK?q>_**B1qoJb#G^x&}?x#Zn5b*@|qCcC%>(kBV?Uy$tj^=QJI^R7)MQHjZ--J90Chd(NXQHCz^h-(~d zrS}>I2IhHkV5NfYfBPJi4Fv*r|_%WDxxC`AdyG>q<=cmt=gvDl~a#h&v2 z=aOhYVI>pFF6+w@aQNxD8KA#8nCG5tk@QhzF@J`#{{9GyxTk=TYu(E{oO0l!rmOP- zeLDroD_vZi=F|PmG?xaof&1c{KW;pYeZo&yCb>!?k9N_7Y69I~kAXrrN-H>Qd5a4y z+6t34KvVIlAMM5FvdXhlC!=Ba4 zr9H%)mDHmRA@r>YmHd5t%})0q9K$>hLT879%^k%?I<8;NbKqmP1-Efq&}dlF@k+&v z^TKYni+;fECt+$6hhaz2+m6NA??=^%{Q$xoES zE>6Yol8rk*!-ZwyX;zCd0=N0%3@z*3vlxZq*49yEhgydC^M3f`5{QTwaA~Gut_XQE zmQgYyGTQK;NTLZk4Lm$#)CLM(BQtbd=1&5DHpwLc@p5~H1BGcAauc>R6e-W*Tm*nQ zoK}T_BBn`=c)QP%8XiPL@|XXWMo4NDPdVaVLikwZF+eFpAYvOLx-^p=%k;Q(n+&lc zd4Srh=h-U#Z7sV0Ly|3$cg=jwypEk_rTys7_X|gsBCnduV`A1T-*p@9f`~bE6h~8k z)A5fjrOtl2DwvuUV4{r%d_p|Uqkf|TXn4r$`4-2n_t6-t*YP!ql=XwAymoSX3WaQt zAL=(8*uDPZXwkE|CF=bB>DS7d@>A~u`7{im{?rrL8a?=I9-s+v%YuGTeA@`($oa5l zD%u*bA13r%7_|3^A+H(eG}qF)<_oWZhX-Fx{(!1B!5pU+Gg034HusV%t=?Tf2ep1j zysa2Ggv%#+Q zUJH_Cx&_sIWt#@GCVXrpEu<6zZmAOqs84Yv`UhtV2RqkOXD?~VyCA4ol335$|Cm{tpmp)yN|wVj8LP`%l%B*` zhQ!<(krmlwN*%BWz9KwJTLO{(;Nx$2Te5{7SM#BfX;UdnxDx4KM=w#83>h4_8Gs~A ztoeBJ3myS~ci#%iq2i2^{&-y0R!W#|PllPT>e*i=NgF=ydP;n<6v71f#(5n#6@#oj z;1bDWYNoGvt0%Sd!B-r1T9-E(Gg(bncMUIc-net8KdP5h^zB8+0}Zlp>!d92rE+Bc#tqu@@U|$^WRBE zuZ6+hWbae3&9Eo*1kP8i%T5X(Fnny<_{byy;jf0Sc{rpT_5`SlJ>PHO?3<`NiP~yh z>b%!%jETv-$3F4@wH^RQmdL`tR49JsmuZ3wM z*wGMplL|A$L{6tqb(bdtiO$1AJ)P96qAs@|(1Ii`7fZ*!7=q;%c-FJ53=*Ws?qg}u zMy;0%RnV5)vxkA+pc(?an6%RFr!>gN(72;c9e5h6je4$P^hV8KApHAr=Z)$!8V%CCjvUbrI)xW6?O%{$t}y@}e!4r% z8qyr!{%mgf3o2I8CsM%)Pz}ftmSidVMor!zOt6~0`at!uW8g1Yl8n$jH(V%1^StGB z-a^^pgpthaxMamVEcFH|GZ2s?CTT?R(5Ps{B~}7CV-)NoGoMOj4u6{Zk#GjggXP6! z-IhA65avXV$9VCK_&dWyi_AsWmzEF|*BRdcP)llk2(sOtcn8*B@4c-AE4#WhdX}^?-vAwI3+2@y(w%q1Pk@O3_cwSVO>~SfSOo=EkqriE{NO`v%^2!{#@;McV{+pHp@`Z&T+k(Wu@;HWGAt@nnN zF?>}s^3~`5(E(}vLagPtb4>s+tr*#=#;(G79=yaygi}WUHf>o{M0Tx@ya`{`sRmtJ z<@FzRi?cG>5L$DUXH2Kdc?Y-^cTp*i;^uef%UlIH)2%?a zud`-p4rI4;=%=a600-qjeSC23cHg7ZQVMukC-#3!CL)0iF8E-D%ZD}BnTCSVGD`PeC7H11 z({wKE_006Or07P&Q?J8qznRxn*-g*%-Ixd%;E#tA(PPOnIN~^&gwE-wK41$fJ{9Gd zPBTN*_S2;UN<5!mldJhyv|}UnD|^zs&uVsz>ZbcEnO`PGH~q+KQ!>HTtKo{}1u}Nk|57nc)YwsH>yMJTzZ= z)&FG8YD-lcC4YfCMB;mC7<)lIaC7BI>7^K>y%zgVsJ79&^VO!1t6VCj2ZKdYhWa!R zJ?$sG=RS+Kn45#YB<@xfGpKC7PR)s|wQ@JG3YPDd3JM<0sUPc9>azp~Vna)dAxs zQ_J*9v$9du%L^uLYUlZaGRX=5CVFwM%@*r&mLXjT4wg9WW*QHL)z?H zA?&DhHx4$QCW#g2IRxkZr(6s}zoTv40^MB1hgiHjQ*uFFIL9C2JBuik;=KSJ*twzq zP7##QFVnc1tvlaUdgTo|kUx(lZHk)>kQwyv@0{VWdZ4vi>K|lOa>Cqd#@(GQmWfWb zU>uyOFiB6XHgYID$tk~C7+shbsK^#ymcD=FT{WhA<8eWGAcN2Ed(_&laNs!Fj|m=E z7$}zsUa2G#(^sKC`*Nt2L=-Q~h*7__N*H^X4_yI6QT|O|eDc@2s<6#A_Y&`_#n~b~ zH=C!gYA@6v6{>O&jo>fczgDt$O2woTSFTd)HtIztY7Ux0*bmASWwF7w(adA`!&90w zhXSF8-B7{#;|an2RU&6Xtn`DbbPj;Zv)DN2f2&wzFvh()g}jj7bB+ST&`6=dZxe&d zRk|hLA@bx9+U_Ud4OFFO%x+mJ9j))}nqcB?bIDx1nDJWX>t=wPzR8&Q-fSHh=o{ub z#eqgm;Ynmy&iGM|fOt&KnRCQfuk{Z2PDIGpBu=grCD#aE;qPGDuXq*I9HJ?fV5*lA7iGiomzp z$K`Rdp`z`dJlRS1-)~-p{b!3J=Q`TB(K0Ykza>FEiF#7AjE|igNO_WbLB{k z;;K*|Kk8%K^-V8^d-{cZqxeFi4`c3I@4g;TFoovSwmbm*jqy%<%pX0rH*b`B2}yWxHroSeS~?NNSF ztM-O6P%2%1UFoV^<0r@Aj|)~pEYH(G?i0q+&FwU!qj4-d+xT+?8;AF5fK|lOY_2T= zv}iiF+Y`k!=L24mB6Y+LIFxpm0_*F^3weJxlcBWt0Se59&}V3l zG$jKD7`pAMmN#MsA?(S0Y>MnlWqYbwze@aLk6s%F17Fd2!wzjo(#(Acc9Iftr5jR% z^(CMWvm^vuF_`j}P9l=YFMWW4R#!ub?0v}97i2SV)=O@{EX)a#_H8V`qF*~ca7G02 zf$diSLAUx;cP|L*a2>p+eunv4k=LR6II$*jA^bx4@pKvc`p6G(&1ZHac+(P;X3s}c za5oWBXs&fvCg;|Vd~R_crYXSovAxe#l3wF^hr}EVvWtOrTq_l9keoi@Qa$wMNm9~( z{{zHs#-#G~W%NJTCx)xud%fg;z?zoJ@5bh7$&^}XAW>E5rZA;xm>lS}#8%S4W_K^I zmR1!R4p;V2Kd5z6-ABU7_YyVcAJ_U>cp}}n*Cu*Ut#w5DMcpBzqT}^T{3-mNtC3_2 z(inF=xVhyrF}foi6nM$>2TH?*cP_Z@F^DYSV#rzk{;m(o11=Keb*!uk-Kdc6 zlcUw)TT=>N_qt>*JzqLb=Sz=Y{krc5q|kv+!%tpVc>^C+j%F;txo^Xf|oyAZo1`EY%$1 zszD=UN)}1}KtnN9bSf6V_+zH z*eX_M$a>~cAj2zf03@PN0aSn+FiZokmha?Q-ctJ17u67EE0o_cPtv|&d}~5_=-Fw| zIx6DG-IDvGcz%6N@Q6i9+Mnt{lcygfrP)p1h)*^*%b{BogVT8-YSRcYd?{lZ3>A0} zhsT?@fiJlIQQo{nR9c_zY`h%g9PpUtcdU=seCwhAN{F|w;1tRsuCsVQmp5lJA_bD- zxys_^WyBb>2&_#ugX7Q0VM?4@^g&gEm!Oap_fQl*KiO^C7L4gouYL`z-MnVD56_pj z%UFEMpg96IayTQ|@m8Q)Y(?Q4#1Y3A|Ck#Af16k24`*Tr#_Bs@h^vB=M(qb&=KO4r zJMRazOGR2(S4Znuie1**2!h*}WVDdl$2`~%gS)*EQ?b}`e*OSBwu8Nt!mcjA+P};e z${V@vO~wm*z1zN8`08M;Q}09A?0~Xn_3h-5v&cjgt;OPXYVg~1TyW>q|Jh>M39o{K z+3BMgRM{wD+h)uj4q7#pEMrm9=Y0smR>lwmKVNoCdfwtx8c5` zvav*zI^XW(INllCIIHdk-EX~9dwq`jK=_Jt-$KGK*Wc|Ba=y_ysRPR{p(SSQEQUi` zJ-dr>XF}(m5=aVZo;=@So+EQUC%tg0QZD@+`6l8kdO?jQCdfJtNX9Wp1cEkGF{xkd z!fDG96kuT2*_*jl_19C0qrvyLXnVkTY zx~H`fPdQ_<4~+yhn#K&9FQpPNj&ovRhXkj6`~o@~ikGj;NNtreBlI7X#kV%SV#JGQ zs!%OgPftIoG?*ydHQIO#@X>sIn5|igB|qs27}I<4iN6$4pAk;YW7At)sBmb0YY_%$ zQE-{Mnns5P7C*hHm-kE<7hql27d$AJAH4JLxOQGyefLmY9XC4|=!&;!8o*RZqorrq zg7tE%VGFFz_pn+2l5C{~t2|mWq69$|({jKK*G*JmChPp(ua4!P?{;}2ecv`=0Mo=? z4mX7#IxOx>9|q-WW1M5j2Iqc>Pmugqn~g?ku)7Lc1WgP`%SnU&C>a3-Id`Wr0~jD~ zNdX)ff8ph3s13icIX(Z6ex6lqp%-J(ylF{?`t=mSYKf|Y>r6)^shgu7Z>=%m z4+?z*JSOhC!tn7^+P`B_0mK?b%x8vVZxjIJP+oeW#<*Z3EV@`m%{h&o<69QxfXmi* zV^py=Hv99f!%+dM;)e(G3&gr(POD=E$qN7qPVSZMz9KituBHDoV#NQ}EOF4ROO%pyAUk}E9JIf?&Z(?!Whi=pG>JYC6aoRA};P)RT`a&h4wtwI@@4U1qPwrtAkasa$yf5Y$iCpKl~I z=t@@sqe373DVfWyChq}Ro>uoQuK&%nTqc1WyClDp9F`YXFsmf(WI29nW}=3P$nDL- za`B`x^ob50&eBvcR19iKA2iB(vIy%;Vu_P6$OPrLR19Fh8&=*5n7%=}MeZ)nZ}!tw%#7arm7}XJbX9%%V&1yQae_y& zuwgxg;O3+7)jXEiQw|rX@R1=sB0zDG&faWjHa^$v|6}T^qoV$owjv-PNVkNbgmkAY z-Hp;6(%rd&bfNOyO4EwyaC>;2vL-h0mX@CWC>`Si>@GxI!GrCXm)^s!j+ zzpU^uf$0qAI0d*^?Ys*DvdR3FQpdpb5O!PXZpIJc_kZjB{vFZ9WBoPkc3&+l_l{=c zW{+Odzl|t)n}zc`G?U(NOej0CrTj(=gD>SuPr^%<*gSIvq(IE{;y6LC)+e8AtN_d% zk)bQ-u3r7))rm^OLux31@ccgKwF1Z}7A6_@KFWpgXyHbBEN-#+w)FPS%X2~iUV6NO zeiW8a$SGx67L~KJ_R9vJB#FG7qg0&5b8H|*oy<3ZC7jh?x>le@QVzYiU{**{w{z38%wyF6xev#c&Xl1csMM&<^1bl_1D zKe1V%UJ%lr@BFyXie&&TPo^G-?*34Qo5sinL_V;TZwwY1_hVQcbhuWSgiDAYsS9t_ zBp+RmJye6NM6|yCHs{uFcK3b9?{z<@WpVWfWwGQEQ~8boZ>G=gN#-Fe_yzXHJC`4G z8$XM|E2T>SI`Fy{DGM$}r3`au+#~d)&-XStc}FjYhA+#n^v%Yxd9k=b z7U9YFksv}>i)+Z$2NEY0O7EMez(qkfA5nISokC5?HzK+_!0l_|uc?fB1y_K>OiQ!r zSq3fq35Tb__IV9_#SYUxtc)5HojCw+qc^9uKKOY9@&n&*bj~PEmdRj9iRw(T@^7$7 zWBSy=J*(qohna9doPk6b^wr>yjL?ba8o5(T@3dO^&zeQ+$ky=MD}7xw=rZ za1?tx7fBz!nZ|NTvH`bNsrse zOun5Yzk$g`{V@%jV->F)WQ+BRN{7R{)~ zc4(ZX9z)fDq7KWuN1R{gFKpgK5AIB{>-IoGZXek1)7qXkT0Y;ac-l;_9r2Ywhu^GS zfg3mm&pxy0Nst*|Q%;$AufmA+I`h~|^v*5dJ4?q^R|-|;uUoHn_`xMGjGshOJwv$Q z41-Cx3SfI?i+RQ!*EUthK!T%CfiBTcmn?dKMcxXE3$9QZe9F-p$^ za~z3KvS&9=iuZ<_|ea0D9PqwU2$FfGWV4|9g?u=jdatnyz@laP}kz!Bvt z5+}M-!uFp;k4i1}D5q$$s# zf$#pXZ$<$_TBcmrol}n!VnS;r0GD(Zz;NTJYJrk@n4OUC&wgt5yM~^_Yb!O0b2LVy zf&$o7_rVec>vz`sdeJrq4GdRfSst2nnO2??mKRQb#n6(;MEDNX1{+`RYq94fH&M&H z6w+#*C%EO`fIyX-T4GAiVXx2tRqH3bd%2q!>S<(Bh?cl(piD=s%PHgGQ=ZdsCtSHP zFctj}ZxTCldwYXGkEzo|IjDuO)U4eN`jp_r-E%PkA#~k6dKM zL(YO}mU%ygPoh0Fdpt2M=|!F0TT0w;*5}vpx5f0|ZzQ!ZnwOZ^-6iLZ0vDVeKi&yu zMbF)X`~mZTYaPWkRk&U@+|u>EGdTwuI%VPgIWj}|cq&Lb;ejACX^hCRwm_xOYp&am zRbL=?>1SpLdM++O|5OvMljl(`*2s>2f`Xd|f6^pxAQ7J!wB>e7{SDT`AHIYsz8o8N z?A}NX;}`k7X*JW~brw%w(qHs+C!^s1q5-I3fv-roTWx(d-dHh{K)f-x+W=S?q$p(K zLNJ)eulRs`GL3gT;~hIdliL_Ff+XVT)aqZgDhhXQ)dBZe`jbZ(j4$k?tdM?I7bQ0A z+f$~rFlx02UCPREW&eVWjek;w>abW#DPv^#!=Lt) z^)Pz%-1+VCpzCqWtMxC_@I}h2(D5o^n4=i@;|1`?G&WiYpT~RKp>fp2D-)xi+CMp! z=@;|PgnY&?INy{y+pX4$6{-mpBCR)tY#wK=kW|w6i`SN>^IY(?``K9Z zrbJd_CAMBSXnbK#4?05*73*kr1w1}k-_v+gl7>~HI{Lv)QOmDJGofiRq%OM~p6g~V zF#HY{xekq2VVZ|ajh6KQs-VK&9t92QxBfz`Da;|o+*f$E_VsVkJmwDsSIUW*hhCI>7 zNb7#7v}H|=$kqm5_49!!rGvv(ipWmn2-Ki07U7|40-Z+;%-{)2Y=sj+88hbU&n2mt zjc4R!=&2NKjU#5ckQXw9hRh8n-vYHOgFDV6Z&AZc0w zx(7=hvIhhIor~NbP{NXl(|gPQqAd<+Y=*u;CZ!|z1OA0Od?%bX=%4iRKhSf$0djjD zbzAsRR-dBC=#?3ou{HzxCikRZCzm)u?INqTu|R&tNfoLn$!TN$AddSGB!Jwj8hX`Lhw*mz%Akinmh zXt!Ly5(T1sJujU`<1v?1Mh<_*|-Kdl;5JD$o&PUq@fd=VNSFA^m0jRdAJvyr1!+F_wmQxQaHZS zuLZ*kJx%@9>J(ON(jYZ6LxNMB?{)mI00Oc^X}~qL$6^3l>nvt8KtzW6m%M*yXTg^NTI0ym zVVsCXiaT5i>JR z=-;Ah4rf{>NPseAG(|jb^%O_1y4|lUT_&BW zVb9NrZXGmG_QOovEK3KyIJfrO;t%@VBSrnppkVPX9~@(t?I}CCzoqGQYbi)C(EH~W z$+Nn0WBJkPg_o{!lDP-T8U`Egx8>Z4%1S=L?!y(ZPqe_T<@@etELrQIKZIw7>^^r0 zIQ~Myr!3r*ZQ;%lZU_HAu7Hw+Sod4ofK1f|s(=IW2hQ<+;$sRP*PB z%Lbojw(Rox`;k#%>{8e>RL86Il^0XEXni+gLH--5S+wW7ADW!SP+h$GXvGf%$#v`hG+rm_g*8>hQn zxw6dNii;t;nl2}v3}~0*1Ucvd&{%oWoVY@n!8bc9#}C78 zp}$K61epnQnQ1br<|)Omj5iyJwA{SpQMPne;)ZRbh^D%CNk0`TC6ys=d7FI`-_j^ohDiDP+dQDKY(>Z>iICn`0+^<3E~ z`E^fX3hViwIE)#0=-bDotqbSxHP|<3h;r^k1P{aqj7&a9B9X9IhE}e`;0a##=E3#R zQ3g6}Ni@2|=s&{+?xYzkLnECFI+_F@hgPI9&TIHib)cfKE9HJ+m+2?QSjL@B{Y=j5 z;N)ofXq;erzsA6xk9R~Gz^ZhX--f#9QMplF4cdWBVRGgni76>^Kf`^|+3)h}bXLA1 zPef@>q@s)*{P8O@Wix6b2tA$tM6HGzZR)*9lBYIU53%Nbnfx-H|A8sX`SF^emhqPi zyV(?hPK!y4ZdG+iia^!4b1n$iBdGGoZhP~5wNb*rlbht~_pek@LO0H=6#36RHdYE2 zAMF45hjayP&cYXt0K+^J1|+5u`6xVZ#EFU6(c~WNUVQUCWhsI35QlXtrRzW!>^VTj(!F)&#G$&)|k3i1d=a`qsC6EVQe+UfeN2hrag5 zeoT%Odf%T+xPZB%w2>BtSCgDL6F2taDiH+0a40cV0oj4YwWfU-4FYPNUQ$1T zoTZ+Njt-+X+I%@l*8=Dxv{6#UD2&&(qyfVM_wL(vO zqF`qiayzxmAm*9sY_=>SV8SfSoVN(XNkX5*6ZeTD=!5hnkntp@x}#mLoqUet@M!-R#m zP&Z3Jbxe|T3F%&ub3u5|EBa?3>)+7&Frg6XA0$TvpFM#z8H&<6p`8q=L&Tk!!eF{S zTYBmcaNCVB9~|+!gU(W;4RfzEWG`@7X%lxu`PFdvJuz{TZpvx(`DUNnkdbW`-i?r+v7v3%$(?@m+wv;#OqlSlH2i0< zgGMDg6Jj4Ibq`juguizVUoN9B4nABAI4U*y>i&F|M!5a`wM}imke;}29B|^`&|ufh ztE@6`f-FS*Urw(;+*br!uW*ToOkDRmO?*-iQsiof0&bvq$g88GV2z})%qHrTbU(?{ z&-K5lMHFJpXlc@yENoujtXNEE3B(L^*Tq;HE}`u^Y;b({ucHV3F!UjuT{PfB)@)zs zZgrRJ^UtgU^<`zt_b&jV3n^7{*(L4kJkO|1GoJw$;3g<($^@Uoab%^=H* zt5TwAI|p4;s@rh|V=WOhZ2ON#r$gqZNonXBqe*W~cWPzt z-2Wkl7*((VAy}G|$(ButgjL#gDm6i5P1uJ?s=eSaHbpg;a@gB+rGV>9+Qwd3f4v6JV?MnPB$#Rp`TqTFJ42QmS#0$lN63geBJ(|$UvnpHh=_Z5GCLkunR|wV| zQ{+06^HYkz-!;(>2zj{K$bCZwOm%D1)fJbWZ2w|A>{TGHdk-Ms-eh z-jCfeUuOv#=61R$Q`nfs?9-0bFQ^Xw7B!7Rp#l*9ZCX=(S*?>Fo5OFS@T>YrL)6^5 z7IVI<`WRR?tum5oq&sgDb)0??(lhs9w!FMUbtkX^zeDilefyZD*{~um`_+u)%=q=7 z`#1=&Qfd#l^ZZ}eGYs(vn7^pb`RHq7r)(DLchyoch>ljHks+Dh!tqA!Ih~A$x&yc3 zH~J3Ju3;8q5YN!=@A9#^b$m5mlfQ%k2zr1&q&RM_D*C8=p`}!f%)&&&pH}`dHGEOy z>KHpS=!Cx)@0J>hL$HqqiMWvX0lj8_2zsKuBcl-+{U3iL_%8w`u8sGy(P~P(>~r_g zU?^6m`DS+@*_5J8kQ}-MqVGN7xkM*M`bRI*1Gazl#drLxFW!!;s#m+fF9Oi|ro^$9 zXAcCz>VG@>_7IVKpJ=(ef55j#Fhc}U0VR9*xmZU(|NTb+5FP$-C3c@#GYG20(|{yG zmcZ;JqOuEaFIs&^-MRL6;#sFwH*TY5PXF5q-shK`H8!g5 zvA$gE4PIt_%Spr;he#`wah1-)5j{ijotHG-lW~9g{pWU-;&An&zlFUYqA>>_Y!qv3~^?zoDEG2$bc*-;2F(UsB6vD!%6_53Be4- zH{%UBvvL5WWdk1$?Roj0|Ee1ZEM&9YjiO*1?(BLVjGUzQzgK`d^Y13|K37msNV3fJ zC%WM;vozi3bURc<@OCko^97>}q#s4=aEAiY*7_AcBkddYnHpIUso`kw2-&BEhKCYyRb-Z4T*ZMyFMw~bTR32}hz@xT=L1rw@@mhep{rgjUYBW8h#7TP?CMM^Z}Q}7>6un zW`_QqXwN^yFNZJ2J!uF6GA5*dZU90!9;Pq%y8w|vQD~5@)P)D^or^fB-ZP)q+UqC( zU%Z8IWQhJQ64A5wzR*bt?4NXjIy)1zeW6%Ms4-+C_$tD0PZsRiM-!ArNu#&~VskIa zSe|nw@E$M98XwFFu5f7Nu(xeyqw{DbS>j9xFCKn5PkRAM_Z1bp;SF%T1udCB7o>!j z!D~x#h&JGEqnC*_zBtsOsQ%$^etBb}W*uo^6b^8vGR*EFeX2C>NdwkKU(%S;5A-((%2Fl5@PjsIAYP23?=q{mv^1sSEU-4fUz-yt-OzL!^b z>LL`#esJ(QtTH%uKpSDsV0@(nEVyIIu!B{yGeNEoYMQLed(74(Og_WZJ+xi)=PQnm zY7PT~DJheUf!XOC5?B^rl_#R#x?Apz?N@PdcVcloX1pmVFQwYr2EUeq{B8c>B2vL! zuVm6`PM)n$9TW+@@2G^A*k_uLo^I?-kkS^f!J9lOpZO5(tpgkHzpuHWq06BK6WNrB zAzv959ePShRa1+rs`cAGPq5v>Foe!d1Vs`MN)kwAXyYKG6wzA8uj3DWNwOQc`+nh_ z!)Q6)cJakCiRI!PrgEKQJu#YfBgAECnZsoBt)?N3h4#1$?j zVK)#iwVUtRp=2?ML=%%Z+q_p;6#ZgGZMIc zP=)s=&ts9VIP7lYM(Xs_32fg`KSTQGgVH~f5`(9^M>)^ySWb~Uwu^bLxf8D;w9D&H zZ!J3AP01J=TY5v&*+?B*gJaSe`WcYTGbOJy;0(W3ctE>v)!zBbiOpiw1cZY#Rojx% z!0&S0%BAPjojx!-n7)&L?@0?Bv;|$lZ_2T!$GX~bGFCFou%V~k2h&Y}>zTBTPGNXs ztfp@J)@Pq|!WY#Q-m&Y8?a6{|qo(;S0*@S0V$5~#V*Q5!9O;3~aG>5bJ$TtsL`JDn zsm6}zO#l(pc#F+6V05>B8-!EC1AK>Q_5k6O9tKqcNuY_J77WIL{ zK!4ZqsD=aY5pX^2*v5#ydZXz|l?$IY$UG9lUXx|3@LLYBk1PK9S}T;f=NxIfF79y< zkG$xb?G!HsxyCQsjOrQ~@EdTLAq0@85XQ?Zl^}ZUxA|x^X~})x#cSh*$qb>sVyw!jmN~^A%N8(?_6y$Z`5Ifr%6faCx zyq3djJfeoHjlp*XUY&kB?^zr?U^Dt<(cDg&xFNB;{V`}{V>m2l>Lw3QaFG(~3m@?m-8)OMxW zVw0=^i{+&uV8wOUZiUJui`KeSuJ65;9b*J}m10Zn%))hQLJqD<*~z(c^WTO?!VntOM6_yKo}q6?VxO zCyZD{wkFb7kzfDkFpJ>HTK%}=AnFfI{o1&W*Je~Dx0~77!y`TRk#?yO5cEa!J9!NjM-;vvHlf6m9DEMZFM8~;DbNWOzrGq&yYp`^?{|H*%E+WX+@%~pvGJO zRH#!D_EXp}DpIs$H^Q-a6Qn~cTCA<#iSZ*&T8U_*n10w<`2&@?nO~($?L*efYpNHJ z7PS6Y?w=L@QM`6W3XW&X^7!)fQ&~U4rXCW9b;a*BVdjIuM*26#%nj}-H!yWTos};hztBk@OTc?E+O1f|aQ+f!fZqV_n zmZ|Bow_I+6sst9*yFbntHl5L=Y4b)0zS#tC*CUpAa|F49?N|y&ZosSJGZRns|7Vi& z&mx056Xj6^tgmGI#;HoscaxvhMh8YSdz4Up8SQ`=_tzW~`0dE;#{B6nI(~wz7r+eH z4_JtHPPW_pvLhfY4Qk^Xgb_TBRe5pHUDLJ7?nePveYFybI2KZ{7FfB}e20&GOoagG6d$1iFlbhbR?+39>wHXGZz|X8pP_4Po1Jgs}f7Y0QM~X&n$6-h0_Zyo#(5sY}hZ+~H zv8$gFaL2mh_9o)*&???JGQtG_{VBP#)*V8B9*`ZI0jJmjX-`Ac1 znC@B282$?U=k`8B1IbelFD@AFh#IG%J*c&)acNZd^L)$V>`ve3Z0WH^DLNXiRDYxr zLMWXkC%llRx#qd%{a!DQ|C;{j+PwtV%$q5X<1slcD2nZR$cXWhvB1(AW) z`AU7p0QZP}8he5Zq0tK3fNRdLX8}6UCr{1u-7g1VFQF69$;24=^*OWyNNj|s_`Pe( z;zvyHLEO{5l(lhg7Ycdh3I5&-ax{ecF0SmgE@ksy(b%uuzwo{m7;9hMv)z<^GszsU zI%6J|3`CERw;e{dX^T*4geFtdD?jqSf0^s=6PPQD+(CUmC??g>PKT5APHLA;G?^uL z786Zs{%gYl=|he2hwHeiF3%OV90)peZ!XLS2lJe#8LrAtcwxjgXXNPBV0xkQs1i-? z2|uRtoR(W05Rf; zz&7~qVkuAY6e;`w}3^cL?xq-|1yVMQQ@)s5CbGfdHvxZpSiPa-;K9 z^8*WmC;Q=bU3H0Pz^&JDZEiQ;=gJGM#~XBk5i3UEc|ZlDkci(wVUvcpS_pPaq9-f= z$;e-wuRtfy=mO9IxU%235-tAoEF*q`rJvDafCm`ei;A*~ zV_0Ss<5-!qB8IWvrMTvw{&FZZ#{n*Mb3Gs5+x0{K&ShfQD-#1O7lYxtd?6G_;}NRx z1zpo;_-?9!cH-cF7|?^Bsu`;CXkQBs5K8~F{prq+nVH8e{hT#0Z?%P>V&RYhxL@++ zW*iC7alG)!@Gu#9AEtmYFjd2);^wJ5Wo~2n;1v1aoyg@ctMHINlOdsy(^{eN63QH?*Rn*qd0L+O7SO+$V(7-09hG8Rw8h zvffm8aRy7ZHKM!L{zD> z3V%;@{l&iy!2AY(#nKK`N{^6_Wn!2g6fC@iLZ9#zOCwT@`Tw(9KD3Cyz%f{zMPE;~ zk*65dw;9#9z4Esft4Zetg0UQ-8X$HtE}4p&qV&~&2I8eeZ`}(z?T_!WunW}g!zCw& zG@zwxu-#M1f|ZLOZYv=uI?=V)2hFD)FagHrU(P{nrH$|7#spj7+nOs~j=T|Q$05PsSRU(w0DxfpUcaRs( zmS<;=LINBzJ2#&9#QsU1A|KOy8R4G7uota&RVu2sZRt6i>dn{L3zIQa5LSyKi(jcg zak5o%ph;)GO}2vkn0gMLN#fQd!}He?(0jL}6kt2NJ~}vr@q>tNTOV~pSf|Z~!ed+t zAT?3r9Y3Gqf899TZqrcz-eel_X}W{Zl6oGcZxJlV>>@7TaW;P8nYgEKrEWVeR~d@a zEYiyN6`z@A3U51SVP0GMeZSG(VY6VoiZ|I`7h(WLxU8)Wj~ICh>~Mkq&!DL-${SQh^`eB#n#?6|r@}_1r|_=;FnUy~;(FQ4#zpoaC@}1(uEik>6|9-;>@1FMT{N6ZBG-p(-PH{;CcLlOc}V!X73_cy3|r((zHDE4qk{UNITg=zg5C1o7e&+&dL*ow%nR%*`cTHr1G z7sUT5zj}(vHs`*;^s%QmcV>6`Mem2cxDh&=M5>2tsx~B#cdHU`w$Ev>$-(O76)iIZ}@;D5#%RAokFW_&kO*u3M+1RKv*U z1R`x6S53r8#%?OK?LhymM6+I39_(V@RmqmiM|$(s(kB^M<9rM?OnrGzXXO3Pf0qUD zcUdOv9!#CuWXRe}o)Z{I&w6NNqQ~>9I~}G<-UuZ4+viz-Bu_mdKyfZwASkjD$D1Zp z*`1I~Q4nPcoUz)0ci-GkBRm2Og;y*q4`gegHnwSB<<_*}9-8^Ucz2FGcL?2Z_j+0sJOyTaKF;k8tP89y0X>=d@lO0JQhuY;d1oLqq5rcQ=oRitmm((nfGZ zJh%n9pHiEN(|}uyp4Kc-=D#O5ymB#Zx$3k{U%LWz&akS%^T(zy7p{$G#s2XtI7#`_c2BmF|3l zFgq`~xc^C|Dl9J8mLrG%zXnwr@c^u&6>B$JgBMZb&q$=s^4?MS`a(bKAPaygHSv(Y zeKJs(kX5+W)Wq_jzk4e#mRcc+QLvm9lh3J)^B89ZgKOXMGa*;SeKc~mtFA11nFHrx zS-T`#^E@+K#dRoES@S^Wp>w;Fx{Iq8E|U-Rewi!EMNd(Ha+(8NRc`ut=Z}7VRc2jQ zLZ2!^^a>b5_zK(aO78BY1JcoH?W`zA{p#Alf8G5GiIQMDkQGkeSY6CkmsnI+&gFqE zbW(oULmVJcUFjPU0HoM>W$AZopL(pB$RXx21VU1yej@-S5j_1$E0;_)9DAE$D;w~U zg419t;x~{pGU!(j{VE)6Z)epVU zsJ%YmtvZZ`e0(KgV|S^d&oCkJ>pRwG+aE0r5rRTOW=3Q9d#$alrW8vbG`UYlYASu0 z8cr4-*B=30*GJk!2X9i%#zA&*+l&?#8T9r<@YzFPZ&*65te`T=WrvoBYxduSD;hT|1x@{0Y4)t zC5d6Z(D!av;^^k$@q@)t11JE_h8!6b4Z;NPw&dPFzEVl;HNG-(dRVI846l3iscT)E z0inN~DV=zt8IDELNnZjDd-?~SS*-2KUn?ACnT;2EQu=m=T3qYCcye#vPT7T>WS$&O zr2?rGV2Mq@(Pj!}4^rFBw|L_}O0rN(Aac__&7dWdioh^5{?ZH=aTsODq|?Mdv*p#^ z1IpeUv^h5oGzI_njN~GFbuip7HRp1?71>3eo|ddO>s)rbUeI0})YlbX-bO^)?jyzC zQB6`JCS^l(l=($i3fskRkExJ-%A2TxsP9$iY4Do#dINFgbuZo-k`=(j@|t|2Cit#N zGanat=caY|-i%4yh9F-pqr&C*^g&n+gDP(s$rJk(BxV6pDv5%_u-Z-z6P^MT zd4w7#Q>dQ!$8rH1Ao8blGHC)3{-RT4X(_qISMCRW%faY?26?0wenWx)3>+>hKC+N7k;D6#!}^j+_+s$Q%L?F-#Mgx z4naAiE&GJyibZK1bygFV|LCC;M@%A*Y{26nn?4}ISNf^Hb(Y;DsmT7YCRJ|u&l03} zHpxhwArSIWr;$H#lkNTBv})g?k~dlN^*toA)E~Fw0W?yU<%u$R9!xaVGhuXn?Kwb{ zY4&HnYXf%Ukcfi4_Vxs?{bupi?2%0aSGJ-jS2Bstz2mvX=AR8&Ul{Ep%)*~Q=a*J2 z2$S1&w634@|KS;#XF##1L0j!&t~xJP&Zf5F$C-JeN;iiLiTw)V!wMVeS0wUj){U;u z^m^&qJ%3nE=3y5p*7BmHzAUw_SQEB;c8xvswTmgQPZefpc$2knn5>0ptMmV?TKjut z9yJ+g@z#RZc`%p_G6=tHCHV3&_`DD&Nd3IRrT`}NMvc)d*MtFX#t6TAZHP7-q|*33 z@>e$^oMdN5J~f&Ml*9ai00WB%5LQ|J*!dJ{S}UbyOb)2DA2VERFZ!m{pjZ5-%3xDM zKWeUe{!IAY(hw{9%{HNke}fZ^2nFqCUknf@5`sm3Y5CgW8M~fA0$Is*Q`o|7yqZ96 z$K}YoKS752b669PzCY$Ix(%7?hY~Y9r=t z1>C-JD)#Nt(h{N-1L;2jh9}ZmD$9khzU*s`KnTR|XLa>LsFte+S4xUBQ-K0Gv(3*s zrH?eZX)`_}dAcj#2{XIMX>C_)(UMs+hVo4)^V=mc`#|!hq;&ePueIhk0rh(qITIrC zJTY_cgx;rd3e`VmGSX$;pfSQrDLv>u2JA4+&M!&-_I_U@7z?KAU{lh$j++xUZikb| z_A`WY(eMSd$Mx!gjjA59pzc zK$N$oxXE42UWd5RM1D^D zyc=;J1bppJpVtO;^3@QzYsK#LkE4(iSHr21_163Sa0RCMuIy#8cPC4_S*@?@P$FZF03qU{}pMiyG+7;^hYO~cb4fz#Y!+ZYbo_iWSE-n`o6y(307tI z&};hmOYhsR`TUqu;vc2{o%iZ{{aW8tVXAPy1PuAt>i~5r2nX2)V1IPu-lk!$t#3B^#EURzeYw6EHl-)tRknV<_&jj+sn53dL;?&XEg7^kWmDF zXtY1Dd$>x&Hsh`>ZDsB;LcwA8ao*O~ExuRrE@p0ek{rmTy`U?#*Gd(m6#$F#E9)?3Z$QM`R$-ZG$ul67+e~_Bt z;;-@g($gi<2x2y>GRU?W-qI+kanwTwacj=4IaviA){N<^jy}}{4u_LW5-Yr^)A+6s zTK1le@0iJeS%80AT9fKS!4w{i`vBhY7NJ?YFIs&u=0#V{Ei4rUNENV)8!(a4r2_Vx z`Hu28&GJHlnvE^)wTKs;g{8&xfV<-)qDt2L)AI{Kyl5Ko#Wx9vW$#``8*=OY!^b!I z$AV4GFo2T6dp99Dz2(A}7?H!^TV^EUbzB$r-*nNz!isSlr0~4+37Nj*9Xs4S$+p+Z z9BTYQf$pI&#fmy z2~+bh+l1cCtT+}*uiqj{0Gk8dMmYw{DCqw#IX$%zTO9v3n&58y(yX?T#EBML;lNtM zodt#MDW>1PEH1JrwY2E5ng1HAF8H`ku=k)JU*+AA&=+&_{$216!b=0xtBc+pmsj6m zZDcxjrvLo;v(+f29_@_HN9oBC%sWZ4?3GMFBj)Vvmx{KM{UyyJD=n_H7oDCsMTwrR zamc}}&cr@HPY&~u>!rd=h*G-fcfhj0eN1WP^ zGzN(2hKu_62+l2>7ii`9Mr(G!FqG0cQ^rM}>=M-gPp*g^0DwZ@o)@PY%5zzcO=sEK z*c>J*u;!1Ck3Xag9mqL6wBVk1a-0_FKO1SzpCBW^I3^b%`a`4H{q7>0RmYJAcq#KJ z0j_V_JF)U znl5ppA{cS)QfEEP1T0O>eWab!)FQQb&W-vF{w%&PjV?YRL5@d%1Hsk)R`>dt>yv7{ z0_WNJ<~QthM^?nk!q=OxL@gwg>Ckbj8SrSslt~Fc*>QW^OC3x`arnW#@+OW?dd~&= z(|4M;PJLPy-*!>E&d_eLZ;0&Ui&w3f`oDM#GFbW5*1ke$hhkG)UILT5%B-yHUEGI0 zvFwOL@yWd4n^0SukSr-VYT!#MAjFvhGxd8;36Q?7wwC7Y;@|f$MpR7=H`{wZ-EH0b z;ASYu#m9g62DGYyJ+qHhURGF{vQlyAfJx@}PWTJDtdVMfql1nD5O^x0TXQOv!QL6f z+Ir~kHb82b*0~)pJ^VmM^^}fUfX(rI3TK#zdqVgI^B_xF;X7*yv5{Zkde3rUZ!gka zgeO}sWd}%tTc2fGsiBrNGMD+xhZ(Qi<0xCZT9M z7QYE`lL3|aB|)+%obDL6aRS-|Uhp)R{A$&j<1PIjq2DMip2BT01mE6BU2OcM`0O_2 zyP<$Be3{p^1Ml4G`s}><(fIEZ>xLvCARxTzjggX)GF4?DWZegztM72(DlWCWB#JCf zPRZ_Dpk{OVo3?+F>i&R+FbURc$Hl^ln+M=F@uphsoVY&%0jm`m0_@%|0z3o7;jKlM|}er^r6Cj6|mt>(deyLo0Is zzJ0D2&*7m?Y-ks_P5nU~eR|uya{9{Cdnbx)$H?t6t9Y;OUOPs-qIVXMOReVWD8s*` zbXH(q6l#CZn$^Ge&Y*J~RG{4L+s{&Cd~$evyc?>T8k=QnYhOH2LTBik_x(fD=jr3Q z@*`_XrF;5y8ZW*JSfR!ee-5TSY&x~7G}ED#G3U8HV#E8yjaM4&LDLFsSK$l9KU{nX zV{*Ny#{?kA85}2Q9~jH?xUIwfyn2DiV~bWJgZkLCQo*e@%k?HL4!~yj9kLIsAM$2Y z0NDsJCxQ%Kdonl+rjT(5-Aeg00+bFKdH+EAT)#vS=Q(<-vtynwU!NJ(cKQ@oobFMq zvMk=#g1*~}CFO4lGU|3lwC*mJNc4BSZ1eYJNsOYgii{|A=o!j=-u9Bt?co{lud%B_ z^MXrWC^T+C%d2JX@9l}$^u1109Oejfl%9>-$nE zQzY!m#K*wb8Qvs>1m7=bgWaOpWkbS3Y-Qz;s&+OIp{Cw^b7BSDykC>BB2Sc=Rvn)6k_f$HE#DACH2G7dMDo-lPun=(( zBNtc~MQgPdl3#qeb_>S0UXE`0^hr=zrtSP_`$wd2tNIO{(`RaSNo4cjEQU!0Zc^s# zNzu_@fz{D@i1Hc7&7<*ZDDa!x)nt-aX+mORqFBcV1`Jffi_3SNe@juI-ouIS>#b+# zmK$7CU$59XIFxwIpEur!hZ`6e)XN08M2P7bR`XAtAy)vKHqEWA#Pl77{H-If+5fvCZnF};3^O-RvJ*sPzk@ER7Z^fbWMg5K+@ zt0WncB+pX%&?${z^kXG83(=QsL6$JTB`!;-M5G>iP|kPhIZg9#TgR%3{P+k;W{7Y& zFoCwA(8rivDjDf+&eh1H7hG<&nlTn%XekBI4SkM*_R^i zK}DjDpGGcPYeDo2BsLxuEEpDIk@Kl5D3GUoRxBZ{i$i#(ISP9_^coI^f$J~7t127~ zMnsWYAwf`-;a3~o-}mW$mM`^a{9kX^+0|6GhDl6FDAE!HX$G*+q$3&-6CgBc8Onf) z1OzS}Q3#*|7$74;#4seppePWE#0VJaMWsWecNt*lNL491ATl@JDNE)D+_TPy_sc%} zJ^Q@nIs19m+JeqKqts--FBheiRdwR?Z8Mn+NXpqyE*Y904Ow>;pdkm7;>f%!+@Spa zjyFcZdGz%3swdi}o0I+KIMgL|B^5?tzmNy9K3g|*DNIFOd6+2d{jRKuFpaa9gNVtV z0y znXCkrIi$od!*~Dt>A7~iEeg389v)sA^Yuoic8zB6FrAO=41#mGbR~k5&~m$5DQ_fRs^C#}fcnq^*sR zp3m#=`-CA&y%@Pl2;X`7bi6PbmQI296CiSJijeufsoO_vXIk$qh=PfYH*Ru0P>t~t z+V24ZK?GYKDYBLJ4#1S_tg50V^z9EKjoxo-aj2BWpiKmE~mvNXScyqj)>@o zMT$1b4RHXjB9i_A&zp^QDMIm1b(X8EK`f|JU(msY4myKF z;GWUBRBdPyU3Yd4fQArKx7rvZaQcVPqbv{PaVkXe2c~J>%K~&)F;`i|3HIH(apr z4|%{fG(n@0twm;XP9@P_@NWG$ZkJ{0j^rjI%1cYvbraN7CYAYSI44S`aZ-05hasp` zyPVox1x{yYe=l^Ah3Xl98957pLzG;P z+Y{@P_>Y|6&r+b-0rDbLjvLBmGnrPg1|zK5R0tQBRiiu|9AL5At2t^DdfBLaRf_Wm z;GLO5R-gj6FdfArX6M99rV$RW#0ox^{v5W~E~+A=$?tLh$R@%o1P=6hj_w7$8vw|K zDtKF&Jq~;BcIXIeY76X-4_d$isgCY1k_$vvru{&JEJ@!f?X(t#^R@7V*SVH3zrH3* z-ACpEZHWZSwBy)NE!DihD5->tlAA}fl_bNO+WwY4{l(qlO86{J*ijB>JE6Q);0P&)?jysQDe(J-QS3EagaI55FX{6Gsq%{5F~O28K*9S$nTLQy zqlQlV1_ceKLb!luqhGxI#Wae-$3Dwoh4(an{X=j9ucf1UyWj+T$&o0Am7zG=hx%2m zm5^xYY-@Ruxz`kBhsaKX z*g;w$h_TJp(deFY8{;Z${?>1NGf<>kBwlj3kXKSeSEfPuZDK}-zZ(L(8xXKOpw58J zl8c+zm@6J0%Hq%avhGK?C{2Tx)&;C0myG4-);Eb`hRzBfy5D+N$E(HsrwrO6;uJ^k z7kMQYN+h<*fVLAZ!tJ0c2#+<1KKZ8XD$}qiVP@gEb{558FV8#A@K_$Hpn$R{b%5Q4!eGGd zi2$tN?}p=BBmgWaPU{0XH2GgW>E;Q9tOdQir>7_D)y~chx1*ulj!->c&wW+LLWCk5 zi5xBKe_UUq?zP?qq%+CQoCKLY?+wqSsw!~p22j{sLv6kndW_|$)JKGGygkVwcCf5D z7TubSQ+cqJn9=k2;LG0b$j0@46En5b7rj0|k{NLOZ8F;0^rrJ~@8AN-GWo4**Y53R zcK+OSP+BsILyJB;B2tr5Cvx`==_NE~DZS^l-H?Fq!Xn^gXU+E0*t(eQ5dS9&3r+Y5 z*Q`R1iZ!DiPInPi0v;F;)LRJ3L_W*1p&HU_Yi_M*+P7HU;+K;}1VG!0YcElZnq!u3 zSsx+mL9ULpHVZ36Edu2CM=JM+tr6r!11~-)eLB8r(X>OC9hpzG3KlNa#lUF-UqK$U>b+FEAhVr_(F z1Qz)oed6-?Q1^)UvZd&C+32eGwtJHyF>Li)>SArdjiuBI;lROO9@$#WEWt1O!Befl zLIHDr@|`vfWp~Dds*h!3d&0EN&(l-Q=pCkTK!du0!FhunA`ye+M^SjRtx#sXi9aCD zJvNX2KmjTmu<;1V6qQ6#a3fkaD(?FX;DM4XfPIJ4iLi0Iu|CoCEuk^r1XBkyl{QD- zR8fbR9BYxhxUh!?$Xt_gZ{!C&h_Kx8CL+@H9D2yK2XjaV6N+$e_pZlW_D!WiZ9Q=K zU=eWK+j@B$ae+Vy?>H4z7fak%FaLzFNv?ZLpSOR#T*eFC5H zzY+vd5&|-nM_2(r!!T%7;WT}QaK{ty^9M&_ZH80%JUKr`EQDCYd=B{yXsnK*X(q_` z^CjcK%cNbWq&egMmXc7#xdg!>b zbK2r0_tCjS8?OiVl|1gpe@zyFnoB31HA;F-5|aEy0DWB9oJ+k^ZXT^f{5xm9ZOa=0 zC1_d8dct@(c+xf`3)W>KrMNQ3ZjFH4NA JX^}B5?!RR0hjah{ literal 0 HcmV?d00001 diff --git a/notebooks/llms/langchain/readthedocs_rag_zilliz.ipynb b/notebooks/llms/langchain/readthedocs_rag_zilliz.ipynb index 3f4c209a0..6325037e1 100755 --- a/notebooks/llms/langchain/readthedocs_rag_zilliz.ipynb +++ b/notebooks/llms/langchain/readthedocs_rag_zilliz.ipynb @@ -13,16 +13,16 @@ "id": "f6ffd11a", "metadata": {}, "source": [ - "In this notebook, we are going to use Milvus documentation pages to create a chatbot about our product.\n", + "In this notebook, we are going to use Milvus documentation pages to create a chatbot about our product. The chatbot is going to follow RAG steps to retrieve chunks of data using Semantic Vector Search, then the Question + Context will be fed as a Prompt to a LLM to generate an answer.\n", "\n", - "A chatbot is going to follow RAG steps to retrieve chunks of data using Semantic Vector Search, then the Question + Context will be fed as a Prompt to a LLM to generate an answer.\n", + "Many RAG demos use OpenAI for the Embedding Model and ChatGPT for the Generative AI model. **In this notebook, we will demo a fully open source RAG stack.**\n", + "\n", + "Using open-source Q&A with retrieval saves money since we make free calls to our own data almost all the time - retrieval, evaluation, and development iterations. We only make a paid call to OpenAI once for the final chat generation step. \n", "\n", "
\n", "\n", "
\n", "\n", - "Many RAG demos use OpenAI for the Embedding Model and ChatGPT for the Generative AI model. In this notebook, we will demo a fully open source RAG stack - open source embedding model available on HuggingFace, Milvus, and an open source LLM.\n", - "\n", "Let's get started!" ] }, @@ -46,7 +46,13 @@ "id": "e059b674", "metadata": {}, "source": [ - "## Download Milvus documentation to a local directory." + "## Download Milvus documentation to a local directory.\n", + "\n", + "The data we’ll use is our own product documentation web pages. ReadTheDocs is an open-source free software documentation hosting platform, where documentation is written with the Sphinx document generator.\n", + "\n", + "The code block below downloads the web pages into a local directory called `rtdocs`. \n", + "\n", + "I've already uploaded the `rtdocs` data folder to github, so you should see it if you cloned my repo." ] }, { @@ -56,7 +62,7 @@ "metadata": {}, "outputs": [], "source": [ - "# # Uncomment to download readthedocs page locally.\n", + "# # Uncomment to download readthedocs pages locally.\n", "\n", "# DOCS_PAGE=\"https://pymilvus.readthedocs.io/en/latest/\"\n", "# !echo $DOCS_PAGE\n", @@ -79,15 +85,10 @@ "metadata": {}, "source": [ "Code in this notebook uses fully-managed Milvus on [Ziliz Cloud free trial](https://cloud.zilliz.com/login). Choose the default \"Starter\" option when you provision > Create collection > Give it a name > Create cluster and collection.\n", - "- pip install pymilvus\n", "\n", - "💡 **For production purposes**, use a local Milvus docker, Milvus clusters, or fully-managed Milvus on Zilliz Cloud.\n", - "- [Local Milvus docker](https://milvus.io/docs/install_standalone-docker.md) requires local docker installed and running.\n", - "- [Milvus clusters](https://milvus.io/docs/install_cluster-milvusoperator.md) requires a K8s cluster up and running.\n", - "- [Milvus client](https://milvus.io/docs/using_milvusclient.md) with [Milvus lite](https://milvus.io/docs/milvus_lite.md), which runs a local server. ⛔️ Milvus lite is only meant for demos and local testing.\n", + "💡 Note: To keep your tokens private, best practice is to use an **env variable**. See [how to save api key in env variable](https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety).
\n", "\n", - "💡 Note: To keep your tokens private, best practice is to use an env variable.\n", - "In Jupyter, need .env file (in same dir as notebooks) containing lines like this:\n", + "In Jupyter, you also need a .env file (in same dir as notebooks) containing lines like this:\n", "- VARIABLE_NAME=value\n" ] }, @@ -106,6 +107,7 @@ } ], "source": [ + "# !pip install pymilvus #python sdk for milvus\n", "from pymilvus import connections, utility\n", "\n", "import os\n", @@ -113,8 +115,9 @@ "load_dotenv()\n", "TOKEN = os.getenv(\"ZILLIZ_API_KEY\")\n", "\n", - "# Connect to Zilliz cloud.\n", - "CLUSTER_ENDPOINT=\"https://in03-e3348b7ab973336.api.gcp-us-west1.zillizcloud.com:443\"\n", + "# Connect to Zilliz cloud using enpoint URI and API key TOKEN.\n", + "# TODO change this before checking into github.\n", + "CLUSTER_ENDPOINT=\"https://in03-xxxx.api.gcp-us-west1.zillizcloud.com:443\"\n", "connections.connect(\n", " alias='default',\n", " # Public endpoint obtained from Zilliz Cloud\n", @@ -133,7 +136,7 @@ "metadata": {}, "source": [ "## Load the Embedding Model checkpoint and use it to create vector embeddings\n", - "**Embedding model:** We will use the open-source [sentence transformers](https://www.sbert.net/docs/pretrained_models.html) available on HuggingFace to encode the documentation text. We will download the model from HuggingFace and run it locally. We'll save the model's generated embeedings to a pandas dataframe and then into the milvus database.\n", + "**Embedding model:** We will use the open-source [sentence transformers](https://www.sbert.net/docs/pretrained_models.html) available on HuggingFace to encode the documentation text. We will download the model from HuggingFace and run it locally. \n", "\n", "Two model parameters of note below:\n", "1. EMBEDDING_LENGTH refers to the dimensionality or length of the embedding vector. In this case, the embeddings generated for EACH token in the input text will have the SAME length = 768. This size of embedding is often associated with BERT-based models, where the embeddings are used for downstream tasks such as classification, question answering, or text generation.

\n", @@ -199,6 +202,9 @@ "You can think of a collection in Milvus like a \"table\" in SQL databases. The **collection** will contain the \n", "- **Schema** (or no-schema Milvus Client). \n", "💡 You'll need the vector `EMBEDDING_LENGTH` parameter from your embedding model.\n", + "Typical values are:\n", + " - 768 for sbert embedding models\n", + " - 1536 for ada-002 OpenAI embedding models\n", "- **Vector index** for efficient vector search\n", "- **Vector distance metric** for measuring nearest neighbor vectors\n", "- **Consistency level**\n", @@ -206,7 +212,6 @@ "\n", "Some supported [data types](https://milvus.io/docs/schema.md) for Milvus schemas are:\n", "- INT64 - primary key\n", - "- VARCHAR - raw texts\n", "- FLOAT_VECTOR - embedings = list of `numpy.ndarray` of `numpy.float32` numbers" ] }, @@ -220,7 +225,7 @@ "output_type": "stream", "text": [ "Embedding length: 768\n", - "Created collection: MIlvusDocs\n", + "Successfully created collection: `MilvusDocs`\n", "Schema: {'auto_id': True, 'description': 'The schema for docs pages', 'fields': [{'name': 'pk', 'description': '', 'type': , 'is_primary': True, 'auto_id': True}, {'name': 'vector', 'description': '', 'type': , 'params': {'dim': 768}}], 'enable_dynamic_field': True}\n" ] } @@ -231,26 +236,32 @@ " CollectionSchema, Collection)\n", "\n", "# 1. Name your collection.\n", - "COLLECTION_NAME = \"MIlvusDocs\"\n", + "COLLECTION_NAME = \"MilvusDocs\"\n", "\n", "# 2. Use embedding length from the embedding model.\n", "print(f\"Embedding length: {EMBEDDING_LENGTH}\")\n", "\n", - "# 3. Define minimum required fields.\n", + "# 3. Define a minimum expandable schema.\n", "fields = [\n", - " FieldSchema(name=\"pk\", dtype=DataType.INT64, is_primary=True, auto_id=True),\n", - " FieldSchema(name=\"vector\", dtype=DataType.FLOAT_VECTOR, dim=EMBEDDING_LENGTH),\n", + " FieldSchema(\"pk\", DataType.INT64, is_primary=True, auto_id=True),\n", + " FieldSchema(\"vector\", DataType.FLOAT_VECTOR, dim=EMBEDDING_LENGTH),\n", "]\n", - "\n", - "# 4. Create schema with dynamic field enabled.\n", "schema = CollectionSchema(\n", "\t\tfields,\n", "\t\tdescription=\"The schema for docs pages\",\n", "\t\tenable_dynamic_field=True\n", ")\n", + "\n", + "# 4. Check if collection already exists, if so drop it.\n", + "has = utility.has_collection(COLLECTION_NAME)\n", + "if has:\n", + " drop_result = utility.drop_collection(COLLECTION_NAME)\n", + " print(f\"Successfully dropped collection: `{COLLECTION_NAME}`\")\n", + "\n", + "# 5. Create the collection.\n", "mc = Collection(COLLECTION_NAME, schema, consistency_level=\"Eventually\")\n", "\n", - "print(f\"Created collection: {COLLECTION_NAME}\")\n", + "print(f\"Successfully created collection: `{COLLECTION_NAME}`\")\n", "print(f\"Schema: {mc.schema}\")" ] }, @@ -260,7 +271,9 @@ "source": [ "## Add a Vector Index\n", "\n", - "The vector index determines the vector **search algorithm** used to find the closest vectors in your data to the query a user submits. Most vector indexes use different sets of parameters depending on whether the database is:\n", + "The vector index determines the vector **search algorithm** used to find the closest vectors in your data to the query a user submits. \n", + "\n", + "Most vector indexes use different sets of parameters depending on whether the database is:\n", "- **inserting vectors** (creation mode) - vs - \n", "- **searching vectors** (search mode) \n", "\n", @@ -348,12 +361,16 @@ "## Chunking\n", "\n", "Before embedding, it is necessary to decide your chunk strategy, chunk size, and chunk overlap. In this demo, I will use:\n", - "- **Strategy** = Use markdown header hierarchies. Split markdown sections if too long.\n", + "- **Strategy** = Use markdown header hierarchies. Keep markdown sections together unless they are too long.\n", "- **Chunk size** = Use the embedding model's parameter `MAX_SEQ_LENGTH`\n", "- **Overlap** = Rule-of-thumb 10-15%\n", "- **Function** = \n", " - Langchain's `HTMLHeaderTextSplitter` to split markdown sections.\n", - " - Langchain's `RecursiveCharacterTextSplitter` to split up long reviews recursively.\n" + " - Langchain's `RecursiveCharacterTextSplitter` to split up long reviews recursively.\n", + "\n", + "\n", + "Notice below, each chunk is grounded with the document source page.
\n", + "In addition, header titles are kept together with the chunk of markdown text." ] }, { @@ -365,13 +382,13 @@ "name": "stdout", "output_type": "stream", "text": [ - "chunking time: 0.01805710792541504\n", + "chunking time: 0.023195981979370117\n", "docs: 15, split into: 15\n", "split into chunks: 159, type: list of \n", "\n", "Looking at a sample chunk...\n", - "{'h1': 'Installation', 'h2': 'Installing via pip', 'source': 'rtdocs/pymilvus.readthedocs.io/en/latest/install.html'}\n", - "demonstrate how to install and using PyMilvus in a virtual environment. See virtualenv for more info\n" + "Installation¶ Installing via pip¶ PyMilvus is in the Python Package Index. PyMilvus only support pyt\n", + "{'h1': 'Installation', 'h2': 'Installing via pip', 'source': 'rtdocs/pymilvus.readthedocs.io/en/latest/install.html'}\n" ] } ], @@ -382,7 +399,6 @@ "headers_to_split_on = [\n", " (\"h1\", \"Header 1\"),\n", " (\"h2\", \"Header 2\"),\n", - " (\"h3\", \"Header 3\"),\n", "]\n", "# Create an instance of the HTMLHeaderTextSplitter\n", "html_splitter = HTMLHeaderTextSplitter(headers_to_split_on=headers_to_split_on)\n", @@ -430,13 +446,13 @@ "print(f\"docs: {len(docs)}, split into: {len(html_header_splits)}\")\n", "print(f\"split into chunks: {len(chunks)}, type: list of {type(chunks[0])}\") \n", "\n", - "# Inspect chunks.\n", + "# Inspect a chunk.\n", "print()\n", "print(\"Looking at a sample chunk...\")\n", - "print(chunks[1].metadata)\n", - "print(chunks[1].page_content[:100])\n", + "print(chunks[0].page_content[:100])\n", + "print(chunks[0].metadata)\n", "\n", - "# TODO - remove this before saving in github.\n", + "# # TODO - remove this before saving in github.\n", "# # Print the child splits with their associated header metadata\n", "# print()\n", "# for child in chunks:\n", @@ -478,9 +494,22 @@ "source": [ "## Insert data into Milvus\n", "\n", - "Milvus and Milvus Lite support loading pandas dataframes directly.\n", + "For each original text chunk, we'll write the quadruplet (`vector, text, source, h1, h2`) into the database.\n", "\n", - "Milvus Client, however, requires conerting pandas df into a list of dictionaries first.\n" + "
\n", + "\n", + "
" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Milvus and Milvus Lite support loading data from:\n", + "- pandas dataframes \n", + "- list of dictionaries\n", + "\n", + "Below, we use the embedding model provided by HuggingFace, download its checkpoint, and run it locally as the encoder. " ] }, { @@ -489,9 +518,11 @@ "metadata": {}, "outputs": [], "source": [ - "# Convert chunks and embeddings to a list of dictionaries.\n", + "# Convert chunks to a list of dictionaries.\n", "chunk_list = []\n", "for chunk in chunks:\n", + "\n", + " # Generate embeddings using encoder from HuggingFace.\n", " embeddings = torch.tensor(encoder.encode([chunk.page_content]))\n", " embeddings = F.normalize(embeddings, p=2, dim=1)\n", " converted_values = list(map(np.float32, embeddings))[0]\n", @@ -501,9 +532,10 @@ " h2 = chunk.metadata['h2'][:50]\n", " except:\n", " h2 = \"\"\n", + " # Assemble embedding vector, original text chunk, metadata.\n", " chunk_dict = {\n", " 'vector': converted_values,\n", - " 'chunk': chunk.page_content,\n", + " 'text': chunk.page_content,\n", " 'source': chunk.metadata['source'],\n", " 'h1': chunk.metadata['h1'][:50],\n", " 'h2': h2,\n", @@ -526,14 +558,13 @@ "output_type": "stream", "text": [ "Start inserting entities\n", - "Milvus insert time for 159 vectors: 1.0154786109924316 seconds\n", - "(insert count: 159, delete count: 0, upsert count: 0, timestamp: 445785288603074562, success count: 159, err count: 0)\n", - "[{\"name\":\"_default\",\"collection_name\":\"MIlvusDocs\",\"description\":\"\"}]\n" + "Milvus insert time for 159 vectors: 0.7005021572113037 seconds\n", + "[{\"name\":\"_default\",\"collection_name\":\"MilvusDocs\",\"description\":\"\"}]\n" ] } ], "source": [ - "# Insert a batch of data into the Milvus collection.\n", + "# Insert data into the Milvus collection.\n", "\n", "print(\"Start inserting entities\")\n", "start_time = time.time()\n", @@ -546,7 +577,6 @@ "mc.flush() \n", "\n", "# Inspect results.\n", - "print(insert_result)\n", "print(mc.partitions) # list[Partition] objects\n" ] }, @@ -558,7 +588,6 @@ "## Run a Semantic Search\n", "\n", "Now we can search all the documentation embeddings to find the `TOP_K` documentation chunks with the closest embeddings to a user's query.\n", - "- In this example, we'll ask about AUTOINDEX.\n", "\n", "💡 The same model should always be used for consistency for all the embeddings." ] @@ -576,7 +605,7 @@ "\n", "Next, you can ask a question about your custom data!\n", "\n", - "💡 In LLM lingo:\n", + "💡 In LLM vocabulary:\n", "> **Query** is the generic term for user questions. \n", "A query is a list of multiple individual questions, up to maybe 1000 different questions!\n", "\n", @@ -600,11 +629,11 @@ ], "source": [ "# Define a sample question about your data.\n", - "question = \"what is the default distance metric used in AUTOINDEX?\"\n", - "query = [question]\n", + "QUESTION = \"what is the default distance metric used in AUTOINDEX?\"\n", + "QUERY = [QUESTION]\n", "\n", "# Inspect the length of the query.\n", - "QUERY_LENGTH = len(query[0])\n", + "QUERY_LENGTH = len(QUERY[0])\n", "print(f\"query length: {QUERY_LENGTH}\")" ] }, @@ -634,20 +663,20 @@ "output_type": "stream", "text": [ "Loaded milvus collection into memory.\n", - "Milvus search time: 0.06506514549255371 sec\n", + "Milvus search time: 0.0449519157409668 sec\n", "type: , count: 5\n" ] } ], "source": [ - "# RETRIEVAL USING MILVUS.\n", + "# RETRIEVAL USING MILVUS API.\n", "\n", "# Before conducting a search based on a query, you need to load the data into memory.\n", "mc.load()\n", "print(\"Loaded milvus collection into memory.\")\n", "\n", - "# Embed the question using the same embedding model.\n", - "embedded_question = torch.tensor(encoder.encode([question]))\n", + "# Embed the question using the same encoder.\n", + "embedded_question = torch.tensor(encoder.encode(QUERY))\n", "# Normalize embeddings to unit length.\n", "embedded_question = F.normalize(embedded_question, p=2, dim=1)\n", "# Convert the embeddings to list of list of np.float32.\n", @@ -663,9 +692,9 @@ " anns_field=\"vector\", \n", " # No params for AUTOINDEX\n", " param={},\n", - " # Access dynamic fields in the boolean expression.\n", + " # Milvus can utilize metadata to enhance the search experience in boolean expressions.\n", " # expr=\"\",\n", - " output_fields=[\"h1\", \"h2\", \"chunk\", \"source\"], \n", + " output_fields=[\"h1\", \"h2\", \"text\", \"source\"], \n", " limit=TOP_K,\n", " consistency_level=\"Eventually\"\n", " )\n", @@ -695,23 +724,39 @@ "name": "stdout", "output_type": "stream", "text": [ - "2267\n" + "0th query result\n", + "id: 445766022949312658, distance: 0.708217978477478, entity: {'text': \"index_file_size (int) – Segment size. See Storage Concepts. metric_type (MetricType) – Distance Metrics type. Valued form MetricType. See Distance Metrics. A demo is as follow: param={'collection_name': 'name', 'dimension': 16, 'index_file_size': 1024 # Optional, default 1024, 'metric_type': MetricType.L2 # Optional, default MetricType.L2 } timeout (float) – An optional duration of time in seconds to allow for the RPC. When timeout is set to None, client waits until server responses or error occurs.\", 'source': 'https://pymilvus.readthedocs.io/en/latest/api.html', 'h1': 'API reference', 'h2': 'Client'}\n", + "id: 445766022949312769, distance: 0.690363883972168, entity: {'text': 'A metric for binary vectors, only support :attr:`~milvus.IndexType.FLAT` index. #: See `Substructure `_. SUPERSTRUCTURE = 7 def __repr__(self): return \"\".format(self.__class__.__name__, self._name_) def __str__(self): return self._name_', 'source': 'https://pymilvus.readthedocs.io/en/latest/_modules/milvus/client/types.html', 'h1': 'Source code for milvus.client.types from enum impo', 'h2': ''}\n", + "id: 445766022949312619, distance: 0.688593864440918, entity: {'text': 'you can refer to Storage Concepts for more information about segments and index_file_size. metric_type:Milvus compute distance between two vectors, you can refer to Distance Metrics for more information. Now we can create a collection: >>> collection_name = \\'demo_film_tutorial\\' >>> collection_param = { ... \"collection_name\": collection_name, ... \"dimension\": 8, ... \"index_file_size\": 2048, ... \"metric_type\": MetricType.L2 ... } >>> client.create_collection(collection_param) Status(code=0, message=\\'Create', 'source': 'https://pymilvus.readthedocs.io/en/latest/tutorial.html', 'h1': 'Tutorial', 'h2': 'This is a basic introduction to Milvus by PyMilvus'}\n", + "id: 445766022949312621, distance: 0.6716917157173157, entity: {'text': \"metric_type=) The attributes of collection can be extracted from info. >>> info.collection_name 'demo_film_tutorial' >>> info.dimension 8 >>> info.index_file_size 2048 >>> info.metric_type This tutorial is a basic intro tutorial, building index won’t be covered by this tutorial. If you want to go further into Milvus with indexes, it’s recommended to check our index examples. If you’re already known about indexes from index examples, and you want a full lists of params supported by PyMilvus, you check out\", 'source': 'https://pymilvus.readthedocs.io/en/latest/tutorial.html', 'h1': 'Tutorial', 'h2': 'This is a basic introduction to Milvus by PyMilvus'}\n", + "id: 445766022949312711, distance: 0.6690606474876404, entity: {'text': \"-- Segment size. See `Storage Concepts `_. * *metric_type* (``MetricType``) -- Distance Metrics type. Valued form :class:`~milvus.MetricType`. See `Distance Metrics `_. A demo is as follow: .. code-block:: python param={'collection_name': 'name', 'dimension': 16, 'index_file_size': 1024 # Optional, default 1024, 'metric_type': MetricType.L2 # Optional, default MetricType.L2 } :param timeout: An optional duration of time in seconds to allow for the RPC. When timeout is set to None, client waits until\", 'source': 'https://pymilvus.readthedocs.io/en/latest/_modules/milvus/client/stub.html', 'h1': 'Source code for milvus.client.stub # -*- coding: U', 'h2': ''}\n", + "505\n" ] } ], "source": [ - "# # TODO - remove this before saving in github.\n", - "# for n, hits in enumerate(results):\n", - "# print(f\"{n}th query result\")\n", - "# for hit in hits:\n", - "# print(hit)\n", + "# TODO - remove this before saving in github.\n", + "for n, hits in enumerate(results):\n", + " print(f\"{n}th query result\")\n", + " for hit in hits:\n", + " print(hit)\n", "\n", "# Assemble the context as a stuffed string.\n", "context = \"\"\n", + "i = 0\n", "for r in results[0]:\n", - " text = r.entity.chunk\n", - " context += f\"{text} \"\n", - "print(len(context))" + " text = r.entity.text\n", + " if i == 0: # only first result\n", + " context += f\"{text} \"\n", + " i += 1\n", + "print(len(context))\n", + "\n", + "# Also save the context metadata to retrieve along with the answer.\n", + "context_metadata = {\n", + " \"h1\": results[0][0].entity.h1,\n", + " \"h2\": results[0][0].entity.h2,\n", + " \"source\": results[0][0].entity.source,\n", + "}" ] }, { @@ -721,12 +766,33 @@ "source": [ "## Use an LLM to Generate a chat response to the user's question using the Retrieved Context.\n", "\n", - "Below, we're using an open, very tiny generative AI model, or LLM. Many demos use OpenAI as the LLM choice instead." + "Below, we'll use an open, very tiny generative AI model, or LLM, available on HuggingFace. Many demos use OpenAI as the LLM choice instead." ] }, { "cell_type": "code", "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "def assemble_grounding_sources(answer, context_metadata):\n", + " \"\"\"Assemble the answer and grounding sources into a string\"\"\"\n", + " grounded_answer = f\"Answer: {answer}\\n\"\n", + " grounded_answer += \"Grounding sources and citations:\\n\"\n", + " try:\n", + " grounded_answer += f\"'h1': {context_metadata['h1']}, 'h2':{context_metadata['h2']}\\n\"\n", + " except:\n", + " pass\n", + " try:\n", + " grounded_answer += f\"'source': {context_metadata['source']}\"\n", + " except:\n", + " pass\n", + " return grounded_answer" + ] + }, + { + "cell_type": "code", + "execution_count": 16, "id": "3e7fa0b6", "metadata": {}, "outputs": [ @@ -745,21 +811,22 @@ "from transformers import AutoModelForQuestionAnswering, AutoTokenizer, pipeline\n", "\n", "# Load the Hugging Face auto-regressive LLM checkpoint.\n", - "llm = \"deepset/tinyroberta-squad2\"\n", - "tokenizer = AutoTokenizer.from_pretrained(llm)\n", + "tiny_llm = \"deepset/tinyroberta-squad2\"\n", + "tokenizer = AutoTokenizer.from_pretrained(tiny_llm)\n", "\n", "# context cannot be empty so just put random text in it.\n", "QA_input = {\n", - " 'question': question,\n", + " 'question': QUESTION,\n", " 'context': 'The quick brown fox jumped over the lazy dog'\n", "}\n", "\n", "nlp = pipeline('question-answering', \n", - " model=llm, \n", + " model=tiny_llm, \n", " tokenizer=tokenizer)\n", - "\n", "result = nlp(QA_input)\n", - "print(f\"Question: {question}\")\n", + "\n", + "# Print the question and answer.\n", + "print(f\"Question: {QUESTION}\")\n", "print(f\"Answer: {result['answer']}\")\n", "\n", "# The baseline LLM chat is not very helpful." @@ -767,7 +834,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 17, "id": "a68e87b1", "metadata": {}, "outputs": [ @@ -776,31 +843,135 @@ "output_type": "stream", "text": [ "Question: what is the default distance metric used in AUTOINDEX?\n", - "Answer: MetricType.L2\n" + "Answer: MetricType.L2\n", + "Grounding sources and citations:\n", + "'h1': API reference, 'h2':Client\n", + "'source': https://pymilvus.readthedocs.io/en/latest/api.html\n" ] } ], "source": [ "# NOW ASK THE SAME LLM THE SAME QUESTION USING THE RETRIEVED CONTEXT.\n", "QA_input = {\n", - " 'question': question,\n", + " 'question': QUESTION,\n", " 'context': context,\n", "}\n", "\n", "nlp = pipeline('question-answering', \n", - " model=llm, \n", + " model=tiny_llm, \n", " tokenizer=tokenizer)\n", - "\n", "result = nlp(QA_input)\n", - "print(f\"Question: {question}\")\n", - "print(f\"Answer: {result['answer']}\")\n", + "\n", + "# Print the question and answer along with grounding sources and citations.\n", + "answer = assemble_grounding_sources(result['answer'], context_metadata)\n", + "print(f\"Question: {QUESTION}\")\n", + "print(answer)\n", "\n", "# That answer looks a little better!" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Use OpenAI to generate a more human-like chat response to the user's question \n", + "\n", + "We've practiced retrieval for free on our own data using open-source LLMs.
\n", + "\n", + "Now let's make a call to the paid OpenAI GPT.\n", + "\n", + "💡 Note: We’re using a temperature of 0.0 to enable reproducible experiments. For use cases that need to always be factually grounded, use very low temperature values while more creative tasks can benefit from higher temperatures." + ] + }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 18, + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "from dotenv import load_dotenv, find_dotenv\n", + "\n", + "# See how to save api key in env variable.\n", + "# https://help.openai.com/en/articles/5112595-best-practices-for-api-key-safety\n", + "_ = load_dotenv(find_dotenv())\n", + "openai.api_key = os.environ['OPENAI_API_KEY']\n", + "\n", + "# Define the generation llm model to use.\n", + "LLM_NAME = \"gpt-3.5-turbo-1106\"" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": {}, + "outputs": [], + "source": [ + "def prepare_response(response):\n", + " return response[\"choices\"][-1][\"message\"][\"content\"]\n", + "\n", + "def generate_response(\n", + " llm, temperature=0.0, \n", + " grounding_sources=None,\n", + " system_content=\"\", assistant_content=\"\", user_content=\"\"):\n", + " \"\"\"Generate response from an LLM.\"\"\"\n", + "\n", + " try:\n", + " response = openai.ChatCompletion.create(\n", + " model=llm,\n", + " temperature=temperature,\n", + " api_key=openai.api_key,\n", + " messages=[\n", + " {\"role\": \"system\", \"content\": system_content},\n", + " {\"role\": \"assistant\", \"content\": assistant_content},\n", + " {\"role\": \"user\", \"content\": user_content},\n", + " ],\n", + " )\n", + " answer = prepare_response(response=response)\n", + " \n", + " # Add the grounding sources and citations.\n", + " answer = assemble_grounding_sources(answer, grounding_sources)\n", + " return answer\n", + "\n", + " except Exception as e:\n", + " print(f\"Exception: {e}\")\n", + " return \"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Question: what is the default distance metric used in AUTOINDEX?\n", + "Answer: The default distance metric used in AUTOINDEX is L2.\n", + "Grounding sources and citations:\n", + "'h1': API reference, 'h2':Client\n", + "'source': https://pymilvus.readthedocs.io/en/latest/api.html\n" + ] + } + ], + "source": [ + "# Generate response\n", + "response = generate_response(\n", + " llm=LLM_NAME,\n", + " temperature=0.0,\n", + " grounding_sources=context_metadata,\n", + " system_content=\"Answer the question using the context provided. Be succinct.\",\n", + " user_content=f\"question: {QUESTION}, context: {context}\")\n", + "\n", + "# Print the question and answer along with grounding sources and citations.\n", + "print(f\"Question: {QUESTION}\")\n", + "print(response)" + ] + }, + { + "cell_type": "code", + "execution_count": 21, "id": "d0e81e68", "metadata": {}, "outputs": [], @@ -811,7 +982,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 22, "id": "c777937e", "metadata": {}, "outputs": [ @@ -819,8 +990,6 @@ "name": "stdout", "output_type": "stream", "text": [ - "The watermark extension is already loaded. To reload it, use:\n", - " %reload_ext watermark\n", "Author: Christy Bergman\n", "\n", "Python implementation: CPython\n", @@ -832,6 +1001,7 @@ "sentence_transformers: 2.2.2\n", "pymilvus : 2.3.3\n", "langchain : 0.0.322\n", + "openai : 0.28.0\n", "\n", "conda environment: py310\n", "\n" @@ -843,7 +1013,7 @@ "# !pip install watermark\n", "\n", "%load_ext watermark\n", - "%watermark -a 'Christy Bergman' -v -p torch,transformers,sentence_transformers,pymilvus,langchain --conda" + "%watermark -a 'Christy Bergman' -v -p torch,transformers,sentence_transformers,pymilvus,langchain,openai --conda" ] } ], diff --git a/notebooks/text/imdb_search_milvus_client.ipynb b/notebooks/text/imdb_search_milvus_client.ipynb index 1e371d108..b9a0df3dd 100755 --- a/notebooks/text/imdb_search_milvus_client.ipynb +++ b/notebooks/text/imdb_search_milvus_client.ipynb @@ -55,7 +55,7 @@ "💡 **For production purposes**, use a local Milvus docker, Milvus clusters, or fully-managed Milvus on Zilliz Cloud.\n", "- [Local Milvus docker](https://milvus.io/docs/install_standalone-docker.md) requires local docker installed and running.\n", "- [Milvus clusters](https://milvus.io/docs/install_cluster-milvusoperator.md) requires a K8s cluster up and running.\n", - "- [Ziliz Cloud free trial](https://cloud.zilliz.com/login) choose a \"free\" option when you provision.\n" + "- [Ziliz Cloud free trial](https://cloud.zilliz.com/login) choose a \"Default\" option when you provision.\n" ] }, { @@ -76,8 +76,7 @@ "source": [ "from milvus import default_server\n", "from pymilvus import (\n", - " connections, utility, \n", - " MilvusClient,\n", + " connections, utility\n", ")\n", "\n", "# Cleanup previous data and stop server in case it is still running.\n", @@ -945,7 +944,7 @@ "start_time = time.time()\n", "insert_result = mc.insert(\n", " COLLECTION_NAME,\n", - " data=dict_list, \n", + " data=dict_list,\n", " progress_bar=True)\n", "end_time = time.time()\n", "print(f\"Milvus insert time for {batch.shape[0]} vectors: {end_time - start_time} seconds\")\n",