Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
f2a21e9
Adding headers file
jonathan-buttner Feb 20, 2026
ebdb4a6
Using base class for task settings
jonathan-buttner Feb 23, 2026
bb0907e
Refactoring base class to allow for returning empty instance
jonathan-buttner Feb 23, 2026
8cedcd0
Fixing test failures
jonathan-buttner Feb 23, 2026
c4b846d
Fixing test
jonathan-buttner Feb 24, 2026
e3d466f
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Feb 24, 2026
bc2384e
Update docs/changelog/142969.yaml
jonathan-buttner Feb 24, 2026
e7a8fc2
[CI] Auto commit changes from spotless
Feb 24, 2026
56bf6dc
clean up and consolidating request header logic
jonathan-buttner Feb 24, 2026
1f64d15
Returning validation exception instead of xcontent when possible
jonathan-buttner Feb 24, 2026
4741068
Adding unwrapper utils tests
jonathan-buttner Feb 24, 2026
f1e92f7
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Feb 24, 2026
e55d554
Merge branch 'ia-azure-headers' of github.com:jonathan-buttner/elasti…
jonathan-buttner Feb 24, 2026
2eb28ce
Update x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/…
jonathan-buttner Feb 24, 2026
838ede1
Update x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/…
jonathan-buttner Feb 24, 2026
6242017
[CI] Auto commit changes from spotless
Feb 24, 2026
bfd1953
Address chatgpt feedback
jonathan-buttner Feb 24, 2026
3313304
Merge branch 'ia-azure-headers' of github.com:jonathan-buttner/elasti…
jonathan-buttner Feb 24, 2026
e13ba77
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Feb 25, 2026
4ae979a
Adding test for unwrap exception
jonathan-buttner Feb 25, 2026
569673e
Working refactor
jonathan-buttner Feb 26, 2026
f03baf4
Removing duplicate method
jonathan-buttner Feb 26, 2026
2d9f471
Adding tests for statefulvalue
jonathan-buttner Feb 26, 2026
efb0956
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Feb 26, 2026
7cb22c3
Using a single model reference
jonathan-buttner Feb 26, 2026
0b1cff0
Fixing test and renaming
jonathan-buttner Feb 27, 2026
80a2272
Fixing transport version
jonathan-buttner Feb 27, 2026
2b600d2
Fixing tests and refactoring validation
jonathan-buttner Feb 27, 2026
8ebbd3b
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Feb 27, 2026
710f8aa
[CI] Auto commit changes from spotless
Feb 27, 2026
83df8d5
Removing exception unwrap logic
jonathan-buttner Feb 27, 2026
179d7b2
Merge branch 'ia-azure-headers' of github.com:jonathan-buttner/elasti…
jonathan-buttner Feb 27, 2026
30d256d
Addressing feedback
jonathan-buttner Mar 2, 2026
fe979eb
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Mar 2, 2026
c75b7f4
[CI] Auto commit changes from spotless
Mar 2, 2026
e24b927
Allowing null in update api
jonathan-buttner Mar 2, 2026
3fe6c9e
Merge branch 'ia-azure-headers' of github.com:jonathan-buttner/elasti…
jonathan-buttner Mar 2, 2026
69b4732
Merge branch 'main' of github.com:elastic/elasticsearch into ia-azure…
jonathan-buttner Mar 3, 2026
ef2b448
Using orElse
jonathan-buttner Mar 3, 2026
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
5 changes: 5 additions & 0 deletions docs/changelog/142969.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
area: Inference
issues: []
pr: 142969
summary: "[Inference API] Add custom headers for Azure OpenAI Service"
type: enhancement
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
9294000
2 changes: 1 addition & 1 deletion server/src/main/resources/transport/upper_bounds/9.4.csv
Original file line number Diff line number Diff line change
@@ -1 +1 @@
shard_heap_usage_in_cluster_info,9293000
inference_azure_openai_task_settings_headers,9294000
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.inference.common.parser;

import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentFragment;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Map;
import java.util.Objects;

import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;
import static org.elasticsearch.xpack.inference.services.ServiceUtils.removeNullValues;
import static org.elasticsearch.xpack.inference.services.ServiceUtils.validateMapStringValues;

public record Headers(Map<String, String> headersMap) implements ToXContentFragment, Writeable {

private static final ParseField HEADERS = new ParseField("headers");

public static final Headers EMPTY_INSTANCE = new Headers(Map.of());

public static <Value, Context> void initParser(ConstructingObjectParser<Value, Context> parser) {
parser.declareObjectOrNull(optionalConstructorArg(), (p, c) -> p.mapOrdered(), null, HEADERS);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the nullValue argument here be Map.of() instead of null? If you set "headers": null in the JSON, we get an NPE right now.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep switch it and added a test 👍

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we still provide Map.of() as the null value?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seeing the comment right after, there is also a p.mapStrings() that parses the field as Map<String, String>. See an example usage of that in DatafeedConfig which also has headers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typical way of doing this would be to have a static parser built here. Then, instead of calling initParser, we can call declareObject(...) and pass the parser in as argument.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's sync on this tomorrow 👍

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also added a sentinel value so we can identify when the user explicitly sets the value to null.

}

@SuppressWarnings("unchecked")
public static Headers create(Object arg) {
if (arg == null) {
return null;
}
Comment on lines +69 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an argument for returning EMPTY_INSTANCE here? I think it would allow us to make the headers field on AzureOpenAiTaskSettings not @nullable, so we'd have to check .isEmpty() instead of null in a few places, but it would mean that we wouldn't end up potentially creating a AzureOpenAiTaskSettings with a null user and empty headers, which could happen at the moment. Alternately, we could check if headers is null or empty in the places we're currently just checking if it's null.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, now that I'm thinking about this more I wonder how a user would use the _update endpoint to remove headers 🤔

If we removed null as a valid state then I don't think we'd be able to differentiate between an update action with no headers and an update action to remove the headers.

I think we can use declareObjectOrNull and if the user explicitly set headers: null (so the parser sees a value_null we can return empty headers which can wipe out the headers. If the user does not specify headers then we'll see it as null it just use the existing task setting headers.

I agree though, it'd be nice to have a single state with only empty headers. Let me know if you can think of a way to handle that. I suppose we could use an enum as well 🤔

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok give this another look, I think it's in a better state 😅


var validationException = new ValidationException();

removeNullValues((Map<String, Object>) arg);

var stringHeaders = validateMapStringValues(
(Map<String, String>) arg,
HEADERS.getPreferredName(),
validationException,
false,
Map.of()
);

if (validationException.validationErrors().isEmpty() == false) {
throw validationException;
}

if (stringHeaders.isEmpty()) {
return EMPTY_INSTANCE;
}

return new Headers(stringHeaders);
}

public Headers {
Objects.requireNonNull(headersMap, "headers map is required");
}

public Headers(StreamInput in) throws IOException {
this(in.readImmutableMap(StreamInput::readString, StreamInput::readString));
}

public boolean isEmpty() {
return headersMap.isEmpty();
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.field(HEADERS.getPreferredName(), headersMap);
return builder;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeMap(headersMap, StreamOutput::writeString, StreamOutput::writeString);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.inference.ModelConfigurations;
import org.elasticsearch.xpack.inference.services.azureopenai.embeddings.AzureOpenAiEmbeddingsRequestTaskSettings;

import java.util.Map;

Expand All @@ -31,7 +30,7 @@ public record AzureAiStudioEmbeddingsRequestTaskSettings(@Nullable String user)
* does not throw an error.
*
* @param map the settings received from a request
* @return a {@link AzureOpenAiEmbeddingsRequestTaskSettings}
* @return a {@link AzureAiStudioEmbeddingsRequestTaskSettings}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was referencing the wrong class

*/
public static AzureAiStudioEmbeddingsRequestTaskSettings fromMap(Map<String, Object> map) {
if (map.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,9 @@ public void parseRequestConfig(

throwIfNotEmptyMap(config, NAME);
throwIfNotEmptyMap(serviceSettingsMap, NAME);
throwIfNotEmptyMap(taskSettingsMap, NAME);
// The new approach is to leverage an ConstructingObjectParser to parse the task settings, this does not mutate the original map
Comment thread
jonathan-buttner marked this conversation as resolved.
Outdated
// so we don't need to check if it's empty after parsing. The ConstructingObjectParser will throw an exception if there are any
// unrecognized fields in the task settings

parsedModelListener.onResponse(model);
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@ public class AzureOpenAiServiceFields {
public static final String DEPLOYMENT_ID = "deployment_id";
public static final String API_VERSION = "api_version";
public static final String USER = "user";
public static final String HEADERS = "headers";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.inference.services.azureopenai;

import org.elasticsearch.TransportVersion;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.ValidationException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.inference.ModelConfigurations;
import org.elasticsearch.inference.TaskSettings;
import org.elasticsearch.xcontent.ConstructingObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParserConfiguration;
import org.elasticsearch.xcontent.json.JsonXContent;
import org.elasticsearch.xpack.core.inference.InferenceUtils;
import org.elasticsearch.xpack.inference.common.parser.Headers;
import org.elasticsearch.xpack.inference.services.ConfigurationParseContext;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg;

/**
* Base class for Azure OpenAI task settings (embeddings and completion). Holds optional user and optional
* custom HTTP headers via {@link Headers}.
*/
public abstract class AzureOpenAiTaskSettings<T extends AzureOpenAiTaskSettings<T>> implements TaskSettings {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This generally follows the same pattern that we started in OpenAiTaskSettings except this class leverages a ConstructingObjectParser to parse out the fields. Since we'd like to move in that direction, hopefully this will make that easier in the future.


private static final Settings EMPTY_SETTINGS = new Settings(null, null);

protected static final TransportVersion INFERENCE_AZURE_OPENAI_TASK_SETTINGS_HEADERS = TransportVersion.fromName(
"inference_azure_openai_task_settings_headers"
);

protected record Settings(@Nullable String user, @Nullable Headers headers) {}

private static final ConstructingObjectParser<Settings, Void> STORAGE_PARSER = createParser(true);
private static final ConstructingObjectParser<Settings, Void> REQUEST_PARSER = createParser(false);

private static ConstructingObjectParser<Settings, Void> createParser(boolean ignoreUnknownFields) {
ConstructingObjectParser<Settings, Void> constructingObjectParser = new ConstructingObjectParser<>(
"azure_openai_task_settings_parser",
ignoreUnknownFields,
args -> createSettings((String) args[0], Headers.create(args[1]))
);

constructingObjectParser.declareString(optionalConstructorArg(), new ParseField(AzureOpenAiServiceFields.USER));
Headers.initParser(constructingObjectParser);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apply the header parsing logic to the constructing object parser.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is where we would call parser.declareObject(optionalConstructorArg(), Headers.PARSER, new ParseField("headers") or something

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I was struggling with that. The issue I see is that headers is only a map so there isn't another level for parsing. For example if we had this:

{
  "headers: {
    "some_other_field": {
      ...
    }
  }
}

Then the headers class could have a parser that explicitly looks for some_other_field. But headers just wants it as a map. Like imagine headers was a string with some special parsing logic around it. How would we leverage a parser that was wrapping a string.

In the examples I've seen we typically declare another parser if it encapsulates a whole object.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yes, of course. Makes sense 👍


return constructingObjectParser;
}

private static Settings createSettings(@Nullable String user, @Nullable Headers headers) {
if (user == null && headers == null) {
return EMPTY_SETTINGS;
}
return new Settings(user, headers);
}

protected abstract static class Factory<T> {
private T emptyInstance;

protected abstract T create(@Nullable String user, @Nullable Headers headers);

protected abstract T createEmptyInstance();

public T emptySettings() {
// Ideally we'd be able to pass the empty instance in via the Factory constructor, but since the empty instance relies on the
// factory to be created, we have to lazily create it here. The empty instance will call the AzureOpenAiTaskSettings
// constructor with the factory. If we don't do it this way we end up getting an NPE in the constructor because the factory
// hasn't finished initialization yet.
if (emptyInstance == null) {
emptyInstance = createEmptyInstance();
}
return emptyInstance;
}
}

protected static <T extends AzureOpenAiTaskSettings<T>> T parseSettingsFromMap(
Map<String, Object> map,
ConfigurationParseContext configurationParseContext,
Factory<T> factory
) {
if (map.isEmpty()) {
return factory.emptySettings();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option I looked into was to use an abstract method on the AzureOpenAiTaskSettings class to handle "creating" the right type of an empty instance. The problem is that we're in a static context here and to avoid having to construct a new object entirely we need to do it in a static method.

OpenAiTaskSettings doesn't leverage empty instances and requires an object to be created each time.

}

try {
try (
var xContent = XContentBuilder.builder(JsonXContent.jsonXContent).map(map);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This converts a Map to json and then parses it.

var parser = JsonXContent.jsonXContent.createParser(XContentParserConfiguration.EMPTY, Strings.toString(xContent))
) {
Settings createdSettings;

if (configurationParseContext == ConfigurationParseContext.REQUEST) {
createdSettings = REQUEST_PARSER.parse(parser, null);
validateSettings(createdSettings);
} else {
createdSettings = STORAGE_PARSER.parse(parser, null);
}

return factory.create(createdSettings.user(), createdSettings.headers());
}
} catch (IOException e) {
throw new IllegalArgumentException("Failed to parse Azure OpenAI task settings", e);
}
}

private static void validateSettings(Settings settings) {
var validationException = new ValidationException();

if (settings.user() != null && settings.user().isEmpty()) {
validationException.addValidationError(
InferenceUtils.mustBeNonEmptyString(AzureOpenAiServiceFields.USER, ModelConfigurations.TASK_SETTINGS)
);
throw validationException;
}
}

private final Settings taskSettings;
private final Factory<T> factory;

protected AzureOpenAiTaskSettings(@Nullable String user, @Nullable Headers headers, Factory<T> factory) {
this(createSettings(user, headers), factory);
}

protected AzureOpenAiTaskSettings(Settings taskSettings, Factory<T> factory) {
this.taskSettings = Objects.requireNonNull(taskSettings);
this.factory = Objects.requireNonNull(factory);
}

protected AzureOpenAiTaskSettings(StreamInput in, Factory<T> factory) throws IOException {
this(readTaskSettingsFromStream(in), factory);
}

private static Settings readTaskSettingsFromStream(StreamInput in) throws IOException {
var user = in.readOptionalString();
var headers = in.getTransportVersion().supports(INFERENCE_AZURE_OPENAI_TASK_SETTINGS_HEADERS)
? in.readOptionalWriteable(Headers::new)
: null;
return createSettings(user, headers);
}

public String user() {
return taskSettings.user();
}

public Headers headers() {
return taskSettings.headers();
}

@Override
public boolean isEmpty() {
var user = taskSettings.user();
var headers = taskSettings.headers();
return (user == null || user.isEmpty()) && (headers == null || headers.isEmpty());
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (taskSettings.user() != null) {
builder.field(AzureOpenAiServiceFields.USER, taskSettings.user());
}
if (taskSettings.headers() != null) {
taskSettings.headers().toXContent(builder, params);
}
Comment on lines +213 to 215

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might cause problems, since we parse the user field as StatefulValue.of("") if it's specified as the empty string, but if it's not present, then we parse it as StatefulValue.undefined(). Not writing the user field if it's the empty string will cause it to be changed from the present but empty to undefined when we parse it back.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It shouldn't really be possible to get in a scenario where user is an empty string. We throw a validation exception if it is when in request context. The isEmpty() check is just try to be extra safe.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, that should be fine then

builder.endObject();
return builder;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AzureOpenAiTaskSettings<?> that = (AzureOpenAiTaskSettings<?>) o;
return Objects.equals(taskSettings, that.taskSettings);
}

@Override
public int hashCode() {
return Objects.hash(taskSettings);
}

@Override
public T updatedTaskSettings(Map<String, Object> newSettings) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't try to return an empty instance. To return an empty instance we'd need to cast to T I think. I don't think it's worth it because in the flow of a request including task settings the caller will check if the task settings are empty and return the same model. This will also be called in the update api flow but I don't think that needs to be very memory conscious.

If you have other ideas I'm open to improving this though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with this as it is, but one option would be to have an abstract emptyInstance() method on this class and then add

        if (userToUse.isUndefined() && headersToUse.value().isUndefined()) {
            return emptyInstance();
        }

at the end of this method.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, we actually have this via the factory already 🙌 so I'll add the if block.

var updated = parseSettingsFromMap(new HashMap<>(newSettings), ConfigurationParseContext.REQUEST, factory);
var userToUse = updated.user() == null ? taskSettings.user() : updated.user();
var headersToUse = updated.headers() == null ? taskSettings.headers() : updated.headers();
return factory.create(userToUse, headersToUse);
}

@Override
public TransportVersion getMinimalSupportedVersion() {
assert false : "should never be called when supportsVersion is used";
return INFERENCE_AZURE_OPENAI_TASK_SETTINGS_HEADERS;
}

@Override
public boolean supportsVersion(TransportVersion version) {
return INFERENCE_AZURE_OPENAI_TASK_SETTINGS_HEADERS.supports(version);
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeOptionalString(user());
if (out.getTransportVersion().supports(INFERENCE_AZURE_OPENAI_TASK_SETTINGS_HEADERS)) {
out.writeOptionalWriteable(headers());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ public static AzureOpenAiCompletionModel of(AzureOpenAiCompletionModel model, Ma
return model;
}

var requestTaskSettings = AzureOpenAiCompletionRequestTaskSettings.fromMap(taskSettings);
return new AzureOpenAiCompletionModel(model, AzureOpenAiCompletionTaskSettings.of(model.getTaskSettings(), requestTaskSettings));
return new AzureOpenAiCompletionModel(model, model.getTaskSettings().updatedTaskSettings(taskSettings));
}

public AzureOpenAiCompletionModel(
Expand All @@ -47,7 +46,7 @@ public AzureOpenAiCompletionModel(
taskType,
service,
AzureOpenAiCompletionServiceSettings.fromMap(serviceSettings, context),
AzureOpenAiCompletionTaskSettings.fromMap(taskSettings),
AzureOpenAiCompletionTaskSettings.fromMap(taskSettings, context),
AzureOpenAiSecretSettings.fromMap(secrets)
);
}
Expand Down
Loading