Skip to content
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
20 changes: 13 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ plugins {
id "com.webcohesion.enunciate" version "2.17.0" apply false
id "io.freefair.lombok" version "8.6" apply false

id 'com.github.jk1.dependency-license-report' version '1.16'
id 'com.github.jk1.dependency-license-report' version '3.0.1'
id "com.github.hierynomus.license-report" version "0.15.0"
}

Expand Down Expand Up @@ -54,7 +54,6 @@ licenseReport {
// }
// }

excludeGroups = ['javax.servlet.*']
excludeGroups = ['javax.servlet.*']
excludes = [
'com.fasterxml.jackson:jackson-bom',
Expand All @@ -63,7 +62,7 @@ licenseReport {
]

configurations = ['runtimeClasspath']
allowedLicensesFile = new File("$rootDir/allowed-lic.json")
allowedLicensesFile = new File("$rootDir/allowed-licenses.json")
}

checkLicense {
Expand Down Expand Up @@ -167,7 +166,6 @@ configure(javaProjects) {
dependency 'org.slf4j:slf4j-api:1.7.25'

// resolving conflicts
dependency 'com.squareup.okhttp3:okhttp:3.3.1'
dependency 'net.minidev:json-smart:2.5.2'

dependency 'commons-logging:commons-logging:1.2'
Expand Down Expand Up @@ -288,9 +286,17 @@ configure(javaProjects) {
dependency 'org.apache.bcel:bcel:6.6.0'

// AI integration
dependency 'dev.langchain4j:langchain4j:1.7.1'
dependency 'dev.langchain4j:langchain4j-azure-open-ai-spring-boot-starter:1.7.1-beta14'
dependency 'dev.langchain4j:langchain4j-spring-boot-starter:1.7.1-beta14'
dependency 'dev.langchain4j:langchain4j:1.8.0'
dependency 'dev.langchain4j:langchain4j-spring-boot-starter:1.8.0-beta15'
dependency 'dev.langchain4j:langchain4j-open-ai-official:1.8.0-beta15'

// Conflict resolution
dependency 'com.google.errorprone:error_prone_annotations:2.33.0'
dependency 'org.jetbrains.kotlin:kotlin-stdlib:1.9.10'
dependency 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10'
dependency 'com.fasterxml:classmate:1.7.0'
dependency 'org.apache.httpcomponents.client5:httpclient5:5.5'
dependency 'org.apache.httpcomponents.core5:httpcore5:5.3.4'

dependency 'org.reactivestreams:reactive-streams:1.0.4'

Expand Down
42 changes: 41 additions & 1 deletion guide/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,4 +163,44 @@ services:
- SECURITY_API-KEYS-PROVIDER_API-KEYS_0_KEY=TEST_API_SECRET
- SECURITY_API-KEYS-PROVIDER_API-KEYS_0_USER=admin
- SECURITY_API-KEYS-PROVIDER_API-KEYS_0_AUTHORITIES=TB_ALLOW_READ, TB_ALLOW_WRITE
```
```

## Gen AI Configuration

TimeBase Web Administrator can generate QQL queries with the help of an external Gen AI provider.
The feature reads its settings from the `ai-api` section of `application.yaml`.

Example config:

```yaml
ai-api:
enabled: true
provider: AZURE_LEGACY # OPENAI | AZURE | AZURE_LEGACY | GITHUB
endpointUrl: "https://YOUR-RESOURCE-NAME.openai.azure.com" # base url of the ai api, may be ommitted for OPENAI and GITHUB providers
deploymentName: gpt-5-mini-2025-08-07 # deployment for QQL generation
embeddingDeploymentName: text-embedding-3-small-1 # deployment for embeddings
keys: # ai api keys per user
- username: admin
key: ${ADMIN_AI_KEY} # resolves from environment variable
- username: reader
key: READER_AI_KEY # takes priority over key from security.oauth2.users section
maxAttempts: 3 # max attempts allowed for an AI to produce a valid QQL query
```

If a user is not present in `ai-api.keys`, the system falls back to `security.oauth2.users[].aiApiKey` when it is defined.

> [!IMPORTANT]
> The Gen AI endpoint must be compatible with the selected provider's API.

Supported AI providers:

1. AZURE_LEGACY includes the deployment name in the request URL. Example:
`https://YOUR-RESOURCE-NAME.openai.azure.com/openai/deployments/gpt-5-mini-2025-08-07/chat/completions`
where `https://YOUR-RESOURCE-NAME.openai.azure.com` is the `endpointUrl` and
`gpt-5-mini-2025-08-07` is the `deploymentName`.
2. AZURE uses the `endpointUrl` and provides the model name in the request body.
3. OPENAI is the official OpenAI API. The endpoint defaults to
`https://api.openai.com/v1`, but may be any compatible endpoint.
4. GITHUB uses the GitHub Models compatible API. The endpoint defaults to
`https://models.inference.ai.azure.com`. Use a GitHub personal access token
as the key — see GitHub docs [docs](https://docs.github.com/en/github-models/about-github-models) for details.
2 changes: 1 addition & 1 deletion java/ws-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ dependencies {

// AI integration
implementation 'dev.langchain4j:langchain4j'
implementation 'dev.langchain4j:langchain4j-azure-open-ai-spring-boot-starter'
implementation 'dev.langchain4j:langchain4j-spring-boot-starter'
implementation 'dev.langchain4j:langchain4j-open-ai-official'

compileOnly 'com.webcohesion.enunciate:enunciate-core-annotations'

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,27 +54,21 @@ public class GenAiConfig {
private static final String FALLBACK_EMB_FILE = "qql-docs-embeddings.json";

@Bean
public PerUserAzureChatModel perUserAzureChatModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserAzureChatModel(settings.getEndpointUrl(),
settings.getDeploymentName(),
keyProvider);
public ChatModel perUserChatModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserOpenAiOfficialChatModel(settings, keyProvider);
}

@Bean
public PerUserAzureStreamingChatModel perUserAzureStreamingChatModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserAzureStreamingChatModel(settings.getEndpointUrl(),
settings.getDeploymentName(),
keyProvider);
public StreamingChatModel perUserStreamingChatModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserOpenAiOfficialStreamingChatModel(settings, keyProvider);
}

@Bean
public PerUserAzureEmbeddingModel perUserAzureEmbeddingModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserAzureEmbeddingModel(settings.getEndpointUrl(),
settings.getEmbeddingDeploymentName(),
keyProvider);
public EmbeddingModel perUserEmbeddingModel(AiApiSettings settings,
UserAiApiKeyProvider keyProvider) {
return new PerUserOpenAiOfficialEmbeddingModel(settings, keyProvider);
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@

import com.epam.deltix.tbwg.webapp.interceptors.TimebaseLoginInterceptor;
import com.epam.deltix.tbwg.webapp.interceptors.RestLogInterceptor;
import com.epam.deltix.tbwg.webapp.utils.json.JsonBigIntEncodingArgumentResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.util.UrlPathHelper;

import java.util.List;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

Expand All @@ -37,6 +41,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
private final RestLogInterceptor logInterceptor;
private final TimebaseLoginInterceptor timebaseLoginInterceptor;

@Autowired
private JsonBigIntEncodingArgumentResolver argumentResolver;

@Autowired
public WebMvcConfig(AsyncTaskExecutor asyncTaskExecutor,
RestLogInterceptor logInterceptor,
Expand Down Expand Up @@ -65,4 +72,9 @@ public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor);
registry.addInterceptor(timebaseLoginInterceptor);
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(argumentResolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.epam.deltix.tbwg.webapp.config.WebSocketConfig;
import com.epam.deltix.tbwg.webapp.services.timebase.MonitorService;
import com.epam.deltix.tbwg.webapp.utils.HeaderAccessorHelper;
import com.epam.deltix.tbwg.webapp.utils.json.JsonBigIntEncoding;
import com.epam.deltix.tbwg.webapp.websockets.subscription.Subscription;
import com.epam.deltix.tbwg.webapp.websockets.subscription.SubscriptionChannel;
import com.epam.deltix.tbwg.webapp.websockets.subscription.SubscriptionController;
Expand Down Expand Up @@ -53,8 +54,9 @@ public Subscription onSubscribe(SimpMessageHeaderAccessor headerAccessor, Subscr
long fromTimestamp = headerAccessorHelper.getTimestamp(headerAccessor);
List<String> symbols = headerAccessorHelper.getSymbols(headerAccessor);
List<String> types = headerAccessorHelper.getTypes(headerAccessor);
JsonBigIntEncoding bigIntEncoding = HeaderAccessorHelper.getJsonBigIntEncoding(headerAccessor);

monitorService.subscribe(sessionId, subscriptionId, stream, null, fromTimestamp, types, symbols, channel::sendMessage);
monitorService.subscribe(sessionId, subscriptionId, stream, null, fromTimestamp, types, symbols, channel::sendMessage, bigIntEncoding);
return () -> monitorService.unsubscribe(sessionId, subscriptionId);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,10 @@
*/
package com.epam.deltix.tbwg.webapp.controllers;

import com.epam.deltix.gflog.api.Log;
import com.epam.deltix.gflog.api.LogFactory;
import com.epam.deltix.tbwg.webapp.config.WebSocketConfig;
import com.epam.deltix.tbwg.webapp.services.timebase.MonitorService;
import com.epam.deltix.tbwg.webapp.utils.HeaderAccessorHelper;
import com.epam.deltix.tbwg.webapp.utils.json.JsonBigIntEncoding;
import com.epam.deltix.tbwg.webapp.websockets.subscription.Subscription;
import com.epam.deltix.tbwg.webapp.websockets.subscription.SubscriptionChannel;
import com.epam.deltix.tbwg.webapp.websockets.subscription.SubscriptionController;
Expand Down Expand Up @@ -55,8 +54,9 @@ public Subscription onSubscribe(SimpMessageHeaderAccessor headerAccessor, Subscr
long fromTimestamp = headerAccessorHelper.getTimestamp(headerAccessor);
List<String> symbols = headerAccessorHelper.getSymbols(headerAccessor);
List<String> types = headerAccessorHelper.getTypes(headerAccessor);
JsonBigIntEncoding bigIntEncoding = HeaderAccessorHelper.getJsonBigIntEncoding(headerAccessor);

monitorService.subscribe(sessionId, subscriptionId, null, qql, fromTimestamp, types, symbols, channel::sendMessage);
monitorService.subscribe(sessionId, subscriptionId, null, qql, fromTimestamp, types, symbols, channel::sendMessage, bigIntEncoding);
return () -> monitorService.unsubscribe(sessionId, subscriptionId);
}

Expand Down
Loading