Skip to main content
Version: 3.0.x

Architecture overview

🚧Documentation Under Construction

We are actively working to improve this documentation. The content you see here may be incomplete, subject to change, or may not fully reflect the current state of the feature. We appreciate your understanding as we continue to enhance our docs.

The AI plugin provides the fundamental building blocks for AI-powered features, including:

  • Site-specific plugin configuration
  • Text chunking for handling large content
  • Text embedding generation for semantic search
  • Chat conversations with AI assistants
  • Tool calling for agentic workflows

Ingestion​

Content is chunked and embedded on save via AiRecordData:

Chat completion​

ChatClient completes a single turn, mapping to the provider's API:

Agent orchestration​

An Agent represents a configured AI identity (instructions, tools, an executor). Calling Agent#run builds the request with the agent's defaults, then dispatches to an Engine. The default ToolCallingEngine loops over ChatClient, executing tool calls until the model produces a final response:

Plugin configuration​

AiPlugin​

The main plugin entry point that provides global AI configuration.

  • Configures the TextEmbeddingGenerator and TextChunker used for embedding generation
  • Defines additional content types for AI indexing
  • Handles legacy configuration migration

AiPluginConfig​

Site-specific configuration for the AI plugin.

  • Configures the ChatClient for AI conversations
  • Holds the list of Agent instances configured for the site (AskAiAgent, CreateWithAiAgent, and any custom agents)
  • Enables/disables Create with AI and Ask AI

Agent-specific settings (brand guidelines, prompt suggestions, author personas, generation timeout, AI-generated content detectors) live on the agent class itself, not on AiPluginConfig.

Agents​

Agent​

Abstract base class for AI agents. Each agent holds an optional Engine override and contributes its defaults (instructions, tool groups, etc.) to outgoing requests via applyDefaults(ChatRequest.Builder). Configured per-site via AiPluginConfig's agents list.

1
var askAi = Agent.get(AskAiAgent.class);
2
3
var messages = askAi.run(b -> b.addMessages(history).addMessage(userMessage), listener);

Agent.get(Class) resolves the configured instance for the current site context, or returns a default instance if not configured. Callers pass a Consumer<ChatRequest.Builder> to add per-call inputs (messages, optional per-call instructions via appendInstructions); the agent owns the prompt structure and tool surface through applyDefaults.

AskAiAgent​

The Ask AI agent. Provides the structural system prompt for agentic tool-calling behavior (read-only tools act silently, destructive tools require confirmation, search before answering, cite sources, etc.) and exposes all discovered ToolGroups for the model to use. Default executor is ToolCallingEngine.

additionalInstructions is an editor-facing field that is appended to the structural prompt. The structural prompt itself is not editable.

CreateWithAiAgent​

The Create with AI agent. Provides the structural system prompt for content generation and exposes only read-only tools to the engine (the engine itself remains tool-agnostic; the read-only constraint is expressed via a predicate when adding tool groups in applyDefaults).

additionalInstructions (labeled Brand Guidelines in the CMS) is appended to the structural prompt. Per-site config — prompt suggestions, author personas, generation timeout, AI-generated content detectors — also lives on this class.

Text chunking​

TextChunker​

An abstract base class for splitting text into smaller chunks suitable for embedding generation. Part of the RAG pipeline for improved precision in semantic search. Typical use might look like:

1
List<String> chunks = TextChunker.get().chunk(
2
"This is a very long piece of text that needs to be split...",
3
countTokens,
4
10);

If not configured, defaults to RecursiveJsonTextChunker.

FixedLengthTextChunker​

Splits text into fixed-length chunks with configurable overlap (default 10%).

RecursiveTextChunker​

Splits text using a recursive document splitter (via LangChain4j) with configurable overlap (default 10%).

JsonTextChunker​

An abstract base class for chunkers that work on JSON data directly.

RecursiveJsonTextChunker​

Recursively splits large JSON data while preserving context. Uses a two-stage approach: small fields serve as context, large fields are individually split and combined with context to create chunks. Configurable context ratio (default 40%) and string chunker (default RecursiveTextChunker).

Embedding generation​

EmbeddingGeneratable​

An interface that marks content types for automatic embedding generation. For example:

1
public class Article extends Content implements EmbeddingGeneratable {
2
private String title;
3
private String body;
4
}

The default shouldGenerateEmbedding() method returns true when the record is visible, but can be overridden for custom logic.

@ExcludeFromEmbedding​

A field-level annotation that excludes specific fields from embedding generation. For example, this is how to exclude sensitive data from an embedding:

1
public class Article extends Content implements EmbeddingGeneratable {
2
private String title;
3
4
@ExcludeFromEmbedding
5
private String internalId;
6
}

Common use cases are:

  • Personally identifiable information (PII)
  • Internal system identifiers
  • Administrative metadata (creation dates, flags)
  • Large binary data or encoded content

TextEmbeddingGenerator​

An abstract base class for generating text embeddings (vector representations of text). Typical use might look like:

1
TextEmbeddingGenerator generator = TextEmbeddingGenerator.get();
2
int tokens = generator.countTokens("This is some text");
3
float[] embedding = generator.generate("This is some text");

Implementations must provide getModel(), getMaxTokens(), getDimension(), and generate(List<String>).

AiRecordData​

A modification class that adds AI-specific data and behaviors to all Brightspot records.

  • Automatically generates embeddings when records are saved
  • Deletes embeddings when records are deleted
  • Extracts embedding-ready content by filtering excluded fields
  • Extracts filterable metadata for vector records

Whenever a record is saved, it follows these steps to generate an embedding:

  1. Checks if the record type should generate embeddings
  2. Chunks the content using TextChunker
  3. Generates embeddings for each chunk using TextEmbeddingGenerator
  4. Saves embeddings to the vector database

AiFieldData​

A modification class for field-level AI configuration. Tracks whether a field should be excluded from embedding generation.

Chat​

ChatClient​

An abstract base class for clients that complete a single turn of conversation with an AI model. Each provider (OpenAI, Bedrock) implements this to handle model-specific API calls. Providers always stream responses internally.

1
ChatClient client = ChatClient.get();
2
3
// Blocking
4
AssistantMessage response = client.complete(request, listener);
5
6
// Async (cancelable)
7
CompletableFuture<AssistantMessage> future = client.completeAsync(request, listener);

The listener parameter is an optional AssistantMessageListener that receives streaming events as the response is built. Provider errors are translated to TransientAiException (retryable) or NonTransientAiException (permanent).

Engine​

An abstract base class in com.brightspot.ai.agent for multi-turn orchestration. The default ToolCallingEngine implements a tool-calling loop: it calls ChatClient#completeAsync, checks if the model requested tool calls, executes them, feeds results back, and repeats until the model produces a final text response. If the maximum iteration limit is reached, the future completes exceptionally with ToolIterationLimitException.

Engines are usually invoked indirectly through an Agent, which is the layer that owns the request's prompt and tool surface:

1
var agent = Agent.get(AskAiAgent.class);
2
3
// Blocking
4
List<Message> messages = agent.run(b -> b.addMessages(history).addMessage(userMessage), listener);
5
6
// Async (cancelable)
7
CompletableFuture<List<Message>> future = agent.runAsync(
8
b -> b.addMessages(history).addMessage(userMessage),
9
listener);

The listener parameter is an optional RunListener that extends AssistantMessageListener with tool execution events. The engine passes it through to ChatClient#completeAsync for text and tool-call streaming, and calls RunListener#onToolResultMessage after each tool execution.

An agent can override its executor via Agent#setEngine; if none is set, Agent#getEngine falls back to a fresh ToolCallingEngine. For advanced use (custom request shapes), callers can dispatch to the engine directly with a fully-built ChatRequest, but the agent's contract is bypassed in that path.

ChatRequest​

An immutable request containing instructions, messages, tools, and optional generation parameters. Built with a fluent builder. Most callers do not build one directly — they pass a customizer to Agent#run, which produces the request after applying the agent's defaults:

1
var agent = Agent.get(AskAiAgent.class);
2
3
// Agent contributes structural instructions and tool groups via applyDefaults;
4
// the customizer adds per-call inputs.
5
var messages = agent.run(b -> b
6
.appendInstructions(extraPerCallContext)
7
.addMessages(history)
8
.addMessage(userMessage)
9
.temperature(0.7)
10
.maxOutputTokens(4096),
11
listener);

appendInstructions(String) adds to the existing instructions separated by a blank line; the agent uses it internally in applyDefaults and callers can use it to layer per-invocation context without clobbering the agent's prompt. Tool-group methods accept an optional Predicate<? super Tool> filter (addToolGroup(group, Tool::isReadOnly), etc.) so an agent can express a constrained tool surface without engine-level enforcement.

AssistantMessageListener​

Listener for streaming events on an AssistantMessage as it is built by a ChatClient. Provides callbacks for text deltas and fully assembled tool calls:

  • onTextDelta(String delta) -- called when a text fragment is received from the model
  • onToolCall(ToolCall toolCall) -- called when a fully assembled tool call is received

RunListener​

Extends AssistantMessageListener with tool execution events:

  • onToolResultMessage(ToolResultMessage result) -- called after a tool has been executed

Message​

A sealed base class for chat messages. Each message carries raw text and an optional templated variant that may include additional context for AI processing. The permitted subclasses are:

  • UserMessage -- a user-generated prompt
  • AssistantMessage -- an AI-generated response, with metadata for timing, token usage, completion status, and tool calls
  • ToolResultMessage -- the result of executing a tool, correlated to a ToolCall by ID

ToolCall​

An embedded record on AssistantMessage representing a tool call requested by the model. Each tool call has a provider-assigned correlation ID (toolCallId), the tool name, and raw JSON arguments.

ToolCall.Builder accumulates streaming tool call fragments (ID, name, argument chunks) and assembles them into a complete ToolCall. Used by ChatClient implementations to handle streaming responses.

Tool system​

ToolGroup​

An abstract base class for grouping related tool methods. Concrete subclasses are discovered automatically via ClassFinder and their @ToolMethod-annotated methods are registered as tools for use in agentic workflows and MCP.

ToolGroup.getAll() returns all tool groups discovered on the classpath, cached for performance. Each group exposes its tools via getTools() (lazily resolved from @ToolMethod annotations) and optional getInstructions().

Tool​

Schema definition for a tool that can be offered to an LLM. A concrete class with a builder, holding the tool's name, description, input schema, and behavioral annotations (readOnly, destructive, idempotent).

RunnableTool​

A Tool that can be executed. Extends Tool with run(), title, and outputSchema. Produced by ToolGroup from @ToolMethod-annotated methods.

@ToolMethod​

A method-level annotation that marks a method as an AI tool. Attributes include:

  • name -- tool name (defaults to method name, prefixed with group name)
  • title -- human-readable title
  • description -- description for AI tool selection
  • readOnly, destructive, idempotent -- behavioral hints

@ToolParam​

A parameter-level annotation for @ToolMethod method parameters. Provides metadata for schema generation including name, description, default value, and required status. Supports custom SchemaSupplier implementations for non-standard JSON schemas.