From 45b2079d5772434e9f453f41db291cff02b6e75e Mon Sep 17 00:00:00 2001 From: James Briggs <35938317+jamescalam@users.noreply.github.com> Date: Sun, 7 Jan 2024 15:24:33 +0100 Subject: [PATCH] added LLM to llm classes, update version and docs --- README.md | 22 ++++---- docs/02-dynamic-routes.ipynb | 80 ++++++++++++++++-------------- pyproject.toml | 2 +- semantic_router/layer.py | 16 +++--- semantic_router/llms/__init__.py | 8 +-- semantic_router/llms/cohere.py | 2 +- semantic_router/llms/openai.py | 2 +- semantic_router/llms/openrouter.py | 2 +- semantic_router/route.py | 7 ++- 9 files changed, 73 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index da3fe685..fe5db343 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ <img alt="Github License" src="https://img.shields.io/badge/License-MIT-yellow.svg" /> </p> -Semantic Router is a superfast decision layer for your LLMs and agents. Rather than waiting for slow LLM generations to make tool-use decisions, we use the magic of semantic vector space to make those decisions — _routing_ our requests using _semantic_ meaning. +Semantic Router is a superfast decision-making layer for your LLMs and agents. Rather than waiting for slow LLM generations to make tool-use decisions, we use the magic of semantic vector space to make those decisions — _routing_ our requests using _semantic_ meaning. ## Quickstart @@ -22,7 +22,9 @@ To get started with _semantic-router_ we install it like so: pip install -qU semantic-router ``` -We begin by defining a set of `Decision` objects. These are the decision paths that the semantic router can decide to use, let's try two simple decisions for now — one for talk on _politics_ and another for _chitchat_: +â—ï¸ _If wanting to use local embeddings you can use `FastEmbedEncoder` (`pip install -qU semantic-router[fastembed]`). To use the `HybridRouteLayer` you must `pip install -qU semantic-router[hybrid]`._ + +We begin by defining a set of `Route` objects. These are the decision paths that the semantic router can decide to use, let's try two simple routes for now — one for talk on _politics_ and another for _chitchat_: ```python from semantic_router import Route @@ -56,7 +58,7 @@ chitchat = Route( routes = [politics, chitchat] ``` -We have our decisions ready, now we initialize an embedding / encoder model. We currently support a `CohereEncoder` and `OpenAIEncoder` — more encoders will be added soon. To initialize them we do: +We have our routes ready, now we initialize an embedding / encoder model. We currently support a `CohereEncoder` and `OpenAIEncoder` — more encoders will be added soon. To initialize them we do: ```python import os @@ -71,18 +73,18 @@ os.environ["OPENAI_API_KEY"] = "<YOUR_API_KEY>" encoder = OpenAIEncoder() ``` -With our `decisions` and `encoder` defined we now create a `DecisionLayer`. The decision layer handles our semantic decision making. +With our `routes` and `encoder` defined we now create a `RouteLayer`. The route layer handles our semantic decision making. ```python from semantic_router.layer import RouteLayer -dl = RouteLayer(encoder=encoder, routes=routes) +rl = RouteLayer(encoder=encoder, routes=routes) ``` -We can now use our decision layer to make super fast decisions based on user queries. Let's try with two queries that should trigger our decisions: +We can now use our route layer to make super fast decisions based on user queries. Let's try with two queries that should trigger our route decisions: ```python -dl("don't you love politics?").name +rl("don't you love politics?").name ``` ``` @@ -92,7 +94,7 @@ dl("don't you love politics?").name Correct decision, let's try another: ```python -dl("how's the weather today?").name +rl("how's the weather today?").name ``` ``` @@ -102,14 +104,14 @@ dl("how's the weather today?").name We get both decisions correct! Now lets try sending an unrelated query: ```python -dl("I'm interested in learning about llama 2").name +rl("I'm interested in learning about llama 2").name ``` ``` [Out]: ``` -In this case, no decision could be made as we had no matches — so our decision layer returned `None`! +In this case, no decision could be made as we had no matches — so our route layer returned `None`! ## 📚 [Resources](https://github.com/aurelio-labs/semantic-router/tree/main/docs) diff --git a/docs/02-dynamic-routes.ipynb b/docs/02-dynamic-routes.ipynb index d8078cb2..c695838e 100644 --- a/docs/02-dynamic-routes.ipynb +++ b/docs/02-dynamic-routes.ipynb @@ -36,7 +36,7 @@ "metadata": {}, "outputs": [], "source": [ - "!pip install -qU semantic-router==0.0.14" + "!pip install -qU semantic-router==0.0.15" ] }, { @@ -64,17 +64,7 @@ "cell_type": "code", "execution_count": 1, "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/jamesbriggs/opt/anaconda3/envs/decision-layer/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" - ] - } - ], + "outputs": [], "source": [ "from semantic_router import Route\n", "\n", @@ -102,16 +92,23 @@ "routes = [politics, chitchat]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We initialize our `RouteLayer` with our `encoder` and `routes`. We can use popular encoder APIs like `CohereEncoder` and `OpenAIEncoder`, or local alternatives like `FastEmbedEncoder`." + ] + }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 2, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2023-12-28 19:19:39 INFO semantic_router.utils.logger Initializing RouteLayer\u001b[0m\n" + "\u001b[32m2024-01-07 15:23:12 INFO semantic_router.utils.logger Initializing RouteLayer\u001b[0m\n" ] } ], @@ -119,13 +116,21 @@ "import os\n", "from getpass import getpass\n", "from semantic_router import RouteLayer\n", + "from semantic_router.encoders import CohereEncoder, OpenAIEncoder\n", "\n", "# dashboard.cohere.ai\n", - "os.environ[\"COHERE_API_KEY\"] = os.getenv(\"COHERE_API_KEY\") or getpass(\n", - " \"Enter Cohere API Key: \"\n", + "# os.environ[\"COHERE_API_KEY\"] = os.getenv(\"COHERE_API_KEY\") or getpass(\n", + "# \"Enter Cohere API Key: \"\n", + "# )\n", + "# platform.openai.com\n", + "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\") or getpass(\n", + " \"Enter OpenAI API Key: \"\n", ")\n", "\n", - "rl = RouteLayer(routes=routes)" + "# encoder = CohereEncoder()\n", + "encoder = OpenAIEncoder()\n", + "\n", + "rl = RouteLayer(encoder=encoder, routes=routes)" ] }, { @@ -137,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -146,7 +151,7 @@ "RouteChoice(name='chitchat', function_call=None)" ] }, - "execution_count": 5, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -171,7 +176,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -193,16 +198,16 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "'13:19'" + "'09:23'" ] }, - "execution_count": 7, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } @@ -220,7 +225,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 6, "metadata": {}, "outputs": [ { @@ -232,7 +237,7 @@ " 'output': \"<class 'str'>\"}" ] }, - "execution_count": 8, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" } @@ -253,7 +258,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -277,16 +282,14 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "metadata": {}, "outputs": [ { - "name": "stdout", + "name": "stderr", "output_type": "stream", "text": [ - "Adding route `get_time`\n", - "Adding route to categories\n", - "Adding route to index\n" + "\u001b[32m2024-01-07 15:23:16 INFO semantic_router.utils.logger Adding `get_time` route\u001b[0m\n" ] } ], @@ -303,31 +306,32 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ - "\u001b[32m2023-12-28 19:21:58 INFO semantic_router.utils.logger Extracting function input...\u001b[0m\n" + "\u001b[33m2024-01-07 15:23:17 WARNING semantic_router.utils.logger No LLM provided for dynamic route, will use OpenAI LLM default. Ensure API key is set in OPENAI_API_KEY environment variable.\u001b[0m\n", + "\u001b[32m2024-01-07 15:23:17 INFO semantic_router.utils.logger Extracting function input...\u001b[0m\n" ] }, { "data": { "text/plain": [ - "RouteChoice(name='get_time', function_call={'timezone': 'America/New_York'})" + "RouteChoice(name='get_time', function_call={'timezone': 'new york city'})" ] }, - "execution_count": 11, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ - "# https://openrouter.ai/keys\n", - "os.environ[\"OPENROUTER_API_KEY\"] = os.getenv(\"OPENROUTER_API_KEY\") or getpass(\n", - " \"Enter OpenRouter API Key: \"\n", + "# https://platform.openai.com/\n", + "os.environ[\"OPENAI_API_KEY\"] = os.getenv(\"OPENAI_API_KEY\") or getpass(\n", + " \"Enter OpenAI API Key: \"\n", ")\n", "\n", "rl(\"what is the time in new york city?\")" diff --git a/pyproject.toml b/pyproject.toml index d3561c64..b24ed4f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "semantic-router" -version = "0.0.14" +version = "0.0.15" description = "Super fast semantic router for AI decision making" authors = [ "James Briggs <james@aurelio.ai>", diff --git a/semantic_router/layer.py b/semantic_router/layer.py index eed04e36..b3173728 100644 --- a/semantic_router/layer.py +++ b/semantic_router/layer.py @@ -10,7 +10,7 @@ from semantic_router.encoders import ( OpenAIEncoder, FastEmbedEncoder, ) -from semantic_router.llms import BaseLLM +from semantic_router.llms import BaseLLM, OpenAILLM from semantic_router.linear import similarity_matrix, top_scores from semantic_router.route import Route from semantic_router.schema import Encoder, EncoderType, RouteChoice @@ -193,9 +193,13 @@ class RouteLayer: route = [route for route in self.routes if route.name == top_class][0] if route.function_schema and not isinstance(route.llm, BaseLLM): if not self.llm: - raise ValueError( - "LLM is required for dynamic routes. Please ensure the 'llm' is set." + logger.warning( + "No LLM provided for dynamic route, will use OpenAI LLM " + "default. Ensure API key is set in OPENAI_API_KEY environment " + "variable." ) + self.llm = OpenAILLM() + route.llm = self.llm else: route.llm = self.llm return route(text) @@ -228,24 +232,20 @@ class RouteLayer: return cls(encoder=encoder, routes=config.routes) def add(self, route: Route): - print(f"Adding route `{route.name}`") + logger.info(f"Adding `{route.name}` route") # create embeddings embeds = self.encoder(route.utterances) # create route array if self.categories is None: - print("Initializing categories array") self.categories = np.array([route.name] * len(embeds)) else: - print("Adding route to categories") str_arr = np.array([route.name] * len(embeds)) self.categories = np.concatenate([self.categories, str_arr]) # create utterance array (the index) if self.index is None: - print("Initializing index array") self.index = np.array(embeds) else: - print("Adding route to index") embed_arr = np.array(embeds) self.index = np.concatenate([self.index, embed_arr]) # add route to routes list diff --git a/semantic_router/llms/__init__.py b/semantic_router/llms/__init__.py index 446f7c42..c7d6962b 100644 --- a/semantic_router/llms/__init__.py +++ b/semantic_router/llms/__init__.py @@ -1,7 +1,7 @@ from semantic_router.llms.base import BaseLLM -from semantic_router.llms.openai import OpenAI -from semantic_router.llms.openrouter import OpenRouter -from semantic_router.llms.cohere import Cohere +from semantic_router.llms.openai import OpenAILLM +from semantic_router.llms.openrouter import OpenRouterLLM +from semantic_router.llms.cohere import CohereLLM -__all__ = ["BaseLLM", "OpenAI", "OpenRouter", "Cohere"] +__all__ = ["BaseLLM", "OpenAILLM", "OpenRouterLLM", "CohereLLM"] diff --git a/semantic_router/llms/cohere.py b/semantic_router/llms/cohere.py index 80512d5c..be99bbc4 100644 --- a/semantic_router/llms/cohere.py +++ b/semantic_router/llms/cohere.py @@ -4,7 +4,7 @@ from semantic_router.llms import BaseLLM from semantic_router.schema import Message -class Cohere(BaseLLM): +class CohereLLM(BaseLLM): client: cohere.Client | None = None def __init__( diff --git a/semantic_router/llms/openai.py b/semantic_router/llms/openai.py index 18b6e706..5ee56398 100644 --- a/semantic_router/llms/openai.py +++ b/semantic_router/llms/openai.py @@ -5,7 +5,7 @@ from semantic_router.llms import BaseLLM from semantic_router.schema import Message -class OpenAI(BaseLLM): +class OpenAILLM(BaseLLM): client: openai.OpenAI | None temperature: float | None max_tokens: int | None diff --git a/semantic_router/llms/openrouter.py b/semantic_router/llms/openrouter.py index 3b7a9b49..5c3b317f 100644 --- a/semantic_router/llms/openrouter.py +++ b/semantic_router/llms/openrouter.py @@ -5,7 +5,7 @@ from semantic_router.llms import BaseLLM from semantic_router.schema import Message -class OpenRouter(BaseLLM): +class OpenRouterLLM(BaseLLM): client: openai.OpenAI | None base_url: str | None temperature: float | None diff --git a/semantic_router/route.py b/semantic_router/route.py index 454cfe79..0d8269f0 100644 --- a/semantic_router/route.py +++ b/semantic_router/route.py @@ -5,12 +5,10 @@ from typing import Any, Callable, Union from pydantic import BaseModel from semantic_router.llms import BaseLLM -from semantic_router.schema import RouteChoice +from semantic_router.schema import Message, RouteChoice from semantic_router.utils import function_call from semantic_router.utils.logger import logger -from semantic_router.schema import Message - def is_valid(route_config: str) -> bool: try: @@ -51,7 +49,8 @@ class Route(BaseModel): if self.function_schema: if not self.llm: raise ValueError( - "LLM is required for dynamic routes. Please ensure the 'llm' is set." + "LLM is required for dynamic routes. Please ensure the `llm` " + "attribute is set." ) # if a function schema is provided we generate the inputs extracted_inputs = function_call.extract_function_inputs( -- GitLab