From f14c926bbdccc45d8c69927ae2b3f921dd038f1a Mon Sep 17 00:00:00 2001 From: Tiina vaahtio <111491535+vaahtio@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:59:01 +0300 Subject: [PATCH] Flow Judge Integration (#275) * haystack integration docs * renamed integration file * updated logo * fix yaml error * modified doc * Flow Judge integration * flow-judge integration * udpate installation * flow-judge integration * flow-judge integration * flow-judge integration * flow-judge integration --- integrations/flow-judge.md | 144 +++++++++++++++++++++++++++++++++++++ logos/flow-ai.png | Bin 0 -> 23608 bytes 2 files changed, 144 insertions(+) create mode 100644 integrations/flow-judge.md create mode 100644 logos/flow-ai.png diff --git a/integrations/flow-judge.md b/integrations/flow-judge.md new file mode 100644 index 00000000..e9cb0eb7 --- /dev/null +++ b/integrations/flow-judge.md @@ -0,0 +1,144 @@ +--- +layout: integration +name: Flow Judge +description: Evaluate Haystack pipelines using Flow Judge +authors: + - name: Flow AI + socials: + github: flowaicom + twitter: flowaicom + linkedin: https://www.linkedin.com/company/flowaicom/ +pypi: https://pypi.org/project/flow-judge/ +repo: https://github.com/flowaicom/flow-judge +type: Evaluation Framework +report_issue: https://github.com/flowaicom/flow-judge/issues +logo: /logos/flow-ai.png +version: Haystack 2.0 +toc: true +--- +### **Table of Contents** +- [Overview](#overview) +- [Installation](#installation) +- [Usage](#usage) +- [License](#license) + +## Overview +This integration allows you to evaluate Haystack pipelines using Flow Judge. + +Flow Judge is an open-source, lightweight (3.8B) language model optimized for LLM system evaluations. Crafted for accuracy, speed, and customization. + +Read the technical report [here](https://www.flow-ai.com/blog/flow-judge). + +## Installation + +For running Flow Judge with vLLM engine: +```bash +pip install flow-judge[vllm] +pip install 'flash_attn>=2.6.3' --no-build-isolation +``` +For running Flow Judge with transformers: +```bash +pip install flow-judge[hf] +``` +If flash attention: +```bash +pip install 'flash_attn>=2.6.3' --no-build-isolation +``` +For running Flow Judge with Llamafile on macOS: +```bash +pip install flow-judge[llamafile] +pip install 'flash_attn>=2.6.3' --no-build-isolation +``` +To learn more about the installation, visit the [Flow Judge Installation](https://pypi.org/project/flow-judge/) page. + +Finally install Haystack: +```bash +pip install haystack-ai +``` + +## Usage +Flow Judge integration with Haystack is designed to facilitate the evaluation of Haystack pipelines using Flow Judge. This integration allows you to seamlessly integrate Flow Judge into your Haystack workflows, enabling you to evaluate and improve your LLM systems with precision and efficiency. + +Flow Judge offers a set-of built-in metrics and easy-to-create custom metrics. + +### Available Built-in Metrics + +Built-in metrics come with 3 different scoring scales Binary, 3-point Likert and 5-point Likert: +- Response Correctness +- Response Faithfulness +- Response Relevance + +To check the available metrics you can run: +```python +from flow_judge.metrics import list_all_metrics +list_all_metrics() +``` + +While these preset metrics provide a solid foundation for evaluation, the true power of Flow Judge lies in its ability to create custom metrics tailored to your specific requirements. This flexibility allows for a more nuanced and comprehensive assessment of your LLM systems. Please refer to our [tutorial](https://github.com/flowaicom/flow-judge/blob/main/examples/2_custom_evaluation_criteria.ipynb) for creating custom metrics for more details. + +### Components +This integration introduces `HaystackFlowJudge` component, which is used just like other evaluator components in Haystack. + +For details about the use and parameters of this component please refer to [HaystackFlowJudge class](https://github.com/flowaicom/flow-judge/blob/main/flow_judge/integrations/haystack.py) and Haystack's [LLMEvaluator component](https://docs.haystack.deepset.ai/reference/evaluators-api#module-llm_evaluator). + +### Use Flow Judge with Haystack +We have created a comprehensive guide on how to effectively use Flow Judge with Haystack. You can access it [here](https://github.com/flowaicom/flow-judge/blob/main/examples/5_evaluate_haystack_rag_pipeline.ipynb). This tutorial demonstrates how to evaluate a RAG pipeline built with Haystack using Flow Judge. + +### Quick Example +The code snippet below provides a simpler example of how to integrate Flow Judge with Haystack. However, we recommend following the full tutorial for a deeper understanding of the concepts and implementation. + +```python +from flow_judge.integrations.haystack import HaystackFlowJudge +from flow_judge.metrics.presets import RESPONSE_FAITHFULNESS_5POINT +from flow_judge import Hf + +from haystack import Pipeline + +# Create a model using Hugging Face Transformers with Flash Attention +model = Hf() # We support also Vllm, Llamafile + +# Evaluation sample +questions = ["What is the termination clause in the contract?"] +contexts = ["This contract may be terminated by either party upon providing thirty (30) days written notice to the other party. In the event of a breach of contract, the non-breaching party may terminate the contract immediately."] +answers = ["The contract can be terminated by either party with thirty days written notice."] + +# Define the HaystackFlowJudge evaluator, we will use the built-in metric for faithfulness +# For parameters refer to Haystack's [LLMEvaluator](https://docs.haystack.deepset.ai/reference/evaluators-api#module-llm_evaluator) and HaystackFlowJudge class. +ff_evaluator = HaystackFlowJudge( + metric=RESPONSE_FAITHFULNESS_5POINT, + model=model, + progress_bar=True, + raise_on_failure=True, + save_results=True, + fail_on_parse_error=False +) + +# Setup the pipeline +eval_pipeline = Pipeline() + +# Add components to the pipeline +eval_pipeline.add_component("ff_evaluator", ff_evaluator) + +# Run the eval pipeline +results = eval_pipeline.run( + { + "ff_evaluator": { + 'query': questions, + 'context': contexts, + 'response': answers, + } + } +) + +# Print eval results +for result in results['ff_evaluator']['results']: + score = result['score'] + feedback = result['feedback'] + print(f"Score: {score}") + print(f"Feedback: {feedback}\n") + +``` + +### License +The code is licensed under the [Apache 2.0 license.](https://github.com/flowaicom/flow-judge/blob/main/LICENSE) + diff --git a/logos/flow-ai.png b/logos/flow-ai.png new file mode 100644 index 0000000000000000000000000000000000000000..617ed44f1fd877af540c895dcf49c504841ce315 GIT binary patch literal 23608 zcmYj(c_5VA`}mnbmV`p-hM6{7V%iaAq(!AtlBF<3r9z4pvb@#3DO^-;gqY6_6)MV= zNaj|eOA>`r%th8pLfQSEbKY@(Uw?Ve@|U|7%^w+jn;M)U&{!9WIiW zkB5X5d1|^W%gFr@@Zv}BUx$y69J-Rdv%a^$bfg>BZb=Ip`>-tIZf$Mja98QL*I(Ta zi}z%AEko#jmdw4^&mv6bc)AvaZ8@^Lch|EcN0>0>;M zj%ra6^NN4pB+QFE_GlpOp+R$R76ZoG+M4BgKY5Y7^HrmlVUHwIi$_)WwfywmG)%?{ z`%lhxV2Ms7{=s1GNC-rl!iReOkgvptJ8QtJaTga zF4cs@XNWLH)%PyU?SF3_DKi(@7%|*KwE|4T`(sMrfV4vcDcbwukySGg7?!-969jCa-0 zvS&&tFRw|$N{&_LH#pz(7+yT0OY63)t&IN+By10x!rmF0P$Z85mEC&qA|SaYLB-Nr z0;#RJ$e33gwP_$v4p9h|uGQc*#K|$Je^pg^+XnI?`;Xgaz$8>^|3^(%PVk8xGD!aV zd9D1rOp)0I{sf&FC#wr=p7dgnqJXrvZz zKTvpdtqe*$e2Fpdx2V1OBUfsUK?jPtv7sybfD$tLcs(voh1+@G-2B`CW_`?|t2M0t zuQOgMo?)VO@#T_MzLkToc9)U$UD9)s45x=DAf&MT0@_0wsgqadlmgBQmx9px9yisl zfg`GTh29;%-M2s@wqr7*D)DE}>}QJ5NWEWtTd^bQg7ad;kMfkT3M#eFf24qi=Ps-s ziu-!aNLv+|xId8E;afLYpOYksyPOjzGtt6(a%_c_((q~YzDva-SPqGnypxR93VzcT zq>mXuxjouAwZ$Ky@zOcec`PclzhG0>5xLnX7-*NdxT$ce1ri*2jphX70ezb#8B`u0 zP37XRNpsI2&Z(#-CXK3v^Ns$UNEq?ECXa5|npqVT_ke+xEIWwSAI`VW9_u)}7f`v7mBcc1avk(z>q~%=??2a+ybAolcH6)S317WRu91ZiVhE8rbM!V zG^tsi<)Q1&su4V)^yM)pg*DQGla`uiT#7Cl5YBvflvh*~G8NfmS>y^g92RiX%DV7i zwtfF}hel&XNorfaCNJKXYo2zD$gU+b?3m;cO&L@?bI;_=p%02Z7!Et6%m>s&^C zXlz{hlc=3jXD@FD1O3+FH92!%R|XdwAL`bvCp#RFO4=*gXhlMx%EG6lAr|fF!kR?A zxUV`Ltua0|S%0T19-(qwZs%@>&3SVzo@Wr=O*ZHDIvFyNoKKBp>FDU_eLw$M(28PO z>WlYml|t5aFO=CYYwQ(D@Pax~tdmmw?-lQuT_rx>jG={hVTZzMX@2MC_sp`t+wjcd zjOpRUAU)+{os#Eo-^ES;9zUmyh*Hp6?(E`YtcL|8zU>RoV9+$x@Ih2vU7l z>RvfXYQ4!pW{UAhrzM`tnUTgraVc)vyu?vFh+{~;&;Iwl6MrYVY4NN>@c8n}x=Ki^ zO(3gOO?31Ai3bk)j}=hdc}s0xFg6sZ^>gRg*q9>{u2%nrN2|W?PfPgxJcWJhb8U|s zw#2CXqu$2_PQU@-Y-RR8=D2CraXAKmnu(&ti2+PIQUrDSvL`!lGAgZmEE{cy+l1Qo z2h5=1!(+5(7>fXD4w)mi@9mxZkByDB%f>@6705N>MJJJ8+>FSkx>PM!CtAT&3m*J z&-T%8qfqpfOwfqX$h92TW7qyo-BiBk9XVuuUUE8+EW^gL`}So!Z2ym#_A)QT(@LaAxsG8m&}$FvVMYb5nc@FXmz5aw4Nl5!v;FPFbl8M{&W*4h8R;HWfVP? zft82y{b)2UL4Ba1CwA2$s(j*qSZT=wp+=huN9|Nm^i1)rx-mIQ{~dEO24VYFI=M&@ zb_lJ`w;JHcEdU~A+0cs;J7M88dg0AS3ShTNpvChk+~p$&w#9uD!txV2yxo(1at4ZC zM6cjh2+U8UAudYK!CPFLo)qCaBYX{@^3a6ZMek|*)P4iRXVPmZ>>ZMzUSvs4$18;1 ze9|Y4l;_YhWnek$f11o?ahHdE>OhYd68;)T*5@aKS}D6R2~!NxxF~KOt;2qUx`=;< zhW-B0PW|cK5=g6**2|p1(pe8{qLVek1vlpT-^FEEf~{2qmR)AveX5u2M@c1J@ab*c2gacjI!LG9XWeEwr0~% z@6YA!ztNn_9TN4nF`^%0hjh#9>ni=r9f^;~-X^6zm513-cXN-~=vFCSTKt#W!4Vys z0us}cWFD^XI#KOkA|7!v$@@;>WU72=wdS$DlT&-oYkH+Sltiz{{pbA_!aKpft_AFs z9Y6XP*ZkY;SiUmw^S0D}t=m7fejZmQi`;xtW6xU~?{z0Q@?OOcwW{Hq;7uWsMT%py zDU1UjrcZuS@-98c<+rER<65M#JmL0TD*RAxe!#3J>yjH%`wv#kUQL#ZyQ>Fh?k*eO zyg|ck^X9X=ABA5%r$p3-|G0DDh%%ac=*haEyuomvyg`SC;__9c*U2)GE*DtB`6&ZE z`p)KQ{fnPPEi&8K71cLBUcav_yXh(oJXxaYZVb;pjrW0?;MoTQ9TUaLvCq`itZ zB;{q!PEiv4dEodSc>C}5q#*~Xhc_RvhAXXGR9+CnB78KG3p%o03o^Zmavut^?68Jbh`A#dK%` z?4XS+ieiB`&1*R8%5s7e{<-2n>=nH*jo&%?pA9rV$%$1>A0c7h5d0cJ~=aFYIKo>;+hrY)Qe8GZ<6-OR58C( zmHyz-G;Um2z^VgTy*wJ^qets|n)f30**Go?v_7QDS=jH-KD&A)O?XSQR&i7b!{=AO z@Si5m!IML;EphJO;n0}&Q&`Y`k({KI>GlI=Q>eKl4h^W!*empVyL0#GG&9;t8Qk5_ zb)=CyC>OJ^IEv+_!M(#w>vmM`3z$pfxN8T*id&_Gr>lN%O0NENjOQ2}sY#!4v9?WCY1*YJWZ~0SO!2$<>d5I=iU=k@iANKFCKV04UWzr(|n;W`+Z(OTL z8&2)0w>jCq>!b`y~60NEK?DiiX&jn{(h#o z@8G)g4F!jlc>S;GHSO%ZkZ4XQOs_x8CZg6jl0sFmCWt&ssF26`+rwSlzO1Ahdw zGMnTp=>7&T*!5CEM%BXlA?-$2X`jmtTvo{in)SOsru7?r6h53xnN@vx55(_9vs{yh zG!IVJ2iVlVz*eB%@;oc6`bGV^cpBnb)vgy&dgzDMg9D9eW%rS0{>JW9*AiKp zTSmu*zGUw|NN=Y0SGGM8bvG`s`ddN=7@@arq(+X7(kFjkvws+C1*3BJ0@yA_&lQi% zqje^+n`k0Wx<{hoXy0-N+QY2q1vBzveb4oV$8B!sSw894AN)Oy2ZW-{_9YMjSI#bu zvLie+D!J_D;z={18aVIL@#9dC_umC*$S1bpRFQdKF%2B&JGre?o@Z* z&#-jSp>X@>6AJ*&c@`(kBv{=~fwo7LUzXo4VN_a-P+M;+_`fc~o-egLq$H|%&yg8u z;kJ>J2?np2YGonNIcK!>5s7p()2oIykDz=YdFqLd(ZQ}D!+XSy62_);1{t4c2MddL zr8b6S1R2uoFt;x@3Es}gvdr&sY786&h1*rtaJEtk#{$%rbR{mOaBRdXR`H1Nr9Izf zyx}L4Hbl2U^gsx2$BtnaHU)zb@J>@VSl@DlO4Lu#dAEURQ0pD>xZEAijVe4CMj2;7 z+Q7uKIBG9?=y*KHmL8Wb9{0mUan%fZT>i$cTLX-osa*avn7k{cvGgb21{~J0k(=6F zZwWq#T0_u zaryViw&2$!g|T=Ekd2yklLe^jI^_mg5{x$dG%b!wKz0Aa%2;BDZ2P)%59?PQ{x%Fa zARSu`ZiAFip-Eb$xvMWY7tAeF~ z(uNUg&XPkIp8%5FuLbpMh5}->A~Q-j$FoOr;?jJvll&nP_a@Jb(`zT<7E+ zLc6T7kdOn(h<1hdNxulff{55iRu$f=Y6~xJ%_av9-;>6);^LV6!}q4sc`%MzL_=>s zO;joZlMF3et|Um)S~rPa%Zz4mgqWCDw92W_04LGg=BzT|Hive8)nbS=C`QnIf4VP8I^#x0pw_!n?{hi1+Ag1cfB2_MQBO0q?9&Z z>ee(%B59IY^pZ23YQ&=r==I=w0K@j8#p_;9d`O}RAgQb5NT;sRoz}Esc#7FEl@|Of zTJUkvcRNF9ecwcjP=%c86pf|qQ2tmb5mm1ikvU)NWG?}u4l2O!BxuHI(`YK6up1{v zfEuK?f(;_f-)BV=wVoag8A_~Bsg5b!x~~#IhR!v5xvG>`W|N8H+L(*S4B2Bk=buVy zDk(s979(iGKH`i2L+@#UAJLnb_4EQARmr{^$&&xT0fP}VAWQ0m(+e1rs7|0xGqH6o z-VzkH7qAE7{zv@e3?8NE^8Vara@uzCh4q(n<70 zO|A(A**^$-X_9z@))ai%fQiM@DnDyk3B5WO z=#^TDq6t*&oEl-NSv?Yb7ZU69WYZnCU?=SC0E5E~A7XV(5#b~|zF!0X3kWhL?lJ$L ziSIx!rWKBPIScJTp3(rRfh^2fBmPFj{8(>N1+2l^2y$idy$wQx#QB1&Do;qdl3Nh} z|5}z{e?X|R9X5#u+&;k_DoiMK7K7l=2o->li@vW1S^Ec-(}B4K9Hef$Idk*(3+ax0?9= zAHhIARSbob5=!f&k?RCQJ(8uO`~L1Tus#ide3B+eQPcc#@@o+m){R@@i5)M?wHaM(m_(kSrhAsrfL3VdKm6Q;J$RLcx+xwF8Y27 zT6hm8mYq*jOr$b_|Hhp;K~Ig@FeYb$>K4(~lAp9}EGLHT@{@{AfI;F|p-CDUcf4n2 zG>mnIU=knT$O^1RB(TI@ogXb^ptKvog6= z`FQ`frir;1eZN9B8rtijk?%dhgNOBSY5N7M4@Y|fA6*Ca?|=5C{V2-95)-OV=ulcy zv~2WyjH$ryzp=)C%#@_D0as|XNuOZE8}+4(D)nVvHZ-l7;AV`dPBuDT7t`M{v1wwO z+M!P1G>PTXCWyDWv?h^VIMbl|QxweS>rIfKYP@%HriVGFUJg?D{9h)T=bId0rd(-I zySv9sifF=rThu1~vKH@P$J1WtI3@_E)KN^Fz+HO_<(abLAH|M)nC(ZmiPc2MnZ;-^ zaW=DX|mK;}Tq$RttklAFq#H0q6+pCMfWIQ#Re9zG~LNLRC^Zz&SU;*)Y|0(G87YGza`mRRO@phsV*g@hAQ(RW*MLUmp}B=G9&e6if%&2y z(?%Yho5Hq-o9#qtP{>L+*08x2zUMQ5b3U;h+gM@2Sbo@h#;47<7fPXm@$1HlEeD|0 z$3Q&3ZPFC>Uw!|MXgNb|0HhYcnS=8M4O9t$L%upDfaH4m3`GUMPY2dd8GPX9KMjVe zH)-9z0k#<;1M0Y&bo3CXPZ5%#(BmAu2@>!q2dtx@{wBsJF&1*WWzfZ+AznB5x^#5B z48|NnD2$&^+`1!-j;Dct7(`X4<1O|(J{aMErk*;xGu@xglL1t?oj3*M1nlxj44*^; z0H5O{U~JS-EMc`t61cige7^d`>odhI?T~osW(q(j(2X)J5Twlaoldnd*5*z@PEs09 z)-S{`UR^J>8v5p%TGpJ?0F>~qLIN_J#RH^KMXVjVm0BbTm&8lW0x@Uz-rA!9F+71f zS_(BVOG8l@8)oT3t;?OOfzowwtW#s|vv>zLN#43)kq&d>?yTtOUeVtI7j}d_u8taK z8q$o_M=hN&d*ZGnZyPwNK@P8W*BWK^te58pP8=wd1%4cW%rfL|i6_KYyBvq!T`^aA zWEDGf_HS%IT5g%QjNIxqr-FbGwTc#|)ypdmfXwsGUUL^op(dC$`9sro^3Ar7$_6FG1Nhv)R9fWO!xYlrKP!DY@)!B(F%M$A1uFO9xP)Ajw;&+fwVhy%uIj3T49&~go zy((4|LCA`BHF#?AH190~v-(ZAFC*hbzoLzl@B&OOed;Mu`h17X;1(i>*5@->yu-M? zFQJMQlT6slvye&WrShS;v0$A|CO z1Mge*DVup7k&?%Z43uT>-@x+gl-YLI&S~6R6SjYI#@rIPxZ2XSwhx^HHN~ zJEz~>DrodXuY(!`QNeFdH*4J<4?p%TZwhr0c1tvu;lBE1PU+D@LCVQZ=Wpl08RD$5 zGTq_uq5eiCQ))7}-uz!Aqa#ujZOR-%FJ|oX(hN~eR&WhDDTSzQABu0>U)gy`!zA5- zwaZ0{Hwicsr@(M;iuc&I)7z|2R!1@UZcwkN>r3tPsf{b!{(|dK$^bHPNRIN?HzsM8H zjs)^#2oFLzu`7(=IOZK7{x$yxpr=E_E{}(adkJjn9S|wlMJ94K0_+$%Q?L)hBG z01Rg-0iy=VMigN|z^uiqx{=%fwG|JKqlf$8;Uq#rIKPB;ogrM8hr(R41t z2m&C~LQ)SXk_@MzU&lzQ&;o&55b$ltN+LE!cs>zMRxC`l{+%F#mxrY(78s|S%P8vk_??N0#mVzhN6to(k5t?<84c|r$uO~2eIWX417SGgVH$|uqeJvy2nNsq;W@>GB`0z)O~5$HkWA$42G~Mx zGTa(Vx{`2$k_E=0e0sDAuECNRDDuCfY=@dw2EnYQ(PTgQDjhcxH@+I-`JbHK>9&S#CPr}1@ z(N_Qxxb{+ZTk zWVq2qJlv2B-%N%RyG6|KFoZVpikJ@C$P1?fM6r=yif7XK4!B*IF!K8#Hz+ppLTt^%Mm}yKo=6+{`?-BKxiXh9)>Zbjr@I#A#LP^`gnQT$lI7=f@mWjhBrHHfRrj5J+ZW<iSOeB#hXp4y66rHwp)DV|Cjc{OZ> z#YTP-8BV9cEbwsJ$P4*+Rock&u?C2ZeC0~KDsAN3@ev|6@&VYc(MFzf!K|T;ya+Bq zCXBohB%vpa{AMg)+Q?Iv$#7!KMEGn+50!aZ2BWEN?@S*Z+~M`G|I(MXLyeyYYW3Y7 zfATlbiZQ4+~TrA}{dZ!2}j8l(xIi>oAl}ppIyCo$T4< z>-@ZJw}j|_>7R!72g+$Bw2cMpCt>d6vC|kS`!}^aJU8(hIn@Qx(euA#=hk0rx!aoW z@1oB@3Rr76%izI5l`FF|4+_9O=Xp;TzWMO{hSm4|o@*|tSV1XV+%n8eIG7@1%m?o7 z(J%&QRAFpVtKhxUG4{icfnH}M(Lb0|aA1{czPz$yOV!X8UsY3N{@|+St-IsDA5(j% zx)$+=u&mOo(Yya$(0sMW>{GTJ+MIoL+O6xQo6gTc9PFkbTW#|CAf4M%|6Y!-mZ0-J zBwJw)#Lw>Fi6MdHeMR=moRX*mg)*-tMAN+YXsq^@oQm@Bo}{*wFQ;$;*ucqYsHH{g zcB32g!ux>Vu|U`Fo59P?eF#;)4>Mp^@G=c1dWz2totIGD`HjKx?`L#&R}YyLNB;!J z1^W(y)y2}$eHOu24T^rfeFts0cVV`cB@$UQn{e47c^#8m-*tST*lcNEynairHs zD%CqPD*u$5Es4m{&Y5vpk=^lhSkJj34XS(VZsQ)z!hL(I81sTo-hEIIFcm^$z1uf* zf5mm~5qP_NFb$L4!1@q-AP8JNs#zbN05n)O)O62>QhK~|IqNP@W_O%D6;uONICmt_ z=3aexK4A9A5%K=Tz*`S4-pOAkeXW-DVN(z!2Tx=}bR&m!2=K&BbEu{DF(cE{%#qq! zRfYA^0uJVeZ!A1iV7<4`j$}cU@-%=vU!X4F(~sSY@LL5F#5a9#!0fO2(~&K{DlF9i*hzW5GVZfq1faM&}NNK4wQUKW#$2emE(A$ z$J$E|Ms*Z+ghDkaX8X5P$oTFU$y0!^te`b*8gj?pF!k|X9rF+_sX*WRj%GVNf{IP- z6`(H$mpwTcC#3*_N$4N=WzVMhw50ML7n zHmyi~^nk&^))8%9;)D*E=Wu0_&PKD313T{*&qaOMccQ9)zbAD?@e3;F{ZVrwytv5# z38cp~A%EL8)09e4{L=rXPMx!2krLMfSM?Nuj4p80!r`q*! zZC>VMCiOh?2{UdH;lSIqD0@f_#ow2k%s)pKXk4=(t15f|U$zN#8z!Oq*oX=_6$-HI z0>vXpv~)$ssrD|!pN@yCRm@jrbAN@G5Jn|CrT>($PR2_=HCaHtsb)1m1##Ppo~J*z zf|_BSJK46Ga)8Ggv4j5$wuE9|d|dHnPCxSJ(C(KY=v|%OM2EE?wgtZ@aS7wQ8(c4+ zys8-k4-vF*a6sKlRN+-k9!l^VHb7MFNjHX264PdrWJ|xJ_>zCCk&lE{Sb=TQo-B8F9-c3X;h34G=5^_ChCJE z*ri#a|C5LzZ0IqW&dV%fQe(#Q8Uo13gDX+rpz6?Huw@+3X#5H#=YTQUXwSPVB}-2* zko><_pH?!?VeSf-s>`v**L-Iw zc-wTCl!xcwu$Ve9e2_VBR4KaF6-L^e>t<3lWJ=JM+rpz`kcj~4cwiR>^?uuq2KU3+ z^DStm0IFQDcPgudbn5L2lcrIV$t**Nd>{#h@`#n|?OlV`b5($0Ht`9F_HxRrn`L3Q zpbgFHO0>n`xJF5oLP{+4MH{6rK|4B}!_dZMBAts%@)2?K5xjR48Ud)^G1a^uM%2j@ zx+vzrOr(wtI@%cm$J<#P^?$mTHcf;xfT-@Tl?=$03$~Z_@n5BHpv*9PM7VtZIlR0H zgaj@^Nsu}bo^-I6{YFs!e18^iOcP?#Od+!lA6O{#^CTrjJ0$Q_SS5WE6DeF&ztj+gPk<){(B3zFSIf?He(tD)I@W+Gn4BS!{tUyxDpai%+NkyPSCxecJPy&g54 zmO~NLtxfz$(xQ&TMjK-JjAB(CKsU=!oGe};55IFzo6&gxv?EqSY9j=j9D~REB}iHZ zdDTsdE&_Q7g&2-tVI#y+JO=rH6DhDH?kn;lSCBY)2QEB31_0}Tk5%MH?iM+OW$jff zIzl7CsG&Z5WT1RfNI`s9)Hho?n)L8+4}Nojo~O!D7qJ^dndA7ERzSgFWBl%bGhLCv zS&a3BBZ*lKsgSjV<>&T5&0Iw3p6r`U{ydB&-^v&ykJKcpSwasiUSXv*mdXdH5GT@> zh4@V^ipv!EU~o2a7#zG9u~~|lqjO;i4~}<9C4mF!{Y4p=qiKg!2mu@B0VE?+{pnCL z!aG8EvK{yw0e7q_*r!7qu1J)QOCg*55NVzT>Gvb(+vEcX9JZt9LOFe;<7z(cri!Ep z|Fa>Y5(L(`GD!49ErKXj5Xh3?JwVo=|WM%3G}ABwF1fU%#l$K*(kiO%cnT{Oz* zBb4IQ4s|bX44L3~2l+;mmrV0HqYOW6EKuv7i^0eVGC5}sODPF(q7r;e*qFST@FeaU zA@?D^N!^QQU>_E>mcrhUf7K^lITOn&ZUGjKX*JY35W!bqIcIqnD+(qPU?;Rdlt!ey zo>zuP;G1?%NqZ;n3DQ`!3gn_eN z$Oz}PKEg?!hm$7AM{_|I63nuLvvwEJQX^dX@-c67PqR6yz_A(H^&FJWqw)3tV;2!k zULu$P5BoowN**wLv@l8n4NRMvU4D|Z@G4pK2S~m$X=iAXL>ra`dOx2+yp`rD15nYr zpP>D|O&;DkC;D4egAMul`*jrB?vcw?10tla(ZNX?iP8^_ofFK<7zBbHA#D^Vf zPlCNBGX1F=yi1x~PC!B*Q(A4;0l|PaS#=1PCTu=Y=%D{qLWJ{0@PHhP`2eoGh5}BI z(irCWhfo!6gS1eVwCr{OxxF;%Q#T@l=BNX^eRiqhT71F|`7+QW^`Gzt@+MP&Glk%+ z0QsUUNSpe=EikYH=iNV_=&JN3_bG^H3?`hq5oF!-^dXCflWU0Q2pes{>5n^IDm+c5 zfI+62;)5j?WDVu`X#|(WfZaF8NKHd{n7fo5E-{c#qh2)tBUxw)(V4x-fl*iNGfDqps5b9YpF#^(H{LS9BBPUsC9YL`f z%es}uh&~VD8Uq~g?7_muJi%ZSLbmD@%=P6;E zfF?dvGJ#q`c7?BxugU5DTzK3eRBJhAUkeBk??7k(Y8hitqIj7lw7?pLqM)ur;OGAx z_U#Ws(#bI3!iFCpDSFj9z*ex`c;dbi{0Z1$G3`4h`iHaNzI0r4>X22g{B4x zG#Z|dELa}3CqZGXS}~)9si1C^4~G5B6vY7Ev!USzx48tu7a2;M2qvc(%+b&`!p@@OUMSKUK|v7|^1Ds=DUv13aRyS_w?P?%4~d_7_Q3IcT%SOTtrF@QDFa;J zKnvdwR9N5zrr%gIWZ|cQe6EK2Ift`^=JX^m?!r`X z{-GS7>|N}9P#Zm)OM;Loj!?kW2F(BiNu4EHaBd!jd=#L%nHXZe2Xx-}uX96|6;}Xh ziui)*TylF2{sLyNExwq>x7qLw4rJx_NY)9)H+)})s46gJJh-XIvJWsG49fo~1%t@D zwi2Msg&>&~BHal(wyamaKWF<49 zWi5CKafZ-vNIRH38I6FasvLN7BVHEYBK3kPfF~L-8W#(5pfXzA8KYmNm=6J`0#wr8 z?Yrc2)q9*6GU@`HE*wP|2X9rZu^r*)GG&B)7@3vy+jq`vXvo10gfF&Q6tadZDY4{^ zLFdx{I>8%(syu7BE)aLmfCC^6JW862aZp0bHvtrY43Gl8GD`z_|Jcye4D~Ir_&(Ta zYFwZZu0uf!?no+8N1zI^@OTrv8i5&CVa9XlizMnDw-CT3!p0Ny2aMX1Jy0G8WP_Z1 zZ14jzm_iy1pdlJt9DHjkJP9(kfXxV57HAopM-fj`9coe`?n7to9iV9+LBX#RnR$R;RH;EvfoETMM$IQ& zTUWW9ZrtB-dt3RsahcnDXgTOi%}}g0h7YsJZZ(Big$g@2qxkxP45ZIvaiiOo!b9m*xU+oAFe2v!gW594O)zK~}@4*pT=SLJ{b| zQ$RNx^wcf?qa>b~r_M`9BFJc0I)t$fSARJMjc5(*#%~ZYdFU5Mbyt5OWE2A#k0E#d zJG`IM|JZ=Zuu*{pz_rIyF><6*i?2IzmYUAdhKcuvDtl`&77<~H ziK~wjflkx@e=B{VO>H(qGEM@x<`~pIXh=p?%^ANGEOeD77RIrv?E3hxzS!mE=YT5N z0#EkCV@fZw@i_;zc+@RW%KIs?YYeNu+<;+dE$r4bI5R4?B@g%LI+u|)uL5R<9CZNp z+eUikRCqj?29~}e*X3>qu6{rWlK6}?@aZddz(leMUO@aFiqybx8SM=ET7t_kkrsr@ z&p{syK7GLaiDNIwq80(i!hy4kVMMZUeGh;6aoim|R4Z zduPa`=sr=l9JL!L>gqiEDw%ASo#9`kJ1qV-K|{ciYgUGbzISF2ExrF25ej6>c-ru+ zkEGjCdBa!9^CvbigFu}&8x5$#I(49@eek{|zi&Bwp{6o0A8_^5Zf!OSS66fkSROwx zi~0M8up|eCXl5DFvUukxxF{;CsNcj=mR={)8)qc~BDW1zOht0Xh6BKxCZ*$KB9zW&C+c|M>Xs zYm&ka;LM4Ry=8|xah|vl-fW_IT0H(^a3!oE$bs+8@cSX_6<7Ph%Z#|iJrZhkZN5-9{qT>#Cn_@7V~O}D zppf_k!V5p7qQ@t3mKl47v4m<@iRd=>XBkpPG8$>~OQPD~{n$2eGGXC#=>=K*q1+zJ zI?O<{4^mpzAb;z>iMnmhF@)C-5nwrSXSrR!J0M}*d$wAS$|imzq8x`?g>9xrza5$Q z-uP-=h9#rrCwiW?p%x-DGV_|OQe33o^2;nc{E5o#{|K4NBNff$d+ewVUdI9)ZTv0V z@khwJS-h&JZ$gzUNHsSZqC50HBE#{5L3=X?hVm+_|hKxS00NQm;Bq&C@|`A-#WaO`cG*6g$JvRK2(L)rk85a z%2N(0ym0RT{1#GWs5-nsMNy6=vKYozFRl^X*GMZ?LKz32tIQ^Y-~KqemNCyP=4H>* zv(oKQ5aHuM^t!~ZUNL?%gG{Eb}3t;Af#xPyg!^g^ek1G4L3Dcuj@HO z3P30bR)Ajq*B!&=M~=W=q`1#pcXP_us3zbMOO**%Anv zEoC*n(4{y@BN@7do}Yuxx-S5oq4Fl%vaMam!XeI&`BY2h9{eMz*8KCZ`%Y!{y9BCZ zD0*y8p;2F~EM-C7Eb#9rBq_dkLbqNq9i^;ac?}H(Yao>y3e_28w#Tvd%U+;#70lfS z8&X0K!ClTJe{vG__I71A%Q;P9_wP>ON=LW`uRCJi{q&$J_2lhcZl(@cAIksX${*72 zlhBLr>1@T>YWMY)!!Q4#h+?8Mt$(IReKNY)kG6F;<%XTfPT!-<{VG8%dZB!(fx;Z` z^;sJ1?rcr1E?n-R$}!0Bb7s}gl7`N4#?0AFT)wNv^P`>G`)lB2TZ2tRSSbx9%Bx&^~G}8uuPNmoU5Z-BdlS;@fxcfwmhF>lqw_W1{2Ju?e#- zdvW;W=R-{Q6=#Gt-*>keR?mONux>2)_8fa%abX3%4R0EDq8FVf+#k!e$HLnC(t@}4 z&4x}&H}Cf)eZTPT_3@evDn?k7_ut%e|9htaic9#pTMk?P=U<;bT=z?#Chy+RwShS@ z&c1cV!icvE6bl>h^q%xRy^~7r%TMDip89L#C%uzjvV1B3jg8XZ!&Atfb@My*VoAdq zdCx+ZqKyw8FuwTPa=$UD5vOLYkLJWT(y|Mv81~x5hb#Dnm)SWt$=M*h(-*W`6@F6> z*2wnPobih(e^CR1+GO6{d<+R0 zJ9dNQg*v+)4IRulFB^S3;MnVMKQl@7*rLrYp!G|4!|wrtH~yqJ<8E%49NRI#sAtjg zap??d^m}?n*HD)fiVv9K;s#@Qx11b<{<3=`m+9QqA22gvnQ9qj_~}IVqX# zi!-oc6Ik88{rJ6}H`6`ij%jVjcZHDLF(-d;_IwQJexpJE!-w!kUgtU1(NcQ$PkX-Em3911OPkDR@sA%R zj6$+eu(AK(n!5`y!Xc2GKjGiddsB;d96}bj6W6sqYrSb~PspcCMKl!SV&9@0O-G4k z;dhcHq7E|M?agLJZ~1N9I4*3Ycnq@tLgZ51dQBU^@qT&_pHwh?k1oAkknM6Vbh=yY z4fCU4PQCt=r;I+^hK#>BoOu&l+4gObtnNRhuREXacL?GQ75D|QBG%sty!)#n+7Rus z#dPC?Egj?mn81tGJL});+LvY7JCDh{P&Bw=@glwMS2#;}7FCBX1Wu=aEitFV+{C0R|%(=*&zRmT=WG0L9-(jCP5yx&&bs`jhP3dSZ7kiFTI?i;3L#4fID!3965cSRSfsCLH5~>=oJmH?!p{vcp~)v!HO}!ZZsQ zArk1xU0_{B->m|&dYnDARz02^@9^6bC!1i_^}CeGs#{w2{P#ly3g z|NHdcR#fgZy6=Lz1WocCUHgMJ9OCTg7+U6hBFzkmIHA|!;N~nDyxJ39 z)wtA<%DL(C$1&yP8o|8^L(!GObQ`o``ZOZd1GGIdIqf zS(|9;9H>z$S&A*K@}l@Z?4x916^-;)GQ;!9+a8WN3qL-Z+YfaDl~?;#ElU2Y-)rcb z-5XoX4x2oE{1zyfpo{3=4(p8exb~Vv@VlT`GT;E_;!wu6gGeb5O6a53ST71 z@0kPHWQvihbo8>f;nUq607kxfw(|qsz(-LRAg_VsqF0qJHV-8?-n^gR^E2Sm%k-Hf zM(nqDSLFunX&JxoU$ngIPI?>%ug%vz7Z~e3IL#8E<-O5^jvb36zy%gseYQR~vLq9p z*&x9~yKn%%0Nwegzj^y5sr>B&-F)sOUSHwG&T~d{aGpaA?2G9O&`AqPTQi zN!k3vBVAaAf2Nat>Bj2Zu6s$?y4&W4&Aju_1edPIomN@>ozYqUm*cq1R^T~bj@Aj^ z&1-Q>b1FJOFUglezogH8bat-e%it--e6210PM9 k88trQT4cB{qJCW3)^Xe3uD}vA1b^$-Y+ik575~it137l7Z2$lO literal 0 HcmV?d00001