From 7eefdbb58e310a040781415bd9e38633ada26f86 Mon Sep 17 00:00:00 2001 From: Harshith Arun Kumar Date: Mon, 15 Jun 2026 01:10:24 -0700 Subject: [PATCH 1/4] feat: add azure-ai-finetuning-sessions SDK Ports the reviewed azure-ai-finetuning-sessions package from azure-sdk-for-python-pr into the main SDK repo. --- eng/common/tsp-client/package-lock.json | 117 +- .../azure-ai-finetuning-sessions/CHANGELOG.md | 7 + sdk/ai/azure-ai-finetuning-sessions/LICENSE | 21 + .../azure-ai-finetuning-sessions/MANIFEST.in | 7 + sdk/ai/azure-ai-finetuning-sessions/README.md | 78 + .../_metadata.json | 3 + .../apiview-properties.json | 72 + .../azure/__init__.py | 1 + .../azure/ai/__init__.py | 1 + .../azure/ai/finetuning_sessions/__init__.py | 33 + .../azure/ai/finetuning_sessions/_client.py | 116 + .../ai/finetuning_sessions/_configuration.py | 79 + .../azure/ai/finetuning_sessions/_patch.py | 565 ++++ .../azure/ai/finetuning_sessions/_types.py | 11 + .../ai/finetuning_sessions/_utils/__init__.py | 6 + .../finetuning_sessions/_utils/model_base.py | 1441 ++++++++ .../_utils/serialization.py | 2041 +++++++++++ .../ai/finetuning_sessions/_validation.py | 66 + .../azure/ai/finetuning_sessions/_version.py | 9 + .../ai/finetuning_sessions/aio/__init__.py | 29 + .../ai/finetuning_sessions/aio/_client.py | 118 + .../finetuning_sessions/aio/_configuration.py | 61 + .../ai/finetuning_sessions/aio/_patch.py | 21 + .../aio/operations/__init__.py | 33 + .../aio/operations/_operations.py | 2614 +++++++++++++++ .../aio/operations/_patch.py | 21 + .../ai/finetuning_sessions/models/__init__.py | 110 + .../ai/finetuning_sessions/models/_enums.py | 97 + .../ai/finetuning_sessions/models/_models.py | 1342 ++++++++ .../ai/finetuning_sessions/models/_patch.py | 21 + .../operations/__init__.py | 33 + .../operations/_operations.py | 2977 +++++++++++++++++ .../finetuning_sessions/operations/_patch.py | 21 + .../azure/ai/finetuning_sessions/py.typed | 1 + ...netuning_sessions-1.0.0b1-py3-none-any.whl | Bin 0 -> 600768 bytes .../dev_requirements.txt | 4 + .../pyproject.toml | 61 + .../test_smoke.py | 155 + .../tests/conftest.py | 132 + .../tests/test_finetuning_session.py | 177 + .../tsp-location.yaml | 6 + 41 files changed, 12605 insertions(+), 103 deletions(-) create mode 100644 sdk/ai/azure-ai-finetuning-sessions/CHANGELOG.md create mode 100644 sdk/ai/azure-ai-finetuning-sessions/LICENSE create mode 100644 sdk/ai/azure-ai-finetuning-sessions/MANIFEST.in create mode 100644 sdk/ai/azure-ai-finetuning-sessions/README.md create mode 100644 sdk/ai/azure-ai-finetuning-sessions/_metadata.json create mode 100644 sdk/ai/azure-ai-finetuning-sessions/apiview-properties.json create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_client.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_configuration.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_types.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/model_base.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/serialization.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_validation.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_version.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_client.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_operations.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_patch.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/__init__.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_operations.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_patch.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/py.typed create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure_ai_finetuning_sessions-1.0.0b1-py3-none-any.whl create mode 100644 sdk/ai/azure-ai-finetuning-sessions/dev_requirements.txt create mode 100644 sdk/ai/azure-ai-finetuning-sessions/pyproject.toml create mode 100644 sdk/ai/azure-ai-finetuning-sessions/test_smoke.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/tests/conftest.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/tests/test_finetuning_session.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml diff --git a/eng/common/tsp-client/package-lock.json b/eng/common/tsp-client/package-lock.json index f0d729273161..d3a911a3bfe0 100644 --- a/eng/common/tsp-client/package-lock.json +++ b/eng/common/tsp-client/package-lock.json @@ -214,7 +214,6 @@ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", @@ -229,7 +228,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -239,7 +237,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.5.tgz", "integrity": "sha512-doc2sWgJpbFQ64UflSVd17ibMGDuxO1yKgOgLMwavzESnXjFWJqUeG8saYosqKpHp4kWiM5x1nXvEjbpx90gzw==", "license": "MIT", - "peer": true, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } @@ -249,7 +246,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.1.3.tgz", "integrity": "sha512-+G7I8CT+EHv/hasNfUl3P37DVoMoZfpA+2FXmM54dA8MxYle1YqucxbacxHalw1iAFSdKNEDTGNV7F+j1Ldqcg==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/core": "^11.1.8", @@ -273,7 +269,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.11.tgz", "integrity": "sha512-pTpHjg0iEIRMYV/7oCZUMf27/383E6Wyhfc/MY+AVQGEoUobffIYWOK9YLP2XFRGz/9i6WlTQh1CkFVIo2Y7XA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" @@ -295,7 +290,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.8.tgz", "integrity": "sha512-/u+yJk2pOKNDOh1ZgdUH2RQaRx6OOH4I0uwL95qPvTFTIL38YBsuSC4r1yXBB3Q6JvNqFFc202gk0Ew79rrcjA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/figures": "^2.0.5", @@ -322,7 +316,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.1.0.tgz", "integrity": "sha512-6wlkYl65Qfayy48gPCfU4D7li6KCAGN79mLXa/tYHZH99OfZ820yY+HA+DgE88r8YwwgeuY6PQgNqMeK6LuMmw==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/external-editor": "^3.0.0", @@ -345,7 +338,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.12.tgz", "integrity": "sha512-vOfrB33b7YIZfDauXS8vNNz2Z86FozTZLIt7e+7/dCaPJ1RXZsHCuI9TlcERzEUq57vkM+UdnBgxP0rFd23JYQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" @@ -367,7 +359,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-3.0.0.tgz", "integrity": "sha512-lDSwMgg+M5rq6JKBYaJwSX6T9e/HK2qqZ1oxmOwn4AQoJE5D+7TumsxLGC02PWS//rkIVqbZv3XA3ejsc9FYvg==", "license": "MIT", - "peer": true, "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" @@ -389,7 +380,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.5.tgz", "integrity": "sha512-NsSs4kzfm12lNetHwAn3GEuH317IzpwrMCbOuMIVytpjnJ90YYHNwdRgYGuKmVxwuIqSgqk3M5qqQt1cDk0tGQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" } @@ -399,7 +389,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.11.tgz", "integrity": "sha512-twUWidn4ocPO8qi6fRM7tNWt7W1FOnOZqQ+/+PsfLUacMR5rFLDPK9ql0nBPwxi0oELbo8T5NhRs8B2+qQEqFQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" @@ -421,7 +410,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.11.tgz", "integrity": "sha512-Vscmim9TCksQsfjPtka/JwPUcbLhqWYrgfPf1cHrCm24X/F2joFwnageD50yMKsaX14oNGOyKf/RNXAFkNjWpA==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" @@ -443,7 +431,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.11.tgz", "integrity": "sha512-9KZFeRaNHIcejtPb0wN4ddFc7EvobVoAFa049eS3LrDZFxI8O7xUXiITEOinBzkZFAIwY5V4yzQae/QfO9cbbg==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/core": "^11.1.8", @@ -466,7 +453,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.4.1.tgz", "integrity": "sha512-AH5xPQ997K7e0F0vulPlteIHke2awMkFi8F0dBemrDfmvtPmHJo82mdHbONC4F/t8d1NHwrbI5cGVI+RbLWdoQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/checkbox": "^5.1.3", "@inquirer/confirm": "^6.0.11", @@ -496,7 +482,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.7.tgz", "integrity": "sha512-AqRMiD9+uE1lskDPrdqHwrV/EUmxKEBLX44SR7uxK3vD2413AmVfE5EQaPeNzYf5Pq5SitHJDYUFVF0poIr09w==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/type": "^4.0.5" @@ -518,7 +503,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.7.tgz", "integrity": "sha512-1y7+0N65AWk5RdlXH/Kn13txf3IjIQ7OEfhCEkDTU+h5wKMLq8DUF3P6z+/kLSxDGDtQT1dRBWEUC3o/VvImsQ==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/core": "^11.1.8", "@inquirer/figures": "^2.0.5", @@ -541,7 +525,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.1.3.tgz", "integrity": "sha512-zYyqWgGQi3NhBcNq4Isc5rB3oEdQEh1Q/EcAnOW0FK4MpnXWkvSBYgA4cYrTM4A9UB573omouZbnL9JJ74Mq3A==", "license": "MIT", - "peer": true, "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/core": "^11.1.8", @@ -565,7 +548,6 @@ "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.5.tgz", "integrity": "sha512-aetVUNeKNc/VriqXlw1NRSW0zhMBB0W4bNbWRJgzRl/3d0QNDQFfk0GO5SDdtjMZVg6o8ZKEiadd7SCCzoOn5Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" }, @@ -583,7 +565,6 @@ "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", "license": "ISC", - "peer": true, "dependencies": { "minipass": "^7.0.4" }, @@ -611,7 +592,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "license": "MIT", - "peer": true, "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -625,7 +605,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "license": "MIT", - "peer": true, "engines": { "node": ">= 8" } @@ -635,7 +614,6 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "license": "MIT", - "peer": true, "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -664,7 +642,6 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -725,7 +702,6 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -738,7 +714,6 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -751,7 +726,6 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz", "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==", "license": "ISC", - "peer": true, "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", @@ -765,15 +739,13 @@ "version": "10.6.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@typespec/compiler/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "license": "MIT", - "peer": true, "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", @@ -791,7 +763,6 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^6.2.2" }, @@ -807,7 +778,6 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "license": "MIT", - "peer": true, "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", @@ -825,7 +795,6 @@ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.0.0.tgz", "integrity": "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg==", "license": "MIT", - "peer": true, "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", @@ -843,7 +812,6 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz", "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==", "license": "ISC", - "peer": true, "engines": { "node": "^20.19.0 || ^22.12.0 || >=23" } @@ -991,7 +959,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1032,7 +999,6 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", - "peer": true, "dependencies": { "fill-range": "^7.1.1" }, @@ -1056,22 +1022,19 @@ "version": "5.4.4", "resolved": "https://registry.npmjs.org/change-case/-/change-case-5.4.4.tgz", "integrity": "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/chardet": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/chownr": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==", "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=18" } @@ -1081,7 +1044,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "license": "ISC", - "peer": true, "engines": { "node": ">= 12" } @@ -1179,7 +1141,6 @@ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", "license": "MIT", - "peer": true, "dependencies": { "is-safe-filename": "^0.1.0" }, @@ -1203,15 +1164,13 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "license": "MIT", - "peer": true, "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -1227,15 +1186,13 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", "integrity": "sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-string-width": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-3.0.2.tgz", "integrity": "sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==", "license": "MIT", - "peer": true, "dependencies": { "fast-string-truncated-width": "^3.0.2" } @@ -1254,15 +1211,13 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fast-wrap-ansi": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", "integrity": "sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==", "license": "MIT", - "peer": true, "dependencies": { "fast-string-width": "^3.0.2" } @@ -1272,7 +1227,6 @@ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "license": "ISC", - "peer": true, "dependencies": { "reusify": "^1.0.4" } @@ -1282,7 +1236,6 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "license": "MIT", - "peer": true, "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1304,7 +1257,6 @@ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1317,7 +1269,6 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "license": "ISC", - "peer": true, "dependencies": { "is-glob": "^4.0.1" }, @@ -1330,7 +1281,6 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-16.1.1.tgz", "integrity": "sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==", "license": "MIT", - "peer": true, "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "fast-glob": "^3.3.3", @@ -1377,7 +1327,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1394,7 +1343,6 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 4" } @@ -1404,7 +1352,6 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1423,7 +1370,6 @@ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "license": "MIT", - "peer": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -1436,7 +1382,6 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.12.0" } @@ -1446,7 +1391,6 @@ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-4.0.0.tgz", "integrity": "sha512-lJJV/5dYS+RcL8uQdBDW9c9uWFLLBNRyFhnAKXw5tVqLlKZ4RMGZKv+YQ/IA3OhD+RpbJa1LLFM1FQPGyIXvOA==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1459,7 +1403,6 @@ "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -1472,7 +1415,6 @@ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1484,22 +1426,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 8" } @@ -1509,7 +1448,6 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "license": "MIT", - "peer": true, "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -1523,7 +1461,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=16 || 14 >=14.17" } @@ -1533,7 +1470,6 @@ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", "license": "MIT", - "peer": true, "dependencies": { "minipass": "^7.1.2" }, @@ -1552,7 +1488,6 @@ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", "license": "MIT", - "peer": true, "bin": { "mustache": "bin/mustache" } @@ -1562,7 +1497,6 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", "license": "ISC", - "peer": true, "engines": { "node": "^20.17.0 || >=22.9.0" } @@ -1571,15 +1505,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "license": "MIT", - "peer": true, "engines": { "node": ">=8.6" }, @@ -1592,7 +1524,6 @@ "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", "integrity": "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -1602,7 +1533,6 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", "license": "MIT", - "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -1640,8 +1570,7 @@ "url": "https://feross.org/support" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/require-directory": { "version": "2.1.1", @@ -1657,7 +1586,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -1667,7 +1595,6 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "license": "MIT", - "peer": true, "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -1692,7 +1619,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "queue-microtask": "^1.2.2" } @@ -1701,15 +1627,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "license": "ISC", - "peer": true, "bin": { "semver": "bin/semver.js" }, @@ -1722,7 +1646,6 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "license": "ISC", - "peer": true, "engines": { "node": ">=14" }, @@ -1752,7 +1675,6 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.16" }, @@ -1812,7 +1734,6 @@ "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.13.tgz", "integrity": "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==", "license": "BlueOak-1.0.0", - "peer": true, "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", @@ -1829,7 +1750,6 @@ "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.3.2.tgz", "integrity": "sha512-TzHthD/heRK947GNiSu3Y5gSPpeUDH34+LESnfsq8bqpFhsB79HFBX8+Z834IVX68P3EUyRPZK5bL/1fh437Eg==", "license": "MIT", - "peer": true, "dependencies": { "temporal-spec": "0.3.1" } @@ -1838,15 +1758,13 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.3.1.tgz", "integrity": "sha512-B4TUhezh9knfSIMwt7RVggApDRJZo73uZdj8AacL2mZ8RP5KtLianh2MXxL06GN9ESYiIsiuoLQhgVfwe55Yhw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "license": "MIT", - "peer": true, "dependencies": { "is-number": "^7.0.0" }, @@ -1865,7 +1783,6 @@ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.4.0.tgz", "integrity": "sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==", "license": "MIT", - "peer": true, "engines": { "node": ">=20" }, @@ -1878,7 +1795,6 @@ "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", "license": "MIT", - "peer": true, "engines": { "node": ">=14.0.0" } @@ -1888,7 +1804,6 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", "license": "MIT", - "peer": true, "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, @@ -1901,7 +1816,6 @@ "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", "license": "MIT", - "peer": true, "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" @@ -1911,15 +1825,13 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/vscode-languageserver-types": { "version": "3.17.5", "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/wrap-ansi": { "version": "7.0.0", @@ -1973,7 +1885,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==", "license": "BlueOak-1.0.0", - "peer": true, "engines": { "node": ">=18" } diff --git a/sdk/ai/azure-ai-finetuning-sessions/CHANGELOG.md b/sdk/ai/azure-ai-finetuning-sessions/CHANGELOG.md new file mode 100644 index 000000000000..b957b2575b48 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/CHANGELOG.md @@ -0,0 +1,7 @@ +# Release History + +## 1.0.0b1 (1970-01-01) + +### Other Changes + + - Initial version \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/LICENSE b/sdk/ai/azure-ai-finetuning-sessions/LICENSE new file mode 100644 index 000000000000..63447fd8bbbf --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/LICENSE @@ -0,0 +1,21 @@ +Copyright (c) Microsoft Corporation. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/MANIFEST.in b/sdk/ai/azure-ai-finetuning-sessions/MANIFEST.in new file mode 100644 index 000000000000..04c3af1c8fda --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/MANIFEST.in @@ -0,0 +1,7 @@ +include *.md +include LICENSE +include azure/ai/finetuning_sessions/py.typed +recursive-include tests *.py +recursive-include samples *.py *.md +include azure/__init__.py +include azure/ai/__init__.py diff --git a/sdk/ai/azure-ai-finetuning-sessions/README.md b/sdk/ai/azure-ai-finetuning-sessions/README.md new file mode 100644 index 000000000000..543764f269cf --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/README.md @@ -0,0 +1,78 @@ +# Azure Finetuning Sessions client library for Python + + +## Getting started + +### Install the package + +```bash +python -m pip install azure-ai-finetuning-sessions +``` + +#### Prequisites + +- Python 3.9 or later is required to use this package. +- You need an [Azure subscription][azure_sub] to use this package. +- An existing Azure Finetuning Sessions instance. + +#### Create with an Azure Active Directory Credential +To use an [Azure Active Directory (AAD) token credential][authenticate_with_token], +provide an instance of the desired credential type obtained from the +[azure-identity][azure_identity_credentials] library. + +To authenticate with AAD, you must first [pip][pip] install [`azure-identity`][azure_identity_pip] + +After setup, you can choose which type of [credential][azure_identity_credentials] from azure.identity to use. +As an example, [DefaultAzureCredential][default_azure_credential] can be used to authenticate the client: + +Set the values of the client ID, tenant ID, and client secret of the AAD application as environment variables: +`AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, `AZURE_CLIENT_SECRET` + +Use the returned token credential to authenticate the client: + +```python +>>> from azure.ai.finetuning_sessions import FineTuningSessionClient +>>> from azure.identity import DefaultAzureCredential +>>> client = FineTuningSessionClient(endpoint='', credential=DefaultAzureCredential()) +``` + +## Examples + +```python +>>> from azure.ai.finetuning_sessions import FineTuningSessionClient +>>> from azure.identity import DefaultAzureCredential +>>> from azure.core.exceptions import HttpResponseError + +>>> client = FineTuningSessionClient(endpoint='', credential=DefaultAzureCredential()) +>>> try: + + except HttpResponseError as e: + print('service responds error: {}'.format(e.response.json())) + +``` + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require +you to agree to a Contributor License Agreement (CLA) declaring that you have +the right to, and actually do, grant us the rights to use your contribution. +For details, visit https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether +you need to provide a CLA and decorate the PR appropriately (e.g., label, +comment). Simply follow the instructions provided by the bot. You will only +need to do this once across all repos using our CLA. + +This project has adopted the +[Microsoft Open Source Code of Conduct][code_of_conduct]. For more information, +see the Code of Conduct FAQ or contact opencode@microsoft.com with any +additional questions or comments. + + +[code_of_conduct]: https://opensource.microsoft.com/codeofconduct/ +[authenticate_with_token]: https://docs.microsoft.com/azure/cognitive-services/authentication?tabs=powershell#authenticate-with-an-authentication-token +[azure_identity_credentials]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#credentials +[azure_identity_pip]: https://pypi.org/project/azure-identity/ +[default_azure_credential]: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/identity/azure-identity#defaultazurecredential +[pip]: https://pypi.org/project/pip/ +[azure_sub]: https://azure.microsoft.com/free/ diff --git a/sdk/ai/azure-ai-finetuning-sessions/_metadata.json b/sdk/ai/azure-ai-finetuning-sessions/_metadata.json new file mode 100644 index 000000000000..49515fdbafdf --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/_metadata.json @@ -0,0 +1,3 @@ +{ + "apiVersions": {} +} \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/apiview-properties.json b/sdk/ai/azure-ai-finetuning-sessions/apiview-properties.json new file mode 100644 index 000000000000..ddec01ea26af --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/apiview-properties.json @@ -0,0 +1,72 @@ +{ + "CrossLanguagePackageId": "Azure.AI.FineTuning.Sessions", + "CrossLanguageDefinitionId": { + "azure.ai.finetuning_sessions.models.AdamParams": "Azure.AI.FineTuning.Sessions.AdamParams", + "azure.ai.finetuning_sessions.models.ApiError": "OpenAI.Error", + "azure.ai.finetuning_sessions.models.ApiErrorResponse": "Azure.AI.Projects.ApiErrorResponse", + "azure.ai.finetuning_sessions.models.Checkpoint": "Azure.AI.FineTuning.Sessions.FineTuningCheckpoint", + "azure.ai.finetuning_sessions.models.CheckpointInfo": "Azure.AI.FineTuning.Sessions.FineTuningCheckpointInfo", + "azure.ai.finetuning_sessions.models.CheckpointList": "Azure.AI.FineTuning.Sessions.FineTuningCheckpointList", + "azure.ai.finetuning_sessions.models.CreateSessionRequest": "Azure.AI.FineTuning.Sessions.CreateFineTuningSessionRequest", + "azure.ai.finetuning_sessions.models.Cursor": "Azure.AI.FineTuning.Sessions.FineTuningCursor", + "azure.ai.finetuning_sessions.models.Datum": "Azure.AI.FineTuning.Sessions.FineTuningDatum", + "azure.ai.finetuning_sessions.models.ForwardBackwardInput": "Azure.AI.FineTuning.Sessions.ForwardBackwardInput", + "azure.ai.finetuning_sessions.models.OperationResult": "Azure.AI.FineTuning.Sessions.FineTuningOperationResult", + "azure.ai.finetuning_sessions.models.ForwardBackwardOperationResult": "Azure.AI.FineTuning.Sessions.ForwardBackwardOperationResult", + "azure.ai.finetuning_sessions.models.ForwardBackwardRequest": "Azure.AI.FineTuning.Sessions.ForwardBackwardRequest", + "azure.ai.finetuning_sessions.models.HeartbeatResponse": "Azure.AI.FineTuning.Sessions.FineTuningHeartbeatResponse", + "azure.ai.finetuning_sessions.models.LoRAConfig": "Azure.AI.FineTuning.Sessions.LoRAConfig", + "azure.ai.finetuning_sessions.models.LossFnConfig": "Azure.AI.FineTuning.Sessions.LossFnConfig", + "azure.ai.finetuning_sessions.models.LossFnInputs": "Azure.AI.FineTuning.Sessions.LossFnInputs", + "azure.ai.finetuning_sessions.models.ModelInput": "Azure.AI.FineTuning.Sessions.FineTuningModelInput", + "azure.ai.finetuning_sessions.models.ModelInputChunk": "Azure.AI.FineTuning.Sessions.FineTuningModelInputChunk", + "azure.ai.finetuning_sessions.models.OptimStepOperationResult": "Azure.AI.FineTuning.Sessions.OptimStepOperationResult", + "azure.ai.finetuning_sessions.models.OptimStepRequest": "Azure.AI.FineTuning.Sessions.OptimStepRequest", + "azure.ai.finetuning_sessions.models.SampledSequence": "Azure.AI.FineTuning.Sessions.FineTuningSampledSequence", + "azure.ai.finetuning_sessions.models.SampleOperationResult": "Azure.AI.FineTuning.Sessions.FineTuningSampleOperationResult", + "azure.ai.finetuning_sessions.models.SampleRequest": "Azure.AI.FineTuning.Sessions.FineTuningSampleRequest", + "azure.ai.finetuning_sessions.models.SamplingParams": "Azure.AI.FineTuning.Sessions.FineTuningSamplingParams", + "azure.ai.finetuning_sessions.models.SaveCheckpointOperationResult": "Azure.AI.FineTuning.Sessions.SaveCheckpointOperationResult", + "azure.ai.finetuning_sessions.models.SaveCheckpointRequest": "Azure.AI.FineTuning.Sessions.SaveCheckpointRequest", + "azure.ai.finetuning_sessions.models.SaveSamplerWeightsOperationResult": "Azure.AI.FineTuning.Sessions.SaveSamplerWeightsOperationResult", + "azure.ai.finetuning_sessions.models.SaveSamplerWeightsRequest": "Azure.AI.FineTuning.Sessions.SaveSamplerWeightsRequest", + "azure.ai.finetuning_sessions.models.Session": "Azure.AI.FineTuning.Sessions.FineTuningSession", + "azure.ai.finetuning_sessions.models.SessionList": "Azure.AI.FineTuning.Sessions.FineTuningSessionList", + "azure.ai.finetuning_sessions.models.SessionModelData": "Azure.AI.FineTuning.Sessions.FineTuningSessionModelData", + "azure.ai.finetuning_sessions.models.SessionSummary": "Azure.AI.FineTuning.Sessions.FineTuningSessionSummary", + "azure.ai.finetuning_sessions.models.TensorData": "Azure.AI.FineTuning.Sessions.TensorData", + "azure.ai.finetuning_sessions.models.OperationType": "Azure.AI.FineTuning.Sessions.FineTuningOperationType", + "azure.ai.finetuning_sessions.models.OperationStatus": "Azure.AI.FineTuning.Sessions.FineTuningOperationStatus", + "azure.ai.finetuning_sessions.models.FoundryFeaturesOptInKeys": "Azure.AI.Projects.FoundryFeaturesOptInKeys", + "azure.ai.finetuning_sessions.models.SessionType": "Azure.AI.FineTuning.Sessions.FineTuningSessionType", + "azure.ai.finetuning_sessions.models.SessionStatus": "Azure.AI.FineTuning.Sessions.FineTuningSessionStatus", + "azure.ai.finetuning_sessions.models.LossFn": "Azure.AI.FineTuning.Sessions.FineTuningLossFn", + "azure.ai.finetuning_sessions.models.CheckpointType": "Azure.AI.FineTuning.Sessions.FineTuningCheckpointType", + "azure.ai.finetuning_sessions.operations.SessionsOperations.begin_create": "Azure.AI.FineTuning.Sessions.FineTuningSessions.create", + "azure.ai.finetuning_sessions.aio.operations.SessionsOperations.begin_create": "Azure.AI.FineTuning.Sessions.FineTuningSessions.create", + "azure.ai.finetuning_sessions.operations.SessionsOperations.list": "Azure.AI.FineTuning.Sessions.FineTuningSessions.list", + "azure.ai.finetuning_sessions.aio.operations.SessionsOperations.list": "Azure.AI.FineTuning.Sessions.FineTuningSessions.list", + "azure.ai.finetuning_sessions.operations.SessionsOperations.get": "Azure.AI.FineTuning.Sessions.FineTuningSessions.get", + "azure.ai.finetuning_sessions.aio.operations.SessionsOperations.get": "Azure.AI.FineTuning.Sessions.FineTuningSessions.get", + "azure.ai.finetuning_sessions.operations.SessionsOperations.begin_unload": "Azure.AI.FineTuning.Sessions.FineTuningSessions.unload", + "azure.ai.finetuning_sessions.aio.operations.SessionsOperations.begin_unload": "Azure.AI.FineTuning.Sessions.FineTuningSessions.unload", + "azure.ai.finetuning_sessions.operations.SessionsOperations.heartbeat": "Azure.AI.FineTuning.Sessions.FineTuningSessions.heartbeat", + "azure.ai.finetuning_sessions.aio.operations.SessionsOperations.heartbeat": "Azure.AI.FineTuning.Sessions.FineTuningSessions.heartbeat", + "azure.ai.finetuning_sessions.operations.TrainingOperations.begin_forward_backward": "Azure.AI.FineTuning.Sessions.Training.forwardBackward", + "azure.ai.finetuning_sessions.aio.operations.TrainingOperations.begin_forward_backward": "Azure.AI.FineTuning.Sessions.Training.forwardBackward", + "azure.ai.finetuning_sessions.operations.TrainingOperations.begin_optim_step": "Azure.AI.FineTuning.Sessions.Training.optimStep", + "azure.ai.finetuning_sessions.aio.operations.TrainingOperations.begin_optim_step": "Azure.AI.FineTuning.Sessions.Training.optimStep", + "azure.ai.finetuning_sessions.operations.CheckpointsOperations.begin_save": "Azure.AI.FineTuning.Sessions.Checkpoints.save", + "azure.ai.finetuning_sessions.aio.operations.CheckpointsOperations.begin_save": "Azure.AI.FineTuning.Sessions.Checkpoints.save", + "azure.ai.finetuning_sessions.operations.CheckpointsOperations.begin_save_sampler_weights": "Azure.AI.FineTuning.Sessions.Checkpoints.saveSamplerWeights", + "azure.ai.finetuning_sessions.aio.operations.CheckpointsOperations.begin_save_sampler_weights": "Azure.AI.FineTuning.Sessions.Checkpoints.saveSamplerWeights", + "azure.ai.finetuning_sessions.operations.CheckpointsOperations.list": "Azure.AI.FineTuning.Sessions.Checkpoints.list", + "azure.ai.finetuning_sessions.aio.operations.CheckpointsOperations.list": "Azure.AI.FineTuning.Sessions.Checkpoints.list", + "azure.ai.finetuning_sessions.operations.CheckpointsOperations.get": "Azure.AI.FineTuning.Sessions.Checkpoints.get", + "azure.ai.finetuning_sessions.aio.operations.CheckpointsOperations.get": "Azure.AI.FineTuning.Sessions.Checkpoints.get", + "azure.ai.finetuning_sessions.operations.SamplingOperations.begin_sample": "Azure.AI.FineTuning.Sessions.Sampling.sample", + "azure.ai.finetuning_sessions.aio.operations.SamplingOperations.begin_sample": "Azure.AI.FineTuning.Sessions.Sampling.sample", + "azure.ai.finetuning_sessions.operations.Operations.get": "Azure.AI.FineTuning.Sessions.Operations.get", + "azure.ai.finetuning_sessions.aio.operations.Operations.get": "Azure.AI.FineTuning.Sessions.Operations.get" + } +} \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/__init__.py new file mode 100644 index 000000000000..d55ccad1f573 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/__init__.py new file mode 100644 index 000000000000..d55ccad1f573 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/__init__.py @@ -0,0 +1 @@ +__path__ = __import__("pkgutil").extend_path(__path__, __name__) # type: ignore diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py new file mode 100644 index 000000000000..5e8a0824d3fe --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._client import FineTuningSessionClient # type: ignore +from ._version import VERSION + +__version__ = VERSION + +try: + from ._patch import __all__ as _patch_all + from ._patch import * +except ImportError: + _patch_all = [] +from ._patch import patch_sdk as _patch_sdk + +__all__ = [ + "FineTuningSessionClient", + "FineTuningSession", +] +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore + +_patch_sdk() diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_client.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_client.py new file mode 100644 index 000000000000..d820cfd7ac31 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_client.py @@ -0,0 +1,116 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from copy import deepcopy +from typing import Any, TYPE_CHECKING +from typing_extensions import Self + +from azure.core import PipelineClient +from azure.core.pipeline import policies +from azure.core.rest import HttpRequest, HttpResponse + +from ._configuration import FineTuningSessionClientConfiguration +from ._utils.serialization import Deserializer, Serializer +from .operations import CheckpointsOperations, Operations, SamplingOperations, SessionsOperations, TrainingOperations + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + + +class FineTuningSessionClient: # pylint: disable=client-accepts-api-version-keyword + """FineTuningSessionClient. + + :ivar sessions: SessionsOperations operations + :vartype sessions: azure.ai.finetuning_sessions.operations.SessionsOperations + :ivar training: TrainingOperations operations + :vartype training: azure.ai.finetuning_sessions.operations.TrainingOperations + :ivar checkpoints: CheckpointsOperations operations + :vartype checkpoints: azure.ai.finetuning_sessions.operations.CheckpointsOperations + :ivar sampling: SamplingOperations operations + :vartype sampling: azure.ai.finetuning_sessions.operations.SamplingOperations + :ivar operations: Operations operations + :vartype operations: azure.ai.finetuning_sessions.operations.Operations + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Required. + :type credential: ~azure.core.credentials.TokenCredential + :keyword int polling_interval: Default waiting time between two polls for LRO operations if no + Retry-After header is present. + """ + + def __init__(self, endpoint: str, credential: "TokenCredential", *, allow_insecure_http: bool = False, **kwargs: Any) -> None: + _endpoint = "{endpoint}" + self._config = FineTuningSessionClientConfiguration(endpoint=endpoint, credential=credential, allow_insecure_http=allow_insecure_http, **kwargs) + + _policies = kwargs.pop("policies", None) + if _policies is None: + _policies = [ + policies.RequestIdPolicy(**kwargs), + self._config.headers_policy, + self._config.user_agent_policy, + self._config.proxy_policy, + policies.ContentDecodePolicy(**kwargs), + self._config.redirect_policy, + self._config.retry_policy, + self._config.authentication_policy, + self._config.custom_hook_policy, + self._config.logging_policy, + policies.DistributedTracingPolicy(**kwargs), + policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None, + self._config.http_logging_policy, + ] + self._client: PipelineClient = PipelineClient(base_url=_endpoint, policies=_policies, **kwargs) + + self._serialize = Serializer() + self._deserialize = Deserializer() + self._serialize.client_side_validation = False + self.sessions = SessionsOperations(self._client, self._config, self._serialize, self._deserialize) + self.training = TrainingOperations(self._client, self._config, self._serialize, self._deserialize) + self.checkpoints = CheckpointsOperations(self._client, self._config, self._serialize, self._deserialize) + self.sampling = SamplingOperations(self._client, self._config, self._serialize, self._deserialize) + self.operations = Operations(self._client, self._config, self._serialize, self._deserialize) + + def send_request(self, request: HttpRequest, *, stream: bool = False, **kwargs: Any) -> HttpResponse: + """Runs the network request through the client's chained policies. + + >>> from azure.core.rest import HttpRequest + >>> request = HttpRequest("GET", "https://www.example.org/") + + >>> response = client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/dpcodegen/python/send_request + + :param request: The network request you want to make. Required. + :type request: ~azure.core.rest.HttpRequest + :keyword bool stream: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.core.rest.HttpResponse + """ + + request_copy = deepcopy(request) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) + return self._client.send_request(request_copy, stream=stream, **kwargs) # type: ignore + + def close(self) -> None: + self._client.close() + + def __enter__(self) -> Self: + self._client.__enter__() + return self + + def __exit__(self, *exc_details: Any) -> None: + self._client.__exit__(*exc_details) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_configuration.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_configuration.py new file mode 100644 index 000000000000..f0d058726816 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_configuration.py @@ -0,0 +1,79 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Any, TYPE_CHECKING + +from azure.core.credentials import AzureKeyCredential +from azure.core.pipeline import policies +from azure.core.pipeline.policies import AzureKeyCredentialPolicy + +from ._version import VERSION + +if TYPE_CHECKING: + from azure.core.credentials import TokenCredential + + +class _InsecureBearerTokenCredentialPolicy(policies.BearerTokenCredentialPolicy): + """BearerTokenCredentialPolicy that skips HTTPS enforcement (for local/http:// dev).""" + + def on_request(self, request: Any) -> None: + request.context.options["enforce_https"] = False + super().on_request(request) + + +class FineTuningSessionClientConfiguration: # pylint: disable=too-many-instance-attributes + """Configuration for FineTuningSessionClient. + + Note that all parameters used to create this instance are saved as instance + attributes. + + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Required. + :type credential: ~azure.core.credentials.TokenCredential + """ + + def __init__(self, endpoint: str, credential: "TokenCredential", *, allow_insecure_http: bool = False, **kwargs: Any) -> None: + if endpoint is None: + raise ValueError("Parameter 'endpoint' must not be None.") + if credential is None: + raise ValueError("Parameter 'credential' must not be None.") + + self.endpoint = endpoint + self.credential = credential + self.allow_insecure_http = allow_insecure_http + self.api_version = kwargs.pop("api_version", "v1") + self.credential_scopes = kwargs.pop("credential_scopes", ["https://ai.azure.com/.default"]) + kwargs.setdefault("sdk_moniker", "finetuning-sessions/{}".format(VERSION)) + self.polling_interval = kwargs.get("polling_interval", 30) + self._configure(**kwargs) + + def _configure(self, **kwargs: Any) -> None: + self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs) + self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs) + self.proxy_policy = kwargs.get("proxy_policy") or policies.ProxyPolicy(**kwargs) + self.logging_policy = kwargs.get("logging_policy") or policies.NetworkTraceLoggingPolicy(**kwargs) + self.http_logging_policy = kwargs.get("http_logging_policy") or policies.HttpLoggingPolicy(**kwargs) + self.custom_hook_policy = kwargs.get("custom_hook_policy") or policies.CustomHookPolicy(**kwargs) + self.redirect_policy = kwargs.get("redirect_policy") or policies.RedirectPolicy(**kwargs) + self.retry_policy = kwargs.get("retry_policy") or policies.RetryPolicy(**kwargs) + self.authentication_policy = kwargs.get("authentication_policy") + if self.credential and not self.authentication_policy: + if isinstance(self.credential, AzureKeyCredential): + # API key auth: sends "api-key: " header on every request. + self.authentication_policy = AzureKeyCredentialPolicy(self.credential, name="api-key") + else: + # Token (OAuth2) auth — enforce HTTPS unless running against http://. + policy_cls = _InsecureBearerTokenCredentialPolicy if self.allow_insecure_http else policies.BearerTokenCredentialPolicy + self.authentication_policy = policy_cls( + self.credential, *self.credential_scopes, **kwargs + ) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py new file mode 100644 index 000000000000..4eb1a5a762b7 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py @@ -0,0 +1,565 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Handwritten convenience layer on top of the generated FineTuningSessionClient. + +``FineTuningSession`` wraps a live session and exposes the hero-code API from +SPEC_FOUNDRY_AICLIENT.md: + + session = FineTuningSession(client, session_id="session_xxx") + fb_result = session.forward_backward(batch, loss_fn="cross_entropy") + opt_result = session.optim_step(AdamParams(learning_rate=1e-4)) + ckpt_result = session.save_weights("my_checkpoint") + sampler_result = session.save_weights_for_sampler(seq_id=0) + sample_result = session.sample(prompt_tokens, sampling_params, num_samples=4) + session.close() + +Each mutating method follows loom's two-step protocol: + 1. POST to the action endpoint — loom returns **200** with + ``{request_id, session_id, status: "pending"}``. + 2. GET ``/fine_tuning/sessions/{sessionId}/request/{requestId}`` — the server + long-polls (up to 5 minutes) and returns the typed result when the GPU finishes. + +Note: the generated ``begin_*`` methods on sub-clients use the Azure LRO (202 + +Operation-Location header) pattern and will **not** work against loom, which returns +200. Always use ``FineTuningSession`` methods for training operations. +""" +from __future__ import annotations + +import json as _json +import logging as _logging +import os as _os +import threading as _threading +import time as _time +from typing import TYPE_CHECKING, Any, List, Optional, Union + +from azure.core.rest import HttpRequest as _HttpRequest + +from .models import ( + AdamParams, + CreateSessionRequest, + Datum, + ForwardBackwardInput, + ForwardBackwardRequest, + LoRAConfig, + LossFn, + LossFnConfig, + ModelInput, + ModelInputChunk, + OperationResult, + OptimStepRequest, + SampleRequest, + SamplingParams, + SaveCheckpointRequest, + SaveSamplerWeightsRequest, + FoundryFeaturesOptInKeys, +) +from ._utils.model_base import SdkJSONEncoder as _SdkJSONEncoder, _deserialize as _deserialize_model + +# ── Loom wire-format → OperationResult discriminator map ───────────────────── +# Maps the last path segment of a Loom action URL to the SDK's "type" value. +_LOOM_SUBPATH_TO_OP_TYPE: dict[str, str] = { + "forward_backward": "forward_backward", + "optim_step": "optim_step", + "checkpoint": "save_checkpoint", + "checkpoint_sample": "save_sampler_weights", + "sample": "sample", +} + + +def _normalize_loom_result(data: dict, op_type: str, request_id: str) -> dict: + """Normalize the Loom poll-endpoint wire format into an OperationResult dict. + + The Loom server returns raw engine results (no ``"type"`` discriminator, metrics + under namespaced keys like ``"total_loss:sum"``). This function injects the + discriminator and promotes metric fields so ``_deserialize(OperationResult, ...)`` + returns the correct typed subclass. + """ + out = dict(data) + out.setdefault("type", op_type) + out.setdefault("operation_id", request_id) + out.setdefault("status", "succeeded") + + metrics: dict = out.get("metrics") or {} + + if op_type == "forward_backward": + if "total_loss" not in out: + out["total_loss"] = float(metrics.get("total_loss:sum", 0.0)) + + elif op_type == "optim_step": + if "grad_norm" not in out: + out["grad_norm"] = float(metrics.get("skyrl.ai/grad_norm", 0.0)) + if "step_count" not in out: + out["step_count"] = int(metrics.get("step_count", 0)) + + elif op_type == "save_sampler_weights": + # Server may return "type": "save_weights_for_sampler" — normalise to SDK value. + out["type"] = "save_sampler_weights" + out.setdefault("checkpoint_id", out.get("checkpoint_id", "")) + out.setdefault("sampling_session_id", out.get("sampling_session_id", "")) + + elif op_type == "save_checkpoint": + out.setdefault("checkpoint_id", out.get("checkpoint_id", "")) + out.setdefault("path", out.get("path", "")) + + return out + +if TYPE_CHECKING: + from ._client import FineTuningSessionClient + +_PREVIEW = FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW +_API_VERSION = "v1" +_logger = _logging.getLogger(__name__) + +# --------------------------------------------------------------------------- +# Verbose HTTP logging toggle +# --------------------------------------------------------------------------- +# Set to True (or set env var FINETUNING_VERBOSE_HTTP=1) to log every request +# URL + body and response status + body for all SDK API calls. +VERBOSE_HTTP: bool = _os.environ.get("FINETUNING_VERBOSE_HTTP", "").lower() in ("1", "true", "yes") + + +def _log_http(direction: str, method: str, url: str, status: Optional[int] = None, body: Any = None) -> None: + """Log an HTTP request or response if VERBOSE_HTTP is enabled.""" + if not VERBOSE_HTTP: + return + body_str = "" + if body is not None: + try: + body_str = _json.dumps(body, indent=2) if isinstance(body, (dict, list)) else str(body) + except Exception: # pragma: no cover + body_str = repr(body) + body_str = f"\n{body_str}" + if direction == "request": + _logger.info("[HTTP] --> %s %s%s", method, url, body_str) + else: + _logger.info("[HTTP] <-- %s %s status=%d%s", method, url, status or 0, body_str) + + +def _base_headers(extra: Optional[dict] = None) -> dict: + """Build the common headers for every Loom request. + + Reads ``X_COGNITIVE_SUBSCRIPTION_ID`` (or ``COGNITIVE_SUBSCRIPTION_ID`` / + ``AZURE_SUBSCRIPTION_ID`` as fallbacks) from the environment and injects it + as ``apim-subscription-id``. The remote Loom endpoint (LOOM_SETUP_MODE=prod) + requires this header on every request — same as ``loom_client`` and + ``clean_remote.sh`` which set ``X_COGNITIVE_SUBSCRIPTION_ID=local-sub``. + """ + headers: dict = { + "Accept": "application/json", + "Foundry-Features": _PREVIEW.value, + } + sub_id = ( + _os.environ.get("X_COGNITIVE_SUBSCRIPTION_ID") + or _os.environ.get("COGNITIVE_SUBSCRIPTION_ID") + or _os.environ.get("AZURE_SUBSCRIPTION_ID") + ) + if sub_id: + headers["apim-subscription-id"] = sub_id + if extra: + headers.update(extra) + return headers + + +class FineTuningSession: + """Convenience wrapper around a single fine-tuning session. + + Mirrors the hero-code surface from SPEC_FOUNDRY_AICLIENT.md so callers + can write training loops without constructing raw request bodies. + + :param client: The generated ``FineTuningSessionClient``. + :param session_id: The session ID returned by the server after creating a session. + """ + + def __init__(self, client: "FineTuningSessionClient", session_id: str) -> None: + self._client = client + self.session_id = session_id + # Derive the heartbeat session_id: heartbeat endpoint looks up by + # "session_xxx" in the sessions table, not "model_xxx". + raw_id = session_id.removeprefix("model_") + self._heartbeat_session_id = f"session_{raw_id}" + self._heartbeat_stop = _threading.Event() + self._heartbeat_thread: Optional[_threading.Thread] = None + self._start_heartbeat() + + # ── Background heartbeat ────────────────────────────────────────────────── + + def _start_heartbeat(self, interval_sec: float = 30.0) -> None: + """Start a daemon thread that sends heartbeat every interval_sec.""" + def _heartbeat_loop() -> None: + while not self._heartbeat_stop.wait(interval_sec): + try: + hb_req = _HttpRequest( + "POST", + "{endpoint}" + f"/fine_tuning/sessions/{self._heartbeat_session_id}/heartbeat", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + resp = self._client.send_request(hb_req) + if resp.status_code != 200: + _logger.warning("[heartbeat] status=%d for %s", resp.status_code, self._heartbeat_session_id) + except Exception as exc: + _logger.warning("[heartbeat] failed for %s: %s", self._heartbeat_session_id, exc) + + self._heartbeat_thread = _threading.Thread( + target=_heartbeat_loop, name="fts-heartbeat", daemon=True + ) + self._heartbeat_thread.start() + _logger.info("[heartbeat] started (interval=%.0fs, session=%s)", interval_sec, self._heartbeat_session_id) + + def _stop_heartbeat(self) -> None: + """Stop the background heartbeat thread.""" + self._heartbeat_stop.set() + if self._heartbeat_thread is not None: + self._heartbeat_thread.join(timeout=5.0) + self._heartbeat_thread = None + + # ── Factory ─────────────────────────────────────────────────────────────── + + @classmethod + def create( + cls, + client: "FineTuningSessionClient", + *, + base_model: str, + lora_config: Optional[LoRAConfig] = None, + type: str = "training", + poll_interval_sec: float = 5.0, + timeout_sec: float = 600.0, + ) -> "FineTuningSession": + """Create a fine-tuning session and wait until the model is loaded. + + Combines ``POST /fine_tuning/sessions`` (which triggers an async model-load + on the server) with polling of the returned ``request_id`` until the load + completes, then returns a ready-to-use :class:`FineTuningSession`. + + :param client: The :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`. + :param base_model: Name of the base model to load (e.g. ``"Llama-3.1-8B"``). + :param lora_config: Optional LoRA adapter config. Server default is used if omitted. + :param type: Session type string. Defaults to ``"training"``. + :param poll_interval_sec: Seconds between poll attempts. Defaults to ``5.0``. + :param timeout_sec: Maximum seconds to wait for the model to load. Defaults to ``600.0``. + :raises RuntimeError: If the server reports status ``"failed"`` or the timeout expires. + :return: A :class:`FineTuningSession` instance ready for training operations. + """ + body_json = _json.dumps( + CreateSessionRequest(type=type, base_model=base_model, lora_config=lora_config), + cls=_SdkJSONEncoder, + exclude_readonly=True, + ) + post_req = _HttpRequest( + "POST", + "{endpoint}/fine_tuning/sessions", + headers=_base_headers({"Content-Type": "application/json"}), + params={"api-version": _API_VERSION}, + content=body_json, + ) + _log_http("request", "POST", "/fine_tuning/sessions", body=_json.loads(body_json)) + post_resp = client.send_request(post_req) + _log_http("response", "POST", "/fine_tuning/sessions", status=post_resp.status_code, body=post_resp.json()) + post_resp.raise_for_status() + data = post_resp.json() + raw_session_id: str = data["session_id"] + request_id: str = data["request_id"] + _logger.info("[create] POST /fine_tuning/sessions response: raw_session_id=%s, request_id=%s, full_response=%s", raw_session_id, request_id, data) + + # The loom server stores the model record as f"model_{session_id}" (see + # loom_create_model in the SQL/Cosmos providers). Every route handler that + # performs a training operation calls loom_require_model(provider, ) + # which does loom_get_model(). Using the "model_" prefixed form as + # our session_id means the URL path parameter matches the stored model_id, so + # the lookup succeeds — no changes required on the server or engine side. + session_id: str = f"model_{raw_session_id}" + _logger.info("[create] session_id transformed: raw=%s -> resource_id=%s (used in all subsequent URL paths)", raw_session_id, session_id) + + # Wait for the model-load request to complete. + # The retrieve-future endpoint (GET /fine_tuning/sessions/{id}/request/{rid}) + # long-polls server-side for up to 5 minutes. A 200 response means the + # request completed successfully — the server only returns 200 when the + # engine has finished loading the model. If the model is still loading + # after the server's internal timeout, it returns 408 which raises an + # HttpResponseError here. We retry on 408 until our own deadline. + deadline = _time.monotonic() + timeout_sec + while True: + poll_req = _HttpRequest( + "GET", + "{endpoint}" + f"/fine_tuning/sessions/{session_id}/request/{request_id}", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" + _log_http("request", "GET", poll_path) + poll_resp = client.send_request(poll_req) + _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=poll_resp.json() if poll_resp.status_code == 200 else None) + + if poll_resp.status_code == 200: + _logger.info("[create] model load completed: %s", poll_resp.json()) + break + + if poll_resp.status_code == 408: + # Server timed out waiting (model still loading) — retry if within deadline + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for session_id={raw_session_id} to become ready" + ) + _logger.info("[create] server returned 408 (still loading), retrying...") + continue + + # Any other error — fail immediately + poll_resp.raise_for_status() + + return cls(client, session_id=session_id) + + # ── Low-level helper ────────────────────────────────────────────────────── + + def _post_and_poll(self, subpath: str, body_model: Any, extra_params: Optional[dict] = None, extra_result_fields: Optional[dict] = None) -> OperationResult: + """POST to a loom action endpoint (returns 200 + request_id), then + long-poll GET /request/{request_id} until the GPU finishes. + + Loom returns 200 (not 202) with ``{request_id, session_id, status}`` + from all mutating operations. The poll endpoint blocks server-side + (up to 5 minutes) and returns the typed result directly. + """ + body_json = _json.dumps(body_model, cls=_SdkJSONEncoder, exclude_readonly=True) + post_params: dict = {"api-version": _API_VERSION} + if extra_params: + post_params.update(extra_params) + post_req = _HttpRequest( + "POST", + "{endpoint}" + subpath, + headers=_base_headers({"Content-Type": "application/json"}), + params=post_params, + content=body_json, + ) + _log_http("request", "POST", subpath, body=_json.loads(body_json)) + post_resp = self._client.send_request(post_req) + _log_http("response", "POST", subpath, status=post_resp.status_code, body=post_resp.json()) + post_resp.raise_for_status() + data = post_resp.json() + request_id = data["request_id"] + session_id = data.get("session_id", self.session_id) + + # Long-poll the result directly so we can normalize the Loom wire format + # before deserializing. The generated operations.get() passes the raw JSON + # straight to _deserialize(OperationResult, ...) which expects a "type" + # discriminator field — but the Loom server returns its own engine format + # (no "type", metrics under namespaced keys). We do the GET ourselves, + # normalize, then deserialize. + op_type = _LOOM_SUBPATH_TO_OP_TYPE.get(subpath.rsplit("/", 1)[-1], "") + poll_req = _HttpRequest( + "GET", + "{endpoint}" + f"/fine_tuning/sessions/{session_id}/request/{request_id}", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" + + # Retry up to 3 times on 408 (server long-polls ~5 min per attempt, + # so 3 retries = ~15 min max wait before giving up). + max_retries = 3 + for attempt in range(max_retries + 1): + _log_http("request", "GET", poll_path) + poll_resp = self._client.send_request(poll_req) + if poll_resp.status_code == 200: + _log_http("response", "GET", poll_path, status=200, body=poll_resp.json()) + break + if poll_resp.status_code == 408 and attempt < max_retries: + _logger.info("[_post_and_poll] server returned 408 (GPU still processing), retry %d/%d", attempt + 1, max_retries) + continue + _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=None) + poll_resp.raise_for_status() + + raw = poll_resp.json() + normalized = _normalize_loom_result(raw, op_type, request_id) + # Merge caller-supplied fallback fields BEFORE deserialization so that + # generated model classes receive them (post-deserialization attr + # assignment doesn't work on SDK objects with __slots__). + if extra_result_fields: + for k, v in extra_result_fields.items(): + if not normalized.get(k): # server value takes precedence + normalized[k] = v + return _deserialize_model(OperationResult, normalized) + + # ── Training ────────────────────────────────────────────────────────────── + + def forward_backward( + self, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, + **kwargs: Any, + ) -> OperationResult: + """Submit a mini-batch for a forward + backward pass. + + Blocks until the GPU finishes the backward pass. + + Spec: ``fb_result = session.forward_backward(batch, loss_fn="cross_entropy")`` + + :param batch: List of :class:`~azure.ai.finetuning_sessions.models.Datum`. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: :class:`~azure.ai.finetuning_sessions.models.OperationResult`. + """ + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/forward_backward", + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=batch, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + + def optim_step( + self, + adam_params: AdamParams, + **kwargs: Any, + ) -> OperationResult: + """Apply accumulated gradients with Adam. + + Blocks until the GPU applies the weight update. + + Spec: ``opt_result = session.optim_step(AdamParams(learning_rate=1e-4))`` + """ + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/optim_step", + OptimStepRequest(adam_params=adam_params), + ) + + # ── Checkpoints ─────────────────────────────────────────────────────────── + + def save_weights( + self, + path: str, + **kwargs: Any, + ) -> OperationResult: + """Save a training checkpoint (LoRA weights + optimizer state). + + Blocks until the checkpoint is written to storage. + + Spec: ``ckpt_result = session.save_weights("sft_piglatin_v1")`` + """ + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/checkpoint", + SaveCheckpointRequest(path=path), + ) + + def save_weights_for_sampler( + self, + seq_id: int, + *, + sampling_session_seq_id: Optional[int] = None, + path: Optional[str] = None, + **kwargs: Any, + ) -> OperationResult: + """Push current LoRA weights to the sampler (required before calling ``sample``). + + Blocks until the sampler weights are ready. + + Spec: ``sampler_result = session.save_weights_for_sampler(seq_id=step)`` + + :param seq_id: Training step index -- must match the ``seq_id`` passed to ``sample``. + :param sampling_session_seq_id: Ordinal of this sampling session in the run. + :param path: Optional explicit checkpoint identifier. + """ + # Compute the checkpoint_id using the same formula the server uses + # (loom_sampling.py line 270). The server doesn't echo it back in the + # poll response, so we inject it before deserialization. + computed_checkpoint_id = path or f"ss{sampling_session_seq_id}_seq{seq_id}" + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/checkpoint_sample", + SaveSamplerWeightsRequest( + seq_id=seq_id, + sampling_session_seq_id=sampling_session_seq_id, + path=path, + ), + extra_result_fields={"checkpoint_id": computed_checkpoint_id}, + ) + + # ── Sampling ────────────────────────────────────────────────────────────── + + def sample( + self, + prompt_tokens: List[int], + sampling_params: SamplingParams, + *, + checkpoint_id: str, + num_samples: int = 1, + sampling_session_id: Optional[str] = None, + seq_id: Optional[int] = None, + prompt_logprobs: bool = False, + topk_prompt_logprobs: int = 0, + **kwargs: Any, + ) -> OperationResult: + """Generate completions using current LoRA weights. + + Blocks until the GPU finishes sampling. + + Spec: ``sample_result = session.sample(prompt_tokens, sampling_params, checkpoint_id=..., num_samples=4, ...)`` + + :param prompt_tokens: Tokenised input prompt as a list of integer IDs. + :param sampling_params: Generation parameters (max_tokens, temperature, etc.). + :param checkpoint_id: Sampler checkpoint ID returned by ``save_weights_for_sampler``. + :param num_samples: Number of independent completions to generate. Default 1. + :param sampling_session_id: ID returned by a prior ``save_weights_for_sampler`` call. + :param seq_id: Training step index; must match the one used in ``save_weights_for_sampler``. + :param prompt_logprobs: If True, return per-token log-probabilities for the prompt. + :param topk_prompt_logprobs: Top-k log-probabilities per prompt token. 0 = none. + """ + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/sample", + SampleRequest( + num_samples=num_samples, + prompt=ModelInput(chunks=[ModelInputChunk(tokens=prompt_tokens)]), + sampling_params=sampling_params, + topk_prompt_logprobs=topk_prompt_logprobs, + sampling_session_id=sampling_session_id, + seq_id=seq_id, + prompt_logprobs=prompt_logprobs, + ), + extra_params={"checkpoint_id": checkpoint_id}, + ) + + # ── Session lifecycle ───────────────────────────────────────────────────── + + def heartbeat(self, **kwargs: Any) -> Any: + """Refresh an active session to prevent idle expiry.""" + return self._client.sessions.heartbeat( + session_id=self.session_id, + foundry_features=_PREVIEW, + api_version=_API_VERSION, + **kwargs, + ) + + def close(self, **kwargs: Any) -> None: + """Unload the session from the GPU engine. + + Stops the background heartbeat, then issues the complete request. + + Spec: ``session.close()`` + """ + self._stop_heartbeat() + close_req = _HttpRequest( + "POST", + "{endpoint}" + f"/fine_tuning/sessions/{self.session_id}/complete", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + resp = self._client.send_request(close_req) + resp.raise_for_status() + + +__all__: list[str] = ["FineTuningSession"] + + +def patch_sdk(): + """Do not remove from this file. + + `patch_sdk` is a last resort escape hatch that allows you to do customizations + you can't accomplish using the techniques described in + https://aka.ms/azsdk/python/dpcodegen/python/customize + """ diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_types.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_types.py new file mode 100644 index 000000000000..f96d9134fd15 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_types.py @@ -0,0 +1,11 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Union + +StopCriteria = Union[list[int], list[str]] diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/__init__.py new file mode 100644 index 000000000000..8026245c2abc --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/__init__.py @@ -0,0 +1,6 @@ +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/model_base.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/model_base.py new file mode 100644 index 000000000000..db24930fdca9 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/model_base.py @@ -0,0 +1,1441 @@ +# pylint: disable=line-too-long,useless-suppression,too-many-lines +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=protected-access, broad-except + +import copy +import calendar +import decimal +import functools +import sys +import logging +import base64 +import re +import typing +import enum +import email.utils +from datetime import datetime, date, time, timedelta, timezone +from json import JSONEncoder +import xml.etree.ElementTree as ET +from collections.abc import MutableMapping +from typing_extensions import Self +import isodate +from azure.core.exceptions import DeserializationError +from azure.core import CaseInsensitiveEnumMeta +from azure.core.pipeline import PipelineResponse +from azure.core.serialization import _Null +from azure.core.rest import HttpResponse + +_LOGGER = logging.getLogger(__name__) + +__all__ = ["SdkJSONEncoder", "Model", "rest_field", "rest_discriminator"] + +TZ_UTC = timezone.utc +_T = typing.TypeVar("_T") +_NONE_TYPE = type(None) + + +def _timedelta_as_isostr(td: timedelta) -> str: + """Converts a datetime.timedelta object into an ISO 8601 formatted string, e.g. 'P4DT12H30M05S' + + Function adapted from the Tin Can Python project: https://github.com/RusticiSoftware/TinCanPython + + :param timedelta td: The timedelta to convert + :rtype: str + :return: ISO8601 version of this timedelta + """ + + # Split seconds to larger units + seconds = td.total_seconds() + minutes, seconds = divmod(seconds, 60) + hours, minutes = divmod(minutes, 60) + days, hours = divmod(hours, 24) + + days, hours, minutes = list(map(int, (days, hours, minutes))) + seconds = round(seconds, 6) + + # Build date + date_str = "" + if days: + date_str = "%sD" % days + + if hours or minutes or seconds: + # Build time + time_str = "T" + + # Hours + bigger_exists = date_str or hours + if bigger_exists: + time_str += "{:02}H".format(hours) + + # Minutes + bigger_exists = bigger_exists or minutes + if bigger_exists: + time_str += "{:02}M".format(minutes) + + # Seconds + try: + if seconds.is_integer(): + seconds_string = "{:02}".format(int(seconds)) + else: + # 9 chars long w/ leading 0, 6 digits after decimal + seconds_string = "%09.6f" % seconds + # Remove trailing zeros + seconds_string = seconds_string.rstrip("0") + except AttributeError: # int.is_integer() raises + seconds_string = "{:02}".format(seconds) + + time_str += "{}S".format(seconds_string) + else: + time_str = "" + + return "P" + date_str + time_str + + +def _serialize_bytes(o, format: typing.Optional[str] = None) -> str: + encoded = base64.b64encode(o).decode() + if format == "base64url": + return encoded.strip("=").replace("+", "-").replace("/", "_") + return encoded + + +def _serialize_datetime(o, format: typing.Optional[str] = None): + if hasattr(o, "year") and hasattr(o, "hour"): + if format == "rfc7231": + return email.utils.format_datetime(o, usegmt=True) + if format == "unix-timestamp": + return int(calendar.timegm(o.utctimetuple())) + + # astimezone() fails for naive times in Python 2.7, so make make sure o is aware (tzinfo is set) + if not o.tzinfo: + iso_formatted = o.replace(tzinfo=TZ_UTC).isoformat() + else: + iso_formatted = o.astimezone(TZ_UTC).isoformat() + # Replace the trailing "+00:00" UTC offset with "Z" (RFC 3339: https://www.ietf.org/rfc/rfc3339.txt) + return iso_formatted.replace("+00:00", "Z") + # Next try datetime.date or datetime.time + return o.isoformat() + + +def _is_readonly(p): + try: + return p._visibility == ["read"] + except AttributeError: + return False + + +class SdkJSONEncoder(JSONEncoder): + """A JSON encoder that's capable of serializing datetime objects and bytes.""" + + def __init__(self, *args, exclude_readonly: bool = False, format: typing.Optional[str] = None, **kwargs): + super().__init__(*args, **kwargs) + self.exclude_readonly = exclude_readonly + self.format = format + + def default(self, o): # pylint: disable=too-many-return-statements + if _is_model(o): + if self.exclude_readonly: + readonly_props = [p._rest_name for p in o._attr_to_rest_field.values() if _is_readonly(p)] + return {k: v for k, v in o.items() if k not in readonly_props} + return dict(o.items()) + try: + return super(SdkJSONEncoder, self).default(o) + except TypeError: + if isinstance(o, _Null): + return None + if isinstance(o, decimal.Decimal): + return float(o) + if isinstance(o, (bytes, bytearray)): + return _serialize_bytes(o, self.format) + try: + # First try datetime.datetime + return _serialize_datetime(o, self.format) + except AttributeError: + pass + # Last, try datetime.timedelta + try: + return _timedelta_as_isostr(o) + except AttributeError: + # This will be raised when it hits value.total_seconds in the method above + pass + return super(SdkJSONEncoder, self).default(o) + + +_VALID_DATE = re.compile(r"\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}" + r"\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?") +_VALID_RFC7231 = re.compile( + r"(Mon|Tue|Wed|Thu|Fri|Sat|Sun),\s\d{2}\s" + r"(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT" +) + +_ARRAY_ENCODE_MAPPING = { + "pipeDelimited": "|", + "spaceDelimited": " ", + "commaDelimited": ",", + "newlineDelimited": "\n", +} + + +def _deserialize_array_encoded(delimit: str, attr): + if isinstance(attr, str): + if attr == "": + return [] + return attr.split(delimit) + return attr + + +def _deserialize_datetime(attr: typing.Union[str, datetime]) -> datetime: + """Deserialize ISO-8601 formatted string into Datetime object. + + :param str attr: response string to be deserialized. + :rtype: ~datetime.datetime + :returns: The datetime object from that input + """ + if isinstance(attr, datetime): + # i'm already deserialized + return attr + attr = attr.upper() + match = _VALID_DATE.match(attr) + if not match: + raise ValueError("Invalid datetime string: " + attr) + + check_decimal = attr.split(".") + if len(check_decimal) > 1: + decimal_str = "" + for digit in check_decimal[1]: + if digit.isdigit(): + decimal_str += digit + else: + break + if len(decimal_str) > 6: + attr = attr.replace(decimal_str, decimal_str[0:6]) + + date_obj = isodate.parse_datetime(attr) + test_utc = date_obj.utctimetuple() + if test_utc.tm_year > 9999 or test_utc.tm_year < 1: + raise OverflowError("Hit max or min date") + return date_obj # type: ignore[no-any-return] + + +def _deserialize_datetime_rfc7231(attr: typing.Union[str, datetime]) -> datetime: + """Deserialize RFC7231 formatted string into Datetime object. + + :param str attr: response string to be deserialized. + :rtype: ~datetime.datetime + :returns: The datetime object from that input + """ + if isinstance(attr, datetime): + # i'm already deserialized + return attr + match = _VALID_RFC7231.match(attr) + if not match: + raise ValueError("Invalid datetime string: " + attr) + + return email.utils.parsedate_to_datetime(attr) + + +def _deserialize_datetime_unix_timestamp(attr: typing.Union[float, datetime]) -> datetime: + """Deserialize unix timestamp into Datetime object. + + :param str attr: response string to be deserialized. + :rtype: ~datetime.datetime + :returns: The datetime object from that input + """ + if isinstance(attr, datetime): + # i'm already deserialized + return attr + return datetime.fromtimestamp(attr, TZ_UTC) + + +def _deserialize_date(attr: typing.Union[str, date]) -> date: + """Deserialize ISO-8601 formatted string into Date object. + :param str attr: response string to be deserialized. + :rtype: date + :returns: The date object from that input + """ + # This must NOT use defaultmonth/defaultday. Using None ensure this raises an exception. + if isinstance(attr, date): + return attr + return isodate.parse_date(attr, defaultmonth=None, defaultday=None) # type: ignore + + +def _deserialize_time(attr: typing.Union[str, time]) -> time: + """Deserialize ISO-8601 formatted string into time object. + + :param str attr: response string to be deserialized. + :rtype: datetime.time + :returns: The time object from that input + """ + if isinstance(attr, time): + return attr + return isodate.parse_time(attr) # type: ignore[no-any-return] + + +def _deserialize_bytes(attr): + if isinstance(attr, (bytes, bytearray)): + return attr + return bytes(base64.b64decode(attr)) + + +def _deserialize_bytes_base64(attr): + if isinstance(attr, (bytes, bytearray)): + return attr + padding = "=" * (3 - (len(attr) + 3) % 4) # type: ignore + attr = attr + padding # type: ignore + encoded = attr.replace("-", "+").replace("_", "/") + return bytes(base64.b64decode(encoded)) + + +def _deserialize_duration(attr): + if isinstance(attr, timedelta): + return attr + return isodate.parse_duration(attr) + + +def _deserialize_decimal(attr): + if isinstance(attr, decimal.Decimal): + return attr + return decimal.Decimal(str(attr)) + + +def _deserialize_int_as_str(attr): + if isinstance(attr, int): + return attr + return int(attr) + + +_DESERIALIZE_MAPPING = { + datetime: _deserialize_datetime, + date: _deserialize_date, + time: _deserialize_time, + bytes: _deserialize_bytes, + bytearray: _deserialize_bytes, + timedelta: _deserialize_duration, + typing.Any: lambda x: x, + decimal.Decimal: _deserialize_decimal, +} + +_DESERIALIZE_MAPPING_WITHFORMAT = { + "rfc3339": _deserialize_datetime, + "rfc7231": _deserialize_datetime_rfc7231, + "unix-timestamp": _deserialize_datetime_unix_timestamp, + "base64": _deserialize_bytes, + "base64url": _deserialize_bytes_base64, +} + + +def get_deserializer(annotation: typing.Any, rf: typing.Optional["_RestField"] = None): + if annotation is int and rf and rf._format == "str": + return _deserialize_int_as_str + if annotation is str and rf and rf._format in _ARRAY_ENCODE_MAPPING: + return functools.partial(_deserialize_array_encoded, _ARRAY_ENCODE_MAPPING[rf._format]) + if rf and rf._format: + return _DESERIALIZE_MAPPING_WITHFORMAT.get(rf._format) + return _DESERIALIZE_MAPPING.get(annotation) # pyright: ignore + + +def _get_type_alias_type(module_name: str, alias_name: str): + types = { + k: v + for k, v in sys.modules[module_name].__dict__.items() + if isinstance(v, typing._GenericAlias) # type: ignore + } + if alias_name not in types: + return alias_name + return types[alias_name] + + +def _get_model(module_name: str, model_name: str): + models = {k: v for k, v in sys.modules[module_name].__dict__.items() if isinstance(v, type)} + module_end = module_name.rsplit(".", 1)[0] + models.update({k: v for k, v in sys.modules[module_end].__dict__.items() if isinstance(v, type)}) + if isinstance(model_name, str): + model_name = model_name.split(".")[-1] + if model_name not in models: + return model_name + return models[model_name] + + +_UNSET = object() + + +class _MyMutableMapping(MutableMapping[str, typing.Any]): + def __init__(self, data: dict[str, typing.Any]) -> None: + self._data = data + + def __contains__(self, key: typing.Any) -> bool: + return key in self._data + + def __getitem__(self, key: str) -> typing.Any: + # If this key has been deserialized (for mutable types), we need to handle serialization + if hasattr(self, "_attr_to_rest_field"): + cache_attr = f"_deserialized_{key}" + if hasattr(self, cache_attr): + rf = _get_rest_field(getattr(self, "_attr_to_rest_field"), key) + if rf: + value = self._data.get(key) + if isinstance(value, (dict, list, set)): + # For mutable types, serialize and return + # But also update _data with serialized form and clear flag + # so mutations via this returned value affect _data + serialized = _serialize(value, rf._format) + # If serialized form is same type (no transformation needed), + # return _data directly so mutations work + if isinstance(serialized, type(value)) and serialized == value: + return self._data.get(key) + # Otherwise return serialized copy and clear flag + try: + object.__delattr__(self, cache_attr) + except AttributeError: + pass + # Store serialized form back + self._data[key] = serialized + return serialized + return self._data.__getitem__(key) + + def __setitem__(self, key: str, value: typing.Any) -> None: + # Clear any cached deserialized value when setting through dictionary access + cache_attr = f"_deserialized_{key}" + try: + object.__delattr__(self, cache_attr) + except AttributeError: + pass + self._data.__setitem__(key, value) + + def __delitem__(self, key: str) -> None: + self._data.__delitem__(key) + + def __iter__(self) -> typing.Iterator[typing.Any]: + return self._data.__iter__() + + def __len__(self) -> int: + return self._data.__len__() + + def __ne__(self, other: typing.Any) -> bool: + return not self.__eq__(other) + + def keys(self) -> typing.KeysView[str]: + """ + :returns: a set-like object providing a view on D's keys + :rtype: ~typing.KeysView + """ + return self._data.keys() + + def values(self) -> typing.ValuesView[typing.Any]: + """ + :returns: an object providing a view on D's values + :rtype: ~typing.ValuesView + """ + return self._data.values() + + def items(self) -> typing.ItemsView[str, typing.Any]: + """ + :returns: set-like object providing a view on D's items + :rtype: ~typing.ItemsView + """ + return self._data.items() + + def get(self, key: str, default: typing.Any = None) -> typing.Any: + """ + Get the value for key if key is in the dictionary, else default. + :param str key: The key to look up. + :param any default: The value to return if key is not in the dictionary. Defaults to None + :returns: D[k] if k in D, else d. + :rtype: any + """ + try: + return self[key] + except KeyError: + return default + + @typing.overload + def pop(self, key: str) -> typing.Any: ... # pylint: disable=arguments-differ + + @typing.overload + def pop(self, key: str, default: _T) -> _T: ... # pylint: disable=signature-differs + + @typing.overload + def pop(self, key: str, default: typing.Any) -> typing.Any: ... # pylint: disable=signature-differs + + def pop(self, key: str, default: typing.Any = _UNSET) -> typing.Any: + """ + Removes specified key and return the corresponding value. + :param str key: The key to pop. + :param any default: The value to return if key is not in the dictionary + :returns: The value corresponding to the key. + :rtype: any + :raises KeyError: If key is not found and default is not given. + """ + if default is _UNSET: + return self._data.pop(key) + return self._data.pop(key, default) + + def popitem(self) -> tuple[str, typing.Any]: + """ + Removes and returns some (key, value) pair + :returns: The (key, value) pair. + :rtype: tuple + :raises KeyError: if D is empty. + """ + return self._data.popitem() + + def clear(self) -> None: + """ + Remove all items from D. + """ + self._data.clear() + + def update(self, *args: typing.Any, **kwargs: typing.Any) -> None: # pylint: disable=arguments-differ + """ + Updates D from mapping/iterable E and F. + :param any args: Either a mapping object or an iterable of key-value pairs. + """ + self._data.update(*args, **kwargs) + + @typing.overload + def setdefault(self, key: str, default: None = None) -> None: ... + + @typing.overload + def setdefault(self, key: str, default: typing.Any) -> typing.Any: ... # pylint: disable=signature-differs + + def setdefault(self, key: str, default: typing.Any = _UNSET) -> typing.Any: + """ + Same as calling D.get(k, d), and setting D[k]=d if k not found + :param str key: The key to look up. + :param any default: The value to set if key is not in the dictionary + :returns: D[k] if k in D, else d. + :rtype: any + """ + if default is _UNSET: + return self._data.setdefault(key) + return self._data.setdefault(key, default) + + def __eq__(self, other: typing.Any) -> bool: + if isinstance(other, _MyMutableMapping): + return self._data == other._data + try: + other_model = self.__class__(other) + except Exception: + return False + return self._data == other_model._data + + def __repr__(self) -> str: + return str(self._data) + + +def _is_model(obj: typing.Any) -> bool: + return getattr(obj, "_is_model", False) + + +def _serialize(o, format: typing.Optional[str] = None): # pylint: disable=too-many-return-statements + if isinstance(o, list): + if format in _ARRAY_ENCODE_MAPPING and all(isinstance(x, str) for x in o): + return _ARRAY_ENCODE_MAPPING[format].join(o) + return [_serialize(x, format) for x in o] + if isinstance(o, dict): + return {k: _serialize(v, format) for k, v in o.items()} + if isinstance(o, set): + return {_serialize(x, format) for x in o} + if isinstance(o, tuple): + return tuple(_serialize(x, format) for x in o) + if isinstance(o, (bytes, bytearray)): + return _serialize_bytes(o, format) + if isinstance(o, decimal.Decimal): + return float(o) + if isinstance(o, enum.Enum): + return o.value + if isinstance(o, int): + if format == "str": + return str(o) + return o + try: + # First try datetime.datetime + return _serialize_datetime(o, format) + except AttributeError: + pass + # Last, try datetime.timedelta + try: + return _timedelta_as_isostr(o) + except AttributeError: + # This will be raised when it hits value.total_seconds in the method above + pass + return o + + +def _get_rest_field(attr_to_rest_field: dict[str, "_RestField"], rest_name: str) -> typing.Optional["_RestField"]: + try: + return next(rf for rf in attr_to_rest_field.values() if rf._rest_name == rest_name) + except StopIteration: + return None + + +def _create_value(rf: typing.Optional["_RestField"], value: typing.Any) -> typing.Any: + if not rf: + return _serialize(value, None) + if rf._is_multipart_file_input: + return value + if rf._is_model: + return _deserialize(rf._type, value) + if isinstance(value, ET.Element): + value = _deserialize(rf._type, value) + return _serialize(value, rf._format) + + +class Model(_MyMutableMapping): + _is_model = True + # label whether current class's _attr_to_rest_field has been calculated + # could not see _attr_to_rest_field directly because subclass inherits it from parent class + _calculated: set[str] = set() + + def __init__(self, *args: typing.Any, **kwargs: typing.Any) -> None: + class_name = self.__class__.__name__ + if len(args) > 1: + raise TypeError(f"{class_name}.__init__() takes 2 positional arguments but {len(args) + 1} were given") + dict_to_pass = { + rest_field._rest_name: rest_field._default + for rest_field in self._attr_to_rest_field.values() + if rest_field._default is not _UNSET + } + if args: + if isinstance(args[0], ET.Element): + dict_to_pass.update(self._init_from_xml(args[0])) + else: + dict_to_pass.update( + {k: _create_value(_get_rest_field(self._attr_to_rest_field, k), v) for k, v in args[0].items()} + ) + else: + non_attr_kwargs = [k for k in kwargs if k not in self._attr_to_rest_field] + if non_attr_kwargs: + # actual type errors only throw the first wrong keyword arg they see, so following that. + raise TypeError(f"{class_name}.__init__() got an unexpected keyword argument '{non_attr_kwargs[0]}'") + dict_to_pass.update( + { + self._attr_to_rest_field[k]._rest_name: _create_value(self._attr_to_rest_field[k], v) + for k, v in kwargs.items() + if v is not None + } + ) + super().__init__(dict_to_pass) + + def _init_from_xml(self, element: ET.Element) -> dict[str, typing.Any]: + """Deserialize an XML element into a dict mapping rest field names to values. + + :param ET.Element element: The XML element to deserialize from. + :returns: A dictionary of rest_name to deserialized value pairs. + :rtype: dict + """ + result: dict[str, typing.Any] = {} + model_meta = getattr(self, "_xml", {}) + existed_attr_keys: list[str] = [] + + for rf in self._attr_to_rest_field.values(): + prop_meta = getattr(rf, "_xml", {}) + xml_name = prop_meta.get("name", rf._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + # attribute + if prop_meta.get("attribute", False) and element.get(xml_name) is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, element.get(xml_name)) + continue + + # unwrapped element is array + if prop_meta.get("unwrapped", False): + # unwrapped array could either use prop items meta/prop meta + _items_name = prop_meta.get("itemsName") + if _items_name: + xml_name = _items_name + _items_ns = prop_meta.get("itemsNs") + if _items_ns is not None: + xml_ns = _items_ns + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + items = element.findall(xml_name) # pyright: ignore + if len(items) > 0: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, items) + elif not rf._is_optional: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = [] + continue + + # text element is primitive type + if prop_meta.get("text", False): + if element.text is not None: + result[rf._rest_name] = _deserialize(rf._type, element.text) + continue + + # wrapped element could be normal property or array, it should only have one element + item = element.find(xml_name) + if item is not None: + existed_attr_keys.append(xml_name) + result[rf._rest_name] = _deserialize(rf._type, item) + + # rest thing is additional properties + for e in element: + if e.tag not in existed_attr_keys: + result[e.tag] = _convert_element(e) + + return result + + def copy(self) -> "Model": + return Model(self.__dict__) + + def __new__(cls, *args: typing.Any, **kwargs: typing.Any) -> Self: + if f"{cls.__module__}.{cls.__qualname__}" not in cls._calculated: + # we know the last nine classes in mro are going to be 'Model', '_MyMutableMapping', 'MutableMapping', + # 'Mapping', 'Collection', 'Sized', 'Iterable', 'Container' and 'object' + mros = cls.__mro__[:-9][::-1] # ignore parents, and reverse the mro order + attr_to_rest_field: dict[str, _RestField] = { # map attribute name to rest_field property + k: v for mro_class in mros for k, v in mro_class.__dict__.items() if k[0] != "_" and hasattr(v, "_type") + } + annotations = { + k: v + for mro_class in mros + if hasattr(mro_class, "__annotations__") + for k, v in mro_class.__annotations__.items() + } + for attr, rf in attr_to_rest_field.items(): + rf._module = cls.__module__ + if not rf._type: + rf._type = rf._get_deserialize_callable_from_annotation(annotations.get(attr, None)) + if not rf._rest_name_input: + rf._rest_name_input = attr + cls._attr_to_rest_field: dict[str, _RestField] = dict(attr_to_rest_field.items()) + cls._calculated.add(f"{cls.__module__}.{cls.__qualname__}") + + return super().__new__(cls) + + def __init_subclass__(cls, discriminator: typing.Optional[str] = None) -> None: + for base in cls.__bases__: + if hasattr(base, "__mapping__"): + base.__mapping__[discriminator or cls.__name__] = cls # type: ignore + + @classmethod + def _get_discriminator(cls, exist_discriminators) -> typing.Optional["_RestField"]: + for v in cls.__dict__.values(): + if isinstance(v, _RestField) and v._is_discriminator and v._rest_name not in exist_discriminators: + return v + return None + + @classmethod + def _deserialize(cls, data, exist_discriminators): + if not hasattr(cls, "__mapping__"): + return cls(data) + discriminator = cls._get_discriminator(exist_discriminators) + if discriminator is None: + return cls(data) + exist_discriminators.append(discriminator._rest_name) + if isinstance(data, ET.Element): + model_meta = getattr(cls, "_xml", {}) + prop_meta = getattr(discriminator, "_xml", {}) + xml_name = prop_meta.get("name", discriminator._rest_name) + xml_ns = _resolve_xml_ns(prop_meta, model_meta) + if xml_ns: + xml_name = "{" + xml_ns + "}" + xml_name + + if data.get(xml_name) is not None: + discriminator_value = data.get(xml_name) + else: + discriminator_value = data.find(xml_name).text # pyright: ignore + else: + discriminator_value = data.get(discriminator._rest_name) + mapped_cls = cls.__mapping__.get(discriminator_value, cls) # pyright: ignore # pylint: disable=no-member + return mapped_cls._deserialize(data, exist_discriminators) + + def as_dict(self, *, exclude_readonly: bool = False) -> dict[str, typing.Any]: + """Return a dict that can be turned into json using json.dump. + + :keyword bool exclude_readonly: Whether to remove the readonly properties. + :returns: A dict JSON compatible object + :rtype: dict + """ + + result = {} + readonly_props = [] + if exclude_readonly: + readonly_props = [p._rest_name for p in self._attr_to_rest_field.values() if _is_readonly(p)] + for k, v in self.items(): + if exclude_readonly and k in readonly_props: # pyright: ignore + continue + is_multipart_file_input = False + try: + is_multipart_file_input = next( + rf for rf in self._attr_to_rest_field.values() if rf._rest_name == k + )._is_multipart_file_input + except StopIteration: + pass + result[k] = v if is_multipart_file_input else Model._as_dict_value(v, exclude_readonly=exclude_readonly) + return result + + @staticmethod + def _as_dict_value(v: typing.Any, exclude_readonly: bool = False) -> typing.Any: + if v is None or isinstance(v, _Null): + return None + if isinstance(v, (list, tuple, set)): + return type(v)(Model._as_dict_value(x, exclude_readonly=exclude_readonly) for x in v) + if isinstance(v, dict): + return {dk: Model._as_dict_value(dv, exclude_readonly=exclude_readonly) for dk, dv in v.items()} + return v.as_dict(exclude_readonly=exclude_readonly) if hasattr(v, "as_dict") else v + + +def _deserialize_model(model_deserializer: typing.Optional[typing.Callable], obj): + if _is_model(obj): + return obj + return _deserialize(model_deserializer, obj) + + +def _deserialize_with_optional(if_obj_deserializer: typing.Optional[typing.Callable], obj): + if obj is None: + return obj + return _deserialize_with_callable(if_obj_deserializer, obj) + + +def _deserialize_with_union(deserializers, obj): + for deserializer in deserializers: + try: + return _deserialize(deserializer, obj) + except DeserializationError: + pass + raise DeserializationError() + + +def _deserialize_dict( + value_deserializer: typing.Optional[typing.Callable], + module: typing.Optional[str], + obj: dict[typing.Any, typing.Any], +): + if obj is None: + return obj + if isinstance(obj, ET.Element): + obj = {child.tag: child for child in obj} + return {k: _deserialize(value_deserializer, v, module) for k, v in obj.items()} + + +def _deserialize_multiple_sequence( + entry_deserializers: list[typing.Optional[typing.Callable]], + module: typing.Optional[str], + obj, +): + if obj is None: + return obj + return type(obj)(_deserialize(deserializer, entry, module) for entry, deserializer in zip(obj, entry_deserializers)) + + +def _is_array_encoded_deserializer(deserializer: functools.partial) -> bool: + return ( + isinstance(deserializer, functools.partial) + and isinstance(deserializer.args[0], functools.partial) + and deserializer.args[0].func == _deserialize_array_encoded # pylint: disable=comparison-with-callable + ) + + +def _deserialize_sequence( + deserializer: typing.Optional[typing.Callable], + module: typing.Optional[str], + obj, +): + if obj is None: + return obj + if isinstance(obj, ET.Element): + obj = list(obj) + + # encoded string may be deserialized to sequence + if isinstance(obj, str) and isinstance(deserializer, functools.partial): + # for list[str] + if _is_array_encoded_deserializer(deserializer): + return deserializer(obj) + + # for list[Union[...]] + if isinstance(deserializer.args[0], list): + for sub_deserializer in deserializer.args[0]: + if _is_array_encoded_deserializer(sub_deserializer): + return sub_deserializer(obj) + + return type(obj)(_deserialize(deserializer, entry, module) for entry in obj) + + +def _sorted_annotations(types: list[typing.Any]) -> list[typing.Any]: + return sorted( + types, + key=lambda x: hasattr(x, "__name__") and x.__name__.lower() in ("str", "float", "int", "bool"), + ) + + +def _get_deserialize_callable_from_annotation( # pylint: disable=too-many-return-statements, too-many-statements, too-many-branches + annotation: typing.Any, + module: typing.Optional[str], + rf: typing.Optional["_RestField"] = None, +) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]: + if not annotation: + return None + + # is it a type alias? + if isinstance(annotation, str): + if module is not None: + annotation = _get_type_alias_type(module, annotation) + + # is it a forward ref / in quotes? + if isinstance(annotation, (str, typing.ForwardRef)): + try: + model_name = annotation.__forward_arg__ # type: ignore + except AttributeError: + model_name = annotation + if module is not None: + annotation = _get_model(module, model_name) # type: ignore + + try: + if module and _is_model(annotation): + if rf: + rf._is_model = True + + return functools.partial(_deserialize_model, annotation) # pyright: ignore + except Exception: + pass + + # is it a literal? + try: + if annotation.__origin__ is typing.Literal: # pyright: ignore + return None + except AttributeError: + pass + + # is it optional? + try: + if any(a is _NONE_TYPE for a in annotation.__args__): # pyright: ignore + if rf: + rf._is_optional = True + if len(annotation.__args__) <= 2: # pyright: ignore + if_obj_deserializer = _get_deserialize_callable_from_annotation( + next(a for a in annotation.__args__ if a is not _NONE_TYPE), module, rf # pyright: ignore + ) + + return functools.partial(_deserialize_with_optional, if_obj_deserializer) + # the type is Optional[Union[...]], we need to remove the None type from the Union + annotation_copy = copy.copy(annotation) + annotation_copy.__args__ = [a for a in annotation_copy.__args__ if a is not _NONE_TYPE] # pyright: ignore + return _get_deserialize_callable_from_annotation(annotation_copy, module, rf) + except AttributeError: + pass + + # is it union? + if getattr(annotation, "__origin__", None) is typing.Union: + # initial ordering is we make `string` the last deserialization option, because it is often them most generic + deserializers = [ + _get_deserialize_callable_from_annotation(arg, module, rf) + for arg in _sorted_annotations(annotation.__args__) # pyright: ignore + ] + + return functools.partial(_deserialize_with_union, deserializers) + + try: + annotation_name = ( + annotation.__name__ if hasattr(annotation, "__name__") else annotation._name # pyright: ignore + ) + if annotation_name.lower() == "dict": + value_deserializer = _get_deserialize_callable_from_annotation( + annotation.__args__[1], module, rf # pyright: ignore + ) + + return functools.partial( + _deserialize_dict, + value_deserializer, + module, + ) + except (AttributeError, IndexError): + pass + try: + annotation_name = ( + annotation.__name__ if hasattr(annotation, "__name__") else annotation._name # pyright: ignore + ) + if annotation_name.lower() in ["list", "set", "tuple", "sequence"]: + if len(annotation.__args__) > 1: # pyright: ignore + entry_deserializers = [ + _get_deserialize_callable_from_annotation(dt, module, rf) + for dt in annotation.__args__ # pyright: ignore + ] + return functools.partial(_deserialize_multiple_sequence, entry_deserializers, module) + deserializer = _get_deserialize_callable_from_annotation( + annotation.__args__[0], module, rf # pyright: ignore + ) + + return functools.partial(_deserialize_sequence, deserializer, module) + except (TypeError, IndexError, AttributeError, SyntaxError): + pass + + def _deserialize_default( + deserializer, + obj, + ): + if obj is None: + return obj + try: + return _deserialize_with_callable(deserializer, obj) + except Exception: + pass + return obj + + if get_deserializer(annotation, rf): + return functools.partial(_deserialize_default, get_deserializer(annotation, rf)) + + return functools.partial(_deserialize_default, annotation) + + +def _deserialize_with_callable( + deserializer: typing.Optional[typing.Callable[[typing.Any], typing.Any]], + value: typing.Any, +): # pylint: disable=too-many-return-statements + try: + if value is None or isinstance(value, _Null): + return None + if isinstance(value, ET.Element): + if deserializer is str: + return value.text or "" + if deserializer is int: + return int(value.text) if value.text else None + if deserializer is float: + return float(value.text) if value.text else None + if deserializer is bool: + return value.text == "true" if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING.values(): + return deserializer(value.text) if value.text else None + if deserializer and deserializer in _DESERIALIZE_MAPPING_WITHFORMAT.values(): + return deserializer(value.text) if value.text else None + if deserializer is None: + return value + if deserializer in [int, float, bool]: + return deserializer(value) + if isinstance(deserializer, CaseInsensitiveEnumMeta): + try: + return deserializer(value.text if isinstance(value, ET.Element) else value) + except ValueError: + # for unknown value, return raw value + return value.text if isinstance(value, ET.Element) else value + if isinstance(deserializer, type) and issubclass(deserializer, Model): + return deserializer._deserialize(value, []) + return typing.cast(typing.Callable[[typing.Any], typing.Any], deserializer)(value) + except Exception as e: + raise DeserializationError() from e + + +def _deserialize( + deserializer: typing.Any, + value: typing.Any, + module: typing.Optional[str] = None, + rf: typing.Optional["_RestField"] = None, + format: typing.Optional[str] = None, +) -> typing.Any: + if isinstance(value, PipelineResponse): + value = value.http_response.json() + if rf is None and format: + rf = _RestField(format=format) + if not isinstance(deserializer, functools.partial): + deserializer = _get_deserialize_callable_from_annotation(deserializer, module, rf) + return _deserialize_with_callable(deserializer, value) + + +def _failsafe_deserialize( + deserializer: typing.Any, + response: HttpResponse, + module: typing.Optional[str] = None, + rf: typing.Optional["_RestField"] = None, + format: typing.Optional[str] = None, +) -> typing.Any: + try: + return _deserialize(deserializer, response.json(), module, rf, format) + except Exception: # pylint: disable=broad-except + _LOGGER.warning( + "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True + ) + return None + + +def _failsafe_deserialize_xml( + deserializer: typing.Any, + response: HttpResponse, +) -> typing.Any: + try: + return _deserialize_xml(deserializer, response.text()) + except Exception: # pylint: disable=broad-except + _LOGGER.warning( + "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True + ) + return None + + +# pylint: disable=too-many-instance-attributes +class _RestField: + def __init__( + self, + *, + name: typing.Optional[str] = None, + type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin + is_discriminator: bool = False, + visibility: typing.Optional[list[str]] = None, + default: typing.Any = _UNSET, + format: typing.Optional[str] = None, + is_multipart_file_input: bool = False, + xml: typing.Optional[dict[str, typing.Any]] = None, + ): + self._type = type + self._rest_name_input = name + self._module: typing.Optional[str] = None + self._is_discriminator = is_discriminator + self._visibility = visibility + self._is_model = False + self._is_optional = False + self._default = default + self._format = format + self._is_multipart_file_input = is_multipart_file_input + self._xml = xml if xml is not None else {} + + @property + def _class_type(self) -> typing.Any: + result = getattr(self._type, "args", [None])[0] + # type may be wrapped by nested functools.partial so we need to check for that + if isinstance(result, functools.partial): + return getattr(result, "args", [None])[0] + return result + + @property + def _rest_name(self) -> str: + if self._rest_name_input is None: + raise ValueError("Rest name was never set") + return self._rest_name_input + + def __get__(self, obj: Model, type=None): # pylint: disable=redefined-builtin + # by this point, type and rest_name will have a value bc we default + # them in __new__ of the Model class + # Use _data.get() directly to avoid triggering __getitem__ which clears the cache + item = obj._data.get(self._rest_name) + if item is None: + return item + if self._is_model: + return item + + # For mutable types, we want mutations to directly affect _data + # Check if we've already deserialized this value + cache_attr = f"_deserialized_{self._rest_name}" + if hasattr(obj, cache_attr): + # Return the value from _data directly (it's been deserialized in place) + return obj._data.get(self._rest_name) + + deserialized = _deserialize(self._type, _serialize(item, self._format), rf=self) + + # For mutable types, store the deserialized value back in _data + # so mutations directly affect _data + if isinstance(deserialized, (dict, list, set)): + obj._data[self._rest_name] = deserialized + object.__setattr__(obj, cache_attr, True) # Mark as deserialized + return deserialized + + return deserialized + + def __set__(self, obj: Model, value) -> None: + # Clear the cached deserialized object when setting a new value + cache_attr = f"_deserialized_{self._rest_name}" + if hasattr(obj, cache_attr): + object.__delattr__(obj, cache_attr) + + if value is None: + # we want to wipe out entries if users set attr to None + try: + obj.__delitem__(self._rest_name) + except KeyError: + pass + return + if self._is_model: + if not _is_model(value): + value = _deserialize(self._type, value) + obj.__setitem__(self._rest_name, value) + return + obj.__setitem__(self._rest_name, _serialize(value, self._format)) + + def _get_deserialize_callable_from_annotation( + self, annotation: typing.Any + ) -> typing.Optional[typing.Callable[[typing.Any], typing.Any]]: + return _get_deserialize_callable_from_annotation(annotation, self._module, self) + + +def rest_field( + *, + name: typing.Optional[str] = None, + type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin + visibility: typing.Optional[list[str]] = None, + default: typing.Any = _UNSET, + format: typing.Optional[str] = None, + is_multipart_file_input: bool = False, + xml: typing.Optional[dict[str, typing.Any]] = None, +) -> typing.Any: + return _RestField( + name=name, + type=type, + visibility=visibility, + default=default, + format=format, + is_multipart_file_input=is_multipart_file_input, + xml=xml, + ) + + +def rest_discriminator( + *, + name: typing.Optional[str] = None, + type: typing.Optional[typing.Callable] = None, # pylint: disable=redefined-builtin + visibility: typing.Optional[list[str]] = None, + xml: typing.Optional[dict[str, typing.Any]] = None, +) -> typing.Any: + return _RestField(name=name, type=type, is_discriminator=True, visibility=visibility, xml=xml) + + +def serialize_xml(model: Model, exclude_readonly: bool = False) -> str: + """Serialize a model to XML. + + :param Model model: The model to serialize. + :param bool exclude_readonly: Whether to exclude readonly properties. + :returns: The XML representation of the model. + :rtype: str + """ + return ET.tostring(_get_element(model, exclude_readonly), encoding="unicode") # type: ignore + + +def _get_xml_ns(meta: dict[str, typing.Any]) -> typing.Optional[str]: + """Return the XML namespace from a metadata dict, checking both 'ns' (old-style) and 'namespace' (DPG) keys. + + :param dict meta: The metadata dictionary to extract namespace from. + :returns: The namespace string if 'ns' or 'namespace' key is present, None otherwise. + :rtype: str or None + """ + ns = meta.get("ns") + if ns is None: + ns = meta.get("namespace") + return ns + + +def _resolve_xml_ns( + prop_meta: dict[str, typing.Any], model_meta: typing.Optional[dict[str, typing.Any]] = None +) -> typing.Optional[str]: + """Resolve XML namespace for a property, falling back to model namespace when appropriate. + + Checks the property metadata first; if no namespace is found and the model does not declare + an explicit prefix, falls back to the model-level namespace. + + :param dict prop_meta: The property metadata dictionary. + :param dict model_meta: The model metadata dictionary, used as fallback. + :returns: The resolved namespace string, or None. + :rtype: str or None + """ + ns = _get_xml_ns(prop_meta) + if ns is None and model_meta is not None and not model_meta.get("prefix"): + ns = _get_xml_ns(model_meta) + return ns + + +def _set_xml_attribute(element: ET.Element, name: str, value: typing.Any, prop_meta: dict[str, typing.Any]) -> None: + """Set an XML attribute on an element, handling namespace prefix registration. + + :param ET.Element element: The element to set the attribute on. + :param str name: The default attribute name (wire name). + :param any value: The attribute value. + :param dict prop_meta: The property metadata dictionary. + """ + xml_name = prop_meta.get("name", name) + _attr_ns = _get_xml_ns(prop_meta) + if _attr_ns: + _attr_prefix = prop_meta.get("prefix") + if _attr_prefix: + _safe_register_namespace(_attr_prefix, _attr_ns) + xml_name = "{" + _attr_ns + "}" + xml_name + element.set(xml_name, _get_primitive_type_value(value)) + + +def _get_element( + o: typing.Any, + exclude_readonly: bool = False, + parent_meta: typing.Optional[dict[str, typing.Any]] = None, + wrapped_element: typing.Optional[ET.Element] = None, +) -> typing.Union[ET.Element, list[ET.Element]]: + if _is_model(o): + model_meta = getattr(o, "_xml", {}) + + # if prop is a model, then use the prop element directly, else generate a wrapper of model + if wrapped_element is None: + # When serializing as an array item (parent_meta is set), check if the parent has an + # explicit itemsName. This ensures correct element names for unwrapped arrays (where + # the element tag is the property/items name, not the model type name). + _items_name = parent_meta.get("itemsName") if parent_meta is not None else None + element_name = _items_name if _items_name else (model_meta.get("name") or o.__class__.__name__) + _model_ns = _get_xml_ns(model_meta) + wrapped_element = _create_xml_element( + element_name, + model_meta.get("prefix"), + _model_ns, + ) + + readonly_props = [] + if exclude_readonly: + readonly_props = [p._rest_name for p in o._attr_to_rest_field.values() if _is_readonly(p)] + + for k, v in o.items(): + # do not serialize readonly properties + if exclude_readonly and k in readonly_props: + continue + + prop_rest_field = _get_rest_field(o._attr_to_rest_field, k) + if prop_rest_field: + prop_meta = getattr(prop_rest_field, "_xml").copy() + # use the wire name as xml name if no specific name is set + if prop_meta.get("name") is None: + prop_meta["name"] = k + else: + # additional properties will not have rest field, use the wire name as xml name + prop_meta = {"name": k} + + # Propagate model namespace to properties only for old-style "ns"-keyed models. + # DPG-generated models use the "namespace" key and explicitly declare namespace on + # each property that needs it, so propagation is intentionally skipped for them. + if prop_meta.get("ns") is None and model_meta.get("ns"): + prop_meta["ns"] = model_meta.get("ns") + prop_meta["prefix"] = model_meta.get("prefix") + + if prop_meta.get("unwrapped", False): + # unwrapped could only set on array + wrapped_element.extend(_get_element(v, exclude_readonly, prop_meta)) + elif prop_meta.get("text", False): + # text could only set on primitive type + wrapped_element.text = _get_primitive_type_value(v) + elif prop_meta.get("attribute", False): + _set_xml_attribute(wrapped_element, k, v, prop_meta) + else: + # other wrapped prop element + wrapped_element.append(_get_wrapped_element(v, exclude_readonly, prop_meta)) + return wrapped_element + if isinstance(o, list): + return [_get_element(x, exclude_readonly, parent_meta) for x in o] # type: ignore + if isinstance(o, dict): + result = [] + _dict_ns = _get_xml_ns(parent_meta) if parent_meta else None + for k, v in o.items(): + result.append( + _get_wrapped_element( + v, + exclude_readonly, + { + "name": k, + "ns": _dict_ns, + "prefix": parent_meta.get("prefix") if parent_meta else None, + }, + ) + ) + return result + + # primitive case need to create element based on parent_meta + if parent_meta: + _items_ns = parent_meta.get("itemsNs") + if _items_ns is None: + _items_ns = _get_xml_ns(parent_meta) + return _get_wrapped_element( + o, + exclude_readonly, + { + "name": parent_meta.get("itemsName", parent_meta.get("name")), + "prefix": parent_meta.get("itemsPrefix", parent_meta.get("prefix")), + "ns": _items_ns, + }, + ) + + raise ValueError("Could not serialize value into xml: " + o) + + +def _get_wrapped_element( + v: typing.Any, + exclude_readonly: bool, + meta: typing.Optional[dict[str, typing.Any]], +) -> ET.Element: + _meta_ns = _get_xml_ns(meta) if meta else None + wrapped_element = _create_xml_element( + meta.get("name") if meta else None, meta.get("prefix") if meta else None, _meta_ns + ) + if isinstance(v, (dict, list)): + wrapped_element.extend(_get_element(v, exclude_readonly, meta)) + elif _is_model(v): + _get_element(v, exclude_readonly, meta, wrapped_element) + else: + wrapped_element.text = _get_primitive_type_value(v) + return wrapped_element # type: ignore[no-any-return] + + +def _get_primitive_type_value(v) -> str: + if v is True: + return "true" + if v is False: + return "false" + if isinstance(v, _Null): + return "" + return str(v) + + +def _safe_register_namespace(prefix: str, ns: str) -> None: + """Register an XML namespace prefix, handling reserved prefix patterns. + + Some prefixes (e.g. 'ns2') match Python's reserved 'ns\\d+' pattern used for + auto-generated prefixes, causing register_namespace to raise ValueError. + Falls back to directly registering in the internal namespace map. + + :param str prefix: The namespace prefix to register. + :param str ns: The namespace URI. + """ + try: + ET.register_namespace(prefix, ns) + except ValueError: + _ns_map = getattr(ET, "_namespace_map", None) + if _ns_map is not None: + _ns_map[ns] = prefix + + +def _create_xml_element( + tag: typing.Any, prefix: typing.Optional[str] = None, ns: typing.Optional[str] = None +) -> ET.Element: + if prefix and ns: + _safe_register_namespace(prefix, ns) + if ns: + return ET.Element("{" + ns + "}" + tag) + return ET.Element(tag) + + +def _deserialize_xml( + deserializer: typing.Any, + value: str, +) -> typing.Any: + element = ET.fromstring(value) # nosec + return _deserialize(deserializer, element) + + +def _convert_element(e: ET.Element): + # dict case + if len(e.attrib) > 0 or len({child.tag for child in e}) > 1: + dict_result: dict[str, typing.Any] = {} + for child in e: + if dict_result.get(child.tag) is not None: + if isinstance(dict_result[child.tag], list): + dict_result[child.tag].append(_convert_element(child)) + else: + dict_result[child.tag] = [dict_result[child.tag], _convert_element(child)] + else: + dict_result[child.tag] = _convert_element(child) + dict_result.update(e.attrib) + return dict_result + # array case + if len(e) > 0: + array_result: list[typing.Any] = [] + for child in e: + array_result.append(_convert_element(child)) + return array_result + # primitive case + return e.text diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/serialization.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/serialization.py new file mode 100644 index 000000000000..81ec1de5922b --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_utils/serialization.py @@ -0,0 +1,2041 @@ +# pylint: disable=line-too-long,useless-suppression,too-many-lines +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +# pyright: reportUnnecessaryTypeIgnoreComment=false + +from base64 import b64decode, b64encode +import calendar +import datetime +import decimal +import email +from enum import Enum +import json +import logging +import re +import sys +import codecs +from typing import ( + Any, + cast, + Optional, + Union, + AnyStr, + IO, + Mapping, + Callable, + MutableMapping, +) + +try: + from urllib import quote # type: ignore +except ImportError: + from urllib.parse import quote +import xml.etree.ElementTree as ET + +import isodate # type: ignore +from typing_extensions import Self + +from azure.core.exceptions import DeserializationError, SerializationError +from azure.core.serialization import NULL as CoreNull + +_BOM = codecs.BOM_UTF8.decode(encoding="utf-8") + +JSON = MutableMapping[str, Any] + + +class RawDeserializer: + + # Accept "text" because we're open minded people... + JSON_REGEXP = re.compile(r"^(application|text)/([a-z+.]+\+)?json$") + + # Name used in context + CONTEXT_NAME = "deserialized_data" + + @classmethod + def deserialize_from_text(cls, data: Optional[Union[AnyStr, IO]], content_type: Optional[str] = None) -> Any: + """Decode data according to content-type. + + Accept a stream of data as well, but will be load at once in memory for now. + + If no content-type, will return the string version (not bytes, not stream) + + :param data: Input, could be bytes or stream (will be decoded with UTF8) or text + :type data: str or bytes or IO + :param str content_type: The content type. + :return: The deserialized data. + :rtype: object + """ + if hasattr(data, "read"): + # Assume a stream + data = cast(IO, data).read() + + if isinstance(data, bytes): + data_as_str = data.decode(encoding="utf-8-sig") + else: + # Explain to mypy the correct type. + data_as_str = cast(str, data) + + # Remove Byte Order Mark if present in string + data_as_str = data_as_str.lstrip(_BOM) + + if content_type is None: + return data + + if cls.JSON_REGEXP.match(content_type): + try: + return json.loads(data_as_str) + except ValueError as err: + raise DeserializationError("JSON is invalid: {}".format(err), err) from err + elif "xml" in (content_type or []): + try: + + try: + if isinstance(data, unicode): # type: ignore + # If I'm Python 2.7 and unicode XML will scream if I try a "fromstring" on unicode string + data_as_str = data_as_str.encode(encoding="utf-8") # type: ignore + except NameError: + pass + + return ET.fromstring(data_as_str) # nosec + except ET.ParseError as err: + # It might be because the server has an issue, and returned JSON with + # content-type XML.... + # So let's try a JSON load, and if it's still broken + # let's flow the initial exception + def _json_attemp(data): + try: + return True, json.loads(data) + except ValueError: + return False, None # Don't care about this one + + success, json_result = _json_attemp(data) + if success: + return json_result + # If i'm here, it's not JSON, it's not XML, let's scream + # and raise the last context in this block (the XML exception) + # The function hack is because Py2.7 messes up with exception + # context otherwise. + _LOGGER.critical("Wasn't XML not JSON, failing") + raise DeserializationError("XML is invalid") from err + elif content_type.startswith("text/"): + return data_as_str + raise DeserializationError("Cannot deserialize content-type: {}".format(content_type)) + + @classmethod + def deserialize_from_http_generics(cls, body_bytes: Optional[Union[AnyStr, IO]], headers: Mapping) -> Any: + """Deserialize from HTTP response. + + Use bytes and headers to NOT use any requests/aiohttp or whatever + specific implementation. + Headers will tested for "content-type" + + :param bytes body_bytes: The body of the response. + :param dict headers: The headers of the response. + :returns: The deserialized data. + :rtype: object + """ + # Try to use content-type from headers if available + content_type = None + if "content-type" in headers: + content_type = headers["content-type"].split(";")[0].strip().lower() + # Ouch, this server did not declare what it sent... + # Let's guess it's JSON... + # Also, since Autorest was considering that an empty body was a valid JSON, + # need that test as well.... + else: + content_type = "application/json" + + if body_bytes: + return cls.deserialize_from_text(body_bytes, content_type) + return None + + +_LOGGER = logging.getLogger(__name__) + +try: + _long_type = long # type: ignore +except NameError: + _long_type = int + +TZ_UTC = datetime.timezone.utc + +_FLATTEN = re.compile(r"(? None: + self.additional_properties: Optional[dict[str, Any]] = {} + for k in kwargs: # pylint: disable=consider-using-dict-items + if k not in self._attribute_map: + _LOGGER.warning("%s is not a known attribute of class %s and will be ignored", k, self.__class__) + elif k in self._validation and self._validation[k].get("readonly", False): + _LOGGER.warning("Readonly attribute %s will be ignored in class %s", k, self.__class__) + else: + setattr(self, k, kwargs[k]) + + def __eq__(self, other: Any) -> bool: + """Compare objects by comparing all attributes. + + :param object other: The object to compare + :returns: True if objects are equal + :rtype: bool + """ + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + return False + + def __ne__(self, other: Any) -> bool: + """Compare objects by comparing all attributes. + + :param object other: The object to compare + :returns: True if objects are not equal + :rtype: bool + """ + return not self.__eq__(other) + + def __str__(self) -> str: + return str(self.__dict__) + + @classmethod + def enable_additional_properties_sending(cls) -> None: + cls._attribute_map["additional_properties"] = {"key": "", "type": "{object}"} + + @classmethod + def is_xml_model(cls) -> bool: + try: + cls._xml_map # type: ignore + except AttributeError: + return False + return True + + @classmethod + def _create_xml_node(cls): + """Create XML node. + + :returns: The XML node + :rtype: xml.etree.ElementTree.Element + """ + try: + xml_map = cls._xml_map # type: ignore + except AttributeError: + xml_map = {} + + return _create_xml_node(xml_map.get("name", cls.__name__), xml_map.get("prefix", None), xml_map.get("ns", None)) + + def serialize(self, keep_readonly: bool = False, **kwargs: Any) -> JSON: + """Return the JSON that would be sent to server from this model. + + This is an alias to `as_dict(full_restapi_key_transformer, keep_readonly=False)`. + + If you want XML serialization, you can pass the kwargs is_xml=True. + + :param bool keep_readonly: If you want to serialize the readonly attributes + :returns: A dict JSON compatible object + :rtype: dict + """ + serializer = Serializer(self._infer_class_models()) + return serializer._serialize( # type: ignore # pylint: disable=protected-access + self, keep_readonly=keep_readonly, **kwargs + ) + + def as_dict( + self, + keep_readonly: bool = True, + key_transformer: Callable[[str, dict[str, Any], Any], Any] = attribute_transformer, + **kwargs: Any + ) -> JSON: + """Return a dict that can be serialized using json.dump. + + Advanced usage might optionally use a callback as parameter: + + .. code::python + + def my_key_transformer(key, attr_desc, value): + return key + + Key is the attribute name used in Python. Attr_desc + is a dict of metadata. Currently contains 'type' with the + msrest type and 'key' with the RestAPI encoded key. + Value is the current value in this object. + + The string returned will be used to serialize the key. + If the return type is a list, this is considered hierarchical + result dict. + + See the three examples in this file: + + - attribute_transformer + - full_restapi_key_transformer + - last_restapi_key_transformer + + If you want XML serialization, you can pass the kwargs is_xml=True. + + :param bool keep_readonly: If you want to serialize the readonly attributes + :param function key_transformer: A key transformer function. + :returns: A dict JSON compatible object + :rtype: dict + """ + serializer = Serializer(self._infer_class_models()) + return serializer._serialize( # type: ignore # pylint: disable=protected-access + self, key_transformer=key_transformer, keep_readonly=keep_readonly, **kwargs + ) + + @classmethod + def _infer_class_models(cls): + try: + str_models = cls.__module__.rsplit(".", 1)[0] + models = sys.modules[str_models] + client_models = {k: v for k, v in models.__dict__.items() if isinstance(v, type)} + if cls.__name__ not in client_models: + raise ValueError("Not Autorest generated code") + except Exception: # pylint: disable=broad-exception-caught + # Assume it's not Autorest generated (tests?). Add ourselves as dependencies. + client_models = {cls.__name__: cls} + return client_models + + @classmethod + def deserialize(cls, data: Any, content_type: Optional[str] = None) -> Self: + """Parse a str using the RestAPI syntax and return a model. + + :param str data: A str using RestAPI structure. JSON by default. + :param str content_type: JSON by default, set application/xml if XML. + :returns: An instance of this model + :raises DeserializationError: if something went wrong + :rtype: Self + """ + deserializer = Deserializer(cls._infer_class_models()) + return deserializer(cls.__name__, data, content_type=content_type) # type: ignore + + @classmethod + def from_dict( + cls, + data: Any, + key_extractors: Optional[Callable[[str, dict[str, Any], Any], Any]] = None, + content_type: Optional[str] = None, + ) -> Self: + """Parse a dict using given key extractor return a model. + + By default consider key + extractors (rest_key_case_insensitive_extractor, attribute_key_case_insensitive_extractor + and last_rest_key_case_insensitive_extractor) + + :param dict data: A dict using RestAPI structure + :param function key_extractors: A key extractor function. + :param str content_type: JSON by default, set application/xml if XML. + :returns: An instance of this model + :raises DeserializationError: if something went wrong + :rtype: Self + """ + deserializer = Deserializer(cls._infer_class_models()) + deserializer.key_extractors = ( # type: ignore + [ # type: ignore + attribute_key_case_insensitive_extractor, + rest_key_case_insensitive_extractor, + last_rest_key_case_insensitive_extractor, + ] + if key_extractors is None + else key_extractors + ) + return deserializer(cls.__name__, data, content_type=content_type) # type: ignore + + @classmethod + def _flatten_subtype(cls, key, objects): + if "_subtype_map" not in cls.__dict__: + return {} + result = dict(cls._subtype_map[key]) + for valuetype in cls._subtype_map[key].values(): + result |= objects[valuetype]._flatten_subtype(key, objects) # pylint: disable=protected-access + return result + + @classmethod + def _classify(cls, response, objects): + """Check the class _subtype_map for any child classes. + We want to ignore any inherited _subtype_maps. + + :param dict response: The initial data + :param dict objects: The class objects + :returns: The class to be used + :rtype: class + """ + for subtype_key in cls.__dict__.get("_subtype_map", {}).keys(): + subtype_value = None + + if not isinstance(response, ET.Element): + rest_api_response_key = cls._get_rest_key_parts(subtype_key)[-1] + subtype_value = response.get(rest_api_response_key, None) or response.get(subtype_key, None) + else: + subtype_value = xml_key_extractor(subtype_key, cls._attribute_map[subtype_key], response) + if subtype_value: + # Try to match base class. Can be class name only + # (bug to fix in Autorest to support x-ms-discriminator-name) + if cls.__name__ == subtype_value: + return cls + flatten_mapping_type = cls._flatten_subtype(subtype_key, objects) + try: + return objects[flatten_mapping_type[subtype_value]] # type: ignore + except KeyError: + _LOGGER.warning( + "Subtype value %s has no mapping, use base class %s.", + subtype_value, + cls.__name__, + ) + break + else: + _LOGGER.warning("Discriminator %s is absent or null, use base class %s.", subtype_key, cls.__name__) + break + return cls + + @classmethod + def _get_rest_key_parts(cls, attr_key): + """Get the RestAPI key of this attr, split it and decode part + :param str attr_key: Attribute key must be in attribute_map. + :returns: A list of RestAPI part + :rtype: list + """ + rest_split_key = _FLATTEN.split(cls._attribute_map[attr_key]["key"]) + return [_decode_attribute_map_key(key_part) for key_part in rest_split_key] + + +def _decode_attribute_map_key(key): + """This decode a key in an _attribute_map to the actual key we want to look at + inside the received data. + + :param str key: A key string from the generated code + :returns: The decoded key + :rtype: str + """ + return key.replace("\\.", ".") + + +class Serializer: # pylint: disable=too-many-public-methods + """Request object model serializer.""" + + basic_types = {str: "str", int: "int", bool: "bool", float: "float"} + + _xml_basic_types_serializers = {"bool": lambda x: str(x).lower()} + days = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri", 5: "Sat", 6: "Sun"} + months = { + 1: "Jan", + 2: "Feb", + 3: "Mar", + 4: "Apr", + 5: "May", + 6: "Jun", + 7: "Jul", + 8: "Aug", + 9: "Sep", + 10: "Oct", + 11: "Nov", + 12: "Dec", + } + validation = { + "min_length": lambda x, y: len(x) < y, + "max_length": lambda x, y: len(x) > y, + "minimum": lambda x, y: x < y, + "maximum": lambda x, y: x > y, + "minimum_ex": lambda x, y: x <= y, + "maximum_ex": lambda x, y: x >= y, + "min_items": lambda x, y: len(x) < y, + "max_items": lambda x, y: len(x) > y, + "pattern": lambda x, y: not re.match(y, x, re.UNICODE), + "unique": lambda x, y: len(x) != len(set(x)), + "multiple": lambda x, y: x % y != 0, + } + + def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: + self.serialize_type = { + "iso-8601": Serializer.serialize_iso, + "rfc-1123": Serializer.serialize_rfc, + "unix-time": Serializer.serialize_unix, + "duration": Serializer.serialize_duration, + "date": Serializer.serialize_date, + "time": Serializer.serialize_time, + "decimal": Serializer.serialize_decimal, + "long": Serializer.serialize_long, + "bytearray": Serializer.serialize_bytearray, + "base64": Serializer.serialize_base64, + "object": self.serialize_object, + "[]": self.serialize_iter, + "{}": self.serialize_dict, + } + self.dependencies: dict[str, type] = dict(classes) if classes else {} + self.key_transformer = full_restapi_key_transformer + self.client_side_validation = True + + def _serialize( # pylint: disable=too-many-nested-blocks, too-many-branches, too-many-statements, too-many-locals + self, target_obj, data_type=None, **kwargs + ): + """Serialize data into a string according to type. + + :param object target_obj: The data to be serialized. + :param str data_type: The type to be serialized from. + :rtype: str, dict + :raises SerializationError: if serialization fails. + :returns: The serialized data. + """ + key_transformer = kwargs.get("key_transformer", self.key_transformer) + keep_readonly = kwargs.get("keep_readonly", False) + if target_obj is None: + return None + + attr_name = None + class_name = target_obj.__class__.__name__ + + if data_type: + return self.serialize_data(target_obj, data_type, **kwargs) + + if not hasattr(target_obj, "_attribute_map"): + data_type = type(target_obj).__name__ + if data_type in self.basic_types.values(): + return self.serialize_data(target_obj, data_type, **kwargs) + + # Force "is_xml" kwargs if we detect a XML model + try: + is_xml_model_serialization = kwargs["is_xml"] + except KeyError: + is_xml_model_serialization = kwargs.setdefault("is_xml", target_obj.is_xml_model()) + + serialized = {} + if is_xml_model_serialization: + serialized = target_obj._create_xml_node() # pylint: disable=protected-access + try: + attributes = target_obj._attribute_map # pylint: disable=protected-access + for attr, attr_desc in attributes.items(): + attr_name = attr + if not keep_readonly and target_obj._validation.get( # pylint: disable=protected-access + attr_name, {} + ).get("readonly", False): + continue + + if attr_name == "additional_properties" and attr_desc["key"] == "": + if target_obj.additional_properties is not None: + serialized |= target_obj.additional_properties + continue + try: + + orig_attr = getattr(target_obj, attr) + if is_xml_model_serialization: + pass # Don't provide "transformer" for XML for now. Keep "orig_attr" + else: # JSON + keys, orig_attr = key_transformer(attr, attr_desc.copy(), orig_attr) + keys = keys if isinstance(keys, list) else [keys] + + kwargs["serialization_ctxt"] = attr_desc + new_attr = self.serialize_data(orig_attr, attr_desc["type"], **kwargs) + + if is_xml_model_serialization: + xml_desc = attr_desc.get("xml", {}) + xml_name = xml_desc.get("name", attr_desc["key"]) + xml_prefix = xml_desc.get("prefix", None) + xml_ns = xml_desc.get("ns", None) + if xml_desc.get("attr", False): + if xml_ns: + ET.register_namespace(xml_prefix, xml_ns) + xml_name = "{{{}}}{}".format(xml_ns, xml_name) + serialized.set(xml_name, new_attr) # type: ignore + continue + if xml_desc.get("text", False): + serialized.text = new_attr # type: ignore + continue + if isinstance(new_attr, list): + serialized.extend(new_attr) # type: ignore + elif isinstance(new_attr, ET.Element): + # If the down XML has no XML/Name, + # we MUST replace the tag with the local tag. But keeping the namespaces. + if "name" not in getattr(orig_attr, "_xml_map", {}): + splitted_tag = new_attr.tag.split("}") + if len(splitted_tag) == 2: # Namespace + new_attr.tag = "}".join([splitted_tag[0], xml_name]) + else: + new_attr.tag = xml_name + serialized.append(new_attr) # type: ignore + else: # That's a basic type + # Integrate namespace if necessary + local_node = _create_xml_node(xml_name, xml_prefix, xml_ns) + local_node.text = str(new_attr) + serialized.append(local_node) # type: ignore + else: # JSON + for k in reversed(keys): # type: ignore + new_attr = {k: new_attr} + + _new_attr = new_attr + _serialized = serialized + for k in keys: # type: ignore + if k not in _serialized: + _serialized.update(_new_attr) # type: ignore + _new_attr = _new_attr[k] # type: ignore + _serialized = _serialized[k] + except ValueError as err: + if isinstance(err, SerializationError): + raise + + except (AttributeError, KeyError, TypeError) as err: + msg = "Attribute {} in object {} cannot be serialized.\n{}".format(attr_name, class_name, str(target_obj)) + raise SerializationError(msg) from err + return serialized + + def body(self, data, data_type, **kwargs): + """Serialize data intended for a request body. + + :param object data: The data to be serialized. + :param str data_type: The type to be serialized from. + :rtype: dict + :raises SerializationError: if serialization fails. + :raises ValueError: if data is None + :returns: The serialized request body + """ + + # Just in case this is a dict + internal_data_type_str = data_type.strip("[]{}") + internal_data_type = self.dependencies.get(internal_data_type_str, None) + try: + is_xml_model_serialization = kwargs["is_xml"] + except KeyError: + if internal_data_type and issubclass(internal_data_type, Model): + is_xml_model_serialization = kwargs.setdefault("is_xml", internal_data_type.is_xml_model()) + else: + is_xml_model_serialization = False + if internal_data_type and not isinstance(internal_data_type, Enum): + try: + deserializer = Deserializer(self.dependencies) + # Since it's on serialization, it's almost sure that format is not JSON REST + # We're not able to deal with additional properties for now. + deserializer.additional_properties_detection = False + if is_xml_model_serialization: + deserializer.key_extractors = [ # type: ignore + attribute_key_case_insensitive_extractor, + ] + else: + deserializer.key_extractors = [ + rest_key_case_insensitive_extractor, + attribute_key_case_insensitive_extractor, + last_rest_key_case_insensitive_extractor, + ] + data = deserializer._deserialize(data_type, data) # pylint: disable=protected-access + except DeserializationError as err: + raise SerializationError("Unable to build a model: " + str(err)) from err + + return self._serialize(data, data_type, **kwargs) + + def url(self, name, data, data_type, **kwargs): + """Serialize data intended for a URL path. + + :param str name: The name of the URL path parameter. + :param object data: The data to be serialized. + :param str data_type: The type to be serialized from. + :rtype: str + :returns: The serialized URL path + :raises TypeError: if serialization fails. + :raises ValueError: if data is None + """ + try: + output = self.serialize_data(data, data_type, **kwargs) + if data_type == "bool": + output = json.dumps(output) + + if kwargs.get("skip_quote") is True: + output = str(output) + output = output.replace("{", quote("{")).replace("}", quote("}")) + else: + output = quote(str(output), safe="") + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return output + + def query(self, name, data, data_type, **kwargs): + """Serialize data intended for a URL query. + + :param str name: The name of the query parameter. + :param object data: The data to be serialized. + :param str data_type: The type to be serialized from. + :rtype: str, list + :raises TypeError: if serialization fails. + :raises ValueError: if data is None + :returns: The serialized query parameter + """ + try: + # Treat the list aside, since we don't want to encode the div separator + if data_type.startswith("["): + internal_data_type = data_type[1:-1] + do_quote = not kwargs.get("skip_quote", False) + return self.serialize_iter(data, internal_data_type, do_quote=do_quote, **kwargs) + + # Not a list, regular serialization + output = self.serialize_data(data, data_type, **kwargs) + if data_type == "bool": + output = json.dumps(output) + if kwargs.get("skip_quote") is True: + output = str(output) + else: + output = quote(str(output), safe="") + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return str(output) + + def header(self, name, data, data_type, **kwargs): + """Serialize data intended for a request header. + + :param str name: The name of the header. + :param object data: The data to be serialized. + :param str data_type: The type to be serialized from. + :rtype: str + :raises TypeError: if serialization fails. + :raises ValueError: if data is None + :returns: The serialized header + """ + try: + if data_type in ["[str]"]: + data = ["" if d is None else d for d in data] + + output = self.serialize_data(data, data_type, **kwargs) + if data_type == "bool": + output = json.dumps(output) + except SerializationError as exc: + raise TypeError("{} must be type {}.".format(name, data_type)) from exc + return str(output) + + def serialize_data(self, data, data_type, **kwargs): + """Serialize generic data according to supplied data type. + + :param object data: The data to be serialized. + :param str data_type: The type to be serialized from. + :raises AttributeError: if required data is None. + :raises ValueError: if data is None + :raises SerializationError: if serialization fails. + :returns: The serialized data. + :rtype: str, int, float, bool, dict, list + """ + if data is None: + raise ValueError("No value for given attribute") + + try: + if data is CoreNull: + return None + if data_type in self.basic_types.values(): + return self.serialize_basic(data, data_type, **kwargs) + + if data_type in self.serialize_type: + return self.serialize_type[data_type](data, **kwargs) + + # If dependencies is empty, try with current data class + # It has to be a subclass of Enum anyway + enum_type = self.dependencies.get(data_type, cast(type, data.__class__)) + if issubclass(enum_type, Enum): + return Serializer.serialize_enum(data, enum_obj=enum_type) + + iter_type = data_type[0] + data_type[-1] + if iter_type in self.serialize_type: + return self.serialize_type[iter_type](data, data_type[1:-1], **kwargs) + + except (ValueError, TypeError) as err: + msg = "Unable to serialize value: {!r} as type: {!r}." + raise SerializationError(msg.format(data, data_type)) from err + return self._serialize(data, **kwargs) + + @classmethod + def _get_custom_serializers(cls, data_type, **kwargs): # pylint: disable=inconsistent-return-statements + custom_serializer = kwargs.get("basic_types_serializers", {}).get(data_type) + if custom_serializer: + return custom_serializer + if kwargs.get("is_xml", False): + return cls._xml_basic_types_serializers.get(data_type) + + @classmethod + def serialize_basic(cls, data, data_type, **kwargs): + """Serialize basic builting data type. + Serializes objects to str, int, float or bool. + + Possible kwargs: + - basic_types_serializers dict[str, callable] : If set, use the callable as serializer + - is_xml bool : If set, use xml_basic_types_serializers + + :param obj data: Object to be serialized. + :param str data_type: Type of object in the iterable. + :rtype: str, int, float, bool + :return: serialized object + :raises TypeError: raise if data_type is not one of str, int, float, bool. + """ + custom_serializer = cls._get_custom_serializers(data_type, **kwargs) + if custom_serializer: + return custom_serializer(data) + if data_type == "str": + return cls.serialize_unicode(data) + if data_type == "int": + return int(data) + if data_type == "float": + return float(data) + if data_type == "bool": + return bool(data) + raise TypeError("Unknown basic data type: {}".format(data_type)) + + @classmethod + def serialize_unicode(cls, data): + """Special handling for serializing unicode strings in Py2. + Encode to UTF-8 if unicode, otherwise handle as a str. + + :param str data: Object to be serialized. + :rtype: str + :return: serialized object + """ + try: # If I received an enum, return its value + return data.value + except AttributeError: + pass + + try: + if isinstance(data, unicode): # type: ignore + # Don't change it, JSON and XML ElementTree are totally able + # to serialize correctly u'' strings + return data + except NameError: + return str(data) + return str(data) + + def serialize_iter(self, data, iter_type, div=None, **kwargs): + """Serialize iterable. + + Supported kwargs: + - serialization_ctxt dict : The current entry of _attribute_map, or same format. + serialization_ctxt['type'] should be same as data_type. + - is_xml bool : If set, serialize as XML + + :param list data: Object to be serialized. + :param str iter_type: Type of object in the iterable. + :param str div: If set, this str will be used to combine the elements + in the iterable into a combined string. Default is 'None'. + Defaults to False. + :rtype: list, str + :return: serialized iterable + """ + if isinstance(data, str): + raise SerializationError("Refuse str type as a valid iter type.") + + serialization_ctxt = kwargs.get("serialization_ctxt", {}) + is_xml = kwargs.get("is_xml", False) + + serialized = [] + for d in data: + try: + serialized.append(self.serialize_data(d, iter_type, **kwargs)) + except ValueError as err: + if isinstance(err, SerializationError): + raise + serialized.append(None) + + if kwargs.get("do_quote", False): + serialized = ["" if s is None else quote(str(s), safe="") for s in serialized] + + if div: + serialized = ["" if s is None else str(s) for s in serialized] + serialized = div.join(serialized) + + if "xml" in serialization_ctxt or is_xml: + # XML serialization is more complicated + xml_desc = serialization_ctxt.get("xml", {}) + xml_name = xml_desc.get("name") + if not xml_name: + xml_name = serialization_ctxt["key"] + + # Create a wrap node if necessary (use the fact that Element and list have "append") + is_wrapped = xml_desc.get("wrapped", False) + node_name = xml_desc.get("itemsName", xml_name) + if is_wrapped: + final_result = _create_xml_node(xml_name, xml_desc.get("prefix", None), xml_desc.get("ns", None)) + else: + final_result = [] + # All list elements to "local_node" + for el in serialized: + if isinstance(el, ET.Element): + el_node = el + else: + el_node = _create_xml_node(node_name, xml_desc.get("prefix", None), xml_desc.get("ns", None)) + if el is not None: # Otherwise it writes "None" :-p + el_node.text = str(el) + final_result.append(el_node) + return final_result + return serialized + + def serialize_dict(self, attr, dict_type, **kwargs): + """Serialize a dictionary of objects. + + :param dict attr: Object to be serialized. + :param str dict_type: Type of object in the dictionary. + :rtype: dict + :return: serialized dictionary + """ + serialization_ctxt = kwargs.get("serialization_ctxt", {}) + serialized = {} + for key, value in attr.items(): + try: + serialized[self.serialize_unicode(key)] = self.serialize_data(value, dict_type, **kwargs) + except ValueError as err: + if isinstance(err, SerializationError): + raise + serialized[self.serialize_unicode(key)] = None + + if "xml" in serialization_ctxt: + # XML serialization is more complicated + xml_desc = serialization_ctxt["xml"] + xml_name = xml_desc["name"] + + final_result = _create_xml_node(xml_name, xml_desc.get("prefix", None), xml_desc.get("ns", None)) + for key, value in serialized.items(): + ET.SubElement(final_result, key).text = value + return final_result + + return serialized + + def serialize_object(self, attr, **kwargs): # pylint: disable=too-many-return-statements + """Serialize a generic object. + This will be handled as a dictionary. If object passed in is not + a basic type (str, int, float, dict, list) it will simply be + cast to str. + + :param dict attr: Object to be serialized. + :rtype: dict or str + :return: serialized object + """ + if attr is None: + return None + if isinstance(attr, ET.Element): + return attr + obj_type = type(attr) + if obj_type in self.basic_types: + return self.serialize_basic(attr, self.basic_types[obj_type], **kwargs) + if obj_type is _long_type: + return self.serialize_long(attr) + if obj_type is str: + return self.serialize_unicode(attr) + if obj_type is datetime.datetime: + return self.serialize_iso(attr) + if obj_type is datetime.date: + return self.serialize_date(attr) + if obj_type is datetime.time: + return self.serialize_time(attr) + if obj_type is datetime.timedelta: + return self.serialize_duration(attr) + if obj_type is decimal.Decimal: + return self.serialize_decimal(attr) + + # If it's a model or I know this dependency, serialize as a Model + if obj_type in self.dependencies.values() or isinstance(attr, Model): + return self._serialize(attr) + + if obj_type == dict: + serialized = {} + for key, value in attr.items(): + try: + serialized[self.serialize_unicode(key)] = self.serialize_object(value, **kwargs) + except ValueError: + serialized[self.serialize_unicode(key)] = None + return serialized + + if obj_type == list: + serialized = [] + for obj in attr: + try: + serialized.append(self.serialize_object(obj, **kwargs)) + except ValueError: + pass + return serialized + return str(attr) + + @staticmethod + def serialize_enum(attr, enum_obj=None): + try: + result = attr.value + except AttributeError: + result = attr + try: + enum_obj(result) # type: ignore + return result + except ValueError as exc: + for enum_value in enum_obj: # type: ignore + if enum_value.value.lower() == str(attr).lower(): + return enum_value.value + error = "{!r} is not valid value for enum {!r}" + raise SerializationError(error.format(attr, enum_obj)) from exc + + @staticmethod + def serialize_bytearray(attr, **kwargs): # pylint: disable=unused-argument + """Serialize bytearray into base-64 string. + + :param str attr: Object to be serialized. + :rtype: str + :return: serialized base64 + """ + return b64encode(attr).decode() + + @staticmethod + def serialize_base64(attr, **kwargs): # pylint: disable=unused-argument + """Serialize str into base-64 string. + + :param str attr: Object to be serialized. + :rtype: str + :return: serialized base64 + """ + encoded = b64encode(attr).decode("ascii") + return encoded.strip("=").replace("+", "-").replace("/", "_") + + @staticmethod + def serialize_decimal(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Decimal object to float. + + :param decimal attr: Object to be serialized. + :rtype: float + :return: serialized decimal + """ + return float(attr) + + @staticmethod + def serialize_long(attr, **kwargs): # pylint: disable=unused-argument + """Serialize long (Py2) or int (Py3). + + :param int attr: Object to be serialized. + :rtype: int/long + :return: serialized long + """ + return _long_type(attr) + + @staticmethod + def serialize_date(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Date object into ISO-8601 formatted string. + + :param Date attr: Object to be serialized. + :rtype: str + :return: serialized date + """ + if isinstance(attr, str): + attr = isodate.parse_date(attr) + t = "{:04}-{:02}-{:02}".format(attr.year, attr.month, attr.day) + return t + + @staticmethod + def serialize_time(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Time object into ISO-8601 formatted string. + + :param datetime.time attr: Object to be serialized. + :rtype: str + :return: serialized time + """ + if isinstance(attr, str): + attr = isodate.parse_time(attr) + t = "{:02}:{:02}:{:02}".format(attr.hour, attr.minute, attr.second) + if attr.microsecond: + t += ".{:02}".format(attr.microsecond) + return t + + @staticmethod + def serialize_duration(attr, **kwargs): # pylint: disable=unused-argument + """Serialize TimeDelta object into ISO-8601 formatted string. + + :param TimeDelta attr: Object to be serialized. + :rtype: str + :return: serialized duration + """ + if isinstance(attr, str): + attr = isodate.parse_duration(attr) + return isodate.duration_isoformat(attr) + + @staticmethod + def serialize_rfc(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Datetime object into RFC-1123 formatted string. + + :param Datetime attr: Object to be serialized. + :rtype: str + :raises TypeError: if format invalid. + :return: serialized rfc + """ + try: + if not attr.tzinfo: + _LOGGER.warning("Datetime with no tzinfo will be considered UTC.") + utc = attr.utctimetuple() + except AttributeError as exc: + raise TypeError("RFC1123 object must be valid Datetime object.") from exc + + return "{}, {:02} {} {:04} {:02}:{:02}:{:02} GMT".format( + Serializer.days[utc.tm_wday], + utc.tm_mday, + Serializer.months[utc.tm_mon], + utc.tm_year, + utc.tm_hour, + utc.tm_min, + utc.tm_sec, + ) + + @staticmethod + def serialize_iso(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Datetime object into ISO-8601 formatted string. + + :param Datetime attr: Object to be serialized. + :rtype: str + :raises SerializationError: if format invalid. + :return: serialized iso + """ + if isinstance(attr, str): + attr = isodate.parse_datetime(attr) + try: + if not attr.tzinfo: + _LOGGER.warning("Datetime with no tzinfo will be considered UTC.") + utc = attr.utctimetuple() + if utc.tm_year > 9999 or utc.tm_year < 1: + raise OverflowError("Hit max or min date") + + microseconds = str(attr.microsecond).rjust(6, "0").rstrip("0").ljust(3, "0") + if microseconds: + microseconds = "." + microseconds + date = "{:04}-{:02}-{:02}T{:02}:{:02}:{:02}".format( + utc.tm_year, utc.tm_mon, utc.tm_mday, utc.tm_hour, utc.tm_min, utc.tm_sec + ) + return date + microseconds + "Z" + except (ValueError, OverflowError) as err: + msg = "Unable to serialize datetime object." + raise SerializationError(msg) from err + except AttributeError as err: + msg = "ISO-8601 object must be valid Datetime object." + raise TypeError(msg) from err + + @staticmethod + def serialize_unix(attr, **kwargs): # pylint: disable=unused-argument + """Serialize Datetime object into IntTime format. + This is represented as seconds. + + :param Datetime attr: Object to be serialized. + :rtype: int + :raises SerializationError: if format invalid + :return: serialied unix + """ + if isinstance(attr, int): + return attr + try: + if not attr.tzinfo: + _LOGGER.warning("Datetime with no tzinfo will be considered UTC.") + return int(calendar.timegm(attr.utctimetuple())) + except AttributeError as exc: + raise TypeError("Unix time object must be valid Datetime object.") from exc + + +def rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument + key = attr_desc["key"] + working_data = data + + while "." in key: + # Need the cast, as for some reasons "split" is typed as list[str | Any] + dict_keys = cast(list[str], _FLATTEN.split(key)) + if len(dict_keys) == 1: + key = _decode_attribute_map_key(dict_keys[0]) + break + working_key = _decode_attribute_map_key(dict_keys[0]) + working_data = working_data.get(working_key, data) + if working_data is None: + # If at any point while following flatten JSON path see None, it means + # that all properties under are None as well + return None + key = ".".join(dict_keys[1:]) + + return working_data.get(key) + + +def rest_key_case_insensitive_extractor( # pylint: disable=unused-argument, inconsistent-return-statements + attr, attr_desc, data +): + key = attr_desc["key"] + working_data = data + + while "." in key: + dict_keys = _FLATTEN.split(key) + if len(dict_keys) == 1: + key = _decode_attribute_map_key(dict_keys[0]) + break + working_key = _decode_attribute_map_key(dict_keys[0]) + working_data = attribute_key_case_insensitive_extractor(working_key, None, working_data) + if working_data is None: + # If at any point while following flatten JSON path see None, it means + # that all properties under are None as well + return None + key = ".".join(dict_keys[1:]) + + if working_data: + return attribute_key_case_insensitive_extractor(key, None, working_data) + + +def last_rest_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument + """Extract the attribute in "data" based on the last part of the JSON path key. + + :param str attr: The attribute to extract + :param dict attr_desc: The attribute description + :param dict data: The data to extract from + :rtype: object + :returns: The extracted attribute + """ + key = attr_desc["key"] + dict_keys = _FLATTEN.split(key) + return attribute_key_extractor(dict_keys[-1], None, data) + + +def last_rest_key_case_insensitive_extractor(attr, attr_desc, data): # pylint: disable=unused-argument + """Extract the attribute in "data" based on the last part of the JSON path key. + + This is the case insensitive version of "last_rest_key_extractor" + :param str attr: The attribute to extract + :param dict attr_desc: The attribute description + :param dict data: The data to extract from + :rtype: object + :returns: The extracted attribute + """ + key = attr_desc["key"] + dict_keys = _FLATTEN.split(key) + return attribute_key_case_insensitive_extractor(dict_keys[-1], None, data) + + +def attribute_key_extractor(attr, _, data): + return data.get(attr) + + +def attribute_key_case_insensitive_extractor(attr, _, data): + found_key = None + lower_attr = attr.lower() + for key in data: + if lower_attr == key.lower(): + found_key = key + break + + return data.get(found_key) + + +def _extract_name_from_internal_type(internal_type): + """Given an internal type XML description, extract correct XML name with namespace. + + :param dict internal_type: An model type + :rtype: tuple + :returns: A tuple XML name + namespace dict + """ + internal_type_xml_map = getattr(internal_type, "_xml_map", {}) + xml_name = internal_type_xml_map.get("name", internal_type.__name__) + xml_ns = internal_type_xml_map.get("ns", None) + if xml_ns: + xml_name = "{{{}}}{}".format(xml_ns, xml_name) + return xml_name + + +def xml_key_extractor(attr, attr_desc, data): # pylint: disable=unused-argument,too-many-return-statements + if isinstance(data, dict): + return None + + # Test if this model is XML ready first + if not isinstance(data, ET.Element): + return None + + xml_desc = attr_desc.get("xml", {}) + xml_name = xml_desc.get("name", attr_desc["key"]) + + # Look for a children + is_iter_type = attr_desc["type"].startswith("[") + is_wrapped = xml_desc.get("wrapped", False) + internal_type = attr_desc.get("internalType", None) + internal_type_xml_map = getattr(internal_type, "_xml_map", {}) + + # Integrate namespace if necessary + xml_ns = xml_desc.get("ns", internal_type_xml_map.get("ns", None)) + if xml_ns: + xml_name = "{{{}}}{}".format(xml_ns, xml_name) + + # If it's an attribute, that's simple + if xml_desc.get("attr", False): + return data.get(xml_name) + + # If it's x-ms-text, that's simple too + if xml_desc.get("text", False): + return data.text + + # Scenario where I take the local name: + # - Wrapped node + # - Internal type is an enum (considered basic types) + # - Internal type has no XML/Name node + if is_wrapped or (internal_type and (issubclass(internal_type, Enum) or "name" not in internal_type_xml_map)): + children = data.findall(xml_name) + # If internal type has a local name and it's not a list, I use that name + elif not is_iter_type and internal_type and "name" in internal_type_xml_map: + xml_name = _extract_name_from_internal_type(internal_type) + children = data.findall(xml_name) + # That's an array + else: + if internal_type: # Complex type, ignore itemsName and use the complex type name + items_name = _extract_name_from_internal_type(internal_type) + else: + items_name = xml_desc.get("itemsName", xml_name) + children = data.findall(items_name) + + if len(children) == 0: + if is_iter_type: + if is_wrapped: + return None # is_wrapped no node, we want None + return [] # not wrapped, assume empty list + return None # Assume it's not there, maybe an optional node. + + # If is_iter_type and not wrapped, return all found children + if is_iter_type: + if not is_wrapped: + return children + # Iter and wrapped, should have found one node only (the wrap one) + if len(children) != 1: + raise DeserializationError( + "Tried to deserialize an array not wrapped, and found several nodes '{}'. Maybe you should declare this array as wrapped?".format( + xml_name + ) + ) + return list(children[0]) # Might be empty list and that's ok. + + # Here it's not a itertype, we should have found one element only or empty + if len(children) > 1: + raise DeserializationError("Find several XML '{}' where it was not expected".format(xml_name)) + return children[0] + + +class Deserializer: + """Response object model deserializer. + + :param dict classes: Class type dictionary for deserializing complex types. + :ivar list key_extractors: Ordered list of extractors to be used by this deserializer. + """ + + basic_types = {str: "str", int: "int", bool: "bool", float: "float"} + + valid_date = re.compile(r"\d{4}[-]\d{2}[-]\d{2}T\d{2}:\d{2}:\d{2}\.?\d*Z?[-+]?[\d{2}]?:?[\d{2}]?") + + def __init__(self, classes: Optional[Mapping[str, type]] = None) -> None: + self.deserialize_type = { + "iso-8601": Deserializer.deserialize_iso, + "rfc-1123": Deserializer.deserialize_rfc, + "unix-time": Deserializer.deserialize_unix, + "duration": Deserializer.deserialize_duration, + "date": Deserializer.deserialize_date, + "time": Deserializer.deserialize_time, + "decimal": Deserializer.deserialize_decimal, + "long": Deserializer.deserialize_long, + "bytearray": Deserializer.deserialize_bytearray, + "base64": Deserializer.deserialize_base64, + "object": self.deserialize_object, + "[]": self.deserialize_iter, + "{}": self.deserialize_dict, + } + self.deserialize_expected_types = { + "duration": (isodate.Duration, datetime.timedelta), + "iso-8601": (datetime.datetime), + } + self.dependencies: dict[str, type] = dict(classes) if classes else {} + self.key_extractors = [rest_key_extractor, xml_key_extractor] + # Additional properties only works if the "rest_key_extractor" is used to + # extract the keys. Making it to work whatever the key extractor is too much + # complicated, with no real scenario for now. + # So adding a flag to disable additional properties detection. This flag should be + # used if your expect the deserialization to NOT come from a JSON REST syntax. + # Otherwise, result are unexpected + self.additional_properties_detection = True + + def __call__(self, target_obj, response_data, content_type=None): + """Call the deserializer to process a REST response. + + :param str target_obj: Target data type to deserialize to. + :param requests.Response response_data: REST response object. + :param str content_type: Swagger "produces" if available. + :raises DeserializationError: if deserialization fails. + :return: Deserialized object. + :rtype: object + """ + data = self._unpack_content(response_data, content_type) + return self._deserialize(target_obj, data) + + def _deserialize(self, target_obj, data): # pylint: disable=inconsistent-return-statements + """Call the deserializer on a model. + + Data needs to be already deserialized as JSON or XML ElementTree + + :param str target_obj: Target data type to deserialize to. + :param object data: Object to deserialize. + :raises DeserializationError: if deserialization fails. + :return: Deserialized object. + :rtype: object + """ + # This is already a model, go recursive just in case + if hasattr(data, "_attribute_map"): + constants = [name for name, config in getattr(data, "_validation", {}).items() if config.get("constant")] + try: + for attr, mapconfig in data._attribute_map.items(): # pylint: disable=protected-access + if attr in constants: + continue + value = getattr(data, attr) + if value is None: + continue + local_type = mapconfig["type"] + internal_data_type = local_type.strip("[]{}") + if internal_data_type not in self.dependencies or isinstance(internal_data_type, Enum): + continue + setattr(data, attr, self._deserialize(local_type, value)) + return data + except AttributeError: + return + + response, class_name = self._classify_target(target_obj, data) + + if isinstance(response, str): + return self.deserialize_data(data, response) + if isinstance(response, type) and issubclass(response, Enum): + return self.deserialize_enum(data, response) + + if data is None or data is CoreNull: + return data + try: + attributes = response._attribute_map # type: ignore # pylint: disable=protected-access + d_attrs = {} + for attr, attr_desc in attributes.items(): + # Check empty string. If it's not empty, someone has a real "additionalProperties"... + if attr == "additional_properties" and attr_desc["key"] == "": + continue + raw_value = None + # Enhance attr_desc with some dynamic data + attr_desc = attr_desc.copy() # Do a copy, do not change the real one + internal_data_type = attr_desc["type"].strip("[]{}") + if internal_data_type in self.dependencies: + attr_desc["internalType"] = self.dependencies[internal_data_type] + + for key_extractor in self.key_extractors: + found_value = key_extractor(attr, attr_desc, data) + if found_value is not None: + if raw_value is not None and raw_value != found_value: + msg = ( + "Ignoring extracted value '%s' from %s for key '%s'" + " (duplicate extraction, follow extractors order)" + ) + _LOGGER.warning(msg, found_value, key_extractor, attr) + continue + raw_value = found_value + + value = self.deserialize_data(raw_value, attr_desc["type"]) + d_attrs[attr] = value + except (AttributeError, TypeError, KeyError) as err: + msg = "Unable to deserialize to object: " + class_name # type: ignore + raise DeserializationError(msg) from err + additional_properties = self._build_additional_properties(attributes, data) + return self._instantiate_model(response, d_attrs, additional_properties) + + def _build_additional_properties(self, attribute_map, data): + if not self.additional_properties_detection: + return None + if "additional_properties" in attribute_map and attribute_map.get("additional_properties", {}).get("key") != "": + # Check empty string. If it's not empty, someone has a real "additionalProperties" + return None + if isinstance(data, ET.Element): + data = {el.tag: el.text for el in data} + + known_keys = { + _decode_attribute_map_key(_FLATTEN.split(desc["key"])[0]) + for desc in attribute_map.values() + if desc["key"] != "" + } + present_keys = set(data.keys()) + missing_keys = present_keys - known_keys + return {key: data[key] for key in missing_keys} + + def _classify_target(self, target, data): + """Check to see whether the deserialization target object can + be classified into a subclass. + Once classification has been determined, initialize object. + + :param str target: The target object type to deserialize to. + :param str/dict data: The response data to deserialize. + :return: The classified target object and its class name. + :rtype: tuple + """ + if target is None: + return None, None + + if isinstance(target, str): + try: + target = self.dependencies[target] + except KeyError: + return target, target + + try: + target = target._classify(data, self.dependencies) # type: ignore # pylint: disable=protected-access + except AttributeError: + pass # Target is not a Model, no classify + return target, target.__class__.__name__ # type: ignore + + def failsafe_deserialize(self, target_obj, data, content_type=None): + """Ignores any errors encountered in deserialization, + and falls back to not deserializing the object. Recommended + for use in error deserialization, as we want to return the + HttpResponseError to users, and not have them deal with + a deserialization error. + + :param str target_obj: The target object type to deserialize to. + :param str/dict data: The response data to deserialize. + :param str content_type: Swagger "produces" if available. + :return: Deserialized object. + :rtype: object + """ + try: + return self(target_obj, data, content_type=content_type) + except: # pylint: disable=bare-except + _LOGGER.debug( + "Ran into a deserialization error. Ignoring since this is failsafe deserialization", exc_info=True + ) + return None + + @staticmethod + def _unpack_content(raw_data, content_type=None): + """Extract the correct structure for deserialization. + + If raw_data is a PipelineResponse, try to extract the result of RawDeserializer. + if we can't, raise. Your Pipeline should have a RawDeserializer. + + If not a pipeline response and raw_data is bytes or string, use content-type + to decode it. If no content-type, try JSON. + + If raw_data is something else, bypass all logic and return it directly. + + :param obj raw_data: Data to be processed. + :param str content_type: How to parse if raw_data is a string/bytes. + :raises JSONDecodeError: If JSON is requested and parsing is impossible. + :raises UnicodeDecodeError: If bytes is not UTF8 + :rtype: object + :return: Unpacked content. + """ + # Assume this is enough to detect a Pipeline Response without importing it + context = getattr(raw_data, "context", {}) + if context: + if RawDeserializer.CONTEXT_NAME in context: + return context[RawDeserializer.CONTEXT_NAME] + raise ValueError("This pipeline didn't have the RawDeserializer policy; can't deserialize") + + # Assume this is enough to recognize universal_http.ClientResponse without importing it + if hasattr(raw_data, "body"): + return RawDeserializer.deserialize_from_http_generics(raw_data.text(), raw_data.headers) + + # Assume this enough to recognize requests.Response without importing it. + if hasattr(raw_data, "_content_consumed"): + return RawDeserializer.deserialize_from_http_generics(raw_data.text, raw_data.headers) + + if isinstance(raw_data, (str, bytes)) or hasattr(raw_data, "read"): + return RawDeserializer.deserialize_from_text(raw_data, content_type) # type: ignore + return raw_data + + def _instantiate_model(self, response, attrs, additional_properties=None): + """Instantiate a response model passing in deserialized args. + + :param Response response: The response model class. + :param dict attrs: The deserialized response attributes. + :param dict additional_properties: Additional properties to be set. + :rtype: Response + :return: The instantiated response model. + """ + if callable(response): + subtype = getattr(response, "_subtype_map", {}) + try: + readonly = [ + k + for k, v in response._validation.items() # pylint: disable=protected-access # type: ignore + if v.get("readonly") + ] + const = [ + k + for k, v in response._validation.items() # pylint: disable=protected-access # type: ignore + if v.get("constant") + ] + kwargs = {k: v for k, v in attrs.items() if k not in subtype and k not in readonly + const} + response_obj = response(**kwargs) + for attr in readonly: + setattr(response_obj, attr, attrs.get(attr)) + if additional_properties: + response_obj.additional_properties = additional_properties # type: ignore + return response_obj + except TypeError as err: + msg = "Unable to deserialize {} into model {}. ".format(kwargs, response) # type: ignore + raise DeserializationError(msg + str(err)) from err + else: + try: + for attr, value in attrs.items(): + setattr(response, attr, value) + return response + except Exception as exp: + msg = "Unable to populate response model. " + msg += "Type: {}, Error: {}".format(type(response), exp) + raise DeserializationError(msg) from exp + + def deserialize_data(self, data, data_type): # pylint: disable=too-many-return-statements + """Process data for deserialization according to data type. + + :param str data: The response string to be deserialized. + :param str data_type: The type to deserialize to. + :raises DeserializationError: if deserialization fails. + :return: Deserialized object. + :rtype: object + """ + if data is None: + return data + + try: + if not data_type: + return data + if data_type in self.basic_types.values(): + return self.deserialize_basic(data, data_type) + if data_type in self.deserialize_type: + if isinstance(data, self.deserialize_expected_types.get(data_type, tuple())): + return data + + is_a_text_parsing_type = lambda x: x not in [ # pylint: disable=unnecessary-lambda-assignment + "object", + "[]", + r"{}", + ] + if isinstance(data, ET.Element) and is_a_text_parsing_type(data_type) and not data.text: + return None + data_val = self.deserialize_type[data_type](data) + return data_val + + iter_type = data_type[0] + data_type[-1] + if iter_type in self.deserialize_type: + return self.deserialize_type[iter_type](data, data_type[1:-1]) + + obj_type = self.dependencies[data_type] + if issubclass(obj_type, Enum): + if isinstance(data, ET.Element): + data = data.text + return self.deserialize_enum(data, obj_type) + + except (ValueError, TypeError, AttributeError) as err: + msg = "Unable to deserialize response data." + msg += " Data: {}, {}".format(data, data_type) + raise DeserializationError(msg) from err + return self._deserialize(obj_type, data) + + def deserialize_iter(self, attr, iter_type): + """Deserialize an iterable. + + :param list attr: Iterable to be deserialized. + :param str iter_type: The type of object in the iterable. + :return: Deserialized iterable. + :rtype: list + """ + if attr is None: + return None + if isinstance(attr, ET.Element): # If I receive an element here, get the children + attr = list(attr) + if not isinstance(attr, (list, set)): + raise DeserializationError("Cannot deserialize as [{}] an object of type {}".format(iter_type, type(attr))) + return [self.deserialize_data(a, iter_type) for a in attr] + + def deserialize_dict(self, attr, dict_type): + """Deserialize a dictionary. + + :param dict/list attr: Dictionary to be deserialized. Also accepts + a list of key, value pairs. + :param str dict_type: The object type of the items in the dictionary. + :return: Deserialized dictionary. + :rtype: dict + """ + if isinstance(attr, list): + return {x["key"]: self.deserialize_data(x["value"], dict_type) for x in attr} + + if isinstance(attr, ET.Element): + # Transform value into {"Key": "value"} + attr = {el.tag: el.text for el in attr} + return {k: self.deserialize_data(v, dict_type) for k, v in attr.items()} + + def deserialize_object(self, attr, **kwargs): # pylint: disable=too-many-return-statements + """Deserialize a generic object. + This will be handled as a dictionary. + + :param dict attr: Dictionary to be deserialized. + :return: Deserialized object. + :rtype: dict + :raises TypeError: if non-builtin datatype encountered. + """ + if attr is None: + return None + if isinstance(attr, ET.Element): + # Do no recurse on XML, just return the tree as-is + return attr + if isinstance(attr, str): + return self.deserialize_basic(attr, "str") + obj_type = type(attr) + if obj_type in self.basic_types: + return self.deserialize_basic(attr, self.basic_types[obj_type]) + if obj_type is _long_type: + return self.deserialize_long(attr) + + if obj_type == dict: + deserialized = {} + for key, value in attr.items(): + try: + deserialized[key] = self.deserialize_object(value, **kwargs) + except ValueError: + deserialized[key] = None + return deserialized + + if obj_type == list: + deserialized = [] + for obj in attr: + try: + deserialized.append(self.deserialize_object(obj, **kwargs)) + except ValueError: + pass + return deserialized + + error = "Cannot deserialize generic object with type: " + raise TypeError(error + str(obj_type)) + + def deserialize_basic(self, attr, data_type): # pylint: disable=too-many-return-statements + """Deserialize basic builtin data type from string. + Will attempt to convert to str, int, float and bool. + This function will also accept '1', '0', 'true' and 'false' as + valid bool values. + + :param str attr: response string to be deserialized. + :param str data_type: deserialization data type. + :return: Deserialized basic type. + :rtype: str, int, float or bool + :raises TypeError: if string format is not valid or data_type is not one of str, int, float, bool. + """ + # If we're here, data is supposed to be a basic type. + # If it's still an XML node, take the text + if isinstance(attr, ET.Element): + attr = attr.text + if not attr: + if data_type == "str": + # None or '', node is empty string. + return "" + # None or '', node with a strong type is None. + # Don't try to model "empty bool" or "empty int" + return None + + if data_type == "bool": + if attr in [True, False, 1, 0]: + return bool(attr) + if isinstance(attr, str): + if attr.lower() in ["true", "1"]: + return True + if attr.lower() in ["false", "0"]: + return False + raise TypeError("Invalid boolean value: {}".format(attr)) + + if data_type == "str": + return self.deserialize_unicode(attr) + if data_type == "int": + return int(attr) + if data_type == "float": + return float(attr) + raise TypeError("Unknown basic data type: {}".format(data_type)) + + @staticmethod + def deserialize_unicode(data): + """Preserve unicode objects in Python 2, otherwise return data + as a string. + + :param str data: response string to be deserialized. + :return: Deserialized string. + :rtype: str or unicode + """ + # We might be here because we have an enum modeled as string, + # and we try to deserialize a partial dict with enum inside + if isinstance(data, Enum): + return data + + # Consider this is real string + try: + if isinstance(data, unicode): # type: ignore + return data + except NameError: + return str(data) + return str(data) + + @staticmethod + def deserialize_enum(data, enum_obj): + """Deserialize string into enum object. + + If the string is not a valid enum value it will be returned as-is + and a warning will be logged. + + :param str data: Response string to be deserialized. If this value is + None or invalid it will be returned as-is. + :param Enum enum_obj: Enum object to deserialize to. + :return: Deserialized enum object. + :rtype: Enum + """ + if isinstance(data, enum_obj) or data is None: + return data + if isinstance(data, Enum): + data = data.value + if isinstance(data, int): + # Workaround. We might consider remove it in the future. + try: + return list(enum_obj.__members__.values())[data] + except IndexError as exc: + error = "{!r} is not a valid index for enum {!r}" + raise DeserializationError(error.format(data, enum_obj)) from exc + try: + return enum_obj(str(data)) + except ValueError: + for enum_value in enum_obj: + if enum_value.value.lower() == str(data).lower(): + return enum_value + # We don't fail anymore for unknown value, we deserialize as a string + _LOGGER.warning("Deserializer is not able to find %s as valid enum in %s", data, enum_obj) + return Deserializer.deserialize_unicode(data) + + @staticmethod + def deserialize_bytearray(attr): + """Deserialize string into bytearray. + + :param str attr: response string to be deserialized. + :return: Deserialized bytearray + :rtype: bytearray + :raises TypeError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + return bytearray(b64decode(attr)) # type: ignore + + @staticmethod + def deserialize_base64(attr): + """Deserialize base64 encoded string into string. + + :param str attr: response string to be deserialized. + :return: Deserialized base64 string + :rtype: bytearray + :raises TypeError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + padding = "=" * (3 - (len(attr) + 3) % 4) # type: ignore + attr = attr + padding # type: ignore + encoded = attr.replace("-", "+").replace("_", "/") + return b64decode(encoded) + + @staticmethod + def deserialize_decimal(attr): + """Deserialize string into Decimal object. + + :param str attr: response string to be deserialized. + :return: Deserialized decimal + :raises DeserializationError: if string format invalid. + :rtype: decimal + """ + if isinstance(attr, ET.Element): + attr = attr.text + try: + return decimal.Decimal(str(attr)) # type: ignore + except decimal.DecimalException as err: + msg = "Invalid decimal {}".format(attr) + raise DeserializationError(msg) from err + + @staticmethod + def deserialize_long(attr): + """Deserialize string into long (Py2) or int (Py3). + + :param str attr: response string to be deserialized. + :return: Deserialized int + :rtype: long or int + :raises ValueError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + return _long_type(attr) # type: ignore + + @staticmethod + def deserialize_duration(attr): + """Deserialize ISO-8601 formatted string into TimeDelta object. + + :param str attr: response string to be deserialized. + :return: Deserialized duration + :rtype: TimeDelta + :raises DeserializationError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + try: + duration = isodate.parse_duration(attr) + except (ValueError, OverflowError, AttributeError) as err: + msg = "Cannot deserialize duration object." + raise DeserializationError(msg) from err + return duration + + @staticmethod + def deserialize_date(attr): + """Deserialize ISO-8601 formatted string into Date object. + + :param str attr: response string to be deserialized. + :return: Deserialized date + :rtype: Date + :raises DeserializationError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + if re.search(r"[^\W\d_]", attr, re.I + re.U): # type: ignore + raise DeserializationError("Date must have only digits and -. Received: %s" % attr) + # This must NOT use defaultmonth/defaultday. Using None ensure this raises an exception. + return isodate.parse_date(attr, defaultmonth=0, defaultday=0) + + @staticmethod + def deserialize_time(attr): + """Deserialize ISO-8601 formatted string into time object. + + :param str attr: response string to be deserialized. + :return: Deserialized time + :rtype: datetime.time + :raises DeserializationError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + if re.search(r"[^\W\d_]", attr, re.I + re.U): # type: ignore + raise DeserializationError("Date must have only digits and -. Received: %s" % attr) + return isodate.parse_time(attr) + + @staticmethod + def deserialize_rfc(attr): + """Deserialize RFC-1123 formatted string into Datetime object. + + :param str attr: response string to be deserialized. + :return: Deserialized RFC datetime + :rtype: Datetime + :raises DeserializationError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + try: + parsed_date = email.utils.parsedate_tz(attr) # type: ignore + date_obj = datetime.datetime( + *parsed_date[:6], tzinfo=datetime.timezone(datetime.timedelta(minutes=(parsed_date[9] or 0) / 60)) + ) + if not date_obj.tzinfo: + date_obj = date_obj.astimezone(tz=TZ_UTC) + except ValueError as err: + msg = "Cannot deserialize to rfc datetime object." + raise DeserializationError(msg) from err + return date_obj + + @staticmethod + def deserialize_iso(attr): + """Deserialize ISO-8601 formatted string into Datetime object. + + :param str attr: response string to be deserialized. + :return: Deserialized ISO datetime + :rtype: Datetime + :raises DeserializationError: if string format invalid. + """ + if isinstance(attr, ET.Element): + attr = attr.text + try: + attr = attr.upper() # type: ignore + match = Deserializer.valid_date.match(attr) + if not match: + raise ValueError("Invalid datetime string: " + attr) + + check_decimal = attr.split(".") + if len(check_decimal) > 1: + decimal_str = "" + for digit in check_decimal[1]: + if digit.isdigit(): + decimal_str += digit + else: + break + if len(decimal_str) > 6: + attr = attr.replace(decimal_str, decimal_str[0:6]) + + date_obj = isodate.parse_datetime(attr) + test_utc = date_obj.utctimetuple() + if test_utc.tm_year > 9999 or test_utc.tm_year < 1: + raise OverflowError("Hit max or min date") + except (ValueError, OverflowError, AttributeError) as err: + msg = "Cannot deserialize datetime object." + raise DeserializationError(msg) from err + return date_obj + + @staticmethod + def deserialize_unix(attr): + """Serialize Datetime object into IntTime format. + This is represented as seconds. + + :param int attr: Object to be serialized. + :return: Deserialized datetime + :rtype: Datetime + :raises DeserializationError: if format invalid + """ + if isinstance(attr, ET.Element): + attr = int(attr.text) # type: ignore + try: + attr = int(attr) + date_obj = datetime.datetime.fromtimestamp(attr, TZ_UTC) + except ValueError as err: + msg = "Cannot deserialize to unix datetime object." + raise DeserializationError(msg) from err + return date_obj diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_validation.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_validation.py new file mode 100644 index 000000000000..f5af3a4eb8a2 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_validation.py @@ -0,0 +1,66 @@ +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +import functools + + +def api_version_validation(**kwargs): + params_added_on = kwargs.pop("params_added_on", {}) + method_added_on = kwargs.pop("method_added_on", "") + api_versions_list = kwargs.pop("api_versions_list", []) + + def _index_with_default(value: str, default: int = -1) -> int: + """Get the index of value in lst, or return default if not found. + + :param value: The value to search for in the api_versions_list. + :type value: str + :param default: The default value to return if the value is not found. + :type default: int + :return: The index of the value in the list, or the default value if not found. + :rtype: int + """ + try: + return api_versions_list.index(value) + except ValueError: + return default + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + try: + # this assumes the client has an _api_version attribute + client = args[0] + client_api_version = client._config.api_version # pylint: disable=protected-access + except AttributeError: + return func(*args, **kwargs) + + if _index_with_default(method_added_on) > _index_with_default(client_api_version): + raise ValueError( + f"'{func.__name__}' is not available in API version " + f"{client_api_version}. Pass service API version {method_added_on} or newer to your client." + ) + + unsupported = { + parameter: api_version + for api_version, parameters in params_added_on.items() + for parameter in parameters + if parameter in kwargs and _index_with_default(api_version) > _index_with_default(client_api_version) + } + if unsupported: + raise ValueError( + "".join( + [ + f"'{param}' is not available in API version {client_api_version}. " + f"Use service API version {version} or newer.\n" + for param, version in unsupported.items() + ] + ) + ) + return func(*args, **kwargs) + + return wrapper + + return decorator diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_version.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_version.py new file mode 100644 index 000000000000..be71c81bd282 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_version.py @@ -0,0 +1,9 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +VERSION = "1.0.0b1" diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/__init__.py new file mode 100644 index 000000000000..3a6cc7aa9809 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/__init__.py @@ -0,0 +1,29 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._client import FineTuningSessionClient # type: ignore + +try: + from ._patch import __all__ as _patch_all + from ._patch import * +except ImportError: + _patch_all = [] +from ._patch import patch_sdk as _patch_sdk + +__all__ = [ + "FineTuningSessionClient", +] +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore + +_patch_sdk() diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_client.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_client.py new file mode 100644 index 000000000000..6b6e271d5e1b --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_client.py @@ -0,0 +1,118 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from copy import deepcopy +from typing import Any, Awaitable, TYPE_CHECKING +from typing_extensions import Self + +from azure.core import AsyncPipelineClient +from azure.core.pipeline import policies +from azure.core.rest import AsyncHttpResponse, HttpRequest + +from .._utils.serialization import Deserializer, Serializer +from ._configuration import FineTuningSessionClientConfiguration +from .operations import CheckpointsOperations, Operations, SamplingOperations, SessionsOperations, TrainingOperations + +if TYPE_CHECKING: + from azure.core.credentials_async import AsyncTokenCredential + + +class FineTuningSessionClient: # pylint: disable=client-accepts-api-version-keyword + """FineTuningSessionClient. + + :ivar sessions: SessionsOperations operations + :vartype sessions: azure.ai.finetuning_sessions.aio.operations.SessionsOperations + :ivar training: TrainingOperations operations + :vartype training: azure.ai.finetuning_sessions.aio.operations.TrainingOperations + :ivar checkpoints: CheckpointsOperations operations + :vartype checkpoints: azure.ai.finetuning_sessions.aio.operations.CheckpointsOperations + :ivar sampling: SamplingOperations operations + :vartype sampling: azure.ai.finetuning_sessions.aio.operations.SamplingOperations + :ivar operations: Operations operations + :vartype operations: azure.ai.finetuning_sessions.aio.operations.Operations + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Required. + :type credential: ~azure.core.credentials_async.AsyncTokenCredential + :keyword int polling_interval: Default waiting time between two polls for LRO operations if no + Retry-After header is present. + """ + + def __init__(self, endpoint: str, credential: "AsyncTokenCredential", **kwargs: Any) -> None: + _endpoint = "{endpoint}" + self._config = FineTuningSessionClientConfiguration(endpoint=endpoint, credential=credential, **kwargs) + + _policies = kwargs.pop("policies", None) + if _policies is None: + _policies = [ + policies.RequestIdPolicy(**kwargs), + self._config.headers_policy, + self._config.user_agent_policy, + self._config.proxy_policy, + policies.ContentDecodePolicy(**kwargs), + self._config.redirect_policy, + self._config.retry_policy, + self._config.authentication_policy, + self._config.custom_hook_policy, + self._config.logging_policy, + policies.DistributedTracingPolicy(**kwargs), + policies.SensitiveHeaderCleanupPolicy(**kwargs) if self._config.redirect_policy else None, + self._config.http_logging_policy, + ] + self._client: AsyncPipelineClient = AsyncPipelineClient(base_url=_endpoint, policies=_policies, **kwargs) + + self._serialize = Serializer() + self._deserialize = Deserializer() + self._serialize.client_side_validation = False + self.sessions = SessionsOperations(self._client, self._config, self._serialize, self._deserialize) + self.training = TrainingOperations(self._client, self._config, self._serialize, self._deserialize) + self.checkpoints = CheckpointsOperations(self._client, self._config, self._serialize, self._deserialize) + self.sampling = SamplingOperations(self._client, self._config, self._serialize, self._deserialize) + self.operations = Operations(self._client, self._config, self._serialize, self._deserialize) + + def send_request( + self, request: HttpRequest, *, stream: bool = False, **kwargs: Any + ) -> Awaitable[AsyncHttpResponse]: + """Runs the network request through the client's chained policies. + + >>> from azure.core.rest import HttpRequest + >>> request = HttpRequest("GET", "https://www.example.org/") + + >>> response = await client.send_request(request) + + + For more information on this code flow, see https://aka.ms/azsdk/dpcodegen/python/send_request + + :param request: The network request you want to make. Required. + :type request: ~azure.core.rest.HttpRequest + :keyword bool stream: Whether the response payload will be streamed. Defaults to False. + :return: The response of your network call. Does not do error handling on your response. + :rtype: ~azure.core.rest.AsyncHttpResponse + """ + + request_copy = deepcopy(request) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + request_copy.url = self._client.format_url(request_copy.url, **path_format_arguments) + return self._client.send_request(request_copy, stream=stream, **kwargs) # type: ignore + + async def close(self) -> None: + await self._client.close() + + async def __aenter__(self) -> Self: + await self._client.__aenter__() + return self + + async def __aexit__(self, *exc_details: Any) -> None: + await self._client.__aexit__(*exc_details) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py new file mode 100644 index 000000000000..25cf30658869 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py @@ -0,0 +1,61 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from typing import Any, TYPE_CHECKING + +from azure.core.pipeline import policies + +from .._version import VERSION + +if TYPE_CHECKING: + from azure.core.credentials_async import AsyncTokenCredential + + +class FineTuningSessionClientConfiguration: # pylint: disable=too-many-instance-attributes + """Configuration for FineTuningSessionClient. + + Note that all parameters used to create this instance are saved as instance + attributes. + + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Required. + :type credential: ~azure.core.credentials_async.AsyncTokenCredential + """ + + def __init__(self, endpoint: str, credential: "AsyncTokenCredential", **kwargs: Any) -> None: + if endpoint is None: + raise ValueError("Parameter 'endpoint' must not be None.") + if credential is None: + raise ValueError("Parameter 'credential' must not be None.") + + self.endpoint = endpoint + self.credential = credential + self.credential_scopes = kwargs.pop("credential_scopes", ["https://ai.azure.com/.default"]) + kwargs.setdefault("sdk_moniker", "finetuning-sessions/{}".format(VERSION)) + self.polling_interval = kwargs.get("polling_interval", 30) + self._configure(**kwargs) + + def _configure(self, **kwargs: Any) -> None: + self.user_agent_policy = kwargs.get("user_agent_policy") or policies.UserAgentPolicy(**kwargs) + self.headers_policy = kwargs.get("headers_policy") or policies.HeadersPolicy(**kwargs) + self.proxy_policy = kwargs.get("proxy_policy") or policies.ProxyPolicy(**kwargs) + self.logging_policy = kwargs.get("logging_policy") or policies.NetworkTraceLoggingPolicy(**kwargs) + self.http_logging_policy = kwargs.get("http_logging_policy") or policies.HttpLoggingPolicy(**kwargs) + self.custom_hook_policy = kwargs.get("custom_hook_policy") or policies.CustomHookPolicy(**kwargs) + self.redirect_policy = kwargs.get("redirect_policy") or policies.AsyncRedirectPolicy(**kwargs) + self.retry_policy = kwargs.get("retry_policy") or policies.AsyncRetryPolicy(**kwargs) + self.authentication_policy = kwargs.get("authentication_policy") + if self.credential and not self.authentication_policy: + self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy( + self.credential, *self.credential_scopes, **kwargs + ) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py new file mode 100644 index 000000000000..87676c65a8f0 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + + +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + + +def patch_sdk(): + """Do not remove from this file. + + `patch_sdk` is a last resort escape hatch that allows you to do customizations + you can't accomplish using the techniques described in + https://aka.ms/azsdk/python/dpcodegen/python/customize + """ diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/__init__.py new file mode 100644 index 000000000000..8f152cec5e7e --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/__init__.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._operations import SessionsOperations # type: ignore +from ._operations import TrainingOperations # type: ignore +from ._operations import CheckpointsOperations # type: ignore +from ._operations import SamplingOperations # type: ignore +from ._operations import Operations # type: ignore + +from ._patch import __all__ as _patch_all +from ._patch import * +from ._patch import patch_sdk as _patch_sdk + +__all__ = [ + "SessionsOperations", + "TrainingOperations", + "CheckpointsOperations", + "SamplingOperations", + "Operations", +] +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore +_patch_sdk() diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_operations.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_operations.py new file mode 100644 index 000000000000..51ba49cfa3c7 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_operations.py @@ -0,0 +1,2614 @@ +# pylint: disable=too-many-lines +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from collections.abc import MutableMapping +from io import IOBase +import json +from typing import Any, AsyncIterator, Callable, IO, Literal, Optional, TypeVar, Union, cast, overload + +from azure.core import AsyncPipelineClient +from azure.core.exceptions import ( + ClientAuthenticationError, + HttpResponseError, + ResourceExistsError, + ResourceNotFoundError, + ResourceNotModifiedError, + StreamClosedError, + StreamConsumedError, + map_error, +) +from azure.core.pipeline import PipelineResponse +from azure.core.polling import AsyncLROPoller, AsyncNoPolling, AsyncPollingMethod +from azure.core.polling.async_base_polling import AsyncLROBasePolling +from azure.core.rest import AsyncHttpResponse, HttpRequest +from azure.core.tracing.decorator_async import distributed_trace_async +from azure.core.utils import case_insensitive_dict + +from ... import models as _models +from ..._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize +from ..._utils.serialization import Deserializer, Serializer +from ..._validation import api_version_validation +from ...models._enums import FoundryFeaturesOptInKeys +from ...operations._operations import ( + build_checkpoints_get_request, + build_checkpoints_list_request, + build_checkpoints_save_request, + build_checkpoints_save_sampler_weights_request, + build_operations_get_request, + build_sampling_sample_request, + build_sessions_create_request, + build_sessions_get_request, + build_sessions_heartbeat_request, + build_sessions_list_request, + build_sessions_unload_request, + build_training_forward_backward_request, + build_training_optim_step_request, +) +from .._configuration import FineTuningSessionClientConfiguration + +JSON = MutableMapping[str, Any] +T = TypeVar("T") +ClsType = Optional[Callable[[PipelineResponse[HttpRequest, AsyncHttpResponse], T, dict[str, Any]], Any]] +List = list + + +class SessionsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.aio.FineTuningSessionClient`'s + :attr:`sessions` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _create_initial( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sessions_create_request( + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_create( + self, + body: _models.CreateSessionRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_create( + self, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_create( + self, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_create( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Is one of the following types: CreateSessionRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._create_initial( + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def create( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> JSON: + """Create a fine-tuning session (synchronous 200 response). + + POSTs to ``/fine_tuning/sessions`` and returns the response body directly. + Use this instead of ``begin_create`` when the server returns HTTP 200 (not 202 LRO). + + :param body: Is one of the following types: CreateSessionRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest or JSON or IO[bytes] + :keyword foundry_features: Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: dict (JSON response body) + :rtype: dict + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[JSON] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sessions_create_request( + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize(_models.ApiErrorResponse, response) + raise HttpResponseError(response=response, model=error) + + deserialized: JSON = response.json() + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def list( + self, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + limit: Optional[int] = None, + offset: Optional[int] = None, + **kwargs: Any + ) -> _models.SessionList: + """List fine-tuning sessions. + + List all fine-tuning sessions for this project. + + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword limit: Default value is None. + :paramtype limit: int + :keyword offset: Default value is None. + :paramtype offset: int + :return: SessionList. The SessionList is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.SessionList + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.SessionList] = kwargs.pop("cls", None) + + _request = build_sessions_list_request( + foundry_features=foundry_features, + api_version=api_version, + limit=limit, + offset=offset, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.SessionList, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def get( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.Session: + """Get a fine-tuning session. + + Get information about a specific fine-tuning session. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: Session. The Session is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.Session + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Session] = kwargs.pop("cls", None) + + _request = build_sessions_get_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Session, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _unload_initial( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + _request = build_sessions_unload_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_unload( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Unload a fine-tuning session. + + Unload a session from the GPU engine, freeing memory. LoRA weights are lost; save a checkpoint + before calling this. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._unload_initial( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def heartbeat( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.HeartbeatResponse: + """Session heartbeat. + + Heartbeat — refresh an active session to prevent idle expiry. The SDK sends this automatically + every 30 seconds. Returns 404 if the session has already expired. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: HeartbeatResponse. The HeartbeatResponse is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.HeartbeatResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.HeartbeatResponse] = kwargs.pop("cls", None) + + _request = build_sessions_heartbeat_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.HeartbeatResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class TrainingOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.aio.FineTuningSessionClient`'s + :attr:`training` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _forward_backward_initial( + self, + session_id: str, + body: Union[_models.ForwardBackwardRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_training_forward_backward_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_forward_backward( + self, + session_id: str, + body: _models.ForwardBackwardRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.ForwardBackwardRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_forward_backward( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_forward_backward( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_forward_backward( + self, + session_id: str, + body: Union[_models.ForwardBackwardRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: ForwardBackwardRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.ForwardBackwardRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._forward_backward_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _optim_step_initial( + self, + session_id: str, + body: Union[_models.OptimStepRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_training_optim_step_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_optim_step( + self, + session_id: str, + body: _models.OptimStepRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.OptimStepRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_optim_step( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_optim_step( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_optim_step( + self, + session_id: str, + body: Union[_models.OptimStepRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: OptimStepRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.OptimStepRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._optim_step_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + +class CheckpointsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.aio.FineTuningSessionClient`'s + :attr:`checkpoints` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _save_initial( + self, + session_id: str, + body: Union[_models.SaveCheckpointRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_checkpoints_save_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_save( + self, + session_id: str, + body: _models.SaveCheckpointRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveCheckpointRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_save( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_save( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_save( + self, + session_id: str, + body: Union[_models.SaveCheckpointRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SaveCheckpointRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveCheckpointRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._save_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _save_sampler_weights_initial( + self, + session_id: str, + body: Union[_models.SaveSamplerWeightsRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_checkpoints_save_sampler_weights_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_save_sampler_weights( + self, + session_id: str, + body: _models.SaveSamplerWeightsRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveSamplerWeightsRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_save_sampler_weights( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_save_sampler_weights( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_save_sampler_weights( + self, + session_id: str, + body: Union[_models.SaveSamplerWeightsRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SaveSamplerWeightsRequest, JSON, IO[bytes] + Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveSamplerWeightsRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._save_sampler_weights_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def list( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.CheckpointList: + """List checkpoints. + + List all checkpoints (training and sampler) for this session. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: CheckpointList. The CheckpointList is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.CheckpointList + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.CheckpointList] = kwargs.pop("cls", None) + + _request = build_checkpoints_list_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.CheckpointList, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def get( + self, + session_id: str, + checkpoint_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.CheckpointInfo: + """Get checkpoint info. + + Get metadata for a specific checkpoint. + + :param session_id: Required. + :type session_id: str + :param checkpoint_id: Required. + :type checkpoint_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: CheckpointInfo. The CheckpointInfo is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.CheckpointInfo + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.CheckpointInfo] = kwargs.pop("cls", None) + + _request = build_checkpoints_get_request( + session_id=session_id, + checkpoint_id=checkpoint_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.CheckpointInfo, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class SamplingOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.aio.FineTuningSessionClient`'s + :attr:`sampling` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def _sample_initial( + self, + session_id: str, + body: Union[_models.SampleRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncIterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[AsyncIterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sampling_sample_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [202]: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + async def begin_sample( + self, + session_id: str, + body: _models.SampleRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SampleRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_sample( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + async def begin_sample( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def begin_sample( + self, + session_id: str, + body: Union[_models.SampleRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> AsyncLROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SampleRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.SampleRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of AsyncLROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: + ~azure.core.polling.AsyncLROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, AsyncPollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = await self._sample_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + await raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: AsyncPollingMethod = cast( + AsyncPollingMethod, + AsyncLROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs), + ) + elif polling is False: + polling_method = cast(AsyncPollingMethod, AsyncNoPolling()) + else: + polling_method = polling + if cont_token: + return AsyncLROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return AsyncLROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + +class Operations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.aio.FineTuningSessionClient`'s + :attr:`operations` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: AsyncPipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace_async + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + async def get( + self, + session_id: str, + operation_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.OperationResult: + """Poll operation status. + + Poll for the result of an async fine-tuning operation (Azure LRO). + + Returns ``status: "running"`` while in progress, ``"succeeded"`` on completion, + or ``"failed"`` on error. When succeeded, the body contains the typed result + (``ForwardBackwardOperationResult``, ``OptimStepOperationResult``, etc.) + discriminated by ``type``. + + :param session_id: Required. + :type session_id: str + :param operation_id: Required. + :type operation_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: OperationResult. The OperationResult is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.OperationResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + + _request = build_operations_get_request( + session_id=session_id, + operation_id=operation_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = await self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + await response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.OperationResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_patch.py new file mode 100644 index 000000000000..87676c65a8f0 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/operations/_patch.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + + +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + + +def patch_sdk(): + """Do not remove from this file. + + `patch_sdk` is a last resort escape hatch that allows you to do customizations + you can't accomplish using the techniques described in + https://aka.ms/azsdk/python/dpcodegen/python/customize + """ diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py new file mode 100644 index 000000000000..dbca8dabe58d --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py @@ -0,0 +1,110 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + + +from ._models import ( # type: ignore + AdamParams, + ApiError, + ApiErrorResponse, + Checkpoint, + CheckpointInfo, + CheckpointList, + CreateSessionRequest, + Cursor, + Datum, + ForwardBackwardInput, + ForwardBackwardOperationResult, + ForwardBackwardRequest, + HeartbeatResponse, + LoRAConfig, + LossFnConfig, + LossFnInputs, + ModelInput, + ModelInputChunk, + OperationResult, + OptimStepOperationResult, + OptimStepRequest, + SampleOperationResult, + SampleRequest, + SampledSequence, + SamplingParams, + SaveCheckpointOperationResult, + SaveCheckpointRequest, + SaveSamplerWeightsOperationResult, + SaveSamplerWeightsRequest, + Session, + SessionList, + SessionModelData, + SessionSummary, + TensorData, +) + +from ._enums import ( # type: ignore + CheckpointType, + FoundryFeaturesOptInKeys, + LossFn, + OperationStatus, + OperationType, + SessionStatus, + SessionType, +) +from ._patch import __all__ as _patch_all +from ._patch import * +from ._patch import patch_sdk as _patch_sdk + +__all__ = [ + "AdamParams", + "ApiError", + "ApiErrorResponse", + "Checkpoint", + "CheckpointInfo", + "CheckpointList", + "CreateSessionRequest", + "Cursor", + "Datum", + "ForwardBackwardInput", + "ForwardBackwardOperationResult", + "ForwardBackwardRequest", + "HeartbeatResponse", + "LoRAConfig", + "LossFnConfig", + "LossFnInputs", + "ModelInput", + "ModelInputChunk", + "OperationResult", + "OptimStepOperationResult", + "OptimStepRequest", + "SampleOperationResult", + "SampleRequest", + "SampledSequence", + "SamplingParams", + "SaveCheckpointOperationResult", + "SaveCheckpointRequest", + "SaveSamplerWeightsOperationResult", + "SaveSamplerWeightsRequest", + "Session", + "SessionList", + "SessionModelData", + "SessionSummary", + "TensorData", + "CheckpointType", + "FoundryFeaturesOptInKeys", + "LossFn", + "OperationStatus", + "OperationType", + "SessionStatus", + "SessionType", +] +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore +_patch_sdk() diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py new file mode 100644 index 000000000000..3a69b82b84b5 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py @@ -0,0 +1,97 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +from enum import Enum +from azure.core import CaseInsensitiveEnumMeta + + +class CheckpointType(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Fine-tuning checkpoint type.""" + + TRAINING = "training" + """Full training checkpoint (optimizer state + LoRA weights).""" + SAMPLER = "sampler" + """Sampler-compatible weights snapshot (no optimizer state).""" + + +class FoundryFeaturesOptInKeys(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Type of FoundryFeaturesOptInKeys.""" + + EVALUATIONS_V1_PREVIEW = "Evaluations=V1Preview" + """EVALUATIONS_V1_PREVIEW.""" + SCHEDULES_V1_PREVIEW = "Schedules=V1Preview" + """SCHEDULES_V1_PREVIEW.""" + RED_TEAMS_V1_PREVIEW = "RedTeams=V1Preview" + """RED_TEAMS_V1_PREVIEW.""" + INSIGHTS_V1_PREVIEW = "Insights=V1Preview" + """INSIGHTS_V1_PREVIEW.""" + MEMORY_STORES_V1_PREVIEW = "MemoryStores=V1Preview" + """MEMORY_STORES_V1_PREVIEW.""" + FINETUNING_SESSIONS_V1_PREVIEW = "FineTuningSessions=V1Preview" + """FINETUNING_SESSIONS_V1_PREVIEW.""" + + +class LossFn(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Supported loss functions for forward-backward passes.""" + + CROSS_ENTROPY = "cross_entropy" + """Standard language-model cross-entropy loss.""" + IMPORTANCE_SAMPLING = "importance_sampling" + """REINFORCE-style importance-sampled policy gradient loss.""" + PPO = "ppo" + """PPO (Proximal Policy Optimization) clipped surrogate loss.""" + CISPO = "cispo" + """Clipped Importance-Sampled Policy Optimization (CISPO) loss.""" + SAPO = "sapo" + """Soft-Advantage Policy Optimization (SAPO) loss.""" + + +class OperationStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Lifecycle status of an async fine-tuning operation — standard Azure LRO values.""" + + RUNNING = "running" + """Operation is in progress.""" + SUCCEEDED = "succeeded" + """Operation completed successfully.""" + FAILED = "failed" + """Operation failed.""" + + +class OperationType(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Discriminator values for async fine-tuning operation results.""" + + FORWARD_BACKWARD = "forward_backward" + """A forward-backward pass operation.""" + OPTIM_STEP = "optim_step" + """An optimizer step operation.""" + SAMPLE = "sample" + """A sampling operation.""" + SAVE_CHECKPOINT = "save_checkpoint" + """A training checkpoint save operation.""" + SAVE_SAMPLER_WEIGHTS = "save_sampler_weights" + """A sampler-weights save operation.""" + + +class SessionStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Lifecycle status of a fine-tuning session.""" + + CREATED = "created" + """Session has been created and is waiting for the GPU engine.""" + READY = "ready" + """Session is loaded and ready to receive training or sampling requests.""" + UNLOADED = "unloaded" + """Session has been unloaded from the GPU engine.""" + FAILED = "failed" + """The engine hosting this session has died; weights are lost.""" + + +class SessionType(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Fine-tuning session type.""" + + TRAINING = "training" + """A training session for fine-tuning a model.""" diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py new file mode 100644 index 000000000000..1a29bde36e89 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py @@ -0,0 +1,1342 @@ +# pylint: disable=line-too-long,useless-suppression,too-many-lines +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=useless-super-delegation + +import datetime +from typing import Any, Literal, Mapping, Optional, TYPE_CHECKING, Union, overload + +from .._utils.model_base import Model as _Model, rest_discriminator, rest_field +from ._enums import OperationType + +if TYPE_CHECKING: + from .. import _types, models as _models + + +class AdamParams(_Model): + """Adam optimizer hyper-parameters. + + :ivar learning_rate: Learning rate. Required. + :vartype learning_rate: float + :ivar beta1: Adam β₁ coefficient. Required. + :vartype beta1: float + :ivar beta2: Adam β₂ coefficient. Required. + :vartype beta2: float + :ivar eps: Adam ε (numerical stability floor). Required. + :vartype eps: float + :ivar weight_decay: L₂ weight-decay coefficient. Required. + :vartype weight_decay: float + """ + + learning_rate: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Learning rate. Required.""" + beta1: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Adam β₁ coefficient. Required.""" + beta2: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Adam β₂ coefficient. Required.""" + eps: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Adam ε (numerical stability floor). Required.""" + weight_decay: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """L₂ weight-decay coefficient. Required.""" + + @overload + def __init__( + self, + *, + learning_rate: float, + beta1: float, + beta2: float, + eps: float, + weight_decay: float, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ApiError(_Model): + """ApiError. + + :ivar code: Required. + :vartype code: str + :ivar message: Required. + :vartype message: str + :ivar param: + :vartype param: str + :ivar type: + :vartype type: str + :ivar details: + :vartype details: list[~azure.ai.finetuning_sessions.models.ApiError] + :ivar additional_info: + :vartype additional_info: dict[str, any] + :ivar debug_info: + :vartype debug_info: dict[str, any] + """ + + code: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + message: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + param: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + type: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + details: Optional[list["_models.ApiError"]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + additional_info: Optional[dict[str, Any]] = rest_field( + name="additionalInfo", visibility=["read", "create", "update", "delete", "query"] + ) + debug_info: Optional[dict[str, Any]] = rest_field( + name="debugInfo", visibility=["read", "create", "update", "delete", "query"] + ) + + @overload + def __init__( + self, + *, + code: str, + message: str, + param: Optional[str] = None, + type: Optional[str] = None, + details: Optional[list["_models.ApiError"]] = None, + additional_info: Optional[dict[str, Any]] = None, + debug_info: Optional[dict[str, Any]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ApiErrorResponse(_Model): + """Error response for API failures. + + :ivar error: Required. + :vartype error: ~azure.ai.finetuning_sessions.models.ApiError + """ + + error: "_models.ApiError" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + error: "_models.ApiError", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class Checkpoint(_Model): + """A checkpoint item returned in GET /fine_tuning/sessions/{sessionId}/checkpoints. + + :ivar checkpoint_id: Required. + :vartype checkpoint_id: str + :ivar checkpoint_type: Required. Known values are: "training" and "sampler". + :vartype checkpoint_type: str or ~azure.ai.finetuning_sessions.models.CheckpointType + :ivar time: Timestamp when the checkpoint was saved. Required. + :vartype time: ~datetime.datetime + """ + + checkpoint_id: str = rest_field(visibility=["read"]) + """Required.""" + checkpoint_type: Union[str, "_models.CheckpointType"] = rest_field(visibility=["read"]) + """Required. Known values are: \"training\" and \"sampler\".""" + time: datetime.datetime = rest_field(visibility=["read"], format="rfc3339") + """Timestamp when the checkpoint was saved. Required.""" + + +class CheckpointInfo(_Model): + """Detailed metadata for a single checkpoint. + + :ivar base_model: Required. + :vartype base_model: str + :ivar is_lora: Required. + :vartype is_lora: bool + :ivar lora_rank: + :vartype lora_rank: int + """ + + base_model: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + is_lora: bool = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + lora_rank: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + base_model: str, + is_lora: bool, + lora_rank: Optional[int] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class CheckpointList(_Model): + """List of all checkpoints for a session (no pagination). + + :ivar checkpoints: Required. + :vartype checkpoints: list[~azure.ai.finetuning_sessions.models.Checkpoint] + """ + + checkpoints: list["_models.Checkpoint"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + checkpoints: list["_models.Checkpoint"], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class CreateSessionRequest(_Model): + """Request body for POST /fine_tuning/sessions. + + :ivar type: The session type. Required. "training" + :vartype type: str or ~azure.ai.finetuning_sessions.models.SessionType + :ivar base_model: Required. + :vartype base_model: str + :ivar lora_config: LoRA adapter config. Rank is fixed server-side for v1; omit to use the + server default. + :vartype lora_config: ~azure.ai.finetuning_sessions.models.LoRAConfig + :ivar user_metadata: + :vartype user_metadata: dict[str, str] + """ + + type: Union[str, "_models.SessionType"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The session type. Required. \"training\"""" + base_model: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + lora_config: Optional["_models.LoRAConfig"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """LoRA adapter config. Rank is fixed server-side for v1; omit to use the server default.""" + user_metadata: Optional[dict[str, str]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + type: Union[str, "_models.SessionType"], + base_model: str, + lora_config: Optional["_models.LoRAConfig"] = None, + user_metadata: Optional[dict[str, str]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class Cursor(_Model): + """Pagination cursor returned in list responses. + + :ivar offset: Zero-based index of the first item in the current page. Required. + :vartype offset: int + :ivar limit: Maximum number of items per page. Required. + :vartype limit: int + :ivar total_count: Total number of items across all pages. Required. + :vartype total_count: int + """ + + offset: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Zero-based index of the first item in the current page. Required.""" + limit: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Maximum number of items per page. Required.""" + total_count: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Total number of items across all pages. Required.""" + + @overload + def __init__( + self, + *, + offset: int, + limit: int, + total_count: int, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class Datum(_Model): + """A single training example. + + :ivar model_input: Token-ID input to the model. Required. + :vartype model_input: ~azure.ai.finetuning_sessions.models.ModelInput + :ivar loss_fn_inputs: Loss-function targets, aligned with model_input tokens. Required. + :vartype loss_fn_inputs: ~azure.ai.finetuning_sessions.models.LossFnInputs + """ + + model_input: "_models.ModelInput" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Token-ID input to the model. Required.""" + loss_fn_inputs: "_models.LossFnInputs" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Loss-function targets, aligned with model_input tokens. Required.""" + + @overload + def __init__( + self, + *, + model_input: "_models.ModelInput", + loss_fn_inputs: "_models.LossFnInputs", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ForwardBackwardInput(_Model): + """Inner payload for a forward-backward request. + + :ivar data: Required. + :vartype data: list[~azure.ai.finetuning_sessions.models.Datum] + :ivar loss_fn: Required. Known values are: "cross_entropy", "importance_sampling", "ppo", + "cispo", and "sapo". + :vartype loss_fn: str or ~azure.ai.finetuning_sessions.models.LossFn + :ivar loss_fn_config: + :vartype loss_fn_config: ~azure.ai.finetuning_sessions.models.LossFnConfig + """ + + data: list["_models.Datum"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + loss_fn: Union[str, "_models.LossFn"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required. Known values are: \"cross_entropy\", \"importance_sampling\", \"ppo\", \"cispo\", and + \"sapo\".""" + loss_fn_config: Optional["_models.LossFnConfig"] = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + + @overload + def __init__( + self, + *, + data: list["_models.Datum"], + loss_fn: Union[str, "_models.LossFn"], + loss_fn_config: Optional["_models.LossFnConfig"] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class OperationResult(_Model): + """Discriminated union of all async operation results. Returned by GET + /fine_tuning/sessions/{sessionId}/operations/{operationId}. + + You probably want to use the sub-classes and not this class directly. Known sub-classes are: + ForwardBackwardOperationResult, OptimStepOperationResult, SampleOperationResult, + SaveCheckpointOperationResult, SaveSamplerWeightsOperationResult + + :ivar type: Required. Known values are: "forward_backward", "optim_step", "sample", + "save_checkpoint", and "save_sampler_weights". + :vartype type: str or ~azure.ai.finetuning_sessions.models.OperationType + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + """ + + __mapping__: dict[str, _Model] = {} + type: str = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) + """Required. Known values are: \"forward_backward\", \"optim_step\", \"sample\", + \"save_checkpoint\", and \"save_sampler_weights\".""" + operation_id: str = rest_field(visibility=["read"]) + """Required.""" + status: Union[str, "_models.OperationStatus"] = rest_field(visibility=["read"]) + """Required. Known values are: \"running\", \"succeeded\", and \"failed\".""" + + @overload + def __init__( + self, + *, + type: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ForwardBackwardOperationResult(OperationResult, discriminator="forward_backward"): + """ForwardBackwardOperationResult. + + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + :ivar type: Required. A forward-backward pass operation. + :vartype type: str or ~azure.ai.finetuning_sessions.models.FORWARD_BACKWARD + :ivar total_loss: Required. + :vartype total_loss: float + :ivar per_datum_logprobs: + :vartype per_datum_logprobs: list[~azure.ai.finetuning_sessions.models.TensorData] + :ivar metrics: + :vartype metrics: dict[str, float] + """ + + type: Literal[OperationType.FORWARD_BACKWARD] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. A forward-backward pass operation.""" + total_loss: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + per_datum_logprobs: Optional[list["_models.TensorData"]] = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + metrics: Optional[dict[str, float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + total_loss: float, + per_datum_logprobs: Optional[list["_models.TensorData"]] = None, + metrics: Optional[dict[str, float]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = OperationType.FORWARD_BACKWARD # type: ignore + + +class ForwardBackwardRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/forward_backward. + + :ivar forward_backward_input: Required. + :vartype forward_backward_input: ~azure.ai.finetuning_sessions.models.ForwardBackwardInput + """ + + forward_backward_input: "_models.ForwardBackwardInput" = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """Required.""" + + @overload + def __init__( + self, + *, + forward_backward_input: "_models.ForwardBackwardInput", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class HeartbeatResponse(_Model): + """Response from POST /fine_tuning/sessions/{sessionId}/heartbeat. + + :ivar session_id: Required. + :vartype session_id: str + """ + + session_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + session_id: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class LoRAConfig(_Model): + """LoRA adapter configuration. ``rank`` is fixed server-side for v1; omit to use the server + default. + + :ivar rank: Number of LoRA rank dimensions. + :vartype rank: int + :ivar seed: Seed for LoRA weight initialisation. If omitted, the server picks a random seed and + echoes it back. + :vartype seed: int + """ + + rank: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Number of LoRA rank dimensions.""" + seed: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Seed for LoRA weight initialisation. If omitted, the server picks a random seed and echoes it + back.""" + + @overload + def __init__( + self, + *, + rank: Optional[int] = None, + seed: Optional[int] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class LossFnConfig(_Model): + """Optional per-loss-function hyper-parameters. + + :ivar clip_low_threshold: PPO (Proximal Policy Optimization) / CISPO (Clipped + Importance-Sampled Policy Optimization): lower clip threshold. + :vartype clip_low_threshold: float + :ivar clip_high_threshold: PPO (Proximal Policy Optimization) / CISPO (Clipped + Importance-Sampled Policy Optimization): upper clip threshold. + :vartype clip_high_threshold: float + :ivar tau_pos: SAPO (Soft-Advantage Policy Optimization): positive advantage temperature. + :vartype tau_pos: float + :ivar tau_neg: SAPO (Soft-Advantage Policy Optimization): negative advantage temperature. + :vartype tau_neg: float + """ + + clip_low_threshold: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """PPO (Proximal Policy Optimization) / CISPO (Clipped Importance-Sampled Policy Optimization): + lower clip threshold.""" + clip_high_threshold: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """PPO (Proximal Policy Optimization) / CISPO (Clipped Importance-Sampled Policy Optimization): + upper clip threshold.""" + tau_pos: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """SAPO (Soft-Advantage Policy Optimization): positive advantage temperature.""" + tau_neg: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """SAPO (Soft-Advantage Policy Optimization): negative advantage temperature.""" + + @overload + def __init__( + self, + *, + clip_low_threshold: Optional[float] = None, + clip_high_threshold: Optional[float] = None, + tau_pos: Optional[float] = None, + tau_neg: Optional[float] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class LossFnInputs(_Model): + """Per-datum loss function inputs used in forward-backward. + + :ivar target_tokens: Target token ids (shifted by 1 relative to model input). Required. + :vartype target_tokens: ~azure.ai.finetuning_sessions.models.TensorData + :ivar weights: Per-token weights (0.0 = masked, 1.0 = counted). Required. + :vartype weights: ~azure.ai.finetuning_sessions.models.TensorData + :ivar advantages: Per-token advantage estimates (required for REINFORCE/PPO (Proximal Policy + Optimization)/CISPO (Clipped Importance-Sampled Policy Optimization)/SAPO (Soft-Advantage + Policy Optimization)). Omit or pass empty array for cross-entropy. + :vartype advantages: ~azure.ai.finetuning_sessions.models.TensorData + :ivar logprobs: Per-token reference log-probabilities for KL-divergence regularisation. Omit or + pass empty array to skip KL. + :vartype logprobs: ~azure.ai.finetuning_sessions.models.TensorData + """ + + target_tokens: "_models.TensorData" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Target token ids (shifted by 1 relative to model input). Required.""" + weights: "_models.TensorData" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Per-token weights (0.0 = masked, 1.0 = counted). Required.""" + advantages: Optional["_models.TensorData"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Per-token advantage estimates (required for REINFORCE/PPO (Proximal Policy Optimization)/CISPO + (Clipped Importance-Sampled Policy Optimization)/SAPO (Soft-Advantage Policy Optimization)). + Omit or pass empty array for cross-entropy.""" + logprobs: Optional["_models.TensorData"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Per-token reference log-probabilities for KL-divergence regularisation. Omit or pass empty + array to skip KL.""" + + @overload + def __init__( + self, + *, + target_tokens: "_models.TensorData", + weights: "_models.TensorData", + advantages: Optional["_models.TensorData"] = None, + logprobs: Optional["_models.TensorData"] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ModelInput(_Model): + """Full model input as one or more token-ID chunks. + + :ivar chunks: Ordered list of token-ID chunks that together form the complete model input. + Required. + :vartype chunks: list[~azure.ai.finetuning_sessions.models.ModelInputChunk] + """ + + chunks: list["_models.ModelInputChunk"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Ordered list of token-ID chunks that together form the complete model input. Required.""" + + @overload + def __init__( + self, + *, + chunks: list["_models.ModelInputChunk"], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class ModelInputChunk(_Model): + """A contiguous block of token IDs forming part of a model input. + + :ivar tokens: Sequence of token IDs in this chunk. Required. + :vartype tokens: list[int] + """ + + tokens: list[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Sequence of token IDs in this chunk. Required.""" + + @overload + def __init__( + self, + *, + tokens: list[int], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class OptimStepOperationResult(OperationResult, discriminator="optim_step"): + """OptimStepOperationResult. + + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + :ivar type: Required. An optimizer step operation. + :vartype type: str or ~azure.ai.finetuning_sessions.models.OPTIM_STEP + :ivar grad_norm: Required. + :vartype grad_norm: float + :ivar step_count: Required. + :vartype step_count: int + :ivar metrics: + :vartype metrics: dict[str, float] + """ + + type: Literal[OperationType.OPTIM_STEP] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. An optimizer step operation.""" + grad_norm: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + step_count: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + metrics: Optional[dict[str, float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + grad_norm: float, + step_count: int, + metrics: Optional[dict[str, float]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = OperationType.OPTIM_STEP # type: ignore + + +class OptimStepRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/optim_step. + + :ivar adam_params: Required. + :vartype adam_params: ~azure.ai.finetuning_sessions.models.AdamParams + """ + + adam_params: "_models.AdamParams" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + adam_params: "_models.AdamParams", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SampledSequence(_Model): + """A single sampled sequence. + + :ivar tokens: Required. + :vartype tokens: list[int] + :ivar logprobs: + :vartype logprobs: list[float] + :ivar prompt_logprobs: + :vartype prompt_logprobs: list[float] + """ + + tokens: list[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + logprobs: Optional[list[float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + prompt_logprobs: Optional[list[float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + tokens: list[int], + logprobs: Optional[list[float]] = None, + prompt_logprobs: Optional[list[float]] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SampleOperationResult(OperationResult, discriminator="sample"): + """SampleOperationResult. + + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + :ivar type: Required. A sampling operation. + :vartype type: str or ~azure.ai.finetuning_sessions.models.SAMPLE + :ivar sequences: Required. + :vartype sequences: list[~azure.ai.finetuning_sessions.models.SampledSequence] + """ + + type: Literal[OperationType.SAMPLE] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. A sampling operation.""" + sequences: list["_models.SampledSequence"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + sequences: list["_models.SampledSequence"], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = OperationType.SAMPLE # type: ignore + + +class SampleRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/sample. + + :ivar num_samples: Number of independent completions to generate. Default 1. Required. + :vartype num_samples: int + :ivar prompt: Tokenised input prompt. Required. + :vartype prompt: ~azure.ai.finetuning_sessions.models.ModelInput + :ivar sampling_params: Required. + :vartype sampling_params: ~azure.ai.finetuning_sessions.models.SamplingParams + :ivar sampling_session_id: Sampling session ID from a prior save_sampler_weights call. + :vartype sampling_session_id: str + :ivar seq_id: Training step index; must match the seq_id used in save_sampler_weights. + :vartype seq_id: int + :ivar prompt_logprobs: If true, return per-token log-probabilities for the prompt tokens. + :vartype prompt_logprobs: bool + :ivar topk_prompt_logprobs: Number of top-k log-probabilities to return per prompt token. 0 = + none. Required. + :vartype topk_prompt_logprobs: int + """ + + num_samples: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Number of independent completions to generate. Default 1. Required.""" + prompt: "_models.ModelInput" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Tokenised input prompt. Required.""" + sampling_params: "_models.SamplingParams" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + sampling_session_id: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Sampling session ID from a prior save_sampler_weights call.""" + seq_id: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Training step index; must match the seq_id used in save_sampler_weights.""" + prompt_logprobs: Optional[bool] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """If true, return per-token log-probabilities for the prompt tokens.""" + topk_prompt_logprobs: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Number of top-k log-probabilities to return per prompt token. 0 = none. Required.""" + + @overload + def __init__( + self, + *, + num_samples: int, + prompt: "_models.ModelInput", + sampling_params: "_models.SamplingParams", + topk_prompt_logprobs: int, + sampling_session_id: Optional[str] = None, + seq_id: Optional[int] = None, + prompt_logprobs: Optional[bool] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SamplingParams(_Model): + """Token-generation sampling parameters. + + :ivar max_tokens: Maximum tokens to generate. Required. + :vartype max_tokens: int + :ivar temperature: Softmax temperature. Default 1.0. Required. + :vartype temperature: float + :ivar top_p: Nucleus (top-p) probability mass. Default 1.0. Required. + :vartype top_p: float + :ivar top_k: Top-k candidates. -1 = disabled. Required. + :vartype top_k: int + :ivar seed: RNG seed for reproducible samples. Server chooses randomly if omitted. + :vartype seed: int + :ivar stop_criteria: Stop criteria: either stop_token_ids or stop_strings, not both. Is either + a [int] type or a [str] type. + :vartype stop_criteria: list[int] or list[str] + """ + + max_tokens: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Maximum tokens to generate. Required.""" + temperature: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Softmax temperature. Default 1.0. Required.""" + top_p: float = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Nucleus (top-p) probability mass. Default 1.0. Required.""" + top_k: int = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Top-k candidates. -1 = disabled. Required.""" + seed: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """RNG seed for reproducible samples. Server chooses randomly if omitted.""" + stop_criteria: Optional["_types.StopCriteria"] = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """Stop criteria: either stop_token_ids or stop_strings, not both. Is either a [int] type or a + [str] type.""" + + @overload + def __init__( + self, + *, + max_tokens: int, + temperature: float, + top_p: float, + top_k: int, + seed: Optional[int] = None, + stop_criteria: Optional["_types.StopCriteria"] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SaveCheckpointOperationResult(OperationResult, discriminator="save_checkpoint"): + """SaveCheckpointOperationResult. + + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + :ivar type: Required. A training checkpoint save operation. + :vartype type: str or ~azure.ai.finetuning_sessions.models.SAVE_CHECKPOINT + :ivar checkpoint_id: Required. + :vartype checkpoint_id: str + :ivar path: Required. + :vartype path: str + """ + + type: Literal[OperationType.SAVE_CHECKPOINT] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. A training checkpoint save operation.""" + checkpoint_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + path: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + checkpoint_id: str, + path: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = OperationType.SAVE_CHECKPOINT # type: ignore + + +class SaveCheckpointRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/checkpoint. + + :ivar path: User-supplied checkpoint identifier. Alphanumeric plus underscores and hyphens; max + 255 characters. Required. + :vartype path: str + """ + + path: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """User-supplied checkpoint identifier. Alphanumeric plus underscores and hyphens; max 255 + characters. Required.""" + + @overload + def __init__( + self, + *, + path: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SaveSamplerWeightsOperationResult(OperationResult, discriminator="save_sampler_weights"): + """SaveSamplerWeightsOperationResult. + + :ivar operation_id: Required. + :vartype operation_id: str + :ivar status: Required. Known values are: "running", "succeeded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.OperationStatus + :ivar type: Required. A sampler-weights save operation. + :vartype type: str or ~azure.ai.finetuning_sessions.models.SAVE_SAMPLER_WEIGHTS + :ivar checkpoint_id: Required. + :vartype checkpoint_id: str + :ivar sampling_session_id: Required. + :vartype sampling_session_id: str + """ + + type: Literal[OperationType.SAVE_SAMPLER_WEIGHTS] = rest_discriminator(name="type", visibility=["read", "create", "update", "delete", "query"]) # type: ignore + """Required. A sampler-weights save operation.""" + checkpoint_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + sampling_session_id: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + checkpoint_id: str, + sampling_session_id: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + self.type = OperationType.SAVE_SAMPLER_WEIGHTS # type: ignore + + +class SaveSamplerWeightsRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/checkpoint_sample. + + :ivar path: Optional explicit identifier for the sampler checkpoint. + :vartype path: str + :ivar sampling_session_seq_id: Ordinal of this sampling session within the training run. + :vartype sampling_session_seq_id: int + :ivar seq_id: Training step sequence number. + :vartype seq_id: int + """ + + path: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Optional explicit identifier for the sampler checkpoint.""" + sampling_session_seq_id: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Ordinal of this sampling session within the training run.""" + seq_id: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Training step sequence number.""" + + @overload + def __init__( + self, + *, + path: Optional[str] = None, + sampling_session_seq_id: Optional[int] = None, + seq_id: Optional[int] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class Session(_Model): + """Response from GET /fine_tuning/sessions/{sessionId}. + + :ivar session_id: Unique identifier for this fine-tuning session. Required. + :vartype session_id: str + :ivar type: The session type. Required. "training" + :vartype type: str or ~azure.ai.finetuning_sessions.models.SessionType + :ivar status: Current lifecycle status of the session. Required. Known values are: "created", + "ready", "unloaded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.SessionStatus + :ivar model_data: Model and adapter configuration associated with this session. Required. + :vartype model_data: ~azure.ai.finetuning_sessions.models.SessionModelData + """ + + session_id: str = rest_field(visibility=["read"]) + """Unique identifier for this fine-tuning session. Required.""" + type: Union[str, "_models.SessionType"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The session type. Required. \"training\"""" + status: Union[str, "_models.SessionStatus"] = rest_field(visibility=["read"]) + """Current lifecycle status of the session. Required. Known values are: \"created\", \"ready\", + \"unloaded\", and \"failed\".""" + model_data: "_models.SessionModelData" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Model and adapter configuration associated with this session. Required.""" + + @overload + def __init__( + self, + *, + type: Union[str, "_models.SessionType"], + model_data: "_models.SessionModelData", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SessionList(_Model): + """Paginated list of fine-tuning sessions. + + :ivar data: Required. + :vartype data: list[~azure.ai.finetuning_sessions.models.SessionSummary] + :ivar cursor: Required. + :vartype cursor: ~azure.ai.finetuning_sessions.models.Cursor + """ + + data: list["_models.SessionSummary"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + cursor: "_models.Cursor" = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + + @overload + def __init__( + self, + *, + data: list["_models.SessionSummary"], + cursor: "_models.Cursor", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SessionModelData(_Model): + """Nested model sub-object within a session response. + + :ivar base_model: Required. + :vartype base_model: str + :ivar lora_config: + :vartype lora_config: ~azure.ai.finetuning_sessions.models.LoRAConfig + :ivar model_name: + :vartype model_name: str + """ + + base_model: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Required.""" + lora_config: Optional["_models.LoRAConfig"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + model_name: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + + @overload + def __init__( + self, + *, + base_model: str, + lora_config: Optional["_models.LoRAConfig"] = None, + model_name: Optional[str] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class SessionSummary(_Model): + """Summary item returned in the paginated GET /fine_tuning/sessions list. + + :ivar session_id: Unique identifier for this fine-tuning session. Required. + :vartype session_id: str + :ivar base_model: Base model used for this fine-tuning session. Required. + :vartype base_model: str + :ivar status: Current lifecycle status of the session. Required. Known values are: "created", + "ready", "unloaded", and "failed". + :vartype status: str or ~azure.ai.finetuning_sessions.models.SessionStatus + :ivar is_lora: Whether the session uses a LoRA adapter. Required. + :vartype is_lora: bool + :ivar lora_rank: LoRA rank, if applicable. + :vartype lora_rank: int + :ivar corrupted: Indicates whether the session state is corrupted and cannot be resumed. + Required. + :vartype corrupted: bool + :ivar last_request_time: Timestamp of the most recent request made against this session. + Required. + :vartype last_request_time: ~datetime.datetime + """ + + session_id: str = rest_field(visibility=["read"]) + """Unique identifier for this fine-tuning session. Required.""" + base_model: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Base model used for this fine-tuning session. Required.""" + status: Union[str, "_models.SessionStatus"] = rest_field(visibility=["read"]) + """Current lifecycle status of the session. Required. Known values are: \"created\", \"ready\", + \"unloaded\", and \"failed\".""" + is_lora: bool = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Whether the session uses a LoRA adapter. Required.""" + lora_rank: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """LoRA rank, if applicable.""" + corrupted: bool = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Indicates whether the session state is corrupted and cannot be resumed. Required.""" + last_request_time: datetime.datetime = rest_field(visibility=["read"], format="rfc3339") + """Timestamp of the most recent request made against this session. Required.""" + + @overload + def __init__( + self, + *, + base_model: str, + is_lora: bool, + corrupted: bool, + lora_rank: Optional[int] = None, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +class TensorData(_Model): + """A 1-D array of floating-point values serialised for the wire. + + :ivar data: The floating-point values of the tensor. Required. + :vartype data: list[float] + """ + + data: list[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """The floating-point values of the tensor. Required.""" + + @overload + def __init__( + self, + *, + data: list[float], + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py new file mode 100644 index 000000000000..87676c65a8f0 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + + +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + + +def patch_sdk(): + """Do not remove from this file. + + `patch_sdk` is a last resort escape hatch that allows you to do customizations + you can't accomplish using the techniques described in + https://aka.ms/azsdk/python/dpcodegen/python/customize + """ diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/__init__.py new file mode 100644 index 000000000000..8f152cec5e7e --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/__init__.py @@ -0,0 +1,33 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +# pylint: disable=wrong-import-position + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from ._patch import * # pylint: disable=unused-wildcard-import + +from ._operations import SessionsOperations # type: ignore +from ._operations import TrainingOperations # type: ignore +from ._operations import CheckpointsOperations # type: ignore +from ._operations import SamplingOperations # type: ignore +from ._operations import Operations # type: ignore + +from ._patch import __all__ as _patch_all +from ._patch import * +from ._patch import patch_sdk as _patch_sdk + +__all__ = [ + "SessionsOperations", + "TrainingOperations", + "CheckpointsOperations", + "SamplingOperations", + "Operations", +] +__all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore +_patch_sdk() diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_operations.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_operations.py new file mode 100644 index 000000000000..bf9ee2182f96 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_operations.py @@ -0,0 +1,2977 @@ +# pylint: disable=too-many-lines +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- +from collections.abc import MutableMapping +from io import IOBase +import json +from typing import Any, Callable, IO, Iterator, Literal, Optional, TypeVar, Union, cast, overload + +from azure.core import PipelineClient +from azure.core.exceptions import ( + ClientAuthenticationError, + HttpResponseError, + ResourceExistsError, + ResourceNotFoundError, + ResourceNotModifiedError, + StreamClosedError, + StreamConsumedError, + map_error, +) +from azure.core.pipeline import PipelineResponse +from azure.core.polling import LROPoller, NoPolling, PollingMethod +from azure.core.polling.base_polling import LROBasePolling +from azure.core.rest import HttpRequest, HttpResponse +from azure.core.tracing.decorator import distributed_trace +from azure.core.utils import case_insensitive_dict + +from .. import models as _models +from .._configuration import FineTuningSessionClientConfiguration +from .._utils.model_base import SdkJSONEncoder, _deserialize, _failsafe_deserialize +from .._utils.serialization import Deserializer, Serializer +from .._validation import api_version_validation +from ..models._enums import FoundryFeaturesOptInKeys + +JSON = MutableMapping[str, Any] +T = TypeVar("T") +ClsType = Optional[Callable[[PipelineResponse[HttpRequest, HttpResponse], T, dict[str, Any]], Any]] +List = list + +_SERIALIZER = Serializer() +_SERIALIZER.client_side_validation = False + + +def build_sessions_create_request( + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_sessions_list_request( + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + limit: Optional[int] = None, + offset: Optional[int] = None, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions" + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + if limit is not None: + _params["limit"] = _SERIALIZER.query("limit", limit, "int") + if offset is not None: + _params["offset"] = _SERIALIZER.query("offset", offset, "int") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_sessions_get_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_sessions_unload_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/complete" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_sessions_heartbeat_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/heartbeat" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_training_forward_backward_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/forward_backward" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_training_optim_step_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/optim_step" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_checkpoints_save_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/checkpoint" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_checkpoints_save_sampler_weights_request( # pylint: disable=name-too-long + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/checkpoint_sample" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_checkpoints_list_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/checkpoints" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_checkpoints_get_request( + session_id: str, + checkpoint_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/checkpoints/{checkpointId}" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + "checkpointId": _SERIALIZER.url("checkpoint_id", checkpoint_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_sampling_sample_request( + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/sample" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + if content_type is not None: + _headers["Content-Type"] = _SERIALIZER.header("content_type", content_type, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="POST", url=_url, params=_params, headers=_headers, **kwargs) + + +def build_operations_get_request( + session_id: str, + operation_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any +) -> HttpRequest: + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = case_insensitive_dict(kwargs.pop("params", {}) or {}) + + accept = _headers.pop("Accept", "application/json") + + # Construct URL + _url = "/fine_tuning/sessions/{sessionId}/request/{requestId}" + path_format_arguments = { + "sessionId": _SERIALIZER.url("session_id", session_id, "str"), + "requestId": _SERIALIZER.url("operation_id", operation_id, "str"), + } + + _url: str = _url.format(**path_format_arguments) # type: ignore + + # Construct parameters + _params["api-version"] = _SERIALIZER.query("api_version", api_version, "str") + + # Construct headers + _headers["Foundry-Features"] = _SERIALIZER.header("foundry_features", foundry_features, "str") + _headers["Accept"] = _SERIALIZER.header("accept", accept, "str") + + return HttpRequest(method="GET", url=_url, params=_params, headers=_headers, **kwargs) + + +class SessionsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`'s + :attr:`sessions` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _create_initial( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sessions_create_request( + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_create( + self, + body: _models.CreateSessionRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_create( + self, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_create( + self, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_create( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Create a fine-tuning session. + + Create a new fine-tuning session and allocate it to a GPU engine. + + Returns ``session_id`` array ``["session_xxx", "sampling_xxx"]``. + Use ``session_xxx`` as ``{sessionId}`` for training ops and + ``sampling_xxx`` as ``{samplingId}`` for sampling ops. + + :param body: Is one of the following types: CreateSessionRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._create_initial( + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def create( + self, + body: Union[_models.CreateSessionRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> JSON: + """Create a fine-tuning session (synchronous 200 response). + + POSTs to ``/fine_tuning/sessions`` and returns the response body directly. + Use this instead of ``begin_create`` when the server returns HTTP 200 (not 202 LRO). + + :param body: Is one of the following types: CreateSessionRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.CreateSessionRequest or JSON or IO[bytes] + :keyword foundry_features: Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: dict (JSON response body) + :rtype: dict + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[JSON] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sessions_create_request( + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize(_models.ApiErrorResponse, response) + raise HttpResponseError(response=response, model=error) + + deserialized: JSON = response.json() + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def list( + self, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + limit: Optional[int] = None, + offset: Optional[int] = None, + **kwargs: Any + ) -> _models.SessionList: + """List fine-tuning sessions. + + List all fine-tuning sessions for this project. + + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword limit: Default value is None. + :paramtype limit: int + :keyword offset: Default value is None. + :paramtype offset: int + :return: SessionList. The SessionList is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.SessionList + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.SessionList] = kwargs.pop("cls", None) + + _request = build_sessions_list_request( + foundry_features=foundry_features, + api_version=api_version, + limit=limit, + offset=offset, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.SessionList, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def get( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.Session: + """Get a fine-tuning session. + + Get information about a specific fine-tuning session. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: Session. The Session is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.Session + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.Session] = kwargs.pop("cls", None) + + _request = build_sessions_get_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.Session, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _unload_initial( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + _request = build_sessions_unload_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_unload( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Unload a fine-tuning session. + + Unload a session from the GPU engine, freeing memory. LoRA weights are lost; save a checkpoint + before calling this. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._unload_initial( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def heartbeat( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.HeartbeatResponse: + """Session heartbeat. + + Heartbeat — refresh an active session to prevent idle expiry. The SDK sends this automatically + every 30 seconds. Returns 404 if the session has already expired. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: HeartbeatResponse. The HeartbeatResponse is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.HeartbeatResponse + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.HeartbeatResponse] = kwargs.pop("cls", None) + + _request = build_sessions_heartbeat_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.HeartbeatResponse, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class TrainingOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`'s + :attr:`training` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _forward_backward_initial( + self, + session_id: str, + body: Union[_models.ForwardBackwardRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_training_forward_backward_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_forward_backward( + self, + session_id: str, + body: _models.ForwardBackwardRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.ForwardBackwardRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_forward_backward( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_forward_backward( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_forward_backward( + self, + session_id: str, + body: Union[_models.ForwardBackwardRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Forward and backward pass. + + Submit a mini-batch for a combined forward + backward pass. + + Gradients accumulate until an optimizer step is issued. + Poll the returned operation URL for ``ForwardBackwardResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: ForwardBackwardRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.ForwardBackwardRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._forward_backward_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _optim_step_initial( + self, + session_id: str, + body: Union[_models.OptimStepRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_training_optim_step_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_optim_step( + self, + session_id: str, + body: _models.OptimStepRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.OptimStepRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_optim_step( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_optim_step( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_optim_step( + self, + session_id: str, + body: Union[_models.OptimStepRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Optimizer step. + + Apply accumulated gradients to the LoRA weights using the Adam optimizer. + + Poll the returned operation URL for ``OptimStepResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: OptimStepRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.OptimStepRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._optim_step_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + +class CheckpointsOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`'s + :attr:`checkpoints` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _save_initial( + self, + session_id: str, + body: Union[_models.SaveCheckpointRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_checkpoints_save_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_save( + self, + session_id: str, + body: _models.SaveCheckpointRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveCheckpointRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_save( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_save( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_save( + self, + session_id: str, + body: Union[_models.SaveCheckpointRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save a training checkpoint. + + Save a training checkpoint (LoRA weights + optimizer state) to blob storage. + + Poll the returned operation URL for ``SaveCheckpointResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SaveCheckpointRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveCheckpointRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._save_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _save_sampler_weights_initial( + self, + session_id: str, + body: Union[_models.SaveSamplerWeightsRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_checkpoints_save_sampler_weights_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_save_sampler_weights( + self, + session_id: str, + body: _models.SaveSamplerWeightsRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveSamplerWeightsRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_save_sampler_weights( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_save_sampler_weights( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_save_sampler_weights( + self, + session_id: str, + body: Union[_models.SaveSamplerWeightsRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Save sampler weights. + + Save sampler-compatible weights (no optimizer state) for generation. + + Poll the returned operation URL for ``SaveSamplerWeightsResult``. + The result contains ``sampling_session_id`` — pass this to subsequent sample calls. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SaveSamplerWeightsRequest, JSON, IO[bytes] + Required. + :type body: ~azure.ai.finetuning_sessions.models.SaveSamplerWeightsRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._save_sampler_weights_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def list( + self, + session_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.CheckpointList: + """List checkpoints. + + List all checkpoints (training and sampler) for this session. + + :param session_id: Required. + :type session_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: CheckpointList. The CheckpointList is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.CheckpointList + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.CheckpointList] = kwargs.pop("cls", None) + + _request = build_checkpoints_list_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.CheckpointList, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def get( + self, + session_id: str, + checkpoint_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.CheckpointInfo: + """Get checkpoint info. + + Get metadata for a specific checkpoint. + + :param session_id: Required. + :type session_id: str + :param checkpoint_id: Required. + :type checkpoint_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: CheckpointInfo. The CheckpointInfo is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.CheckpointInfo + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.CheckpointInfo] = kwargs.pop("cls", None) + + _request = build_checkpoints_get_request( + session_id=session_id, + checkpoint_id=checkpoint_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.CheckpointInfo, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + + +class SamplingOperations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`'s + :attr:`sampling` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def _sample_initial( + self, + session_id: str, + body: Union[_models.SampleRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> Iterator[bytes]: + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[Iterator[bytes]] = kwargs.pop("cls", None) + + content_type = content_type or "application/json" + _content = None + if isinstance(body, (IOBase, bytes)): + _content = body + else: + _content = json.dumps(body, cls=SdkJSONEncoder, exclude_readonly=True) # type: ignore + + _request = build_sampling_sample_request( + session_id=session_id, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + content=_content, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = True + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + response_headers = {} + response_headers["Operation-Location"] = self._deserialize("str", response.headers.get("Operation-Location")) + + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + + return deserialized # type: ignore + + @overload + def begin_sample( + self, + session_id: str, + body: _models.SampleRequest, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: ~azure.ai.finetuning_sessions.models.SampleRequest + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_sample( + self, + session_id: str, + body: JSON, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: JSON + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for JSON body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @overload + def begin_sample( + self, + session_id: str, + body: IO[bytes], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + content_type: str = "application/json", + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Required. + :type body: IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :keyword content_type: Body Parameter content-type. Content type parameter for binary body. + Default value is "application/json". + :paramtype content_type: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def begin_sample( + self, + session_id: str, + body: Union[_models.SampleRequest, JSON, IO[bytes]], + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> LROPoller[_models.OperationResult]: + """Generate completions. + + Generate one or more completions using the session's current LoRA weights. + + Requires a prior ``checkpoint_sample`` call to push weights to the sampler. + Pass ``sampling_session_id`` and ``seq_id`` from that call in the request body. + + Poll the returned operation URL for ``FineTuningSampleResult``. + + :param session_id: Required. + :type session_id: str + :param body: Is one of the following types: SampleRequest, JSON, IO[bytes] Required. + :type body: ~azure.ai.finetuning_sessions.models.SampleRequest or JSON or IO[bytes] + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: An instance of LROPoller that returns OperationResult. The OperationResult is + compatible with MutableMapping + :rtype: ~azure.core.polling.LROPoller[~azure.ai.finetuning_sessions.models.OperationResult] + :raises ~azure.core.exceptions.HttpResponseError: + """ + _headers = case_insensitive_dict(kwargs.pop("headers", {}) or {}) + _params = kwargs.pop("params", {}) or {} + + content_type: Optional[str] = kwargs.pop("content_type", _headers.pop("Content-Type", None)) + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + polling: Union[bool, PollingMethod] = kwargs.pop("polling", True) + lro_delay = kwargs.pop("polling_interval", self._config.polling_interval) + cont_token: Optional[str] = kwargs.pop("continuation_token", None) + if cont_token is None: + raw_result = self._sample_initial( + session_id=session_id, + body=body, + foundry_features=foundry_features, + api_version=api_version, + content_type=content_type, + cls=lambda x, y, z: x, + headers=_headers, + params=_params, + **kwargs + ) + raw_result.http_response.read() # type: ignore + kwargs.pop("error_map", None) + + def get_long_running_output(pipeline_response): + response_headers = {} + response = pipeline_response.http_response + response_headers["Operation-Location"] = self._deserialize( + "str", response.headers.get("Operation-Location") + ) + + deserialized = _deserialize(_models.OperationResult, response.json().get("result", {})) + if cls: + return cls(pipeline_response, deserialized, response_headers) # type: ignore + return deserialized + + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + + if polling is True: + polling_method: PollingMethod = cast( + PollingMethod, LROBasePolling(lro_delay, path_format_arguments=path_format_arguments, **kwargs) + ) + elif polling is False: + polling_method = cast(PollingMethod, NoPolling()) + else: + polling_method = polling + if cont_token: + return LROPoller[_models.OperationResult].from_continuation_token( + polling_method=polling_method, + continuation_token=cont_token, + client=self._client, + deserialization_callback=get_long_running_output, + ) + return LROPoller[_models.OperationResult]( + self._client, raw_result, get_long_running_output, polling_method # type: ignore + ) + + +class Operations: + """ + .. warning:: + **DO NOT** instantiate this class directly. + + Instead, you should access the following operations through + :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`'s + :attr:`operations` attribute. + """ + + def __init__(self, *args, **kwargs) -> None: + input_args = list(args) + self._client: PipelineClient = input_args.pop(0) if input_args else kwargs.pop("client") + self._config: FineTuningSessionClientConfiguration = input_args.pop(0) if input_args else kwargs.pop("config") + self._serialize: Serializer = input_args.pop(0) if input_args else kwargs.pop("serializer") + self._deserialize: Deserializer = input_args.pop(0) if input_args else kwargs.pop("deserializer") + + @distributed_trace + @api_version_validation( + params_added_on={"virtual-public-preview": ["foundry_features", "api_version"]}, + ) + def get( + self, + session_id: str, + operation_id: str, + *, + foundry_features: Literal[FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW], + api_version: str, + **kwargs: Any + ) -> _models.OperationResult: + """Poll operation status. + + Poll for the result of an async fine-tuning operation (Azure LRO). + + Returns ``status: "running"`` while in progress, ``"succeeded"`` on completion, + or ``"failed"`` on error. When succeeded, the body contains the typed result + (``ForwardBackwardOperationResult``, ``OptimStepOperationResult``, etc.) + discriminated by ``type``. + + :param session_id: Required. + :type session_id: str + :param operation_id: Required. + :type operation_id: str + :keyword foundry_features: A feature flag opt-in required when using preview operations or + modifying persisted preview resources. FINETUNING_SESSIONS_V1_PREVIEW. Required. + :paramtype foundry_features: str or + ~azure.ai.finetuning_sessions.models.FINETUNING_SESSIONS_V1_PREVIEW + :keyword api_version: The API version to use for this operation. Required. + :paramtype api_version: str + :return: OperationResult. The OperationResult is compatible with MutableMapping + :rtype: ~azure.ai.finetuning_sessions.models.OperationResult + :raises ~azure.core.exceptions.HttpResponseError: + """ + error_map: MutableMapping = { + 401: ClientAuthenticationError, + 404: ResourceNotFoundError, + 409: ResourceExistsError, + 304: ResourceNotModifiedError, + } + error_map.update(kwargs.pop("error_map", {}) or {}) + + _headers = kwargs.pop("headers", {}) or {} + _params = kwargs.pop("params", {}) or {} + + cls: ClsType[_models.OperationResult] = kwargs.pop("cls", None) + + _request = build_operations_get_request( + session_id=session_id, + operation_id=operation_id, + foundry_features=foundry_features, + api_version=api_version, + headers=_headers, + params=_params, + ) + path_format_arguments = { + "endpoint": self._serialize.url("self._config.endpoint", self._config.endpoint, "str", skip_quote=True), + } + _request.url = self._client.format_url(_request.url, **path_format_arguments) + + _decompress = kwargs.pop("decompress", True) + _stream = kwargs.pop("stream", False) + pipeline_response: PipelineResponse = self._client._pipeline.run( # pylint: disable=protected-access + _request, stream=_stream, **kwargs + ) + + response = pipeline_response.http_response + + if response.status_code not in [200]: + if _stream: + try: + response.read() # Load the body in memory and close the socket + except (StreamConsumedError, StreamClosedError): + pass + map_error(status_code=response.status_code, response=response, error_map=error_map) + error = _failsafe_deserialize( + _models.ApiErrorResponse, + response, + ) + raise HttpResponseError(response=response, model=error) + + if _stream: + deserialized = response.iter_bytes() if _decompress else response.iter_raw() + else: + deserialized = _deserialize(_models.OperationResult, response.json()) + + if cls: + return cls(pipeline_response, deserialized, {}) # type: ignore + + return deserialized # type: ignore + diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_patch.py new file mode 100644 index 000000000000..87676c65a8f0 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/operations/_patch.py @@ -0,0 +1,21 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Customize generated code here. + +Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +""" + + +__all__: list[str] = [] # Add all objects you want publicly available to users at this package level + + +def patch_sdk(): + """Do not remove from this file. + + `patch_sdk` is a last resort escape hatch that allows you to do customizations + you can't accomplish using the techniques described in + https://aka.ms/azsdk/python/dpcodegen/python/customize + """ diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/py.typed b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/py.typed new file mode 100644 index 000000000000..e5aff4f83af8 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/py.typed @@ -0,0 +1 @@ +# Marker file for PEP 561. \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure_ai_finetuning_sessions-1.0.0b1-py3-none-any.whl b/sdk/ai/azure-ai-finetuning-sessions/azure_ai_finetuning_sessions-1.0.0b1-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..7e77bd8109fbedfbbe8d736a12c5addca48464be GIT binary patch literal 600768 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

hUjMm!qx^nsOg~&wgT>CT4MS=rBs3Z@ILfLX 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%0A9kx=;Gt>@EO?2 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 z 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.Zoh?MmmTW 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É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%0Dt!SuNA2g^wK?vAbce1crxYEk& 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∾wc4}3*-pK}M*ACe|`#~3=Ez2XsnDb26bFMUy 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


y3z%(%7B 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%0DhLin8{<<_mS~{?P19MF%l6|^d`#M}Utvdt@4-jH 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%0AjolHdR6}sqV7?v&+Av%+W@ih*d+3fskY0 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 literal 0 HcmV?d00001 diff --git a/sdk/ai/azure-ai-finetuning-sessions/dev_requirements.txt b/sdk/ai/azure-ai-finetuning-sessions/dev_requirements.txt new file mode 100644 index 000000000000..ad0907b03b93 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/dev_requirements.txt @@ -0,0 +1,4 @@ +-e ../../../eng/tools/azure-sdk-tools +../../core/azure-core +../../identity/azure-identity +aiohttp \ No newline at end of file diff --git a/sdk/ai/azure-ai-finetuning-sessions/pyproject.toml b/sdk/ai/azure-ai-finetuning-sessions/pyproject.toml new file mode 100644 index 000000000000..bc92a0259c82 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/pyproject.toml @@ -0,0 +1,61 @@ +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# Code generated by Microsoft (R) Python Code Generator. +# Changes may cause incorrect behavior and will be lost if the code is regenerated. +# -------------------------------------------------------------------------- + +[build-system] +requires = ["setuptools>=77.0.3", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "azure-ai-finetuning-sessions" +authors = [ + { name = "Microsoft Corporation", email = "azpysdkhelp@microsoft.com" }, +] +description = "Microsoft Corporation Azure Finetuning Sessions Client Library for Python" +license = "MIT" +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", +] +requires-python = ">=3.9" +keywords = ["azure", "azure sdk"] + +dependencies = [ + "isodate>=0.6.1", + "azure-core>=1.37.0", + "typing-extensions>=4.6.0", +] +dynamic = [ +"version", "readme" +] + +[project.urls] +repository = "https://github.com/Azure/azure-sdk-for-python" + +[tool.setuptools.dynamic] +version = {attr = "azure.ai.finetuning_sessions._version.VERSION"} +readme = {file = ["README.md", "CHANGELOG.md"], content-type = "text/markdown"} + +[tool.setuptools.packages.find] +exclude = [ + "tests*", + "generated_tests*", + "samples*", + "generated_samples*", + "doc*", + "azure", + "azure.ai", +] + +[tool.setuptools.package-data] +pytyped = ["py.typed"] diff --git a/sdk/ai/azure-ai-finetuning-sessions/test_smoke.py b/sdk/ai/azure-ai-finetuning-sessions/test_smoke.py new file mode 100644 index 000000000000..e1ba0b11cf71 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/test_smoke.py @@ -0,0 +1,155 @@ +""" +Offline smoke test for azure-ai-finetuning-sessions SDK. +Mirrors the hero code patterns from SPEC_FOUNDRY_AICLIENT.md. +Run: python test_smoke.py +No real endpoint or credentials required. +""" +import time +from azure.core.credentials import AccessToken +from azure.core.pipeline.transport import HttpTransport, HttpResponse as _TransportHttpResponse +from azure.ai.finetuning_sessions import FineTuningSessionClient, FineTuningSession +from azure.ai.finetuning_sessions.models import ( + CreateSessionRequest, + Datum, + ModelInput, + ModelInputChunk, + LossFnInputs, + TensorData, + AdamParams, + LoRAConfig, + SamplingParams, +) + + +class _FakeCredential: + def get_token(self, *scopes, **kwargs): + return AccessToken("fake_token", int(time.time()) + 3600) + + def close(self): + pass + + +class _FakeHttpResponse(_TransportHttpResponse): + """Returns 200 OK with smart bodies: POST→pending, GET→succeeded.""" + + def __init__(self, request): + super().__init__(request, None) + self.status_code = 200 + if getattr(request, 'method', 'POST') == "GET": + self._body = b'{"type": "forward_backward", "operation_id": "op1", "status": "succeeded"}' + else: + self._body = b'{"request_id": "op1", "session_id": "session_xxx", "status": "pending"}' + self.headers = {"content-type": "application/json"} + self.content_type = "application/json" + self.reason = "OK" + + def body(self): + return self._body + + def text(self, encoding="utf-8"): + return self._body.decode(encoding or "utf-8") + + def stream_download(self, pipeline, **kwargs): + yield self._body + + def iter_bytes(self, **kwargs): + yield self._body + + def iter_raw(self, **kwargs): + yield self._body + + def read(self): + return self._body + + def json(self): + import json + return json.loads(self._body) + + def close(self): + pass + + def raise_for_status(self): + pass + + +class _FakeTransport(HttpTransport): + """Intercepts all HTTP calls and returns a fake 202 Accepted — no network needed.""" + + def send(self, request, **kwargs): + return _FakeHttpResponse(request) + + def open(self): pass + def close(self): pass + def __enter__(self): return self + def __exit__(self, *args): self.close() + + +# ── Setup ───────────────────────────────────────────────────────────────────── +print("=== azure-ai-finetuning-sessions smoke test ===") +print("(Based on SPEC_FOUNDRY_AICLIENT.md hero code samples)\n") + +client = FineTuningSessionClient( + endpoint="https://fake", + credential=_FakeCredential(), + transport=_FakeTransport(), +) +session = FineTuningSession(client, session_id="session_xxx") +print(f"✓ FineTuningSession: session_id={session.session_id}") + +assert hasattr(client, "sessions"), "missing: client.sessions" +assert hasattr(client, "training"), "missing: client.training" +assert hasattr(client, "checkpoints"), "missing: client.checkpoints" +assert hasattr(client, "sampling"), "missing: client.sampling" +assert hasattr(client, "operations"), "missing: client.operations" +print("✓ Sub-clients: sessions, training, checkpoints, sampling, operations") + +# ── Build training data ──────────────────────────────────────────────────────── +prompt_ids = [1, 2, 3, 4] +target_ids = [5, 6, 7] +all_ids = prompt_ids + target_ids +weights = [0.0] * len(prompt_ids) + [1.0] * len(target_ids) + +batch = [ + Datum( + model_input=ModelInput(chunks=[ModelInputChunk(tokens=all_ids[:-1])]), + loss_fn_inputs=LossFnInputs( + target_tokens=TensorData(data=[float(t) for t in all_ids[1:]]), + weights=TensorData(data=weights[1:]), + ), + ) +] +print(f"✓ Batch of {len(batch)} Datum built\n") + +# ── Spec Scenario 1: SFT training loop ──────────────────────────────────────── +fb_op = session.forward_backward(batch, loss_fn="cross_entropy") +print(f"✓ fb_op = session.forward_backward(batch, loss_fn='cross_entropy') → {type(fb_op).__name__}") + +opt_op = session.optim_step(AdamParams(learning_rate=1e-4, beta1=0.9, beta2=0.95, eps=1e-12, weight_decay=0.0)) +print(f"✓ opt_op = session.optim_step(AdamParams(learning_rate=1e-4)) → {type(opt_op).__name__}") + +ckpt_op = session.save_weights("sft_piglatin_v1") +print(f"✓ ckpt_op = session.save_weights('sft_piglatin_v1') → {type(ckpt_op).__name__}") + +# ── Spec Scenario 2: RFT sampling ───────────────────────────────────────────── +sampler_op = session.save_weights_for_sampler(seq_id=0) +print(f"✓ sampler_op = session.save_weights_for_sampler(seq_id=0) → {type(sampler_op).__name__}") + +sample_op = session.sample( + prompt_tokens=prompt_ids, + sampling_params=SamplingParams(max_tokens=32, temperature=1.0, top_p=1.0, top_k=-1), + num_samples=4, + sampling_session_id="sampling_abc123", + seq_id=0, + prompt_logprobs=True, +) +print(f"✓ sample_op = session.sample(prompt_tokens, params, num_samples=4) → {type(sample_op).__name__}") + +# ── Session creation body ────────────────────────────────────────────────────── +session_body = CreateSessionRequest( + type="training", + base_model="Qwen/Qwen3-0.6B", + lora_config=LoRAConfig(rank=16), +) +print(f"✓ CreateSessionRequest: base_model={session_body.base_model}, lora_rank={session_body.lora_config.rank}") + +print("\n=== All checks passed ✓ ===") diff --git a/sdk/ai/azure-ai-finetuning-sessions/tests/conftest.py b/sdk/ai/azure-ai-finetuning-sessions/tests/conftest.py new file mode 100644 index 000000000000..9b307a8b6deb --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/tests/conftest.py @@ -0,0 +1,132 @@ +# --------------------------------------------------------------------------- +# Shared fixtures for azure-ai-finetuning-sessions unit tests. +# --------------------------------------------------------------------------- +import time + +import pytest +from azure.core.credentials import AccessToken +from azure.core.pipeline.transport import HttpTransport +from azure.core.pipeline.transport import HttpResponse as _TransportHttpResponse + +from azure.ai.finetuning_sessions import FineTuningSessionClient, FineTuningSession +from azure.ai.finetuning_sessions.models import ( + Datum, + ModelInput, + ModelInputChunk, + LossFnInputs, + TensorData, +) + + +# ── Fake credential ────────────────────────────────────────────────────────── + +class FakeCredential: + def get_token(self, *scopes, **kwargs): + return AccessToken("fake_token", int(time.time()) + 3600) + + def close(self): + pass + + +# ── Fake HTTP transport ─────────────────────────────────────────────────────── + +class FakeHttpResponse(_TransportHttpResponse): + """Returns 200 OK with smart request/result bodies for POST vs GET.""" + + def __init__(self, request, body: bytes = None, status_code: int = 200): + super().__init__(request, None) + self.status_code = status_code + if body is None: + if getattr(request, 'method', 'POST') == "GET": + body = b'{"type": "forward_backward", "operation_id": "req1", "status": "succeeded"}' + else: + body = b'{"request_id": "req1", "session_id": "session_test", "status": "pending"}' + self.headers = {"content-type": "application/json"} + self.content_type = "application/json" + self.reason = "OK" + self._body = body + + def body(self): + return self._body + + def text(self, encoding=None): + return self._body.decode(encoding or "utf-8") + + def stream_download(self, pipeline, **kwargs): + yield self._body + + def iter_bytes(self, **kwargs): + yield self._body + + def iter_raw(self, **kwargs): + yield self._body + + def read(self): + return self._body + + def json(self): + import json + return json.loads(self._body) + + def close(self): + pass + + def raise_for_status(self): + pass + + +class FakeTransport(HttpTransport): + """Captures all outgoing requests and returns configurable fake responses.""" + + def __init__(self, response_body: bytes = None, status_code: int = 200): + self.requests: list = [] + self._response_body = response_body + self._status_code = status_code + + def send(self, request, **kwargs): + self.requests.append(request) + return FakeHttpResponse(request, self._response_body, self._status_code) + + def open(self): pass + def close(self): pass + def __enter__(self): return self + def __exit__(self, *args): self.close() + + +# ── Pytest fixtures ─────────────────────────────────────────────────────────── + +@pytest.fixture +def transport(): + return FakeTransport() + + +@pytest.fixture +def client(transport): + return FineTuningSessionClient( + endpoint="https://fake", + credential=FakeCredential(), + transport=transport, + ) + + +@pytest.fixture +def session(client): + return FineTuningSession(client, session_id="session_test") + + +@pytest.fixture +def batch(): + """A minimal single-datum training batch.""" + prompt_ids = [1, 2, 3, 4] + target_ids = [5, 6, 7] + all_ids = prompt_ids + target_ids + weights = [0.0] * len(prompt_ids) + [1.0] * len(target_ids) + return [ + Datum( + model_input=ModelInput(chunks=[ModelInputChunk(tokens=all_ids[:-1])]), + loss_fn_inputs=LossFnInputs( + target_tokens=TensorData(data=[float(t) for t in all_ids[1:]]), + weights=TensorData(data=weights[1:]), + ), + ) + ] diff --git a/sdk/ai/azure-ai-finetuning-sessions/tests/test_finetuning_session.py b/sdk/ai/azure-ai-finetuning-sessions/tests/test_finetuning_session.py new file mode 100644 index 000000000000..41bf32862a89 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/tests/test_finetuning_session.py @@ -0,0 +1,177 @@ +# --------------------------------------------------------------------------- +# Unit tests for FineTuningSession (_patch.py convenience wrapper). +# --------------------------------------------------------------------------- +import json + +import pytest + +from azure.ai.finetuning_sessions import FineTuningSession +from azure.ai.finetuning_sessions.models import ( + AdamParams, + LoRAConfig, + CreateSessionRequest, + OperationResult, + SamplingParams, +) + + +class TestFineTuningSessionInstantiation: + + def test_session_id_stored(self, client): + s = FineTuningSession(client, session_id="abc123") + assert s.session_id == "abc123" + + def test_client_stored(self, client): + s = FineTuningSession(client, session_id="abc123") + assert s._client is client + + +class TestSubClients: + + def test_all_sub_clients_present(self, client): + for name in ("sessions", "training", "checkpoints", "sampling", "operations"): + assert hasattr(client, name), f"missing: client.{name}" + + +class TestForwardBackward: + + def test_returns_operation_result(self, session, batch): + result = session.forward_backward(batch, loss_fn="cross_entropy") + assert isinstance(result, OperationResult) + + def test_default_loss_fn_is_cross_entropy(self, session, batch, transport): + session.forward_backward(batch) + req = transport.requests[0] + body = json.loads(req.body) + assert body["forward_backward_input"]["loss_fn"] == "cross_entropy" + + def test_custom_loss_fn(self, session, batch, transport): + session.forward_backward(batch, loss_fn="dpo") + req = transport.requests[0] + body = json.loads(req.body) + assert body["forward_backward_input"]["loss_fn"] == "dpo" + + def test_request_targets_correct_session(self, session, batch, transport): + session.forward_backward(batch) + req = transport.requests[0] + assert "session_test" in req.url + + def test_preview_header_sent(self, session, batch, transport): + session.forward_backward(batch) + req = transport.requests[0] + assert req.headers.get("Foundry-Features") == "FineTuningSessions=V1Preview" + + +class TestOptimStep: + + def test_returns_operation_result(self, session): + params = AdamParams(learning_rate=1e-4, beta1=0.9, beta2=0.95, eps=1e-12, weight_decay=0.0) + result = session.optim_step(params) + assert isinstance(result, OperationResult) + + def test_request_contains_adam_params(self, session, transport): + params = AdamParams(learning_rate=2e-5, beta1=0.9, beta2=0.95, eps=1e-12, weight_decay=0.01) + session.optim_step(params) + req = transport.requests[0] + body = json.loads(req.body) + assert body["adam_params"]["learning_rate"] == pytest.approx(2e-5) + + def test_request_targets_correct_session(self, session, transport): + session.optim_step(AdamParams(learning_rate=1e-4, beta1=0.9, beta2=0.95, eps=1e-12, weight_decay=0.0)) + assert "session_test" in transport.requests[0].url + + +class TestSaveWeights: + + def test_returns_operation_result(self, session): + assert isinstance(session.save_weights("my_ckpt"), OperationResult) + + def test_request_contains_path(self, session, transport): + session.save_weights("sft_piglatin_v1") + body = json.loads(transport.requests[0].body) + assert body["path"] == "sft_piglatin_v1" + + +class TestSaveWeightsForSampler: + + def test_returns_operation_result(self, session): + assert isinstance(session.save_weights_for_sampler(seq_id=0), OperationResult) + + def test_request_contains_seq_id(self, session, transport): + session.save_weights_for_sampler(seq_id=7) + body = json.loads(transport.requests[0].body) + assert body["seq_id"] == 7 + + def test_optional_path(self, session, transport): + session.save_weights_for_sampler(seq_id=0, path="explicit_path") + body = json.loads(transport.requests[0].body) + assert body["path"] == "explicit_path" + + +class TestSample: + + def test_returns_operation_result(self, session): + result = session.sample( + prompt_tokens=[1, 2, 3], + sampling_params=SamplingParams(max_tokens=16, temperature=1.0, top_p=1.0, top_k=-1), + num_samples=2, + ) + assert isinstance(result, OperationResult) + + def test_request_contains_num_samples(self, session, transport): + session.sample( + prompt_tokens=[1, 2, 3], + sampling_params=SamplingParams(max_tokens=16, temperature=1.0, top_p=1.0, top_k=-1), + num_samples=4, + ) + body = json.loads(transport.requests[0].body) + assert body["num_samples"] == 4 + + def test_request_contains_prompt_tokens(self, session, transport): + session.sample( + prompt_tokens=[10, 20, 30], + sampling_params=SamplingParams(max_tokens=16, temperature=1.0, top_p=1.0, top_k=-1), + ) + body = json.loads(transport.requests[0].body) + tokens = body["prompt"]["chunks"][0]["tokens"] + assert tokens == [10, 20, 30] + + def test_prompt_logprobs_default_false(self, session, transport): + session.sample( + prompt_tokens=[1, 2], + sampling_params=SamplingParams(max_tokens=8, temperature=1.0, top_p=1.0, top_k=-1), + ) + body = json.loads(transport.requests[0].body) + assert body.get("promptLogprobs", False) is False + + +class TestHeartbeat: + + def test_request_targets_correct_session(self, session, transport): + # heartbeat is a synchronous POST — returns 200 with a HeartbeatResponse body + transport._status_code = 200 + transport._response_body = b'{"session_id": "session_test"}' + session.heartbeat() + assert "session_test" in transport.requests[0].url + + +class TestClose: + + def test_returns_none(self, session): + assert session.close() is None + + def test_request_targets_correct_session(self, session, transport): + session.close() + assert "session_test" in transport.requests[0].url + + +class TestCreateSessionRequest: + + def test_fields(self): + req = CreateSessionRequest( + type="training", + base_model="Qwen/Qwen3-0.6B", + lora_config=LoRAConfig(rank=16), + ) + assert req.base_model == "Qwen/Qwen3-0.6B" + assert req.lora_config.rank == 16 diff --git a/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml new file mode 100644 index 000000000000..1aeac3c8648b --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml @@ -0,0 +1,6 @@ +directory: specification/ai-foundry/data-plane/Foundry/src/sdk-session +commit: 6d7e8e2c93583d3fe8092aa0d6d953decf26d109 +repo: Azure/azure-rest-api-specs-pr +additionalDirectories: +- specification/ai-foundry/data-plane/Foundry/src/session-finetuning +- specification/ai-foundry/data-plane/Foundry/src/common From 9e3cd1ea8f9c07bde794f401922e222f1e839c2c Mon Sep 17 00:00:00 2001 From: Harshith Arun Kumar Date: Mon, 15 Jun 2026 10:16:34 -0700 Subject: [PATCH 2/4] feat: sync azure-ai-finetuning-sessions SDK with loom master Updates generated models (SessionStatus vocabulary, LoRAConfig.alpha, SampledSequence.text, SaveCheckpointRequest.step_number/metrics, ejectable, Forward models) and ports custom patch files, typed exceptions (_exceptions.py), and logging setup (_logging_setup.py) from loom master. --- .../azure/ai/finetuning_sessions/__init__.py | 25 + .../ai/finetuning_sessions/_exceptions.py | 373 +++++ .../ai/finetuning_sessions/_logging_setup.py | 138 ++ .../azure/ai/finetuning_sessions/_patch.py | 909 ++++++++++- .../finetuning_sessions/aio/_configuration.py | 14 +- .../ai/finetuning_sessions/aio/_patch.py | 1412 ++++++++++++++++- .../ai/finetuning_sessions/models/__init__.py | 4 + .../ai/finetuning_sessions/models/_enums.py | 19 +- .../ai/finetuning_sessions/models/_models.py | 80 +- .../ai/finetuning_sessions/models/_patch.py | 47 +- 10 files changed, 2918 insertions(+), 103 deletions(-) create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_exceptions.py create mode 100644 sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_logging_setup.py diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py index 5e8a0824d3fe..b3670902c57f 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/__init__.py @@ -13,10 +13,27 @@ from ._patch import * # pylint: disable=unused-wildcard-import from ._client import FineTuningSessionClient # type: ignore +from ._exceptions import ( + BatchTooLargeError, + ContentionError, + EngineDeadError, + MalformedDatumError, + TrainingEngineError, + FineTuningSessionsError, + RequestValidationError, + NoCapacityError, +) +from ._logging_setup import install_default_logging as _install_default_logging from ._version import VERSION __version__ = VERSION +# Prepend a UTC timestamp to SDK warnings when no logging handler is +# configured to render one (i.e. records would fall through to +# logging.lastResort). No-op in any environment with a real handler. +# Idempotent; installs no handler. +_install_default_logging() + try: from ._patch import __all__ as _patch_all from ._patch import * @@ -27,6 +44,14 @@ __all__ = [ "FineTuningSessionClient", "FineTuningSession", + "FineTuningSessionsError", + "BatchTooLargeError", + "NoCapacityError", + "TrainingEngineError", + "EngineDeadError", + "ContentionError", + "RequestValidationError", + "MalformedDatumError", ] __all__.extend([p for p in _patch_all if p not in __all__]) # pyright: ignore diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_exceptions.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_exceptions.py new file mode 100644 index 000000000000..06599c760dd3 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_exceptions.py @@ -0,0 +1,373 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Typed exceptions for actionable failure modes. + +Customers should branch on exception type rather than grepping message strings: + + from azure.ai.finetuning_sessions import ( + BatchTooLargeError, + NoCapacityError, + TrainingEngineError, + ContentionError, + RequestValidationError, + ) + + try: + result = session.forward_backward(batch, loss_fn="cross_entropy") + except BatchTooLargeError as e: + # Split batch and retry + ... + except NoCapacityError as e: + # Wait e.retry_after_sec, or switch project + ... + except TrainingEngineError as e: + # Stop the run — model weights are lost + ... + +Each exception carries structured metadata extracted from the server's response +body so callers can make decisions without string parsing. +""" +from __future__ import annotations + +from typing import Any, Optional + +from azure.core.exceptions import HttpResponseError + + +class FineTuningSessionsError(HttpResponseError): + """Base class for all typed SDK exceptions. + + Inherits from ``azure.core.exceptions.HttpResponseError`` so existing + ``except HttpResponseError`` handlers still catch these. + """ + + def __init__(self, message: str, *, response: Any = None, **kwargs: Any) -> None: + super().__init__(message=message, response=response, **kwargs) + + +class BatchTooLargeError(FineTuningSessionsError): + """The batch exceeded the server's size limit. + + Action: split the batch into smaller chunks and retry. + + Attributes: + max_batch_size: The maximum batch size the server accepts (if reported). + actual_batch_size: The batch size that was rejected (if reported). + """ + + def __init__( + self, + message: str, + *, + max_batch_size: Optional[int] = None, + actual_batch_size: Optional[int] = None, + response: Any = None, + **kwargs: Any, + ) -> None: + super().__init__(message, response=response, **kwargs) + self.max_batch_size = max_batch_size + self.actual_batch_size = actual_batch_size + + +class NoCapacityError(FineTuningSessionsError): + """No engine capacity is currently available. + + Action: wait ``retry_after_sec`` seconds and retry, or switch to a + different project/endpoint. + + Attributes: + retry_after_sec: Suggested wait time in seconds before retrying. + reason: Server-reported reason string (e.g. ``"engine_busy"``). + """ + + def __init__( + self, + message: str, + *, + retry_after_sec: Optional[float] = None, + reason: Optional[str] = None, + response: Any = None, + **kwargs: Any, + ) -> None: + super().__init__(message, response=response, **kwargs) + self.retry_after_sec = retry_after_sec + self.reason = reason + + +class TrainingEngineError(FineTuningSessionsError): + """The engine serving this session has died. + + Action: stop the training run, alert on-call. LoRA weights in VRAM are + lost. Do NOT retry on the same session — create a new one (optionally + from the last checkpoint). + + Attributes: + session_id: The session that was being served. + error_code: Server error code (e.g. ``"worker_crashed"``). + debug_ref: Opaque reference for support tickets. + """ + + def __init__( + self, + message: str, + *, + session_id: Optional[str] = None, + error_code: Optional[str] = None, + debug_ref: Optional[str] = None, + response: Any = None, + **kwargs: Any, + ) -> None: + super().__init__(message, response=response, **kwargs) + self.session_id = session_id + self.error_code = error_code + self.debug_ref = debug_ref + + +class ContentionError(FineTuningSessionsError): + """The engine is temporarily contended (busy with other tenants). + + Action: back off with exponential delay. Do NOT retry immediately. + + Attributes: + retry_after_sec: Suggested wait time before retrying. + reason: Server-reported reason string. + """ + + def __init__( + self, + message: str, + *, + retry_after_sec: Optional[float] = None, + reason: Optional[str] = None, + response: Any = None, + **kwargs: Any, + ) -> None: + super().__init__(message, response=response, **kwargs) + self.retry_after_sec = retry_after_sec + self.reason = reason + + +class RequestValidationError(FineTuningSessionsError): + """One or more datums in the batch were rejected as invalid. + + Action: this is terminal for the affected datums — fix the data. + + Attributes: + field: The field that failed validation (e.g. ``"forward_backward_input.data"``). + error_code: Server error code (e.g. ``"invalid_request"``). + debug_ref: Opaque reference for support tickets. + """ + + def __init__( + self, + message: str, + *, + field: Optional[str] = None, + error_code: Optional[str] = None, + debug_ref: Optional[str] = None, + response: Any = None, + **kwargs: Any, + ) -> None: + super().__init__(message, response=response, **kwargs) + self.field = field + self.error_code = error_code + self.debug_ref = debug_ref + + +def _classify_http_error( + status_code: int, + body: Optional[dict], + *, + response: Any = None, + session_id: Optional[str] = None, +) -> Optional[FineTuningSessionsError]: + """Attempt to classify an HTTP error response into a typed exception. + + Returns ``None`` if the error doesn't match any known pattern (caller + should fall through to generic error handling). + """ + if body is None: + body = {} + + # --- HTTP 413: Batch too large --- + if status_code == 413: + msg = body.get("message") or body.get("detail") or "Batch too large" + field = body.get("field") + # Try to extract numbers from the message + max_size = None + actual_size = None + if isinstance(msg, str): + import re + # "Batch size (N) exceeds the maximum allowed (M)" + m = re.search(r"Batch size \((\d+)\) exceeds the maximum allowed \((\d+)\)", msg) + if m: + actual_size = int(m.group(1)) + max_size = int(m.group(2)) + if field and "data" in field: + return BatchTooLargeError( + msg, + max_batch_size=max_size, + actual_batch_size=actual_size, + response=response, + ) + # 413 for metadata is not a batch error — return None to let generic handling take over + return BatchTooLargeError( + msg, max_batch_size=max_size, actual_batch_size=actual_size, response=response + ) + + # --- HTTP 503: No capacity / contention --- + if status_code == 503: + reason = body.get("reason", "") + msg = body.get("message") or body.get("detail") or "No available engine capacity" + retry_after: Optional[float] = None + if body.get("retry_after_sec") is not None: + retry_after = float(body["retry_after_sec"]) + + # If the body is a plain string (legacy format), extract from detail + if isinstance(msg, str) and ("capacity" in msg.lower() or "no engine" in msg.lower()): + return NoCapacityError( + msg, retry_after_sec=retry_after, reason=reason or "engine_busy", response=response + ) + if reason == "engine_busy": + return NoCapacityError( + msg, retry_after_sec=retry_after, reason=reason, response=response + ) + # Generic 503 — treat as contention + return ContentionError( + msg, retry_after_sec=retry_after, reason=reason or None, response=response + ) + + # --- HTTP 500: Engine dead / worker crashed / capacity exhaustion --- + if status_code == 500: + # The body may be a flat dict or nested under "detail". + detail = body.get("detail") if isinstance(body.get("detail"), dict) else None + effective = detail or body + msg = ( + effective.get("error_message") + or effective.get("message") + or body.get("detail") + or body.get("error") + or "Internal server error" + ) + error_type = effective.get("type", "") + error_code = effective.get("error_code") or effective.get("code") + debug_ref = effective.get("debug_ref") + + # Capacity exhaustion: server returns type="internal_model_error" with + # a message mentioning capacity. Should really be a 503, + # but older servers return 500. + if isinstance(msg, str) and ("capacity" in msg.lower() or "no engine" in msg.lower()): + return NoCapacityError( + msg, + retry_after_sec=None, + reason=error_type or "no_capacity", + response=response, + ) + + if error_code in ("worker_crashed", "engine_oom", "engine_timeout"): + return TrainingEngineError( + msg, + session_id=session_id, + error_code=error_code, + debug_ref=debug_ref, + response=response, + ) + # Check message heuristics for legacy plain-string responses + if isinstance(msg, str) and any( + kw in msg.lower() for kw in ("engine", "dead", "crashed", "died") + ): + return TrainingEngineError( + msg, + session_id=session_id, + error_code=error_code, + debug_ref=debug_ref, + response=response, + ) + return None # Unknown 500 — don't classify + + # --- HTTP 400/422: Malformed datum / invalid request --- + if status_code in (400, 422): + error_type = body.get("type", "") + msg = body.get("message") or body.get("detail") or "Invalid request" + field = body.get("field") + error_code = body.get("error_code") or body.get("code") + debug_ref = body.get("debug_ref") + + if error_type == "validation_error" or error_code == "invalid_request": + return RequestValidationError( + msg, + field=field, + error_code=error_code or "invalid_request", + debug_ref=debug_ref, + response=response, + ) + return None + + return None + + +def _classify_poll_failure( + envelope: dict, + *, + session_id: Optional[str] = None, +) -> Optional[FineTuningSessionsError]: + """Classify a failed poll-endpoint envelope into a typed exception. + + The poll endpoint returns ``{"status": "failed", "error": "...", "error_code": "...", ...}`` + when a GPU operation fails. This function translates known error codes into typed exceptions. + + Returns ``None`` if the failure doesn't match any known pattern. + """ + error_code = envelope.get("error_code") or envelope.get("code") + error_msg = envelope.get("error") or "Operation failed" + debug_ref = envelope.get("debug_ref") + + if error_code == "engine_oom": + return BatchTooLargeError( + error_msg + " (Try a smaller batch or shorter sequences.)", + response=None, + ) + + if error_code in ("worker_crashed", "engine_timeout"): + return TrainingEngineError( + error_msg, + session_id=session_id, + error_code=error_code, + debug_ref=debug_ref, + ) + + if error_code == "invalid_request": + return RequestValidationError( + error_msg, + error_code=error_code, + debug_ref=debug_ref, + ) + + if error_code == "model_not_found": + return TrainingEngineError( + error_msg, + session_id=session_id, + error_code=error_code, + debug_ref=debug_ref, + ) + + return None + + +# Backward-compat aliases. +EngineDeadError = TrainingEngineError +MalformedDatumError = RequestValidationError + +__all__ = [ + "FineTuningSessionsError", + "BatchTooLargeError", + "NoCapacityError", + "TrainingEngineError", + "EngineDeadError", + "ContentionError", + "RequestValidationError", + "MalformedDatumError", +] diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_logging_setup.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_logging_setup.py new file mode 100644 index 000000000000..bcab287ba2f6 --- /dev/null +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_logging_setup.py @@ -0,0 +1,138 @@ +# coding=utf-8 +# -------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------- +"""Prepend a UTC timestamp to SDK log records when no handler will render one. + +Scope: enriches the SDK's intentional, hand-written telemetry (HTTP +traces, session lifecycle, heartbeat, crash warnings) on these loggers: + +* ``azure.ai.finetuning_sessions._patch`` +* ``azure.ai.finetuning_sessions.aio._patch`` + +Autorest / code-generator helpers under ``_utils/`` are deliberately +excluded -- their logs are plumbing noise that operators do not correlate. + +Mechanism: a :class:`logging.Filter` attached to each emitting logger. +For each record, the filter walks the logger's parent chain (honoring +``propagate=False``) and asks "is any handler reachable?". If **no** +handler is reachable, the record will fall through to +``logging.lastResort`` -- whose hardcoded format string is +``"%(levelname)s:%(name)s:%(message)s"`` -- and thus show no timestamp. +In that case the filter prepends ``[] `` to ``record.msg`` +so the rendered line includes a timestamp. If a handler **is** reachable, +the filter is a no-op: the caller's formatter is responsible for the +timestamp (via ``%(asctime)s``, ``record.created``, or JSON time-field +handling), and the SDK does not duplicate it. + +The timestamp uses ``record.created`` (set automatically by Python at log +call time), not the time the filter runs -- preserving event time under +queued / async handlers. + +This module installs **no handler**, does not change propagation, and +never causes duplicate log lines in any caller-configured setup. + +Operators can opt out without code changes by setting the env var +``AZURE_AI_FINETUNING_SESSIONS_SDK_LOG_CONTEXT`` to any of ``0``, +``false``, ``no``, ``off``, ``disable``, ``disabled`` (case-insensitive). +Programmatic control is also available via +``install_default_logging(enabled=True)`` or ``enabled=False``. +""" +from __future__ import annotations + +import logging as _logging +import os as _os +from datetime import datetime as _datetime +from datetime import timezone as _timezone +from typing import Optional, Tuple + +_SDK_ROOT = "azure.ai.finetuning_sessions" + +# Environment variable that lets operators opt OUT of the timestamp +# filter without touching code. Default is enabled. Recognized "falsey" +# values (case-insensitive): "0", "false", "no", "off", "disable", +# "disabled". Any other value -- including unset -- leaves the filter +# enabled. +_ENV_VAR = "AZURE_AI_FINETUNING_SESSIONS_SDK_LOG_CONTEXT" +_FALSEY = frozenset({"0", "false", "no", "off", "disable", "disabled"}) + +# Child loggers that emit the SDK's intentional, user-facing telemetry. +# When a NEW hand-written SDK module starts emitting telemetry that +# should benefit from the no-handler timestamp fallback, add its dotted +# name here. +_SDK_EMITTING_LOGGERS: Tuple[str, ...] = ( + f"{_SDK_ROOT}._patch", + f"{_SDK_ROOT}.aio._patch", +) + + +def _enabled_from_env() -> bool: + raw = _os.environ.get(_ENV_VAR) + if raw is None: + return True + return raw.strip().lower() not in _FALSEY + + +def _has_any_handler(name: str) -> bool: + """True iff at least one handler is reachable in the logger's chain. + + Walks parents until a handler is found or propagation breaks. Mirrors + the lookup ``Logger.callHandlers`` does, so a ``False`` return means + the record will be dispatched to ``logging.lastResort``. + """ + c: Optional[_logging.Logger] = _logging.getLogger(name) + while c is not None: + if c.handlers: + return True + if not c.propagate: + return False + c = c.parent + return False + + +class _SdkTimestampFilter(_logging.Filter): + """Prepend an ISO-8601 UTC timestamp to ``record.msg`` only when no + handler is configured to render the record's time. Always returns + ``True`` -- enriches, never drops.""" + + def filter(self, record: _logging.LogRecord) -> bool: + if not _has_any_handler(record.name): + ts = _datetime.fromtimestamp( + record.created, _timezone.utc + ).isoformat(timespec="milliseconds") + record.msg = f"[{ts}] {record.msg}" + return True + + +def install_default_logging(enabled: Optional[bool] = None) -> None: + """Attach the timestamp filter to every SDK emitting logger. + + :keyword enabled: If ``None`` (default), enablement is read from the + ``AZURE_AI_FINETUNING_SESSIONS_SDK_LOG_CONTEXT`` env var; the + filter is on unless the var is set to a falsey value (``0``, + ``false``, ``no``, ``off``, ``disable``, ``disabled``). Pass + ``True`` / ``False`` to force-enable or force-disable + programmatically. + + Idempotent: a second call with ``enabled=True`` does not duplicate + the filter; a call with ``enabled=False`` removes any previously + installed instance. Installs no handler and does not change logger + propagation. + """ + if enabled is None: + enabled = _enabled_from_env() + + for name in _SDK_EMITTING_LOGGERS: + logger = _logging.getLogger(name) + existing = [f for f in logger.filters if isinstance(f, _SdkTimestampFilter)] + if not enabled: + for f in existing: + logger.removeFilter(f) + continue + if existing: + continue + logger.addFilter(_SdkTimestampFilter()) + + +__all__ = ["install_default_logging"] diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py index 4eb1a5a762b7..e3d067106e10 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/_patch.py @@ -28,46 +28,234 @@ """ from __future__ import annotations +import concurrent.futures as _futures import json as _json import logging as _logging import os as _os import threading as _threading import time as _time -from typing import TYPE_CHECKING, Any, List, Optional, Union +from typing import TYPE_CHECKING, Any, Callable, List, Optional, Union +from azure.core import PipelineClient +from azure.core.exceptions import HttpResponseError as _HttpResponseError +from azure.core.exceptions import ServiceRequestError as _ServiceRequestError +from azure.core.exceptions import ServiceResponseError as _ServiceResponseError +from azure.core.pipeline import policies from azure.core.rest import HttpRequest as _HttpRequest +from ._exceptions import ( + _classify_http_error, + _classify_poll_failure, +) + from .models import ( AdamParams, CreateSessionRequest, Datum, ForwardBackwardInput, + ForwardBackwardOperationResult, ForwardBackwardRequest, + FromCheckpoint, LoRAConfig, LossFn, LossFnConfig, ModelInput, ModelInputChunk, OperationResult, + OperationType, OptimStepRequest, SampleRequest, SamplingParams, SaveCheckpointRequest, SaveSamplerWeightsRequest, + TensorData, FoundryFeaturesOptInKeys, ) +from ._client import FineTuningSessionClient as FineTuningSessionClientGenerated from ._utils.model_base import SdkJSONEncoder as _SdkJSONEncoder, _deserialize as _deserialize_model # ── Loom wire-format → OperationResult discriminator map ───────────────────── # Maps the last path segment of a Loom action URL to the SDK's "type" value. +# NOTE: "forward" maps to "forward_backward" because there is no dedicated +# ForwardOperationResult class yet. This means ForwardBackwardOperationResult +# must tolerate missing fields (total_loss, metrics) — do NOT add required +# fields to that class without also providing a separate ForwardOperationResult. _LOOM_SUBPATH_TO_OP_TYPE: dict[str, str] = { "forward_backward": "forward_backward", + "forward": "forward_backward", "optim_step": "optim_step", "checkpoint": "save_checkpoint", "checkpoint_sample": "save_sampler_weights", "sample": "sample", } +# --------------------------------------------------------------------------- +# Chunked forward_backward helpers +# --------------------------------------------------------------------------- + +#: Maximum number of datums in a single forward_backward HTTP request. +_MAX_CHUNK_LEN = 1024 + +#: Approximate maximum payload size (bytes) for a single request. +_MAX_CHUNK_BYTES = 5_000_000 + + +def _estimate_bytes_count(datum: Datum) -> int: + """Estimate the serialised size of a single Datum.""" + size = 0 + # Model input chunks — each token ID ≈ 10 bytes when JSON-serialised. + for chunk in datum.model_input.chunks: + size += len(chunk.tokens) * 10 + # Loss function inputs — each TensorData field's data list × 10. + lfi = datum.loss_fn_inputs + for field_name in ("target_tokens", "weights", "advantages", "logprobs"): + td = getattr(lfi, field_name, None) + if td is not None and hasattr(td, "data") and td.data is not None: + size += len(td.data) * 10 + return size + + +def _chunk_data(data: List[Datum]) -> List[List[Datum]]: + """Split Datum list into chunks respecting size limits.""" + chunks: List[List[Datum]] = [] + current: List[Datum] = [] + current_bytes = 0 + for datum in data: + est = _estimate_bytes_count(datum) + if ( + len(current) > 0 + and current_bytes + est > _MAX_CHUNK_BYTES + ) or len(current) == _MAX_CHUNK_LEN: + chunks.append(current) + current = [] + current_bytes = 0 + current.append(datum) + current_bytes += est + if current: + chunks.append(current) + return chunks + + +# ── Metric reduction ────────────────────────────────────────────────────── + + +def _reduce_mean(xs: List[float], weights: Optional[List[int]] = None) -> float: + if weights is None or sum(weights) == 0: + return sum(xs) / len(xs) if xs else 0.0 + total = sum(x * w for x, w in zip(xs, weights)) + return total / sum(weights) + + +def _reduce_sum(xs: List[float]) -> float: + return sum(xs) + + +def _reduce_min(xs: List[float]) -> float: + return min(xs) + + +def _reduce_max(xs: List[float]) -> float: + return max(xs) + + +def _reduce_slack(xs: List[float], weights: Optional[List[int]] = None) -> float: + return max(xs) - _reduce_mean(xs, weights) + + +def _order_insensitive_hash(xs: list) -> int: + """Order-insensitive hash for metric deduplication.""" + if xs and isinstance(xs[0], set): + return hash(tuple(sorted([y for x in xs for y in x]))) + return hash(tuple(sorted(int(x) for x in xs))) + + +_REDUCE_MAP = { + "mean": _reduce_mean, + "sum": _reduce_sum, + "min": _reduce_min, + "max": _reduce_max, + "slack": _reduce_slack, + "hash_unordered": _order_insensitive_hash, + "unique": lambda xs: xs, +} + + +def _metrics_reduction( + results: List[ForwardBackwardOperationResult], + chunk_sizes: List[int], +) -> dict: + """Reduce metrics across chunked forward_backward results. + + Uses ``chunk_sizes`` (number of datums per chunk) as weights. + """ + if not results: + return {} + # `forward` route returns a base ``OperationResult`` with no `metrics` + # field; tolerate that by using getattr throughout. + # TODO(forward-route): add a proper ``ForwardOperationResult`` subclass + # to ``models/_models.py`` (per_datum_logprobs only, no total_loss / + # metrics) and drop the ``"forward": "forward_backward"`` mapping in + # ``_LOOM_SUBPATH_TO_OP_TYPE``. Then this defensive getattr can go away. + first_metrics = getattr(results[0], "metrics", None) or {} + keys = first_metrics.keys() + res: dict = {} + for key in keys: + parts = key.split(":") + if len(parts) != 2: + continue + name, reduction = parts + if reduction not in _REDUCE_MAP: + _logger.debug( + "Invalid reduction=%s for metric name=%s. Expecting one of %s", + reduction, name, list(_REDUCE_MAP.keys()), + ) + continue + if not all(key in (getattr(m, "metrics", None) or {}) for m in results): + continue + values = [(getattr(m, "metrics", None) or {})[key] for m in results] + reduce_fn = _REDUCE_MAP[reduction] + + if reduction in ("mean", "slack"): + res[key] = reduce_fn(values, chunk_sizes) + elif reduction == "unique": + res[key] = values[0] + res.update({f"{key}_{i + 1}": v for i, v in enumerate(values[1:])}) + else: + res[key] = reduce_fn(values) + return res + + +def _combine_fwd_bwd_results( + results: List[ForwardBackwardOperationResult], + chunk_sizes: List[int], +) -> ForwardBackwardOperationResult: + """Combine results from multiple forward_backward chunks.""" + if not results: + return ForwardBackwardOperationResult(total_loss=0.0) + + combined_metrics = _metrics_reduction(results, chunk_sizes) + combined_logprobs: List[TensorData] = [] + for r in results: + if r.per_datum_logprobs: + combined_logprobs.extend(r.per_datum_logprobs) + # Combine loss_fn_outputs (extra JSON field carrying per-datum logprobs + # from the Loom server). The cookbook reads this field first, falling + # back to per_datum_logprobs only when it is absent. + combined_lfo: list = [] + for r in results: + lfo = r.get("loss_fn_outputs") if hasattr(r, "get") else None + if lfo: + combined_lfo.extend(lfo) + total_loss = sum(r.total_loss for r in results) + combined: dict = { + "total_loss": total_loss, + "per_datum_logprobs": combined_logprobs or None, + "metrics": combined_metrics or None, + } + if combined_lfo: + combined["loss_fn_outputs"] = combined_lfo + return ForwardBackwardOperationResult(combined) + def _normalize_loom_result(data: dict, op_type: str, request_id: str) -> dict: """Normalize the Loom poll-endpoint wire format into an OperationResult dict. @@ -101,18 +289,160 @@ def _normalize_loom_result(data: dict, op_type: str, request_id: str) -> dict: out.setdefault("sampling_session_id", out.get("sampling_session_id", "")) elif op_type == "save_checkpoint": + out["type"] = "save_checkpoint" # force, in case server returns a different value out.setdefault("checkpoint_id", out.get("checkpoint_id", "")) out.setdefault("path", out.get("path", "")) return out -if TYPE_CHECKING: - from ._client import FineTuningSessionClient - _PREVIEW = FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW _API_VERSION = "v1" _logger = _logging.getLogger(__name__) +#: Per-call HTTP timeout for the (now short-poll) retrieve-status endpoint. +#: The server returns immediately, so this only needs to cover network RTT and +#: a one-shot DB read. +_RETRIEVE_TIMEOUT = 15.0 + +#: Adaptive poll backoff bounds (seconds) for the retrieve-status endpoint. +#: We start at MIN (catches fast operations cheaply) and double up to MAX +#: (bounds RPS for long-running operations). Backoff resets on every new +#: poll loop (i.e. per-request), so each future starts polling at MIN. +_RETRIEVE_POLL_MIN = 1.0 +_RETRIEVE_POLL_MAX = 30.0 + + +def _operation_timeout_from_env() -> Optional[float]: + env_name = "AZURE_AI_FINETUNING_SESSIONS_OPERATION_TIMEOUT_SEC" + raw = _os.environ.get(env_name) + if raw is None: + _logger.debug("%s not set; using default 3600s operation timeout", env_name) + return 3600.0 # not set -> default + try: + value = float(raw) + except ValueError: + _logger.warning("Invalid %s=%r; using default 3600s", env_name, raw) + return 3600.0 # invalid -> default + if value > 0: + _logger.debug("%s=%s; operation timeout set to %.0fs", env_name, raw, value) + return value + _logger.warning("%s=%s; operation timeout DISABLED (retries can run unbounded)", env_name, raw) + return None # 0 (or negative) -> disabled + + +# Per-operation polling timeout for training/sample/checkpoint requests. This +# bounds retry loops on repeated 5xx/network failures while still allowing long +# operations; set env var to 0 (or negative) to disable. +_DEFAULT_OPERATION_TIMEOUT_SEC = _operation_timeout_from_env() + + +class _ErrorBudget: + """Bounds a sustained error streak; healthy progress is unbounded. + + ``budget_sec=None`` disables the budget. ``on_exhausted(reason, budget_sec)`` + builds the exception to raise, where ``reason`` is the short error label + (e.g. ``"HTTP 503"``) and ``budget_sec`` is the configured budget in seconds. + """ + + def __init__( + self, + budget_sec: Optional[float], + *, + on_exhausted: Callable[[str, float], BaseException], + ) -> None: + self._budget = budget_sec + self._on_exhausted = on_exhausted + self._deadline: Optional[float] = None + + @classmethod + def for_polling( + cls, + budget_sec: Optional[float], + *, + op_type: str, + request_id: str, + ) -> "_ErrorBudget": + """Build a poll-loop budget that raises ``TimeoutError`` when exhausted. + + Shared by the sync and async poll loops so the exhaustion message lives + in one place. + """ + return cls( + budget_sec, + on_exhausted=lambda reason, budget: TimeoutError( + f"Timed out after {budget:.0f}s of sustained " + f"errors ({reason}) waiting for {op_type or 'operation'} request {request_id}" + ), + ) + + def clear(self) -> None: + """Disarm; call on every healthy poll.""" + self._deadline = None + + def consume(self, reason: str) -> None: + """Arm on the first error, raise once the streak outlasts the budget.""" + if self._budget is None: + return + now = _time.monotonic() + if self._deadline is None: + self._deadline = now + self._budget + elif now > self._deadline: + raise self._on_exhausted(reason, self._budget) + + +# Poll-progress state shared by sync and async clients. +_poll_log_last: dict[tuple[str, str], float] = {} +_request_active_since: dict[tuple[str, str], float] = {} +_POLL_LOG_DEDUP_SEC = 30.0 + + +def _maybe_log_poll_progress( + envelope: dict, + session_id: str, + request_id: str, + op_type: str, + elapsed: float, +) -> None: + """Emit a throttled poll-progress log for a pending request.""" + now = _time.monotonic() + is_queued = envelope.get("phase") == "resuming_session" + req_key = (session_id, request_id) + + if is_queued: + # Restart active timing if a request moves back to the queue. + _request_active_since.pop(req_key, None) + display_elapsed = elapsed + else: + active_since = _request_active_since.get(req_key) + if active_since is None: + _request_active_since[req_key] = now + display_elapsed = 0.0 + else: + display_elapsed = now - active_since + + if display_elapsed < _POLL_LOG_DEDUP_SEC: + return + dedup_key = (session_id, op_type) + if now - _poll_log_last.get(dedup_key, 0.0) < _POLL_LOG_DEDUP_SEC: + return + _poll_log_last[dedup_key] = now + if is_queued: + _logger.info( + "[poller] %s/%s (op=%s) queued waiting for capacity \u2014 %.0fs elapsed", + session_id, request_id, op_type, display_elapsed, + ) + else: + _logger.info( + "[poller] %s/%s (op=%s) in progress \u2014 %.0fs elapsed", + session_id, request_id, op_type, display_elapsed, + ) + + +def _clear_poll_log_state(session_id: str, request_id: str, op_type: str) -> None: + """Drop poll-progress state for a finished request.""" + _request_active_since.pop((session_id, request_id), None) + _poll_log_last.pop((session_id, op_type), None) + # --------------------------------------------------------------------------- # Verbose HTTP logging toggle # --------------------------------------------------------------------------- @@ -144,8 +474,8 @@ def _base_headers(extra: Optional[dict] = None) -> dict: Reads ``X_COGNITIVE_SUBSCRIPTION_ID`` (or ``COGNITIVE_SUBSCRIPTION_ID`` / ``AZURE_SUBSCRIPTION_ID`` as fallbacks) from the environment and injects it as ``apim-subscription-id``. The remote Loom endpoint (LOOM_SETUP_MODE=prod) - requires this header on every request — same as ``loom_client`` and - ``clean_remote.sh`` which set ``X_COGNITIVE_SUBSCRIPTION_ID=local-sub``. + requires this header on every request — same as + ``clean_remote.sh`` which sets ``X_COGNITIVE_SUBSCRIPTION_ID=local-sub``. """ headers: dict = { "Accept": "application/json", @@ -163,6 +493,58 @@ def _base_headers(extra: Optional[dict] = None) -> dict: return headers +class FineTuningSessionClient(FineTuningSessionClientGenerated): # pylint: disable=client-accepts-api-version-keyword + """FineTuningSessionClient. + + :ivar sessions: SessionsOperations operations + :vartype sessions: azure.ai.finetuning_sessions.operations.SessionsOperations + :ivar training: TrainingOperations operations + :vartype training: azure.ai.finetuning_sessions.operations.TrainingOperations + :ivar checkpoints: CheckpointsOperations operations + :vartype checkpoints: azure.ai.finetuning_sessions.operations.CheckpointsOperations + :ivar sampling: SamplingOperations operations + :vartype sampling: azure.ai.finetuning_sessions.operations.SamplingOperations + :ivar operations: Operations operations + :vartype operations: azure.ai.finetuning_sessions.operations.Operations + :param endpoint: Foundry Project endpoint in the form + "https://{ai-services-account-name}.services.ai.azure.com/api/projects/{project-name}". If you + only have one Project in your Foundry Hub, or to target the default Project in your Hub, use + the form "https://{ai-services-account-name}.services.ai.azure.com/api/projects/_project". + Required. + :type endpoint: str + :param credential: Credential used to authenticate requests to the service. Required. + :type credential: ~azure.core.credentials.TokenCredential + :keyword int polling_interval: Default waiting time between two polls for LRO operations if no + Retry-After header is present. + """ + def __init__(self, endpoint: str, credential: "TokenCredential", *, allow_insecure_http: bool = False, + **kwargs: Any) -> None: + provided_policies = kwargs.get("policies") + original_kwargs = dict(kwargs) + super().__init__(endpoint=endpoint, credential=credential, allow_insecure_http=allow_insecure_http, **original_kwargs) + + _policies = provided_policies + if _policies is None: + _policies = [ + policies.RequestIdPolicy(**original_kwargs), + self._config.headers_policy, + self._config.user_agent_policy, + self._config.proxy_policy, + policies.ContentDecodePolicy(**original_kwargs), + self._config.redirect_policy, + self._config.retry_policy, + self._config.authentication_policy, + self._config.custom_hook_policy, + self._config.logging_policy, + policies.DistributedTracingPolicy(**original_kwargs), + policies.SensitiveHeaderCleanupPolicy(**original_kwargs) if self._config.redirect_policy else None, + self._config.http_logging_policy, + ] + + self._session_client = PipelineClient(base_url=endpoint, policies=_policies, **original_kwargs) + self.sessions._client = self._session_client + + class FineTuningSession: """Convenience wrapper around a single fine-tuning session. @@ -191,15 +573,7 @@ def _start_heartbeat(self, interval_sec: float = 30.0) -> None: def _heartbeat_loop() -> None: while not self._heartbeat_stop.wait(interval_sec): try: - hb_req = _HttpRequest( - "POST", - "{endpoint}" + f"/fine_tuning/sessions/{self._heartbeat_session_id}/heartbeat", - headers=_base_headers(), - params={"api-version": _API_VERSION}, - ) - resp = self._client.send_request(hb_req) - if resp.status_code != 200: - _logger.warning("[heartbeat] status=%d for %s", resp.status_code, self._heartbeat_session_id) + self.heartbeat() except Exception as exc: _logger.warning("[heartbeat] failed for %s: %s", self._heartbeat_session_id, exc) @@ -226,7 +600,7 @@ def create( base_model: str, lora_config: Optional[LoRAConfig] = None, type: str = "training", - poll_interval_sec: float = 5.0, + from_checkpoint: Optional[FromCheckpoint] = None, timeout_sec: float = 600.0, ) -> "FineTuningSession": """Create a fine-tuning session and wait until the model is loaded. @@ -239,16 +613,31 @@ def create( :param base_model: Name of the base model to load (e.g. ``"Llama-3.1-8B"``). :param lora_config: Optional LoRA adapter config. Server default is used if omitted. :param type: Session type string. Defaults to ``"training"``. - :param poll_interval_sec: Seconds between poll attempts. Defaults to ``5.0``. + :param from_checkpoint: Optional :class:`FromCheckpoint` specifying the + source session and checkpoint to bootstrap from (continual fine-tuning + / resume from checkpoint). :param timeout_sec: Maximum seconds to wait for the model to load. Defaults to ``600.0``. + :note: Poll cadence is controlled by an internal adaptive backoff + (``_RETRIEVE_POLL_MIN`` doubling up to ``_RETRIEVE_POLL_MAX``); + it is not currently caller-configurable. :raises RuntimeError: If the server reports status ``"failed"`` or the timeout expires. :return: A :class:`FineTuningSession` instance ready for training operations. """ - body_json = _json.dumps( - CreateSessionRequest(type=type, base_model=base_model, lora_config=lora_config), + create_request = CreateSessionRequest( + type=type, + base_model=base_model, + lora_config=lora_config, + ) + body = _json.loads(_json.dumps( + create_request, cls=_SdkJSONEncoder, exclude_readonly=True, - ) + )) + if from_checkpoint is not None: + body["from_checkpoint"] = _json.loads( + _json.dumps(from_checkpoint, cls=_SdkJSONEncoder, exclude_readonly=True) + ) + body_json = _json.dumps(body) post_req = _HttpRequest( "POST", "{endpoint}/fine_tuning/sessions", @@ -259,7 +648,17 @@ def create( _log_http("request", "POST", "/fine_tuning/sessions", body=_json.loads(body_json)) post_resp = client.send_request(post_req) _log_http("response", "POST", "/fine_tuning/sessions", status=post_resp.status_code, body=post_resp.json()) - post_resp.raise_for_status() + if post_resp.status_code >= 400: + try: + resp_body = post_resp.json() + except Exception: + resp_body = None + typed = _classify_http_error( + post_resp.status_code, resp_body, response=post_resp + ) + if typed is not None: + raise typed + post_resp.raise_for_status() data = post_resp.json() raw_session_id: str = data["session_id"] request_id: str = data["request_id"] @@ -275,43 +674,174 @@ def create( _logger.info("[create] session_id transformed: raw=%s -> resource_id=%s (used in all subsequent URL paths)", raw_session_id, session_id) # Wait for the model-load request to complete. - # The retrieve-future endpoint (GET /fine_tuning/sessions/{id}/request/{rid}) - # long-polls server-side for up to 5 minutes. A 200 response means the - # request completed successfully — the server only returns 200 when the - # engine has finished loading the model. If the model is still loading - # after the server's internal timeout, it returns 408 which raises an - # HttpResponseError here. We retry on 408 until our own deadline. + # The retrieve-status endpoint (GET /fine_tuning/sessions/{id}/request/{rid}) + # is now non-blocking: each call returns a {status, result, error} envelope + # immediately. We short-poll until status=="completed" (or "failed") + # using adaptive backoff (MIN doubling up to MAX) to keep poll RPS bounded + # for slow model loads while still reacting quickly when ready. deadline = _time.monotonic() + timeout_sec + _create_conn_backoff = 1.0 + _create_poll_backoff = _RETRIEVE_POLL_MIN + _create_poll_start = _time.monotonic() while True: - poll_req = _HttpRequest( - "GET", - "{endpoint}" + f"/fine_tuning/sessions/{session_id}/request/{request_id}", - headers=_base_headers(), - params={"api-version": _API_VERSION}, - ) - poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" - _log_http("request", "GET", poll_path) - poll_resp = client.send_request(poll_req) - _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=poll_resp.json() if poll_resp.status_code == 200 else None) - - if poll_resp.status_code == 200: - _logger.info("[create] model load completed: %s", poll_resp.json()) - break + try: + poll_req = _HttpRequest( + "GET", + "{endpoint}" + f"/fine_tuning/sessions/{session_id}/request/{request_id}", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" + _log_http("request", "GET", poll_path) + poll_resp = client.send_request(poll_req) + # Parse JSON once — `azure.core.rest` responses do not guarantee + # the body can be read more than once. + envelope = poll_resp.json() if poll_resp.status_code == 200 else None + _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=envelope) + + if poll_resp.status_code == 200: + env_status = envelope.get("status") + if env_status == "completed": + _logger.info("[create] model load completed: %s", envelope) + _clear_poll_log_state(session_id, request_id, "create_session") + break + if env_status == "failed": + _clear_poll_log_state(session_id, request_id, "create_session") + typed = _classify_poll_failure(envelope, session_id=session_id) + if typed is not None: + raise typed + raise RuntimeError( + f"Model load failed for session_id={raw_session_id} " + f"[{envelope.get('error_code') or envelope.get('code') or 'unknown'}]: " + f"{envelope.get('error') or 'unknown error'} " + f"(debug_ref={envelope.get('debug_ref') or 'n/a'})" + ) + # pending -> sleep with adaptive backoff and retry (subject to deadline). + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for session_id={raw_session_id} to become ready" + ) + elapsed = _time.monotonic() - _create_poll_start + _maybe_log_poll_progress(envelope, session_id, request_id, "create_session", elapsed) + _create_conn_backoff = 1.0 # reset on successful HTTP exchange + _time.sleep(_create_poll_backoff) + _create_poll_backoff = min(_create_poll_backoff * 2, _RETRIEVE_POLL_MAX) + continue + + # Retry on 5xx and on transient client-side conditions: + # 408 Request Timeout -- intermittent network/proxy timeout + # 429 Too Many Requests -- server-side throttling + # Both are safe to retry with the same adaptive backoff used + # for pending polls. + if ( + 500 <= poll_resp.status_code < 600 + or poll_resp.status_code in (408, 429) + ): + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for session_id={raw_session_id} to become ready" + ) + elapsed = _time.monotonic() - _create_poll_start + _logger.debug( + "[poller] retry on %s/%s after HTTP %d (%.0fs elapsed)", + session_id, request_id, poll_resp.status_code, elapsed, + ) + _create_conn_backoff = 1.0 + # Honor Retry-After header if present. + retry_after = poll_resp.headers.get("Retry-After") + if retry_after is not None: + try: + poll_wait = float(retry_after) + except (ValueError, TypeError): + poll_wait = _RETRIEVE_POLL_MIN + else: + poll_wait = _RETRIEVE_POLL_MIN + _time.sleep(poll_wait) + continue + + # Any other error — fail immediately + try: + poll_body = poll_resp.json() + except Exception: + poll_body = None + typed = _classify_http_error( + poll_resp.status_code, poll_body, response=poll_resp, session_id=session_id + ) + if typed is not None: + raise typed + poll_resp.raise_for_status() - if poll_resp.status_code == 408: - # Server timed out waiting (model still loading) — retry if within deadline + except (_ServiceRequestError, _ServiceResponseError) as exc: + # Transient network error — exponential backoff then retry. if _time.monotonic() > deadline: raise RuntimeError( f"Timed out after {timeout_sec}s waiting for session_id={raw_session_id} to become ready" - ) - _logger.info("[create] server returned 408 (still loading), retrying...") + ) from exc + elapsed = _time.monotonic() - _create_poll_start + _logger.warning( + "[poller] retry on %s/%s after %s(%s) (%.0fs elapsed), backoff %.1fs", + session_id, request_id, type(exc).__name__, exc, elapsed, _create_conn_backoff, + ) + _time.sleep(_create_conn_backoff) + _create_conn_backoff = min(_create_conn_backoff * 2, 30.0) continue - # Any other error — fail immediately - poll_resp.raise_for_status() - return cls(client, session_id=session_id) + @classmethod + def create_from_checkpoint( + cls, + client: "FineTuningSessionClient", + *, + checkpoint_path: str, + base_model: str, + lora_config: Optional[LoRAConfig] = None, + type: str = "training", + timeout_sec: float = 600.0, + ) -> "FineTuningSession": + """Create a session resumed from a previously saved training checkpoint. + + This is a convenience wrapper around :meth:`create` that parses a + checkpoint path string and passes it as ``from_checkpoint``. + + The new session's LoRA weights, optimizer state, and scheduler step are + all bootstrapped from the checkpoint — equivalent to calling ``create`` + with ``from_checkpoint=FromCheckpoint(source_session_id=..., checkpoint_id=...)``. + + :param client: The :class:`~azure.ai.finetuning_sessions.FineTuningSessionClient`. + :param checkpoint_path: Reference to a saved training checkpoint. + Accepted formats: + - ``"/"`` + - ``"model_/"`` + :param base_model: Base model name. Must match the checkpoint's source. + :param lora_config: Optional LoRA config override. + :param type: Session type. Defaults to ``"training"``. + :param timeout_sec: Maximum seconds to wait for model load. + :raises ValueError: If ``checkpoint_path`` cannot be parsed. + :return: A ready-to-use :class:`FineTuningSession`. + """ + parts = checkpoint_path.split("/") + if len(parts) != 2 or not parts[0] or not parts[1]: + raise ValueError( + "checkpoint_path must be '/' with exactly one '/' separator, " + f"got: {checkpoint_path!r}" + ) + source_session_id, checkpoint_id = parts + # Normalize: the API expects the model_ prefix on source_session_id + if not source_session_id.startswith("model_"): + source_session_id = f"model_{source_session_id}" + return cls.create( + client, + base_model=base_model, + lora_config=lora_config, + type=type, + from_checkpoint=FromCheckpoint( + source_session_id=source_session_id, + checkpoint_id=checkpoint_id, + ), + timeout_sec=timeout_sec, + ) + # ── Low-level helper ────────────────────────────────────────────────────── def _post_and_poll(self, subpath: str, body_model: Any, extra_params: Optional[dict] = None, extra_result_fields: Optional[dict] = None) -> OperationResult: @@ -321,6 +851,32 @@ def _post_and_poll(self, subpath: str, body_model: Any, extra_params: Optional[d Loom returns 200 (not 202) with ``{request_id, session_id, status}`` from all mutating operations. The poll endpoint blocks server-side (up to 5 minutes) and returns the typed result directly. + + Retries 408 / 5xx / transient network errors. The timeout is an ERROR + budget, not a wall-clock budget — see below. Set + AZURE_AI_FINETUNING_SESSIONS_OPERATION_TIMEOUT_SEC=0 to disable it. + + Timeout policy — IMPORTANT: + The budget (``_DEFAULT_OPERATION_TIMEOUT_SEC``, default 3600s) bounds + how long we tolerate a **sustained error streak**, NOT how long the + operation may take. It is per operation, not per job. + + * Healthy progress is NOT bounded. While the server keeps returning a + pending 200 — whether the request is queued waiting for GPU capacity + or actively in progress — we keep polling indefinitely. A healthy + poll CLEARS the error budget. + * Errors ARE bounded. The first 5xx / 408 / 429 / transient network + error after a healthy poll arms the error deadline + (``_DEFAULT_OPERATION_TIMEOUT_SEC`` from that moment). Further errors + do NOT extend it; the next healthy 200 disarms it. If errors persist + past the budget we raise ``TimeoutError`` so a real backend outage + fails fast instead of hanging. + + Net effect: a request can sit in the capacity queue for hours without + being killed, but a stuck/erroring backend is surfaced within the + budget. Note this means a server that returns healthy-pending forever + (never completes, never errors) will poll forever — guard against that + with server-side stall detection, not this client timeout. """ body_json = _json.dumps(body_model, cls=_SdkJSONEncoder, exclude_readonly=True) post_params: dict = {"api-version": _API_VERSION} @@ -336,7 +892,17 @@ def _post_and_poll(self, subpath: str, body_model: Any, extra_params: Optional[d _log_http("request", "POST", subpath, body=_json.loads(body_json)) post_resp = self._client.send_request(post_req) _log_http("response", "POST", subpath, status=post_resp.status_code, body=post_resp.json()) - post_resp.raise_for_status() + if post_resp.status_code >= 400: + try: + resp_body = post_resp.json() + except Exception: + resp_body = None + typed = _classify_http_error( + post_resp.status_code, resp_body, response=post_resp, session_id=self.session_id + ) + if typed is not None: + raise typed + post_resp.raise_for_status() data = post_resp.json() request_id = data["request_id"] session_id = data.get("session_id", self.session_id) @@ -356,23 +922,113 @@ def _post_and_poll(self, subpath: str, body_model: Any, extra_params: Optional[d ) poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" - # Retry up to 3 times on 408 (server long-polls ~5 min per attempt, - # so 3 retries = ~15 min max wait before giving up). - max_retries = 3 - for attempt in range(max_retries + 1): - _log_http("request", "GET", poll_path) - poll_resp = self._client.send_request(poll_req) - if poll_resp.status_code == 200: - _log_http("response", "GET", poll_path, status=200, body=poll_resp.json()) - break - if poll_resp.status_code == 408 and attempt < max_retries: - _logger.info("[_post_and_poll] server returned 408 (GPU still processing), retry %d/%d", attempt + 1, max_retries) + # Short-poll the {status, result, error} envelope. The server returns + # immediately on every call; we sleep with adaptive backoff (MIN doubling + # up to MAX) between pending polls. + # + # Timeout policy: the budget is an ERROR budget, not a wall-clock budget. + # * A healthy pending 200 (queued waiting for capacity, or in progress) + # does NOT consume the budget — it CLEARS it. So a request can sit in + # the capacity queue indefinitely as long as the server keeps + # reporting healthy progress. + # * The first 5xx / 408 / 429 / transient network error after a healthy + # poll ARMS the error deadline (`_DEFAULT_OPERATION_TIMEOUT_SEC` from + # now). Subsequent errors do NOT extend it; the next healthy 200 + # disarms it. If the error streak outlasts the budget we raise + # TimeoutError (fail fast on a real outage). + # Set the env var <= 0 to disable the error budget (retry forever). + error_budget = _ErrorBudget.for_polling( + _DEFAULT_OPERATION_TIMEOUT_SEC, op_type=op_type, request_id=request_id + ) + + connection_error_backoff = 1.0 + poll_backoff = _RETRIEVE_POLL_MIN + result_data: Any = None + poll_start = _time.monotonic() + while True: + try: + _log_http("request", "GET", poll_path) + poll_resp = self._client.send_request(poll_req) + + if poll_resp.status_code == 200: + envelope = poll_resp.json() + _log_http("response", "GET", poll_path, status=200, body=envelope) + env_status = envelope.get("status") + if env_status == "completed": + result_data = envelope.get("result") or {} + _clear_poll_log_state(session_id, request_id, op_type) + break + if env_status == "failed": + _clear_poll_log_state(session_id, request_id, op_type) + typed = _classify_poll_failure(envelope, session_id=session_id) + if typed is not None: + raise typed + raise RuntimeError( + f"Request failed " + f"[{envelope.get('error_code') or envelope.get('code') or 'unknown'}]: " + f"{envelope.get('error') or 'no error message'} " + f"(debug_ref={envelope.get('debug_ref') or 'n/a'})" + ) + # pending -> healthy progress: clear the error budget (queued + # / in-progress time is unbounded), then sleep with backoff. + elapsed = _time.monotonic() - poll_start + _maybe_log_poll_progress(envelope, session_id, request_id, op_type, elapsed) + error_budget.clear() + connection_error_backoff = 1.0 + _time.sleep(poll_backoff) + poll_backoff = min(poll_backoff * 2, _RETRIEVE_POLL_MAX) + continue + + # Retry on 5xx and on transient 408/429 (timeout / throttling). + if ( + 500 <= poll_resp.status_code < 600 + or poll_resp.status_code in (408, 429) + ): + elapsed = _time.monotonic() - poll_start + _logger.debug( + "[poller] retry on %s/%s after HTTP %d (%.0fs elapsed)", + session_id, request_id, poll_resp.status_code, elapsed, + ) + error_budget.consume(f"HTTP {poll_resp.status_code}") + connection_error_backoff = 1.0 + # Honor Retry-After header if present. + retry_after = poll_resp.headers.get("Retry-After") + if retry_after is not None: + try: + poll_wait = float(retry_after) + except (ValueError, TypeError): + poll_wait = _RETRIEVE_POLL_MIN + else: + poll_wait = _RETRIEVE_POLL_MIN + _time.sleep(poll_wait) + continue + + # Non-retryable HTTP error (4xx other than 408/429). + _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=None) + try: + poll_body = poll_resp.json() + except Exception: + poll_body = None + typed = _classify_http_error( + poll_resp.status_code, poll_body, response=poll_resp, session_id=session_id + ) + if typed is not None: + raise typed + poll_resp.raise_for_status() + + except (_ServiceRequestError, _ServiceResponseError) as exc: + # Transient network error — exponential backoff then retry. + elapsed = _time.monotonic() - poll_start + _logger.warning( + "[poller] retry on %s/%s after %s(%s) (%.0fs elapsed), backoff %.1fs", + session_id, request_id, type(exc).__name__, exc, elapsed, connection_error_backoff, + ) + error_budget.consume(type(exc).__name__) + _time.sleep(connection_error_backoff) + connection_error_backoff = min(connection_error_backoff * 2, 30.0) continue - _log_http("response", "GET", poll_path, status=poll_resp.status_code, body=None) - poll_resp.raise_for_status() - raw = poll_resp.json() - normalized = _normalize_loom_result(raw, op_type, request_id) + normalized = _normalize_loom_result(result_data, op_type, request_id) # Merge caller-supplied fallback fields BEFORE deserialization so that # generated model classes receive them (post-deserialization attr # assignment doesn't work on SDK objects with __slots__). @@ -394,7 +1050,9 @@ def forward_backward( ) -> OperationResult: """Submit a mini-batch for a forward + backward pass. - Blocks until the GPU finishes the backward pass. + If the batch exceeds ``_MAX_CHUNK_LEN`` datums or ``_MAX_CHUNK_BYTES`` + estimated payload size, the batch is automatically split into chunks. + Chunks are submitted in parallel and results are combined. Spec: ``fb_result = session.forward_backward(batch, loss_fn="cross_entropy")`` @@ -403,17 +1061,54 @@ def forward_backward( :param loss_fn_config: Optional per-loss hyper-parameters. :return: :class:`~azure.ai.finetuning_sessions.models.OperationResult`. """ - return self._post_and_poll( - f"/fine_tuning/sessions/{self.session_id}/forward_backward", - ForwardBackwardRequest( - forward_backward_input=ForwardBackwardInput( - data=batch, - loss_fn=loss_fn, - loss_fn_config=loss_fn_config, - ) - ), + chunks = _chunk_data(batch) + if len(chunks) <= 1: + # Single chunk — no combining needed. + return self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/forward_backward", + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=batch, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + + _logger.info( + "[forward_backward] batch of %d datums split into %d chunks: %s", + len(batch), len(chunks), [len(c) for c in chunks], ) + def _submit_chunk(idx_chunk: tuple) -> ForwardBackwardOperationResult: + i, chunk = idx_chunk + _logger.info("[forward_backward] sending chunk %d/%d (%d datums)", i + 1, len(chunks), len(chunk)) + result = self._post_and_poll( + f"/fine_tuning/sessions/{self.session_id}/forward_backward", + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + if isinstance(result, ForwardBackwardOperationResult): + return result + return ForwardBackwardOperationResult( + total_loss=getattr(result, "total_loss", 0.0), + per_datum_logprobs=getattr(result, "per_datum_logprobs", None), + metrics=getattr(result, "metrics", None), + ) + + # Fire all chunks in parallel. + # Wall-clock time ≈ max(chunk times) instead of sum. + with _futures.ThreadPoolExecutor(max_workers=len(chunks)) as pool: + chunk_results = list(pool.map(_submit_chunk, enumerate(chunks))) + + chunk_sizes = [len(c) for c in chunks] + return _combine_fwd_bwd_results(chunk_results, chunk_sizes) + def optim_step( self, adam_params: AdamParams, @@ -430,6 +1125,68 @@ def optim_step( OptimStepRequest(adam_params=adam_params), ) + def forward( + self, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, + **kwargs: Any, + ) -> OperationResult: + """Submit a mini-batch for a forward-only pass (no gradient accumulation). + + Returns the same result shape as ``forward_backward`` but does not + accumulate gradients on the worker, making it safe for evaluation. + + If the batch exceeds ``_MAX_CHUNK_LEN`` datums or ``_MAX_CHUNK_BYTES`` + estimated payload size, the batch is automatically split into chunks. + Chunks are submitted in parallel and results are combined. + + :param batch: List of :class:`~azure.ai.finetuning_sessions.models.Datum`. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: :class:`~azure.ai.finetuning_sessions.models.OperationResult`. + """ + # Server expects a ForwardRequest with `forward_input` wrapping the + # shared ForwardBackwardInput payload. + subpath = f"/fine_tuning/sessions/{self.session_id}/forward" + + def _build_body(chunk: List[Datum]) -> dict: + return { + "forward_input": ForwardBackwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + } + + chunks = _chunk_data(batch) + if len(chunks) <= 1: + return self._post_and_poll(subpath, _build_body(batch)) + + _logger.info( + "[forward] batch of %d datums split into %d chunks: %s", + len(batch), len(chunks), [len(c) for c in chunks], + ) + + def _submit_chunk(idx_chunk: tuple) -> ForwardBackwardOperationResult: + i, chunk = idx_chunk + _logger.info("[forward] sending chunk %d/%d (%d datums)", i + 1, len(chunks), len(chunk)) + result = self._post_and_poll(subpath, _build_body(chunk)) + if isinstance(result, ForwardBackwardOperationResult): + return result + return ForwardBackwardOperationResult( + total_loss=getattr(result, "total_loss", 0.0), + per_datum_logprobs=getattr(result, "per_datum_logprobs", None), + metrics=getattr(result, "metrics", None), + ) + + with _futures.ThreadPoolExecutor(max_workers=len(chunks)) as pool: + chunk_results = list(pool.map(_submit_chunk, enumerate(chunks))) + + chunk_sizes = [len(c) for c in chunks] + return _combine_fwd_bwd_results(chunk_results, chunk_sizes) + # ── Checkpoints ─────────────────────────────────────────────────────────── def save_weights( @@ -529,7 +1286,7 @@ def sample( def heartbeat(self, **kwargs: Any) -> Any: """Refresh an active session to prevent idle expiry.""" return self._client.sessions.heartbeat( - session_id=self.session_id, + session_id=self._heartbeat_session_id, foundry_features=_PREVIEW, api_version=_API_VERSION, **kwargs, @@ -553,7 +1310,7 @@ def close(self, **kwargs: Any) -> None: resp.raise_for_status() -__all__: list[str] = ["FineTuningSession"] +__all__: list[str] = ["FineTuningSession", "FineTuningSessionClient"] def patch_sdk(): diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py index 25cf30658869..bdbda2b25b1b 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_configuration.py @@ -8,7 +8,9 @@ from typing import Any, TYPE_CHECKING +from azure.core.credentials import AzureKeyCredential from azure.core.pipeline import policies +from azure.core.pipeline.policies import AzureKeyCredentialPolicy from .._version import VERSION @@ -56,6 +58,12 @@ def _configure(self, **kwargs: Any) -> None: self.retry_policy = kwargs.get("retry_policy") or policies.AsyncRetryPolicy(**kwargs) self.authentication_policy = kwargs.get("authentication_policy") if self.credential and not self.authentication_policy: - self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy( - self.credential, *self.credential_scopes, **kwargs - ) + if isinstance(self.credential, AzureKeyCredential): + # API key auth: sends "api-key: " header on every request. + self.authentication_policy = AzureKeyCredentialPolicy( + self.credential, name="api-key" + ) + else: + self.authentication_policy = policies.AsyncBearerTokenCredentialPolicy( + self.credential, *self.credential_scopes, **kwargs + ) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py index 87676c65a8f0..960b3ce3e559 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/aio/_patch.py @@ -3,19 +3,1415 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------- -"""Customize generated code here. +"""Async convenience methods patched onto FineTuningSessionClient. -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize +All async training operations (create_session, forward_backward, optim_step, +save_weights, save_weights_for_sampler, sample, close_session) are added +directly to the generated ``FineTuningSessionClient`` class via ``patch_sdk()``. + +Concurrency: + - Heartbeat uses an ``asyncio.Task`` per session. + - Chunked ``forward_backward`` uses ``asyncio.gather``. + - A configurable ``asyncio.Semaphore`` gates concurrent POSTs to prevent + connection storms (default: 64). + +Usage:: + + from azure.ai.finetuning_sessions.aio import FineTuningSessionClient + + async with FineTuningSessionClient(endpoint, credential) as client: + session_id = await client.create_session(base_model="Llama-3.1-8B") + fb = await client.forward_backward(session_id, batch, loss_fn="cross_entropy") + opt = await client.optim_step(session_id, AdamParams(learning_rate=1e-4)) + await client.close_session(session_id) """ +from __future__ import annotations +import asyncio +import json as _json +import logging as _logging +import random as _random +import time as _time +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level +from azure.core.exceptions import ServiceRequestError as _ServiceRequestError +from azure.core.exceptions import ServiceResponseError as _ServiceResponseError +from azure.core.rest import HttpRequest as _HttpRequest +from .._exceptions import ( + _classify_http_error, + _classify_poll_failure, +) -def patch_sdk(): - """Do not remove from this file. +from ..models import ( + AdamParams, + CreateSessionRequest, + Datum, + ForwardBackwardInput, + ForwardBackwardOperationResult, + ForwardBackwardRequest, + ForwardInput, + ForwardRequest, + FromCheckpoint, + LoRAConfig, + LossFn, + LossFnConfig, + ModelInput, + ModelInputChunk, + OperationResult, + OptimStepRequest, + SampleRequest, + SamplingParams, + SaveCheckpointRequest, + SaveSamplerWeightsRequest, + FoundryFeaturesOptInKeys, +) +from .._utils.model_base import SdkJSONEncoder as _SdkJSONEncoder, _deserialize as _deserialize_model +from .._patch import ( + _chunk_data, + _combine_fwd_bwd_results, + _normalize_loom_result, + _base_headers, + _log_http, + _LOOM_SUBPATH_TO_OP_TYPE, + _API_VERSION, + _DEFAULT_OPERATION_TIMEOUT_SEC, + _ErrorBudget, + _RETRIEVE_POLL_MIN, + _RETRIEVE_POLL_MAX, + _maybe_log_poll_progress, + _clear_poll_log_state, +) + +if TYPE_CHECKING: + from ._client import FineTuningSessionClient + +_PREVIEW = FoundryFeaturesOptInKeys.FINETUNING_SESSIONS_V1_PREVIEW +_logger = _logging.getLogger(__name__) + +_POLL_LOG_DEDUP_SEC = 30.0 + +#: Default maximum concurrent POST requests. +_DEFAULT_POST_CONCURRENCY = 64 + + +# -- Internal state initializer ----------------------------------------------- + +def _ensure_async_state(self: "FineTuningSessionClient") -> None: + """Lazily initialize async state on the client instance.""" + if not hasattr(self, "_heartbeat_tasks"): + self._heartbeat_tasks: dict[str, asyncio.Task] = {} + if not hasattr(self, "_post_semaphore"): + self._post_semaphore = asyncio.Semaphore(_DEFAULT_POST_CONCURRENCY) + if not hasattr(self, "_sampling_session_seq"): + self._sampling_session_seq: dict[str, int] = {} + + +# -- Background heartbeat ----------------------------------------------------- + +def _start_heartbeat( + self: "FineTuningSessionClient", + session_id: str, + interval_sec: float = 30.0, +) -> None: + """Start an asyncio task that sends heartbeat every interval_sec.""" + _ensure_async_state(self) + raw_id = session_id.removeprefix("model_") + heartbeat_session_id = f"session_{raw_id}" + + async def _heartbeat_loop() -> None: + while True: + await asyncio.sleep(interval_sec) + try: + hb_req = _HttpRequest( + "POST", + "{endpoint}" + + f"/fine_tuning/sessions/{heartbeat_session_id}/heartbeat", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + resp = await self.send_request(hb_req) + if resp.status_code != 200: + _logger.warning( + "[heartbeat] status=%d for %s", + resp.status_code, + heartbeat_session_id, + ) + except asyncio.CancelledError: + return + except Exception as exc: + _logger.warning( + "[heartbeat] failed for %s: %s", + heartbeat_session_id, + exc, + ) + + task = asyncio.create_task( + _heartbeat_loop(), name=f"fts-heartbeat-{session_id}" + ) + self._heartbeat_tasks[session_id] = task + _logger.info( + "[heartbeat] started (interval=%.0fs, session=%s)", + interval_sec, + heartbeat_session_id, + ) + + +def _stop_heartbeat(self: "FineTuningSessionClient", session_id: str) -> None: + """Cancel the background heartbeat task for a session.""" + _ensure_async_state(self) + task = self._heartbeat_tasks.pop(session_id, None) + if task is not None: + task.cancel() + + +# -- Low-level helpers --------------------------------------------------------- + +async def _post( + self: "FineTuningSessionClient", + subpath: str, + body_model: Any, + extra_params: Optional[dict] = None, +) -> tuple[str, str]: + """POST to enqueue a job. Returns ``(request_id, op_type)``. + + Gated by ``_post_semaphore`` to prevent connection storms. + Retries on 408/409/429/5xx with exponential backoff (max 5 retries). + """ + _ensure_async_state(self) + body_json = _json.dumps( + body_model, cls=_SdkJSONEncoder, exclude_readonly=True + ) + post_params: dict = {"api-version": _API_VERSION} + if extra_params: + post_params.update(extra_params) + op_type = _LOOM_SUBPATH_TO_OP_TYPE.get(subpath.rsplit("/", 1)[-1], "") + _log_http("request", "POST", subpath, body=_json.loads(body_json)) + + max_retries = 2 + _BASE_TIMEOUT_SEC = 100 # per-request timeout; escalated on each retry + _TIMEOUT_MULTIPLIER = 1.5 + # Non-retryable status codes: deterministic rejections that will + # never succeed on retry. Surface a typed exception immediately. + _NON_RETRYABLE = frozenset({400, 413, 422}) + + async with self._post_semaphore: + last_status: Optional[int] = None + consecutive_same_status = 0 + + for attempt in range(max_retries + 1): + # Escalate per-request timeout on retries so later attempts + # aren't doomed to the same cutoff when the server is slow. + request_timeout = _BASE_TIMEOUT_SEC * (_TIMEOUT_MULTIPLIER ** attempt) + try: + post_req = _HttpRequest( + "POST", + "{endpoint}" + subpath, + headers=_base_headers({"Content-Type": "application/json"}), + params=post_params, + content=body_json, + ) + resp = await self.send_request( + post_req, connection_timeout=request_timeout + ) + sc = resp.status_code + + # --- Non-retryable: classify and raise immediately --- + if sc in _NON_RETRYABLE: + try: + resp_body = resp.json() + except Exception: + resp_body = None + typed = _classify_http_error(sc, resp_body, response=resp) + if typed is not None: + raise typed + resp.raise_for_status() + + # --- Track repeated same-status for pattern detection --- + if sc in (408, 409, 429) or (500 <= sc < 600): + if sc == last_status: + consecutive_same_status += 1 + else: + consecutive_same_status = 1 + last_status = sc + + # After 2 consecutive identical failures, it's likely + # persistent — classify and raise typed if possible. + if consecutive_same_status >= 2: + try: + resp_body = resp.json() + except Exception: + resp_body = None + typed = _classify_http_error(sc, resp_body, response=resp) + if typed is not None: + raise typed + + if attempt < max_retries: + # Honor Retry-After header from server (seconds). + retry_after = resp.headers.get("Retry-After") + if retry_after is not None: + try: + wait = float(retry_after) + except (ValueError, TypeError): + wait = min(0.5 * (2**attempt), 10.0) + else: + wait = min(0.5 * (2**attempt), 10.0) + # Add jitter to prevent thundering herd. + wait *= 1 - 0.25 * _random.random() + _logger.warning( + "POST %s returned %d, retry %d/%d in %.1fs", + subpath, + sc, + attempt + 1, + max_retries, + wait, + ) + await asyncio.sleep(wait) + continue + + # Exhausted retries — classify before raising generic error. + try: + resp_body = resp.json() + except Exception: + resp_body = None + typed = _classify_http_error(sc, resp_body, response=resp) + if typed is not None: + raise typed + + _log_http( + "response", + "POST", + subpath, + status=sc, + body=resp.json() if sc < 400 else None, + ) + resp.raise_for_status() + data = resp.json() + return ( + data["request_id"], + op_type, + ) + + except (_ServiceRequestError, _ServiceResponseError) as exc: + if attempt < max_retries: + # Back off longer for network errors (timeout/connection + # failures). The actual request timeout is escalated via + # connection_timeout above. + wait = min(1.0 * (2**attempt), 10.0) * ( + 1 - 0.25 * _random.random() + ) + _logger.warning( + "POST %s %s(%s), retry %d/%d in %.1fs", + subpath, + type(exc).__name__, + exc, + attempt + 1, + max_retries, + wait, + ) + await asyncio.sleep(wait) + continue + raise + + # Should not be reached. + raise RuntimeError(f"POST {subpath} failed after {max_retries} retries") + + +async def _poll( + self: "FineTuningSessionClient", + session_id: str, + request_id: str, + op_type: str, + extra_result_fields: Optional[dict] = None, + error_budget_sec: Optional[float] = None, +) -> OperationResult: + """Short-poll the envelope endpoint until the request resolves. + + Adaptive backoff: starts at ``_RETRIEVE_POLL_MIN``, doubles up to + ``_RETRIEVE_POLL_MAX``. Retries 5xx and transient network errors. + + ``error_budget_sec`` is an ERROR budget, not a wall-clock budget (matching + the sync ``_post_and_poll``): healthy pending progress is unbounded and + CLEARS the budget, while a sustained streak of 5xx / 408 / 429 / transient + network errors longer than the budget raises ``TimeoutError``. Pass ``None`` + to disable it (retry forever). + """ + poll_path = f"/fine_tuning/sessions/{session_id}/request/{request_id}" + conn_backoff = 1.0 + poll_backoff = _RETRIEVE_POLL_MIN + error_budget = _ErrorBudget.for_polling( + error_budget_sec, op_type=op_type, request_id=request_id + ) + poll_start = _time.monotonic() + + while True: + try: + poll_req = _HttpRequest( + "GET", + "{endpoint}" + + f"/fine_tuning/sessions/{session_id}/request/{request_id}", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + _log_http("request", "GET", poll_path) + resp = await self.send_request(poll_req) + + if resp.status_code == 200: + envelope = resp.json() + _log_http("response", "GET", poll_path, status=200, body=envelope) + conn_backoff = 1.0 + + status = envelope.get("status") + if status == "pending": + elapsed = _time.monotonic() - poll_start + _maybe_log_poll_progress(envelope, session_id, request_id, op_type, elapsed) + error_budget.clear() + await asyncio.sleep(poll_backoff) + poll_backoff = min(poll_backoff * 2, _RETRIEVE_POLL_MAX) + continue + if status == "failed": + _clear_poll_log_state(session_id, request_id, op_type) + typed = _classify_poll_failure(envelope, session_id=session_id) + if typed is not None: + raise typed + raise RuntimeError( + f"{op_type} request {request_id} failed " + f"[{envelope.get('error_code') or envelope.get('code') or 'unknown'}]: " + f"{envelope.get('error')} " + f"(debug_ref={envelope.get('debug_ref') or 'n/a'})" + ) + if status != "completed": + _clear_poll_log_state(session_id, request_id, op_type) + raise RuntimeError( + f"Unexpected envelope status {status!r} for " + f"{op_type} request {request_id}: {envelope}" + ) + + # completed -- normalize and deserialize. + _clear_poll_log_state(session_id, request_id, op_type) + raw = envelope.get("result") or {} + normalized = _normalize_loom_result(raw, op_type, request_id) + if extra_result_fields: + for k, v in extra_result_fields.items(): + if not normalized.get(k): + normalized[k] = v + return _deserialize_model(OperationResult, normalized) + + # Retryable HTTP status. + if ( + 500 <= resp.status_code < 600 + or resp.status_code in (408, 429) + ): + body: Optional[Any] = None + try: + body = resp.json() + except Exception: + body = None + _log_http( + "response", "GET", poll_path, + status=resp.status_code, body=body, + ) + elapsed = _time.monotonic() - poll_start + _logger.debug( + "[poller] retry on %s/%s after HTTP %d (%.0fs elapsed)", + session_id, request_id, resp.status_code, elapsed, + ) + error_budget.consume(f"HTTP {resp.status_code}") + conn_backoff = 1.0 + # Honor Retry-After header if present. + retry_after = resp.headers.get("Retry-After") + if retry_after is not None: + try: + poll_wait = float(retry_after) + except (ValueError, TypeError): + poll_wait = _RETRIEVE_POLL_MIN + else: + poll_wait = _RETRIEVE_POLL_MIN + await asyncio.sleep(poll_wait) + continue + + # Non-retryable HTTP error. + _log_http( + "response", "GET", poll_path, + status=resp.status_code, body=None, + ) + try: + poll_body = resp.json() + except Exception: + poll_body = None + typed = _classify_http_error( + resp.status_code, poll_body, response=resp, session_id=session_id + ) + if typed is not None: + raise typed + resp.raise_for_status() + + except (_ServiceRequestError, _ServiceResponseError) as exc: + elapsed = _time.monotonic() - poll_start + _logger.warning( + "[poller] retry on %s/%s after %s(%s) (%.0fs elapsed), backoff %.1fs", + session_id, request_id, type(exc).__name__, exc, elapsed, conn_backoff, + ) + error_budget.consume(type(exc).__name__) + await asyncio.sleep(conn_backoff) + conn_backoff = min(conn_backoff * 2, 30.0) + continue + + +async def _post_and_poll( + self: "FineTuningSessionClient", + session_id: str, + subpath: str, + body_model: Any, + extra_params: Optional[dict] = None, + extra_result_fields: Optional[dict] = None, +) -> OperationResult: + """POST to enqueue a job, then poll until it completes.""" + request_id, op_type = await _post( + self, subpath, body_model, extra_params + ) + return await _poll( + self, + session_id, + request_id, + op_type, + extra_result_fields, + error_budget_sec=_DEFAULT_OPERATION_TIMEOUT_SEC, + ) + + +# -- Public methods patched onto FineTuningSessionClient ----------------------- + +async def create_session( + self: "FineTuningSessionClient", + *, + base_model: str, + lora_config: Optional[LoRAConfig] = None, + type: str = "training", + from_checkpoint: Optional[FromCheckpoint] = None, + timeout_sec: float = 600.0, +) -> str: + """Create a fine-tuning session and wait until the model is loaded. + + :param base_model: Name of the base model to load. + :param lora_config: Optional LoRA adapter config. + :param type: Session type string. Defaults to ``"training"``. + :param from_checkpoint: Optional checkpoint to resume from. + :param timeout_sec: Maximum seconds to wait for model load. + :return: The ``session_id`` string (e.g. ``"model_abc123"``). + """ + _ensure_async_state(self) + + body = _json.loads( + _json.dumps( + CreateSessionRequest( + type=type, base_model=base_model, lora_config=lora_config, + ), + cls=_SdkJSONEncoder, + exclude_readonly=True, + ) + ) + if from_checkpoint is not None: + body["from_checkpoint"] = _json.loads( + _json.dumps(from_checkpoint, cls=_SdkJSONEncoder, exclude_readonly=True) + ) + + body_json = _json.dumps(body) + post_req = _HttpRequest( + "POST", + "{endpoint}/fine_tuning/sessions", + headers=_base_headers({"Content-Type": "application/json"}), + params={"api-version": _API_VERSION}, + content=body_json, + ) + _log_http("request", "POST", "/fine_tuning/sessions", body=_json.loads(body_json)) + post_resp = await self.send_request(post_req) + _log_http( + "response", + "POST", + "/fine_tuning/sessions", + status=post_resp.status_code, + body=post_resp.json() if post_resp.status_code < 400 else None, + ) + if post_resp.status_code >= 400: + try: + resp_body = post_resp.json() + except Exception: + resp_body = None + typed = _classify_http_error(post_resp.status_code, resp_body, response=post_resp) + if typed is not None: + raise typed + post_resp.raise_for_status() + data = post_resp.json() + raw_session_id: str = data["session_id"] + request_id: str = data["request_id"] + _logger.info( + "[create_session] POST response: raw_session_id=%s, request_id=%s", + raw_session_id, + request_id, + ) + + session_id: str = f"model_{raw_session_id}" + _logger.info( + "[create_session] session_id transformed: raw=%s -> resource_id=%s", + raw_session_id, + session_id, + ) + + # Poll until model load completes. + deadline = _time.monotonic() + timeout_sec + conn_backoff = 1.0 + poll_backoff = _RETRIEVE_POLL_MIN + _create_poll_start = _time.monotonic() + while True: + try: + poll_req = _HttpRequest( + "GET", + "{endpoint}" + + f"/fine_tuning/sessions/{session_id}/request/{request_id}", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + poll_path = ( + f"/fine_tuning/sessions/{session_id}/request/{request_id}" + ) + _log_http("request", "GET", poll_path) + poll_resp = await self.send_request(poll_req) + envelope = ( + poll_resp.json() if poll_resp.status_code == 200 else None + ) + _log_http( + "response", "GET", poll_path, + status=poll_resp.status_code, body=envelope, + ) + + if poll_resp.status_code == 200: + env_status = envelope.get("status") + if env_status == "completed": + _logger.info("[create_session] model load completed: %s", envelope) + _clear_poll_log_state(session_id, request_id, "create_session") + break + if env_status == "failed": + _clear_poll_log_state(session_id, request_id, "create_session") + typed = _classify_poll_failure(envelope, session_id=session_id) + if typed is not None: + raise typed + raise RuntimeError( + f"Model load failed for session_id={raw_session_id} " + f"[{envelope.get('error_code') or envelope.get('code') or 'unknown'}]: " + f"{envelope.get('error') or 'unknown error'} " + f"(debug_ref={envelope.get('debug_ref') or 'n/a'})" + ) + # pending -> adaptive backoff + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for " + f"session_id={raw_session_id} to become ready" + ) + elapsed = _time.monotonic() - _create_poll_start + _maybe_log_poll_progress(envelope, session_id, request_id, "create_session", elapsed) + conn_backoff = 1.0 + await asyncio.sleep(poll_backoff) + poll_backoff = min(poll_backoff * 2, _RETRIEVE_POLL_MAX) + continue + + # Retryable HTTP status codes. + if ( + 500 <= poll_resp.status_code < 600 + or poll_resp.status_code in (408, 429) + ): + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for " + f"session_id={raw_session_id} to become ready" + ) + elapsed = _time.monotonic() - _create_poll_start + _logger.debug( + "[poller] retry on %s/%s after HTTP %d (%.0fs elapsed)", + session_id, request_id, poll_resp.status_code, elapsed, + ) + conn_backoff = 1.0 + await asyncio.sleep(_RETRIEVE_POLL_MIN) + continue + + # Non-retryable error. + try: + poll_body = poll_resp.json() + except Exception: + poll_body = None + typed = _classify_http_error( + poll_resp.status_code, poll_body, response=poll_resp, session_id=session_id + ) + if typed is not None: + raise typed + poll_resp.raise_for_status() + + except (_ServiceRequestError, _ServiceResponseError) as exc: + if _time.monotonic() > deadline: + raise RuntimeError( + f"Timed out after {timeout_sec}s waiting for " + f"session_id={raw_session_id} to become ready" + ) from exc + elapsed = _time.monotonic() - _create_poll_start + _logger.warning( + "[poller] retry on %s/%s after %s(%s) (%.0fs elapsed), backoff %.1fs", + session_id, request_id, type(exc).__name__, exc, elapsed, conn_backoff, + ) + await asyncio.sleep(conn_backoff) + conn_backoff = min(conn_backoff * 2, 30.0) + continue + + _start_heartbeat(self, session_id) + return session_id + + +async def create_session_from_checkpoint( + self: "FineTuningSessionClient", + *, + checkpoint_path: str, + base_model: str, + lora_config: Optional[LoRAConfig] = None, + type: str = "training", + timeout_sec: float = 600.0, +) -> str: + """Create a session resumed from a previously saved training checkpoint. + + :param checkpoint_path: Format: ``"/"``. + :param base_model: Base model name. + :param lora_config: Optional LoRA config override. + :param type: Session type. Defaults to ``"training"``. + :param timeout_sec: Maximum seconds to wait for model load. + :return: The ``session_id`` string. + """ + parts = checkpoint_path.split("/") + if len(parts) != 2 or not parts[0] or not parts[1]: + raise ValueError( + "checkpoint_path must be '/' " + f"with exactly one '/' separator, got: {checkpoint_path!r}" + ) + source_session_id, checkpoint_id = parts + if not source_session_id.startswith("model_"): + source_session_id = f"model_{source_session_id}" + return await create_session( + self, + base_model=base_model, + lora_config=lora_config, + type=type, + from_checkpoint=FromCheckpoint( + source_session_id=source_session_id, + checkpoint_id=checkpoint_id, + ), + timeout_sec=timeout_sec, + ) + + +# -- Training ------------------------------------------------------------------ + +async def forward_backward( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> OperationResult: + """Submit a mini-batch for a forward + backward pass. + + Automatically chunks large batches and submits chunks in parallel + using ``asyncio.gather``. + + :param session_id: The session ID returned by ``create_session``. + :param batch: List of Datum. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: OperationResult. + """ + chunks = _chunk_data(batch) + if len(chunks) <= 1: + return await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/forward_backward", + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=batch, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + + _logger.info( + "[forward_backward] batch of %d datums split into %d chunks: %s", + len(batch), + len(chunks), + [len(c) for c in chunks], + ) + + async def _submit_chunk( + i: int, chunk: List[Datum] + ) -> ForwardBackwardOperationResult: + _logger.info( + "[forward_backward] sending chunk %d/%d (%d datums)", + i + 1, + len(chunks), + len(chunk), + ) + result = await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/forward_backward", + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + if isinstance(result, ForwardBackwardOperationResult): + return result + return ForwardBackwardOperationResult( + total_loss=getattr(result, "total_loss", 0.0), + per_datum_logprobs=getattr(result, "per_datum_logprobs", None), + metrics=getattr(result, "metrics", None), + ) + + # Fire all chunks in parallel. + chunk_results = await asyncio.gather( + *(_submit_chunk(i, chunk) for i, chunk in enumerate(chunks)) + ) + chunk_sizes = [len(c) for c in chunks] + return _combine_fwd_bwd_results(list(chunk_results), chunk_sizes) + + +async def forward( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> OperationResult: + """Submit a mini-batch for a forward-only pass (no gradients). + + Automatically chunks large batches and submits chunks in parallel + using ``asyncio.gather``. + + :param session_id: The session ID returned by ``create_session``. + :param batch: List of Datum. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: OperationResult. + """ + chunks = _chunk_data(batch) + if len(chunks) <= 1: + return await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/forward", + ForwardRequest( + forward_input=ForwardInput( + data=batch, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + + _logger.info( + "[forward] batch of %d datums split into %d chunks: %s", + len(batch), + len(chunks), + [len(c) for c in chunks], + ) + + async def _submit_chunk( + i: int, chunk: List[Datum] + ) -> ForwardBackwardOperationResult: + _logger.info( + "[forward] sending chunk %d/%d (%d datums)", + i + 1, + len(chunks), + len(chunk), + ) + result = await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/forward", + ForwardRequest( + forward_input=ForwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + if isinstance(result, ForwardBackwardOperationResult): + return result + return ForwardBackwardOperationResult( + total_loss=getattr(result, "total_loss", 0.0), + per_datum_logprobs=getattr(result, "per_datum_logprobs", None), + metrics=getattr(result, "metrics", None), + ) + + # Fire all chunks in parallel. + chunk_results = await asyncio.gather( + *(_submit_chunk(i, chunk) for i, chunk in enumerate(chunks)) + ) + chunk_sizes = [len(c) for c in chunks] + return _combine_fwd_bwd_results(list(chunk_results), chunk_sizes) + + +async def forward_post( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> "PendingRequests": + """POST a forward-only pass without polling for completion. + + Automatically chunks large batches. Each chunk's POST is awaited + sequentially so the server assigns monotonically increasing UUID v7 + request IDs. Returns a :class:`PendingRequests` handle whose + ``poll_result()`` can be awaited later. + + :param session_id: The session ID. + :param batch: List of Datum. + :param loss_fn: Loss function name. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: PendingRequests handle. + """ + subpath = f"/fine_tuning/sessions/{session_id}/forward" + chunks = _chunk_data(batch) + + if len(chunks) > 1: + _logger.info( + "[forward_post] batch of %d datums split into %d chunks: %s", + len(batch), + len(chunks), + [len(c) for c in chunks], + ) + + posted: List[tuple] = [] + for chunk in chunks: + request_id, op_type = await _post( + self, + subpath, + ForwardRequest( + forward_input=ForwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + posted.append((request_id, op_type)) + + return PendingRequests(self, session_id, posted, chunks) - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize + +async def forward_async( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> "asyncio.Task[OperationResult]": + """Submit a forward-only pass, return an asyncio Task for the result. + + Awaits all POSTs (with chunking) so that the server assigns UUID v7 + request IDs *before* this method returns. + + **Multi-chunk correctness:** When the batch is split into multiple HTTP + chunks, this method awaits GPU completion of *all* chunks before + returning, matching the forward_backward_async behavior. + + :param session_id: The session ID. + :param batch: List of Datum. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: An asyncio.Task whose result is an OperationResult. + """ + pending = await forward_post( + self, session_id, batch, loss_fn=loss_fn, loss_fn_config=loss_fn_config + ) + + if len(pending._posted) > 1: + _logger.info( + "[forward_async] multi-chunk (%d): awaiting all chunk results before returning", + len(pending._posted), + ) + result = await pending.poll_result() + done: asyncio.Future[OperationResult] = asyncio.get_running_loop().create_future() + done.set_result(result) + return done # type: ignore[return-value] + + return asyncio.create_task(pending.poll_result(), name="fwd_poll") + + +async def optim_step( + self: "FineTuningSessionClient", + session_id: str, + adam_params: AdamParams, +) -> OperationResult: + """Apply accumulated gradients with Adam. + + :param session_id: The session ID. + :param adam_params: Optimizer hyper-parameters. + :return: OperationResult. + """ + return await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/optim_step", + OptimStepRequest(adam_params=adam_params), + ) + + +# -- POST-only variants (for pipelined training) ------------------------------- + + +class PendingRequests: + """Opaque handle returned by ``*_post`` methods. + + Holds the request IDs assigned by the server so that + ``poll_result`` can wait for completion later. This lets callers + guarantee POST ordering (UUID v7) while backgrounding the poll. + """ + + def __init__( + self, + client: "FineTuningSessionClient", + session_id: str, + posted: List[tuple], + chunks: Optional[List[List[Datum]]] = None, + extra_result_fields: Optional[dict] = None, + ): + self._client = client + self._session_id = session_id + self._posted = posted # list of (request_id, op_type) + self._chunks = chunks # only set for chunked forward_backward + self._extra_result_fields = extra_result_fields + + async def poll_result(self) -> OperationResult: + """Poll until all POSTed requests complete and return the combined result.""" + if len(self._posted) == 1: + rid, ot = self._posted[0] + return await _poll( + self._client, self._session_id, rid, ot, + extra_result_fields=self._extra_result_fields, + error_budget_sec=_DEFAULT_OPERATION_TIMEOUT_SEC, + ) + + # Multiple chunks — poll in parallel and combine. + async def _poll_one(rid: str, ot: str) -> ForwardBackwardOperationResult: + result = await _poll( + self._client, self._session_id, rid, ot, + extra_result_fields=self._extra_result_fields, + error_budget_sec=_DEFAULT_OPERATION_TIMEOUT_SEC, + ) + if isinstance(result, ForwardBackwardOperationResult): + return result + return ForwardBackwardOperationResult( + total_loss=getattr(result, "total_loss", 0.0), + per_datum_logprobs=getattr(result, "per_datum_logprobs", None), + metrics=getattr(result, "metrics", None), + ) + + chunk_results = await asyncio.gather( + *(_poll_one(rid, ot) for rid, ot in self._posted) + ) + chunk_sizes = [len(c) for c in self._chunks] if self._chunks else [1] * len(self._posted) + return _combine_fwd_bwd_results(list(chunk_results), chunk_sizes) + + +async def forward_backward_post( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> PendingRequests: + """POST a forward+backward pass without polling for completion. + + Automatically chunks large batches. Each chunk's POST is awaited + sequentially so the server assigns monotonically increasing UUID v7 + request IDs. Returns a :class:`PendingRequests` handle whose + ``poll_result()`` can be awaited later. + + Use this instead of :meth:`forward_backward` when you need to + guarantee that all forward_backward requests are registered on the + server *before* a subsequent ``optim_step_post``. + + :param session_id: The session ID. + :param batch: List of Datum. + :param loss_fn: Loss function name. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: PendingRequests handle. + """ + subpath = f"/fine_tuning/sessions/{session_id}/forward_backward" + chunks = _chunk_data(batch) + + if len(chunks) > 1: + _logger.info( + "[forward_backward_post] batch of %d datums split into %d chunks: %s", + len(batch), + len(chunks), + [len(c) for c in chunks], + ) + + posted: List[tuple] = [] + for chunk in chunks: + request_id, op_type = await _post( + self, + subpath, + ForwardBackwardRequest( + forward_backward_input=ForwardBackwardInput( + data=chunk, + loss_fn=loss_fn, + loss_fn_config=loss_fn_config, + ) + ), + ) + posted.append((request_id, op_type)) + + return PendingRequests(self, session_id, posted, chunks) + + +async def forward_backward_async( + self: "FineTuningSessionClient", + session_id: str, + batch: List[Datum], + *, + loss_fn: Union[str, LossFn] = LossFn.CROSS_ENTROPY, + loss_fn_config: Optional[LossFnConfig] = None, +) -> "asyncio.Task[OperationResult]": + """Submit a forward+backward pass, return an asyncio Task for the result. + + Awaits all POSTs (with chunking) so that the server assigns UUID v7 + request IDs *before* this method returns. + + **Multi-chunk correctness:** When the batch is split into multiple HTTP + chunks, this method awaits GPU completion of *all* chunks before + returning. This ensures that a subsequent ``optim_step_async`` POST + cannot reach the engine until every chunk's gradients have been + accumulated. The returned task resolves immediately with the combined + result. + + For single-chunk batches (the common case), only the poll phase runs + in the background — fully pipelined, no extra latency. + + :param session_id: The session ID. + :param batch: List of Datum. + :param loss_fn: Loss function name. Defaults to ``"cross_entropy"``. + :param loss_fn_config: Optional per-loss hyper-parameters. + :return: An asyncio.Task whose result is an OperationResult. + """ + pending = await forward_backward_post( + self, session_id, batch, loss_fn=loss_fn, loss_fn_config=loss_fn_config + ) + + if len(pending._posted) > 1: + # Multiple chunks: await GPU completion of all chunks NOW so that + # the caller can safely post optim_step after this returns. + # + # Why: the engine polls the DB on a 0.5-2s cadence. If we fire + # chunk1-POST, chunk2-POST, optim-POST in ~600ms, the engine can + # poll between chunk1 and chunk2, see chunk1 with no barrier, and + # process chunk1 + optim_step before chunk2 lands — applying + # gradients from only half the batch. + # + # Waiting for GPU completion of all chunks before posting + # optim_step eliminates the race at the cost of one extra engine + # poll cycle (~0.5-2s) of latency. + _logger.info( + "[forward_backward_async] multi-chunk (%d): awaiting all chunk results before returning", + len(pending._posted), + ) + result = await pending.poll_result() + done: asyncio.Future[OperationResult] = asyncio.get_running_loop().create_future() + done.set_result(result) + return done # type: ignore[return-value] # Future is awaitable like Task + + return asyncio.create_task(pending.poll_result(), name="fwd_bwd_poll") + + +async def optim_step_post( + self: "FineTuningSessionClient", + session_id: str, + adam_params: AdamParams, +) -> PendingRequests: + """POST an optim_step without polling for completion. + + Returns a :class:`PendingRequests` handle whose ``poll_result()`` + can be awaited later. + + :param session_id: The session ID. + :param adam_params: Optimizer hyper-parameters. + :return: PendingRequests handle. + """ + subpath = f"/fine_tuning/sessions/{session_id}/optim_step" + request_id, op_type = await _post( + self, + subpath, + OptimStepRequest(adam_params=adam_params), + ) + return PendingRequests(self, session_id, [(request_id, op_type)]) + + +async def optim_step_async( + self: "FineTuningSessionClient", + session_id: str, + adam_params: AdamParams, +) -> "asyncio.Task[OperationResult]": + """Submit an optim_step, return an asyncio Task for the result. + + Awaits the POST so the server assigns a UUID v7 *after* all preceding + forward_backward_async calls. Only the poll runs in the background. + + :param session_id: The session ID. + :param adam_params: Optimizer hyper-parameters. + :return: An asyncio.Task whose result is an OperationResult. + """ + pending = await optim_step_post(self, session_id, adam_params) + return asyncio.create_task(pending.poll_result(), name="optim_poll") + + +# -- Checkpoints --------------------------------------------------------------- + +async def save_weights( + self: "FineTuningSessionClient", + session_id: str, + path: str, +) -> OperationResult: + """Save a training checkpoint (LoRA weights + optimizer state). + + :param session_id: The session ID. + :param path: Checkpoint name/path. + :return: OperationResult. + """ + return await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/checkpoint", + SaveCheckpointRequest(path=path), + ) + + +async def save_weights_post( + self: "FineTuningSessionClient", + session_id: str, + path: str, + *, + step_number: Optional[int] = None, + metrics: Optional[Dict[str, Any]] = None, +) -> "PendingRequests": + """POST a save-weights request without polling for completion. + + :param session_id: The session ID. + :param path: Checkpoint name/path. + :param step_number: Training step number for this checkpoint. + :param metrics: Evaluation metrics at checkpoint time. + :return: PendingRequests handle. + """ + subpath = f"/fine_tuning/sessions/{session_id}/checkpoint" + request_id, op_type = await _post( + self, + subpath, + SaveCheckpointRequest(path=path, step_number=step_number, metrics=metrics), + ) + return PendingRequests(self, session_id, [(request_id, op_type)]) + + +async def save_weights_async( + self: "FineTuningSessionClient", + session_id: str, + path: str, + *, + step_number: Optional[int] = None, + metrics: Optional[Dict[str, Any]] = None, +) -> "asyncio.Task[OperationResult]": + """Submit a save-weights request, return an asyncio Task for the result. + + Awaits the POST so the server assigns a UUID v7 *after* all preceding + requests. Only the poll runs in the background. + + :param session_id: The session ID. + :param path: Checkpoint name/path. + :param step_number: Training step number for this checkpoint. + :param metrics: Evaluation metrics at checkpoint time. + :return: An asyncio.Task whose result is an OperationResult. + """ + pending = await save_weights_post(self, session_id, path, step_number=step_number, metrics=metrics) + return asyncio.create_task(pending.poll_result(), name=f"save_{path}") + + +async def _save_weights_for_sampler_post( + self: "FineTuningSessionClient", + session_id: str, + *, + sampling_session_seq_id: Optional[int] = None, + path: Optional[str] = None, +) -> "PendingRequests": + """Internal: POST a save-weights-for-sampler request without polling.""" + subpath = f"/fine_tuning/sessions/{session_id}/checkpoint_sample" + request_id, op_type = await _post( + self, + subpath, + SaveSamplerWeightsRequest( + seq_id=0, + sampling_session_seq_id=sampling_session_seq_id, + path=path, + ), + ) + return PendingRequests( + self, session_id, [(request_id, op_type)], + extra_result_fields={"checkpoint_id": path or ""}, + ) + + +async def save_weights_for_sampler_async( + self: "FineTuningSessionClient", + session_id: str, + name: str, +) -> "asyncio.Task[OperationResult]": + """Save sampler weights and persist them to blob storage. + + The engine persists the checkpoint because ``sampling_session_seq_id`` + is not set. + + :param session_id: The session ID. + :param name: Checkpoint name/identifier. + :return: An asyncio.Task whose result is an OperationResult with + ``checkpoint_id`` set to *name*. """ + _ensure_async_state(self) + pending = await _save_weights_for_sampler_post( + self, session_id, path=name, + ) + return asyncio.create_task(pending.poll_result(), name=f"sampler_{name}") + + +async def save_weights_and_get_sampling_client_async( + self: "FineTuningSessionClient", + session_id: str, + name: str, +) -> "asyncio.Task[OperationResult]": + """Sync current LoRA weights to the sampler (ephemeral — not persisted). + + Used every training step to push weights for rollout sampling. + The engine skips blob persistence because ``sampling_session_seq_id`` + is set. The SDK maintains an internal per-session counter — the user + never sees it. + + :param session_id: The session ID. + :param name: Checkpoint name/identifier (e.g. ``"step5"``). + :return: An asyncio.Task whose result is an OperationResult with + ``checkpoint_id`` set to *name*. + """ + _ensure_async_state(self) + seq = self._sampling_session_seq.get(session_id, 0) + 1 + self._sampling_session_seq[session_id] = seq + pending = await _save_weights_for_sampler_post( + self, session_id, + sampling_session_seq_id=seq, path=name, + ) + return asyncio.create_task(pending.poll_result(), name=f"sync_sampler_{name}") + + +# -- Sampling ------------------------------------------------------------------ + +async def sample( + self: "FineTuningSessionClient", + session_id: str, + prompt_tokens: List[int], + sampling_params: SamplingParams, + *, + checkpoint_id: str, + num_samples: int = 1, + sampling_session_id: Optional[str] = None, + seq_id: Optional[int] = None, + prompt_logprobs: bool = False, + topk_prompt_logprobs: int = 0, +) -> OperationResult: + """Generate completions using current LoRA weights. + + :param session_id: The session ID. + :param prompt_tokens: Tokenised prompt as a list of integer IDs. + :param sampling_params: Generation parameters. + :param checkpoint_id: Sampler checkpoint ID from ``save_weights_for_sampler``. + :param num_samples: Number of completions. Default 1. + :return: OperationResult. + """ + return await _post_and_poll( + self, + session_id, + f"/fine_tuning/sessions/{session_id}/sample", + SampleRequest( + num_samples=num_samples, + prompt=ModelInput(chunks=[ModelInputChunk(tokens=prompt_tokens)]), + sampling_params=sampling_params, + topk_prompt_logprobs=topk_prompt_logprobs, + sampling_session_id=sampling_session_id, + seq_id=seq_id, + prompt_logprobs=prompt_logprobs, + ), + extra_params={"checkpoint_id": checkpoint_id}, + ) + + +# -- Session lifecycle --------------------------------------------------------- + +async def close_session( + self: "FineTuningSessionClient", + session_id: str, +) -> None: + """Unload the session from the GPU engine. + + Stops the background heartbeat, then issues the complete request. + + :param session_id: The session ID to close. + """ + _stop_heartbeat(self, session_id) + close_req = _HttpRequest( + "POST", + "{endpoint}" + f"/fine_tuning/sessions/{session_id}/complete", + headers=_base_headers(), + params={"api-version": _API_VERSION}, + ) + resp = await self.send_request(close_req) + resp.raise_for_status() + + +# -- Patch the generated client ------------------------------------------------ + +__all__: list[str] = [] + + +def patch_sdk(): + """Patch async convenience methods onto FineTuningSessionClient.""" + from ._client import FineTuningSessionClient + + FineTuningSessionClient.create_session = create_session + FineTuningSessionClient.create_session_from_checkpoint = create_session_from_checkpoint + FineTuningSessionClient.forward_backward = forward_backward + FineTuningSessionClient.forward_backward_post = forward_backward_post + FineTuningSessionClient.forward_backward_async = forward_backward_async + FineTuningSessionClient.forward = forward + FineTuningSessionClient.forward_post = forward_post + FineTuningSessionClient.forward_async = forward_async + FineTuningSessionClient.optim_step = optim_step + FineTuningSessionClient.optim_step_post = optim_step_post + FineTuningSessionClient.optim_step_async = optim_step_async + FineTuningSessionClient.save_weights = save_weights + FineTuningSessionClient.save_weights_post = save_weights_post + FineTuningSessionClient.save_weights_async = save_weights_async + FineTuningSessionClient.save_weights_for_sampler_async = save_weights_for_sampler_async + FineTuningSessionClient.save_weights_and_get_sampling_client_async = save_weights_and_get_sampling_client_async + FineTuningSessionClient.sample = sample + FineTuningSessionClient.close_session = close_session diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py index dbca8dabe58d..4dba4f1f078a 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/__init__.py @@ -26,6 +26,8 @@ ForwardBackwardInput, ForwardBackwardOperationResult, ForwardBackwardRequest, + ForwardInput, + ForwardRequest, HeartbeatResponse, LoRAConfig, LossFnConfig, @@ -76,6 +78,8 @@ "ForwardBackwardInput", "ForwardBackwardOperationResult", "ForwardBackwardRequest", + "ForwardInput", + "ForwardRequest", "HeartbeatResponse", "LoRAConfig", "LossFnConfig", diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py index 3a69b82b84b5..ecc09f73674f 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_enums.py @@ -78,16 +78,21 @@ class OperationType(str, Enum, metaclass=CaseInsensitiveEnumMeta): class SessionStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): - """Lifecycle status of a fine-tuning session.""" + """Lifecycle status of a fine-tuning session. - CREATED = "created" + Uses the canonical fine-tuning job vocabulary for consistency + across all API surfaces. + """ + + QUEUED = "queued" """Session has been created and is waiting for the GPU engine.""" - READY = "ready" - """Session is loaded and ready to receive training or sampling requests.""" - UNLOADED = "unloaded" - """Session has been unloaded from the GPU engine.""" + RUNNING = "running" + """Session is loaded and actively processing requests.""" + SUCCEEDED = "succeeded" + """Session completed successfully.""" FAILED = "failed" - """The engine hosting this session has died; weights are lost.""" + """Session ended in a non-success terminal state (engine death, expiry, + or unrecoverable failure). Any in-memory weights are lost.""" class SessionType(str, Enum, metaclass=CaseInsensitiveEnumMeta): diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py index 1a29bde36e89..f5c9f7bf8422 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_models.py @@ -256,6 +256,8 @@ class CreateSessionRequest(_Model): lora_config: Optional["_models.LoRAConfig"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """LoRA adapter config. Rank is fixed server-side for v1; omit to use the server default.""" user_metadata: Optional[dict[str, str]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + ejectable: Optional[bool] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Opt the session into idle hibernation. Default False.""" @overload def __init__( @@ -265,6 +267,7 @@ def __init__( base_model: str, lora_config: Optional["_models.LoRAConfig"] = None, user_metadata: Optional[dict[str, str]] = None, + ejectable: Optional[bool] = None, ) -> None: ... @overload @@ -511,6 +514,45 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class ForwardInput(ForwardBackwardInput): + """Forward-only payload. + + Currently identical to ForwardBackwardInput. Exists as a named alias so + that the forward endpoint has its own type in the SDK, allowing the + contract to diverge later (e.g. making loss_fn optional for forward). + """ + + +class ForwardRequest(_Model): + """Request body for POST /fine_tuning/sessions/{sessionId}/forward. + + :ivar forward_input: Required. + :vartype forward_input: ~azure.ai.finetuning_sessions.models.ForwardInput + """ + + forward_input: "_models.ForwardInput" = rest_field( + visibility=["read", "create", "update", "delete", "query"] + ) + """Required.""" + + @overload + def __init__( + self, + *, + forward_input: "_models.ForwardInput", + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + class HeartbeatResponse(_Model): """Response from POST /fine_tuning/sessions/{sessionId}/heartbeat. @@ -545,6 +587,8 @@ class LoRAConfig(_Model): :ivar rank: Number of LoRA rank dimensions. :vartype rank: int + :ivar alpha: LoRA scaling factor (effective scale = alpha / rank). Defaults to 32.0 server-side. + :vartype alpha: float :ivar seed: Seed for LoRA weight initialisation. If omitted, the server picks a random seed and echoes it back. :vartype seed: int @@ -552,6 +596,8 @@ class LoRAConfig(_Model): rank: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Number of LoRA rank dimensions.""" + alpha: Optional[float] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """LoRA scaling factor (effective scale = alpha / rank). Defaults to 32.0 server-side.""" seed: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Seed for LoRA weight initialisation. If omitted, the server picks a random seed and echoes it back.""" @@ -561,6 +607,7 @@ def __init__( self, *, rank: Optional[int] = None, + alpha: Optional[float] = None, seed: Optional[int] = None, ) -> None: ... @@ -807,6 +854,8 @@ class SampledSequence(_Model): :ivar tokens: Required. :vartype tokens: list[int] + :ivar text: Decoded text of the generated sequence. + :vartype text: str :ivar logprobs: :vartype logprobs: list[float] :ivar prompt_logprobs: @@ -815,6 +864,8 @@ class SampledSequence(_Model): tokens: list[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Required.""" + text: Optional[str] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Decoded text of the generated sequence.""" logprobs: Optional[list[float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) prompt_logprobs: Optional[list[float]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -823,6 +874,7 @@ def __init__( self, *, tokens: list[int], + text: Optional[str] = None, logprobs: Optional[list[float]] = None, prompt_logprobs: Optional[list[float]] = None, ) -> None: ... @@ -1039,17 +1091,29 @@ class SaveCheckpointRequest(_Model): :ivar path: User-supplied checkpoint identifier. Alphanumeric plus underscores and hyphens; max 255 characters. Required. :vartype path: str + :ivar step_number: Training iteration this checkpoint corresponds to. Optional. + :vartype step_number: int + :ivar metrics: Per-step evaluation metrics at checkpoint time. Optional. + :vartype metrics: dict[str, any] """ path: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """User-supplied checkpoint identifier. Alphanumeric plus underscores and hyphens; max 255 characters. Required.""" + step_number: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Training iteration this checkpoint corresponds to. Optional.""" + + metrics: Optional[dict[str, Any]] = rest_field(visibility=["read", "create", "update", "delete", "query"]) + """Per-step evaluation metrics at checkpoint time. Optional.""" + @overload def __init__( self, *, path: str, + step_number: Optional[int] = None, + metrics: Optional[dict[str, Any]] = None, ) -> None: ... @overload @@ -1150,8 +1214,8 @@ class Session(_Model): :vartype session_id: str :ivar type: The session type. Required. "training" :vartype type: str or ~azure.ai.finetuning_sessions.models.SessionType - :ivar status: Current lifecycle status of the session. Required. Known values are: "created", - "ready", "unloaded", and "failed". + :ivar status: Current lifecycle status of the session. Required. Known values are: "queued", + "running", "succeeded", and "failed". :vartype status: str or ~azure.ai.finetuning_sessions.models.SessionStatus :ivar model_data: Model and adapter configuration associated with this session. Required. :vartype model_data: ~azure.ai.finetuning_sessions.models.SessionModelData @@ -1162,8 +1226,8 @@ class Session(_Model): type: Union[str, "_models.SessionType"] = rest_field(visibility=["read", "create", "update", "delete", "query"]) """The session type. Required. \"training\"""" status: Union[str, "_models.SessionStatus"] = rest_field(visibility=["read"]) - """Current lifecycle status of the session. Required. Known values are: \"created\", \"ready\", - \"unloaded\", and \"failed\".""" + """Current lifecycle status of the session. Required. Known values are: \"queued\", \"running\", + \"succeeded\", and \"failed\".""" model_data: "_models.SessionModelData" = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Model and adapter configuration associated with this session. Required.""" @@ -1262,8 +1326,8 @@ class SessionSummary(_Model): :vartype session_id: str :ivar base_model: Base model used for this fine-tuning session. Required. :vartype base_model: str - :ivar status: Current lifecycle status of the session. Required. Known values are: "created", - "ready", "unloaded", and "failed". + :ivar status: Current lifecycle status of the session. Required. Known values are: "queued", + "running", "succeeded", and "failed". :vartype status: str or ~azure.ai.finetuning_sessions.models.SessionStatus :ivar is_lora: Whether the session uses a LoRA adapter. Required. :vartype is_lora: bool @@ -1282,8 +1346,8 @@ class SessionSummary(_Model): base_model: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Base model used for this fine-tuning session. Required.""" status: Union[str, "_models.SessionStatus"] = rest_field(visibility=["read"]) - """Current lifecycle status of the session. Required. Known values are: \"created\", \"ready\", - \"unloaded\", and \"failed\".""" + """Current lifecycle status of the session. Required. Known values are: \"queued\", \"running\", + \"succeeded\", and \"failed\".""" is_lora: bool = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Whether the session uses a LoRA adapter. Required.""" lora_rank: Optional[int] = rest_field(visibility=["read", "create", "update", "delete", "query"]) diff --git a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py index 87676c65a8f0..24835e8ce646 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py +++ b/sdk/ai/azure-ai-finetuning-sessions/azure/ai/finetuning_sessions/models/_patch.py @@ -8,8 +8,53 @@ Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize """ +from typing import Any, Mapping, Optional, overload -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level +from .._utils.model_base import Model as _Model, rest_field + + +class FromCheckpoint(_Model): + """Identifies a saved training checkpoint to bootstrap a new session from. + + When passed to :meth:`~azure.ai.finetuning_sessions.FineTuningSession.create`, + the new session's LoRA weights, optimizer state, and scheduler step are all + initialised from the referenced checkpoint (continual fine-tuning). + + :ivar source_session_id: The ``model_`` of the session that saved + the checkpoint. + :vartype source_session_id: str + :ivar checkpoint_id: Name of the checkpoint within the source session. + :vartype checkpoint_id: str + """ + + source_session_id: str = rest_field() + """The ``model_`` of the session that saved the checkpoint.""" + + checkpoint_id: str = rest_field() + """Name of the checkpoint within the source session.""" + + @overload + def __init__( + self, + *, + source_session_id: str, + checkpoint_id: str, + ) -> None: ... + + @overload + def __init__(self, mapping: Mapping[str, Any]) -> None: + """ + :param mapping: raw JSON to initialize the model. + :type mapping: Mapping[str, Any] + """ + + def __init__(self, *args: Any, **kwargs: Any) -> None: + super().__init__(*args, **kwargs) + + +__all__: list[str] = [ + "FromCheckpoint", +] def patch_sdk(): From 925d26b58f5e694f56aed339f07d7c39b964adeb Mon Sep 17 00:00:00 2001 From: Harshith Arun Kumar Date: Mon, 15 Jun 2026 11:14:03 -0700 Subject: [PATCH 3/4] chore: point tsp-location to reorganized sdk-python-azure-ai-finetuning-sessions folder --- sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml index 1aeac3c8648b..bab394f69d66 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml +++ b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml @@ -1,5 +1,5 @@ -directory: specification/ai-foundry/data-plane/Foundry/src/sdk-session -commit: 6d7e8e2c93583d3fe8092aa0d6d953decf26d109 +directory: specification/ai-foundry/data-plane/Foundry/src/sdk-python-azure-ai-finetuning-sessions +commit: e8a4289ad7a repo: Azure/azure-rest-api-specs-pr additionalDirectories: - specification/ai-foundry/data-plane/Foundry/src/session-finetuning From e9947ef906fc91de405621871aef12938499ff06 Mon Sep 17 00:00:00 2001 From: Harshith Arun Kumar Date: Mon, 15 Jun 2026 11:27:44 -0700 Subject: [PATCH 4/4] chore: use full commit SHA in tsp-location.yaml --- sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml index bab394f69d66..810bfaea3a10 100644 --- a/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml +++ b/sdk/ai/azure-ai-finetuning-sessions/tsp-location.yaml @@ -1,5 +1,5 @@ directory: specification/ai-foundry/data-plane/Foundry/src/sdk-python-azure-ai-finetuning-sessions -commit: e8a4289ad7a +commit: e8a4289ad7a07c4887d5cb4a4a967e61dbaeb365 repo: Azure/azure-rest-api-specs-pr additionalDirectories: - specification/ai-foundry/data-plane/Foundry/src/session-finetuning

AzYoM%ET4w$A^l7Ii7RO?LPmurWXU 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