From 93d5234e6c856ea8e27ed16ef6ca1a0f0381ca7e Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Fri, 25 Apr 2025 18:16:57 -0700 Subject: [PATCH 01/12] up --- src/bespokelabs/curator/__init__.py | 3 +- src/bespokelabs/curator/constants.py | 4 +- src/bespokelabs/curator/datasets/__init__.py | 3 + src/bespokelabs/curator/datasets/base.py | 43 ++++++ src/bespokelabs/curator/finetune/__init__.py | 3 + .../curator/finetune/base_backend.py | 35 +++++ .../curator/finetune/bespoke_backend.py | 90 +++++++++++++ src/bespokelabs/curator/finetune/finetune.py | 30 +++++ .../curator/finetune/openai_backend.py | 33 +++++ src/bespokelabs/curator/finetune/types.py | 23 ++++ src/bespokelabs/curator/utils.py | 1 + test_kluster.ipynb | 124 ++++++++++++++++++ 12 files changed, 389 insertions(+), 3 deletions(-) create mode 100644 src/bespokelabs/curator/datasets/__init__.py create mode 100644 src/bespokelabs/curator/datasets/base.py create mode 100644 src/bespokelabs/curator/finetune/__init__.py create mode 100644 src/bespokelabs/curator/finetune/base_backend.py create mode 100644 src/bespokelabs/curator/finetune/bespoke_backend.py create mode 100644 src/bespokelabs/curator/finetune/finetune.py create mode 100644 src/bespokelabs/curator/finetune/openai_backend.py create mode 100644 src/bespokelabs/curator/finetune/types.py create mode 100644 test_kluster.ipynb diff --git a/src/bespokelabs/curator/__init__.py b/src/bespokelabs/curator/__init__.py index ad303f955..c3289404e 100644 --- a/src/bespokelabs/curator/__init__.py +++ b/src/bespokelabs/curator/__init__.py @@ -3,7 +3,8 @@ from .code_executor.code_executor import CodeExecutor from .llm.llm import LLM from .types import prompt as types +from .finetune.finetune import Finetune -__all__ = ["LLM", "CodeExecutor", "types"] +__all__ = ["LLM", "CodeExecutor", "types", "Finetune"] from .log import _CONSOLE # noqa: F401 diff --git a/src/bespokelabs/curator/constants.py b/src/bespokelabs/curator/constants.py index 565a95f66..bfbc46500 100644 --- a/src/bespokelabs/curator/constants.py +++ b/src/bespokelabs/curator/constants.py @@ -3,8 +3,8 @@ BATCH_REQUEST_ID_TAG = "custom_id" _CURATOR_DEFAULT_CACHE_DIR = "~/.cache/curator" _DEFAULT_CACHE_DIR = "~/.cache" -BASE_CLIENT_URL = "https://api.bespokelabs.ai/v0/viewer" -PUBLIC_CURATOR_VIEWER_HOME_URL = "https://curator.bespokelabs.ai" +BASE_CLIENT_URL = "https://api-dev.bespokelabs.ai/v0/viewer" +PUBLIC_CURATOR_VIEWER_HOME_URL = "https://curator-dev.bespokelabs.ai" PUBLIC_CURATOR_VIEWER_DATASET_URL = PUBLIC_CURATOR_VIEWER_HOME_URL + "/datasets" _INTERNAL_PROMPT_KEY = "__internal_prompt" _CACHE_MSG = ( diff --git a/src/bespokelabs/curator/datasets/__init__.py b/src/bespokelabs/curator/datasets/__init__.py new file mode 100644 index 000000000..d073ff4ad --- /dev/null +++ b/src/bespokelabs/curator/datasets/__init__.py @@ -0,0 +1,3 @@ +from .base import upload + +__all__ = ["upload"] \ No newline at end of file diff --git a/src/bespokelabs/curator/datasets/base.py b/src/bespokelabs/curator/datasets/base.py new file mode 100644 index 000000000..8fa749141 --- /dev/null +++ b/src/bespokelabs/curator/datasets/base.py @@ -0,0 +1,43 @@ +# just upload files, a thin wrapper around push_to_viewer + +from pathlib import Path +from bespokelabs.curator.utils import push_to_viewer +from datasets import load_dataset +import logging + +logger = logging.getLogger(__name__) + +def upload(path: str, name: str, split: str = "train"): + # load file into a huggingface dataset + + # it could be a huggingface dataset or a local file + # first check if it is a huggingface dataset + try: + dataset = load_dataset(path, split=split) + + except Exception as e: + + path = Path(path) + if not path.exists(): + raise FileNotFoundError(f"File {path} does not exist") + + if not (path.suffix in [".jsonl", ".json", ".csv", ".parquet"]): + raise ValueError("Only jsonl, json, csv, and parquet files are supported currently") + + try: + + if path.suffix == ".jsonl" or path.suffix == ".json": + format = "json" + elif path.suffix == ".csv": + format = "csv" + elif path.suffix == ".parquet": + format = "parquet" + + dataset = load_dataset(format, data_files=str(path), split=split) + + except Exception as e: + logger.error(f"Error loading dataset: {e}") + raise e + + link = push_to_viewer(dataset, name) + return link diff --git a/src/bespokelabs/curator/finetune/__init__.py b/src/bespokelabs/curator/finetune/__init__.py new file mode 100644 index 000000000..c16b4bfef --- /dev/null +++ b/src/bespokelabs/curator/finetune/__init__.py @@ -0,0 +1,3 @@ +from .finetune import Finetune + +__all__ = ["Finetune"] \ No newline at end of file diff --git a/src/bespokelabs/curator/finetune/base_backend.py b/src/bespokelabs/curator/finetune/base_backend.py new file mode 100644 index 000000000..2fb969ed2 --- /dev/null +++ b/src/bespokelabs/curator/finetune/base_backend.py @@ -0,0 +1,35 @@ +from abc import ABC, abstractmethod + +class BaseFinetuneBackend(ABC): + + def __init__(self, backend_params: dict): + self.backend_params = backend_params + + @abstractmethod + def create_job(self, model_name: str, dataset_id: str, method: dict): + pass + + @abstractmethod + def list_jobs(self): + pass + + @abstractmethod + def list_job_events(self, job_id: str): + pass + + # @abstractmethod + # def list_job_checkpoints(self, job_id: str): + # pass + + # @abstractmethod + # def list_job_metrics(self, job_id: str): + # pass + + + @abstractmethod + def get_job_details(self, job_id: str): + pass + + @abstractmethod + def cancel_job(self, job_id: str): + pass diff --git a/src/bespokelabs/curator/finetune/bespoke_backend.py b/src/bespokelabs/curator/finetune/bespoke_backend.py new file mode 100644 index 000000000..305993732 --- /dev/null +++ b/src/bespokelabs/curator/finetune/bespoke_backend.py @@ -0,0 +1,90 @@ +from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend +import requests +import json +import os +import logging + +logger = logging.getLogger(__name__) + +class BespokeFinetuneBackend(BaseFinetuneBackend): + + def __init__(self, backend_params: dict): + super().__init__(backend_params) + self.env = "dev" if backend_params.get("env", "prod") == "dev" else "prod" + if not backend_params.get("base_url"): + self.base_url = f"https://api.bespokelabs.com/{self.env}/v0/finetune" + else: + self.base_url = backend_params["base_url"] + "/v0/finetune" + self.api_key = os.environ.get("BESPOKE_API_KEY") + + + def create_job(self, *args, **kwargs): + url = f"{self.base_url}/jobs/create" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + if os.environ.get("HF_TOKEN") is None: + logger.warning("HF_TOKEN is not set. Please set it to use private gated models or datasets.") + + data = { + "model_name": kwargs["model_name"], + "dataset_id": kwargs["dataset_id"], + "seed": kwargs["seed"], + "suffix": kwargs["suffix"], + "method": kwargs["method"], + "job_name": kwargs["job_name"], + "secrets": { + 'HF_TOKEN': os.environ.get("HF_TOKEN", None), + } + } + + response = requests.post(url, headers=headers, json=data) + return response.json() + + + def list_jobs(self, *args, **kwargs): + + url = f"{self.base_url}/jobs" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + response = requests.get(url, headers=headers) + return response.json() + + def list_job_events(self, *args, **kwargs): + + url = f"{self.base_url}/jobs/{kwargs['job_id']}/events" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + response = requests.get(url, headers=headers) + return response.json() + + + def get_job_details(self, *args, **kwargs): + + url = f"{self.base_url}/jobs/{kwargs['job_id']}?log_type={kwargs.get('type', 'EVENTS')}" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + response = requests.get(url, headers=headers) + return response.json() + + def cancel_job(self, *args, **kwargs): + + url = f"{self.base_url}/jobs/{kwargs['job_id']}/cancel" + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.api_key}" + } + + response = requests.post(url, headers=headers) + return response.json() diff --git a/src/bespokelabs/curator/finetune/finetune.py b/src/bespokelabs/curator/finetune/finetune.py new file mode 100644 index 000000000..5760a11ed --- /dev/null +++ b/src/bespokelabs/curator/finetune/finetune.py @@ -0,0 +1,30 @@ +from abc import ABC, abstractmethod +from bespokelabs.curator.finetune.bespoke_backend import BespokeFinetuneBackend +from bespokelabs.curator.finetune.openai_backend import OpenAIFinetuneBackend + +class Finetune(ABC): + + def __init__(self, backend: str, backend_params: dict): + self.backend_params = backend_params + if backend == "bespoke": + self._backend = BespokeFinetuneBackend(backend_params) + elif backend == "openai": + self._backend = OpenAIFinetuneBackend(backend_params) + else: + raise ValueError(f"Invalid backend: {backend}") + + def create_job(self, *args, **kwargs): + self._backend.create_job(*args, **kwargs) + + def list_jobs(self, *args, **kwargs): + return self._backend.list_jobs(*args, **kwargs) + + def list_job_events(self, *args, **kwargs): + return self._backend.list_job_events(*args, **kwargs) + + def get_job_details(self, *args, **kwargs): + return self._backend.get_job_details(*args, **kwargs) + + def cancel_job(self, *args, **kwargs): + return self._backend.cancel_job(*args, **kwargs) + diff --git a/src/bespokelabs/curator/finetune/openai_backend.py b/src/bespokelabs/curator/finetune/openai_backend.py new file mode 100644 index 000000000..5a2db8563 --- /dev/null +++ b/src/bespokelabs/curator/finetune/openai_backend.py @@ -0,0 +1,33 @@ +import os +from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend +from openai import OpenAI + +class OpenAIFinetuneBackend(BaseFinetuneBackend): + + def __init__(self, backend_params: dict): + super().__init__(backend_params) + self.client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) + + def create_job(self, *args, **kwargs): + job_id = self.client.fine_tuning.jobs.create( + training_file=kwargs["dataset_id"], + model=kwargs["model_name"], + method=kwargs["method"] + ) + return job_id + + + def list_jobs(self, *args, **kwargs): + jobs = self.client.fine_tuning.jobs.list() + return jobs + + def list_job_events(self, *args, **kwargs): + events = self.client.fine_tuning.jobs.list_events(job_id=kwargs["job_id"]) + return events + + def get_job_details(self, *args, **kwargs): + job = self.client.fine_tuning.jobs.retrieve(job_id=kwargs["job_id"]) + return job + + def cancel_job(self, *args, **kwargs): + self.client.fine_tuning.jobs.cancel(job_id=kwargs["job_id"]) diff --git a/src/bespokelabs/curator/finetune/types.py b/src/bespokelabs/curator/finetune/types.py new file mode 100644 index 000000000..0e1028cf5 --- /dev/null +++ b/src/bespokelabs/curator/finetune/types.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel + +class FinetuneMethod(BaseModel): + type: str + hyperparameters: dict + +class FinetuneRequest(BaseModel): + job_name: str + dataset_id: str + model_name: str + seed: int + suffix: str + method: FinetuneMethod + +class FinetuneResponse(BaseModel): + job_id: str + job_name: str + dataset_id: str + model_name: str + seed: int + suffix: str + method: FinetuneMethod + status: str \ No newline at end of file diff --git a/src/bespokelabs/curator/utils.py b/src/bespokelabs/curator/utils.py index bbbc8a665..08662b0df 100644 --- a/src/bespokelabs/curator/utils.py +++ b/src/bespokelabs/curator/utils.py @@ -92,4 +92,5 @@ async def send_row(idx, row): await client.session_completed() run_in_event_loop(send_responses()) + return view_url diff --git a/test_kluster.ipynb b/test_kluster.ipynb new file mode 100644 index 000000000..11b222826 --- /dev/null +++ b/test_kluster.ipynb @@ -0,0 +1,124 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from bespokelabs.curator.datasets import upload" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from bespokelabs import curator" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "from bespokelabs import curator" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "finetuning_client = curator.Finetune(\n", + " backend = \"bespoke\",\n", + " backend_params = {\n", + " \"base_url\": 'http://localhost:8080'\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "bespoke-d2bf08bd92fa6567b251e825ee4182fba880c7b1f16215ccd5f509f86871a4ba\n", + "{'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'dataset_id': 'test', 'seed': 42, 'suffix': 'test', 'method': {'type': 'supervised', 'hyperparameters': {'learning_rate': 0.001, 'epochs': 1, 'batch_size': 16}}, 'job_name': 'test_job'}\n", + "{'job_id': '925fed8203c64f69a036ba2431e36409', 'status': 'queued', 'created_at': '2025-04-22T01:25:38.058546Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'learning_rate': 0.001, 'epochs': 1, 'batch_size': 16}}, 'seed': 42, 'suffix': 'test', 'user_id': '4a9259382afd4bea890ac57d9cce60ec', 'organization_id': '1345c8cca02a4859aabf8f9c1e8b707d', 'job_name': 'test_job', 'dataset_id': 'test'}\n" + ] + } + ], + "source": [ + "finetuning_client.create_job(\n", + " model_name=\"unsloth/Llama-3-8B-Instruct\",\n", + " job_name=\"test_job\",\n", + " dataset_id=\"test\",\n", + " seed=42,\n", + " suffix=\"test\",\n", + " method = {\n", + " \"type\": \"supervised\",\n", + " \"hyperparameters\": {\n", + " \"learning_rate\": 0.001,\n", + " \"epochs\": 1,\n", + " \"batch_size\": 16,\n", + " }\n", + " }\n", + ")\n" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[{'job_id': 'c7eeb19bf13e4ca38e520d9e868f84fc', 'status': 'queued', 'created_at': '2025-04-18T01:47:32.515836Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '6096228b-c45f-4486-af75-3026d26e2155', 'organization_id': 'a61334f1-7adf-4c0a-bafa-2a99e8009c2a', 'job_name': 'test_job', 'dataset_id': 'test'}, {'job_id': '159abd326a894ab9bd9b5a84a26e6747', 'status': 'queued', 'created_at': '2025-04-18T01:48:45.360006Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '61c25770-ba5d-49e0-b867-da46a7dba887', 'organization_id': '0424be00-87c2-4555-824e-529557626d45', 'job_name': 'test_job', 'dataset_id': 'test'}, {'job_id': '86334be4ced84056bdb202445ffc2d58', 'status': 'queued', 'created_at': '2025-04-18T02:00:17.035462Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '939ff2c7-697b-43c9-aafe-3210901b288c', 'organization_id': '60ebb12e-f720-4e51-a3e1-a712117acd4d', 'job_name': 'test_job', 'dataset_id': 'test'}]\n" + ] + } + ], + "source": [ + "print(finetuning_client.list_jobs())" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "bespokelabs-curator-Ky8xEGex-py3.10", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.16" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From ae722a01638dc8d8ad7f9f6720701dae43e1ba66 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Fri, 25 Apr 2025 18:24:11 -0700 Subject: [PATCH 02/12] update upload fn --- src/bespokelabs/curator/__init__.py | 2 +- src/bespokelabs/curator/datasets/__init__.py | 2 +- src/bespokelabs/curator/datasets/base.py | 19 +++---- src/bespokelabs/curator/finetune/__init__.py | 2 +- .../curator/finetune/base_backend.py | 3 +- .../curator/finetune/bespoke_backend.py | 49 ++++++------------- src/bespokelabs/curator/finetune/finetune.py | 14 +++--- .../curator/finetune/openai_backend.py | 19 +++---- src/bespokelabs/curator/finetune/types.py | 7 ++- test_kluster.ipynb | 24 +++------ 10 files changed, 55 insertions(+), 86 deletions(-) diff --git a/src/bespokelabs/curator/__init__.py b/src/bespokelabs/curator/__init__.py index c3289404e..76d28b660 100644 --- a/src/bespokelabs/curator/__init__.py +++ b/src/bespokelabs/curator/__init__.py @@ -1,9 +1,9 @@ """BespokeLabs Curator.""" from .code_executor.code_executor import CodeExecutor +from .finetune.finetune import Finetune from .llm.llm import LLM from .types import prompt as types -from .finetune.finetune import Finetune __all__ = ["LLM", "CodeExecutor", "types", "Finetune"] diff --git a/src/bespokelabs/curator/datasets/__init__.py b/src/bespokelabs/curator/datasets/__init__.py index d073ff4ad..a20843849 100644 --- a/src/bespokelabs/curator/datasets/__init__.py +++ b/src/bespokelabs/curator/datasets/__init__.py @@ -1,3 +1,3 @@ from .base import upload -__all__ = ["upload"] \ No newline at end of file +__all__ = ["upload"] diff --git a/src/bespokelabs/curator/datasets/base.py b/src/bespokelabs/curator/datasets/base.py index 8fa749141..a05bf0b5d 100644 --- a/src/bespokelabs/curator/datasets/base.py +++ b/src/bespokelabs/curator/datasets/base.py @@ -1,13 +1,16 @@ # just upload files, a thin wrapper around push_to_viewer +import logging from pathlib import Path -from bespokelabs.curator.utils import push_to_viewer + from datasets import load_dataset -import logging + +from bespokelabs.curator.utils import push_to_viewer logger = logging.getLogger(__name__) -def upload(path: str, name: str, split: str = "train"): + +def upload(path: str, split: str = "train"): # load file into a huggingface dataset # it could be a huggingface dataset or a local file @@ -15,17 +18,15 @@ def upload(path: str, name: str, split: str = "train"): try: dataset = load_dataset(path, split=split) - except Exception as e: - + except Exception: path = Path(path) if not path.exists(): - raise FileNotFoundError(f"File {path} does not exist") + raise FileNotFoundError(f"File {path} does not exist") - if not (path.suffix in [".jsonl", ".json", ".csv", ".parquet"]): + if path.suffix not in [".jsonl", ".json", ".csv", ".parquet"]: raise ValueError("Only jsonl, json, csv, and parquet files are supported currently") try: - if path.suffix == ".jsonl" or path.suffix == ".json": format = "json" elif path.suffix == ".csv": @@ -39,5 +40,5 @@ def upload(path: str, name: str, split: str = "train"): logger.error(f"Error loading dataset: {e}") raise e - link = push_to_viewer(dataset, name) + link = push_to_viewer(dataset) return link diff --git a/src/bespokelabs/curator/finetune/__init__.py b/src/bespokelabs/curator/finetune/__init__.py index c16b4bfef..a382d1fa1 100644 --- a/src/bespokelabs/curator/finetune/__init__.py +++ b/src/bespokelabs/curator/finetune/__init__.py @@ -1,3 +1,3 @@ from .finetune import Finetune -__all__ = ["Finetune"] \ No newline at end of file +__all__ = ["Finetune"] diff --git a/src/bespokelabs/curator/finetune/base_backend.py b/src/bespokelabs/curator/finetune/base_backend.py index 2fb969ed2..9736d10e5 100644 --- a/src/bespokelabs/curator/finetune/base_backend.py +++ b/src/bespokelabs/curator/finetune/base_backend.py @@ -1,7 +1,7 @@ from abc import ABC, abstractmethod -class BaseFinetuneBackend(ABC): +class BaseFinetuneBackend(ABC): def __init__(self, backend_params: dict): self.backend_params = backend_params @@ -24,7 +24,6 @@ def list_job_events(self, job_id: str): # @abstractmethod # def list_job_metrics(self, job_id: str): # pass - @abstractmethod def get_job_details(self, job_id: str): diff --git a/src/bespokelabs/curator/finetune/bespoke_backend.py b/src/bespokelabs/curator/finetune/bespoke_backend.py index 305993732..a1518dea1 100644 --- a/src/bespokelabs/curator/finetune/bespoke_backend.py +++ b/src/bespokelabs/curator/finetune/bespoke_backend.py @@ -1,29 +1,26 @@ -from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend -import requests -import json -import os import logging +import os + +import requests + +from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend logger = logging.getLogger(__name__) -class BespokeFinetuneBackend(BaseFinetuneBackend): +class BespokeFinetuneBackend(BaseFinetuneBackend): def __init__(self, backend_params: dict): super().__init__(backend_params) self.env = "dev" if backend_params.get("env", "prod") == "dev" else "prod" if not backend_params.get("base_url"): self.base_url = f"https://api.bespokelabs.com/{self.env}/v0/finetune" else: - self.base_url = backend_params["base_url"] + "/v0/finetune" + self.base_url = backend_params["base_url"] + "/v0/finetune" self.api_key = os.environ.get("BESPOKE_API_KEY") - def create_job(self, *args, **kwargs): url = f"{self.base_url}/jobs/create" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} if os.environ.get("HF_TOKEN") is None: logger.warning("HF_TOKEN is not set. Please set it to use private gated models or datasets.") @@ -36,55 +33,37 @@ def create_job(self, *args, **kwargs): "method": kwargs["method"], "job_name": kwargs["job_name"], "secrets": { - 'HF_TOKEN': os.environ.get("HF_TOKEN", None), - } + "HF_TOKEN": os.environ.get("HF_TOKEN", None), + }, } response = requests.post(url, headers=headers, json=data) return response.json() - def list_jobs(self, *args, **kwargs): - url = f"{self.base_url}/jobs" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} response = requests.get(url, headers=headers) return response.json() def list_job_events(self, *args, **kwargs): - url = f"{self.base_url}/jobs/{kwargs['job_id']}/events" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} response = requests.get(url, headers=headers) return response.json() - def get_job_details(self, *args, **kwargs): - url = f"{self.base_url}/jobs/{kwargs['job_id']}?log_type={kwargs.get('type', 'EVENTS')}" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} response = requests.get(url, headers=headers) return response.json() def cancel_job(self, *args, **kwargs): - url = f"{self.base_url}/jobs/{kwargs['job_id']}/cancel" - headers = { - "Content-Type": "application/json", - "Authorization": f"Bearer {self.api_key}" - } + headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} response = requests.post(url, headers=headers) return response.json() diff --git a/src/bespokelabs/curator/finetune/finetune.py b/src/bespokelabs/curator/finetune/finetune.py index 5760a11ed..cad121a7c 100644 --- a/src/bespokelabs/curator/finetune/finetune.py +++ b/src/bespokelabs/curator/finetune/finetune.py @@ -1,9 +1,10 @@ -from abc import ABC, abstractmethod +from abc import ABC + from bespokelabs.curator.finetune.bespoke_backend import BespokeFinetuneBackend from bespokelabs.curator.finetune.openai_backend import OpenAIFinetuneBackend -class Finetune(ABC): +class Finetune(ABC): def __init__(self, backend: str, backend_params: dict): self.backend_params = backend_params if backend == "bespoke": @@ -15,16 +16,15 @@ def __init__(self, backend: str, backend_params: dict): def create_job(self, *args, **kwargs): self._backend.create_job(*args, **kwargs) - + def list_jobs(self, *args, **kwargs): return self._backend.list_jobs(*args, **kwargs) - + def list_job_events(self, *args, **kwargs): return self._backend.list_job_events(*args, **kwargs) - + def get_job_details(self, *args, **kwargs): return self._backend.get_job_details(*args, **kwargs) - + def cancel_job(self, *args, **kwargs): return self._backend.cancel_job(*args, **kwargs) - diff --git a/src/bespokelabs/curator/finetune/openai_backend.py b/src/bespokelabs/curator/finetune/openai_backend.py index 5a2db8563..b4914b0e7 100644 --- a/src/bespokelabs/curator/finetune/openai_backend.py +++ b/src/bespokelabs/curator/finetune/openai_backend.py @@ -1,33 +1,30 @@ import os -from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend + from openai import OpenAI -class OpenAIFinetuneBackend(BaseFinetuneBackend): +from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend + +class OpenAIFinetuneBackend(BaseFinetuneBackend): def __init__(self, backend_params: dict): super().__init__(backend_params) self.client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) def create_job(self, *args, **kwargs): - job_id = self.client.fine_tuning.jobs.create( - training_file=kwargs["dataset_id"], - model=kwargs["model_name"], - method=kwargs["method"] - ) + job_id = self.client.fine_tuning.jobs.create(training_file=kwargs["dataset_id"], model=kwargs["model_name"], method=kwargs["method"]) return job_id - def list_jobs(self, *args, **kwargs): jobs = self.client.fine_tuning.jobs.list() return jobs - + def list_job_events(self, *args, **kwargs): events = self.client.fine_tuning.jobs.list_events(job_id=kwargs["job_id"]) return events - + def get_job_details(self, *args, **kwargs): job = self.client.fine_tuning.jobs.retrieve(job_id=kwargs["job_id"]) return job - + def cancel_job(self, *args, **kwargs): self.client.fine_tuning.jobs.cancel(job_id=kwargs["job_id"]) diff --git a/src/bespokelabs/curator/finetune/types.py b/src/bespokelabs/curator/finetune/types.py index 0e1028cf5..5e01f390b 100644 --- a/src/bespokelabs/curator/finetune/types.py +++ b/src/bespokelabs/curator/finetune/types.py @@ -1,10 +1,12 @@ from pydantic import BaseModel + class FinetuneMethod(BaseModel): type: str hyperparameters: dict -class FinetuneRequest(BaseModel): + +class FinetuneRequest(BaseModel): job_name: str dataset_id: str model_name: str @@ -12,6 +14,7 @@ class FinetuneRequest(BaseModel): suffix: str method: FinetuneMethod + class FinetuneResponse(BaseModel): job_id: str job_name: str @@ -20,4 +23,4 @@ class FinetuneResponse(BaseModel): seed: int suffix: str method: FinetuneMethod - status: str \ No newline at end of file + status: str diff --git a/test_kluster.ipynb b/test_kluster.ipynb index 11b222826..e65b5c1cd 100644 --- a/test_kluster.ipynb +++ b/test_kluster.ipynb @@ -5,9 +5,7 @@ "execution_count": 1, "metadata": {}, "outputs": [], - "source": [ - "from bespokelabs.curator.datasets import upload" - ] + "source": [] }, { "cell_type": "code", @@ -23,10 +21,7 @@ "execution_count": 2, "metadata": {}, "outputs": [], - "source": [ - "\n", - "from bespokelabs import curator" - ] + "source": [] }, { "cell_type": "code", @@ -34,12 +29,7 @@ "metadata": {}, "outputs": [], "source": [ - "finetuning_client = curator.Finetune(\n", - " backend = \"bespoke\",\n", - " backend_params = {\n", - " \"base_url\": 'http://localhost:8080'\n", - " }\n", - ")" + "finetuning_client = curator.Finetune(backend=\"bespoke\", backend_params={\"base_url\": \"http://localhost:8080\"})" ] }, { @@ -64,15 +54,15 @@ " dataset_id=\"test\",\n", " seed=42,\n", " suffix=\"test\",\n", - " method = {\n", + " method={\n", " \"type\": \"supervised\",\n", " \"hyperparameters\": {\n", " \"learning_rate\": 0.001,\n", " \"epochs\": 1,\n", " \"batch_size\": 16,\n", - " }\n", - " }\n", - ")\n" + " },\n", + " },\n", + ")" ] }, { From 6245fffb9d4c6a6fd975752c98da0cce6b8dfc89 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Fri, 25 Apr 2025 18:58:54 -0700 Subject: [PATCH 03/12] return job ID --- src/bespokelabs/curator/finetune/finetune.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bespokelabs/curator/finetune/finetune.py b/src/bespokelabs/curator/finetune/finetune.py index cad121a7c..e8d52da59 100644 --- a/src/bespokelabs/curator/finetune/finetune.py +++ b/src/bespokelabs/curator/finetune/finetune.py @@ -15,7 +15,7 @@ def __init__(self, backend: str, backend_params: dict): raise ValueError(f"Invalid backend: {backend}") def create_job(self, *args, **kwargs): - self._backend.create_job(*args, **kwargs) + return self._backend.create_job(*args, **kwargs) def list_jobs(self, *args, **kwargs): return self._backend.list_jobs(*args, **kwargs) From 536fa817543aba50ba8e3403cc4f457be922b8d3 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Mon, 28 Apr 2025 21:23:03 -0700 Subject: [PATCH 04/12] serving infra --- src/bespokelabs/curator/__init__.py | 3 +- src/bespokelabs/curator/models/__init__.py | 12 +++ .../curator/models/base_backend.py | 21 +++++ .../curator/models/bespoke_backend.py | 86 +++++++++++++++++++ src/bespokelabs/curator/models/models.py | 35 ++++++++ 5 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 src/bespokelabs/curator/models/__init__.py create mode 100644 src/bespokelabs/curator/models/base_backend.py create mode 100644 src/bespokelabs/curator/models/bespoke_backend.py create mode 100644 src/bespokelabs/curator/models/models.py diff --git a/src/bespokelabs/curator/__init__.py b/src/bespokelabs/curator/__init__.py index 76d28b660..bf4f456ad 100644 --- a/src/bespokelabs/curator/__init__.py +++ b/src/bespokelabs/curator/__init__.py @@ -4,7 +4,8 @@ from .finetune.finetune import Finetune from .llm.llm import LLM from .types import prompt as types +from .models import Models -__all__ = ["LLM", "CodeExecutor", "types", "Finetune"] +__all__ = ["LLM", "CodeExecutor", "types", "Finetune", "Models"] from .log import _CONSOLE # noqa: F401 diff --git a/src/bespokelabs/curator/models/__init__.py b/src/bespokelabs/curator/models/__init__.py new file mode 100644 index 000000000..41163f05c --- /dev/null +++ b/src/bespokelabs/curator/models/__init__.py @@ -0,0 +1,12 @@ +"""Models module for Curator.""" + +import os +import json +import requests +import typing as t +from abc import ABC, abstractmethod +from ..log import logger + +from .models import Models + +__all__ = ["Models"] \ No newline at end of file diff --git a/src/bespokelabs/curator/models/base_backend.py b/src/bespokelabs/curator/models/base_backend.py new file mode 100644 index 000000000..cc0fed270 --- /dev/null +++ b/src/bespokelabs/curator/models/base_backend.py @@ -0,0 +1,21 @@ +import typing as t +from abc import ABC, abstractmethod + +class BaseModelsBackend(ABC): + """Base interface for model operations.""" + + @abstractmethod + def list_models(self, job_id: str = None) -> t.List[dict]: + """List available models or models related to a specific job.""" + pass + + @abstractmethod + def deploy_model(self, model_id: str) -> bool: + """Deploy a model to make it available for inference.""" + pass + + @abstractmethod + def undeploy_model(self, model_id: str) -> bool: + """Undeploy a model to free up resources.""" + pass + diff --git a/src/bespokelabs/curator/models/bespoke_backend.py b/src/bespokelabs/curator/models/bespoke_backend.py new file mode 100644 index 000000000..cfd2ae0f8 --- /dev/null +++ b/src/bespokelabs/curator/models/bespoke_backend.py @@ -0,0 +1,86 @@ +import os +import requests +import typing as t +from ..log import logger +from .base_backend import BaseModelsBackend + +class BespokeModelsBackend(BaseModelsBackend): + """Client for interacting with Bespoke models API.""" + + def __init__(self, base_url: str = None, api_key: str = None): + """Initialize the Bespoke models client. + + Args: + base_url: Base URL for the API + api_key: API key for authentication (optional) + """ + self.base_url = base_url + self.api_key = api_key or os.environ.get("BESPOKE_API_KEY") + self.headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {} + + def list_models(self, job_id: str = None) -> t.List[dict]: + """List available models or models related to a specific job. + + Args: + job_id: Optional job ID to filter models by + + Returns: + A list of model information dictionaries + """ + url = f"{self.base_url}/v0/models/{job_id}" + + try: + response = requests.get(url, headers=self.headers) + print(response.json()) + if response.status_code == 200: + return response.json().get("models", []) + else: + logger.error(f"Failed to list models: {response.status_code}, {response.text}") + return [] + except Exception as e: + logger.error(f"Error listing models: {str(e)}") + return [] + + def deploy_model(self, model_id: str) -> bool: + """Deploy a model to make it available for inference. + + Args: + model_id: ID of the model to deploy + + Returns: + True if deployment was successful, False otherwise + """ + url = f"{self.base_url}/v0/models/{model_id}/deploy" + + try: + response = requests.post(url, headers=self.headers) + if response.status_code in [200, 201, 202]: + return response.json() + else: + logger.error(f"Failed to deploy model: {response.status_code}, {response.text}") + return False + except Exception as e: + logger.error(f"Error deploying model: {str(e)}") + return False + + def undeploy_model(self, model_id: str) -> bool: + """Undeploy a model to free up resources. + + Args: + model_id: ID of the model to undeploy + + Returns: + True if undeployment was successful, False otherwise + """ + url = f"{self.base_url}/v0/models/{model_id}/undeploy" + + try: + response = requests.post(url, headers=self.headers) + if response.status_code in [200, 202, 204]: + return response.json() + else: + logger.error(f"Failed to undeploy model: {response.status_code}, {response.text}") + return False + except Exception as e: + logger.error(f"Error undeploying model: {str(e)}") + return False \ No newline at end of file diff --git a/src/bespokelabs/curator/models/models.py b/src/bespokelabs/curator/models/models.py new file mode 100644 index 000000000..f5d6577b4 --- /dev/null +++ b/src/bespokelabs/curator/models/models.py @@ -0,0 +1,35 @@ +import typing as t +from .bespoke_backend import BespokeModelsBackend + +class Models: + """Factory for creating model clients based on the specified backend.""" + + def __init__(self, backend: str = "bespoke", backend_params: dict = None): + """Initialize the Models factory. + + Args: + backend: The backend provider to use ('bespoke' supported) + backend_params: Configuration parameters for the backend + """ + self.backend = backend + self.backend_params = backend_params or {} + + if backend == "bespoke": + self.client = BespokeModelsBackend( + base_url=self.backend_params.get("base_url"), + api_key=self.backend_params.get("api_key") + ) + else: + raise ValueError(f"Unsupported backend: {backend}") + + def list_models(self, job_id: str = None) -> t.List[dict]: + """Delegate to the client's list_models method.""" + return self.client.list_models(job_id=job_id) + + def deploy_model(self, model_id: str) -> bool: + """Delegate to the client's deploy_model method.""" + return self.client.deploy_model(model_id=model_id) + + def undeploy_model(self, model_id: str) -> bool: + """Delegate to the client's undeploy_model method.""" + return self.client.undeploy_model(model_id=model_id) From 00bd47f9bc3a70f65f482015ba30be235cfe9ef9 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 29 Apr 2025 13:53:55 -0700 Subject: [PATCH 05/12] add num_gpus parameter --- src/bespokelabs/curator/finetune/bespoke_backend.py | 1 + src/bespokelabs/curator/finetune/types.py | 1 + 2 files changed, 2 insertions(+) diff --git a/src/bespokelabs/curator/finetune/bespoke_backend.py b/src/bespokelabs/curator/finetune/bespoke_backend.py index a1518dea1..feb2f3a2c 100644 --- a/src/bespokelabs/curator/finetune/bespoke_backend.py +++ b/src/bespokelabs/curator/finetune/bespoke_backend.py @@ -32,6 +32,7 @@ def create_job(self, *args, **kwargs): "suffix": kwargs["suffix"], "method": kwargs["method"], "job_name": kwargs["job_name"], + "num_gpus": kwargs["num_gpus"], "secrets": { "HF_TOKEN": os.environ.get("HF_TOKEN", None), }, diff --git a/src/bespokelabs/curator/finetune/types.py b/src/bespokelabs/curator/finetune/types.py index 5e01f390b..69d9d057c 100644 --- a/src/bespokelabs/curator/finetune/types.py +++ b/src/bespokelabs/curator/finetune/types.py @@ -12,6 +12,7 @@ class FinetuneRequest(BaseModel): model_name: str seed: int suffix: str + num_gpus: int method: FinetuneMethod From 8a2682d411615cba0d2d52788f4498273fd56e3f Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 29 Apr 2025 17:58:17 -0700 Subject: [PATCH 06/12] up --- src/bespokelabs/curator/models/bespoke_backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bespokelabs/curator/models/bespoke_backend.py b/src/bespokelabs/curator/models/bespoke_backend.py index cfd2ae0f8..658bf9ba2 100644 --- a/src/bespokelabs/curator/models/bespoke_backend.py +++ b/src/bespokelabs/curator/models/bespoke_backend.py @@ -31,9 +31,8 @@ def list_models(self, job_id: str = None) -> t.List[dict]: try: response = requests.get(url, headers=self.headers) - print(response.json()) if response.status_code == 200: - return response.json().get("models", []) + return response.json() else: logger.error(f"Failed to list models: {response.status_code}, {response.text}") return [] From 3a8a242a73ae0bf080a117b74942a98ab227f096 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 29 Apr 2025 18:39:49 -0700 Subject: [PATCH 07/12] override structured output --- .../online/litellm_online_request_processor.py | 3 ++- .../online/openai_online_request_processor.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py index faeda718d..7609cbc98 100644 --- a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py +++ b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py @@ -146,7 +146,8 @@ def check_structured_output_support(self): - Logs detailed information about support status - Required for models that will use JSON schema responses """ - + return True + class User(BaseModel): name: str age: int diff --git a/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py b/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py index 14dc3c1a1..4182536ef 100644 --- a/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py +++ b/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py @@ -230,6 +230,7 @@ def check_structured_output_support(self) -> bool: - o1 with date >= 2024-12-17 or latest - o3-mini with date >= 2025-01-31 or latest """ + return True model_name = self.config.model.lower() # Check gpt-4o-mini support. From da73fe1977ea49b9a58ecc5b557ac5d9b09383d0 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 29 Apr 2025 18:48:23 -0700 Subject: [PATCH 08/12] remove std out support --- .../curator/request_processor/base_request_processor.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bespokelabs/curator/request_processor/base_request_processor.py b/src/bespokelabs/curator/request_processor/base_request_processor.py index 210957841..1650eb576 100644 --- a/src/bespokelabs/curator/request_processor/base_request_processor.py +++ b/src/bespokelabs/curator/request_processor/base_request_processor.py @@ -132,11 +132,11 @@ def run( self.validate_config() self.prompt_formatter = prompt_formatter - if self.prompt_formatter.response_format: - if not self.check_structured_output_support(): - raise ValueError(f"Model {self.config.model} does not support structured output, response_format: {self.prompt_formatter.response_format}") + # if self.prompt_formatter.response_format: + # if not self.check_structured_output_support(): + # raise ValueError(f"Model {self.config.model} does not support structured output, response_format: {self.prompt_formatter.response_format}") generic_request_files = self.create_request_files(dataset) - + self.requests_to_responses(generic_request_files) return self.create_dataset_files(parse_func_hash) From 62087fdece15c15cd6f29dc5b94505eede802ecc Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 6 May 2025 14:35:09 -0700 Subject: [PATCH 09/12] up --- examples/finetuning/finetuning_client.ipynb | 140 ++++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 examples/finetuning/finetuning_client.ipynb diff --git a/examples/finetuning/finetuning_client.ipynb b/examples/finetuning/finetuning_client.ipynb new file mode 100644 index 000000000..5a400ebd1 --- /dev/null +++ b/examples/finetuning/finetuning_client.ipynb @@ -0,0 +1,140 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Setting up the finetuning job" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "from bespokelabs import curator" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "finetuning_client = curator.Finetune(\n", + " backend = \"bespoke\",\n", + " backend_params = {\n", + " \"base_url\": 'https://api-dev.bespokelabs.ai'\n", + " }\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "dataset_id = \"41da7b9b3e384ae486a9945376d2fd9c\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "finetuning_client.create_job(\n", + " model_name=\"Qwen/Qwen2.5-7B-Instruct\",\n", + " job_name=\"bespoke-devrev-demo-live\",\n", + " dataset_id=dataset_id,\n", + " seed=42,\n", + " suffix=\"ft\",\n", + " method = {\n", + " \"type\": \"supervised\",\n", + " \"hyperparameters\": {\n", + " ## Type\n", + " \"finetuning_type\": \"lora\",\n", + " \"lora_rank\": 16,\n", + "\n", + " ## training dynamics\n", + " \"learning_rate\": 0.0001,\n", + " \"num_train_epochs\": 1,\n", + " \"per_device_train_batch_size\": 4,\n", + " \"gradient_accumulation_steps\": 1,\n", + "\n", + " ## infra\n", + " \"preprocessing_num_workers\": 32,\n", + " \"dataloader_num_workers\": 16,\n", + " \"logging_steps\": 1,\n", + " }\n", + " },\n", + " num_gpus=8\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Deploying the finetuned model" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "models_client = curator.Models(\n", + " backend = \"bespoke\",\n", + " backend_params = {\n", + " \"base_url\": 'https://api-dev.bespokelabs.ai'\n", + " }\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "models_client.list_models(job_id='d3201c37255247c2825ff4ce52c110d6')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "models_client.deploy_model(model_id = '10426763-76be-483e-987b-07d2ee5adbd7')" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 7bfb179d67bbdf758947f3b6823155a341ad100e Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 6 May 2025 15:02:33 -0700 Subject: [PATCH 10/12] pre-commit --- src/bespokelabs/curator/__init__.py | 2 +- src/bespokelabs/curator/datasets/__init__.py | 2 + src/bespokelabs/curator/datasets/base.py | 14 +- src/bespokelabs/curator/finetune/__init__.py | 2 + .../curator/finetune/base_backend.py | 28 +++- .../curator/finetune/bespoke_backend.py | 85 +++++++++++- src/bespokelabs/curator/finetune/finetune.py | 23 ++- .../curator/finetune/openai_backend.py | 131 ++++++++++++++++-- src/bespokelabs/curator/finetune/types.py | 23 +++ src/bespokelabs/curator/models/__init__.py | 9 +- .../curator/models/base_backend.py | 2 +- .../curator/models/bespoke_backend.py | 27 ++-- src/bespokelabs/curator/models/models.py | 19 ++- .../base_request_processor.py | 2 +- .../litellm_online_request_processor.py | 2 +- 15 files changed, 315 insertions(+), 56 deletions(-) diff --git a/src/bespokelabs/curator/__init__.py b/src/bespokelabs/curator/__init__.py index bf4f456ad..29dc6e002 100644 --- a/src/bespokelabs/curator/__init__.py +++ b/src/bespokelabs/curator/__init__.py @@ -3,8 +3,8 @@ from .code_executor.code_executor import CodeExecutor from .finetune.finetune import Finetune from .llm.llm import LLM -from .types import prompt as types from .models import Models +from .types import prompt as types __all__ = ["LLM", "CodeExecutor", "types", "Finetune", "Models"] diff --git a/src/bespokelabs/curator/datasets/__init__.py b/src/bespokelabs/curator/datasets/__init__.py index a20843849..72d10f5a2 100644 --- a/src/bespokelabs/curator/datasets/__init__.py +++ b/src/bespokelabs/curator/datasets/__init__.py @@ -1,3 +1,5 @@ +"""Dataset loading and processing utilities.""" + from .base import upload __all__ = ["upload"] diff --git a/src/bespokelabs/curator/datasets/base.py b/src/bespokelabs/curator/datasets/base.py index a05bf0b5d..a36751232 100644 --- a/src/bespokelabs/curator/datasets/base.py +++ b/src/bespokelabs/curator/datasets/base.py @@ -11,6 +11,16 @@ def upload(path: str, split: str = "train"): + """Uploads a dataset file to the Hugging Face Hub. + + Args: + path: The local path to the dataset file. + split: The name of the split to upload (e.g., "train", "test"). + + Raises: + FileNotFoundError: If the specified file does not exist. + ValueError: If the file type is not supported. + """ # load file into a huggingface dataset # it could be a huggingface dataset or a local file @@ -21,10 +31,10 @@ def upload(path: str, split: str = "train"): except Exception: path = Path(path) if not path.exists(): - raise FileNotFoundError(f"File {path} does not exist") + raise FileNotFoundError(f"File {path} does not exist") from None if path.suffix not in [".jsonl", ".json", ".csv", ".parquet"]: - raise ValueError("Only jsonl, json, csv, and parquet files are supported currently") + raise ValueError("Only jsonl, json, csv, and parquet files are supported currently") from None try: if path.suffix == ".jsonl" or path.suffix == ".json": diff --git a/src/bespokelabs/curator/finetune/__init__.py b/src/bespokelabs/curator/finetune/__init__.py index a382d1fa1..63164cb7b 100644 --- a/src/bespokelabs/curator/finetune/__init__.py +++ b/src/bespokelabs/curator/finetune/__init__.py @@ -1,3 +1,5 @@ +"""Finetuning backend abstraction and implementations.""" + from .finetune import Finetune __all__ = ["Finetune"] diff --git a/src/bespokelabs/curator/finetune/base_backend.py b/src/bespokelabs/curator/finetune/base_backend.py index 9736d10e5..f17aa6217 100644 --- a/src/bespokelabs/curator/finetune/base_backend.py +++ b/src/bespokelabs/curator/finetune/base_backend.py @@ -2,19 +2,33 @@ class BaseFinetuneBackend(ABC): + """Abstract base class for finetuning backends.""" + def __init__(self, backend_params: dict): + """Initializes the finetuning backend. + + Args: + backend_params: A dictionary containing backend-specific parameters. + """ self.backend_params = backend_params @abstractmethod - def create_job(self, model_name: str, dataset_id: str, method: dict): + def create_job(self, *args, **kwargs): + """Creates a new finetuning job.""" pass @abstractmethod - def list_jobs(self): + def list_jobs(self, *args, **kwargs): + """Lists all finetuning jobs.""" pass @abstractmethod def list_job_events(self, job_id: str): + """Lists events for a specific finetuning job. + + Args: + job_id: The ID of the finetuning job. + """ pass # @abstractmethod @@ -27,8 +41,18 @@ def list_job_events(self, job_id: str): @abstractmethod def get_job_details(self, job_id: str): + """Retrieves details for a specific finetuning job. + + Args: + job_id: The ID of the finetuning job. + """ pass @abstractmethod def cancel_job(self, job_id: str): + """Cancels a specific finetuning job. + + Args: + job_id: The ID of the finetuning job. + """ pass diff --git a/src/bespokelabs/curator/finetune/bespoke_backend.py b/src/bespokelabs/curator/finetune/bespoke_backend.py index feb2f3a2c..d99d875b5 100644 --- a/src/bespokelabs/curator/finetune/bespoke_backend.py +++ b/src/bespokelabs/curator/finetune/bespoke_backend.py @@ -1,30 +1,65 @@ import logging import os +from typing import Union import requests from bespokelabs.curator.finetune.base_backend import BaseFinetuneBackend +from bespokelabs.curator.finetune.types import FinetuneBackendParams logger = logging.getLogger(__name__) class BespokeFinetuneBackend(BaseFinetuneBackend): - def __init__(self, backend_params: dict): + """A client for interacting with the Bespoke Labs Finetuning API. + + This class provides methods to create, list, and manage finetuning jobs + on the Bespoke Labs platform. + """ + + def __init__(self, backend_params: Union[FinetuneBackendParams, dict]): + """Initializes the BespokeFinetuneBackend. + + Args: + backend_params: A dictionary or FinetuneBackendParams object containing + configuration for the backend, such as base_url and env. + """ super().__init__(backend_params) - self.env = "dev" if backend_params.get("env", "prod") == "dev" else "prod" + # Determine the environment suffix for the API URL. + self.env = "-dev" if backend_params.get("env", "prod") == "dev" else "" + # Set the base URL for the finetuning API. if not backend_params.get("base_url"): - self.base_url = f"https://api.bespokelabs.com/{self.env}/v0/finetune" + self.base_url = f"https://api{self.env}.bespokelabs.com/v0/finetune" else: self.base_url = backend_params["base_url"] + "/v0/finetune" + # Retrieve the API key from environment variables. self.api_key = os.environ.get("BESPOKE_API_KEY") def create_job(self, *args, **kwargs): + """Creates a new finetuning job. + + Args: + *args: Variable length argument list. + **kwargs: Arbitrary keyword arguments. Expected keys include: + 'model_name': The name of the model to finetune. + 'dataset_id': The ID of the dataset for training. + 'seed': The random seed for reproducibility. + 'suffix': A suffix to append to the finetuned model name. + 'method': The finetuning method and its hyperparameters. + 'job_name': A name for the finetuning job. + 'num_gpus': The number of GPUs to use for finetuning. + + Returns: + dict: The JSON response from the API, typically containing job details. + """ url = f"{self.base_url}/jobs/create" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} + # Warn if Hugging Face token is not set, as it might be needed for private resources. if os.environ.get("HF_TOKEN") is None: logger.warning("HF_TOKEN is not set. Please set it to use private gated models or datasets.") + # Prepare the data payload for the API request. data = { "model_name": kwargs["model_name"], "dataset_id": kwargs["dataset_id"], @@ -34,7 +69,7 @@ def create_job(self, *args, **kwargs): "job_name": kwargs["job_name"], "num_gpus": kwargs["num_gpus"], "secrets": { - "HF_TOKEN": os.environ.get("HF_TOKEN", None), + "HF_TOKEN": os.environ.get("HF_TOKEN", None), # Include HF_TOKEN if available. }, } @@ -42,6 +77,15 @@ def create_job(self, *args, **kwargs): return response.json() def list_jobs(self, *args, **kwargs): + """Lists all finetuning jobs. + + Args: + *args: Variable length argument list (currently unused). + **kwargs: Arbitrary keyword arguments (currently unused). + + Returns: + dict: The JSON response from the API, typically a list of jobs. + """ url = f"{self.base_url}/jobs" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} @@ -49,6 +93,16 @@ def list_jobs(self, *args, **kwargs): return response.json() def list_job_events(self, *args, **kwargs): + """Lists events for a specific finetuning job. + + Args: + *args: Variable length argument list (currently unused). + **kwargs: Arbitrary keyword arguments. Expected key: + 'job_id': The ID of the job to retrieve events for. + + Returns: + dict: The JSON response from the API, typically a list of job events. + """ url = f"{self.base_url}/jobs/{kwargs['job_id']}/events" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} @@ -56,6 +110,19 @@ def list_job_events(self, *args, **kwargs): return response.json() def get_job_details(self, *args, **kwargs): + """Retrieves details for a specific finetuning job. + + Args: + *args: Variable length argument list (currently unused). + **kwargs: Arbitrary keyword arguments. Expected keys: + 'job_id': The ID of the job to retrieve details for. + 'type' (optional): The type of logs to retrieve (e.g., 'EVENTS'). + Defaults to 'EVENTS'. + + Returns: + dict: The JSON response from the API, containing job details. + """ + # Construct URL with optional log_type parameter. url = f"{self.base_url}/jobs/{kwargs['job_id']}?log_type={kwargs.get('type', 'EVENTS')}" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} @@ -63,6 +130,16 @@ def get_job_details(self, *args, **kwargs): return response.json() def cancel_job(self, *args, **kwargs): + """Cancels a specific finetuning job. + + Args: + *args: Variable length argument list (currently unused). + **kwargs: Arbitrary keyword arguments. Expected key: + 'job_id': The ID of the job to cancel. + + Returns: + dict: The JSON response from the API, typically confirming cancellation. + """ url = f"{self.base_url}/jobs/{kwargs['job_id']}/cancel" headers = {"Content-Type": "application/json", "Authorization": f"Bearer {self.api_key}"} diff --git a/src/bespokelabs/curator/finetune/finetune.py b/src/bespokelabs/curator/finetune/finetune.py index e8d52da59..8c3542776 100644 --- a/src/bespokelabs/curator/finetune/finetune.py +++ b/src/bespokelabs/curator/finetune/finetune.py @@ -1,11 +1,23 @@ -from abc import ABC +from typing import Union from bespokelabs.curator.finetune.bespoke_backend import BespokeFinetuneBackend from bespokelabs.curator.finetune.openai_backend import OpenAIFinetuneBackend +from bespokelabs.curator.finetune.types import FinetuneBackendParams -class Finetune(ABC): - def __init__(self, backend: str, backend_params: dict): +class Finetune: + """A class to manage finetuning jobs using different backends.""" + + def __init__(self, backend: str, backend_params: Union[FinetuneBackendParams, dict]): + """Initializes the Finetune class with a specified backend. + + Args: + backend: The name of the backend to use (e.g., "bespoke", "openai"). + backend_params: Parameters for the backend. + + Raises: + ValueError: If an invalid backend is specified. + """ self.backend_params = backend_params if backend == "bespoke": self._backend = BespokeFinetuneBackend(backend_params) @@ -15,16 +27,21 @@ def __init__(self, backend: str, backend_params: dict): raise ValueError(f"Invalid backend: {backend}") def create_job(self, *args, **kwargs): + """Creates a finetuning job using the configured backend.""" return self._backend.create_job(*args, **kwargs) def list_jobs(self, *args, **kwargs): + """Lists finetuning jobs using the configured backend.""" return self._backend.list_jobs(*args, **kwargs) def list_job_events(self, *args, **kwargs): + """Lists job events using the configured backend.""" return self._backend.list_job_events(*args, **kwargs) def get_job_details(self, *args, **kwargs): + """Gets job details using the configured backend.""" return self._backend.get_job_details(*args, **kwargs) def cancel_job(self, *args, **kwargs): + """Cancels a job using the configured backend.""" return self._backend.cancel_job(*args, **kwargs) diff --git a/src/bespokelabs/curator/finetune/openai_backend.py b/src/bespokelabs/curator/finetune/openai_backend.py index b4914b0e7..b888ab2b8 100644 --- a/src/bespokelabs/curator/finetune/openai_backend.py +++ b/src/bespokelabs/curator/finetune/openai_backend.py @@ -6,25 +6,134 @@ class OpenAIFinetuneBackend(BaseFinetuneBackend): + """A client for interacting with the OpenAI Finetuning API. + + This class provides methods to create, list, and manage finetuning jobs + on the OpenAI platform. It serves as a backend for the Finetune class. + """ + def __init__(self, backend_params: dict): + """Initializes the OpenAIFinetuneBackend. + + Args: + backend_params: A dictionary containing configuration for the backend. + Currently, this is not used by the OpenAI backend but is + part of the BaseFinetuneBackend interface. + The OpenAI API key is retrieved from the environment variable + "OPENAI_API_KEY". + """ super().__init__(backend_params) + # Initialize the OpenAI client using the API key from environment variables. self.client = OpenAI(api_key=os.environ["OPENAI_API_KEY"]) - def create_job(self, *args, **kwargs): - job_id = self.client.fine_tuning.jobs.create(training_file=kwargs["dataset_id"], model=kwargs["model_name"], method=kwargs["method"]) - return job_id + def create_job(self, **kwargs): + """Creates a new finetuning job on the OpenAI platform. + + Args: + **kwargs: Arbitrary keyword arguments passed directly to the + `openai.fine_tuning.jobs.create` method. Expected keys include: + 'dataset_id' (str): The ID of an uploaded file that contains training data. + 'model_name' (str): The name of the model to fine-tune. + 'method' (Optional[dict]): The finetuning method and its hyperparameters. + (Note: OpenAI API might use 'hyperparameters' directly + or a nested structure depending on the API version and model type). + 'hyperparameters' (Optional[dict]): The hyperparameters used for the fine-tuning job. + (This might be deprecated in favor of 'method' in some contexts). + 'seed' (Optional[int]): The seed to use for reproducibility. + 'suffix' (Optional[str]): A string of up to 40 characters that will be added to your + fine-tuned model name. + 'validation_file' (Optional[str]): The ID of an uploaded file that contains validation data. + Refer to the OpenAI API documentation for a complete list of parameters. + + Returns: + openai.types.fine_tuning.FineTuningJob: An object representing the created fine-tuning job. + """ + job = self.client.fine_tuning.jobs.create( + training_file=kwargs["dataset_id"], + model=kwargs["model_name"], + hyperparameters=kwargs.get("hyperparameters"), + seed=kwargs.get("seed"), + suffix=kwargs.get("suffix"), + validation_file=kwargs.get("validation_file"), + ) + return job + + def list_jobs(self, **kwargs): + """Lists all finetuning jobs for the organization on the OpenAI platform. + + Args: + **kwargs: Arbitrary keyword arguments passed directly to the + `openai.fine_tuning.jobs.list` method. Common keys include: + 'after' (Optional[str]): Identifier for the last job from the previous pagination request. + 'limit' (Optional[int]): Number of fine-tuning jobs to retrieve. + Refer to the OpenAI API documentation for a complete list of parameters. - def list_jobs(self, *args, **kwargs): - jobs = self.client.fine_tuning.jobs.list() + Returns: + openai.pagination.SyncCursorPage[openai.types.fine_tuning.FineTuningJob]: + A paginated list of fine-tuning jobs. + """ + # List fine-tuning jobs using the OpenAI client. + # All provided keyword arguments are passed to the OpenAI API. + jobs = self.client.fine_tuning.jobs.list(**kwargs) return jobs - def list_job_events(self, *args, **kwargs): - events = self.client.fine_tuning.jobs.list_events(job_id=kwargs["job_id"]) + def list_job_events(self, **kwargs): + """Lists events for a specific finetuning job on the OpenAI platform. + + Args: + **kwargs: Arbitrary keyword arguments. Expected key: + 'job_id' (str): The ID of the fine-tuning job to retrieve events for. + This is passed as `fine_tuning_job_id` to the OpenAI API. + Other kwargs are passed directly to `openai.fine_tuning.jobs.list_events`. + Common keys include: + 'after' (Optional[str]): Identifier for the last event from the previous pagination request. + 'limit' (Optional[int]): Number of events to retrieve. + Refer to the OpenAI API documentation for a complete list of parameters. + + Returns: + openai.pagination.SyncCursorPage[openai.types.fine_tuning.FineTuningJobEvent]: + A paginated list of fine-tuning job events. + """ + # Retrieve the job_id from kwargs and remove it to prevent it from being passed again. + fine_tuning_job_id = kwargs.pop("job_id") + # List events for a specific fine-tuning job using the OpenAI client. + events = self.client.fine_tuning.jobs.list_events(fine_tuning_job_id=fine_tuning_job_id, **kwargs) return events - def get_job_details(self, *args, **kwargs): - job = self.client.fine_tuning.jobs.retrieve(job_id=kwargs["job_id"]) + def get_job_details(self, **kwargs): + """Retrieves details for a specific finetuning job on the OpenAI platform. + + Args: + **kwargs: Arbitrary keyword arguments. Expected key: + 'job_id' (str): The ID of the fine-tuning job to retrieve. + This is passed as `fine_tuning_job_id` to the OpenAI API. + Other kwargs are passed directly to `openai.fine_tuning.jobs.retrieve`. + Refer to the OpenAI API documentation for a complete list of parameters. + + Returns: + openai.types.fine_tuning.FineTuningJob: An object representing the fine-tuning job details. + """ + # Retrieve the job_id from kwargs and remove it to prevent it from being passed again. + fine_tuning_job_id = kwargs.pop("job_id") + # Retrieve details for a specific fine-tuning job using the OpenAI client. + job = self.client.fine_tuning.jobs.retrieve(fine_tuning_job_id=fine_tuning_job_id, **kwargs) return job - def cancel_job(self, *args, **kwargs): - self.client.fine_tuning.jobs.cancel(job_id=kwargs["job_id"]) + def cancel_job(self, **kwargs): + """Cancels a specific finetuning job on the OpenAI platform. + + Args: + **kwargs: Arbitrary keyword arguments. Expected key: + 'job_id' (str): The ID of the fine-tuning job to cancel. + This is passed as `fine_tuning_job_id` to the OpenAI API. + Other kwargs are passed directly to `openai.fine_tuning.jobs.cancel`. + Refer to the OpenAI API documentation for a complete list of parameters. + + Returns: + openai.types.fine_tuning.FineTuningJob: An object representing the cancelled fine-tuning job. + """ + # Retrieve the job_id from kwargs and remove it to prevent it from being passed again. + fine_tuning_job_id = kwargs.pop("job_id") + # Cancel a specific fine-tuning job using the OpenAI client. + job = self.client.fine_tuning.jobs.cancel(fine_tuning_job_id=fine_tuning_job_id, **kwargs) + return job diff --git a/src/bespokelabs/curator/finetune/types.py b/src/bespokelabs/curator/finetune/types.py index 69d9d057c..26cc6da79 100644 --- a/src/bespokelabs/curator/finetune/types.py +++ b/src/bespokelabs/curator/finetune/types.py @@ -1,12 +1,18 @@ +from typing import Optional + from pydantic import BaseModel class FinetuneMethod(BaseModel): + """Pydantic model for specifying the finetuning method and its hyperparameters.""" + type: str hyperparameters: dict class FinetuneRequest(BaseModel): + """Pydantic model for a finetuning job request.""" + job_name: str dataset_id: str model_name: str @@ -17,6 +23,8 @@ class FinetuneRequest(BaseModel): class FinetuneResponse(BaseModel): + """Pydantic model for a finetuning job response.""" + job_id: str job_name: str dataset_id: str @@ -25,3 +33,18 @@ class FinetuneResponse(BaseModel): suffix: str method: FinetuneMethod status: str + + +class FinetuneBackendParams(BaseModel): + """Pydantic model for backend parameters.""" + + base_url: Optional[str] = None + api_key: Optional[str] = None + model_name: Optional[str] = None + dataset_id: Optional[str] = None + method: Optional[FinetuneMethod] = None + seed: Optional[int] = None + suffix: Optional[str] = None + num_gpus: Optional[int] = None + hyperparameters: Optional[dict] = None + validation_file: Optional[str] = None diff --git a/src/bespokelabs/curator/models/__init__.py b/src/bespokelabs/curator/models/__init__.py index 41163f05c..7ef3a2dd1 100644 --- a/src/bespokelabs/curator/models/__init__.py +++ b/src/bespokelabs/curator/models/__init__.py @@ -1,12 +1,5 @@ """Models module for Curator.""" -import os -import json -import requests -import typing as t -from abc import ABC, abstractmethod -from ..log import logger - from .models import Models -__all__ = ["Models"] \ No newline at end of file +__all__ = ["Models"] diff --git a/src/bespokelabs/curator/models/base_backend.py b/src/bespokelabs/curator/models/base_backend.py index cc0fed270..46faa6487 100644 --- a/src/bespokelabs/curator/models/base_backend.py +++ b/src/bespokelabs/curator/models/base_backend.py @@ -1,6 +1,7 @@ import typing as t from abc import ABC, abstractmethod + class BaseModelsBackend(ABC): """Base interface for model operations.""" @@ -18,4 +19,3 @@ def deploy_model(self, model_id: str) -> bool: def undeploy_model(self, model_id: str) -> bool: """Undeploy a model to free up resources.""" pass - diff --git a/src/bespokelabs/curator/models/bespoke_backend.py b/src/bespokelabs/curator/models/bespoke_backend.py index 658bf9ba2..7ae53c8bc 100644 --- a/src/bespokelabs/curator/models/bespoke_backend.py +++ b/src/bespokelabs/curator/models/bespoke_backend.py @@ -1,15 +1,18 @@ import os -import requests import typing as t + +import requests + from ..log import logger from .base_backend import BaseModelsBackend + class BespokeModelsBackend(BaseModelsBackend): """Client for interacting with Bespoke models API.""" def __init__(self, base_url: str = None, api_key: str = None): """Initialize the Bespoke models client. - + Args: base_url: Base URL for the API api_key: API key for authentication (optional) @@ -20,15 +23,15 @@ def __init__(self, base_url: str = None, api_key: str = None): def list_models(self, job_id: str = None) -> t.List[dict]: """List available models or models related to a specific job. - + Args: job_id: Optional job ID to filter models by - + Returns: A list of model information dictionaries """ url = f"{self.base_url}/v0/models/{job_id}" - + try: response = requests.get(url, headers=self.headers) if response.status_code == 200: @@ -42,15 +45,15 @@ def list_models(self, job_id: str = None) -> t.List[dict]: def deploy_model(self, model_id: str) -> bool: """Deploy a model to make it available for inference. - + Args: model_id: ID of the model to deploy - + Returns: True if deployment was successful, False otherwise """ url = f"{self.base_url}/v0/models/{model_id}/deploy" - + try: response = requests.post(url, headers=self.headers) if response.status_code in [200, 201, 202]: @@ -64,15 +67,15 @@ def deploy_model(self, model_id: str) -> bool: def undeploy_model(self, model_id: str) -> bool: """Undeploy a model to free up resources. - + Args: model_id: ID of the model to undeploy - + Returns: True if undeployment was successful, False otherwise """ url = f"{self.base_url}/v0/models/{model_id}/undeploy" - + try: response = requests.post(url, headers=self.headers) if response.status_code in [200, 202, 204]: @@ -82,4 +85,4 @@ def undeploy_model(self, model_id: str) -> bool: return False except Exception as e: logger.error(f"Error undeploying model: {str(e)}") - return False \ No newline at end of file + return False diff --git a/src/bespokelabs/curator/models/models.py b/src/bespokelabs/curator/models/models.py index f5d6577b4..af5f60371 100644 --- a/src/bespokelabs/curator/models/models.py +++ b/src/bespokelabs/curator/models/models.py @@ -1,35 +1,34 @@ import typing as t + from .bespoke_backend import BespokeModelsBackend + class Models: """Factory for creating model clients based on the specified backend.""" - + def __init__(self, backend: str = "bespoke", backend_params: dict = None): """Initialize the Models factory. - + Args: backend: The backend provider to use ('bespoke' supported) backend_params: Configuration parameters for the backend """ self.backend = backend self.backend_params = backend_params or {} - + if backend == "bespoke": - self.client = BespokeModelsBackend( - base_url=self.backend_params.get("base_url"), - api_key=self.backend_params.get("api_key") - ) + self.client = BespokeModelsBackend(base_url=self.backend_params.get("base_url"), api_key=self.backend_params.get("api_key")) else: raise ValueError(f"Unsupported backend: {backend}") - + def list_models(self, job_id: str = None) -> t.List[dict]: """Delegate to the client's list_models method.""" return self.client.list_models(job_id=job_id) - + def deploy_model(self, model_id: str) -> bool: """Delegate to the client's deploy_model method.""" return self.client.deploy_model(model_id=model_id) - + def undeploy_model(self, model_id: str) -> bool: """Delegate to the client's undeploy_model method.""" return self.client.undeploy_model(model_id=model_id) diff --git a/src/bespokelabs/curator/request_processor/base_request_processor.py b/src/bespokelabs/curator/request_processor/base_request_processor.py index 1650eb576..c3c443fdc 100644 --- a/src/bespokelabs/curator/request_processor/base_request_processor.py +++ b/src/bespokelabs/curator/request_processor/base_request_processor.py @@ -136,7 +136,7 @@ def run( # if not self.check_structured_output_support(): # raise ValueError(f"Model {self.config.model} does not support structured output, response_format: {self.prompt_formatter.response_format}") generic_request_files = self.create_request_files(dataset) - + self.requests_to_responses(generic_request_files) return self.create_dataset_files(parse_func_hash) diff --git a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py index 7609cbc98..d95ee6a40 100644 --- a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py +++ b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py @@ -147,7 +147,7 @@ def check_structured_output_support(self): - Required for models that will use JSON schema responses """ return True - + class User(BaseModel): name: str age: int From 8192a6c77e50decaf32dbb44ed947543414094c7 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 6 May 2025 15:04:43 -0700 Subject: [PATCH 11/12] revert structured output changes --- .../curator/request_processor/base_request_processor.py | 6 +++--- .../online/litellm_online_request_processor.py | 1 - .../online/openai_online_request_processor.py | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/bespokelabs/curator/request_processor/base_request_processor.py b/src/bespokelabs/curator/request_processor/base_request_processor.py index 66f71608e..86bbf4393 100644 --- a/src/bespokelabs/curator/request_processor/base_request_processor.py +++ b/src/bespokelabs/curator/request_processor/base_request_processor.py @@ -134,9 +134,9 @@ def run( self.validate_config() self.prompt_formatter = prompt_formatter - # if self.prompt_formatter.response_format: - # if not self.check_structured_output_support(): - # raise ValueError(f"Model {self.config.model} does not support structured output, response_format: {self.prompt_formatter.response_format}") + if self.prompt_formatter.response_format: + if not self.check_structured_output_support(): + raise ValueError(f"Model {self.config.model} does not support structured output, response_format: {self.prompt_formatter.response_format}") generic_request_files = self.create_request_files(dataset) self.requests_to_responses(generic_request_files) diff --git a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py index d95ee6a40..faeda718d 100644 --- a/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py +++ b/src/bespokelabs/curator/request_processor/online/litellm_online_request_processor.py @@ -146,7 +146,6 @@ def check_structured_output_support(self): - Logs detailed information about support status - Required for models that will use JSON schema responses """ - return True class User(BaseModel): name: str diff --git a/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py b/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py index f77a6a2d1..7dfa809b2 100644 --- a/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py +++ b/src/bespokelabs/curator/request_processor/online/openai_online_request_processor.py @@ -232,7 +232,6 @@ def check_structured_output_support(self) -> bool: - gpt-4.1-mini latest - gpt-4.1-nano latest """ - return True model_name = self.config.model.lower() # Check gpt-4o-mini support. From 8bb881dfafef9da577e88e7f09c8a8c15e73d182 Mon Sep 17 00:00:00 2001 From: Shreyas Pimpalgaonkar Date: Tue, 6 May 2025 15:05:19 -0700 Subject: [PATCH 12/12] remove file --- test_kluster.ipynb | 114 --------------------------------------------- 1 file changed, 114 deletions(-) delete mode 100644 test_kluster.ipynb diff --git a/test_kluster.ipynb b/test_kluster.ipynb deleted file mode 100644 index e65b5c1cd..000000000 --- a/test_kluster.ipynb +++ /dev/null @@ -1,114 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "from bespokelabs import curator" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "finetuning_client = curator.Finetune(backend=\"bespoke\", backend_params={\"base_url\": \"http://localhost:8080\"})" - ] - }, - { - "cell_type": "code", - "execution_count": 25, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "bespoke-d2bf08bd92fa6567b251e825ee4182fba880c7b1f16215ccd5f509f86871a4ba\n", - "{'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'dataset_id': 'test', 'seed': 42, 'suffix': 'test', 'method': {'type': 'supervised', 'hyperparameters': {'learning_rate': 0.001, 'epochs': 1, 'batch_size': 16}}, 'job_name': 'test_job'}\n", - "{'job_id': '925fed8203c64f69a036ba2431e36409', 'status': 'queued', 'created_at': '2025-04-22T01:25:38.058546Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'learning_rate': 0.001, 'epochs': 1, 'batch_size': 16}}, 'seed': 42, 'suffix': 'test', 'user_id': '4a9259382afd4bea890ac57d9cce60ec', 'organization_id': '1345c8cca02a4859aabf8f9c1e8b707d', 'job_name': 'test_job', 'dataset_id': 'test'}\n" - ] - } - ], - "source": [ - "finetuning_client.create_job(\n", - " model_name=\"unsloth/Llama-3-8B-Instruct\",\n", - " job_name=\"test_job\",\n", - " dataset_id=\"test\",\n", - " seed=42,\n", - " suffix=\"test\",\n", - " method={\n", - " \"type\": \"supervised\",\n", - " \"hyperparameters\": {\n", - " \"learning_rate\": 0.001,\n", - " \"epochs\": 1,\n", - " \"batch_size\": 16,\n", - " },\n", - " },\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[{'job_id': 'c7eeb19bf13e4ca38e520d9e868f84fc', 'status': 'queued', 'created_at': '2025-04-18T01:47:32.515836Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '6096228b-c45f-4486-af75-3026d26e2155', 'organization_id': 'a61334f1-7adf-4c0a-bafa-2a99e8009c2a', 'job_name': 'test_job', 'dataset_id': 'test'}, {'job_id': '159abd326a894ab9bd9b5a84a26e6747', 'status': 'queued', 'created_at': '2025-04-18T01:48:45.360006Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '61c25770-ba5d-49e0-b867-da46a7dba887', 'organization_id': '0424be00-87c2-4555-824e-529557626d45', 'job_name': 'test_job', 'dataset_id': 'test'}, {'job_id': '86334be4ced84056bdb202445ffc2d58', 'status': 'queued', 'created_at': '2025-04-18T02:00:17.035462Z', 'model_name': 'meta-llama/Meta-Llama-3-8B-Instruct', 'method': {'type': 'supervised', 'hyperparameters': {'epochs': 1, 'batch_size': 16, 'learning_rate': 0.001}}, 'seed': 42, 'suffix': 'test', 'user_id': '939ff2c7-697b-43c9-aafe-3210901b288c', 'organization_id': '60ebb12e-f720-4e51-a3e1-a712117acd4d', 'job_name': 'test_job', 'dataset_id': 'test'}]\n" - ] - } - ], - "source": [ - "print(finetuning_client.list_jobs())" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "metadata": { - "kernelspec": { - "display_name": "bespokelabs-curator-Ky8xEGex-py3.10", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.16" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -}