Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add inference processor #6031

Merged
merged 1 commit into from
Oct 21, 2021
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
9 changes: 8 additions & 1 deletion src/Nest/Ingest/ProcessorFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ internal class ProcessorFormatter : IJsonFormatter<IProcessor>
{ "fingerprint", 34 },
{ "community_id", 35 },
{ "network_direction", 36 },
{ "registered_domain", 37 }
{ "registered_domain", 37 },
{ "inference", 38 }
};

public IProcessor Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver)
Expand Down Expand Up @@ -185,6 +186,9 @@ public IProcessor Deserialize(ref JsonReader reader, IJsonFormatterResolver form
case 37:
processor = Deserialize<RegisteredDomainProcessor>(ref reader, formatterResolver);
break;
case 38:
processor = Deserialize<InferenceProcessor>(ref reader, formatterResolver);
break;
}
}
else
Expand Down Expand Up @@ -320,6 +324,9 @@ public void Serialize(ref JsonWriter writer, IProcessor value, IJsonFormatterRes
case "registered_domain":
Serialize<IRegisteredDomainProcessor>(ref writer, value, formatterResolver);
break;
case "inference":
Serialize<IInferenceProcessor>(ref writer, value, formatterResolver);
break;
default:
var formatter = DynamicObjectResolver.ExcludeNullCamelCase.GetFormatter<IProcessor>();
formatter.Serialize(ref writer, value, formatterResolver);
Expand Down
223 changes: 223 additions & 0 deletions src/Nest/Ingest/Processors/InferenceProcessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
// Licensed to Elasticsearch B.V under one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using Elasticsearch.Net;
using Elasticsearch.Net.Utf8Json;
using Nest;

namespace Nest
{
/// <summary>
/// Uses a pre-trained data frame analytics model to infer against the data that is being ingested in the pipeline.
/// <para />
/// Available in Elasticsearch 7.6.0+ with at least basic license.
/// </summary>
[InterfaceDataContract]
public interface IInferenceProcessor : IProcessor
{
/// <summary>
/// The ID of the model to load and infer against.
/// </summary>
[DataMember(Name = "model_id")]
string ModelId { get; set; }

/// <summary>
/// Field added to incoming documents to contain results objects.
/// </summary>
[DataMember(Name ="target_field")]
Field TargetField { get; set; }

/// <summary>
/// Maps the document field names to the known field names of the model.
/// </summary>
[DataMember(Name = "field_mappings")]
IDictionary<Field, Field> FieldMappings { get; set; }

/// <summary>
/// Contains the inference type and its options.
/// </summary>
[DataMember(Name = "inference_config")]
IInferenceConfig InferenceConfig { get; set; }
}

/// <inheritdoc cref="IInferenceProcessor" />
public class InferenceProcessor : ProcessorBase, IInferenceProcessor
{
/// <inheritdoc />
public string ModelId { get; set; }

/// <inheritdoc />
public Field TargetField { get; set; }

/// <inheritdoc />
public IDictionary<Field, Field> FieldMappings { get; set; }

/// <inheritdoc />
public IInferenceConfig InferenceConfig { get; set; }

protected override string Name => "inference";
}

/// <inheritdoc cref="IInferenceProcessor" />
public class InferenceProcessorDescriptor<T>
: ProcessorDescriptorBase<InferenceProcessorDescriptor<T>, IInferenceProcessor>, IInferenceProcessor
where T : class
{
protected override string Name => "inference";

Field IInferenceProcessor.TargetField { get; set; }
string IInferenceProcessor.ModelId { get; set; }
IInferenceConfig IInferenceProcessor.InferenceConfig { get; set; }
IDictionary<Field, Field> IInferenceProcessor.FieldMappings { get; set; }

/// <inheritdoc cref="IInferenceProcessor.TargetField" />
public InferenceProcessorDescriptor<T> TargetField(Field field) => Assign(field, (a, v) => a.TargetField = v);

/// <inheritdoc cref="IInferenceProcessor.TargetField" />
public InferenceProcessorDescriptor<T> TargetField<TValue>(Expression<Func<T, TValue>> objectPath) =>
Assign(objectPath, (a, v) => a.TargetField = v);

/// <inheritdoc cref="IInferenceProcessor.ModelId" />
public InferenceProcessorDescriptor<T> ModelId(string modelId) =>
Assign(modelId, (a, v) => a.ModelId = v);

/// <inheritdoc cref="IInferenceProcessor.ModelId" />
public InferenceProcessorDescriptor<T> InferenceConfig(Func<InferenceConfigDescriptor<T>, IInferenceConfig> selector) =>
Assign(selector, (a, v) => a.InferenceConfig = v.InvokeOrDefault(new InferenceConfigDescriptor<T>()));

/// <inheritdoc cref="IInferenceProcessor.FieldMappings" />
public InferenceProcessorDescriptor<T> FieldMappings(Func<FluentDictionary<Field, Field>, FluentDictionary<Field, Field>> selector = null) =>
Assign(selector, (a, v) => a.FieldMappings = v.InvokeOrDefault(new FluentDictionary<Field, Field>()));
}

[ReadAs(typeof(InferenceConfig))]
public interface IInferenceConfig
{

[DataMember(Name = "regression")]
IRegressionInferenceConfig Regression { get; set; }

[DataMember(Name = "classification")]
IClassificationInferenceConfig Classification { get; set; }
}

public class InferenceConfig
: IInferenceConfig
{
public IRegressionInferenceConfig Regression { get; set; }

public IClassificationInferenceConfig Classification { get; set; }
}

public class InferenceConfigDescriptor<T> : DescriptorBase<InferenceConfigDescriptor<T>, IInferenceConfig>, IInferenceConfig
{
IRegressionInferenceConfig IInferenceConfig.Regression { get; set; }
IClassificationInferenceConfig IInferenceConfig.Classification { get; set; }

public InferenceConfigDescriptor<T> Regression(Func<RegressionInferenceConfigDescriptor<T>, IRegressionInferenceConfig> selector) =>
Assign(selector, (a, v) => a.Regression = v.InvokeOrDefault(new RegressionInferenceConfigDescriptor<T>()));

public InferenceConfigDescriptor<T> Classification(Func<ClassificationInferenceConfigDescriptor<T>, IClassificationInferenceConfig> selector) =>
Assign(selector, (a, v) => a.Classification = v.InvokeOrDefault(new ClassificationInferenceConfigDescriptor<T>()));
}

[ReadAs(typeof(RegressionInferenceConfig))]
public interface IRegressionInferenceConfig
{
/// <summary>
/// Specifies the field to which the inference prediction is written. Defaults to <c>predicted_value</c>.
/// </summary>
[DataMember(Name = "results_field")]
Field ResultsField { get; set; }
}

public class RegressionInferenceConfig : IRegressionInferenceConfig
{
/// <summary>
/// Specifies the field to which the inference prediction is written. Defaults to <c>predicted_value</c>.
/// </summary>
public Field ResultsField { get; set; }
}

public class RegressionInferenceConfigDescriptor<T>
: DescriptorBase<RegressionInferenceConfigDescriptor<T>, IRegressionInferenceConfig>, IRegressionInferenceConfig
{
Field IRegressionInferenceConfig.ResultsField { get; set; }

/// <inheritdoc cref="IRegressionInferenceConfig.ResultsField" />
public RegressionInferenceConfigDescriptor<T> ResultsField(Field field) => Assign(field, (a, v) => a.ResultsField = v);

/// <inheritdoc cref="IRegressionInferenceConfig.ResultsField" />
public RegressionInferenceConfigDescriptor<T> ResultsField<TValue>(Expression<Func<T, TValue>> objectPath) =>
Assign(objectPath, (a, v) => a.ResultsField = v);
}

[ReadAs(typeof(ClassificationInferenceConfig))]
public interface IClassificationInferenceConfig
{
/// <summary>
/// Specifies the field to which the inference prediction is written. Defaults to <c>predicted_value</c>.
/// </summary>
[DataMember(Name = "results_field")]
Field ResultsField { get; set; }

/// <summary>
/// Specifies the number of top class predictions to return. Defaults to <c>0</c>.
/// </summary>
[DataMember(Name = "num_top_classes")]
int? NumTopClasses { get; set; }

/// <summary>
/// Specifies the field to which the top classes are written. Defaults to <c>top_classes</c>.
/// </summary>
[DataMember(Name = "top_classes_results_field")]
Field TopClassesResultsField { get; set; }
}

public class ClassificationInferenceConfig : IClassificationInferenceConfig
{
/// <summary>
/// Specifies the field to which the inference prediction is written. Defaults to <c>predicted_value</c>.
/// </summary>
public Field ResultsField { get; set; }

/// <summary>
/// Specifies the number of top class predictions to return. Defaults to <c>0</c>.
/// </summary>
public int? NumTopClasses { get; set; }

/// <summary>
/// Specifies the field to which the top classes are written. Defaults to <c>top_classes</c>.
/// </summary>
public Field TopClassesResultsField { get; set; }
}

public class ClassificationInferenceConfigDescriptor<T> : DescriptorBase<ClassificationInferenceConfigDescriptor<T>, IClassificationInferenceConfig>, IClassificationInferenceConfig
{
Field IClassificationInferenceConfig.ResultsField { get; set; }
int? IClassificationInferenceConfig.NumTopClasses { get; set; }
Field IClassificationInferenceConfig.TopClassesResultsField { get; set; }

/// <inheritdoc cref="IClassificationInferenceConfig.ResultsField" />
public ClassificationInferenceConfigDescriptor<T> ResultsField(Field field) => Assign(field, (a, v) => a.ResultsField = v);

/// <inheritdoc cref="IClassificationInferenceConfig.ResultsField" />
public ClassificationInferenceConfigDescriptor<T> ResultsField<TValue>(Expression<Func<T, TValue>> objectPath) =>
Assign(objectPath, (a, v) => a.ResultsField = v);

/// <inheritdoc cref="IClassificationInferenceConfig.NumTopClasses" />
public ClassificationInferenceConfigDescriptor<T> NumTopClasses(int? numTopClasses) => Assign(numTopClasses, (a, v) => a.NumTopClasses = v);

/// <inheritdoc cref="IClassificationInferenceConfig.TopClassesResultsField" />
public ClassificationInferenceConfigDescriptor<T> TopClassesResultsField(Field field) => Assign(field, (a, v) => a.TopClassesResultsField = v);

/// <inheritdoc cref="IClassificationInferenceConfig.TopClassesResultsField" />
public ClassificationInferenceConfigDescriptor<T> TopClassesResultsField<TValue>(Expression<Func<T, TValue>> objectPath) =>
Assign(objectPath, (a, v) => a.TopClassesResultsField = v);
}
}
4 changes: 4 additions & 0 deletions src/Nest/Ingest/ProcessorsDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,9 @@ public ProcessorsDescriptor NetworkDirection<T>(Func<NetworkDirectionProcessorDe
/// <inheritdoc cref="IRegisteredDomainProcessor"/>
public ProcessorsDescriptor RegisteredDomain<T>(Func<RegisteredDomainProcessorDescriptor<T>, IRegisteredDomainProcessor> selector) where T : class =>
Assign(selector, (a, v) => a.AddIfNotNull(v?.Invoke(new RegisteredDomainProcessorDescriptor<T>())));

/// <inheritdoc cref="IInferenceProcessor"/>
public ProcessorsDescriptor Inference<T>(Func<InferenceProcessorDescriptor<T>, IInferenceProcessor> selector) where T : class =>
Assign(selector, (a, v) => a.AddIfNotNull(v?.Invoke(new InferenceProcessorDescriptor<T>())));
}
}
52 changes: 52 additions & 0 deletions tests/Tests/Ingest/ProcessorAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,58 @@ public class Pipeline : ProcessorAssertion
public override string Key => "pipeline";
}

[SkipVersion("<7.6.0", "Introduced in Elasticsearch 7.6.0+")]
public class Inference : ProcessorAssertion
{
public override Func<ProcessorsDescriptor, IPromise<IList<IProcessor>>> Fluent => d => d
.Inference<Project>(c => c
.TargetField(p => p.Name)
.ModelId("model_id")
.FieldMappings()
.InferenceConfig(i => i
.Classification(cc => cc
.ResultsField("results")
.NumTopClasses(10)
.TopClassesResultsField("topClasses")
)
)
);

public override IProcessor Initializer => new InferenceProcessor
{
TargetField = "name",
ModelId = "model_id",
FieldMappings = new Dictionary<Field, Field>(),
InferenceConfig = new InferenceConfig
{
Classification = new ClassificationInferenceConfig
{
ResultsField = "results",
NumTopClasses = 10,
TopClassesResultsField = "topClasses"
}
}
};

public override object Json => new
{
target_field = "name",
model_id = "model_id",
field_mappings = new {},
inference_config = new
{
classification = new
{
results_field = "results",
num_top_classes = 10,
top_classes_results_field = "topClasses"
}
}
};

public override string Key => "inference";
}

[SkipVersion("<7.11.0", "Uses URI parts which was introduced in 7.11.0")]
public class UriParts : ProcessorAssertion
{
Expand Down