diff --git a/app.toml b/app.toml index d10364f..c59edcf 100644 --- a/app.toml +++ b/app.toml @@ -1,7 +1,7 @@ [App] -name = "ai.h2o.wave.sql-sidekick" -title = "SQL-Sidekick" -description = "QnA with tabular data using NLQ" +Name = "ai.h2o.wave.sql-sidekick" +Title = "SQL-Sidekick" +Description = "QnA with tabular data using NLQ" LongDescription = "about.md" InstanceLifecycle = "MANAGED" Tags = ["DATA_SCIENCE", "MACHINE_LEARNING", "NLP", "GENERATIVE_AI"] @@ -22,3 +22,28 @@ EnableOIDC = true [[Env]] Name = "H2O_WAVE_MAX_REQUEST_SIZE" Value = "20M" + +[[Env]] +Name = "HEAP_ID" +Secret = "heap-analytics" +SecretKey = "id" + +[[Env]] +Name = "H2OGPT_URL" +Secret = "h2ogpt-oss-sqlsidekick" +SecretKey = "h2ogpt-url" + +[[Env]] +Name = "H2OGPT_API_TOKEN" +Secret = "h2ogpt-oss-sqlsidekick" +SecretKey = "h2ogpt-key" + +[[Env]] +Name = "H2OGPTE_URL" +Secret = "h2ogpte-sqlsidekick" +SecretKey = "h2ogpte-url" + +[[Env]] +Name = "H2OGPTE_API_TOKEN" +Secret = "h2ogpte-sqlsidekick" +SecretKey = "h2ogpte-key" diff --git a/examples/notebooks/Guardrails_SQL_injection.ipynb b/examples/notebooks/Guardrails_SQL_injection.ipynb index 3613b5c..5bc921f 100644 --- a/examples/notebooks/Guardrails_SQL_injection.ipynb +++ b/examples/notebooks/Guardrails_SQL_injection.ipynb @@ -87,10 +87,10 @@ "# env variables\n", "\n", "os.environ['OPENAI_API_KEY'] = \"\"\n", - "os.environ['H2O_BASE_MODEL_URL'] = ''\n", - "os.environ['H2O_BASE_MODEL_API_KEY'] = \"\"\n", - "os.environ['RECOMMENDATION_MODEL_REMOTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", - "os.environ['RECOMMENDATION_MODEL_API_KEY'] = \"\"\n", + "os.environ['H2OGPT_URL'] = ''\n", + "os.environ['H2OGPT_API_TOKEN'] = \"\"\n", + "os.environ['H2OGPTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", + "os.environ['H2OGPTE_API_TOKEN'] = \"\"\n", "\n", "\n", "base_path = \".\"\n", diff --git a/examples/notebooks/databricks_db.ipynb b/examples/notebooks/databricks_db.ipynb index cc1198e..f48602f 100644 --- a/examples/notebooks/databricks_db.ipynb +++ b/examples/notebooks/databricks_db.ipynb @@ -146,10 +146,10 @@ "os.environ['DATABRICKS_TOKEN'] = \"\"\n", "os.environ['OPENAI_API_KEY'] = \"\"\n", "\n", - "os.environ['H2O_BASE_MODEL_URL'] = 'http://38.128.233.247'\n", - "os.environ['H2O_BASE_MODEL_API_KEY'] = \"\"\n", - "os.environ['RECOMMENDATION_MODEL_REMOTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", - "os.environ['RECOMMENDATION_MODEL_API_KEY'] = \"\"" + "os.environ['H2OGPT_URL'] = 'http://38.128.233.247'\n", + "os.environ['H2OGPT_API_TOKEN'] = \"\"\n", + "os.environ['H2OGPTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", + "os.environ['H2OGPTE_API_TOKEN'] = \"\"" ] }, { diff --git a/examples/notebooks/sdk_quick_tutorial.ipynb b/examples/notebooks/sdk_quick_tutorial.ipynb index e5bc306..de888d8 100644 --- a/examples/notebooks/sdk_quick_tutorial.ipynb +++ b/examples/notebooks/sdk_quick_tutorial.ipynb @@ -71,11 +71,11 @@ "import os\n", "\n", "os.environ['OPENAI_API_KEY'] = \"\"\n", - "os.environ['H2O_BASE_MODEL_URL'] = 'http://38.128.233.247'\n", - "os.environ['H2O_BASE_MODEL_API_KEY'] = \"\"\n", + "os.environ['H2OGPT_URL'] = 'http://38.128.233.247'\n", + "os.environ['H2OGPT_API_TOKEN'] = \"\"\n", "# To get access to h2ogpte endpoint, reach out to cloud-feedback@h2o.ai\n", - "os.environ['RECOMMENDATION_MODEL_REMOTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", - "os.environ['RECOMMENDATION_MODEL_API_KEY'] = \"\"" + "os.environ['H2OGPTE_URL'] = \"https://h2ogpte.genai.h2o.ai\" # e.g. https://<>.h2ogpte.h2o.ai\n", + "os.environ['H2OGPTE_API_TOKEN'] = \"\"" ] }, { diff --git a/sidekick/configs/env.toml b/sidekick/configs/env.toml index 76afcb5..82ed886 100644 --- a/sidekick/configs/env.toml +++ b/sidekick/configs/env.toml @@ -2,10 +2,13 @@ OPENAI_API_KEY = "" # Needed only for openAI models MODEL_NAME = "h2ogpt-sql-sqlcoder-34b-alpha" # Others: e.g. gpt-4, gpt-4-32k, text-davinci-003 QUANT_TYPE = '4bit' -H2O_BASE_MODEL_URL = 'http://38.128.233.247' -H2O_BASE_MODEL_API_KEY = "" -RECOMMENDATION_MODEL_REMOTE_URL = "" -RECOMMENDATION_MODEL_API_KEY = "" + +H2OGPT_URL = 'http://38.128.233.247' +H2OGPT_API_TOKEN = "" +H2OGPTE_URL = "" +H2OGPTE_API_TOKEN = "" + +RECOMMENDATION_MODEL = "h2oai/h2ogpt-4096-llama2-70b-chat" VULNERABILITY_SCANNER = "h2oai/h2ogpt-4096-llama2-70b-chat" # other options openai models depending on availability (e.g. 'gpt-3.5-turbo') SELF_CORRECTION_MODEL = "h2oai/h2ogpt-4096-llama2-70b-chat" # other options openai models depending on availability (e.g. 'gpt-3.5-turbo') diff --git a/sidekick/configs/prompt_template.py b/sidekick/configs/prompt_template.py index b31f35a..2a254cd 100644 --- a/sidekick/configs/prompt_template.py +++ b/sidekick/configs/prompt_template.py @@ -54,16 +54,6 @@ # Add explanation and reasoning for each SQL query """ -# DEBUGGING_PROMPT = { -# "system_prompt": "Act as a SQL expert for {dialect} database", -# "user_prompt": """ -# ### Help fix syntax errors for provided incorrect SQL Query. -# # Error: {ex_traceback} -# # Query:\n {qry_txt} -# # Output: Add ``` as prefix and ``` as suffix to generated SQL -# """, -# } - DEBUGGING_PROMPT = { "system_prompt": "Act as a SQL expert for {dialect} database", "user_prompt": """ diff --git a/sidekick/prompter.py b/sidekick/prompter.py index be842c3..895bb06 100644 --- a/sidekick/prompter.py +++ b/sidekick/prompter.py @@ -32,25 +32,29 @@ env_settings = toml.load(f"{app_base_path}/sidekick/configs/env.toml") db_dialect = env_settings["DB-DIALECT"]["DB_TYPE"] model_name = env_settings["MODEL_INFO"]["MODEL_NAME"] -h2o_remote_url = env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_REMOTE_URL"] -h2o_key = env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_API_KEY"] +h2o_remote_url = env_settings["MODEL_INFO"]["H2OGPTE_URL"] +h2o_key = env_settings["MODEL_INFO"]["H2OGPTE_API_TOKEN"] # h2ogpt base model urls -h2ogpt_base_model_url = env_settings["MODEL_INFO"]["H2O_BASE_MODEL_URL"] -h2ogpt_base_model_key = env_settings["MODEL_INFO"]["H2O_BASE_MODEL_API_KEY"] +h2ogpt_base_model_url = env_settings["MODEL_INFO"]["H2OGPT_URL"] +h2ogpt_base_model_key = env_settings["MODEL_INFO"]["H2OGPT_API_TOKEN"] + self_correction_model = env_settings["MODEL_INFO"]["SELF_CORRECTION_MODEL"] +recommendation_model = env_settings["MODEL_INFO"]['RECOMMENDATION_MODEL'] os.environ["TOKENIZERS_PARALLELISM"] = "False" # Env variables -if not os.getenv("H2O_BASE_MODEL_URL"): - os.environ["H2O_BASE_MODEL_URL"] = h2ogpt_base_model_url -if not os.getenv("H2O_BASE_MODEL_API_KEY"): - os.environ["H2O_BASE_MODEL_API_KEY"] = h2ogpt_base_model_key -if not os.getenv("RECOMMENDATION_MODEL_REMOTE_URL"): - os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] = h2o_remote_url -if not os.getenv("RECOMMENDATION_MODEL_API_KEY"): - os.environ["RECOMMENDATION_MODEL_API_KEY"] = h2o_key +if not os.getenv("H2OGPT_URL"): + os.environ["H2OGPT_URL"] = h2ogpt_base_model_url +if not os.getenv("H2OGPT_API_TOKEN"): + os.environ["H2OGPT_API_TOKEN"] = h2ogpt_base_model_key +if not os.getenv("H2OGPTE_URL"): + os.environ["H2OGPTE_URL"] = h2o_remote_url +if not os.getenv("H2OGPTE_API_TOKEN"): + os.environ["H2OGPTE_API_TOKEN"] = h2o_key if not os.getenv("SELF_CORRECTION_MODEL"): os.environ["SELF_CORRECTION_MODEL"] = self_correction_model +if not os.getenv("RECOMMENDATION_MODEL"): + os.environ["RECOMMENDATION_MODEL"] = recommendation_model def color(fore="", back="", text=None): return f"{fore}{back}{text}{Style.RESET_ALL}" @@ -159,8 +163,8 @@ def recommend_suggestions(cache_path: str, table_name: str, n_qs: int=10): r_url = _key = None # First check for keys in env variables logger.debug(f"Checking environment settings ...") - env_url = os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] - env_key = os.environ["RECOMMENDATION_MODEL_API_KEY"] + env_url = os.environ["H2OGPTE_URL"] + env_key = os.environ["H2OGPTE_API_TOKEN"] if env_url and env_key: r_url = env_url _key = env_key @@ -168,8 +172,8 @@ def recommend_suggestions(cache_path: str, table_name: str, n_qs: int=10): # Reload .env info logger.debug(f"Checking configuration file ...") env_settings = toml.load(f"{app_base_path}/sidekick/configs/env.toml") - r_url = env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_REMOTE_URL"] - _key = env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_API_KEY"] + r_url = env_settings["MODEL_INFO"]["H2OGPTE_URL"] + _key = env_settings["MODEL_INFO"]["H2OGPTE_API_TOKEN"] else: raise Exception("Model url or key is missing.") @@ -666,8 +670,8 @@ def ask( logger.debug(f"Attempt: {attempt+1}") _tmp = err.split("\n") _err = _tmp[0].split("Error occurred:")[1] if len(_tmp) > 0 else None - env_url = os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] - env_key = os.environ["RECOMMENDATION_MODEL_API_KEY"] + env_url = os.environ["H2OGPTE_URL"] + env_key = os.environ["H2OGPTE_API_TOKEN"] corr_sql = sql_g.self_correction(input_query=_val, error_msg=_err, remote_url=env_url, client_key=env_key) q_res, err = DBConfig.execute_query(query=corr_sql) if not 'Error occurred'.lower() in str(err).lower(): diff --git a/sidekick/query.py b/sidekick/query.py index 03ce4bb..6a23600 100644 --- a/sidekick/query.py +++ b/sidekick/query.py @@ -444,7 +444,7 @@ def generate_sql( else: if self.h2ogpt_client is None: # Check if env variable has info about remote hosting - remote_h2ogpt_base_url = os.environ.get("H2O_BASE_MODEL_URL", None) + remote_h2ogpt_base_url = os.environ.get("H2OGPT_URL", None) if model_name == 'h2ogpt-sql-sqlcoder-34b-alpha': remote_h2ogpt_base_url = f"{remote_h2ogpt_base_url}:5000/v1" elif model_name == 'h2ogpt-sql-sqlcoder2': @@ -453,7 +453,7 @@ def generate_sql( remote_h2ogpt_base_url = f"{remote_h2ogpt_base_url}:5002/v1" else: remote_h2ogpt_base_url = None - remote_h2ogpt_key = os.environ.get("H2O_BASE_MODEL_API_KEY", None) + remote_h2ogpt_key = os.environ.get("H2OGPT_API_TOKEN", None) _api_key = remote_h2ogpt_key if remote_h2ogpt_key else "EMPTY" if remote_h2ogpt_base_url: client_args = dict(base_url=remote_h2ogpt_base_url, api_key=_api_key, timeout=20.0) @@ -784,8 +784,8 @@ def generate_sql( except (sqlglot.errors.ParseError, ValueError, RuntimeError) as e: _, ex_value, ex_traceback = sys.exc_info() logger.info(f"Attempting to fix syntax error ...,\n {e}") - env_url = os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] - env_key = os.environ["RECOMMENDATION_MODEL_API_KEY"] + env_url = os.environ["H2OGPTE_URL"] + env_key = os.environ["H2OGPTE_API_TOKEN"] try: result = self.self_correction(input_query=res, error_msg=str(ex_traceback), remote_url=env_url, client_key=env_key) except Exception as se: diff --git a/sidekick/utils.py b/sidekick/utils.py index 191356d..8ea997d 100644 --- a/sidekick/utils.py +++ b/sidekick/utils.py @@ -548,8 +548,8 @@ def check_vulnerability(input_query: str): # Step 2 is optional, if remote url is provided, check for SQL injection patterns in the generated SQL code via LLM # Currently, only support only for models as an endpoints logger.debug(f"Requesting additional scan using configured models") - remote_url = os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] - api_key = os.environ["RECOMMENDATION_MODEL_API_KEY"] + remote_url = os.environ["H2OGPTE_URL"] + api_key = os.environ["H2OGPTE_API_TOKEN"] _system_prompt = GUARDRAIL_PROMPT["system_prompt"].strip() output_schema = """{ @@ -618,12 +618,13 @@ def generate_suggestions(remote_url, client_key:str, column_names: list, n_qs: i input_prompt = RECOMMENDATION_PROMPT.format(data_schema=column_info, n_questions=n_qs ) + recommender_model = os.getenv("RECOMMENDATION_MODEL", "h2oai/h2ogpt-4096-llama2-70b-chat") client = H2OGPTE(address=remote_url, api_key=client_key) text_completion = client.answer_question( system_prompt=f"Act as a data analyst, based on below data schema help answer the question", text_context_list=[], question=input_prompt, - llm='h2oai/h2ogpt-4096-llama2-70b-chat' + llm=recommender_model ) _res = text_completion.content.split("\n")[2:] results = "\n".join(_res) diff --git a/ui/app.py b/ui/app.py index 26f624e..896cff2 100644 --- a/ui/app.py +++ b/ui/app.py @@ -1,5 +1,6 @@ import concurrent.futures import gc +import hashlib import json import os from pathlib import Path @@ -21,7 +22,7 @@ # Load the config file and initialize required paths app_base_path = (Path(__file__).parent / "../").resolve() -app_settings = toml.load(f"{app_base_path}/ui/app_config.toml") +app_settings = toml.load(f"{app_base_path}/ui/ui_config.toml") # Below check is to handle the case when the app is running on the h2o.ai cloud or locally base_path = app_base_path if os.path.isdir("./.sidekickvenv/bin/") else "/meta_data" tmp_path = f"{base_path}/var/lib/tmp" @@ -58,23 +59,23 @@ def initialize_models(): async def user_variable(q: Q): db_settings = toml.load(f"{app_base_path}/sidekick/configs/env.toml") - q.user.db_dialect = db_settings["DB-DIALECT"]["DB_TYPE"] - q.user.host_name = db_settings["LOCAL_DB_CONFIG"]["HOST_NAME"] - q.user.user_name = db_settings["LOCAL_DB_CONFIG"]["USER_NAME"] - q.user.password = db_settings["LOCAL_DB_CONFIG"]["PASSWORD"] - q.user.db_name = db_settings["LOCAL_DB_CONFIG"]["DB_NAME"] - q.user.port = db_settings["LOCAL_DB_CONFIG"]["PORT"] + q.client.db_dialect = db_settings["DB-DIALECT"]["DB_TYPE"] + q.client.host_name = db_settings["LOCAL_DB_CONFIG"]["HOST_NAME"] + q.client.user_name = db_settings["LOCAL_DB_CONFIG"]["USER_NAME"] + q.client.password = db_settings["LOCAL_DB_CONFIG"]["PASSWORD"] + q.client.db_name = db_settings["LOCAL_DB_CONFIG"]["DB_NAME"] + q.client.port = db_settings["LOCAL_DB_CONFIG"]["PORT"] tables, tables_info = get_table_keys(f"{tmp_path}/data/tables.json", None) table_info = tables_info[tables[0]] if len(tables) > 0 else None - q.user.table_info_path = table_info["schema_info_path"] if len(tables) > 0 else None - q.user.table_samples_path = table_info["samples_path"] if len(tables) > 0 else None - q.user.sample_qna_path = table_info["samples_qa"] if len(tables) > 0 else None - q.user.table_name = tables[0] if len(tables) > 0 else None + q.client.table_info_path = table_info["schema_info_path"] if len(tables) > 0 else None + q.client.table_samples_path = table_info["samples_path"] if len(tables) > 0 else None + q.client.sample_qna_path = table_info["samples_qa"] if len(tables) > 0 else None + q.client.table_name = tables[0] if len(tables) > 0 else None - q.user.model_choices = MODEL_CHOICE_MAP_DEFAULT - q.user.eval_mode = False + q.client.model_choices = MODEL_CHOICE_MAP_DEFAULT + q.client.eval_mode = False async def client_variable(q: Q): @@ -129,15 +130,15 @@ async def chat(q: Q): with open(f"{tmp_path}/data/tables.json", "r") as json_file: meta_data = json.load(json_file) for table in tables: - original_name = meta_data[table].get("original_name", q.user.original_name) + original_name = meta_data[table].get("original_name", q.client.original_name) table_names.append(ui.choice(table, f"{original_name}")) - MODEL_CHOICE_MAP = q.user.model_choices + MODEL_CHOICE_MAP = q.client.model_choices model_choices = [ui.choice(_key, _key) for _key in MODEL_CHOICE_MAP.keys()] - q.user.model_choice_dropdown = q.args.model_choice_dropdown = "h2ogpt-sql-sqlcoder-34b-alpha" + q.client.model_choice_dropdown = q.args.model_choice_dropdown = "h2ogpt-sql-sqlcoder-34b-alpha" task_choices = [ui.choice("q_a", "Ask Questions"), ui.choice("sqld", "Debugging")] - q.user.task_choice_dropdown = q.args.task_dropdown = "q_a" + q.client.task_choice_dropdown = q.args.task_dropdown = "q_a" chat_card_command_items = [ ui.command(name="download_accept", label="Download QnA history", icon="Download"), @@ -167,7 +168,7 @@ async def chat(q: Q): label="Table", required=True, choices=table_names, - value=q.user.table_name if q.user.table_name else None, + value=q.client.table_name if q.client.table_name else None, trigger=True, ), ui.dropdown( @@ -175,7 +176,7 @@ async def chat(q: Q): label="Model Choice", required=True, choices=model_choices, - value=q.user.model_choice_dropdown if q.user.model_choice_dropdown else None, + value=q.client.model_choice_dropdown if q.client.model_choice_dropdown else None, trigger=True, ), ], @@ -192,7 +193,7 @@ async def chat(q: Q): label="Mode", required=True, choices=task_choices, - value=q.user.task_choice_dropdown if q.user.task_choice_dropdown else None, + value=q.client.task_choice_dropdown if q.client.task_choice_dropdown else None, trigger=True, ) ], @@ -274,13 +275,13 @@ async def chatbot(q: Q): # Append user message. q.page["chat_card"].data += [q.args.chatbot, True] - if q.page["select_tables"].table_dropdown.value is None or q.user.table_name is None: + if q.page["select_tables"].table_dropdown.value is None or q.client.table_name is None: q.page["chat_card"].data += ["Please select a table to continue!", False] return if ( - f"Table {q.user.table_dropdown} selected" in q.args.chatbot - or f"Model {q.user.model_choice_dropdown} selected" in q.args.chatbot + f"Table {q.client.table_dropdown} selected" in q.args.chatbot + or f"Model {q.client.model_choice_dropdown} selected" in q.args.chatbot or f"mode selected" in q.args.chatbot ): return @@ -288,7 +289,7 @@ async def chatbot(q: Q): # Append bot response. question = f"{q.args.chatbot}" # Check on task choice. - if q.user.task_dropdown == "sqld" or q.args.task_dropdown == "sqld": + if q.client.task_dropdown == "sqld" or q.args.task_dropdown == "sqld": question = f"Execute SQL:\n{q.args.chatbot}" q.args.debug_mode = True logging.info(f"Question: {question}") @@ -298,28 +299,29 @@ async def chatbot(q: Q): # 2. "Try harder mode (THM)" Slow approach by using the diverse beam search llm_response = None try: - if q.args.chatbot and ("preview data" in q.args.chatbot.lower() or "data preview" in q.args.chatbot.lower() or "preview" in q.args.chatbot.lower()) or f"preview {q.user.table_name}" in q.args.chatbot.lower(): - _response_df = data_preview(q.user.table_name) + if q.args.chatbot and ("preview data" in q.args.chatbot.lower() or "data preview" in q.args.chatbot.lower() or "preview" in q.args.chatbot.lower()) or f"preview {q.client.table_name}" in q.args.chatbot.lower(): + _response_df = data_preview(q.client.table_name) # Format as markdown table if not _response_df.empty: df_markdown = make_markdown_table(fields = _response_df.columns.tolist(), rows=_response_df.values.tolist()) n_cols = len(_response_df.columns) llm_response = f"The selected dataset has total number of {n_cols} columns.\nBelow is quick preview:\n{df_markdown}" elif q.args.chatbot and (q.args.chatbot.lower() == "recommend questions" or q.args.chatbot.lower() == "recommend qs"): - llm_response = recommend_suggestions(cache_path=q.user.table_info_path, table_name=q.user.table_name) + llm_response = recommend_suggestions(cache_path=q.client.table_info_path, table_name=q.client.table_name) if not llm_response: llm_response = "Something went wrong, check the API Keys provided." logging.info(f"Recommended Questions:\n{llm_response}") + q.args.chatbot = None elif q.args.chatbot and q.args.chatbot.lower() == "db setup": llm_response, err = db_setup( - db_name=q.user.db_name, - hostname=q.user.host_name, - user_name=q.user.user_name, - password=q.user.password, - port=q.user.port, - table_info_path=q.user.table_info_path, - table_samples_path=q.user.table_samples_path, - table_name=q.user.table_name, + db_name=q.client.db_name, + hostname=q.client.host_name, + user_name=q.client.user_name, + password=q.client.password, + port=q.client.port, + table_info_path=q.client.table_info_path, + table_samples_path=q.client.table_samples_path, + table_name=q.client.table_name, ) elif q.args.chatbot and q.args.chatbot.lower() == "regenerate" or q.args.regenerate: # Attempts to regenerate response on the last supplied query @@ -327,10 +329,10 @@ async def chatbot(q: Q): if q.client.query is not None and q.client.query.strip() != "": llm_response, alt_response, err = ask( question=q.client.query, - sample_queries_path=q.user.sample_qna_path, - table_info_path=q.user.table_info_path, - table_name=q.user.table_name, - model_name=q.user.model_choice_dropdown, + sample_queries_path=q.client.sample_qna_path, + table_info_path=q.client.table_info_path, + table_name=q.client.table_name, + model_name=q.client.model_choice_dropdown, is_regenerate=True, is_regen_with_options=False ) @@ -346,10 +348,10 @@ async def chatbot(q: Q): if q.client.query is not None and q.client.query.strip() != "": llm_response, alt_response, err = ask( question=q.client.query, - sample_queries_path=q.user.sample_qna_path, - table_info_path=q.user.table_info_path, - table_name=q.user.table_name, - model_name=q.user.model_choice_dropdown, + sample_queries_path=q.client.sample_qna_path, + table_info_path=q.client.table_info_path, + table_name=q.client.table_name, + model_name=q.client.model_choice_dropdown, is_regenerate=False, is_regen_with_options=True ) @@ -368,10 +370,10 @@ async def chatbot(q: Q): q.client.query = question with concurrent.futures.ThreadPoolExecutor() as pool: llm_response, alt_response, err = await q.exec(pool, ask, question=q.client.query, - sample_queries_path=q.user.sample_qna_path, - table_info_path=q.user.table_info_path, - table_name=q.user.table_name, - model_name=q.user.model_choice_dropdown, + sample_queries_path=q.client.sample_qna_path, + table_info_path=q.client.table_info_path, + table_name=q.client.table_name, + model_name=q.client.model_choice_dropdown, debug_mode=q.args.debug_mode ) llm_response = "\n".join(llm_response) @@ -388,11 +390,11 @@ async def chatbot(q: Q): async def submit_url_keys(q: Q): # Read/Update env variable if q.args.textbox_remote_url: - env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_REMOTE_URL"] = q.args.textbox_remote_url - os.environ["RECOMMENDATION_MODEL_REMOTE_URL"] = q.args.textbox_remote_url + env_settings["MODEL_INFO"]["H2OGPTE_URL"] = q.args.textbox_remote_url + os.environ["H2OGPTE_URL"] = q.args.textbox_remote_url if q.args.textbox_h2o_api_key: - env_settings["MODEL_INFO"]["RECOMMENDATION_MODEL_API_KEY"] = q.args.textbox_h2o_api_key - os.environ["RECOMMENDATION_MODEL_API_KEY"] = q.args.textbox_h2o_api_key + env_settings["MODEL_INFO"]["H2OGPTE_API_TOKEN"] = q.args.textbox_h2o_api_key + os.environ["H2OGPTE_API_TOKEN"] = q.args.textbox_h2o_api_key if q.args.textbox_openai_api_key: env_settings["MODEL_INFO"]["OPENAI_API_KEY"] = q.args.textbox_openai_api_key os.environ["OPENAI_API_KEY"] = q.args.textbox_openai_api_key @@ -467,20 +469,20 @@ async def fileupload(q: Q): logging.info(f"Table metadata: {table_metadata}") update_tables(f"{tmp_path}/data/tables.json", table_metadata) - q.user.table_name = usr_table_name - q.user.table_samples_path = usr_samples_path - q.user.table_info_path = usr_info_path - q.user.sample_qna_path = usr_sample_qa + q.client.table_name = usr_table_name + q.client.table_samples_path = usr_samples_path + q.client.table_info_path = usr_info_path + q.client.sample_qna_path = usr_sample_qa n_rows, db_resp = db_setup( - db_name=q.user.db_name, - hostname=q.user.host_name, - user_name=q.user.user_name, - password=q.user.password, - port=q.user.port, - table_info_path=q.user.table_info_path, - table_samples_path=q.user.table_samples_path, - table_name=q.user.table_name, + db_name=q.client.db_name, + hostname=q.client.host_name, + user_name=q.client.user_name, + password=q.client.password, + port=q.client.port, + table_info_path=q.client.table_info_path, + table_samples_path=q.client.table_samples_path, + table_name=q.client.table_name, ) logging.info(f"DB updates: \n {db_resp}") if "error" in str(db_resp).lower(): @@ -504,7 +506,7 @@ async def on_settings(q: Q): clear_cards(q) # When routing, drop all the cards except of the main ones (header, sidebar, meta). add_card(q, "settings_header", ui.form_card(box="horizontal", title="Configure", items=[])) - toggle_state = q.user.eval_mode if q.user.eval_mode else False + toggle_state = q.client.eval_mode if q.client.eval_mode else False add_card( q, "settings", @@ -626,23 +628,31 @@ async def submit_table(q: Q): table_name = table_key.lower().replace(" ", "_") _, table_info = get_table_keys(f"{tmp_path}/data/tables.json", table_name) - q.user.table_info_path = table_info["schema_info_path"] - q.user.table_samples_path = table_info["samples_path"] - q.user.sample_qna_path = table_info["samples_qa"] - q.user.table_name = table_key.replace(" ", "_") - q.user.original_name = table_info["original_name"] + q.client.table_info_path = table_info["schema_info_path"] + q.client.table_samples_path = table_info["samples_path"] + q.client.sample_qna_path = table_info["samples_qa"] + q.client.table_name = table_key.replace(" ", "_") + q.client.original_name = table_info["original_name"] q.page["select_tables"].table_dropdown.value = table_name else: - q.page["select_tables"].table_dropdown.value = q.user.table_name + q.page["select_tables"].table_dropdown.value = q.client.table_name await q.page.save() async def init(q: Q) -> None: q.client.timezone = "UTC" q.args.demo_mode = False + q.app.toml = toml.load("app.toml") username, profile_pic = q.auth.username, q.app.persona_path q.page["meta"] = ui.meta_card( + script=heap_analytics( + userid=q.auth.subject, + event_properties=f"{{" + f"version: '{q.app.toml['App']['Version']}', " + f"product: '{q.app.toml['App']['Title']}'" + f"}}", + ), box="", layouts=[ ui.layout( @@ -752,21 +762,21 @@ def upload_demo_examples(q: Q): } update_tables(f"{tmp_path}/data/tables.json", table_metadata) - q.user.org_table_name = org_table_name - q.user.table_name = usr_table_name - q.user.table_samples_path = f"{sample_data_path}/sleep_health_and_lifestyle_dataset.csv" - q.user.table_info_path = f"{sample_data_path}/table_info.jsonl" - q.user.sample_qna_path = None + q.client.org_table_name = org_table_name + q.client.table_name = usr_table_name + q.client.table_samples_path = f"{sample_data_path}/sleep_health_and_lifestyle_dataset.csv" + q.client.table_info_path = f"{sample_data_path}/table_info.jsonl" + q.client.sample_qna_path = None _, db_resp = db_setup( - db_name=q.user.db_name, - hostname=q.user.host_name, - user_name=q.user.user_name, - password=q.user.password, - port=q.user.port, - table_info_path=q.user.table_info_path, - table_samples_path=q.user.table_samples_path, - table_name=q.user.table_name, + db_name=q.client.db_name, + hostname=q.client.host_name, + user_name=q.client.user_name, + password=q.client.password, + port=q.client.port, + table_info_path=q.client.table_info_path, + table_samples_path=q.client.table_samples_path, + table_name=q.client.table_name, ) logging.info(f"DB updated with demo examples: \n {db_resp}") q.args.table_dropdown = usr_table_name @@ -781,18 +791,18 @@ async def on_event(q: Q): q.args.chatbot = "try harder" elif q.args.regenerate: q.args.chatbot = "regenerate" - q.user.eval_mode = False + q.client.eval_mode = False if q.args.suggest: q.args.chatbot = "Recommend questions" await chatbot(q) event_handled = True if q.args.eval_mode: - q.user.eval_mode = True - q.user.model_choices = MODEL_CHOICE_MAP_EVAL_MODE + q.client.eval_mode = True + q.client.model_choices = MODEL_CHOICE_MAP_EVAL_MODE await chat(q) event_handled = True - if q.args.table_dropdown and not q.args.chatbot and q.user.table_name != q.args.table_dropdown: + if q.args.table_dropdown and not q.args.chatbot and q.client.table_name != q.args.table_dropdown: logging.info(f"User selected table: {q.args.table_dropdown}") await submit_table(q) q.args.chatbot = f"Table {q.args.table_dropdown} selected" @@ -800,20 +810,20 @@ async def on_event(q: Q): event_handled = True if ( q.args.model_choice_dropdown - and not q.args.chatbot and q.args.model_choice_dropdown != q.user.model_choice_dropdown + and not q.args.chatbot and q.args.model_choice_dropdown != q.client.model_choice_dropdown ): logging.info(f"User selected model type: {q.args.model_choice_dropdown}") - q.user.model_choice_dropdown = q.args.model_choice_dropdown - q.page["select_tables"].model_choice_dropdown.value = q.user.model_choice_dropdown - q.args.chatbot = f"Model {q.user.model_choice_dropdown} selected" + q.client.model_choice_dropdown = q.args.model_choice_dropdown + q.page["select_tables"].model_choice_dropdown.value = q.client.model_choice_dropdown + q.args.chatbot = f"Model {q.client.model_choice_dropdown} selected" # Refresh response is triggered when user selects a table via dropdown q.args.model_choice_dropdown = None event_handled = True - if q.args.task_dropdown and not q.args.chatbot and q.user.task_dropdown != q.args.task_dropdown: + if q.args.task_dropdown and not q.args.chatbot and q.client.task_dropdown != q.args.task_dropdown: logging.info(f"User selected task: {q.args.task_dropdown}") - q.user.task_dropdown = q.args.task_dropdown - q.page["task_choice"].task_dropdown.value = q.user.task_dropdown - q.args.chatbot = f"'{TASK_CHOICE[q.user.task_dropdown]}' mode selected" + q.client.task_dropdown = q.args.task_dropdown + q.page["task_choice"].task_dropdown.value = q.client.task_dropdown + q.args.chatbot = f"'{TASK_CHOICE[q.client.task_dropdown]}' mode selected" q.args.task_dropdown = None # Refresh response is triggered when user selects a table via dropdown event_handled = True @@ -825,7 +835,7 @@ async def on_event(q: Q): question = q.client.query _val = q.client.llm_response # Currently, any manual input by the user is a Question by default - table_name = q.user.table_name if q.user.table_name else "default" + table_name = q.client.table_name if q.client.table_name else "default" _is_invalid = True if q.args.save_rejected_conversation else False _msg = ( "Conversation saved successfully!" @@ -849,12 +859,12 @@ async def on_event(q: Q): q.page["chat_card"].data += [_msg, False] event_handled = True elif q.args.download_accept: - result_path = f"{base_path}/var/lib/tmp/.cache/{q.user.table_name}/history.jsonl" + result_path = f"{base_path}/var/lib/tmp/.cache/{q.client.table_name}/history.jsonl" # Check if path exists # If the model selected is GPT models from openAI then disable download # We don't want to use those for further improvements externally. - if Path(result_path).exists() and "gpt-4" not in q.user.model_choice_dropdown and "gpt-3.5-turbo" not in q.user.model_choice_dropdown: - logging.info(f"Downloading accepted QnA history for table: {q.user.table_name}") + if Path(result_path).exists() and "gpt-4" not in q.client.model_choice_dropdown and "gpt-3.5-turbo" not in q.client.model_choice_dropdown: + logging.info(f"Downloading accepted QnA history for table: {q.client.table_name}") (server_path,) = await q.site.upload([result_path]) q.page["meta"].script = ui.inline_script(f'window.open("{server_path}", "_blank");') os.remove(result_path) @@ -863,9 +873,9 @@ async def on_event(q: Q): _msg = "No history found!" q.page["chat_card"].data += [_msg, False] event_handled = True - elif q.args.download_reject and "gpt-4" not in q.user.model_choice_dropdown and "gpt-3.5" not in q.user.model_choice_dropdown: - logging.info(f"Downloading rejected QnA history for table: {q.user.table_name}") - result_path = f"{base_path}/var/lib/tmp/.cache/{q.user.table_name}/invalid/history.jsonl" + elif q.args.download_reject and "gpt-4" not in q.client.model_choice_dropdown and "gpt-3.5" not in q.client.model_choice_dropdown: + logging.info(f"Downloading rejected QnA history for table: {q.client.table_name}") + result_path = f"{base_path}/var/lib/tmp/.cache/{q.client.table_name}/invalid/history.jsonl" if Path(result_path).exists(): (server_path,) = await q.site.upload([result_path]) q.page["meta"].script = ui.inline_script(f'window.open("{server_path}", "_blank");') @@ -882,7 +892,7 @@ async def on_event(q: Q): logging.info(f"Switching to demo mode!") # If demo datasets are not present, register them. upload_demo_examples(q) - logging.info(f"Demo dataset selected: {q.user.table_name}") + logging.info(f"Demo dataset selected: {q.client.table_name}") await submit_table(q) sample_qs = """ Data description: The Sleep Health and Lifestyle Dataset comprises 400 rows and 13 columns, @@ -916,6 +926,28 @@ async def on_event(q: Q): return event_handled +# Record analytics and usage +def heap_analytics(userid, event_properties=None) -> ui.inline_script: + + if "HEAP_ID" not in os.environ: + return + + heap_id = os.getenv("HEAP_ID") + script = f""" +window.heap=window.heap||[],heap.load=function(e,t){{window.heap.appid=e,window.heap.config=t=t||{{}};var r=document.createElement("script");r.type="text/javascript",r.async=!0,r.src="https://cdn.heapanalytics.com/js/heap-"+e+".js";var a=document.getElementsByTagName("script")[0];a.parentNode.insertBefore(r,a);for(var n=function(e){{return function(){{heap.push([e].concat(Array.prototype.slice.call(arguments,0)))}}}},p=["addEventProperties","addUserProperties","clearEventProperties","identify","resetIdentity","removeEventProperty","setEventProperties","track","unsetEventProperty"],o=0;o