Skip to content

Commit 63827d4

Browse files
committed
RDBC-944 Introduce AI connection string support with configurations for multiple providers (OpenAI, Azure, Hugging Face, Google, and more).
1 parent 280f2c7 commit 63827d4

24 files changed

+1555
-3
lines changed

src/Documents/Operations/AI/AiConversation.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,6 @@ export class AiConversation {
104104
aiHandleError?: AiHandleErrorStrategy
105105
): void
106106

107-
// Implementation
108107
public handle<TArgs = any>(
109108
actionName: string,
110109
action: ((args: TArgs) => Promise<object> | object) |
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
import { ConnectionString, ConnectionStringType } from "../../Etl/ConnectionString.js";
2+
import { AiConnectorType } from "./AiConnectorType.js";
3+
import { AiModelType } from "./AiModelType.js";
4+
import { AiSettingsCompareDifferences } from "./AiSettingsCompareDifferences.js";
5+
import { OpenAiSettings } from "./Settings/OpenAiSettings.js";
6+
import { AzureOpenAiSettings } from "./Settings/AzureOpenAiSettings.js";
7+
import { OllamaSettings } from "./Settings/OllamaSettings.js";
8+
import { EmbeddedSettings } from "./Settings/EmbeddedSettings.js";
9+
import { GoogleSettings } from "./Settings/GoogleSettings.js";
10+
import { HuggingFaceSettings } from "./Settings/HuggingFaceSettings.js";
11+
import { MistralAiSettings } from "./Settings/MistralAiSettings.js";
12+
import { VertexSettings } from "./Settings/VertexSettings.js";
13+
14+
15+
const PROVIDER_KEYS = [
16+
"openAiSettings",
17+
"azureOpenAiSettings",
18+
"ollamaSettings",
19+
"embeddedSettings",
20+
"googleSettings",
21+
"huggingFaceSettings",
22+
"mistralAiSettings",
23+
"vertexSettings"
24+
] as const;
25+
26+
/**
27+
* Represents an AI service connection string configuration.
28+
* Supports multiple AI providers (OpenAI, Azure OpenAI, Ollama, Google, HuggingFace, Mistral AI, Vertex AI, Embedded).
29+
* Only one provider can be configured per connection string.
30+
*/
31+
export class AiConnectionString extends ConnectionString {
32+
public identifier?: string;
33+
public openAiSettings?: OpenAiSettings;
34+
public azureOpenAiSettings?: AzureOpenAiSettings;
35+
public ollamaSettings?: OllamaSettings;
36+
public embeddedSettings?: EmbeddedSettings;
37+
public googleSettings?: GoogleSettings;
38+
public huggingFaceSettings?: HuggingFaceSettings;
39+
public mistralAiSettings?: MistralAiSettings;
40+
public vertexSettings?: VertexSettings;
41+
public modelType?: AiModelType;
42+
public type: ConnectionStringType = "Ai"
43+
44+
/**
45+
* Validates the connection string configuration.
46+
* Ensures exactly one AI provider is configured and all provider-specific fields are valid.
47+
* @returns Array of validation error messages (empty if valid)
48+
*/
49+
public validate(): string[] {
50+
const errors: string[] = [];
51+
52+
const allSettings = PROVIDER_KEYS
53+
.map(key => this[key])
54+
.filter(Boolean);
55+
56+
for (const setting of allSettings) {
57+
setting.validate(errors);
58+
}
59+
60+
if (allSettings.length === 0) {
61+
errors.push(`At least one of the following settings must be set: ${PROVIDER_KEYS.join(", ")}`);
62+
} else if (allSettings.length > 1) {
63+
const configuredSettingsNames = PROVIDER_KEYS.filter(
64+
key => Boolean(this[key])
65+
);
66+
errors.push(`Only one of the following settings can be set: ${configuredSettingsNames.join(", ")}`);
67+
}
68+
69+
return errors;
70+
}
71+
72+
/**
73+
* Gets the type of the active AI provider.
74+
* @returns The connector type of the configured provider, or "None" if none configured
75+
*/
76+
public getActiveProvider(): AiConnectorType {
77+
if (this.openAiSettings) return "OpenAi";
78+
if (this.azureOpenAiSettings) return "AzureOpenAi";
79+
if (this.ollamaSettings) return "Ollama";
80+
if (this.embeddedSettings) return "Embedded";
81+
if (this.googleSettings) return "Google";
82+
if (this.huggingFaceSettings) return "HuggingFace";
83+
if (this.mistralAiSettings) return "MistralAi";
84+
if (this.vertexSettings) return "Vertex";
85+
return "None";
86+
}
87+
88+
/**
89+
* Gets the active provider settings instance.
90+
* @returns The configured provider settings, or undefined if none configured
91+
*/
92+
public getActiveProviderInstance() {
93+
return this.openAiSettings ??
94+
this.azureOpenAiSettings ??
95+
this.ollamaSettings ??
96+
this.embeddedSettings ??
97+
this.googleSettings ??
98+
this.huggingFaceSettings ??
99+
this.mistralAiSettings ??
100+
this.vertexSettings;
101+
}
102+
103+
/**
104+
* Compares this connection string with another to detect differences.
105+
* @param newConnectionString The connection string to compare with
106+
* @returns Flags indicating which settings differ
107+
*/
108+
public compare(newConnectionString: AiConnectionString | null | undefined): AiSettingsCompareDifferences {
109+
if (!newConnectionString) {
110+
return AiSettingsCompareDifferences.All;
111+
}
112+
113+
let result = AiSettingsCompareDifferences.None;
114+
115+
if (this.identifier !== newConnectionString.identifier) {
116+
result |= AiSettingsCompareDifferences.Identifier;
117+
}
118+
119+
if (this.modelType !== newConnectionString.modelType) {
120+
result |= AiSettingsCompareDifferences.ModelArchitecture;
121+
}
122+
123+
const oldProvider = this.getActiveProvider();
124+
const newProvider = newConnectionString.getActiveProvider();
125+
126+
if (oldProvider !== newProvider) {
127+
return AiSettingsCompareDifferences.All;
128+
}
129+
130+
const oldInstance = this.getActiveProviderInstance();
131+
const newInstance = newConnectionString.getActiveProviderInstance();
132+
133+
if (!oldInstance || !newInstance) {
134+
return AiSettingsCompareDifferences.All;
135+
}
136+
137+
result |= oldInstance.compare(newInstance);
138+
139+
return result;
140+
}
141+
142+
/**
143+
* Checks if this connection string is equal to another.
144+
* @param connectionString The connection string to compare with
145+
* @returns true if the connection strings are equal, false otherwise
146+
*/
147+
public isEqual(connectionString: ConnectionString): boolean {
148+
if (!(connectionString instanceof AiConnectionString)) {
149+
return false;
150+
}
151+
152+
if (this.name !== connectionString.name) {
153+
return false;
154+
}
155+
156+
if (this.identifier !== connectionString.identifier) {
157+
return false;
158+
}
159+
160+
if (this.modelType !== connectionString.modelType) {
161+
return false;
162+
}
163+
164+
const activeProvider = this.getActiveProvider();
165+
const otherActiveProvider = connectionString.getActiveProvider();
166+
167+
if (activeProvider !== otherActiveProvider) {
168+
return false;
169+
}
170+
171+
return this.compare(connectionString) === AiSettingsCompareDifferences.None;
172+
}
173+
174+
/**
175+
* Checks if the connection uses an encrypted communication channel (HTTPS).
176+
* @returns true if the connection is encrypted, false otherwise
177+
*/
178+
public usingEncryptedCommunicationChannel(): boolean {
179+
const aiConnectorType = this.getActiveProvider();
180+
181+
switch (aiConnectorType) {
182+
case "Ollama":
183+
return this.ollamaSettings?.uri?.startsWith("https") ?? false;
184+
case "OpenAi":
185+
return this.openAiSettings?.endpoint?.startsWith("https") ?? false;
186+
case "AzureOpenAi":
187+
return this.azureOpenAiSettings?.endpoint?.startsWith("https") ?? false;
188+
case "MistralAi":
189+
return this.mistralAiSettings?.endpoint?.startsWith("https") ?? false;
190+
case "HuggingFace":
191+
// Endpoint is optional for HuggingFace, default endpoint is HTTPS
192+
return !this.huggingFaceSettings?.endpoint ||
193+
this.huggingFaceSettings.endpoint.startsWith("https");
194+
case "Embedded":
195+
case "Google":
196+
case "Vertex":
197+
return true;
198+
default:
199+
throw new Error(`Unknown AI connector type: ${aiConnectorType}`);
200+
}
201+
}
202+
203+
/**
204+
* Gets the maximum number of concurrent query embedding batches for this connection.
205+
* @param globalQueryEmbeddingsMaxConcurrentBatches The global default value
206+
* @returns The connection-specific value, or the global default if not set
207+
*/
208+
public getQueryEmbeddingsMaxConcurrentBatches(
209+
globalQueryEmbeddingsMaxConcurrentBatches: number
210+
): number {
211+
const provider = this.getActiveProviderInstance();
212+
return provider?.embeddingsMaxConcurrentBatches ?? globalQueryEmbeddingsMaxConcurrentBatches;
213+
}
214+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type AiConnectorType =
2+
| "None"
3+
| "OpenAi"
4+
| "AzureOpenAi"
5+
| "Ollama"
6+
| "Embedded"
7+
| "Google"
8+
| "HuggingFace"
9+
| "MistralAi"
10+
| "Vertex";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type AiModelType =
2+
| "TextEmbeddings"
3+
| "Chat";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Flags enum for detecting differences between AI settings configurations.
3+
* Uses bitwise operations for combining multiple differences.
4+
*/
5+
export enum AiSettingsCompareDifferences {
6+
None = 0,
7+
AuthenticationSettings = 1 << 0, // 1
8+
EndpointConfiguration = 1 << 1, // 2
9+
ModelArchitecture = 1 << 2, // 4
10+
EmbeddingDimensions = 1 << 3, // 8
11+
DeploymentConfiguration = 1 << 4, // 16
12+
Identifier = 1 << 5, // 32
13+
All = ~(~0 << 6) // 63 (all bits set)
14+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { AiSettingsCompareDifferences } from "../AiSettingsCompareDifferences.js";
2+
3+
/**
4+
* Base class for all AI provider settings.
5+
*/
6+
export abstract class AbstractAiSettings {
7+
/**
8+
* Maximum number of query embedding batches that can be processed concurrently.
9+
* Allows users to override the database global value.
10+
*/
11+
public embeddingsMaxConcurrentBatches?: number;
12+
13+
/**
14+
* Validates the settings fields and adds any errors to the provided array.
15+
* @param errors Array to collect validation error messages
16+
*/
17+
public abstract validate(errors: string[]): void;
18+
19+
/**
20+
* Compares this settings instance with another to detect differences.
21+
* @param other The other settings instance to compare with
22+
* @returns Flags indicating which settings differ
23+
*/
24+
public abstract compare(other: AbstractAiSettings): AiSettingsCompareDifferences;
25+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { OpenAiBaseSettings } from "./OpenAiBaseSettings.js";
2+
import { AbstractAiSettings } from "./AbstractAiSettings.js";
3+
import { AiSettingsCompareDifferences } from "../AiSettingsCompareDifferences.js";
4+
import { StringUtil } from "../../../../../Utility/StringUtil.js";
5+
6+
export class AzureOpenAiSettings extends OpenAiBaseSettings {
7+
/**
8+
* Azure OpenAI deployment name.
9+
* Learn more: https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource
10+
*/
11+
public constructor(
12+
apiKey: string,
13+
endpoint: string,
14+
model: string,
15+
public deploymentName: string,
16+
dimensions?: number,
17+
temperature?: number
18+
) {
19+
super(apiKey, endpoint, model, dimensions, temperature);
20+
}
21+
22+
public validate(errors: string[]): void {
23+
super.validate(errors);
24+
25+
if (StringUtil.isNullOrEmpty(this.deploymentName.trim())) {
26+
errors.push("Value for 'deploymentName' field cannot be empty.");
27+
}
28+
}
29+
30+
public compare(other: AbstractAiSettings): AiSettingsCompareDifferences {
31+
if (!(other instanceof AzureOpenAiSettings)) {
32+
return AiSettingsCompareDifferences.All;
33+
}
34+
35+
let differences = super.compare(other);
36+
37+
if (this.deploymentName !== other.deploymentName) {
38+
differences |= AiSettingsCompareDifferences.DeploymentConfiguration;
39+
}
40+
41+
return differences;
42+
}
43+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AbstractAiSettings } from "./AbstractAiSettings.js";
2+
import { AiSettingsCompareDifferences } from "../AiSettingsCompareDifferences.js";
3+
4+
/**
5+
* Settings for embedded AI models (placeholder for future implementation).
6+
*/
7+
export class EmbeddedSettings extends AbstractAiSettings {
8+
public validate(errors: string[]): void {
9+
// nothing to validate.
10+
}
11+
12+
public compare(other: AbstractAiSettings): AiSettingsCompareDifferences {
13+
if (other instanceof EmbeddedSettings) {
14+
return AiSettingsCompareDifferences.None;
15+
}
16+
17+
return AiSettingsCompareDifferences.All;
18+
}
19+
}

0 commit comments

Comments
 (0)