Skip to content

Commit 6e15308

Browse files
authored
Merge pull request #5543 from gchq/gh-5526
Gh 5526
2 parents 07006a7 + 520b354 commit 6e15308

3 files changed

Lines changed: 252 additions & 21 deletions

File tree

stroom-langchain/stroom-langchain-impl/src/main/java/stroom/langchain/impl/OpenAIServiceImpl.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,11 @@ public String getModel(final OpenAIModelDoc modelDoc) {
8686
// -H "Authorization: Bearer $OPENAI_API_KEY"
8787

8888

89+
final HttpClientConfiguration httpClientConfiguration = convert(NullSafe.getOrElse(
90+
modelDoc,
91+
OpenAIModelDoc::getHttpClientConfiguration,
92+
HttpClientConfig.builder().build()));
8993
final HttpClientProviderCache httpClientProviderCache = httpClientCacheProvider.get();
90-
final HttpClientConfiguration httpClientConfiguration = new HttpClientConfiguration();
9194
try (final HttpClientProvider httpClientProvider = httpClientProviderCache.get(httpClientConfiguration)) {
9295
final String url = getUrl(modelDoc, "models");
9396
final String apiKey = getApiKey(modelDoc);
@@ -283,7 +286,7 @@ public ScoringModel getJinaScoringModel(final OpenAIModelDoc modelDoc) {
283286

284287
private HttpClientConfiguration convert(final HttpClientConfig config) {
285288
if (config == null) {
286-
return null;
289+
return new HttpClientConfiguration();
287290
}
288291

289292
return HttpClientConfiguration

stroom-query/stroom-query-impl/src/main/java/stroom/query/impl/AskStroomAiResourceImpl.java

Lines changed: 179 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -17,65 +17,225 @@
1717
package stroom.query.impl;
1818

1919
import stroom.ai.shared.AskStroomAIConfig;
20+
import stroom.ai.shared.AskStroomAiContext;
2021
import stroom.ai.shared.AskStroomAiRequest;
2122
import stroom.ai.shared.AskStroomAiResource;
2223
import stroom.ai.shared.AskStroomAiResponse;
2324
import stroom.ai.shared.ChatMemoryConfig;
25+
import stroom.ai.shared.DashboardTableContext;
26+
import stroom.ai.shared.GeneralTableContext;
27+
import stroom.ai.shared.QueryTableContext;
2428
import stroom.ai.shared.TableSummaryConfig;
29+
import stroom.dashboard.shared.DashboardSearchRequest;
30+
import stroom.dashboard.shared.Search;
2531
import stroom.docref.DocRef;
32+
import stroom.docrefinfo.api.DocRefInfoService;
33+
import stroom.event.logging.api.StroomEventLoggingService;
34+
import stroom.event.logging.api.StroomEventLoggingUtil;
2635
import stroom.event.logging.rs.api.AutoLogged;
2736
import stroom.event.logging.rs.api.AutoLogged.OperationType;
2837
import stroom.node.api.NodeService;
38+
import stroom.query.api.ExpressionOperator;
39+
import stroom.query.api.ExpressionUtil;
40+
import stroom.query.api.Param;
41+
import stroom.query.shared.QuerySearchRequest;
2942
import stroom.util.logging.LambdaLogger;
3043
import stroom.util.logging.LambdaLoggerFactory;
44+
import stroom.util.shared.NullSafe;
3145
import stroom.util.shared.ResourcePaths;
3246

47+
import event.logging.Chat;
48+
import event.logging.ComplexLoggedOutcome;
49+
import event.logging.Data;
50+
import event.logging.DataSources;
51+
import event.logging.MultiObject;
52+
import event.logging.Query;
53+
import event.logging.SearchEventAction;
3354
import jakarta.inject.Inject;
3455
import jakarta.inject.Provider;
3556
import jakarta.ws.rs.client.Entity;
3657

58+
import java.util.List;
59+
3760
@AutoLogged
3861
class AskStroomAiResourceImpl implements AskStroomAiResource {
3962

4063
private static final LambdaLogger LOGGER = LambdaLoggerFactory.getLogger(AskStroomAiResourceImpl.class);
4164

4265
private final Provider<NodeService> nodeServiceProvider;
4366
private final Provider<AskStroomAIService> askStroomAIServiceProvider;
67+
private final Provider<StroomEventLoggingService> stroomEventLoggingServiceProvider;
68+
private final Provider<DocRefInfoService> docRefInfoServiceProvider;
4469

4570
@Inject
4671
AskStroomAiResourceImpl(final Provider<NodeService> nodeServiceProvider,
47-
final Provider<AskStroomAIService> askStroomAIServiceProvider) {
72+
final Provider<AskStroomAIService> askStroomAIServiceProvider,
73+
final Provider<StroomEventLoggingService> stroomEventLoggingServiceProvider,
74+
final Provider<DocRefInfoService> docRefInfoServiceProvider) {
4875
this.nodeServiceProvider = nodeServiceProvider;
4976
this.askStroomAIServiceProvider = askStroomAIServiceProvider;
77+
this.stroomEventLoggingServiceProvider = stroomEventLoggingServiceProvider;
78+
this.docRefInfoServiceProvider = docRefInfoServiceProvider;
5079
}
5180

5281
@AutoLogged(OperationType.MANUALLY_LOGGED)
5382
@Override
5483
public AskStroomAiResponse askStroomAi(final String nodeName, final AskStroomAiRequest request) {
55-
final AskStroomAIService aiService = askStroomAIServiceProvider.get();
56-
try {
57-
// Make sure we ask the question on the right node.
58-
final String node = aiService.getBestNode(nodeName, request);
5984

60-
// If the client doesn't specify a node then execute locally.
61-
if (node == null) {
62-
return askStroomAIServiceProvider.get().askStroomAi(request);
85+
final StroomEventLoggingService eventLoggingService = stroomEventLoggingServiceProvider.get();
86+
return eventLoggingService.loggedWorkBuilder()
87+
.withTypeId(StroomEventLoggingUtil.buildTypeId(this, "askStroomAi"))
88+
.withDescription(getDescription(request))
89+
.withDefaultEventAction(getSearchEventAction(request))
90+
.withComplexLoggedResult(searchEventAction -> {
91+
// Do the work
92+
final AskStroomAiResponse response;
93+
final AskStroomAIService aiService = askStroomAIServiceProvider.get();
94+
try {
95+
// Make sure we ask the question on the right node.
96+
final String node = aiService.getBestNode(nodeName, request);
97+
98+
if (node == null) {
99+
// If the client doesn't specify a node then execute locally.
100+
response = askStroomAIServiceProvider.get().askStroomAi(request);
101+
102+
} else {
103+
// Execute remotely.
104+
response = nodeServiceProvider.get()
105+
.remoteRestResult(
106+
node,
107+
AskStroomAiResponse.class,
108+
() -> ResourcePaths.buildAuthenticatedApiPath(
109+
AskStroomAiResource.BASE_PATH,
110+
AskStroomAiResource.ASK_STROOM_AI_PATH_PART,
111+
node),
112+
() -> askStroomAIServiceProvider.get().askStroomAi(request),
113+
builder -> builder.post(Entity.json(request)));
114+
}
115+
} catch (final RuntimeException e) {
116+
LOGGER.debug(e.getMessage(), e);
117+
throw e;
118+
}
119+
120+
final SearchEventAction newSearchEventAction = searchEventAction.newCopyBuilder()
121+
.withResults(MultiObject
122+
.builder()
123+
.addChat(Chat
124+
.builder()
125+
.withContent(response.getMessage())
126+
.build())
127+
.build())
128+
.build();
129+
130+
return ComplexLoggedOutcome.success(response, newSearchEventAction);
131+
})
132+
.getResultAndLog();
133+
}
134+
135+
private String getDescription(final AskStroomAiRequest request) {
136+
final AskStroomAiContext context = request.getContext();
137+
return switch (context) {
138+
case final DashboardTableContext dashboardTableContext -> "Asking AI about dashboard search results";
139+
case final QueryTableContext queryTableContext -> "Asking AI about Stroom QL search results";
140+
case final GeneralTableContext generalTableContext -> "Asking AI about general table data";
141+
};
142+
}
143+
144+
private SearchEventAction getSearchEventAction(final AskStroomAiRequest request) {
145+
final AskStroomAiContext context = request.getContext();
146+
final String dataSourceInfo = switch (context) {
147+
case final DashboardTableContext dashboardTableContext -> getDashboardDataSource(dashboardTableContext);
148+
case final QueryTableContext queryTableContext ->
149+
NullSafe.get(queryTableContext, QueryTableContext::getSearchRequest, QuerySearchRequest::getQuery);
150+
case final GeneralTableContext generalTableContext -> "General Table";
151+
};
152+
153+
final event.logging.DataSources dataSources = DataSources.builder().addDataSource(dataSourceInfo).build();
154+
return SearchEventAction
155+
.builder()
156+
.withDataSources(dataSources)
157+
.withQuery(Query.builder()
158+
.withRaw(request.getMessage())
159+
.build())
160+
.withData(Data
161+
.builder()
162+
.withName("Model")
163+
.withValue(NullSafe.get(
164+
request,
165+
AskStroomAiRequest::getConfig,
166+
AskStroomAIConfig::getModelRef,
167+
DocRef::getName))
168+
.build())
169+
.build();
170+
}
171+
172+
private String getDashboardDataSource(final DashboardTableContext dashboardTableContext) {
173+
final DashboardSearchRequest searchRequest = dashboardTableContext.getSearchRequest();
174+
final DocRef dataSourceRef = NullSafe.get(
175+
searchRequest,
176+
DashboardSearchRequest::getSearch,
177+
Search::getDataSourceRef);
178+
final Search search = NullSafe.get(searchRequest, DashboardSearchRequest::getSearch);
179+
final List<Param> params = NullSafe.get(search, Search::getParams);
180+
final ExpressionOperator deReferencedExpression = ExpressionUtil.replaceExpressionParameters(
181+
NullSafe.get(search, Search::getExpression),
182+
params);
183+
184+
final String dataSourceInfo = getDataSourceString(dataSourceRef);
185+
return dataSourceInfo +
186+
" query:\n" + NullSafe.getOrElse(
187+
deReferencedExpression,
188+
ExpressionOperator::toMultiLineString,
189+
"");
190+
}
191+
192+
private String getDataSourceString(final DocRef dataSourceRef) {
193+
final StringBuilder sb = new StringBuilder();
194+
195+
final String type = NullSafe.get(dataSourceRef, DocRef::getType);
196+
if (NullSafe.isNonBlankString(type)) {
197+
sb.append(type);
198+
}
199+
200+
final String dataSourceName = getDataSourceName(dataSourceRef);
201+
if (NullSafe.isNonBlankString(dataSourceName)) {
202+
if (!sb.isEmpty()) {
203+
sb.append(" ");
204+
}
205+
sb.append(dataSourceName);
206+
}
207+
208+
final String uuid = NullSafe.get(dataSourceRef, DocRef::getUuid);
209+
if (NullSafe.isNonBlankString(uuid)) {
210+
if (!sb.isEmpty()) {
211+
sb.append(" ");
63212
}
213+
sb.append("{");
214+
sb.append(uuid);
215+
sb.append("}");
216+
}
64217

65-
return nodeServiceProvider.get()
66-
.remoteRestResult(
67-
node,
68-
AskStroomAiResponse.class,
69-
() -> ResourcePaths.buildAuthenticatedApiPath(
70-
AskStroomAiResource.BASE_PATH,
71-
AskStroomAiResource.ASK_STROOM_AI_PATH_PART,
72-
node),
73-
() -> askStroomAIServiceProvider.get().askStroomAi(request),
74-
builder -> builder.post(Entity.json(request)));
218+
if (sb.isEmpty()) {
219+
sb.append("Unknown");
220+
}
221+
222+
return sb.toString();
223+
}
224+
225+
private String getDataSourceName(final DocRef docRef) {
226+
if (docRef == null) {
227+
return null;
228+
}
229+
230+
try {
231+
return docRefInfoServiceProvider.get().name(docRef)
232+
.orElse(docRef.getName());
75233
} catch (final RuntimeException e) {
234+
// We might not have an explorer handler capable of getting info.
76235
LOGGER.debug(e.getMessage(), e);
77-
throw e;
78236
}
237+
238+
return docRef.getName();
79239
}
80240

81241
@AutoLogged(OperationType.UNLOGGED)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
* Feature **#5526** : Add event logging to ask stroom AI.
2+
3+
4+
```sh
5+
# ********************************************************************************
6+
# Issue number: 5526
7+
# Issue title: Ask Stroom AI isn't logging
8+
# Issue tags: f:audit-logging, p:high
9+
# Issue link: https://github.com/gchq/stroom/issues/5526
10+
# ********************************************************************************
11+
12+
# ONLY the top line will be included as a change entry in the CHANGELOG.
13+
# The entry should be in GitHub flavour markdown and should be written on a SINGLE
14+
# line with no hard breaks. You can have multiple change files for a single GitHub issue.
15+
# The entry should be written in the imperative mood, i.e. 'Fix nasty bug' rather than
16+
# 'Fixed nasty bug'.
17+
#
18+
# Examples of acceptable entries are:
19+
#
20+
#
21+
# * Bug **#123** : Fix bug with an associated GitHub issue in this repository.
22+
#
23+
# * Bug **namespace/other-repo#456** : Fix bug with an associated GitHub issue in another repository.
24+
#
25+
# * Feature **#789** : Add new feature X.
26+
#
27+
# * Bug : Fix bug with no associated GitHub issue.
28+
#
29+
#
30+
# Note: The line must start '* XXX ', where 'XXX' is a valid category,
31+
# one of [Bug Feature Refactor Dependency Build].
32+
33+
34+
# --------------------------------------------------------------------------------
35+
# The following is random text to make this file unique for git's change detection
36+
# R5MkxktqqWPYsNaw3Hk6Ss6oR7JInnEeWDkchOYD2mJsO4zIcTN6ZLI2cOVuXSr7KdsNRXvfe6VOaNvy
37+
# My0njrXbZ70v7c6jI3jNOi2t8199txlTFzbhPtpCPk05hp3LOaBrujRLVIuDkkEXgRIWX4ZhCoUzTI1H
38+
# JNthDHHLnLuKQFdkiLRn2Np5BxuRaPgM19MsxpUgKrcMYpbRGtSDHalHSsoLdWyO9mbZbzncQz54NJp8
39+
# ksgK8W81ovMyUcUroIrIRLcSItVJHnSZghI2kknDnpRBKSv5OIiZ4RU3rpo1ZCrlcEXjvjS7V2qgFCWY
40+
# Lzl4dnPsrM7yCpShBuO57KYiJYE5qC55lFl794S9BoRFYKtGWfcTmaapznJn4zSP55oKbiaYcqMi2Kv4
41+
# euO1M2hAGyPIGkj8lkfmdO6Nk6oXZbPfn9017VrAvtjqVMEZhkzylne717a6Ip1fuhhFEPjDK5nj6fHe
42+
# oQt2OgGQvqZTYtfogy88lldhmRFzDvnDlhATKc1QxWNjs1ks98Ia3OLVS9nvTjqICGbUN738zLmYfu8P
43+
# PTBC3ztTyovcDsDZR9xHW4BzAF0kt5j2zhZHqBT1WHMR17FdmoXM8xs1Qf4a7FOrRjMXYJkHgvGSiNSd
44+
# 3y2fw84Fs4BtkchaCtZZlWdVnYMMaI3cHr0NAeaEdJTvzUfDk5ihHxGAiBR5pERVMuJaSFxzxjnehV4Y
45+
# Fj4ChXSPoVHAOK6LONGAhrR4P2O29roAZqvumrhQC3CIrrAzhjRAWu5POvyjWBCQlFxuve6nweNMOK1H
46+
# oyL25zXcMm0C1nIvdH7eDYLXMZgtmkbCgNbrlfB0OuasJnzMhCnPfxCc4hhHgK3ck5rr72AOVlJqn6dg
47+
# ulNcegFKuhD61pO3cbE0Og0C56KVWzaPlYWo0HO0Zp2XrZLjYq9F8nNKJAY1erNtQhgXN0G6ZhrSPwKA
48+
# cJmeSR2dYj5u4e58XaRJhcoGcK6Wihic7w4F4m1bJhphXHBDC4KLWPI5N8H7FbPGTsW0RqvAHrCw82UI
49+
# Nu3DoFFnLllfPQFslC3mAgK0abul7Ve5Ixe5DK7sgmDE9I6pMB3ScZ6U3SU5iyRiTC6tvQGvXepk4RWb
50+
# IWNVdvcyUo9w5nor1UC72kSOTVWOsjIFEFTlEi2nhoIgUA7kunz4HUJSsNATV9hTEdUMuPO2lNzVdJLN
51+
# LdN2MULLzLTkfc983u47mUUDHIuDOKoartqumqRS1jVfNxuEXiHIS4LpuWL1YAjU0hZvRRS78OTesO48
52+
# zkLfRY8nBT6ZfExmI91wYRdMPrzA2oiLbljrTe2fBLPknywQgKndAPRBBboSjGuO4wNiHcO5scM322H5
53+
# O9J36C6BkyVOoc8DL4oUiS6F4TzA7zjRU7IFVgidt0iAiaXgaETYsLYiorrSo9KMRZa0ATNboXfzSAAt
54+
# BanroJUGXqBGATdZd4tnoVuwbxOPaw8bO8Yzhxdr74Z32s9ghomlfeofsXW9Mw2JGXzIYsPzx5bglJ2d
55+
# B9HYxeBDRZwFEXZ92BviCjWlA8cKPN9E8MwIPKtKKlRqtuEVzSligRFyHGICM8muUJIK1dkhMcSgSKwK
56+
# VBSoxvtsBO7sniWdnohYxqryCcxsgsUa8KFHW9cK3ktvqYJnt9AKexWKZ2TccZdmbYDt4dmXmxngYDuM
57+
# kkffVHXAJSVWrRZnwk9wtvq61boP0DWaTtO5i6W2bCM9RCMlFuQAOVyiM9R7DBG1HemK29q3w6Zxhhei
58+
# tGmVP1CLvYjjClorz3XnIoCtqZfHXnMiHLH7JFC8GIxq0DRF7KFoCCsgLulEvzjS8byDJl7QchvO0kLy
59+
# ddeQMChc0tZnMEGZVWQp4uzE17PYzMwrXbAH0d0VAFzLVKuMEQtvW30oxedbwzdRLjQpDph6qvauUJZK
60+
# Ljv2VElqSHllsiB9sCzPEWTPWEoWMV4qIobjA7qmbG7gyGqVT6wMXl2STnQC7LxLvxBG15KMeREU480M
61+
# Kgx9bmo2P5CnUgH6OIMZPBpfs9flRNOkGbB8bdl11zzbtgkVgGZPGV0hFnqoJjBsztDnDViCG2jgS6s1
62+
# DlMCBYm4zPBm4ZCHe16ivnzYMGfCSsLgyRY77JHyRjDGH49MDs86F501xVZJLsouLb7GAOWEU61iWxKO
63+
# 1RLhKBla99V1MbARBCQ652iwVVOqiLU8UbXlntkknU3LvP97rD5dNFOI3yoWkDsj4XPIIm56Jd8TKks4
64+
# XUraOm36o1oUnC1DAijgp8vvmerzJ1VPPmNpgGDKAvJnoqm4A1K2Cn9caTwSog33Q1WUy0YOVj6fxK5I
65+
# ytAYde1a4IDU1Xz7Z36NlmBcCesgGBT9wEkCvtoRDDdvFv8yGqctberZ3OWDgYCkeKzAaCz36Ooa8eyo
66+
# --------------------------------------------------------------------------------
67+
68+
```

0 commit comments

Comments
 (0)