Skip to content

Commit

Permalink
dbeaver/pro#3911 AI and SQL control commands (dbeaver#36653)
Browse files Browse the repository at this point in the history
* dbeaver/pro#3911 AI and SQL control commands

* dbeaver/pro#3911 AI and SQL control commands

* dbeaver/pro#3741 System agent service

* dbeaver/pro#3911 AI and SQL control commands

* dbeaver/pro#3911 AI and SQL control commands

* dbeaver/pro#3911 AI and SQL control commands

* dbeaver/pro#3911 SQL output formatting

* dbeaver/pro#3911 AI error messages

* dbeaver/pro#3911 AI command fixes

* dbeaver/pro#3911 Support custom context

* dbeaver/pro#3911 Disable datasource check in headless products

---------

Co-authored-by: kseniaguzeeva <112612526+kseniaguzeeva@users.noreply.github.com>
  • Loading branch information
serge-rider and kseniaguzeeva authored Dec 30, 2024
1 parent 31790fa commit d6580fc
Show file tree
Hide file tree
Showing 21 changed files with 394 additions and 104 deletions.
1 change: 1 addition & 0 deletions plugins/org.jkiss.dbeaver.model.ai/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Bundle-ActivationPolicy: lazy
Bundle-ClassPath: .
Automatic-Module-Name: org.jkiss.dbeaver.model.ai
Require-Bundle: org.jkiss.dbeaver.model,
org.jkiss.dbeaver.model.sql,
org.jkiss.dbeaver.registry,
org.jkiss.bundle.gpt3,
com.google.gson
Expand Down
4 changes: 4 additions & 0 deletions plugins/org.jkiss.dbeaver.model.ai/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@
<formatter id="core" class="org.jkiss.dbeaver.model.ai.format.DefaultRequestFormatter"/>
</extension>

<extension point="org.jkiss.dbeaver.sqlCommand">
<command id="ai" class="org.jkiss.dbeaver.model.ai.commands.SQLCommandAI" label="Execute prompt" description="Execute quary in human language by using AI translator"/>
</extension>

</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,25 @@

import org.jkiss.code.NotNull;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSource;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.ai.completion.DAICompletionMessage;
import org.jkiss.dbeaver.model.app.DBPProject;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.runtime.DBWorkbench;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

// All these ideally should be a part of a given AI engine
public class AITextUtils {
private static final Log log = Log.getLog(AITextUtils.class);

private AITextUtils() {
// prevents instantiation
}
Expand Down Expand Up @@ -106,4 +115,39 @@ public static MessageChunk[] splitIntoChunks(@NotNull String text) {

return chunks.toArray(MessageChunk[]::new);
}

@NotNull
public static List<DBSEntity> loadCustomEntities(
@NotNull DBRProgressMonitor monitor,
@NotNull DBPDataSource dataSource,
@NotNull Set<String> ids
) {
monitor.beginTask("Load custom entities", ids.size());
try {
return loadCheckedEntitiesById(monitor, dataSource.getContainer().getProject(), ids);
} catch (Exception e) {
log.error(e);
return List.of();
} finally {
monitor.done();
}
}

@NotNull
private static List<DBSEntity> loadCheckedEntitiesById(
@NotNull DBRProgressMonitor monitor,
@NotNull DBPProject project,
@NotNull Set<String> ids
) throws DBException {
final List<DBSEntity> output = new ArrayList<>();

for (String id : ids) {
if (DBUtils.findObjectById(monitor, project, id) instanceof DBSEntity entity) {
output.add(entity);
}
monitor.worked(1);
}

return output;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.model.ai.commands;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.model.exec.output.DBCOutputSeverity;

enum AIOutputSeverity implements DBCOutputSeverity {
PROMPT("AI");

private final String name;

AIOutputSeverity(@NotNull String name) {
this.name = name;
}

@NotNull
@Override
public String getName() {
return name;
}

@Override
public boolean isForced() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.model.ai.commands;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.DBPDataSourceContainer;
import org.jkiss.dbeaver.model.ai.*;
import org.jkiss.dbeaver.model.ai.completion.*;
import org.jkiss.dbeaver.model.ai.format.IAIFormatter;
import org.jkiss.dbeaver.model.logical.DBSLogicalDataSource;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.*;
import org.jkiss.dbeaver.runtime.DBWorkbench;
import org.jkiss.utils.CommonUtils;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
* Control command handler
*/
public class SQLCommandAI implements SQLControlCommandHandler {

private static final Log log = Log.getLog(SQLCommandAI.class);

@NotNull
@Override
public SQLControlResult handleCommand(@NotNull DBRProgressMonitor monitor, @NotNull SQLControlCommand command, @NotNull SQLScriptContext scriptContext) throws DBException {
if (command.getDataSource() == null) {
throw new DBException("Not connected to database");
}
AISettings aiSettings = AISettingsRegistry.getInstance().getSettings();
if (aiSettings.isAiDisabled()) {
throw new DBException("AI services are disabled");
}
DAICompletionEngine<?> engine = AIEngineRegistry.getInstance().getCompletionEngine(
aiSettings.getActiveEngine());

String prompt = command.getParameter();
if (CommonUtils.isEmptyTrimmed(prompt)) {
throw new DBException("Empty AI prompt");
}

IAIFormatter formatter = AIFormatterRegistry.getInstance().getFormatter(AIConstants.CORE_FORMATTER);

final DBSLogicalDataSource dataSource = new DBSLogicalDataSource(
command.getDataSourceContainer(), "AI logical wrapper", null);

DBPDataSourceContainer dataSourceContainer = dataSource.getDataSourceContainer();
DAICompletionSettings completionSettings = new DAICompletionSettings(dataSourceContainer);
if (!DBWorkbench.getPlatform().getApplication().isHeadlessMode() && !completionSettings.isMetaTransferConfirmed()) {
if (DBWorkbench.getPlatformUI().confirmAction("Do you confirm AI usage",
"Do you confirm AI usage for '" + dataSourceContainer.getName() + "'?"
)) {
completionSettings.setMetaTransferConfirmed(true);
completionSettings.saveSettings();
} else {
throw new DBException("AI services restricted for '" + dataSourceContainer.getName() + "'");
}
}
DAICompletionScope scope = completionSettings.getScope();
DAICompletionContext.Builder contextBuilder = new DAICompletionContext.Builder()
.setScope(scope)
.setDataSource(dataSource)
.setExecutionContext(scriptContext.getExecutionContext());
if (scope == DAICompletionScope.CUSTOM) {
contextBuilder.setCustomEntities(
AITextUtils.loadCustomEntities(
monitor,
command.getDataSource(),
Arrays.stream(completionSettings.getCustomObjectIds()).collect(Collectors.toSet()))
);
}
final DAICompletionContext aiContext = contextBuilder.build();

DAICompletionSession aiSession = new DAICompletionSession();
aiSession.add(new DAICompletionMessage(DAICompletionMessage.Role.USER, prompt));

List<DAICompletionResponse> responses = engine.performSessionCompletion(
monitor,
aiContext,
aiSession,
formatter,
true);

DAICompletionResponse response = responses.get(0);
MessageChunk[] messageChunks = AITextUtils.splitIntoChunks(
CommonUtils.notEmpty(response.getResultCompletion()));

String finalSQL = null;
StringBuilder messages = new StringBuilder();
for (MessageChunk chunk : messageChunks) {
if (chunk instanceof MessageChunk.Code code) {
finalSQL = code.text();
} else if (chunk instanceof MessageChunk.Text text) {
messages.append(text.text());
}
}
if (finalSQL == null) {
if (!messages.isEmpty()) {
throw new DBException(messages.toString());
}
throw new DBException("Empty AI completion for '" + prompt + "'");
}

scriptContext.getOutputWriter().println(AIOutputSeverity.PROMPT, prompt + " ==> " + finalSQL + "\n");
return SQLControlResult.transform(
new SQLQuery(command.getDataSource(), finalSQL));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@
*/
package org.jkiss.dbeaver.model.sql;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;

/**
* Control command handler
*/
public interface SQLControlCommandHandler
{
/**
*
* @param monitor
* @param command command
* @param scriptContext script context
* @return false if command failed and execution has to be stopped
*/
boolean handleCommand(SQLControlCommand command, SQLScriptContext scriptContext)
@NotNull
SQLControlResult handleCommand(
@NotNull DBRProgressMonitor monitor,
@NotNull SQLControlCommand command,
@NotNull SQLScriptContext scriptContext)
throws DBException;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jkiss.dbeaver.model.sql;

/**
* Control command result.
*
* It may finish with no extra information or with parameters:
* - message: will be shown in UI
* - error: execution error will be shown in UI
*/
public class SQLControlResult {

public static SQLControlResult success() {
return new SQLControlResult();
}

public static SQLControlResult transform(SQLScriptElement element) {
return new SQLControlResult(element);
}

private SQLScriptElement transformed;

private SQLControlResult() {
}

private SQLControlResult(SQLScriptElement transformed) {
this.transformed = transformed;
}

public SQLScriptElement getTransformed() {
return transformed;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.jkiss.dbeaver.model.exec.output.DBCOutputWriter;
import org.jkiss.dbeaver.model.impl.OutputWriterAdapter;
import org.jkiss.dbeaver.model.impl.sql.AbstractSQLDialect;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.registry.SQLCommandHandlerDescriptor;
import org.jkiss.dbeaver.model.sql.registry.SQLCommandsRegistry;
import org.jkiss.dbeaver.model.sql.registry.SQLQueryParameterRegistry;
Expand Down Expand Up @@ -267,15 +268,16 @@ public void setIgnoreParameters(boolean ignoreParameters) {
this.ignoreParameters = ignoreParameters;
}

public boolean executeControlCommand(SQLControlCommand command) throws DBException {
@NotNull
public SQLControlResult executeControlCommand(DBRProgressMonitor monitor, SQLControlCommand command) throws DBException {
if (command.isEmptyCommand()) {
return true;
return SQLControlResult.success();
}
SQLCommandHandlerDescriptor commandHandler = SQLCommandsRegistry.getInstance().getCommandHandler(command.getCommandId());
if (commandHandler == null) {
throw new DBException("Command '" + command.getCommand() + "' not supported");
}
return commandHandler.createHandler().handleCommand(command, this);
return commandHandler.createHandler().handleCommand(monitor, command, this);
}

public void copyFrom(SQLScriptContext context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
*/
package org.jkiss.dbeaver.model.sql.commands;

import org.jkiss.code.NotNull;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.sql.SQLControlCommand;
import org.jkiss.dbeaver.model.sql.SQLControlCommandHandler;
import org.jkiss.dbeaver.model.sql.SQLControlResult;
import org.jkiss.dbeaver.model.sql.SQLScriptContext;
import org.jkiss.dbeaver.model.sql.eval.ScriptVariablesResolver;
import org.jkiss.dbeaver.utils.GeneralUtils;
Expand All @@ -28,15 +31,16 @@
*/
public class SQLCommandEcho implements SQLControlCommandHandler {

@NotNull
@Override
public boolean handleCommand(SQLControlCommand command, SQLScriptContext scriptContext) throws DBException {
public SQLControlResult handleCommand(@NotNull DBRProgressMonitor monitor, @NotNull SQLControlCommand command, @NotNull SQLScriptContext scriptContext) throws DBException {
String parameter = command.getParameter();
if (parameter != null) {
parameter = GeneralUtils.replaceVariables(parameter, new ScriptVariablesResolver(scriptContext), true);
}
scriptContext.getOutputWriter().println(null, parameter);

return true;
return SQLControlResult.success();
}

}
Loading

0 comments on commit d6580fc

Please sign in to comment.