From c4b49b98f278bf9581722423fd7cc9dd8d3af87e Mon Sep 17 00:00:00 2001 From: Chengxi Li <114854555+Hecate0821@users.noreply.github.com> Date: Thu, 21 May 2026 21:17:16 -0700 Subject: [PATCH 1/2] test(public-repos): exercise promotion and harden workflow (#26573) --- .github/workflows/ci.yml | 10 +- SECURITY.md | 27 -- src/fireworks/training/sdk/client.py | 298 +++++++++++++++++- .../training/sdk/tests/test_client.py | 271 +++++++++++++++- 4 files changed, 557 insertions(+), 49 deletions(-) delete mode 100644 SECURITY.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bf6dc9a6..ed0e5b3e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,6 +30,8 @@ jobs: - name: Install dependencies run: rye sync --all-features + env: + UV_HTTP_TIMEOUT: '120' - name: Run lints run: ./scripts/lint @@ -47,7 +49,13 @@ jobs: python-version: '3.11' - name: Install package - run: pip install -e . 2>/dev/null || pip install -e .[training] + # Smoke import only exercises the base package (`import fireworks`), + # so install without extras. The previous `pip install -e . 2>/dev/null + # || pip install -e .[training]` chain swallowed real install errors + # and silently chose between two install modes -- exactly the + # silent-fallback pattern the rest of this hotfix tightens. Pinned to + # plain install for predictable, fast CI. + run: pip install -e . - name: Smoke import run: python -c "import fireworks; print('fireworks', getattr(fireworks, '__version__', ''))" diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 325f0b93..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,27 +0,0 @@ -# Security Policy - -## Reporting Security Issues - -This SDK is generated by [Stainless Software Inc](http://stainless.com). Stainless takes security seriously, and encourages you to report any security vulnerability promptly so that appropriate action can be taken. - -To report a security issue, please contact the Stainless team at security@stainless.com. - -## Responsible Disclosure - -We appreciate the efforts of security researchers and individuals who help us maintain the security of -SDKs we generate. If you believe you have found a security vulnerability, please adhere to responsible -disclosure practices by allowing us a reasonable amount of time to investigate and address the issue -before making any information public. - -## Reporting Non-SDK Related Security Issues - -If you encounter security issues that are not directly related to SDKs but pertain to the services -or products provided by Fireworks, please follow the respective company's security reporting guidelines. - -### Fireworks Terms and Policies - -Please contact dhuang@fireworks.ai for any questions or concerns regarding the security of our services. - ---- - -Thank you for helping us keep the SDKs and systems they interact with secure. diff --git a/src/fireworks/training/sdk/client.py b/src/fireworks/training/sdk/client.py index d081bd86..7f8057b7 100644 --- a/src/fireworks/training/sdk/client.py +++ b/src/fireworks/training/sdk/client.py @@ -19,17 +19,20 @@ import uuid import logging from enum import Enum -from typing import Literal, TypeVar, Callable, Optional +from typing import Any, Literal, TypeVar, Callable, Optional from dataclasses import dataclass from tinker import types from pydantic import BaseModel -from tinker.lib.api_future_impl import _APIFuture +from tinker.lib.api_future_impl import _APIFuture, _CombinedAPIFuture from tinker.lib.queue_state_logger import QueueStateLogger from tinker.lib.client_connection_pool_type import ClientConnectionPoolType from tinker.lib.public_interfaces.api_future import APIFuture from tinker.lib.public_interfaces.service_client import ServiceClient -from tinker.lib.public_interfaces.training_client import TrainingClient +from tinker.lib.public_interfaces.training_client import ( + TrainingClient, + combine_fwd_bwd_output_results, +) class LoadAdapterResponse(BaseModel): @@ -41,6 +44,7 @@ class LoadAdapterResponse(BaseModel): model_config = {"protected_namespaces": ()} + logger = logging.getLogger(__name__) T = TypeVar("T") @@ -86,8 +90,12 @@ def make_cross_job_checkpoint_ref(*, source_job_id: str, checkpoint_name: str) - raise ValueError("source_job_id cannot be empty") if not normalized_checkpoint_name: raise ValueError("checkpoint_name cannot be empty") - if normalized_checkpoint_name.startswith("gs://") or normalized_checkpoint_name.startswith("/"): - raise ValueError("checkpoint_name must be a logical checkpoint name, not a full path") + if normalized_checkpoint_name.startswith( + "gs://" + ) or normalized_checkpoint_name.startswith("/"): + raise ValueError( + "checkpoint_name must be a logical checkpoint name, not a full path" + ) return f"{CROSS_JOB_CHECKPOINT_REF_PREFIX}{normalized_source_job_id}/{normalized_checkpoint_name}" @@ -163,6 +171,42 @@ def _add_cross_entropy_response_tokens( return output +def _dump_tinker_model(obj: Any) -> Any: + if hasattr(obj, "model_dump"): + return obj.model_dump(exclude_unset=True, mode="json") + if hasattr(obj, "dict"): + return obj.dict(exclude_unset=True) + return obj + + +def _text_token_count(datum: types.Datum) -> int: + raw_datum = _dump_tinker_model(datum) + model_input = raw_datum.get("model_input", {}) + return sum( + len(chunk.get("tokens", [])) + for chunk in model_input.get("chunks", []) + if chunk.get("type", "encoded_text") == "encoded_text" + ) + + +def _pool_embedding_tensor( + embedding, + datum: types.Datum, + pooling: Literal["mean", "last"], +): + if embedding.ndim <= 1: + return embedding + token_count = _text_token_count(datum) + if token_count <= 0: + raise ValueError("Cannot pool embedding from an empty text sequence") + token_embeddings = embedding[:token_count] + if pooling == "mean": + return token_embeddings.mean(dim=0) + if pooling == "last": + return token_embeddings[-1] + raise ValueError(f"Unsupported pooling={pooling!r}; expected 'mean' or 'last'") + + # -- SaveSamplerResult --------------------------------------------------------- @@ -209,7 +253,12 @@ class FiretitanTrainingClient(TrainingClient): tinker.TrainingClient. """ - def __init__(self, holder, model_seq_id: int, model_id): + def __init__( + self, + holder, + model_seq_id: int, + model_id, + ): super().__init__(holder=holder, model_seq_id=model_seq_id, model_id=model_id) # Track checkpoint names to detect reuse within a session. # Sampler and state names are tracked separately because the same name @@ -260,7 +309,9 @@ def optim_step( """ extra_body: dict = {} if grad_accumulation_normalization is not None: - extra_body["grad_accumulation_normalization"] = grad_accumulation_normalization.value + extra_body["grad_accumulation_normalization"] = ( + grad_accumulation_normalization.value + ) request_id = self._get_request_id() async def _optim_step_async(): @@ -306,6 +357,231 @@ def forward_backward( lambda output: _add_cross_entropy_response_tokens(output, data=data), ) + async def _send_single_forward_embedding_request( + self, + request_id: int, + data: list[types.Datum], + pooling: Literal["mean", "last"], + ): + request = types.ForwardRequest( + forward_input=types.ForwardBackwardInput( + data=data, + loss_fn="cross_entropy", + loss_fn_config=None, + ), + model_id=self._guaranteed_model_id(), + seq_id=request_id + 1, + ) + extra_body = { + "forward_input": { + "data": [_dump_tinker_model(datum) for datum in data], + "loss_fn": "cross_entropy", + "loss_fn_config": {"output": "embedding", "pooling": pooling}, + } + } + with self.holder.aclient(ClientConnectionPoolType.TRAIN) as client: + return await client.training.forward( + request=request, + extra_body=extra_body, + ) + + async def _send_single_forward_backward_embedding_request( + self, + request_id: int, + data: list[types.Datum], + pooling: Literal["mean", "last"], + ): + request = types.ForwardBackwardRequest( + forward_backward_input=types.ForwardBackwardInput( + data=data, + loss_fn="cross_entropy", + loss_fn_config=None, + ), + model_id=self._guaranteed_model_id(), + seq_id=request_id + 1, + ) + extra_body = { + "forward_backward_input": { + "data": [_dump_tinker_model(datum) for datum in data], + "loss_fn": "cross_entropy", + "loss_fn_config": {"output": "embedding", "pooling": pooling}, + } + } + with self.holder.aclient(ClientConnectionPoolType.TRAIN) as client: + return await client.training.forward_backward( + request=request, + extra_body=extra_body, + ) + + async def _forward_embedding_async( + self, + data: list[types.Datum], + pooling: Literal["mean", "last"], + ) -> APIFuture[types.ForwardBackwardOutput]: + requests = self._chunked_requests(data) + futures = [] + start_time = time.time() + for request_id, chunk in requests: + async with self._take_turn(request_id): + untyped_future = await self.holder.execute_with_retries( + self._send_single_forward_embedding_request, + request_id, + chunk, + pooling, + ) + futures.append( + _APIFuture( + types.ForwardBackwardOutput, + self.holder, + untyped_future, + request_start_time=start_time, + request_type="Forward", + queue_state_observer=self._queue_state_logger, + ) + ) + return _CombinedAPIFuture(futures, combine_fwd_bwd_output_results, self.holder) + + async def _forward_backward_embedding_async( + self, + data: list[types.Datum], + pooling: Literal["mean", "last"], + ) -> APIFuture[types.ForwardBackwardOutput]: + requests = self._chunked_requests(data) + futures = [] + start_time = time.time() + for request_id, chunk in requests: + async with self._take_turn(request_id): + untyped_future = await self.holder.execute_with_retries( + self._send_single_forward_backward_embedding_request, + request_id, + chunk, + pooling, + ) + futures.append( + _APIFuture( + types.ForwardBackwardOutput, + self.holder, + untyped_future, + request_start_time=start_time, + request_type="ForwardBackward", + queue_state_observer=self._queue_state_logger, + ) + ) + return _CombinedAPIFuture(futures, combine_fwd_bwd_output_results, self.holder) + + async def forward_backward_custom_async( + self, + data: list[types.Datum], + loss_fn: Callable, + *, + loss_type_input: Literal["logprobs"] = "logprobs", + output: Literal["logprobs", "embedding"] = "logprobs", + pooling: Literal["mean", "last"] = "mean", + ) -> APIFuture[types.ForwardBackwardOutput]: + if output == "logprobs": + return await super().forward_backward_custom_async( + data, + loss_fn, + loss_type_input=loss_type_input, + ) + if output != "embedding": + raise ValueError( + f"Unsupported output={output!r}; expected 'logprobs' or 'embedding'" + ) + if loss_type_input != "logprobs": + raise ValueError( + "Set output='embedding' instead of loss_type_input for embedding custom loss." + ) + if pooling not in ("mean", "last"): + raise ValueError( + f"Unsupported pooling={pooling!r}; expected 'mean' or 'last'" + ) + + try: + import torch + except ImportError as err: + raise ImportError( + "PyTorch is not installed. Cannot run custom forward_backward." + ) from err + + forward_future = await self._forward_embedding_async(data, pooling) + forward_result = await forward_future.result_async() + + embeddings = [] + for datum, out in zip(data, forward_result.loss_fn_outputs, strict=True): + if "embedding" not in out: + raise ValueError("Embedding response missing 'embedding' tensor") + embedding_data = out["embedding"] + embedding = torch.tensor(embedding_data.data, dtype=torch.float32) + if embedding_data.shape is not None: + embedding = embedding.reshape(embedding_data.shape) + embedding = _pool_embedding_tensor(embedding, datum, pooling) + embeddings.append(embedding.clone().detach().requires_grad_(True)) + + loss, metrics = loss_fn(data, embeddings) + loss.backward() + + backward_data = [] + for datum, embedding in zip(data, embeddings, strict=True): + if embedding.grad is None: + raise ValueError("No gradient computed for embedding tensor") + grad = ( + embedding.grad.detach() + .to(dtype=torch.float32) + .reshape(-1) + .cpu() + .tolist() + ) + backward_data.append( + types.Datum( + model_input=datum.model_input, + loss_fn_inputs={ + "embedding_grads": types.TensorData( + data=grad, + dtype="float32", + shape=list(embedding.grad.shape), + ) + }, + ) + ) + + backward_future = await self._forward_backward_embedding_async( + backward_data, pooling + ) + + def add_custom_metrics( + output_value: types.ForwardBackwardOutput, + ) -> types.ForwardBackwardOutput: + output_value.metrics.update(metrics) + return output_value + + return _MappedAPIFuture(backward_future, add_custom_metrics) + + def forward_backward_custom( + self, + data: list[types.Datum], + loss_fn: Callable, + *, + loss_type_input: Literal["logprobs"] = "logprobs", + output: Literal["logprobs", "embedding"] = "logprobs", + pooling: Literal["mean", "last"] = "mean", + ) -> APIFuture[types.ForwardBackwardOutput]: + if output == "logprobs": + return super().forward_backward_custom( + data, + loss_fn, + loss_type_input=loss_type_input, + ) + return self.holder.run_coroutine_threadsafe( + self.forward_backward_custom_async( + data, + loss_fn, + loss_type_input=loss_type_input, + output=output, + pooling=pooling, + ) + ).result() + def list_checkpoints(self) -> list[str]: """List available DCP checkpoints from the trainer. @@ -659,7 +935,9 @@ async def _create(): future, request_start_time=start, request_type="CreateModel", - queue_state_observer=QueueStateLogger(base_model, "Base model creation"), + queue_state_observer=QueueStateLogger( + base_model, "Base model creation" + ), ).result_async() return resp.model_id @@ -678,4 +956,6 @@ def create_sampling_client( base_model=None, retry_config=None, ): - raise NotImplementedError("FiretitanServiceClient.create_sampling_client() is not supported") + raise NotImplementedError( + "FiretitanServiceClient.create_sampling_client() is not supported" + ) diff --git a/src/fireworks/training/sdk/tests/test_client.py b/src/fireworks/training/sdk/tests/test_client.py index 5dde414e..e536bfec 100644 --- a/src/fireworks/training/sdk/tests/test_client.py +++ b/src/fireworks/training/sdk/tests/test_client.py @@ -2,9 +2,11 @@ from __future__ import annotations +import asyncio import logging from unittest.mock import MagicMock, patch +import torch import pytest from tinker import types @@ -40,7 +42,9 @@ def test_unique_across_calls(self): class TestQualifySnapshotName: def test_basic(self): - assert qualify_snapshot_name("a1b2c3d4", "step-0-base") == "step-0-base-a1b2c3d4" + assert ( + qualify_snapshot_name("a1b2c3d4", "step-0-base") == "step-0-base-a1b2c3d4" + ) def test_separator_is_dash(self): result = qualify_snapshot_name("deadbeef", "ckpt") @@ -64,14 +68,18 @@ def _make_client(self): def test_first_use_no_warning(self, caplog): client = self._make_client() with caplog.at_level(logging.WARNING): - client._warn_if_name_reused("step-0", client._saved_sampler_names, "Sampler") + client._warn_if_name_reused( + "step-0", client._saved_sampler_names, "Sampler" + ) assert "already used" not in caplog.text def test_duplicate_warns(self, caplog): client = self._make_client() client._saved_sampler_names.add("step-0") with caplog.at_level(logging.WARNING): - client._warn_if_name_reused("step-0", client._saved_sampler_names, "Sampler") + client._warn_if_name_reused( + "step-0", client._saved_sampler_names, "Sampler" + ) assert "already used" in caplog.text @@ -153,8 +161,12 @@ def _make_client(self): client.session_id = "test1234" return client - @patch("tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward") - def test_cross_entropy_adds_response_tokens_from_weights(self, mock_forward_backward): + @patch( + "tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward" + ) + def test_cross_entropy_adds_response_tokens_from_weights( + self, mock_forward_backward + ): client = self._make_client() future = MagicMock() future.result.return_value = types.ForwardBackwardOutput( @@ -165,8 +177,12 @@ def test_cross_entropy_adds_response_tokens_from_weights(self, mock_forward_back mock_forward_backward.return_value = future datum = MagicMock() datum.loss_fn_inputs = { - "weights": types.TensorData(data=[0.0, 1.0, 1.0, 0.0], dtype="float32", shape=[4]), - "target_tokens": types.TensorData(data=[10, 11, 12, 13], dtype="int64", shape=[4]), + "weights": types.TensorData( + data=[0.0, 1.0, 1.0, 0.0], dtype="float32", shape=[4] + ), + "target_tokens": types.TensorData( + data=[10, 11, 12, 13], dtype="int64", shape=[4] + ), } result = client.forward_backward([datum], "cross_entropy").result() @@ -174,8 +190,12 @@ def test_cross_entropy_adds_response_tokens_from_weights(self, mock_forward_back assert result.metrics["response_tokens"] == 2.0 mock_forward_backward.assert_called_once_with([datum], "cross_entropy", None) - @patch("tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward") - def test_cross_entropy_falls_back_to_target_token_length(self, mock_forward_backward): + @patch( + "tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward" + ) + def test_cross_entropy_falls_back_to_target_token_length( + self, mock_forward_backward + ): client = self._make_client() future = MagicMock() future.result.return_value = types.ForwardBackwardOutput( @@ -186,14 +206,18 @@ def test_cross_entropy_falls_back_to_target_token_length(self, mock_forward_back mock_forward_backward.return_value = future datum = MagicMock() datum.loss_fn_inputs = { - "target_tokens": types.TensorData(data=[10, 11, 12], dtype="int64", shape=[3]), + "target_tokens": types.TensorData( + data=[10, 11, 12], dtype="int64", shape=[3] + ), } result = client.forward_backward([datum], "cross_entropy").result() assert result.metrics["response_tokens"] == 3.0 - @patch("tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward") + @patch( + "tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward" + ) def test_existing_response_tokens_metric_is_preserved(self, mock_forward_backward): client = self._make_client() future = MagicMock() @@ -211,6 +235,227 @@ def test_existing_response_tokens_metric_is_preserved(self, mock_forward_backwar assert result.metrics["response_tokens"] == 7.0 +class TestForwardBackwardCustomEmbedding: + def _make_client(self): + client = FiretitanTrainingClient.__new__(FiretitanTrainingClient) + client._saved_sampler_names = set() + client._saved_state_names = set() + client.session_id = "test1234" + return client + + @patch( + "tinker.lib.public_interfaces.training_client.TrainingClient.forward_backward_custom" + ) + def test_logprob_output_delegates_to_upstream_tinker( + self, mock_forward_backward_custom + ): + client = self._make_client() + future = MagicMock() + mock_forward_backward_custom.return_value = future + + result = client.forward_backward_custom([], MagicMock()) + + assert result is future + mock_forward_backward_custom.assert_called_once() + + def test_embedding_output_calls_loss_and_sends_embedding_grads(self, monkeypatch): + client = self._make_client() + datum = types.Datum( + model_input=types.ModelInput.from_ints([1, 2]), + loss_fn_inputs={}, + ) + forward_output = types.ForwardBackwardOutput( + loss_fn_output_type="forward", + loss_fn_outputs=[ + { + "embedding": types.TensorData( + data=[1.0, 2.0], + dtype="float32", + shape=[2], + ) + } + ], + metrics={}, + ) + backward_output = types.ForwardBackwardOutput( + loss_fn_output_type="cross_entropy", + loss_fn_outputs=[], + metrics={"loss:sum": 0.0}, + ) + captured = {} + + class _ImmediateFuture: + def __init__(self, value): + self._value = value + + async def result_async(self, timeout=None): + return self._value + + def result(self, timeout=None): + return self._value + + async def fake_forward(data, pooling): + captured["forward_pooling"] = pooling + return _ImmediateFuture(forward_output) + + async def fake_backward(data, pooling): + captured["backward_pooling"] = pooling + captured["backward_data"] = data + return _ImmediateFuture(backward_output) + + monkeypatch.setattr(client, "_forward_embedding_async", fake_forward) + monkeypatch.setattr(client, "_forward_backward_embedding_async", fake_backward) + + def loss_fn(data, embeddings): + assert data == [datum] + return (embeddings[0] * torch.tensor([3.0, -1.0])).sum(), {"custom": 2.0} + + future = asyncio.run( + client.forward_backward_custom_async( + [datum], + loss_fn, + output="embedding", + pooling="last", + ) + ) + result = future.result() + + assert result.metrics["custom"] == 2.0 + assert captured["forward_pooling"] == "last" + assert captured["backward_pooling"] == "last" + grad_data = captured["backward_data"][0].loss_fn_inputs["embedding_grads"] + assert grad_data.data == [3.0, -1.0] + assert grad_data.shape == [2] + + def test_embedding_output_pools_sequence_hidden_states(self, monkeypatch): + client = self._make_client() + datum = types.Datum( + model_input=types.ModelInput.from_ints([1, 2]), + loss_fn_inputs={}, + ) + forward_output = types.ForwardBackwardOutput( + loss_fn_output_type="forward", + loss_fn_outputs=[ + { + "embedding": types.TensorData( + data=[1.0, 2.0, 3.0, 4.0, 100.0, 200.0], + dtype="float32", + shape=[3, 2], + ) + } + ], + metrics={}, + ) + backward_output = types.ForwardBackwardOutput( + loss_fn_output_type="cross_entropy", + loss_fn_outputs=[], + metrics={"loss:sum": 0.0}, + ) + captured = {} + + class _ImmediateFuture: + def __init__(self, value): + self._value = value + + async def result_async(self, timeout=None): + return self._value + + def result(self, timeout=None): + return self._value + + async def fake_forward(data, pooling): + return _ImmediateFuture(forward_output) + + async def fake_backward(data, pooling): + captured["backward_data"] = data + return _ImmediateFuture(backward_output) + + monkeypatch.setattr(client, "_forward_embedding_async", fake_forward) + monkeypatch.setattr(client, "_forward_backward_embedding_async", fake_backward) + + def loss_fn(data, embeddings): + assert embeddings[0].tolist() == [3.0, 4.0] + return (embeddings[0] * torch.tensor([5.0, -2.0])).sum(), {} + + future = asyncio.run( + client.forward_backward_custom_async( + [datum], + loss_fn, + output="embedding", + pooling="last", + ) + ) + future.result() + + grad_data = captured["backward_data"][0].loss_fn_inputs["embedding_grads"] + assert grad_data.data == [5.0, -2.0] + assert grad_data.shape == [2] + + def test_embedding_output_pools_shaped_sequence_hidden_states(self, monkeypatch): + client = self._make_client() + datum = types.Datum( + model_input=types.ModelInput.from_ints([1, 2]), + loss_fn_inputs={}, + ) + forward_output = types.ForwardBackwardOutput( + loss_fn_output_type="forward", + loss_fn_outputs=[ + { + "embedding": types.TensorData( + data=[1.0, 2.0, 3.0, 4.0, 100.0, 200.0], + dtype="float32", + shape=[3, 2], + ) + } + ], + metrics={}, + ) + backward_output = types.ForwardBackwardOutput( + loss_fn_output_type="cross_entropy", + loss_fn_outputs=[], + metrics={"loss:sum": 0.0}, + ) + captured = {} + + class _ImmediateFuture: + def __init__(self, value): + self._value = value + + async def result_async(self, timeout=None): + return self._value + + def result(self, timeout=None): + return self._value + + async def fake_forward(data, pooling): + return _ImmediateFuture(forward_output) + + async def fake_backward(data, pooling): + captured["backward_data"] = data + return _ImmediateFuture(backward_output) + + monkeypatch.setattr(client, "_forward_embedding_async", fake_forward) + monkeypatch.setattr(client, "_forward_backward_embedding_async", fake_backward) + + def loss_fn(data, embeddings): + assert embeddings[0].tolist() == [3.0, 4.0] + return (embeddings[0] * torch.tensor([7.0, -3.0])).sum(), {} + + future = asyncio.run( + client.forward_backward_custom_async( + [datum], + loss_fn, + output="embedding", + pooling="last", + ) + ) + future.result() + + grad_data = captured["backward_data"][0].loss_fn_inputs["embedding_grads"] + assert grad_data.data == [7.0, -3.0] + assert grad_data.shape == [2] + + # --------------------------------------------------------------------------- # FiretitanServiceClient.create_training_client — duplicate detection # --------------------------------------------------------------------------- @@ -236,7 +481,9 @@ def test_different_lora_rank_ok(self): svc.holder = MagicMock() svc.holder.get_session_id.return_value = 1 svc.holder.get_training_client_id.return_value = 1 - svc.holder.run_coroutine_threadsafe.return_value.result.return_value = "model-id" + svc.holder.run_coroutine_threadsafe.return_value.result.return_value = ( + "model-id" + ) # Should not raise — different lora_rank is a different config try: From 52582221b7b49cf72dd76e0a0cb72b9840a35542 Mon Sep 17 00:00:00 2001 From: Hecate0821 <114854555+Hecate0821@users.noreply.github.com> Date: Fri, 22 May 2026 04:19:04 +0000 Subject: [PATCH 2/2] release: 1.2.0-alpha.73 --- CHANGELOG.md | 7 +++++++ pyproject.toml | 2 +- src/fireworks/_version.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c7c9c918..52ea52ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 1.2.0-alpha.73 (2026-05-22) + +Full Changelog: [v1.2.0-alpha.72...v1.2.0-alpha.73](https://github.com/fw-ai-external/python-sdk/compare/v1.2.0-alpha.72...v1.2.0-alpha.73) + +### Chores +* autorelease single source of truth (#90) ([fc3f8c7](https://github.com/fw-ai-external/python-sdk/commit/fc3f8c72d7a72f40d95d32658488bea44e443d18)) + ## 1.2.0-alpha.72 (2026-05-21) Full Changelog: [v1.2.0-alpha.71...v1.2.0-alpha.72](https://github.com/fw-ai-external/python-sdk/compare/v1.2.0-alpha.71...v1.2.0-alpha.72) diff --git a/pyproject.toml b/pyproject.toml index d2215a0f..ea01178e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "fireworks-ai" -version = "1.2.0-alpha.72" +version = "1.2.0-alpha.73" description = "The official Python library for the fireworks API" dynamic = ["readme"] license = "Apache-2.0" diff --git a/src/fireworks/_version.py b/src/fireworks/_version.py index 3cc8870e..6eb492c7 100644 --- a/src/fireworks/_version.py +++ b/src/fireworks/_version.py @@ -1,4 +1,4 @@ # File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. __title__ = "fireworks" -__version__ = "1.2.0-alpha.72" # x-release-please-version +__version__ = "1.2.0-alpha.73" # x-release-please-version