diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..f94862c0d44dd19a6c431c3ea5f15febb5e0179d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing to the Semantic Router + +The Aurelio Team welcome and encourage any contributions to the Semantic Router, big or small. Please feel free to contribute to new features, bug fixes or documentation. We're always eager to hear your suggestions. + +Please follow these guidelines when making a contribution: +1. **Check for Existing Issues:** Before making any changes, [check here for related issues](https://github.com/aurelio-labs/semantic-router/issues). +2. **Run Your Changes by Us!** If no related issue exists yet, please create one and suggest your changes. Checking in with the team first will allow us to determine if the changes are in scope. +3. **Set Up Development Environment** If the changes are agreed, then you can go ahead and set up a development environment (see [Setting Up Your Development Environment](#setting-up-your-development-environment) below). +4. **Create an Early Draft Pull Request** Once you have commits ready to be shared, initiate a draft Pull Request with an initial version of your implementation and request feedback. It's advisable not to wait until the feature is fully completed. +5. **Ensure that All Pull Request Checks Pass** There are Pull Request checks that need to be satifisfied before the changes can be merged. These appear towards the bottom of the Pull Request webpage on GitHub, and include: + - Ensure that the Pull Request title is prepended with a [valid type](https://flank.github.io/flank/pr_titles/). E.g. `feat: My New Feature`. + - Run linting (and fix any issues that are flagged) by: + - Navigating to /semantic-router. + - Running `make lint` to fix linting issues. + - Running `black .` to fix `black` linting issues. + - Running `ruff . --fix` to fix `ruff` linting issues (where possible, others may need manual changes). + - Running `mypy .` and then fixing any of the issues that are raised. + - Confirming the linters pass using `make lint` again. + - Ensure that, for any new code, new [PyTests are written](https://github.com/aurelio-labs/semantic-router/tree/main/tests/unit). If any code is removed, then ensure that corresponding PyTests are also removed. Finally, ensure that all remaining PyTests pass using `pytest ./tests` (to avoid integration tests you can run `pytest ./tests/unit`. + - Codecov checks will inform you if any code is not covered by PyTests upon creating the PR. You should aim to cover new code with PyTests. + +> **Feedback and Discussion:** +While we encourage you to initiate a draft Pull Request early to get feedback on your implementation, we also highly value discussions and questions. If you're unsure about any aspect of your contribution or need clarification on the project's direction, please don't hesitate to use the [Issues section](https://github.com/aurelio-labs/semantic-router/issues) of our repository. Engaging in discussions or asking questions before starting your work can help ensure that your efforts align well with the project's goals and existing work. + +# Setting Up Your Development Environment + +1. Fork on GitHub: + Go to the [repository's page](https://github.com/aurelio-labs/semantic-router) on GitHub: + Click the "Fork" button in the top-right corner of the page. +2. Clone Your Fork: + After forking, you'll be taken to your new fork of the repository on GitHub. Copy the URL of your fork from the address bar or by clicking the "Code" button and copying the URL under "Clone with HTTPS" or "Clone with SSH". + Open your terminal or command prompt. + Use the git clone command followed by the URL you copied to clone the repository to your local machine. Replace `https://github.com/<your-gh-username>/<semantic-router>.git` with the URL of your fork: + ``` + git clone https://github.com/<your-gh-username>/<semantic-router>.git + ``` +3. Ensure you have poetry installed: `pip install poetry`. +4. Then navigate to the cloned folder, create a virtualenv, and install via poetry (which defaults to an editable installation): + ``` + # Move into the cloned folder + cd semantic-router/ + + # Create a virtual environment + python3 -m venv venv + + # Activate the environment + source venv/bin/activate + + # Install via poetry + poetry install + ``` + diff --git a/docs/05-local-execution.ipynb b/docs/05-local-execution.ipynb index c0c3c52ee544701b03553d32e59dd7db70411022..2ceaf072e4596b72ebf60446a7d053c8bb640444 100644 --- a/docs/05-local-execution.ipynb +++ b/docs/05-local-execution.ipynb @@ -350,8 +350,8 @@ " model_path=\"./mistral-7b-instruct-v0.2.Q4_0.gguf\",\n", " n_gpu_layers=-1 if enable_gpu else 0,\n", " n_ctx=2048,\n", - " verbose=False,\n", ")\n", + "_llm.verbose=False\n", "llm = LlamaCppLLM(name=\"Mistral-7B-v0.2-Instruct\", llm=_llm, max_tokens=None)\n", "\n", "rl = RouteLayer(encoder=encoder, routes=routes, llm=llm)" diff --git a/semantic_router/encoders/openai.py b/semantic_router/encoders/openai.py index 7957f913b342b8fc8a13284783f38d3ca62ce176..14fda1c99adee1a60a9f03621ade6f93151410db 100644 --- a/semantic_router/encoders/openai.py +++ b/semantic_router/encoders/openai.py @@ -20,6 +20,7 @@ class OpenAIEncoder(BaseEncoder): self, name: Optional[str] = None, openai_api_key: Optional[str] = None, + openai_org_id: Optional[str] = None, score_threshold: float = 0.82, dimensions: Union[int, NotGiven] = NotGiven(), ): @@ -27,10 +28,11 @@ class OpenAIEncoder(BaseEncoder): name = os.getenv("OPENAI_MODEL_NAME", "text-embedding-ada-002") super().__init__(name=name, score_threshold=score_threshold) api_key = openai_api_key or os.getenv("OPENAI_API_KEY") + openai_org_id = openai_org_id or os.getenv("OPENAI_ORG_ID") if api_key is None: raise ValueError("OpenAI API key cannot be 'None'.") try: - self.client = openai.Client(api_key=api_key) + self.client = openai.Client(api_key=api_key, organization=openai_org_id) except Exception as e: raise ValueError( f"OpenAI API client failed to initialize. Error: {e}" diff --git a/semantic_router/layer.py b/semantic_router/layer.py index 640a68a7034db84131f1768dde02204fedf38277..186b32d0e8b829aced4fa1e2caeb7d75bf048b14 100644 --- a/semantic_router/layer.py +++ b/semantic_router/layer.py @@ -431,15 +431,19 @@ class RouteLayer: self, X: List[str], y: List[str], + batch_size: int = 500, max_iter: int = 500, ): # convert inputs into array - Xq: Any = np.array(self.encoder(X)) + Xq: List[List[float]] = [] + for i in tqdm(range(0, len(X), batch_size), desc="Generating embeddings"): + emb = np.array(self.encoder(X[i : i + batch_size])) + Xq.extend(emb) # initial eval (we will iterate from here) - best_acc = self._vec_evaluate(Xq=Xq, y=y) + best_acc = self._vec_evaluate(Xq=np.array(Xq), y=y) best_thresholds = self.get_thresholds() # begin fit - for _ in (pbar := tqdm(range(max_iter))): + for _ in (pbar := tqdm(range(max_iter), desc="Training")): pbar.set_postfix({"acc": round(best_acc, 2)}) # Find the best score threshold for each route thresholds = threshold_random_search( @@ -457,12 +461,16 @@ class RouteLayer: # update route layer to best thresholds self._update_thresholds(score_thresholds=best_thresholds) - def evaluate(self, X: List[str], y: List[str]) -> float: + def evaluate(self, X: List[str], y: List[str], batch_size: int = 500) -> float: """ Evaluate the accuracy of the route selection. """ - Xq = np.array(self.encoder(X)) - accuracy = self._vec_evaluate(Xq=Xq, y=y) + Xq: List[List[float]] = [] + for i in tqdm(range(0, len(X), batch_size), desc="Generating embeddings"): + emb = np.array(self.encoder(X[i : i + batch_size])) + Xq.extend(emb) + + accuracy = self._vec_evaluate(Xq=np.array(Xq), y=y) return accuracy def _vec_evaluate(self, Xq: Union[List[float], Any], y: List[str]) -> float: diff --git a/tests/unit/encoders/test_openai.py b/tests/unit/encoders/test_openai.py index 4679ee939f7d4b494150a8a52f4b4c33a0e6c8db..508e9e9e197a8a0b87fb179554d43c18686c1b44 100644 --- a/tests/unit/encoders/test_openai.py +++ b/tests/unit/encoders/test_openai.py @@ -14,7 +14,9 @@ def openai_encoder(mocker): class TestOpenAIEncoder: def test_openai_encoder_init_success(self, mocker): - mocker.patch("os.getenv", return_value="fake-api-key") + # -- Mock the return value of os.getenv 3 times: model name, api key and org ID + side_effect = ["fake-model-name", "fake-api-key", "fake-org-id"] + mocker.patch("os.getenv", side_effect=side_effect) encoder = OpenAIEncoder() assert encoder.client is not None diff --git a/tests/unit/test_layer.py b/tests/unit/test_layer.py index 00bad4ff5f32dda8c0ec748b2f16388ef7e3e101..3f2c413f45f279656bada2ffdc7ee47b96ef1a6c 100644 --- a/tests/unit/test_layer.py +++ b/tests/unit/test_layer.py @@ -432,13 +432,13 @@ class TestLayerFit: # unpack test data X, y = zip(*test_data) # evaluate - route_layer.evaluate(X=X, y=y) + route_layer.evaluate(X=X, y=y, batch_size=int(len(test_data) / 5)) def test_fit(self, openai_encoder, routes, test_data): route_layer = RouteLayer(encoder=openai_encoder, routes=routes) # unpack test data X, y = zip(*test_data) - route_layer.fit(X=X, y=y) + route_layer.fit(X=X, y=y, batch_size=int(len(test_data) / 5)) # Add more tests for edge cases and error handling as needed.