Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions sdk_v2/cpp/include/foundry_local/foundry_local_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -868,6 +868,22 @@ struct flCatalogApi {
FL_API_STATUS(GetCachedModels, _In_ const flCatalog* catalog, _Outptr_ flModelList** out_models);
FL_API_STATUS(GetLoadedModels, _In_ const flCatalog* catalog, _Outptr_ flModelList** out_models);

/// List all known versions of a model (by alias), optionally filtered to a
/// specific variant name. Bypasses the "latest only" filter applied during
/// the normal catalog refresh so older versions visible to the underlying
/// source are returned.
///
/// `model_alias` is the model alias (e.g. "phi-4-mini"); pass null or an
/// empty string to request all versioned models from the source.
/// `variant_name` optionally narrows the result to a single variant name
/// (e.g. "phi-4-mini-cpu-int4-rtn-block-32-acc-level-4"); pass null or an
/// empty string for all variants.
/// Caller owns the returned model list and must release it via ModelList_Release.
FL_API_STATUS(GetModelVersions, _In_ const flCatalog* catalog,
_In_opt_ const char* model_alias,
_In_opt_ const char* variant_name,
_Outptr_ flModelList** out_models);

// End V1
};

Expand Down
9 changes: 9 additions & 0 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,13 @@ class ICatalog {
virtual std::unique_ptr<IModel> GetModel(const std::string& alias) const = 0;
virtual std::unique_ptr<IModel> GetModelVariant(const std::string& model_id) const = 0;
virtual std::unique_ptr<IModel> GetLatestVersion(const IModel& model) const = 0;

/// List all known versions of a model (by alias), optionally filtered to a
/// specific variant name. Bypasses the "latest only" filter that the
/// regular catalog refresh applies. See `flCatalogApi::GetModelVersions`
/// for details. Pass an empty `variant_name` for all variants.
virtual ModelList GetModelVersions(const std::string& model_alias,
const std::string& variant_name = {}) = 0;
};

// ===========================================================================
Expand All @@ -751,6 +758,8 @@ class Catalog final : public ICatalog {
std::unique_ptr<IModel> GetModel(const std::string& alias) const override;
std::unique_ptr<IModel> GetModelVariant(const std::string& model_id) const override;
std::unique_ptr<IModel> GetLatestVersion(const IModel& model) const override;
ModelList GetModelVersions(const std::string& model_alias,
const std::string& variant_name = {}) override;

private:
detail::Base<flCatalog> handle_;
Expand Down
11 changes: 11 additions & 0 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.inline.h
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,17 @@ inline std::unique_ptr<IModel> Catalog::GetLatestVersion(const IModel& model) co
return std::make_unique<Model>(*m);
}

inline ModelList Catalog::GetModelVersions(const std::string& model_alias,
const std::string& variant_name) {
flModelList* models = nullptr;
Check(detail::catalog_api()->GetModelVersions(
handle_.get(),
model_alias.empty() ? nullptr : model_alias.c_str(),
variant_name.empty() ? nullptr : variant_name.c_str(),
&models));
return ModelList(*models);
}

// ===========================================================================
// Item
// ===========================================================================
Expand Down
25 changes: 25 additions & 0 deletions sdk_v2/cpp/src/c_api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,30 @@ FL_API_STATUS_IMPL(Catalog_GetLoadedModelsImpl, const flCatalog* catalog, flMode
API_IMPL_END
}

FL_API_STATUS_IMPL(Catalog_GetModelVersionsImpl, const flCatalog* catalog,
const char* model_alias, const char* variant_name,
flModelList** out_models) {
API_IMPL_BEGIN
if (!catalog || !out_models) {
return MakeStatus(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT, "null argument");
}

std::string alias = model_alias ? model_alias : std::string{};
std::string variant = variant_name ? variant_name : std::string{};

auto models = catalog->impl.GetModelVersions(alias, variant);
auto list = std::make_unique<flModelList>();
list->items.reserve(models.size());

for (auto* m : models) {
list->items.push_back(AsHandle<flModel>(m));
}

*out_models = list.release();
return nullptr;
API_IMPL_END
}

FL_API_STATUS_IMPL(Catalog_GetNameImpl, const flCatalog* catalog, const char** out_name) {
API_IMPL_BEGIN
if (!catalog || !out_name) {
Expand All @@ -693,6 +717,7 @@ static const flCatalogApi g_catalog_api = {
Catalog_GetLatestVersionImpl,
Catalog_GetCachedModelsImpl,
Catalog_GetLoadedModelsImpl,
Catalog_GetModelVersionsImpl,
};

// ========================================================================
Expand Down
17 changes: 17 additions & 0 deletions sdk_v2/cpp/src/catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,23 @@ class ICatalog {
/// Gets the latest version of a model. Returns nullptr if not found.
virtual Model* GetLatestVersion(const Model* model) const = 0;

/// Lists all known versions of a model (by alias), optionally filtered to a
/// specific variant name. Bypasses the "latest only" filter the regular
/// catalog refresh applies — new versions discovered by this call are
/// integrated into the catalog's storage so the returned pointers remain
/// valid for the lifetime of the catalog.
///
/// `model_alias` is the alias of the model (e.g. "phi-4-mini"). When empty,
/// implementations may return all versioned models from the underlying
/// source (still subject to device/EP filtering).
/// `variant_name` optionally narrows results to a specific variant (e.g.
/// "Phi-4-generic-gpu"). Pass an empty string to
/// return every variant.
///
/// Maps to C# `IModelCatalog.GetModelVersionsAsync`.
virtual std::vector<Model*> GetModelVersions(const std::string& model_alias,
const std::string& variant_name) = 0;

/// Lists only models that are cached locally.
virtual std::vector<Model*> GetCachedModels() const = 0;

Expand Down
118 changes: 117 additions & 1 deletion sdk_v2/cpp/src/catalog/azure_model_catalog.cc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
FetchModelVersions// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "catalog/azure_model_catalog.h"
#include "catalog/catalog_cache.h"
Expand All @@ -11,6 +11,9 @@
#include <foundry_local/foundry_local_c.h>
#include <fmt/format.h>

#include <algorithm>
#include <utility>

namespace fl {

AzureModelCatalog::AzureModelCatalog(std::vector<std::pair<std::string, std::optional<std::string>>> catalog_urls,
Expand Down Expand Up @@ -123,4 +126,117 @@ std::vector<Model> AzureModelCatalog::FetchModels() const {
return models;
}

namespace {

// Build a flat list of (url, filter) endpoints to query for direct-lookup
// helpers (FetchModelVersions / FetchModelsByIds). Honours the configured
// catalog_urls_; falls back to the default URL when none are configured.
std::vector<std::pair<std::string, std::optional<std::string>>> EnumerateEndpoints(
const std::vector<std::pair<std::string, std::optional<std::string>>>& configured,
const char* default_url,
const char* default_filter) {
if (!configured.empty()) {
return configured;
}

return {{default_url, std::optional<std::string>(default_filter)}};
}

} // namespace

std::vector<Model> AzureModelCatalog::FetchModelVersions(const std::string& model_alias) const {
if (cache_only_) {
// In cache-only mode we have no remote source to query for older versions.
logger_.Log(LogLevel::Debug,
"FetchModelVersions skipped: catalog is in cache-only mode.");
return {};
}

// Scan local models so any version already on disk is reported as cached.
auto local_models = ScanLocalModels(cache_dir_, logger_);

std::vector<Model> models;
const auto endpoints = EnumerateEndpoints(catalog_urls_, kDefaultCatalogUrl, kDefaultCatalogFilter);

for (const auto& [url, filter] : endpoints) {
try {
auto client = MakeCatalogClient(url, filter.value_or(""), ep_detector_, logger_, cache_dir_);
auto model_infos = client->FetchAllVersionsByAlias(model_alias);

models.reserve(models.size() + model_infos.size());
for (auto& info : model_infos) {
std::string local_path;
auto it = local_models.find(info.model_id);
if (it != local_models.end()) {
local_path = it->second;
}

models.push_back(model_factory_(std::move(info), std::move(local_path)));
}
} catch (const std::exception& ex) {
logger_.Log(LogLevel::Error,
fmt::format("FetchModelVersions: failed to query {} — {}", url, ex.what()));
}
}

logger_.Log(LogLevel::Information,
fmt::format("FetchModelVersions('{}') returned {} variant(s).",
model_alias, models.size()));

return models;
}

std::vector<Model> AzureModelCatalog::FetchModelsByIds(const std::vector<std::string>& model_ids) const {
if (model_ids.empty()) {
return {};
}

if (cache_only_) {
logger_.Log(LogLevel::Debug,
"FetchModelsByIds skipped: catalog is in cache-only mode.");
return {};
}

auto local_models = ScanLocalModels(cache_dir_, logger_);

std::vector<Model> models;
const auto endpoints = EnumerateEndpoints(catalog_urls_, kDefaultCatalogUrl, kDefaultCatalogFilter);

// Track which IDs are still unresolved so we can stop calling further
// endpoints once everything has been found.
std::vector<std::string> remaining(model_ids);

for (const auto& [url, filter] : endpoints) {
if (remaining.empty()) {
break;
}

try {
auto client = MakeCatalogClient(url, filter.value_or(""), ep_detector_, logger_, cache_dir_);
auto model_infos = client->FetchModelsByIds(remaining);

for (auto& info : model_infos) {
std::string local_path;
auto it = local_models.find(info.model_id);
if (it != local_models.end()) {
local_path = it->second;
}

// Drop this id from the remaining list now that it's resolved.
auto rit = std::find(remaining.begin(), remaining.end(), info.model_id);
if (rit != remaining.end()) {
remaining.erase(rit);
}

models.push_back(model_factory_(std::move(info), std::move(local_path)));
}
} catch (const std::exception& ex) {
logger_.Log(LogLevel::Error,
fmt::format("FetchModelsByIds: failed to query {} — {}", url, ex.what()));
}
}

return models;
}

} // namespace fl
2 changes: 2 additions & 0 deletions sdk_v2/cpp/src/catalog/azure_model_catalog.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ class AzureModelCatalog : public BaseModelCatalog {

protected:
std::vector<Model> FetchModels() const override;
std::vector<Model> FetchModelVersions(const std::string& model_alias) const override;
std::vector<Model> FetchModelsByIds(const std::vector<std::string>& model_ids) const override;

private:
#if defined(FOUNDRY_LOCAL_HAVE_LIVE_CATALOG_CLIENT)
Expand Down
Loading
Loading