From 5c147c8996c4002611f0cf3f3766a941e46eca58 Mon Sep 17 00:00:00 2001
From: Simonas <20096648+simjak@users.noreply.github.com>
Date: Tue, 19 Dec 2023 12:38:19 +0200
Subject: [PATCH] dynamic route config generation + helper methods

---
 coverage.xml                         | 856 ++++++++++++++++-----------
 docs/examples/function_calling.ipynb | 483 ++++++---------
 docs/examples/route_config.json      |   1 +
 docs/examples/router.json            |  24 -
 semantic_router/__init__.py          |   3 +-
 semantic_router/hybrid_layer.py      |   2 +-
 semantic_router/layer.py             |   2 +-
 semantic_router/route.py             | 232 ++++++++
 semantic_router/schema.py            |  19 +-
 9 files changed, 911 insertions(+), 711 deletions(-)
 create mode 100644 docs/examples/route_config.json
 delete mode 100644 docs/examples/router.json
 create mode 100644 semantic_router/route.py

diff --git a/coverage.xml b/coverage.xml
index 9af9ebee..001746f7 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 c41a8a2b..b45e1d0d 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 00000000..d43df43f
--- /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 d82eaf6b..00000000
--- 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 0c445bea..2659bfe3 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 dec6336e..a257e801 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 ad9f73fe..72de9900 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 00000000..b1fb1e47
--- /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 1bb2ad00..4646a637 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"
-- 
GitLab