| Server IP : 185.208.173.17 / Your IP : 87.236.161.98 Web Server : Microsoft-IIS/10.0 System : Windows NT SRV8576125506 10.0 build 26100 (Windows Server 2016) AMD64 User : IUSR ( 0) PHP Version : 7.4.13 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : OFF | Perl : OFF | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : C:/Program Files/MySQL/MySQL Workbench 8.0/swb/shell/lib/mysqlsh/plugins/mds_plugin/ |
Upload File : |
# Copyright (c) 2021, 2025, Oracle and/or its affiliates.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License, version 2.0,
# as published by the Free Software Foundation.
#
# This program is also distributed with certain software (including
# but not limited to OpenSSL) that is licensed under separate terms, as
# designated in a particular file or component or in included license
# documentation. The authors of MySQL hereby grant you an additional
# permission to link the program and your derivative works with the
# separately licensed software that they have included with MySQL.
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License, version 2.0, for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
"""Sub-Module to work with GenAI features"""
from mysqlsh.plugin_manager import plugin_function
from mysqlsh.plugin_manager.general import get_shell_user_dir
from mds_plugin import languages
import os
import json
# cSpell:ignore mockchat
def check_dependencies():
try:
from . import mockchat
return True
except:
return False
@plugin_function("mds.genai.status", shell=True, cli=True, web=True)
def get_status(session=None):
"""Returns status information about the current GenAI setup
Args:
session (object): The database session to use.
Returns:
A dict holding the status information
"""
if session.database_type != "MySQL":
return {
"heatwave_support": False,
"local_model_support": False,
"language_support": False
}
heatwave_support = False
local_model_support = False
language_support = False
rows = session.run_sql("""
SELECT EXISTS (
SELECT * FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = 'sys' AND ROUTINE_NAME = 'heatwave_chat') AS heatwave_chat_available
""").fetch_all()
if len(rows) > 0 and rows[0][0] == 1:
heatwave_support = True
else:
local_model_support = check_dependencies()
rows = session.run_sql("""
SELECT EXISTS (
SELECT * FROM information_schema.VIEWS
WHERE TABLE_SCHEMA = 'sys' AND TABLE_NAME = 'ML_SUPPORTED_LLM_LANGUAGES') AS language_support_available
""").fetch_all()
if len(rows) > 0 and rows[0][0] == 1:
language_support = True
return {
"heatwave_support": heatwave_support,
"local_model_support": local_model_support,
"language_support": language_support
}
def pip_install(package):
import subprocess
import sys
r = subprocess.run(
[sys.executable, "-m", "pip", "install", package],
text=True,
stderr=subprocess.PIPE,
capture_output=False,
)
if r.returncode == 0:
return None
return r.stderr
@plugin_function("mds.genai.configure", shell=True, cli=True, web=True)
def configure_local_model_support(cohere_api_key: str = "", **kwargs):
"""Configures a local model setup
Args:
cohere_api_key (string): Optional cohere API key to use. Taken from the CO_API_KEY environment variable if not given.
**kwargs: Additional options
Keyword Args:
options (dict): The options that store information about the request.
session (object): The database session to use.
send_gui_message (object): The function to send a message to he GUI.
Returns:
A dict holding the status information
"""
send_gui_message = kwargs.get("send_gui_message")
if send_gui_message is not None:
send_gui_message("info", "Checking dependencies ...")
while True:
if check_dependencies():
if not cohere_api_key:
api_key_path = os.path.join(
get_shell_user_dir(), "plugin_data", "mds_plugin", "cohere_api_key.txt")
if os.path.exists(api_key_path):
cohere_api_key = open(api_key_path).read().strip()
from . import mockchat
mockchat.set_api_key(cohere_api_key)
return {
"success": True,
"error": "",
}
else:
for m in ["cohere", "numpy", "sentence-transformers", "transformers"]:
if send_gui_message is not None:
send_gui_message("info", f"Installing {m}...")
else:
print(f"Installing {m}...")
error = pip_install(m)
if error:
if send_gui_message is not None:
send_gui_message(
"error", f"Error while installing dependencies. {error}")
return {"success": False, "error": error}
def translate_string(session, text, target_language, model_id=None, source_language="English"):
if target_language == source_language:
return text
if model_id is None:
model_id = "mistral-7b-instruct-v1"
# Load the mistral language model
session.run_sql('CALL sys.ml_model_load(?, NULL);', [model_id])
res = session.run_sql(f"""
SELECT sys.ml_generate(CONCAT(
'translate the following text from ', ?, ' to ', ?, ': ', ?),
JSON_OBJECT("model_id", ?));
""", [source_language, target_language, text, model_id])
rows = res.fetch_all()
if len(rows) > 0:
translation = json.loads(rows[0][0]).get("text")
if '"' in translation:
return translation.split('"')[1].strip()
elif 'The answer' in translation and ':' in translation:
return translation.split(':', 1)[1].strip()
elif 'The translation of' in translation and ':' in translation:
return translation.split(':', 1)[1].strip()
else:
return translation.strip()
return text
@plugin_function("mds.genai.chat", shell=True, cli=True, web=True)
def chat(prompt, **kwargs):
"""Processes a chat request and return a generated answer
If no options are passed, they are generated from the prompt
Args:
prompt (str): The question of the user
**kwargs: Additional options
Keyword Args:
options (dict): The options that store information about the request.
session (object): The database session to use.
send_gui_message (object): The function to send a message to he GUI.
Returns:
A dict with the generated answer and the options used
"""
from mysqlsh import globals
session = kwargs.get("session")
options = kwargs.get("options", None)
send_gui_message = kwargs.get("send_gui_message")
model_options = options.get("model_options", {})
model_language = model_options.get("language", "en")
model_language_name = languages.GenerativeAILanguage(model_language).name
# Clean up options
options.pop("documents", None)
options.pop("request_completed", None)
# If the user selected default as the model_id, remove it from the model_options and let the server pick
if model_options.get("model_id", "") == "default":
model_options.pop("model_id")
options["model_options"] = model_options
# Clear table list if lock_table_list is not set to true
if options.get("lock_table_list", False) == False:
options.pop("tables", None)
if not session:
session = globals.session
if not session:
raise Exception("No database session specified.")
else:
# MySQL Shell VSC - HeatWave Chat
# Note that when called from the GUI, the session already comes as parameter
# so the tracking option reporting is done here, since only a VSCode option
# was added
session.set_option_tracker_feature_id("mysql_ot_msh.vsc.hw_chat")
if send_gui_message is not None:
send_gui_message("data", {"info": "Checking chat engine status ..."})
status = get_status(session=session)
if status.get("heatwave_support") is False and status.get("local_model_support") is False:
raise Exception(
"GenAI support is not available. Please connect to a HeatWave 9.0 instance or higher.")
# Remove language if not supported
if status.get("language_support") is False and "language" in model_options:
model_options.pop("language")
options["model_options"] = model_options
if status.get("heatwave_support") is True:
lang_opts = options.pop("language_options", {})
language = lang_opts.get("language")
# If a language has been selected for translation, do the translation
if language is not None and lang_opts.get("translate_user_prompt") is not False and \
language != model_language_name:
send_gui_message(
"data", {"info": f"Translating prompt from {language} to {model_language_name} ..."})
# Translate the prompt
prompt = translate_string(
session, prompt,
target_language=model_language_name,
model_id=lang_opts.get("model_id"),
source_language=language)
send_gui_message("data", {"info": "Generating answer ..."})
session.run_sql("SET @chat_options = ?", [json.dumps(options)])
res = session.run_sql("CALL sys.heatwave_chat(?)", [prompt])
send_gui_message("data", {"info": "Processing results ..."})
next_result = True
while next_result:
rows = res.fetch_all()
if len(rows) == 0:
next_result = res.next_result()
continue
cols = res.get_column_names()
# Either the first result set column is named "chat_options"
if len(cols) > 0 and cols[0] == "chat_options" and len(rows[0]) > 0:
options = json.loads(rows[0][0])
send_gui_message("data", options)
# Note: For now the last response is ignored since we fetch the @chat_options session var instead
# or "response" for the final response that contains all tokens at once
# elif len(cols) > 0 and cols[0] == "response" and len(rows[0]) > 0:
# options = { "token": json.loads(rows[0][0]) }
# send_gui_message("data", options)
next_result = res.next_result()
res = session.run_sql("SELECT @chat_options")
rows = res.fetch_all()
if len(rows) > 0:
options = json.loads(rows[0][0])
if language is not None and lang_opts.get("translate_response") is not False and \
language != model_language_name:
send_gui_message(
"data", {"info": f"Translating response from {model_language_name} to {language} ..."})
# Translate the response
response = translate_string(
session, options.get("response"),
target_language=language,
model_id=lang_opts.get("model_id"),
source_language=model_language_name)
options["response"] = response
send_gui_message("data", options)
else:
from . import mockchat
api_key_path = os.path.join(
get_shell_user_dir(), "plugin_data", "mds_plugin", "cohere_api_key.txt")
if os.path.exists(api_key_path):
cohere_api_key = open(api_key_path).read().strip()
mockchat.set_api_key(cohere_api_key)
return mockchat.chat(prompt, options, session, send_gui_message)
@plugin_function("mds.genai.lakehouseStatus", shell=True, cli=True, web=True)
def lakehouse_status(**kwargs):
"""Gets lakehouse status information
Args:
**kwargs: Additional options
Keyword Args:
memory_used (int): The amount of used lakehouse memory
memory_total (int): The amount of total lakehouse memory
schema_name (str): The database schema name used to lookup the lakehouse tables
lakehouse_tables_hash (str): The hash calculated for lakehouse tables of the given schema
lakehouse_tasks_hash (str): The hash calculated for lakehouse tasks
session (object): The database session to use.
Returns:
A dict with the status information
"""
from mysqlsh import globals
session = kwargs.get("session")
if not session:
session = globals.session
if not session:
raise Exception("No database session specified.")
memory_used = kwargs.get("memory_used", 0)
memory_total = kwargs.get("memory_total", 0)
schema_name = kwargs.get("schema_name")
lakehouse_tables_hash = kwargs.get("lakehouse_tables_hash", -1)
lakehouse_tasks_hash = kwargs.get("lakehouse_tasks_hash", -1)
status = {}
# cSpell:ignore RNSTATE BASEREL IFNULL
res = session.run_sql("""
SELECT SUM(MEMORY_USAGE), SUM(MEMORY_TOTAL), SUM(BASEREL_MEMORY_USAGE)
FROM performance_schema.rpd_nodes
WHERE STATUS = 'AVAIL_RNSTATE'""")
rows = res.fetch_all()
if len(rows[0]) > 0:
try:
new_memory_used = int(rows[0][0])
new_memory_total = int(rows[0][1])
if not (memory_used == new_memory_used) or not (memory_total == new_memory_total):
status["memory_status"] = {
"memory_used": new_memory_used,
"memory_total": new_memory_total
}
except:
pass
if schema_name is not None:
res = session.run_sql("""
SELECT JSON_OBJECT(
'id', CONCAT(TABLE_SCHEMA, '.', TABLE_NAME),
'table_name', TABLE_NAME,
'schema_name', TABLE_SCHEMA,
'loaded', LOADED,
'progress', IFNULL(LOAD_PROGRESS, 0),
'comment', TABLE_COMMENT,
'rows', TABLE_ROWS,
'data_length', DATA_LENGTH,
'last_change', DATE_FORMAT(IFNULL(UPDATE_TIME, CREATE_TIME), '%Y-%m-%d %H:%i')
) as table_status,
MD5(CONCAT_WS('|', TABLE_SCHEMA, TABLE_NAME, LOADED, LOAD_PROGRESS, TABLE_ROWS,
DATA_LENGTH, IFNULL(UPDATE_TIME, CREATE_TIME))) AS hash
FROM sys.vector_store_load_tables
WHERE TABLE_SCHEMA = ?
ORDER BY CREATE_TIME DESC, TABLE_NAME""",
[schema_name])
rows = res.fetch_all()
tables = []
table_hash = 0
for row in rows:
tables.append(json.loads(row[0]))
table_hash = hash(row[1] + str(table_hash))
if not (lakehouse_tables_hash == str(table_hash)):
status["table_status"] = {
"hash": str(table_hash),
"tables": tables
}
else:
status["table_status"] = {
"hash": "emptyList",
"tables": []
}
# res = session.run_sql("""
# SELECT
# JSON_OBJECT(
# 'id', id,
# 'title', name,
# 'log_time', DATE_FORMAT(log_time, '%Y-%m-%d %H:%i:%s'),
# 'status', status,
# 'status_message', message,
# 'progress', IFNULL(progress, 0),
# 'data', data,
# 'scheduled_time', DATE_FORMAT(scheduled_time, '%Y-%m-%d %H:%i:%s'),
# 'starting_time', DATE_FORMAT(starting_time, '%Y-%m-%d %H:%i:%s'),
# 'estimated_completion_time', DATE_FORMAT(estimated_completion_time, '%Y-%m-%d %H:%i:%s'),
# 'estimated_remaining_time', estimated_remaining_time
# ) AS task_status,
# MD5(CONCAT_WS(',',
# log_time, status, message, progress, data, scheduled_time, starting_time,
# estimated_completion_time, estimated_remaining_time)) AS col_hash
# FROM `mysql_tasks`.`task_status`
# WHERE task_type='GenAI_Load'
# ORDER BY id DESC
# LIMIT 20""")
res = session.run_sql(
"SELECT `mysql_tasks`.`task_status_list`('GenAI_Load', 0, 20);")
rows = res.fetch_all()
tasks = []
task_hash = 0
if rows is not None and len(rows) > 0 and rows[0][0] is not None:
tasks = json.loads(rows[0][0])
for task in tasks:
task_hash = hash(task.get('row_hash', 0) + str(task_hash))
if not (lakehouse_tasks_hash == str(task_hash)):
status["task_status"] = {
"hash": str(task_hash),
"tasks": tasks
}
return status
@plugin_function("mds.genai.saveChatOptions", shell=True, cli=True, web=True)
def save_chat_options(file_path, **kwargs):
"""Saves chat options to a file
Args:
file_path (str): The file to save the options to
**kwargs: Additional options
Keyword Args:
options (dict): The options that store information about the request.
Returns:
None
"""
options = kwargs.get("options")
options["heat_wave_chat_version"] = 1
with open(file_path, 'w') as file:
file.write(json.dumps(options, indent=4))
@plugin_function("mds.genai.loadChatOptions", shell=True, cli=True, web=True)
def load_chat_options(file_path):
"""Loads the chat options from a file
Args:
file_path (str): The path to load the options from
Returns:
A dict representing the options
"""
if not os.path.isfile(file_path):
raise Exception(f"The file {file_path} does not exist.")
with open(file_path, 'r') as file:
options_content = file.read()
try:
options = json.loads(options_content)
except:
raise Exception(
"Failed to parse the selected file. Please select a JSON file.")
version = options.pop("heat_wave_chat_version", None)
if version is None:
raise Exception(
"The selected file is not a HeatWave Chat options file.")
if version != 1:
raise Exception(
"The version of the selected HeatWave Chat options file is not supported.")
return options