From 1f7244955a6e022480b32a6f675508c19ff1e7c5 Mon Sep 17 00:00:00 2001 From: Viktor Valadi <42983197+viktorvaladi@users.noreply.github.com> Date: Mon, 8 Apr 2024 18:13:35 +0200 Subject: [PATCH] Feature/SK-707 | Flower client example (#537) * add flower client example * clarify readme * exclude dir floating imports * gh wf * new flower adapter * formatting * formatting * formatting... * Updated readme, added Dockerfile, added init file * Updated readme * point at stable * Language fixes * move adapter to fedn utils * add adapter in fedn utils * readme fix * Example updated to run natively in the venv * fix readme * python3.9 -> python3 in venv * Updated dependcies * add results for flower compat * Deps * change to fit new clientapp * Revert venv changes * Update run.sh * Code checks * Updated README, removed build.sh, instructions in readme instead * Polish readme * Fix * update gitignore * update gitignore * updater adapter * update adapter * linting * linting * linting * get_parameters and assertion added * linting * linting * readme typo fix --------- Co-authored-by: Andreas Hellander Co-authored-by: Andreas Hellander --- .github/workflows/code-checks.yaml | 1 + .vscode/settings.json | 1 + examples/flower-client/.dockerignore | 4 + examples/flower-client/.gitignore | 6 + examples/flower-client/Dockerfile | 10 ++ examples/flower-client/README.rst | 108 ++++++++++++++++++ examples/flower-client/bin/init_venv.sh | 10 ++ examples/flower-client/client/entrypoint | 108 ++++++++++++++++++ examples/flower-client/client/fedn.yaml | 5 + examples/flower-client/client/flwr_client.py | 42 +++++++ examples/flower-client/client/flwr_task.py | 95 +++++++++++++++ examples/flower-client/init_fedn.py | 8 ++ examples/flower-client/requirements.txt | 5 + examples/mnist-pytorch/API_Example.ipynb | 33 ++++-- fedn/fedn/utils/flowercompat/__init__.py | 1 + .../utils/flowercompat/client_app_adapter.py | 104 +++++++++++++++++ 16 files changed, 530 insertions(+), 11 deletions(-) create mode 100644 examples/flower-client/.dockerignore create mode 100644 examples/flower-client/.gitignore create mode 100644 examples/flower-client/Dockerfile create mode 100644 examples/flower-client/README.rst create mode 100755 examples/flower-client/bin/init_venv.sh create mode 100755 examples/flower-client/client/entrypoint create mode 100644 examples/flower-client/client/fedn.yaml create mode 100644 examples/flower-client/client/flwr_client.py create mode 100644 examples/flower-client/client/flwr_task.py create mode 100644 examples/flower-client/init_fedn.py create mode 100644 examples/flower-client/requirements.txt create mode 100644 fedn/fedn/utils/flowercompat/__init__.py create mode 100644 fedn/fedn/utils/flowercompat/client_app_adapter.py diff --git a/.github/workflows/code-checks.yaml b/.github/workflows/code-checks.yaml index 3b0f615f6..a39b2ab26 100644 --- a/.github/workflows/code-checks.yaml +++ b/.github/workflows/code-checks.yaml @@ -43,6 +43,7 @@ jobs: --exclude-dir='.mnist-pytorch' --exclude-dir='.mnist-keras' --exclude-dir='docs' + --exclude-dir='flower-client' --exclude='tests.py' '^[ \t]+(import|from) ' -I . diff --git a/.vscode/settings.json b/.vscode/settings.json index d4c2ea8ad..4f993daa0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,4 +5,5 @@ }, "python.linting.enabled": true, "python.linting.flake8Enabled": true, + "esbonio.sphinx.confDir": "", } \ No newline at end of file diff --git a/examples/flower-client/.dockerignore b/examples/flower-client/.dockerignore new file mode 100644 index 000000000..8ba9024ad --- /dev/null +++ b/examples/flower-client/.dockerignore @@ -0,0 +1,4 @@ +data +seed.npz +*.tgz +*.tar.gz \ No newline at end of file diff --git a/examples/flower-client/.gitignore b/examples/flower-client/.gitignore new file mode 100644 index 000000000..032fceb3f --- /dev/null +++ b/examples/flower-client/.gitignore @@ -0,0 +1,6 @@ +data +*.npz +*.tgz +*.tar.gz +.flower-client +client.yaml \ No newline at end of file diff --git a/examples/flower-client/Dockerfile b/examples/flower-client/Dockerfile new file mode 100644 index 000000000..a8a191403 --- /dev/null +++ b/examples/flower-client/Dockerfile @@ -0,0 +1,10 @@ +FROM ghcr.io/scaleoutsystems/fedn/fedn:0.8.0 + +COPY requirements.txt /app/config/requirements.txt + +# Install requirements +RUN python -m venv /venv \ + && /venv/bin/pip install --upgrade pip \ + && /venv/bin/pip install --no-cache-dir -r /app/config/requirements.txt \ + # Clean up + && rm -r /app/config/requirements.txt \ No newline at end of file diff --git a/examples/flower-client/README.rst b/examples/flower-client/README.rst new file mode 100644 index 000000000..7b9e30754 --- /dev/null +++ b/examples/flower-client/README.rst @@ -0,0 +1,108 @@ +Using Flower ClientApps in FEDn +============================ + +This example demonstrates how to run a Flower 'ClientApp' on FEDn. + +The FEDn compute package 'client/entrypoint' +uses a built-in Flower compatibiltiy adapter for convenient wrapping of the Flower client. +See `flwr_client.py` and `flwr_task.py` for the Flower client code (which is adapted from +https://github.com/adap/flower/tree/main/examples/app-pytorch). + + +Running the example +------------------- + +See `https://fedn.readthedocs.io/en/stable/quickstart.html` for a general introduction to FEDn. +This example follows the same structure as the pytorch quickstart example. + +Build a virtual environment (note that you might need to install the 'venv' package): + +.. code-block:: + + bin/init_venv.sh + +Activate the virtual environment: + +.. code-block:: + + source .flower-client/bin/activate + +Make the compute package (to be uploaded to FEDn): + +.. code-block:: + + tar -czvf package.tgz client + +Create the seed model (to be uploaded to FEDn): +.. code-block:: + + python client/entrypoint init_seed + +Next, you will upload the compute package and seed model to +a FEDn network. Here you have two main options: using FEDn Studio +(recommended for new users), or a pseudo-local deployment +on your own machine. + +If you are using FEDn Studio (recommended): +----------------------------------------------------- + +Follow instructions here to register for Studio and start a project: https://fedn.readthedocs.io/en/stable/studio.html. + +In your Studio project: + +- From the "Sessions" menu, upload the compute package and seed model. +- Register a client and obtain the corresponding 'client.yaml'. + +On your local machine / client (in the same virtual environment), start the FEDn client: + +.. code-block:: + + CLIENT_NUMBER=0 FEDN_AUTH_SCHEME=Bearer fedn run client -in client.yaml --force-ssl --secure=True + + +Or, if you prefer to use Docker, build an image (this might take a long time): + +.. code-block:: + + docker build -t flower-client . + +Then start the client using Docker: + +.. code-block:: + + docker run \ + -v $PWD/client.yaml:/app/client.yaml \ + -e CLIENT_NUMBER=0 \ + -e FEDN_AUTH_SCHEME=Bearer \ + flower-client run client -in client.yaml --secure=True --force-ssl + + +If you are running FEDn in pseudo-local mode: +------------------------------------------------------------------ + +Deploy a FEDn network on local host (see `https://fedn.readthedocs.io/en/stable/quickstart.html`). + +Use the FEDn API Client to initalize FEDn with the compute package and seed model: + +.. code-block:: + + python init_fedn.py + +Create a file 'client.yaml' with the following content: + +.. code-block:: + + network_id: fedn-network + discover_host: api-server + discover_port: 8092 + name: myclient + +Then start the client (using Docker) + +.. code-block:: + + docker run \ + -v $PWD/client.yaml:/app/client.yaml \ + --network=fedn_default \ + -e CLIENT_NUMBER=0 \ + flower-client run client -in client.yaml diff --git a/examples/flower-client/bin/init_venv.sh b/examples/flower-client/bin/init_venv.sh new file mode 100755 index 000000000..5a0c16d15 --- /dev/null +++ b/examples/flower-client/bin/init_venv.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +# Init venv +python3 -m venv .flower-client + +# Pip deps +.flower-client/bin/pip install --upgrade pip +.flower-client/bin/pip install -e ../../fedn +.flower-client/bin/pip install -r requirements.txt diff --git a/examples/flower-client/client/entrypoint b/examples/flower-client/client/entrypoint new file mode 100755 index 000000000..79dad9eb4 --- /dev/null +++ b/examples/flower-client/client/entrypoint @@ -0,0 +1,108 @@ +#!/usr/bin/env python +import os + +import fire +from flwr_client import app + +from fedn.utils.flowercompat.client_app_adapter import FlwrClientAppAdapter +from fedn.utils.helpers.helpers import get_helper, save_metadata, save_metrics + +HELPER_MODULE = "numpyhelper" +helper = get_helper(HELPER_MODULE) + +flwr_adapter = FlwrClientAppAdapter(app) + + +def _get_node_id(): + """Get client number from environment variable.""" + + number = os.environ.get("CLIENT_NUMBER", "0") + return int(number) + + +def save_parameters(out_path, parameters_np): + """Save model paramters to file. + + :param model: The model to serialize. + :type model: torch.nn.Module + :param out_path: The path to save to. + :type out_path: str + """ + helper.save(parameters_np, out_path) + + +def init_seed(out_path="seed.npz"): + """Initialize seed model and save it to file. + + :param out_path: The path to save the seed model to. + :type out_path: str + """ + # This calls get_parameters in the flower client which needs to be implemented. + parameters_np = flwr_adapter.init_parameters(partition_id=_get_node_id()) + save_parameters(out_path, parameters_np) + + +def train(in_model_path, out_model_path): + """Complete a model update. + + Load model paramters from in_model_path (managed by the FEDn client), + perform a model update through the flower client, and write updated paramters + to out_model_path (picked up by the FEDn client). + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_model_path: The path to save the output model to. + :type out_model_path: str + """ + parameters_np = helper.load(in_model_path) + + # Train on flower client + params, num_examples = flwr_adapter.train( + parameters=parameters_np, partition_id=_get_node_id() + ) + + # Metadata needed for aggregation server side + metadata = { + # num_examples are mandatory + "num_examples": num_examples, + } + + # Save JSON metadata file (mandatory) + save_metadata(metadata, out_model_path) + + # Save model update (mandatory) + save_parameters(out_model_path, params) + + +def validate(in_model_path, out_json_path, data_path=None): + """Validate model on the clients test dataset. + + :param in_model_path: The path to the input model. + :type in_model_path: str + :param out_json_path: The path to save the output JSON to. + :type out_json_path: str + :param data_path: The path to the data file. + :type data_path: str + """ + parameters_np = helper.load(in_model_path) + + loss, accuracy = flwr_adapter.evaluate(parameters_np, partition_id=_get_node_id()) + + # JSON schema + report = { + "test_loss": loss, + "test_accuracy": accuracy, + } + print(f"Loss: {loss}, accuracy: {accuracy}") + # Save JSON + save_metrics(report, out_json_path) + + +if __name__ == "__main__": + fire.Fire( + { + "init_seed": init_seed, + "train": train, + "validate": validate, + } + ) diff --git a/examples/flower-client/client/fedn.yaml b/examples/flower-client/client/fedn.yaml new file mode 100644 index 000000000..e5d3b2166 --- /dev/null +++ b/examples/flower-client/client/fedn.yaml @@ -0,0 +1,5 @@ +entry_points: + train: + command: python entrypoint train $ENTRYPOINT_OPTS + validate: + command: python entrypoint validate $ENTRYPOINT_OPTS \ No newline at end of file diff --git a/examples/flower-client/client/flwr_client.py b/examples/flower-client/client/flwr_client.py new file mode 100644 index 000000000..b3920d940 --- /dev/null +++ b/examples/flower-client/client/flwr_client.py @@ -0,0 +1,42 @@ +"""Flower client code using the ClientApp abstraction. +Code adapted from https://github.com/adap/flower/tree/main/examples/app-pytorch. +""" + +from flwr.client import ClientApp, NumPyClient +from flwr_task import (DEVICE, Net, get_weights, load_data, set_weights, test, + train) + + +# Define FlowerClient and client_fn +class FlowerClient(NumPyClient): + def __init__(self, cid) -> None: + super().__init__() + print(f"STARTED CLIENT WITH CID {cid}") + self.net = Net().to(DEVICE) + self.trainloader, self.testloader = load_data( + partition_id=int(cid), num_clients=10 + ) + + def get_parameters(self, config): + return [val.cpu().numpy() for _, val in self.net.state_dict().items()] + + def fit(self, parameters, config): + set_weights(self.net, parameters) + train(self.net, self.trainloader, epochs=3) + return get_weights(self.net), len(self.trainloader.dataset), {} + + def evaluate(self, parameters, config): + set_weights(self.net, parameters) + loss, accuracy = test(self.net, self.testloader) + return loss, len(self.testloader.dataset), {"accuracy": accuracy} + + +def client_fn(cid: str): + """Create and return an instance of Flower `Client`.""" + return FlowerClient(cid).to_client() + + +# Flower ClientApp +app = ClientApp( + client_fn=client_fn, +) diff --git a/examples/flower-client/client/flwr_task.py b/examples/flower-client/client/flwr_task.py new file mode 100644 index 000000000..dea53d9cc --- /dev/null +++ b/examples/flower-client/client/flwr_task.py @@ -0,0 +1,95 @@ +"""Flower client code for helper functions. +Code adapted from https://github.com/adap/flower/tree/main/examples/app-pytorch. +""" + +from collections import OrderedDict + +import torch +import torch.nn as nn +import torch.nn.functional as F +from flwr_datasets import FederatedDataset +from torch.utils.data import DataLoader +from torchvision.transforms import Compose, Normalize, ToTensor +from tqdm import tqdm + +DEVICE = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") + + +class Net(nn.Module): + """Model (simple CNN adapted from 'PyTorch: A 60 Minute Blitz')""" + + def __init__(self) -> None: + super(Net, self).__init__() + self.conv1 = nn.Conv2d(3, 6, 5) + self.pool = nn.MaxPool2d(2, 2) + self.conv2 = nn.Conv2d(6, 16, 5) + self.fc1 = nn.Linear(16 * 5 * 5, 120) + self.fc2 = nn.Linear(120, 84) + self.fc3 = nn.Linear(84, 10) + + def forward(self, x: torch.Tensor) -> torch.Tensor: + x = self.pool(F.relu(self.conv1(x))) + x = self.pool(F.relu(self.conv2(x))) + x = x.view(-1, 16 * 5 * 5) + x = F.relu(self.fc1(x)) + x = F.relu(self.fc2(x)) + return self.fc3(x) + + +def load_data(partition_id, num_clients): + """Load partition CIFAR10 data.""" + fds = FederatedDataset(dataset="cifar10", partitioners={"train": num_clients}) + partition = fds.load_partition(partition_id) + # Divide data on each node: 80% train, 20% test + partition_train_test = partition.train_test_split(test_size=0.2) + pytorch_transforms = Compose( + [ToTensor(), Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))] + ) + + def apply_transforms(batch): + """Apply transforms to the partition from FederatedDataset.""" + batch["img"] = [pytorch_transforms(img) for img in batch["img"]] + return batch + + partition_train_test = partition_train_test.with_transform(apply_transforms) + trainloader = DataLoader(partition_train_test["train"], batch_size=32, shuffle=True) + testloader = DataLoader(partition_train_test["test"], batch_size=32) + return trainloader, testloader + + +def train(net, trainloader, epochs): + """Train the model on the training set.""" + criterion = torch.nn.CrossEntropyLoss() + optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9) + for _ in range(epochs): + for batch in tqdm(trainloader, "Training"): + images = batch["img"] + labels = batch["label"] + optimizer.zero_grad() + criterion(net(images.to(DEVICE)), labels.to(DEVICE)).backward() + optimizer.step() + + +def test(net, testloader): + """Validate the model on the test set.""" + criterion = torch.nn.CrossEntropyLoss() + correct, loss = 0, 0.0 + with torch.no_grad(): + for batch in tqdm(testloader, "Testing"): + images = batch["img"].to(DEVICE) + labels = batch["label"].to(DEVICE) + outputs = net(images) + loss += criterion(outputs, labels).item() + correct += (torch.max(outputs.data, 1)[1] == labels).sum().item() + accuracy = correct / len(testloader.dataset) + return loss, accuracy + + +def get_weights(net): + return [val.cpu().numpy() for _, val in net.state_dict().items()] + + +def set_weights(net, parameters): + params_dict = zip(net.state_dict().keys(), parameters) + state_dict = OrderedDict({k: torch.tensor(v) for k, v in params_dict}) + net.load_state_dict(state_dict, strict=True) diff --git a/examples/flower-client/init_fedn.py b/examples/flower-client/init_fedn.py new file mode 100644 index 000000000..23078fcd9 --- /dev/null +++ b/examples/flower-client/init_fedn.py @@ -0,0 +1,8 @@ +from fedn import APIClient + +DISCOVER_HOST = '127.0.0.1' +DISCOVER_PORT = 8092 + +client = APIClient(DISCOVER_HOST, DISCOVER_PORT) +client.set_package('package.tgz', 'numpyhelper') +client.set_initial_model('seed.npz') diff --git a/examples/flower-client/requirements.txt b/examples/flower-client/requirements.txt new file mode 100644 index 000000000..a363d216e --- /dev/null +++ b/examples/flower-client/requirements.txt @@ -0,0 +1,5 @@ +fire~=0.6.0 +flwr==1.8.0 +flwr-datasets[vision]==0.0.2 +torch==2.2.1 +torchvision==0.17.1 \ No newline at end of file diff --git a/examples/mnist-pytorch/API_Example.ipynb b/examples/mnist-pytorch/API_Example.ipynb index fabcb8c1a..f4d9e8ae3 100644 --- a/examples/mnist-pytorch/API_Example.ipynb +++ b/examples/mnist-pytorch/API_Example.ipynb @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 1, "id": "743dfe47", "metadata": {}, "outputs": [], @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 2, "id": "1061722d", "metadata": {}, "outputs": [], @@ -58,7 +58,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 3, "id": "5107f6f9", "metadata": {}, "outputs": [ @@ -66,7 +66,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "{'committed_at': 'Wed, 27 Mar 2024 22:00:56 GMT', 'id': '66049718d5d8d370bf266899', 'key': 'models', 'model': '8762bb36-ffcc-4591-ad62-2758c6284a5d', 'parent_model': None, 'session_id': None}\n" + "{'committed_at': 'Tue, 02 Apr 2024 07:03:32 GMT', 'id': '660badc47b37095194dbd57a', 'key': 'models', 'model': '26020dc3-d83c-4048-b3ef-4715935dcaa0', 'parent_model': None, 'session_id': None}\n" ] } ], @@ -87,7 +87,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 4, "id": "f0380d35", "metadata": {}, "outputs": [], @@ -119,10 +119,21 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 5, "id": "4e8044b7", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/andreash/miniforge3/lib/python3.10/site-packages/numpy/core/fromnumeric.py:3474: RuntimeWarning: Mean of empty slice.\n", + " return _methods._mean(a, axis=axis, dtype=dtype,\n", + "/Users/andreash/miniforge3/lib/python3.10/site-packages/numpy/core/_methods.py:189: RuntimeWarning: invalid value encountered in double_scalars\n", + " ret = ret.dtype.type(ret / rcount)\n" + ] + } + ], "source": [ "session_id = \"experiment1\"\n", "models = client.get_model_trail()\n", @@ -145,23 +156,23 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 6, "id": "42425c43", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "" + "" ] }, - "execution_count": 68, + "execution_count": 6, "metadata": {}, "output_type": "execute_result" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] diff --git a/fedn/fedn/utils/flowercompat/__init__.py b/fedn/fedn/utils/flowercompat/__init__.py new file mode 100644 index 000000000..c8ac5b5db --- /dev/null +++ b/fedn/fedn/utils/flowercompat/__init__.py @@ -0,0 +1 @@ +"""Flower to FEDn compatibility utils.""" diff --git a/fedn/fedn/utils/flowercompat/client_app_adapter.py b/fedn/fedn/utils/flowercompat/client_app_adapter.py new file mode 100644 index 000000000..09ce879d8 --- /dev/null +++ b/fedn/fedn/utils/flowercompat/client_app_adapter.py @@ -0,0 +1,104 @@ +from typing import Tuple + +from flwr.client import ClientApp +from flwr.common import (Context, EvaluateIns, FitIns, GetParametersIns, + Message, MessageType, MessageTypeLegacy, Metadata, + NDArrays, ndarrays_to_parameters, + parameters_to_ndarrays) +from flwr.common.recordset_compat import (evaluateins_to_recordset, + fitins_to_recordset, + getparametersins_to_recordset, + recordset_to_evaluateres, + recordset_to_fitres, + recordset_to_getparametersres) + + +class FlwrClientAppAdapter: + """Flwr ClientApp wrapper.""" + + def __init__(self, app: ClientApp) -> None: + self.app = app + + def init_parameters(self, partition_id: int): + # Construct a get_parameters message for the ClientApp + message, context = self._construct_message( + MessageTypeLegacy.GET_PARAMETERS, [], partition_id + ) + # Call client app with train message + client_return_message = self.app(message, context) + # return NDArrays of clients parameters + parameters = self._parse_get_parameters_message(client_return_message) + if len(parameters) == 0: + raise ValueError("The 'parameters' list is empty. Ensure your flower \ + client has implemented a get_parameters() function.") + return parameters + + def train(self, parameters: NDArrays, partition_id: int): + # Construct a train message for the ClientApp with given parameters + message, context = self._construct_message( + MessageType.TRAIN, parameters, partition_id + ) + # Call client app with train message + client_return_message = self.app(message, context) + # Parse return message + params, num_examples = self._parse_train_message(client_return_message) + return params, num_examples + + def evaluate(self, parameters: NDArrays, partition_id: int): + # Construct an evaluate message for the ClientApp with given parameters + message, context = self._construct_message( + MessageType.EVALUATE, parameters, partition_id + ) + # Call client app with evaluate message + client_return_message = self.app(message, context) + # Parse return message + loss, accuracy = self._parse_evaluate_message(client_return_message) + return loss, accuracy + + def _parse_get_parameters_message(self, message: Message) -> NDArrays: + get_parameters_res = recordset_to_getparametersres(message.content, keep_input=False) + return parameters_to_ndarrays(get_parameters_res.parameters) + + def _parse_train_message(self, message: Message) -> Tuple[NDArrays, int]: + fitres = recordset_to_fitres(message.content, keep_input=False) + params, num_examples = ( + parameters_to_ndarrays(fitres.parameters), + fitres.num_examples, + ) + return params, num_examples + + def _parse_evaluate_message(self, message: Message) -> Tuple[float, float]: + evaluateres = recordset_to_evaluateres(message.content) + return evaluateres.loss, evaluateres.metrics.get("accuracy", -1) + + def _construct_message( + self, + message_type: MessageType, + parameters: NDArrays, + partition_id: int, + ) -> Tuple[Message, Context]: + parameters = ndarrays_to_parameters(parameters) + if message_type == MessageType.TRAIN: + fit_ins: FitIns = FitIns(parameters=parameters, config={}) + recordset = fitins_to_recordset(fitins=fit_ins, keep_input=False) + if message_type == MessageType.EVALUATE: + ev_ins: EvaluateIns = EvaluateIns(parameters=parameters, config={}) + recordset = evaluateins_to_recordset(evaluateins=ev_ins, keep_input=False) + if message_type == MessageTypeLegacy.GET_PARAMETERS: + get_parameters_ins: GetParametersIns = GetParametersIns({}) + recordset = getparametersins_to_recordset(getparameters_ins=get_parameters_ins) + + metadata = Metadata( + run_id=0, + message_id="", + src_node_id=0, + dst_node_id=0, + reply_to_message="", + group_id="", + ttl=0.0, + message_type=message_type, + partition_id=partition_id, + ) + context = Context(recordset) + message = Message(metadata=metadata, content=recordset) + return message, context