|
17 | 17 | package stroom.query.impl; |
18 | 18 |
|
19 | 19 | import stroom.ai.shared.AskStroomAIConfig; |
| 20 | +import stroom.ai.shared.AskStroomAiContext; |
20 | 21 | import stroom.ai.shared.AskStroomAiRequest; |
21 | 22 | import stroom.ai.shared.AskStroomAiResource; |
22 | 23 | import stroom.ai.shared.AskStroomAiResponse; |
23 | 24 | import stroom.ai.shared.ChatMemoryConfig; |
| 25 | +import stroom.ai.shared.DashboardTableContext; |
| 26 | +import stroom.ai.shared.GeneralTableContext; |
| 27 | +import stroom.ai.shared.QueryTableContext; |
24 | 28 | import stroom.ai.shared.TableSummaryConfig; |
| 29 | +import stroom.dashboard.shared.DashboardSearchRequest; |
| 30 | +import stroom.dashboard.shared.Search; |
25 | 31 | import stroom.docref.DocRef; |
| 32 | +import stroom.docrefinfo.api.DocRefInfoService; |
| 33 | +import stroom.event.logging.api.StroomEventLoggingService; |
| 34 | +import stroom.event.logging.api.StroomEventLoggingUtil; |
26 | 35 | import stroom.event.logging.rs.api.AutoLogged; |
27 | 36 | import stroom.event.logging.rs.api.AutoLogged.OperationType; |
28 | 37 | 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; |
29 | 42 | import stroom.util.logging.LambdaLogger; |
30 | 43 | import stroom.util.logging.LambdaLoggerFactory; |
| 44 | +import stroom.util.shared.NullSafe; |
31 | 45 | import stroom.util.shared.ResourcePaths; |
32 | 46 |
|
| 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; |
33 | 54 | import jakarta.inject.Inject; |
34 | 55 | import jakarta.inject.Provider; |
35 | 56 | import jakarta.ws.rs.client.Entity; |
36 | 57 |
|
| 58 | +import java.util.List; |
| 59 | + |
37 | 60 | @AutoLogged |
38 | 61 | class AskStroomAiResourceImpl implements AskStroomAiResource { |
39 | 62 |
|
40 | 63 | private static final LambdaLogger LOGGER = LambdaLoggerFactory.getLogger(AskStroomAiResourceImpl.class); |
41 | 64 |
|
42 | 65 | private final Provider<NodeService> nodeServiceProvider; |
43 | 66 | private final Provider<AskStroomAIService> askStroomAIServiceProvider; |
| 67 | + private final Provider<StroomEventLoggingService> stroomEventLoggingServiceProvider; |
| 68 | + private final Provider<DocRefInfoService> docRefInfoServiceProvider; |
44 | 69 |
|
45 | 70 | @Inject |
46 | 71 | 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) { |
48 | 75 | this.nodeServiceProvider = nodeServiceProvider; |
49 | 76 | this.askStroomAIServiceProvider = askStroomAIServiceProvider; |
| 77 | + this.stroomEventLoggingServiceProvider = stroomEventLoggingServiceProvider; |
| 78 | + this.docRefInfoServiceProvider = docRefInfoServiceProvider; |
50 | 79 | } |
51 | 80 |
|
52 | 81 | @AutoLogged(OperationType.MANUALLY_LOGGED) |
53 | 82 | @Override |
54 | 83 | 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); |
59 | 84 |
|
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(" "); |
63 | 212 | } |
| 213 | + sb.append("{"); |
| 214 | + sb.append(uuid); |
| 215 | + sb.append("}"); |
| 216 | + } |
64 | 217 |
|
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()); |
75 | 233 | } catch (final RuntimeException e) { |
| 234 | + // We might not have an explorer handler capable of getting info. |
76 | 235 | LOGGER.debug(e.getMessage(), e); |
77 | | - throw e; |
78 | 236 | } |
| 237 | + |
| 238 | + return docRef.getName(); |
79 | 239 | } |
80 | 240 |
|
81 | 241 | @AutoLogged(OperationType.UNLOGGED) |
|
0 commit comments