Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
Adds an MVP Python implementation of the Foundry Local SDK v2, including ctypes-based core interop, model/catalog abstractions, OpenAI-compatible clients, integration tests, packaging metadata, and CI build workflows.
Changes:
- Introduces the Python SDK package (
foundry_local_sdk) with core interop, catalog/model APIs, and chat/audio clients. - Adds a pytest-based integration test suite that mirrors the JS/C# SDK tests.
- Adds Python build steps to GitHub Actions and wires them into the existing SDK build workflow.
Reviewed changes
Copilot reviewed 36 out of 39 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| sdk_v2/python/test/test_model.py | Model cache + load/unload integration tests |
| sdk_v2/python/test/test_foundry_local_manager.py | Manager initialization/catalog exposure tests |
| sdk_v2/python/test/test_catalog.py | Catalog list/lookup/cached/variant tests |
| sdk_v2/python/test/openai/test_chat_client.py | Chat completion + streaming tests + input validation cases |
| sdk_v2/python/test/openai/test_audio_client.py | Audio transcription + streaming tests + validation cases |
| sdk_v2/python/test/openai/init.py | Test package marker for openai/ tests |
| sdk_v2/python/test/detail/test_model_load_manager.py | ModelLoadManager interop + external-service tests |
| sdk_v2/python/test/detail/init.py | Test package marker for detail/ tests |
| sdk_v2/python/test/conftest.py | Session fixtures/config shared across tests |
| sdk_v2/python/test/init.py | Test package marker |
| sdk_v2/python/test/README.md | Test-suite documentation and conventions |
| sdk_v2/python/src/version.py | Package version source |
| sdk_v2/python/src/openai/chat_client.py | OpenAI-compatible chat client implementation |
| sdk_v2/python/src/openai/audio_client.py | OpenAI-compatible audio transcription client implementation |
| sdk_v2/python/src/openai/init.py | Exports for OpenAI-compatible clients |
| sdk_v2/python/src/model_variant.py | Model variant operations + client creation |
| sdk_v2/python/src/model.py | Alias-level model wrapper over variants |
| sdk_v2/python/src/logging_helper.py | SDK log-level mapping helper |
| sdk_v2/python/src/imodel.py | Shared model interface (Model/ModelVariant) |
| sdk_v2/python/src/foundry_local_manager.py | Singleton manager + web-service lifecycle |
| sdk_v2/python/src/exception.py | SDK exception type |
| sdk_v2/python/src/detail/utils.py | Shared internal helpers (cached model IDs, StrEnum) |
| sdk_v2/python/src/detail/native_downloader.py | NuGet-based native binary downloader + CLI |
| sdk_v2/python/src/detail/model_load_manager.py | Load/unload abstraction (interop vs web service) |
| sdk_v2/python/src/detail/model_data_types.py | Pydantic models for catalog/model metadata |
| sdk_v2/python/src/detail/core_interop.py | ctypes FFI layer for Foundry Local Core |
| sdk_v2/python/src/detail/init.py | Internal module exports |
| sdk_v2/python/src/configuration.py | SDK configuration model + validation |
| sdk_v2/python/src/catalog.py | Catalog implementation (listing/lookup/cached/loaded) |
| sdk_v2/python/src/init.py | Top-level package exports + logger setup |
| sdk_v2/python/requirements.txt | Runtime dependencies |
| sdk_v2/python/requirements-dev.txt | Dev/test dependencies |
| sdk_v2/python/pyproject.toml | Packaging + pytest configuration |
| sdk_v2/python/examples/chat_completion.py | Example usage script |
| sdk_v2/python/README.md | Python SDK documentation |
| sdk_v2/python/LICENSE.txt | License file |
| sdk_v2/python/.gitignore | Python SDK ignore rules |
| .github/workflows/foundry-local-sdk-build.yml | Adds Python build jobs to SDK build workflow |
| .github/workflows/build-python-steps.yml | New reusable workflow to build/test/package Python SDK |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 39 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 39 changed files in this pull request and generated 7 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 36 out of 39 changed files in this pull request and generated 9 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from dataclasses import dataclass, field | ||
| from typing import Callable, Optional | ||
|
|
||
| from ..detail.core_interop import CoreInterop, InteropRequest |
There was a problem hiding this comment.
AudioClient raises FoundryLocalException later in this file, but it is not imported here, which will cause a NameError at runtime on error paths. Import it (e.g., from ..exception import FoundryLocalException) and consider removing the unused field import from dataclasses.
| from dataclasses import dataclass, field | |
| from typing import Callable, Optional | |
| from ..detail.core_interop import CoreInterop, InteropRequest | |
| from dataclasses import dataclass | |
| from typing import Callable, Optional | |
| from ..detail.core_interop import CoreInterop, InteropRequest | |
| from ..exception import FoundryLocalException |
| logger.info("Download response: %s", response) | ||
| if response.error is not None: | ||
| raise FoundryLocalException(f"Failed to download model: {response.error}") |
There was a problem hiding this comment.
FoundryLocalException is referenced but not imported in this module, which will raise NameError when a download fails. Add the missing import (from .exception import FoundryLocalException) near the top of the file.
| f"HTTP request failed when listing loaded models from {self._external_service_url}" | ||
| ) from e | ||
| except json.JSONDecodeError as e: | ||
| raise FoundryLocalException(f"Failed to decode JSON response: Response was: {response.data}") from e |
There was a problem hiding this comment.
response here is a requests.Response, which does not have a .data attribute; this will throw an AttributeError and mask the real JSON parsing failure. Use response.text (or capture the text into a local variable and reference that) in the exception message.
| raise FoundryLocalException(f"Failed to decode JSON response: Response was: {response.data}") from e | |
| raise FoundryLocalException(f"Failed to decode JSON response: Response was: {content}") from e |
| """ | ||
| Load a model by its ID. | ||
| :param model_id: The ID of the model to load. | ||
| :raises NotImplementedError: If loading via external service is attempted. | ||
| """ | ||
| if self._external_service_url: | ||
| self._web_load_model(model_id) | ||
| return |
There was a problem hiding this comment.
The docstring claims NotImplementedError is raised when loading via external service, but the method is implemented (_web_load_model). Update the docstring to describe the actual behavior and raised exception types.
| dynamic = ["version", "dependencies"] | ||
| description = "Foundry Local Manager Python SDK: Control-plane SDK for Foundry Local." | ||
| readme = "README.md" | ||
| requires-python = ">=3.10" |
There was a problem hiding this comment.
requires-python is >=3.10 but the classifiers include Python 3.9, which is inconsistent for packaging metadata. Remove the 3.9 classifier or lower requires-python (and ensure the codebase avoids 3.10-only syntax if doing so).
| "Topic :: Software Development :: Libraries :: Python Modules", | ||
| "Programming Language :: Python", | ||
| "Programming Language :: Python :: 3 :: Only", | ||
| "Programming Language :: Python :: 3.9", |
There was a problem hiding this comment.
requires-python is >=3.10 but the classifiers include Python 3.9, which is inconsistent for packaging metadata. Remove the 3.9 classifier or lower requires-python (and ensure the codebase avoids 3.10-only syntax if doing so).
| "Programming Language :: Python :: 3.9", |
|
|
||
| ## Prerequisites | ||
|
|
||
| 1. **Python 3.9+** (tested with 3.12/3.13) |
There was a problem hiding this comment.
This claims Python 3.9+, but pyproject.toml sets requires-python = \">=3.10\". Update this doc to match the supported minimum version.
| 1. **Python 3.9+** (tested with 3.12/3.13) | |
| 1. **Python 3.10+** (tested with 3.12/3.13) |
| from openai.types.chat.chat_completion_message_param import * | ||
| from openai.types.chat.completion_create_params import CompletionCreateParamsBase, \ | ||
| CompletionCreateParamsNonStreaming, \ | ||
| CompletionCreateParamsStreaming | ||
| from openai.types.shared_params import Metadata |
There was a problem hiding this comment.
Avoid import * here since it obscures what types are used and can pollute the module namespace; import only ChatCompletionMessageParam (and any other required symbols) explicitly. Also, Metadata is imported but not used in this file—removing it reduces confusion and avoids lint failures.
| from openai.types.chat.chat_completion_message_param import * | |
| from openai.types.chat.completion_create_params import CompletionCreateParamsBase, \ | |
| CompletionCreateParamsNonStreaming, \ | |
| CompletionCreateParamsStreaming | |
| from openai.types.shared_params import Metadata | |
| from openai.types.chat.chat_completion_message_param import ChatCompletionMessageParam | |
| from openai.types.chat.completion_create_params import CompletionCreateParamsBase, \ | |
| CompletionCreateParamsNonStreaming, \ | |
| CompletionCreateParamsStreaming |
| pass | ||
|
|
||
| @abstractmethod | ||
| def load(self,) -> None: |
There was a problem hiding this comment.
The trailing comma in the method signature is unnecessary and inconsistent with the other method definitions. Consider changing it to def load(self) -> None: for readability and style consistency.
| def load(self,) -> None: | |
| def load(self) -> None: |
mvp