Skip to main content
Version: 2.1

Building a Custom Provider

If you need to integrate with an LLM service that isn't supported out of the box, you can create a custom provider and register it with the SDK.

Platform support

The approach depends on your server platform:

If your target service exposes an OpenAI-compatible API, you can also use the Custom Endpoints approach with the OpenAI provider on any platform.

Step 1: Create the Provider Class

Implement the IAIProvider interface:

using Reveal.Sdk.AI;

public class MyCustomProvider : IAIProvider
{
private readonly string _apiKey;
private readonly string _model;

public MyCustomProvider(string apiKey, string model)
{
_apiKey = apiKey;
_model = model;
}

public async Task<ProviderResponse> SendPromptAsync(
ProviderRequest request,
CancellationToken cancellationToken = default)
{
var model = request.Model ?? _model;

// Call your LLM service here
var result = await CallMyLLMService(request.Prompt, model, cancellationToken);

return new ProviderResponse
{
Content = result.Text,
FinishReason = FinishReason.Stop,
Usage = new TokenUsage
{
InputTokens = result.InputTokens,
OutputTokens = result.OutputTokens
},
Model = model
};
}

private async Task<MyLLMResult> CallMyLLMService(
string prompt, string model, CancellationToken cancellationToken)
{
// Your LLM integration logic here
throw new NotImplementedException();
}
}

IAIProvider Interface

The IAIProvider interface defines a single method:

public interface IAIProvider
{
Task<ProviderResponse> SendPromptAsync(
ProviderRequest request,
CancellationToken cancellationToken = default);
}

ProviderRequest

The request object contains:

PropertyTypeDescription
PromptstringThe prompt to send to the LLM.
IntentstringThe intent of the request (e.g., "default").
Modelstring?Optional model override. If null, use the provider's default model.

ProviderResponse

Your provider must return a ProviderResponse:

PropertyTypeDescription
ContentstringThe generated text content.
FinishReasonFinishReasonWhy the generation stopped: Stop, Length, or ContentFilter.
UsageTokenUsage?Optional token usage information.
Modelstring?The model that was actually used.

Step 2: Register the Provider

Use the AddProvider method on IRevealAIBuilder to register your custom provider:

builder.Services.AddRevealAI()
.AddProvider<MyCustomProvider>("my-custom", sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
return new MyCustomProvider(
apiKey: config["RevealAI:MyCustom:ApiKey"],
model: config["RevealAI:MyCustom:Model"] ?? "default-model"
);
});

The first parameter is the provider key - a unique string identifier used to resolve the provider. The second parameter is a factory function that receives the IServiceProvider for dependency injection.

Step 3: Set as Default (Optional)

To make your custom provider the default:

appsettings.json
{
"RevealAI": {
"DefaultProvider": "my-custom"
}
}

Complete Example

Here's a complete example with configuration binding:

MyCustomProvider.cs
using Reveal.Sdk.AI;

public class MyCustomOptions
{
public string ApiKey { get; set; } = string.Empty;
public string Model { get; set; } = "default-model";
public int MaxTokens { get; set; } = 4096;
}

public class MyCustomProvider : IAIProvider
{
private readonly MyCustomOptions _options;

public MyCustomProvider(MyCustomOptions options)
{
_options = options ?? throw new ArgumentNullException(nameof(options));
}

public async Task<ProviderResponse> SendPromptAsync(
ProviderRequest request,
CancellationToken cancellationToken = default)
{
var model = request.Model ?? _options.Model;

// Your LLM integration logic
var responseText = await CallYourService(request.Prompt, model, cancellationToken);

return new ProviderResponse
{
Content = responseText,
FinishReason = FinishReason.Stop,
Model = model
};
}

private Task<string> CallYourService(
string prompt, string model, CancellationToken cancellationToken)
{
// Implement your LLM service call
throw new NotImplementedException();
}
}
Program.cs
using Reveal.Sdk;
using Reveal.Sdk.AI;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers().AddReveal();

builder.Services.AddRevealAI()
.AddProvider<MyCustomProvider>("my-custom", sp =>
{
var config = sp.GetRequiredService<IConfiguration>();
var options = new MyCustomOptions
{
ApiKey = config["RevealAI:MyCustom:ApiKey"] ?? "",
Model = config["RevealAI:MyCustom:Model"] ?? "default-model",
MaxTokens = int.Parse(config["RevealAI:MyCustom:MaxTokens"] ?? "4096")
};
return new MyCustomProvider(options);
});

var app = builder.Build();
app.MapControllers();
app.Run();

Using with Built-in Providers

Custom providers can be registered alongside built-in providers:

builder.Services.AddRevealAI()
.AddOpenAI()
.AddProvider<MyCustomProvider>("my-custom", sp =>
{
return new MyCustomProvider(/* ... */);
});

Java: Custom Providers via Callbacks

To implement a custom provider, register a callback with the RevealAIPlugin that receives the prompt request as JSON and returns the response as JSON. The plugin bridges the callback to the underlying AI engine.

Registration Conventions

Callbacks are registered by key in the Map<String, RevealPluginCallback> you pass to RevealAIPlugin.withOptions(options, callbacks):

KeyBehavior
aiProviderRegisters a single custom provider. It is selectable under the reserved name CustomAIProvider.
aiProvider:<key>Registers a named provider. <key> (e.g. my-custom-provider) becomes a selectable provider/model name. You can register multiple named providers.
Reserved name

CustomAIProvider is reserved. Passing aiProvider:CustomAIProvider throws an IllegalArgumentException.

Callback Signature

A provider callback implements RevealPluginCallback:

@FunctionalInterface
public interface RevealPluginCallback {
CompletableFuture<String> invoke(IRVUserContext userContext, String message);
}
  • message - a JSON-serialized request with the following fields:

    FieldTypeDescription
    promptstringThe prompt to send to the LLM.
    intentstringThe intent of the request (e.g. "default").
    modelstring | nullOptional model override. If null, use your provider's default.
  • Return value - a JSON-serialized response with the following fields:

    FieldTypeDescription
    contentstringThe generated text content.
    finishReasonstringWhy generation stopped: Stop, Length, or ContentFilter.
    usageobject | nullOptional token usage: { "inputTokens": <int>, "outputTokens": <int> }.
    modelstring | nullThe model that actually served the request.

The response JSON uses camelCase keys and finishReason is a string value such as "Stop".

Example

Application.java
Map<String, RevealPluginCallback> callbacks = new LinkedHashMap<>();

callbacks.put("aiProvider:my-custom-provider", (userContext, message) -> {
// 'message' is a JSON ProviderRequest: { "prompt": "...", "intent": "default", "model": null }
// Call your LLM service here, then return a JSON ProviderResponse.
return CompletableFuture.completedFuture(
"{\"content\":\"Hello from my custom Java AI provider\","
+ "\"finishReason\":\"Stop\","
+ "\"usage\":{\"inputTokens\":1,\"outputTokens\":1},"
+ "\"model\":\"my-custom-provider\"}");
});

RevealPlugin aiPlugin = RevealAIPlugin.withOptions(aiPluginOptions, callbacks);

IRevealServer server = new RevealServerBuilder()
.addPlugin(aiPlugin)
// ...
.build();

To make your custom provider the default, set its key as the default provider:

RevealAIPluginOptions aiPluginOptions = new RevealAIPluginOptions(
"my-custom-provider", // defaultProvider
metadataCatalogPath,
null, null,
Map.of("settings", aiSettings));
Production callbacks

Real callbacks should parse the incoming JSON (for example with Jackson) to read the prompt/model, call your LLM asynchronously, and serialize the response back. Returning null or an empty string causes the request to fail.

Node.js: Custom Providers via Callbacks

You supply a callback (or a map of named callbacks) to the AI plugin options; each callback receives the request as a JSON string and returns the response as a JSON string.

Registration Conventions

RegistrationBehavior
A single aiProvider functionRegisters one custom provider, selectable under the reserved name Node.
A map of { "<key>": function }Registers named providers. Each <key> becomes a selectable provider/model name.

Request and Response Contract

The request and response payloads are identical to the Java contract:

  • Input - a JSON-serialized request: { "prompt": "...", "intent": "default", "model": null }.
  • Return value - a JSON-serialized response with camelCase keys: content, finishReason (Stop | Length | ContentFilter), optional usage ({ "inputTokens", "outputTokens" }), and optional model.

Example

index.js
const aiProviders = {
"my-custom-provider": async (userContext, message) => {
// 'message' is a JSON ProviderRequest.
// Call your LLM service here, then return a JSON ProviderResponse string.
return JSON.stringify({
content: "Hello from my custom Node.js AI provider",
finishReason: "Stop",
usage: { inputTokens: 1, outputTokens: 1 },
model: "my-custom-provider"
});
}
};

// Pass aiProviders when configuring the Reveal AI plugin options.