diff --git a/coverage.xml b/coverage.xml index 9af9ebee27365dd1289c5962a87b8451a3feef7c..001746f7bba9c18e9312a826e700970a53a33cc8 100644 --- a/coverage.xml +++ b/coverage.xml @@ -1,423 +1,569 @@ <?xml version="1.0" ?> -<coverage version="7.3.3" timestamp="1702894511196" lines-valid="345" lines-covered="345" line-rate="1" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0"> +<coverage version="7.3.3" timestamp="1702982262095" lines-valid="486" lines-covered="5" line-rate="0.01029" branches-covered="0" branches-valid="0" branch-rate="0" complexity="0"> <!-- Generated by coverage.py: https://coverage.readthedocs.io/en/7.3.3 --> <!-- Based on https://raw.githubusercontent.com/cobertura/web/master/htdocs/xml/coverage-04.dtd --> <sources> <source>/Users/jakit/customers/aurelio/semantic-router/semantic_router</source> </sources> <packages> - <package name="." line-rate="1" branch-rate="0" complexity="0"> + <package name="." line-rate="0.01404" branch-rate="0" complexity="0"> <classes> - <class name="__init__.py" filename="__init__.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="__init__.py" filename="__init__.py" complexity="0" line-rate="0.25" branch-rate="0"> <methods/> <lines> <line number="1" hits="1"/> - <line number="2" hits="1"/> - <line number="4" hits="1"/> + <line number="2" hits="0"/> + <line number="3" hits="0"/> + <line number="5" hits="0"/> </lines> </class> - <class name="hybrid_layer.py" filename="hybrid_layer.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="hybrid_layer.py" filename="hybrid_layer.py" complexity="0" line-rate="0.04444" branch-rate="0"> <methods/> <lines> <line number="1" hits="1"/> <line number="2" hits="1"/> <line number="3" hits="1"/> <line number="5" hits="1"/> - <line number="11" hits="1"/> - <line number="12" hits="1"/> - <line number="15" hits="1"/> - <line number="16" hits="1"/> - <line number="17" hits="1"/> - <line number="18" hits="1"/> - <line number="19" hits="1"/> - <line number="21" hits="1"/> - <line number="24" hits="1"/> - <line number="25" hits="1"/> - <line number="26" hits="1"/> - <line number="28" hits="1"/> - <line number="29" hits="1"/> - <line number="30" hits="1"/> - <line number="31" hits="1"/> - <line number="33" hits="1"/> - <line number="35" hits="1"/> - <line number="37" hits="1"/> - <line number="38" hits="1"/> - <line number="40" hits="1"/> - <line number="41" hits="1"/> - <line number="42" hits="1"/> - <line number="43" hits="1"/> - <line number="44" hits="1"/> - <line number="45" hits="1"/> - <line number="47" hits="1"/> - <line number="49" hits="1"/> - <line number="50" hits="1"/> - <line number="52" hits="1"/> - <line number="54" hits="1"/> - <line number="55" hits="1"/> - <line number="60" hits="1"/> - <line number="61" hits="1"/> - <line number="62" hits="1"/> - <line number="64" hits="1"/> - <line number="65" hits="1"/> - <line number="66" hits="1"/> - <line number="70" hits="1"/> - <line number="71" hits="1"/> - <line number="73" hits="1"/> - <line number="75" hits="1"/> - <line number="76" hits="1"/> - <line number="78" hits="1"/> - <line number="80" hits="1"/> - <line number="85" hits="1"/> - <line number="86" hits="1"/> - <line number="88" hits="1"/> - <line number="89" hits="1"/> - <line number="91" hits="1"/> - <line number="93" hits="1"/> - <line number="95" hits="1"/> - <line number="96" hits="1"/> - <line number="97" hits="1"/> - <line number="99" hits="1"/> - <line number="100" hits="1"/> - <line number="101" hits="1"/> - <line number="102" hits="1"/> - <line number="104" hits="1"/> - <line number="105" hits="1"/> - <line number="106" hits="1"/> - <line number="108" hits="1"/> - <line number="109" hits="1"/> - <line number="111" hits="1"/> - <line number="112" hits="1"/> - <line number="114" hits="1"/> - <line number="116" hits="1"/> - <line number="117" hits="1"/> - <line number="118" hits="1"/> - <line number="120" hits="1"/> - <line number="121" hits="1"/> - <line number="122" hits="1"/> - <line number="123" hits="1"/> - <line number="124" hits="1"/> - <line number="125" hits="1"/> - <line number="126" hits="1"/> - <line number="128" hits="1"/> - <line number="131" hits="1"/> - <line number="132" hits="1"/> - <line number="135" hits="1"/> - <line number="136" hits="1"/> - <line number="138" hits="1"/> - <line number="139" hits="1"/> - <line number="141" hits="1"/> - <line number="142" hits="1"/> - <line number="143" hits="1"/> - <line number="145" hits="1"/> + <line number="6" hits="0"/> + <line number="12" hits="0"/> + <line number="15" hits="0"/> + <line number="16" hits="0"/> + <line number="17" hits="0"/> + <line number="18" hits="0"/> + <line number="19" hits="0"/> + <line number="21" hits="0"/> + <line number="24" hits="0"/> + <line number="25" hits="0"/> + <line number="26" hits="0"/> + <line number="28" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="31" hits="0"/> + <line number="33" hits="0"/> + <line number="35" hits="0"/> + <line number="37" hits="0"/> + <line number="38" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="43" hits="0"/> + <line number="44" hits="0"/> + <line number="45" hits="0"/> + <line number="47" hits="0"/> + <line number="49" hits="0"/> + <line number="50" hits="0"/> + <line number="52" hits="0"/> + <line number="54" hits="0"/> + <line number="55" hits="0"/> + <line number="60" hits="0"/> + <line number="61" hits="0"/> + <line number="62" hits="0"/> + <line number="64" hits="0"/> + <line number="65" hits="0"/> + <line number="66" hits="0"/> + <line number="70" hits="0"/> + <line number="71" hits="0"/> + <line number="73" hits="0"/> + <line number="75" hits="0"/> + <line number="76" hits="0"/> + <line number="78" hits="0"/> + <line number="80" hits="0"/> + <line number="85" hits="0"/> + <line number="86" hits="0"/> + <line number="88" hits="0"/> + <line number="89" hits="0"/> + <line number="91" hits="0"/> + <line number="93" hits="0"/> + <line number="95" hits="0"/> + <line number="96" hits="0"/> + <line number="97" hits="0"/> + <line number="99" hits="0"/> + <line number="100" hits="0"/> + <line number="101" hits="0"/> + <line number="102" hits="0"/> + <line number="104" hits="0"/> + <line number="105" hits="0"/> + <line number="106" hits="0"/> + <line number="108" hits="0"/> + <line number="109" hits="0"/> + <line number="111" hits="0"/> + <line number="112" hits="0"/> + <line number="114" hits="0"/> + <line number="116" hits="0"/> + <line number="117" hits="0"/> + <line number="118" hits="0"/> + <line number="120" hits="0"/> + <line number="121" hits="0"/> + <line number="122" hits="0"/> + <line number="123" hits="0"/> + <line number="124" hits="0"/> + <line number="125" hits="0"/> + <line number="126" hits="0"/> + <line number="128" hits="0"/> + <line number="131" hits="0"/> + <line number="132" hits="0"/> + <line number="135" hits="0"/> + <line number="136" hits="0"/> + <line number="138" hits="0"/> + <line number="139" hits="0"/> + <line number="141" hits="0"/> + <line number="142" hits="0"/> + <line number="143" hits="0"/> + <line number="145" hits="0"/> </lines> </class> - <class name="layer.py" filename="layer.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="layer.py" filename="layer.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="8" hits="1"/> - <line number="9" hits="1"/> - <line number="10" hits="1"/> - <line number="13" hits="1"/> - <line number="14" hits="1"/> - <line number="15" hits="1"/> - <line number="16" hits="1"/> - <line number="18" hits="1"/> - <line number="19" hits="1"/> - <line number="21" hits="1"/> - <line number="22" hits="1"/> - <line number="23" hits="1"/> - <line number="24" hits="1"/> - <line number="26" hits="1"/> - <line number="28" hits="1"/> - <line number="30" hits="1"/> - <line number="32" hits="1"/> - <line number="33" hits="1"/> - <line number="34" hits="1"/> - <line number="35" hits="1"/> - <line number="36" hits="1"/> - <line number="37" hits="1"/> - <line number="39" hits="1"/> - <line number="41" hits="1"/> - <line number="43" hits="1"/> - <line number="46" hits="1"/> - <line number="47" hits="1"/> - <line number="49" hits="1"/> - <line number="50" hits="1"/> - <line number="52" hits="1"/> - <line number="53" hits="1"/> - <line number="55" hits="1"/> - <line number="56" hits="1"/> - <line number="58" hits="1"/> - <line number="60" hits="1"/> - <line number="63" hits="1"/> - <line number="66" hits="1"/> - <line number="67" hits="1"/> - <line number="68" hits="1"/> - <line number="75" hits="1"/> - <line number="76" hits="1"/> - <line number="82" hits="1"/> - <line number="87" hits="1"/> - <line number="88" hits="1"/> - <line number="90" hits="1"/> - <line number="92" hits="1"/> - <line number="93" hits="1"/> - <line number="95" hits="1"/> - <line number="96" hits="1"/> - <line number="98" hits="1"/> - <line number="99" hits="1"/> - <line number="101" hits="1"/> - <line number="102" hits="1"/> - <line number="103" hits="1"/> - <line number="104" hits="1"/> - <line number="105" hits="1"/> - <line number="106" hits="1"/> - <line number="107" hits="1"/> - <line number="109" hits="1"/> - <line number="112" hits="1"/> - <line number="113" hits="1"/> - <line number="116" hits="1"/> - <line number="117" hits="1"/> - <line number="119" hits="1"/> - <line number="120" hits="1"/> - <line number="122" hits="1"/> - <line number="123" hits="1"/> - <line number="124" hits="1"/> - <line number="126" hits="1"/> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="4" hits="0"/> + <line number="6" hits="0"/> + <line number="7" hits="0"/> + <line number="12" hits="0"/> + <line number="13" hits="0"/> + <line number="16" hits="0"/> + <line number="17" hits="0"/> + <line number="18" hits="0"/> + <line number="19" hits="0"/> + <line number="21" hits="0"/> + <line number="24" hits="0"/> + <line number="25" hits="0"/> + <line number="27" hits="0"/> + <line number="28" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="32" hits="0"/> + <line number="34" hits="0"/> + <line number="36" hits="0"/> + <line number="38" hits="0"/> + <line number="39" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="43" hits="0"/> + <line number="45" hits="0"/> + <line number="47" hits="0"/> + <line number="48" hits="0"/> + <line number="49" hits="0"/> + <line number="50" hits="0"/> + <line number="51" hits="0"/> + <line number="52" hits="0"/> + <line number="54" hits="0"/> + <line number="55" hits="0"/> + <line number="56" hits="0"/> + <line number="57" hits="0"/> + <line number="58" hits="0"/> + <line number="59" hits="0"/> + <line number="61" hits="0"/> + <line number="63" hits="0"/> + <line number="66" hits="0"/> + <line number="67" hits="0"/> + <line number="69" hits="0"/> + <line number="70" hits="0"/> + <line number="72" hits="0"/> + <line number="73" hits="0"/> + <line number="75" hits="0"/> + <line number="76" hits="0"/> + <line number="78" hits="0"/> + <line number="80" hits="0"/> + <line number="83" hits="0"/> + <line number="86" hits="0"/> + <line number="87" hits="0"/> + <line number="88" hits="0"/> + <line number="95" hits="0"/> + <line number="96" hits="0"/> + <line number="102" hits="0"/> + <line number="107" hits="0"/> + <line number="108" hits="0"/> + <line number="110" hits="0"/> + <line number="112" hits="0"/> + <line number="113" hits="0"/> + <line number="115" hits="0"/> + <line number="116" hits="0"/> + <line number="118" hits="0"/> + <line number="119" hits="0"/> + <line number="121" hits="0"/> + <line number="122" hits="0"/> + <line number="123" hits="0"/> + <line number="124" hits="0"/> + <line number="125" hits="0"/> + <line number="126" hits="0"/> + <line number="127" hits="0"/> + <line number="129" hits="0"/> + <line number="132" hits="0"/> + <line number="133" hits="0"/> + <line number="136" hits="0"/> + <line number="137" hits="0"/> + <line number="139" hits="0"/> + <line number="140" hits="0"/> + <line number="142" hits="0"/> + <line number="143" hits="0"/> + <line number="144" hits="0"/> + <line number="146" hits="0"/> + <line number="148" hits="0"/> + <line number="149" hits="0"/> + <line number="150" hits="0"/> + <line number="151" hits="0"/> </lines> </class> - <class name="linear.py" filename="linear.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="linear.py" filename="linear.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="4" hits="1"/> - <line number="7" hits="1"/> - <line number="18" hits="1"/> - <line number="19" hits="1"/> - <line number="20" hits="1"/> - <line number="21" hits="1"/> - <line number="24" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="28" hits="1"/> - <line number="30" hits="1"/> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="4" hits="0"/> + <line number="7" hits="0"/> + <line number="18" hits="0"/> + <line number="19" hits="0"/> + <line number="20" hits="0"/> + <line number="21" hits="0"/> + <line number="24" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="28" hits="0"/> + <line number="30" hits="0"/> </lines> </class> - <class name="schema.py" filename="schema.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="route.py" filename="route.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="4" hits="1"/> - <line number="6" hits="1"/> - <line number="13" hits="1"/> - <line number="14" hits="1"/> - <line number="15" hits="1"/> - <line number="16" hits="1"/> - <line number="19" hits="1"/> - <line number="20" hits="1"/> - <line number="21" hits="1"/> - <line number="22" hits="1"/> - <line number="25" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="28" hits="1"/> - <line number="29" hits="1"/> - <line number="31" hits="1"/> - <line number="32" hits="1"/> - <line number="33" hits="1"/> - <line number="34" hits="1"/> - <line number="35" hits="1"/> - <line number="36" hits="1"/> - <line number="37" hits="1"/> - <line number="38" hits="1"/> - <line number="39" hits="1"/> - <line number="41" hits="1"/> - <line number="42" hits="1"/> - <line number="45" hits="1"/> - <line number="46" hits="1"/> - <line number="47" hits="1"/> - <line number="48" hits="1"/> - <line number="49" hits="1"/> - <line number="51" hits="1"/> - <line number="52" hits="1"/> - <line number="53" hits="1"/> - <line number="55" hits="1"/> - <line number="56" hits="1"/> + <line number="1" hits="0"/> + <line number="2" hits="0"/> + <line number="3" hits="0"/> + <line number="4" hits="0"/> + <line number="5" hits="0"/> + <line number="7" hits="0"/> + <line number="8" hits="0"/> + <line number="9" hits="0"/> + <line number="11" hits="0"/> + <line number="14" hits="0"/> + <line number="15" hits="0"/> + <line number="16" hits="0"/> + <line number="17" hits="0"/> + <line number="19" hits="0"/> + <line number="20" hits="0"/> + <line number="21" hits="0"/> + <line number="22" hits="0"/> + <line number="23" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="31" hits="0"/> + <line number="34" hits="0"/> + <line number="36" hits="0"/> + <line number="37" hits="0"/> + <line number="38" hits="0"/> + <line number="39" hits="0"/> + <line number="42" hits="0"/> + <line number="43" hits="0"/> + <line number="44" hits="0"/> + <line number="45" hits="0"/> + <line number="47" hits="0"/> + <line number="48" hits="0"/> + <line number="50" hits="0"/> + <line number="51" hits="0"/> + <line number="53" hits="0"/> + <line number="54" hits="0"/> + <line number="55" hits="0"/> + <line number="57" hits="0"/> + <line number="58" hits="0"/> + <line number="62" hits="0"/> + <line number="63" hits="0"/> + <line number="64" hits="0"/> + <line number="66" hits="0"/> + <line number="67" hits="0"/> + <line number="68" hits="0"/> + <line number="69" hits="0"/> + <line number="70" hits="0"/> + <line number="71" hits="0"/> + <line number="72" hits="0"/> + <line number="74" hits="0"/> + <line number="75" hits="0"/> + <line number="76" hits="0"/> + <line number="80" hits="0"/> + <line number="82" hits="0"/> + <line number="83" hits="0"/> + <line number="84" hits="0"/> + <line number="90" hits="0"/> + <line number="96" hits="0"/> + <line number="98" hits="0"/> + <line number="99" hits="0"/> + <line number="101" hits="0"/> + <line number="102" hits="0"/> + <line number="104" hits="0"/> + <line number="105" hits="0"/> + <line number="106" hits="0"/> + <line number="108" hits="0"/> + <line number="110" hits="0"/> + <line number="111" hits="0"/> + <line number="112" hits="0"/> + <line number="114" hits="0"/> + <line number="139" hits="0"/> + <line number="144" hits="0"/> + <line number="156" hits="0"/> + <line number="157" hits="0"/> + <line number="158" hits="0"/> + <line number="159" hits="0"/> + <line number="161" hits="0"/> + <line number="163" hits="0"/> + <line number="164" hits="0"/> + <line number="165" hits="0"/> + <line number="168" hits="0"/> + <line number="173" hits="0"/> + <line number="175" hits="0"/> + <line number="176" hits="0"/> + <line number="178" hits="0"/> + <line number="179" hits="0"/> + <line number="181" hits="0"/> + <line number="182" hits="0"/> + <line number="183" hits="0"/> + <line number="184" hits="0"/> + <line number="185" hits="0"/> + <line number="186" hits="0"/> + <line number="187" hits="0"/> + <line number="189" hits="0"/> + <line number="193" hits="0"/> + <line number="194" hits="0"/> + <line number="195" hits="0"/> + <line number="196" hits="0"/> + <line number="198" hits="0"/> + <line number="200" hits="0"/> + <line number="201" hits="0"/> + <line number="203" hits="0"/> + <line number="205" hits="0"/> + <line number="206" hits="0"/> + <line number="207" hits="0"/> + <line number="208" hits="0"/> + <line number="209" hits="0"/> + <line number="210" hits="0"/> + <line number="211" hits="0"/> + <line number="213" hits="0"/> + <line number="217" hits="0"/> + <line number="218" hits="0"/> + <line number="219" hits="0"/> + <line number="221" hits="0"/> + <line number="222" hits="0"/> + <line number="223" hits="0"/> + <line number="224" hits="0"/> + <line number="225" hits="0"/> + <line number="227" hits="0"/> + <line number="228" hits="0"/> + <line number="229" hits="0"/> + <line number="231" hits="0"/> + <line number="232" hits="0"/> + </lines> + </class> + <class name="schema.py" filename="schema.py" complexity="0" line-rate="0" branch-rate="0"> + <methods/> + <lines> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="5" hits="0"/> + <line number="6" hits="0"/> + <line number="13" hits="0"/> + <line number="14" hits="0"/> + <line number="15" hits="0"/> + <line number="16" hits="0"/> + <line number="19" hits="0"/> + <line number="20" hits="0"/> + <line number="21" hits="0"/> + <line number="22" hits="0"/> + <line number="23" hits="0"/> + <line number="25" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="28" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="31" hits="0"/> + <line number="32" hits="0"/> + <line number="33" hits="0"/> + <line number="35" hits="0"/> + <line number="36" hits="0"/> + <line number="39" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="43" hits="0"/> + <line number="45" hits="0"/> + <line number="46" hits="0"/> + <line number="47" hits="0"/> + <line number="49" hits="0"/> + <line number="50" hits="0"/> </lines> </class> </classes> </package> - <package name="encoders" line-rate="1" branch-rate="0" complexity="0"> + <package name="encoders" line-rate="0" branch-rate="0" complexity="0"> <classes> - <class name="__init__.py" filename="encoders/__init__.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="__init__.py" filename="encoders/__init__.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="2" hits="1"/> - <line number="3" hits="1"/> - <line number="4" hits="1"/> - <line number="6" hits="1"/> + <line number="1" hits="0"/> + <line number="2" hits="0"/> + <line number="3" hits="0"/> + <line number="4" hits="0"/> + <line number="6" hits="0"/> </lines> </class> - <class name="base.py" filename="encoders/base.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="base.py" filename="encoders/base.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="4" hits="1"/> - <line number="5" hits="1"/> - <line number="7" hits="1"/> - <line number="8" hits="1"/> - <line number="10" hits="1"/> - <line number="11" hits="1"/> + <line number="1" hits="0"/> + <line number="4" hits="0"/> + <line number="5" hits="0"/> + <line number="7" hits="0"/> + <line number="8" hits="0"/> + <line number="10" hits="0"/> + <line number="11" hits="0"/> </lines> </class> - <class name="bm25.py" filename="encoders/bm25.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="bm25.py" filename="encoders/bm25.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="5" hits="1"/> - <line number="8" hits="1"/> - <line number="9" hits="1"/> - <line number="10" hits="1"/> - <line number="12" hits="1"/> - <line number="13" hits="1"/> - <line number="14" hits="1"/> - <line number="16" hits="1"/> - <line number="17" hits="1"/> - <line number="18" hits="1"/> - <line number="19" hits="1"/> - <line number="20" hits="1"/> - <line number="22" hits="1"/> - <line number="24" hits="1"/> - <line number="25" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="28" hits="1"/> - <line number="29" hits="1"/> - <line number="30" hits="1"/> - <line number="32" hits="1"/> - <line number="34" hits="1"/> - <line number="35" hits="1"/> - <line number="36" hits="1"/> - <line number="37" hits="1"/> - <line number="38" hits="1"/> - <line number="39" hits="1"/> - <line number="40" hits="1"/> - <line number="41" hits="1"/> - <line number="42" hits="1"/> - <line number="44" hits="1"/> - <line number="45" hits="1"/> - <line number="46" hits="1"/> - <line number="47" hits="1"/> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="5" hits="0"/> + <line number="8" hits="0"/> + <line number="9" hits="0"/> + <line number="10" hits="0"/> + <line number="12" hits="0"/> + <line number="13" hits="0"/> + <line number="14" hits="0"/> + <line number="16" hits="0"/> + <line number="17" hits="0"/> + <line number="18" hits="0"/> + <line number="19" hits="0"/> + <line number="20" hits="0"/> + <line number="22" hits="0"/> + <line number="24" hits="0"/> + <line number="25" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="28" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="32" hits="0"/> + <line number="34" hits="0"/> + <line number="35" hits="0"/> + <line number="36" hits="0"/> + <line number="37" hits="0"/> + <line number="38" hits="0"/> + <line number="39" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="44" hits="0"/> + <line number="45" hits="0"/> + <line number="46" hits="0"/> + <line number="47" hits="0"/> </lines> </class> - <class name="cohere.py" filename="encoders/cohere.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="cohere.py" filename="encoders/cohere.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="5" hits="1"/> - <line number="8" hits="1"/> - <line number="9" hits="1"/> - <line number="11" hits="1"/> - <line number="16" hits="1"/> - <line number="17" hits="1"/> - <line number="18" hits="1"/> - <line number="19" hits="1"/> - <line number="20" hits="1"/> - <line number="21" hits="1"/> - <line number="22" hits="1"/> - <line number="23" hits="1"/> - <line number="25" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="28" hits="1"/> - <line number="29" hits="1"/> - <line number="30" hits="1"/> - <line number="31" hits="1"/> - <line number="32" hits="1"/> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="5" hits="0"/> + <line number="8" hits="0"/> + <line number="9" hits="0"/> + <line number="11" hits="0"/> + <line number="16" hits="0"/> + <line number="17" hits="0"/> + <line number="18" hits="0"/> + <line number="19" hits="0"/> + <line number="20" hits="0"/> + <line number="21" hits="0"/> + <line number="22" hits="0"/> + <line number="23" hits="0"/> + <line number="25" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="28" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="31" hits="0"/> + <line number="32" hits="0"/> </lines> </class> - <class name="openai.py" filename="encoders/openai.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="openai.py" filename="encoders/openai.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="2" hits="1"/> - <line number="4" hits="1"/> - <line number="5" hits="1"/> - <line number="6" hits="1"/> - <line number="8" hits="1"/> - <line number="9" hits="1"/> - <line number="12" hits="1"/> - <line number="13" hits="1"/> - <line number="15" hits="1"/> - <line number="20" hits="1"/> - <line number="21" hits="1"/> - <line number="22" hits="1"/> - <line number="23" hits="1"/> - <line number="24" hits="1"/> - <line number="25" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="29" hits="1"/> - <line number="30" hits="1"/> - <line number="31" hits="1"/> - <line number="32" hits="1"/> - <line number="33" hits="1"/> - <line number="36" hits="1"/> - <line number="37" hits="1"/> - <line number="38" hits="1"/> - <line number="39" hits="1"/> - <line number="40" hits="1"/> - <line number="41" hits="1"/> - <line number="42" hits="1"/> - <line number="43" hits="1"/> - <line number="44" hits="1"/> - <line number="45" hits="1"/> - <line number="46" hits="1"/> - <line number="47" hits="1"/> - <line number="48" hits="1"/> - <line number="50" hits="1"/> - <line number="55" hits="1"/> - <line number="57" hits="1"/> - <line number="58" hits="1"/> + <line number="1" hits="0"/> + <line number="2" hits="0"/> + <line number="4" hits="0"/> + <line number="5" hits="0"/> + <line number="6" hits="0"/> + <line number="8" hits="0"/> + <line number="9" hits="0"/> + <line number="12" hits="0"/> + <line number="13" hits="0"/> + <line number="15" hits="0"/> + <line number="20" hits="0"/> + <line number="21" hits="0"/> + <line number="22" hits="0"/> + <line number="23" hits="0"/> + <line number="24" hits="0"/> + <line number="25" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="29" hits="0"/> + <line number="30" hits="0"/> + <line number="31" hits="0"/> + <line number="32" hits="0"/> + <line number="33" hits="0"/> + <line number="36" hits="0"/> + <line number="37" hits="0"/> + <line number="38" hits="0"/> + <line number="39" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="43" hits="0"/> + <line number="44" hits="0"/> + <line number="45" hits="0"/> + <line number="46" hits="0"/> + <line number="47" hits="0"/> + <line number="48" hits="0"/> + <line number="50" hits="0"/> + <line number="55" hits="0"/> + <line number="57" hits="0"/> + <line number="58" hits="0"/> </lines> </class> </classes> </package> - <package name="utils" line-rate="1" branch-rate="0" complexity="0"> + <package name="utils" line-rate="0" branch-rate="0" complexity="0"> <classes> <class name="__init__.py" filename="utils/__init__.py" complexity="0" line-rate="1" branch-rate="0"> <methods/> <lines/> </class> - <class name="logger.py" filename="utils/logger.py" complexity="0" line-rate="1" branch-rate="0"> + <class name="logger.py" filename="utils/logger.py" complexity="0" line-rate="0" branch-rate="0"> <methods/> <lines> - <line number="1" hits="1"/> - <line number="3" hits="1"/> - <line number="6" hits="1"/> - <line number="7" hits="1"/> - <line number="8" hits="1"/> - <line number="23" hits="1"/> - <line number="24" hits="1"/> - <line number="26" hits="1"/> - <line number="27" hits="1"/> - <line number="29" hits="1"/> - <line number="35" hits="1"/> - <line number="37" hits="1"/> - <line number="40" hits="1"/> - <line number="41" hits="1"/> - <line number="42" hits="1"/> - <line number="44" hits="1"/> - <line number="46" hits="1"/> - <line number="47" hits="1"/> - <line number="49" hits="1"/> - <line number="52" hits="1"/> + <line number="1" hits="0"/> + <line number="3" hits="0"/> + <line number="6" hits="0"/> + <line number="7" hits="0"/> + <line number="8" hits="0"/> + <line number="23" hits="0"/> + <line number="24" hits="0"/> + <line number="26" hits="0"/> + <line number="27" hits="0"/> + <line number="29" hits="0"/> + <line number="35" hits="0"/> + <line number="37" hits="0"/> + <line number="40" hits="0"/> + <line number="41" hits="0"/> + <line number="42" hits="0"/> + <line number="44" hits="0"/> + <line number="46" hits="0"/> + <line number="47" hits="0"/> + <line number="49" hits="0"/> + <line number="52" hits="0"/> </lines> </class> </classes> diff --git a/docs/examples/function_calling.ipynb b/docs/examples/function_calling.ipynb index c41a8a2b535432b0b935ebe39681b6f91df2e0ca..b45e1d0dd44f87e6c6383e8d9574eb11a1e15084 100644 --- a/docs/examples/function_calling.ipynb +++ b/docs/examples/function_calling.ipynb @@ -4,13 +4,40 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define LLMs" + "### Set up functions and routes" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, + "outputs": [], + "source": [ + "def get_time(location: str) -> str:\n", + " \"\"\"Useful to get the time in a specific location\"\"\"\n", + " print(f\"Calling `get_time` function with location: {location}\")\n", + " return \"get_time\"\n", + "\n", + "\n", + "def get_news(category: str, country: str) -> str:\n", + " \"\"\"Useful to get the news in a specific country\"\"\"\n", + " print(\n", + " f\"Calling `get_news` function with category: {category} and country: {country}\"\n", + " )\n", + " return \"get_news\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now generate a dynamic routing config for each function" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, "outputs": [ { "name": "stderr", @@ -18,212 +45,190 @@ "text": [ "/Users/jakit/customers/aurelio/semantic-router/.venv/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", " from .autonotebook import tqdm as notebook_tqdm\n", - "None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.\n" + "None of PyTorch, TensorFlow >= 2.0, or Flax have been found. Models won't be available and only tokenizers, configuration and file/data utilities can be used.\n", + "\u001b[32m2023-12-19 12:30:53 INFO semantic_router.utils.logger Generating dynamic route...\u001b[0m\n", + "\u001b[32m2023-12-19 12:30:58 INFO semantic_router.utils.logger Generated route config:\n", + "{\n", + " \"name\": \"get_time\",\n", + " \"utterances\": [\n", + " \"What's the time in [location]?\",\n", + " \"Can you tell me the time in [location]?\",\n", + " \"I need to know the time in [location].\",\n", + " \"What time is it in [location]?\",\n", + " \"Can you give me the time in [location]?\"\n", + " ]\n", + "}\u001b[0m\n", + "\u001b[32m2023-12-19 12:30:58 INFO semantic_router.utils.logger Generating dynamic route...\u001b[0m\n", + "\u001b[32m2023-12-19 12:31:03 INFO semantic_router.utils.logger Generated route config:\n", + "{\n", + " \"name\": \"get_news\",\n", + " \"utterances\": [\n", + " \"Tell me the latest news from the US\",\n", + " \"What's happening in India today?\",\n", + " \"Get me the top stories from Japan\",\n", + " \"Can you give me the breaking news from Brazil?\",\n", + " \"What's the latest in Germany?\"\n", + " ]\n", + "}\u001b[0m\n" ] } ], "source": [ - "# OpenAI\n", - "import openai\n", - "from semantic_router.utils.logger import logger\n", + "from semantic_router import Route, RouteConfig\n", "\n", + "functions = [get_time, get_news]\n", + "routes = []\n", "\n", - "# Docs # https://platform.openai.com/docs/guides/function-calling\n", - "def llm_openai(prompt: str, model: str = \"gpt-4\") -> str:\n", - " try:\n", - " logger.info(f\"Calling {model} model\")\n", - " response = openai.chat.completions.create(\n", - " model=model,\n", - " messages=[\n", - " {\"role\": \"system\", \"content\": f\"{prompt}\"},\n", - " ],\n", - " )\n", - " ai_message = response.choices[0].message.content\n", - " if not ai_message:\n", - " raise Exception(\"AI message is empty\", ai_message)\n", - " logger.info(f\"AI message: {ai_message}\")\n", - " return ai_message\n", - " except Exception as e:\n", - " raise Exception(\"Failed to call OpenAI API\", e)" + "for function in functions:\n", + " route = await Route.from_dynamic_route(entity=function)\n", + " routes.append(route)\n", + "\n", + "route_config = RouteConfig(routes=routes)" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2023-12-19 12:31:03 INFO semantic_router.utils.logger Added route `get_weather`\u001b[0m\n", + "\u001b[32m2023-12-19 12:31:03 INFO semantic_router.utils.logger Removed route `get_time`\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "[{'name': 'get_news',\n", + " 'utterances': ['Tell me the latest news from the US',\n", + " \"What's happening in India today?\",\n", + " 'Get me the top stories from Japan',\n", + " 'Can you give me the breaking news from Brazil?',\n", + " \"What's the latest in Germany?\"],\n", + " 'description': None},\n", + " {'name': 'get_weather',\n", + " 'utterances': ['what is the weather in SF',\n", + " 'what is the current temperature in London?',\n", + " \"tomorrow's weather in Paris?\"],\n", + " 'description': None}]" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# Mistral\n", - "import os\n", - "import requests\n", + "# You can manually add or remove routes\n", "\n", - "# Docs https://huggingface.co/docs/transformers/main_classes/text_generation\n", - "HF_API_TOKEN = os.getenv(\"HF_API_TOKEN\")\n", + "get_weather_route = Route(\n", + " name=\"get_weather\",\n", + " utterances=[\n", + " \"what is the weather in SF\",\n", + " \"what is the current temperature in London?\",\n", + " \"tomorrow's weather in Paris?\",\n", + " ],\n", + ")\n", + "route_config.add(get_weather_route)\n", "\n", + "route_config.remove(\"get_time\")\n", "\n", - "def llm_mistral(prompt: str) -> str:\n", - " api_url = \"https://z5t4cuhg21uxfmc3.us-east-1.aws.endpoints.huggingface.cloud/\"\n", - " headers = {\n", - " \"Authorization\": f\"Bearer {HF_API_TOKEN}\",\n", - " \"Content-Type\": \"application/json\",\n", - " }\n", - "\n", - " logger.info(\"Calling Mistral model\")\n", - " response = requests.post(\n", - " api_url,\n", - " headers=headers,\n", - " json={\n", - " \"inputs\": f\"You are a helpful assistant, user query: {prompt}\",\n", - " \"parameters\": {\n", - " \"max_new_tokens\": 200,\n", - " \"temperature\": 0.01,\n", - " \"num_beams\": 5,\n", - " \"num_return_sequences\": 1,\n", - " },\n", - " },\n", - " )\n", - " if response.status_code != 200:\n", - " raise Exception(\"Failed to call HuggingFace API\", response.text)\n", - "\n", - " ai_message = response.json()[0][\"generated_text\"]\n", - " if not ai_message:\n", - " raise Exception(\"AI message is empty\", ai_message)\n", - " logger.info(f\"AI message: {ai_message}\")\n", - " return ai_message" + "route_config.to_dict()" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "Route(name='get_weather', utterances=['what is the weather in SF', 'what is the current temperature in London?', \"tomorrow's weather in Paris?\"], description=None)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Get a route by name\n", + "route_config.get(\"get_weather\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Now we need to generate config from function schema using LLM" + "Save config to a file (.json or .yaml)" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 5, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2023-12-19 12:31:50 INFO semantic_router.utils.logger Saving route config to route_config.json\u001b[0m\n" + ] + } + ], "source": [ - "import inspect\n", - "from typing import Any\n", - "\n", - "\n", - "def get_function_schema(function) -> dict[str, Any]:\n", - " schema = {\n", - " \"name\": function.__name__,\n", - " \"description\": str(inspect.getdoc(function)),\n", - " \"signature\": str(inspect.signature(function)),\n", - " \"output\": str(\n", - " inspect.signature(function).return_annotation,\n", - " ),\n", - " }\n", - " return schema" + "route_config.to_file(\"route_config.json\")" ] }, { - "cell_type": "code", - "execution_count": 4, + "cell_type": "markdown", "metadata": {}, - "outputs": [], "source": [ - "import json\n", - "\n", - "\n", - "def is_valid_config(route_config_str: str) -> bool:\n", - " try:\n", - " output_json = json.loads(route_config_str)\n", - " return all(key in output_json for key in [\"name\", \"utterances\"])\n", - " except json.JSONDecodeError:\n", - " return False" + "Load from local file" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "\u001b[32m2023-12-19 12:32:24 INFO semantic_router.utils.logger Loading route config from route_config.json\u001b[0m\n" + ] + } + ], "source": [ - "import json\n", - "from typing import Callable\n", - "\n", - "from semantic_router.utils.logger import logger\n", - "from semantic_router.layer import Route\n", - "\n", - "\n", - "def generate_route(function: Callable) -> Route:\n", - " logger.info(\"Generating config...\")\n", - "\n", - " function_schema = get_function_schema(function)\n", - "\n", - " prompt = f\"\"\"\n", - " You are tasked to generate a JSON configuration based on the provided\n", - " function schema. Please follow the template below:\n", - "\n", - " {{\n", - " \"name\": \"<function_name>\",\n", - " \"utterances\": [\n", - " \"<example_utterance_1>\",\n", - " \"<example_utterance_2>\",\n", - " \"<example_utterance_3>\",\n", - " \"<example_utterance_4>\",\n", - " \"<example_utterance_5>\"]\n", - " }}\n", - "\n", - " Only include the \"name\" and \"utterances\" keys in your answer.\n", - " The \"name\" should match the function name and the \"utterances\"\n", - " should comprise a list of 5 example phrases that could be used to invoke\n", - " the function.\n", - "\n", - " Input schema:\n", - " {function_schema}\n", - " \"\"\"\n", - "\n", - " try:\n", - " ai_message = llm_mistral(prompt)\n", - "\n", - " # Parse the response\n", - " ai_message = ai_message[ai_message.find(\"{\") :]\n", - " ai_message = (\n", - " ai_message.replace(\"'\", '\"')\n", - " .replace('\"s', \"'s\")\n", - " .strip()\n", - " .rstrip(\",\")\n", - " .replace(\"}\", \"}\")\n", - " )\n", - "\n", - " valid_config = is_valid_config(ai_message)\n", - "\n", - " if not valid_config:\n", - " logger.warning(f\"Mistral failed with error, falling back to OpenAI\")\n", - " ai_message = llm_openai(prompt)\n", - " if not is_valid_config(ai_message):\n", - " raise Exception(\"Invalid config generated\")\n", - " except Exception as e:\n", - " logger.error(f\"Fall back to OpenAI failed with error {e}\")\n", - " ai_message = llm_openai(prompt)\n", - " if not is_valid_config(ai_message):\n", - " raise Exception(\"Failed to generate config\")\n", - "\n", - " try:\n", - " route_config = json.loads(ai_message)\n", - " logger.info(f\"Generated config: {route_config}\")\n", - " return Route(**route_config)\n", - " except json.JSONDecodeError as json_error:\n", - " logger.error(f\"JSON parsing error {json_error}\")\n", - " raise Exception(f\"Failed to generate a valid Route {json_error}\")" + "route_config = RouteConfig.from_file(\"route_config.json\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Extract function parameters using `Mistral` open-source model" + "### Define routing layer" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, + "metadata": {}, + "outputs": [], + "source": [ + "from semantic_router import RouteLayer\n", + "\n", + "route_layer = RouteLayer(routes=route_config.routes)" + ] + }, + { + "cell_type": "code", + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -239,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -319,7 +324,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -373,95 +378,14 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, "outputs": [], - "source": [ - "def get_time(location: str) -> str:\n", - " \"\"\"Useful to get the time in a specific location\"\"\"\n", - " print(f\"Calling `get_time` function with location: {location}\")\n", - " return \"get_time\"\n", - "\n", - "\n", - "def get_news(category: str, country: str) -> str:\n", - " \"\"\"Useful to get the news in a specific country\"\"\"\n", - " print(\n", - " f\"Calling `get_news` function with category: {category} and country: {country}\"\n", - " )\n", - " return \"get_news\"" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2023-12-18 16:58:00 INFO semantic_router.utils.logger Generating config...\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:00 INFO semantic_router.utils.logger Calling Mistral model\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:04 INFO semantic_router.utils.logger AI message: \n", - " Example output:\n", - " {\n", - " \"name\": \"get_time\",\n", - " \"utterances\": [\n", - " \"What's the time in New York?\",\n", - " \"Tell me the time in Tokyo.\",\n", - " \"Can you give me the time in London?\",\n", - " \"What's the current time in Sydney?\",\n", - " \"Can you tell me the time in Berlin?\"\n", - " ]\n", - " }\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:04 INFO semantic_router.utils.logger Generated config: {'name': 'get_time', 'utterances': [\"What's the time in New York?\", 'Tell me the time in Tokyo.', 'Can you give me the time in London?', \"What's the current time in Sydney?\", 'Can you tell me the time in Berlin?']}\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:04 INFO semantic_router.utils.logger Generating config...\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:04 INFO semantic_router.utils.logger Calling Mistral model\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:07 INFO semantic_router.utils.logger AI message: \n", - " Example output:\n", - " {\n", - " \"name\": \"get_news\",\n", - " \"utterances\": [\n", - " \"Tell me the latest news from the US\",\n", - " \"What's happening in India today?\",\n", - " \"Get me the top stories from Japan\",\n", - " \"Can you give me the breaking news from Brazil?\",\n", - " \"What's the latest news from Germany?\"\n", - " ]\n", - " }\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:07 INFO semantic_router.utils.logger Generated config: {'name': 'get_news', 'utterances': ['Tell me the latest news from the US', \"What's happening in India today?\", 'Get me the top stories from Japan', 'Can you give me the breaking news from Brazil?', \"What's the latest news from Germany?\"]}\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Generated routes: [Route(name='get_time', utterances=[\"What's the time in New York?\", 'Tell me the time in Tokyo.', 'Can you give me the time in London?', \"What's the current time in Sydney?\", 'Can you tell me the time in Berlin?'], description=None), Route(name='get_news', utterances=['Tell me the latest news from the US', \"What's happening in India today?\", 'Get me the top stories from Japan', 'Can you give me the breaking news from Brazil?', \"What's the latest news from Germany?\"], description=None)]\n" - ] - } - ], - "source": [ - "from semantic_router.layer import RouteLayer\n", - "\n", - "# Registering functions to the router\n", - "def from_functions(functions: list[Callable]) -> RouteLayer:\n", - " routes = []\n", - " for function in functions:\n", - " route = generate_route(function)\n", - " routes.append(route)\n", - "\n", - " print(f\"Generated routes: {routes}\")\n", - " return RouteLayer(routes=routes)\n", - "\n", - "router = from_functions([get_time, get_news])\n", - "\n", - "# Saving the router configuration\n", - "router.to_json(\"router.json\")" - ] + "source": [] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -471,72 +395,9 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2023-12-18 16:58:09 INFO semantic_router.utils.logger Extracting parameters...\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:09 INFO semantic_router.utils.logger Calling Mistral model\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:10 INFO semantic_router.utils.logger AI message: \n", - " {\n", - " \"location\": \"Stockholm\"\n", - " }\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:10 INFO semantic_router.utils.logger Extracted parameters: {'location': 'Stockholm'}\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "parameters: {'location': 'Stockholm'}\n", - "Calling `get_time` function with location: Stockholm\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[32m2023-12-18 16:58:10 INFO semantic_router.utils.logger Extracting parameters...\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:10 INFO semantic_router.utils.logger Calling Mistral model\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:12 INFO semantic_router.utils.logger AI message: \n", - " {\n", - " \"category\": \"tech\",\n", - " \"country\": \"Lithuania\"\n", - " }\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:12 INFO semantic_router.utils.logger Extracted parameters: {'category': 'tech', 'country': 'Lithuania'}\u001b[0m\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "parameters: {'category': 'tech', 'country': 'Lithuania'}\n", - "Calling `get_news` function with category: tech and country: Lithuania\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "\u001b[33m2023-12-18 16:58:12 WARNING semantic_router.utils.logger No function found\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:12 INFO semantic_router.utils.logger Calling Mistral model\u001b[0m\n", - "\u001b[32m2023-12-18 16:58:13 INFO semantic_router.utils.logger AI message: How can I help you today?\u001b[0m\n" - ] - }, - { - "data": { - "text/plain": [ - "' How can I help you today?'" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "tools = [get_time, get_news]\n", "\n", @@ -569,7 +430,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.11.3" } }, "nbformat": 4, diff --git a/docs/examples/route_config.json b/docs/examples/route_config.json new file mode 100644 index 0000000000000000000000000000000000000000..d43df43fe78fd98b1b4d65f6c0d1d78ac7b72816 --- /dev/null +++ b/docs/examples/route_config.json @@ -0,0 +1 @@ +[{"name": "get_news", "utterances": ["Tell me the latest news from the US", "What's happening in India today?", "Get me the top stories from Japan", "Can you give me the breaking news from Brazil?", "What's the latest in Germany?"], "description": null}, {"name": "get_weather", "utterances": ["what is the weather in SF", "what is the current temperature in London?", "tomorrow's weather in Paris?"], "description": null}] diff --git a/docs/examples/router.json b/docs/examples/router.json deleted file mode 100644 index d82eaf6b71a43fe95b86bae160c40872febb0afd..0000000000000000000000000000000000000000 --- a/docs/examples/router.json +++ /dev/null @@ -1,24 +0,0 @@ -[ - { - "name": "get_time", - "utterances": [ - "What's the time in New York?", - "Tell me the time in Tokyo.", - "Can you give me the time in London?", - "What's the current time in Sydney?", - "Can you tell me the time in Berlin?" - ], - "description": null - }, - { - "name": "get_news", - "utterances": [ - "Tell me the latest news from the US", - "What's happening in India today?", - "Get me the top stories from Japan", - "Can you give me the breaking news from Brazil?", - "What's the latest news from Germany?" - ], - "description": null - } -] diff --git a/semantic_router/__init__.py b/semantic_router/__init__.py index 0c445bea3ff4efd8f3aa8950e2c772277d93b20c..2659bfe3bf4cebe2b022c01ec7139658aeb43eb1 100644 --- a/semantic_router/__init__.py +++ b/semantic_router/__init__.py @@ -1,4 +1,5 @@ from .hybrid_layer import HybridRouteLayer from .layer import RouteLayer +from .route import Route, RouteConfig -__all__ = ["RouteLayer", "HybridRouteLayer"] +__all__ = ["RouteLayer", "HybridRouteLayer", "Route", "RouteConfig"] diff --git a/semantic_router/hybrid_layer.py b/semantic_router/hybrid_layer.py index dec6336e917d1a21ead95720d50f5f2e582aaa81..a257e8018fa71dc5c285c564657b91f42aba0de8 100644 --- a/semantic_router/hybrid_layer.py +++ b/semantic_router/hybrid_layer.py @@ -2,13 +2,13 @@ import numpy as np from numpy.linalg import norm from tqdm.auto import tqdm +from semantic_router import Route from semantic_router.encoders import ( BaseEncoder, BM25Encoder, CohereEncoder, OpenAIEncoder, ) -from semantic_router.schema import Route from semantic_router.utils.logger import logger diff --git a/semantic_router/layer.py b/semantic_router/layer.py index ad9f73febf0df63b13f719a190bb7a5b4e64f0e7..72de990081ab09e346647b03d3dd3b9b5379ba94 100644 --- a/semantic_router/layer.py +++ b/semantic_router/layer.py @@ -3,13 +3,13 @@ import json import numpy as np import yaml +from semantic_router import Route from semantic_router.encoders import ( BaseEncoder, CohereEncoder, OpenAIEncoder, ) from semantic_router.linear import similarity_matrix, top_scores -from semantic_router.schema import Route from semantic_router.utils.logger import logger diff --git a/semantic_router/route.py b/semantic_router/route.py new file mode 100644 index 0000000000000000000000000000000000000000..b1fb1e47655d929644d6d136c4e8f4678de7ff5b --- /dev/null +++ b/semantic_router/route.py @@ -0,0 +1,232 @@ +import inspect +import json +import os +import re +from typing import Any, Callable, Union + +import openai +import yaml +from pydantic import BaseModel + +from semantic_router.utils.logger import logger + + +def is_valid(route_config: str) -> bool: + try: + output_json = json.loads(route_config) + required_keys = ["name", "utterances"] + + if isinstance(output_json, list): + for item in output_json: + missing_keys = [key for key in required_keys if key not in item] + if missing_keys: + logger.warning( + f"Missing keys in route config: {', '.join(missing_keys)}" + ) + return False + return True + else: + missing_keys = [key for key in required_keys if key not in output_json] + if missing_keys: + logger.warning( + f"Missing keys in route config: {', '.join(missing_keys)}" + ) + return False + else: + return True + except json.JSONDecodeError as e: + logger.error(e) + return False + + +class Route(BaseModel): + name: str + utterances: list[str] + description: str | None = None + + def to_dict(self): + return self.dict() + + def to_yaml(self): + return yaml.dump(self.dict()) + + @classmethod + def from_dict(cls, data: dict): + return cls(**data) + + @classmethod + async def from_dynamic_route(cls, entity: Union[BaseModel, Callable]): + """ + Generate a dynamic Route object from a function or Pydantic model using LLM + """ + schema = cls._get_schema(item=entity) + dynamic_route = await cls._agenerate_dynamic_route(function_schema=schema) + return dynamic_route + + @classmethod + def _get_schema(cls, item: Union[BaseModel, Callable]) -> dict[str, Any]: + if isinstance(item, BaseModel): + signature_parts = [] + for field_name, field_model in item.__annotations__.items(): + field_info = item.__fields__[field_name] + default_value = field_info.default + + if default_value: + default_repr = repr(default_value) + signature_part = ( + f"{field_name}: {field_model.__name__} = {default_repr}" + ) + else: + signature_part = f"{field_name}: {field_model.__name__}" + + signature_parts.append(signature_part) + signature = f"({', '.join(signature_parts)}) -> str" + schema = { + "name": item.__class__.__name__, + "description": item.__doc__, + "signature": signature, + } + else: + schema = { + "name": item.__name__, + "description": str(inspect.getdoc(item)), + "signature": str(inspect.signature(item)), + "output": str(inspect.signature(item).return_annotation), + } + return schema + + @classmethod + def _parse_route_config(cls, config: str) -> str: + # Regular expression to match content inside <config></config> + config_pattern = r"<config>(.*?)</config>" + match = re.search(config_pattern, config, re.DOTALL) + + if match: + config_content = match.group(1).strip() # Get the matched content + return config_content + else: + raise ValueError("No <config></config> tags found in the output.") + + @classmethod + async def _agenerate_dynamic_route(cls, function_schema: dict[str, Any]): + logger.info("Generating dynamic route...") + + prompt = f""" + You are tasked to generate a JSON configuration based on the provided + function schema. Please follow the template below, no other tokens allowed: + + <config> + {{ + "name": "<function_name>", + "utterances": [ + "<example_utterance_1>", + "<example_utterance_2>", + "<example_utterance_3>", + "<example_utterance_4>", + "<example_utterance_5>"] + }} + </config> + + Only include the "name" and "utterances" keys in your answer. + The "name" should match the function name and the "utterances" + should comprise a list of 5 example phrases that could be used to invoke + the function. + + Input schema: + {function_schema} + """ + + client = openai.AsyncOpenAI( + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENROUTER_API_KEY"), + ) + + completion = await client.chat.completions.create( + model="mistralai/mistral-7b-instruct", + messages=[ + { + "role": "user", + "content": prompt, + }, + ], + temperature=0.01, + max_tokens=200, + ) + + output = completion.choices[0].message.content + if not output: + raise Exception("No output generated") + route_config = cls._parse_route_config(config=output) + + logger.info(f"Generated route config:\n{route_config}") + + if is_valid(route_config): + return Route.from_dict(json.loads(route_config)) + raise Exception("No config generated") + + +class RouteConfig: + """ + Generates a RouteConfig object from a list of Route objects + """ + + routes: list[Route] = [] + + def __init__(self, routes: list[Route] = []): + self.routes = routes + + @classmethod + def from_file(cls, path: str): + """Load the routes from a file in JSON or YAML format""" + logger.info(f"Loading route config from {path}") + _, ext = os.path.splitext(path) + with open(path, "r") as f: + if ext == ".json": + routes = json.load(f) + elif ext in [".yaml", ".yml"]: + routes = yaml.safe_load(f) + else: + raise ValueError( + "Unsupported file type. Only .json and .yaml are supported" + ) + + route_config_str = json.dumps(routes) + if is_valid(route_config_str): + routes = [Route.from_dict(route) for route in routes] + return cls(routes=routes) + else: + raise Exception("Invalid config JSON or YAML") + + def to_dict(self): + return [route.to_dict() for route in self.routes] + + def to_file(self, path: str): + """Save the routes to a file in JSON or YAML format""" + logger.info(f"Saving route config to {path}") + _, ext = os.path.splitext(path) + with open(path, "w") as f: + if ext == ".json": + json.dump(self.to_dict(), f) + elif ext in [".yaml", ".yml"]: + yaml.safe_dump(self.to_dict(), f) + else: + raise ValueError( + "Unsupported file type. Only .json and .yaml are supported" + ) + + def add(self, route: Route): + self.routes.append(route) + logger.info(f"Added route `{route.name}`") + + def get(self, name: str): + for route in self.routes: + if route.name == name: + return route + raise Exception(f"Route `{name}` not found") + + def remove(self, name: str): + if name not in [route.name for route in self.routes]: + logger.error(f"Route `{name}` not found") + else: + self.routes = [route for route in self.routes if route.name != name] + logger.info(f"Removed route `{name}`") diff --git a/semantic_router/schema.py b/semantic_router/schema.py index 1bb2ad006c37a5f8bc4a21a2a507a9d3179effac..4646a637dbffd4ed7ad1b8e2d4dd23cef6df22de 100644 --- a/semantic_router/schema.py +++ b/semantic_router/schema.py @@ -1,9 +1,8 @@ from enum import Enum -import yaml -from pydantic import BaseModel from pydantic.dataclasses import dataclass +from semantic_router import Route from semantic_router.encoders import ( BaseEncoder, CohereEncoder, @@ -11,22 +10,6 @@ from semantic_router.encoders import ( ) -class Route(BaseModel): - name: str - utterances: list[str] - description: str | None = None - - def to_dict(self): - return self.dict() - - def to_yaml(self): - return yaml.dump(self.dict()) - - @classmethod - def from_dict(cls, data: dict): - return cls(**data) - - class EncoderType(Enum): HUGGINGFACE = "huggingface" OPENAI = "openai"