Skip to content
Merged
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
6 changes: 4 additions & 2 deletions sdk_v2/cpp/include/foundry_local/foundry_local_c.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,8 +294,10 @@ typedef enum flTensorDataType {
#define FOUNDRY_LOCAL_PARAM_TOP_P "top_p" ///< float [0.0, 1.0]. nucleus sampling
#define FOUNDRY_LOCAL_PARAM_TOP_K "top_k" ///< int. top-k sampling
#define FOUNDRY_LOCAL_PARAM_MAX_OUTPUT_TOKENS "max_output_tokens" ///< int. max tokens to generate
#define FOUNDRY_LOCAL_PARAM_FREQUENCY_PENALTY "frequency_penalty" ///< float [-2.0, 2.0]
#define FOUNDRY_LOCAL_PARAM_PRESENCE_PENALTY "presence_penalty" ///< float [-2.0, 2.0]
/// Float frequency penalty. Currently only the neutral value 0 is supported.
#define FOUNDRY_LOCAL_PARAM_FREQUENCY_PENALTY "frequency_penalty"
/// Float presence penalty. Currently only the neutral value 0 is supported.
#define FOUNDRY_LOCAL_PARAM_PRESENCE_PENALTY "presence_penalty"
#define FOUNDRY_LOCAL_PARAM_SEED "seed" ///< int. for reproducible outputs
#define FOUNDRY_LOCAL_PARAM_EARLY_STOPPING "early_stopping" ///< bool. whether to stop on stop sequence or only at max tokens
#define FOUNDRY_LOCAL_PARAM_DO_SAMPLE "do_sample" ///< bool. whether to sample (false = greedy decoding)
Expand Down
4 changes: 2 additions & 2 deletions sdk_v2/cpp/include/foundry_local/foundry_local_cpp.h
Original file line number Diff line number Diff line change
Expand Up @@ -943,8 +943,8 @@ struct SearchOptions {
std::optional<float> top_p; ///< Nucleus sampling [0.0, 1.0].
std::optional<int> top_k; ///< Top-k sampling.
std::optional<int> max_output_tokens; ///< Maximum tokens to generate.
std::optional<float> frequency_penalty; ///< Frequency penalty [-2.0, 2.0].
std::optional<float> presence_penalty; ///< Presence penalty [-2.0, 2.0].
std::optional<float> frequency_penalty; ///< Currently only the neutral value 0 is supported.
std::optional<float> presence_penalty; ///< Currently only the neutral value 0 is supported.
std::optional<int> seed; ///< Random seed for reproducibility.
std::optional<bool> early_stopping; ///< Stop on stop-sequence match.
std::optional<bool> do_sample; ///< Whether to sample (false = greedy).
Expand Down
4 changes: 2 additions & 2 deletions sdk_v2/cpp/src/contracts/chat_completions.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ struct ChatCompletionRequest {
std::optional<nlohmann::json> stop; // "stop" — string or array
std::optional<int> max_tokens; // "max_tokens" (deprecated)
std::optional<int> max_completion_tokens; // "max_completion_tokens"
std::optional<float> presence_penalty; // "presence_penalty"
std::optional<float> frequency_penalty; // "frequency_penalty"
std::optional<float> presence_penalty; // "presence_penalty"; only 0 is supported
std::optional<float> frequency_penalty; // "frequency_penalty"; only 0 is supported
std::optional<std::vector<ChatCompletionTool>> tools; // "tools"
std::optional<nlohmann::json> tool_choice; // "tool_choice" — string or object
std::optional<nlohmann::json> response_format; // "response_format"
Expand Down
12 changes: 8 additions & 4 deletions sdk_v2/cpp/src/contracts/chat_completions_converter.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ void ApplyCatalogDefaults(ChatCompletionRequest& req, const KeyValuePairs& model

apply_default_float("temperature", req.temperature);
apply_default_float("top_p", req.top_p);
apply_default_float("presence_penalty", req.presence_penalty);
apply_default_float("frequency_penalty", req.frequency_penalty);
apply_default_int("max_tokens", req.max_tokens);

// top_k and random_seed go through metadata (matches C# behavior)
Expand Down Expand Up @@ -153,8 +151,14 @@ void MapRequestParameters(const ChatCompletionRequest& req, Request& session_req

set_float_param(req.temperature, "temperature");
set_float_param(req.top_p, "top_p");
set_float_param(req.frequency_penalty, "frequency_penalty");
set_float_param(req.presence_penalty, "presence_penalty");
if (req.frequency_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero frequency_penalty is not supported; ORT repetition_penalty has different semantics");
}
if (req.presence_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero presence_penalty is not supported; ORT diversity_penalty has different semantics");
}

if (req.seed.has_value()) {
session_request.options["seed"] = std::to_string(*req.seed);
Expand Down
8 changes: 4 additions & 4 deletions sdk_v2/cpp/src/contracts/responses.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ struct ResponseCreateParams {
std::optional<float> temperature;
std::optional<int> max_output_tokens;
std::optional<float> top_p;
std::optional<float> presence_penalty;
std::optional<float> frequency_penalty;
std::optional<float> presence_penalty; // OpenAI semantics; only the neutral value 0 is currently supported.
std::optional<float> frequency_penalty; // OpenAI semantics; only the neutral value 0 is currently supported.
std::optional<int> seed;
bool stream = false;
bool store = false;
Expand Down Expand Up @@ -257,8 +257,8 @@ struct ResponseObject {
std::optional<ToolChoice> tool_choice;
std::optional<float> temperature;
std::optional<float> top_p;
std::optional<float> presence_penalty;
std::optional<float> frequency_penalty;
std::optional<float> presence_penalty; // OpenAI semantics; only the neutral value 0 is currently supported.
std::optional<float> frequency_penalty; // OpenAI semantics; only the neutral value 0 is currently supported.
std::optional<int> max_output_tokens;
bool parallel_tool_calls = true;
bool store = false;
Expand Down
12 changes: 6 additions & 6 deletions sdk_v2/cpp/src/inferencing/generative/chat/search_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -74,14 +74,14 @@ int ApplySearchOptions(const SearchOptions& options,
gen_params.SetSearchOption("top_k", static_cast<double>(*options.top_k));
}

// Frequency penalty → repetition_penalty in ORT GenAI
if (options.frequency_penalty.has_value()) {
gen_params.SetSearchOption("repetition_penalty", static_cast<double>(*options.frequency_penalty));
if (options.frequency_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero frequency_penalty is not supported; ORT repetition_penalty has different semantics");
}

// Presence penalty → diversity_penalty in ORT GenAI
if (options.presence_penalty.has_value()) {
gen_params.SetSearchOption("diversity_penalty", static_cast<double>(*options.presence_penalty));
if (options.presence_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero presence_penalty is not supported; ORT diversity_penalty has different semantics");
}

// Random seed
Expand Down
4 changes: 2 additions & 2 deletions sdk_v2/cpp/src/inferencing/generative/chat/search_options.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ struct SearchOptions {
std::optional<float> top_p;
std::optional<int> top_k;
std::optional<int> max_output_tokens;
std::optional<float> frequency_penalty;
std::optional<float> presence_penalty;
std::optional<float> frequency_penalty; // Currently only the neutral value 0 is supported.
std::optional<float> presence_penalty; // Currently only the neutral value 0 is supported.
std::optional<int> seed;
std::optional<bool> do_sample;
std::optional<bool> early_stopping;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,14 +418,14 @@ Request ToSessionRequest(const ResponseCreateParams& params,
std::to_string(*params.max_output_tokens);
}

if (params.presence_penalty.has_value()) {
request.options["presence_penalty"] =
std::to_string(*params.presence_penalty);
if (params.presence_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero presence_penalty is not supported; ORT diversity_penalty has different semantics");
}

if (params.frequency_penalty.has_value()) {
request.options["frequency_penalty"] =
std::to_string(*params.frequency_penalty);
if (params.frequency_penalty.value_or(0.0f) != 0.0f) {
FL_THROW(FOUNDRY_LOCAL_ERROR_INVALID_ARGUMENT,
"nonzero frequency_penalty is not supported; ORT repetition_penalty has different semantics");
}

if (params.seed.has_value()) {
Expand Down
6 changes: 6 additions & 0 deletions sdk_v2/cpp/src/service/chat_completions_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ std::shared_ptr<HttpRequestHandler::OutgoingResponse> ChatCompletionsHandler::Pa
return ErrorResponse(Status::CODE_400, "Missing required field: messages");
}

if (req.frequency_penalty.value_or(0.0f) != 0.0f ||
req.presence_penalty.value_or(0.0f) != 0.0f) {
return ErrorResponse(Status::CODE_400, "Unsupported parameter",
"nonzero frequency_penalty and presence_penalty are not supported");
}

return nullptr;
}

Expand Down
6 changes: 6 additions & 0 deletions sdk_v2/cpp/src/service/responses_handler.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ std::shared_ptr<HttpRequestHandler::OutgoingResponse> ResponsesHandler::ParseAnd
return ErrorResponse(Status::CODE_400, "Invalid request parameters", ex.what());
}

if (params.frequency_penalty.value_or(0.0f) != 0.0f ||
params.presence_penalty.value_or(0.0f) != 0.0f) {
return ErrorResponse(Status::CODE_400, "Unsupported parameter",
"nonzero frequency_penalty and presence_penalty are not supported");
}

return nullptr;
}

Expand Down
28 changes: 26 additions & 2 deletions sdk_v2/cpp/test/internal_api/chat/search_options_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,6 @@ TEST_F(SearchOptionsTest, AllOptionsSetSimultaneously) {
opts.top_p = 0.9f;
opts.top_k = 50;
opts.max_output_tokens = 256;
opts.frequency_penalty = 1.1f;
opts.presence_penalty = 0.5f;
opts.seed = 42;
opts.do_sample = true;
auto params = MakeParams();
Expand All @@ -163,6 +161,32 @@ TEST_F(SearchOptionsTest, AllOptionsSetSimultaneously) {
EXPECT_EQ(max_length, 276); // 20 + 256
}

TEST_F(SearchOptionsTest, ZeroPenaltiesDoNotOverrideModelDefaults) {
SearchOptions opts;
opts.frequency_penalty = 0.0f;
opts.presence_penalty = 0.0f;
auto params = MakeParams();
const auto repetition_penalty = params->GetSearchNumber("repetition_penalty");
const auto diversity_penalty = params->GetSearchNumber("diversity_penalty");

ApplySearchOptions(opts, 10, GetConfig(), *params, ExecutionProvider::kDefault);

EXPECT_EQ(params->GetSearchNumber("repetition_penalty"), repetition_penalty);
EXPECT_EQ(params->GetSearchNumber("diversity_penalty"), diversity_penalty);
}

TEST_F(SearchOptionsTest, NonzeroPenaltiesAreRejected) {
for (const auto& [frequency, presence] :
{std::pair{0.5f, 0.0f}, std::pair{0.0f, 0.3f}, std::pair{-0.5f, 0.0f}, std::pair{0.0f, -0.3f}}) {
SearchOptions opts;
opts.frequency_penalty = frequency;
opts.presence_penalty = presence;
auto params = MakeParams();

EXPECT_THROW(ApplySearchOptions(opts, 10, GetConfig(), *params, ExecutionProvider::kDefault), fl::Exception);
}
}

TEST_F(SearchOptionsTest, ZeroMaxOutputTokensThrows) {
SearchOptions opts;
opts.max_output_tokens = 0;
Expand Down
37 changes: 27 additions & 10 deletions sdk_v2/cpp/test/internal_api/chat_completions_converter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,8 @@ TEST(ChatCompletionsConverterTest, ApplyCatalogDefaults_AppliesFloatsWhenNotSet)
ASSERT_TRUE(req.top_p.has_value());
EXPECT_FLOAT_EQ(*req.top_p, 0.9f);

ASSERT_TRUE(req.presence_penalty.has_value());
EXPECT_FLOAT_EQ(*req.presence_penalty, 0.1f);

ASSERT_TRUE(req.frequency_penalty.has_value());
EXPECT_FLOAT_EQ(*req.frequency_penalty, 0.2f);
EXPECT_FALSE(req.presence_penalty.has_value());
EXPECT_FALSE(req.frequency_penalty.has_value());
}

TEST(ChatCompletionsConverterTest, ApplyCatalogDefaults_DoesNotOverrideExisting) {
Expand Down Expand Up @@ -343,20 +340,40 @@ TEST(ChatCompletionsConverterTest, ExtractToolDefinitions_ToolChoiceObject_NoMat
// MapRequestParameters
// ========================================================================

TEST(ChatCompletionsConverterTest, MapRequestParameters_AllFloatParams) {
TEST(ChatCompletionsConverterTest, MapRequestParameters_TemperatureAndTopP) {
ChatCompletionRequest req;
req.temperature = 0.7f;
req.top_p = 0.9f;
req.frequency_penalty = 0.5f;
req.presence_penalty = 0.3f;

Request session_request;
MapRequestParameters(req, session_request);

EXPECT_NE(session_request.options.Find("temperature"), nullptr);
EXPECT_NE(session_request.options.Find("top_p"), nullptr);
EXPECT_NE(session_request.options.Find("frequency_penalty"), nullptr);
EXPECT_NE(session_request.options.Find("presence_penalty"), nullptr);
}

TEST(ChatCompletionsConverterTest, MapRequestParameters_RejectsNonzeroPenalties) {
for (const auto& [frequency, presence] :
{std::pair{0.5f, 0.0f}, std::pair{0.0f, 0.3f}, std::pair{-0.5f, 0.0f}, std::pair{0.0f, -0.3f}}) {
ChatCompletionRequest req;
req.frequency_penalty = frequency;
req.presence_penalty = presence;

Request session_request;
EXPECT_THROW(MapRequestParameters(req, session_request), fl::Exception);
}
}

TEST(ChatCompletionsConverterTest, MapRequestParameters_ZeroPenaltiesAreNoOps) {
ChatCompletionRequest req;
req.frequency_penalty = 0.0f;
req.presence_penalty = 0.0f;

Request session_request;
MapRequestParameters(req, session_request);

EXPECT_EQ(session_request.options.Find("frequency_penalty"), nullptr);
EXPECT_EQ(session_request.options.Find("presence_penalty"), nullptr);
}

TEST(ChatCompletionsConverterTest, MapRequestParameters_Seed) {
Expand Down
30 changes: 26 additions & 4 deletions sdk_v2/cpp/test/internal_api/response_converter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -833,8 +833,6 @@ TEST(ResponseConverterTest, ToSessionRequest_AllRequestOptions_PropagatedToSessi
params.temperature = 0.5f;
params.top_p = 0.95f;
params.max_output_tokens = 256;
params.presence_penalty = 0.25f;
params.frequency_penalty = 0.75f;
params.seed = 42;

ResponseTextConfig text_cfg;
Expand All @@ -857,12 +855,36 @@ TEST(ResponseConverterTest, ToSessionRequest_AllRequestOptions_PropagatedToSessi
expect_opt("temperature", std::to_string(0.5f));
expect_opt("top_p", std::to_string(0.95f));
expect_opt("max_output_tokens", "256");
expect_opt("presence_penalty", std::to_string(0.25f));
expect_opt("frequency_penalty", std::to_string(0.75f));
expect_opt("seed", "42");
expect_opt("guidance_type", "json_schema");
expect_opt("guidance_data", R"({"type":"object"})");
expect_opt("tool_choice", "required");

EXPECT_FALSE(tools_json.empty());
}

TEST(ResponseConverterTest, ToSessionRequest_RejectsNonzeroPenalties) {
for (const auto& [frequency, presence] :
{std::pair{0.75f, 0.0f}, std::pair{0.0f, 0.25f}, std::pair{-0.75f, 0.0f}, std::pair{0.0f, -0.25f}}) {
ResponseCreateParams params;
params.model = "test-model";
params.input = std::string("hello");
params.frequency_penalty = frequency;
params.presence_penalty = presence;

EXPECT_THROW(ToSessionRequest(params), fl::Exception);
}
}

TEST(ResponseConverterTest, ToSessionRequest_ZeroPenaltiesAreNoOps) {
ResponseCreateParams params;
params.model = "test-model";
params.input = std::string("hello");
params.presence_penalty = 0.0f;
params.frequency_penalty = 0.0f;

Request req = ToSessionRequest(params);

EXPECT_EQ(req.options.Find("presence_penalty"), nullptr);
EXPECT_EQ(req.options.Find("frequency_penalty"), nullptr);
}
15 changes: 15 additions & 0 deletions sdk_v2/cpp/test/sdk_api/chat_completions_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,21 @@ TEST_F(WebServiceIntegrationTest, ChatCompletionsMissingMessages) {
EXPECT_EQ(result->status, 400);
}

TEST_F(WebServiceIntegrationTest, ChatCompletionsRejectsNonzeroPenaltyBeforeInference) {
auto client = MakeClient();
json request_body = {
{"model", model_id()},
{"messages", json::array({{{"role", "user"}, {"content", "Hello"}}})},
{"frequency_penalty", 0.5},
};

auto result = client.Post("/v1/chat/completions", request_body.dump(), "application/json");

ASSERT_TRUE(result) << "HTTP request failed";
EXPECT_EQ(result->status, 400);
EXPECT_EQ(json::parse(result->body)["error"]["type"], "invalid_request_error");
}

TEST_F(WebServiceIntegrationTest, ChatCompletionsModelNotFound) {
auto client = MakeClient();
json request_body = {
Expand Down
2 changes: 0 additions & 2 deletions sdk_v2/cpp/test/sdk_api/reasoning_model_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ TEST_F(ReasoningFixture, SingleTurnStripsThinkBlock) {
RequestOptions opts;
opts.search.temperature = 0.0f;
opts.search.max_output_tokens = 1024;
opts.search.frequency_penalty = 1.2f;
request.SetOptions(opts);

Response response = session.ProcessRequest(request);
Expand Down Expand Up @@ -188,7 +187,6 @@ TEST_F(ReasoningFixture, MultiTurnContinuousDecoding) {
RequestOptions session_opts;
session_opts.search.temperature = 0.0f;
session_opts.search.max_output_tokens = 1024;
session_opts.search.frequency_penalty = 1.2f;
session.SetOptions(session_opts);

// Turn 1
Expand Down
15 changes: 15 additions & 0 deletions sdk_v2/cpp/test/sdk_api/responses_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,21 @@ TEST_F(WebServiceIntegrationTest, ResponsesMissingInput) {
EXPECT_EQ(result->status, 400);
}

TEST_F(WebServiceIntegrationTest, ResponsesRejectsNonzeroPenaltyBeforeInference) {
auto client = MakeClient();
json request_body = {
{"model", model_id()},
{"input", "Hello"},
{"presence_penalty", -0.3},
};

auto result = client.Post("/v1/responses", request_body.dump(), "application/json");

ASSERT_TRUE(result) << "HTTP request failed";
EXPECT_EQ(result->status, 400);
EXPECT_EQ(json::parse(result->body)["error"]["type"], "invalid_request_error");
}

TEST_F(WebServiceIntegrationTest, ResponsesModelNotFound) {
auto client = MakeClient();
json request_body = {
Expand Down
2 changes: 1 addition & 1 deletion sdk_v2/cs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ Tune generation parameters per client:
chatClient.Settings.Temperature = 0.7f;
chatClient.Settings.MaxTokens = 256;
chatClient.Settings.TopP = 0.9f;
chatClient.Settings.FrequencyPenalty = 0.5f;
chatClient.Settings.FrequencyPenalty = 0.0f; // Nonzero OpenAI penalties are not currently supported.
Comment thread
baijumeswani marked this conversation as resolved.
```

### Audio Transcription
Expand Down
4 changes: 2 additions & 2 deletions sdk_v2/cs/src/RequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ public sealed record SearchOptions
/// <summary>Maximum tokens to generate.</summary>
public int? MaxOutputTokens { get; init; }

/// <summary>Frequency penalty. Float [-2.0, 2.0].</summary>
/// <summary>Frequency penalty. Currently only the neutral value 0 is supported.</summary>
public float? FrequencyPenalty { get; init; }

/// <summary>Presence penalty. Float [-2.0, 2.0].</summary>
/// <summary>Presence penalty. Currently only the neutral value 0 is supported.</summary>
public float? PresencePenalty { get; init; }

/// <summary>Random seed for reproducibility.</summary>
Expand Down
Loading