Skip to main content

Consuming AI Services via Code

This guide explains how to use the CrestApps AI services programmatically in your Orchard Core modules.

Service Overview

The AI infrastructure provides several key services at different abstraction levels:

ServiceLevelPurpose
IAIClientFactoryLowCreates raw AI clients (IChatClient, IEmbeddingGenerator, IImageGenerator)
IAICompletionServiceMidSends messages to AI and returns completions, with handler pipeline support
IAICompletionContextBuilderMidBuilds AICompletionContext from a resource (profile or chat interaction)
IOrchestratorHighFull orchestration runtime with planning, tool scoping, and execution loops
IOrchestrationContextBuilderHighBuilds OrchestrationContext from a resource for use with an orchestrator

IAIClientFactory

The lowest-level service. Use it when you need direct access to AI clients without any orchestration or context management. It creates standard Microsoft.Extensions.AI clients.

public class MyService
{
private readonly IAIClientFactory _clientFactory;

public MyService(IAIClientFactory clientFactory)
{
_clientFactory = clientFactory;
}

public async Task<string> GetCompletionAsync()
{
// Create a chat client for a specific provider, connection, and model
var chatClient = await _clientFactory.CreateChatClientAsync(
providerName: "OpenAI",
connectionName: "default",
deploymentName: "gpt-4o-mini");

var response = await chatClient.GetResponseAsync("Hello, world!");
return response.Text;
}

public async Task<Embedding<float>> GetEmbeddingAsync(string text)
{
var generator = await _clientFactory.CreateEmbeddingGeneratorAsync(
providerName: "OpenAI",
connectionName: "default",
deploymentName: "text-embedding-3-small");

var embedding = await generator.GenerateEmbeddingAsync(text);
return embedding;
}
}

When to use: Simple, one-off AI calls where you don't need profiles, tools, or orchestration. Ideal for embedding generation, simple completions, or building custom pipelines.

IAICompletionService

A mid-level service that sends messages to AI models and returns completions. It includes a handler pipeline (IAICompletionServiceHandler) that can modify requests and responses.

public class MyService
{
private readonly IAICompletionService _completionService;

public MyService(IAICompletionService completionService)
{
_completionService = completionService;
}

public async Task<ChatResponse> CompleteAsync(
string clientName,
IEnumerable<ChatMessage> messages,
AICompletionContext context)
{
return await _completionService.CompleteAsync(clientName, messages, context);
}

public IAsyncEnumerable<ChatResponseUpdate> StreamAsync(
string clientName,
IEnumerable<ChatMessage> messages,
AICompletionContext context,
CancellationToken cancellationToken)
{
return _completionService.CompleteStreamingAsync(
clientName, messages, context, cancellationToken);
}
}

When to use: When you need to send messages to an AI model with context management, but don't need full orchestration (tool loops, planning, etc.).

IAICompletionContextBuilder

Builds an AICompletionContext from a resource object (such as an AIProfile or ChatInteraction). The builder runs a handler pipeline that populates the context with tools, MCP connections, data sources, and other configuration.

public class MyService
{
private readonly IAICompletionContextBuilder _contextBuilder;
private readonly IAIProfileManager _profileManager;

public MyService(
IAICompletionContextBuilder contextBuilder,
IAIProfileManager profileManager)
{
_contextBuilder = contextBuilder;
_profileManager = profileManager;
}

public async Task<AICompletionContext> BuildContextAsync(string profileName)
{
var profile = await _profileManager.FindByNameAsync(profileName);

var context = await _contextBuilder.BuildAsync(profile, ctx =>
{
// Optionally customize the context
ctx.SystemMessage = "Custom system message override";
});

return context;
}
}

When to use: When you need a fully configured completion context built from an AI Profile or Chat Interaction, but want to manage the completion call yourself.

IOrchestrator

The highest-level service. The orchestrator is a pluggable runtime that manages the full lifecycle of an AI completion session, including:

  • Planning — Decomposing complex requests into steps
  • Tool scoping — Selecting a subset of available tools relevant to the request
  • Execution loops — Calling the LLM with scoped tools, evaluating results, and iterating
  • Capability gap detection — Expanding tool scope when the current tools can't handle the request
  • Streaming — Producing the final streaming response
public class MyService
{
private readonly IOrchestrationContextBuilder _orchestrationContextBuilder;
private readonly IAIProfileManager _profileManager;

public MyService(
IOrchestrationContextBuilder orchestrationContextBuilder,
IAIProfileManager profileManager)
{
_orchestrationContextBuilder = orchestrationContextBuilder;
_profileManager = profileManager;
}

public async IAsyncEnumerable<ChatResponseUpdate> ExecuteAsync(
string profileName,
string userMessage,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var profile = await _profileManager.FindByNameAsync(profileName);

// Build orchestration context from the profile
var context = await _orchestrationContextBuilder.BuildAsync(profile, ctx =>
{
ctx.UserMessage = userMessage;
});

// Resolve the orchestrator for this profile
var orchestrator = context.Orchestrator;

// Execute and stream results
await foreach (var update in orchestrator.ExecuteStreamingAsync(context, cancellationToken))
{
yield return update;
}
}
}

When to use: When you want the full AI experience — tool invocation, MCP integration, document handling, multi-step reasoning — managed automatically.

IOrchestrationContextBuilder

Builds an OrchestrationContext from a resource object. Similar to IAICompletionContextBuilder, but produces a richer context that includes orchestrator selection, tool registry, and extensible properties.

The builder runs a handler pipeline (IOrchestrationContextBuilderHandler) with BuildingAsync and BuiltAsync phases:

public class MyOrchestrationHandler : IOrchestrationContextBuilderHandler
{
public Task BuildingAsync(OrchestrationContextBuildingContext context)
{
// Add custom tools or modify context during building phase
return Task.CompletedTask;
}

public Task BuiltAsync(OrchestrationContextBuiltContext context)
{
// Post-processing after context is built
return Task.CompletedTask;
}
}

Register your handler in Startup:

services.AddScoped<IOrchestrationContextBuilderHandler, MyOrchestrationHandler>();

Choosing the Right Service

ScenarioService
Simple completion or embedding callIAIClientFactory
Send messages with handler pipelineIAICompletionService
Build context from a profile for custom useIAICompletionContextBuilder
Full AI experience with tools, MCP, and documentsIOrchestrator via IOrchestrationContextBuilder
Extend orchestration context buildingIOrchestrationContextBuilderHandler
Extend completion context buildingIAICompletionContextBuilderHandler

Deployment Resolution

When an AI Profile, Chat Interaction, or other resource requests a deployment, the system resolves it using a typed fallback chain:

  1. Explicit deployment — The ChatDeploymentId or UtilityDeploymentId explicitly assigned on the profile/resource
  2. Connection default for type — The deployment marked IsDefault: true for that type on the associated connection
  3. Global default — The default deployment configured in Settings → Artificial Intelligence → Default AI Deployment Settings (e.g., DefaultUtilityDeploymentId, DefaultEmbeddingDeploymentId, DefaultImageDeploymentId)
  4. null/error — If no deployment is found at any level

Each deployment is a first-class typed entity with a Type property (Chat, Utility, Embedding, Image, SpeechToText). This allows the system to automatically resolve the correct model based on the task being performed.

tip

When using IAIClientFactory directly, you still specify the deploymentName explicitly. The typed resolution chain applies to higher-level services that work with profiles and interactions.

Implementing a Custom Orchestrator

You can create a custom orchestrator by implementing IOrchestrator:

public sealed class MyCustomOrchestrator : IOrchestrator
{
public string Name => "MyCustomOrchestrator";

public async IAsyncEnumerable<ChatResponseUpdate> ExecuteStreamingAsync(
OrchestrationContext context,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
// Custom orchestration logic here
// Access tools via context.Tools
// Access messages via context.Messages
// Access completion context via context.CompletionContext
}
}

Register your orchestrator in Startup:

services.AddOrchestrator<MyCustomOrchestrator>("MyCustomOrchestrator", options =>
{
options.DisplayName = S["My Custom Orchestrator"];
options.Description = S["A custom orchestration runtime."];
});