22import io
33import logging
44import os
5+ import pprint
56import re
67import shlex
78import sys
89from runpy import run_module
9- from typing import Optional , Tuple
1010from time import time
11+ from typing import Optional , Tuple
1112
1213import click
13-
14- try :
15- import llm
16- from llm .cli import cli
17-
18- LLM_CLI_COMMANDS = list (cli .commands .keys ())
19- MODELS = {x .model_id : None for x in llm .get_models ()}
20- except ImportError :
21- llm = None
22- cli = None
23- LLM_CLI_COMMANDS = []
24- MODELS = {}
14+ import llm
15+ from llm .cli import cli
2516
2617from . import export
27- from .main import parse_special_command
18+ from .main import Verbosity , parse_special_command
2819
2920log = logging .getLogger (__name__ )
3021
22+ LLM_TEMPLATE_NAME = "litecli-llm-template"
23+ LLM_CLI_COMMANDS = list (cli .commands .keys ())
24+ MODELS = {x .model_id : None for x in llm .get_models ()}
25+
3126
32- def run_external_cmd (cmd , * args , capture_output = False , restart_cli = False , raise_exception = True ):
27+ def run_external_cmd (cmd , * args , capture_output = False , restart_cli = False , raise_exception = True ) -> Tuple [ int , str ] :
3328 original_exe = sys .executable
3429 original_args = sys .argv
3530
@@ -55,6 +50,13 @@ def run_external_cmd(cmd, *args, capture_output=False, restart_cli=False, raise_
5550 raise RuntimeError (buffer .getvalue ())
5651 else :
5752 raise RuntimeError (f"Command { cmd } failed with exit code { code } ." )
53+ except Exception as e :
54+ code = 1
55+ if raise_exception :
56+ if capture_output :
57+ raise RuntimeError (buffer .getvalue ())
58+ else :
59+ raise RuntimeError (f"Command { cmd } failed: { e } " )
5860
5961 if restart_cli and code == 0 :
6062 os .execv (original_exe , [original_exe ] + original_args )
@@ -153,45 +155,46 @@ def __init__(self, results=None):
153155"""
154156
155157_SQL_CODE_FENCE = r"```sql\n(.*?)\n```"
156- PROMPT = """A SQLite database has the following schema:
158+ PROMPT = """
159+ You are a helpful assistant who is a SQLite expert. You are embedded in a SQLite
160+ cli tool called litecli.
157161
158- $db_schema
162+ Answer this question:
163+
164+ $question
159165
160- Here is a sample row of data from each table: $sample_data
166+ Use the following context if it is relevant to answering the question. If the
167+ question is not about the current database then ignore the context.
161168
162- Use the provided schema and the sample data to construct a SQL query that
163- can be run in SQLite3 to answer
169+ You are connected to a SQLite database with the following schema:
164170
165- $question
171+ $db_schema
172+
173+ Here is a sample row of data from each table:
174+
175+ $sample_data
166176
167- Explain the reason for choosing each table in the SQL query you have
168- written. Keep the explanation concise.
169- Finally include a sql query in a code fence such as this one:
177+ If the answer can be found using a SQL query, include a sql query in a code
178+ fence such as this one:
170179
171180```sql
172181SELECT count(*) FROM table_name;
173182```
183+ Keep your explanation concise and focused on the question asked.
174184"""
175185
176186
177- def initialize_llm ():
178- # Initialize the LLM library.
179- if click .confirm ("This feature requires additional libraries. Install LLM library?" , default = True ):
180- click .echo ("Installing LLM library. Please wait..." )
181- run_external_cmd ("pip" , "install" , "--quiet" , "llm" , restart_cli = True )
182-
183-
184187def ensure_litecli_template (replace = False ):
185188 """
186189 Create a template called litecli with the default prompt.
187190 """
188191 if not replace :
189192 # Check if it already exists.
190- code , _ = run_external_cmd ("llm" , "templates" , "show" , "litecli" , capture_output = True , raise_exception = False )
193+ code , _ = run_external_cmd ("llm" , "templates" , "show" , LLM_TEMPLATE_NAME , capture_output = True , raise_exception = False )
191194 if code == 0 : # Template already exists. No need to create it.
192195 return
193196
194- run_external_cmd ("llm" , PROMPT , "--save" , "litecli" )
197+ run_external_cmd ("llm" , PROMPT , "--save" , LLM_TEMPLATE_NAME )
195198 return
196199
197200
@@ -205,12 +208,10 @@ def handle_llm(text, cur) -> Tuple[str, Optional[str], float]:
205208 FinishIteration() which will be caught by the main loop AND print any
206209 output that was supplied (or None).
207210 """
208- _ , verbose , arg = parse_special_command (text )
209-
210- # LLM is not installed.
211- if llm is None :
212- initialize_llm ()
213- raise FinishIteration (None )
211+ # Determine invocation mode: regular, verbose (+), or succinct (-)
212+ _ , mode , arg = parse_special_command (text )
213+ is_verbose = mode is Verbosity .VERBOSE
214+ is_succinct = mode is Verbosity .SUCCINCT
214215
215216 if not arg .strip (): # No question provided. Print usage and bail.
216217 output = [(None , None , None , USAGE )]
@@ -268,20 +269,23 @@ def handle_llm(text, cur) -> Tuple[str, Optional[str], float]:
268269 output = [(None , None , None , result )]
269270 raise FinishIteration (output )
270271
271- return result if verbose else "" , sql , end - start
272+ context = "" if is_succinct else result
273+ return context , sql , end - start
272274 else :
273275 run_external_cmd ("llm" , * args , restart_cli = restart )
274276 raise FinishIteration (None )
275277
276278 try :
277279 ensure_litecli_template ()
278- # Measure end to end llm command invocation.
279- # This measures the internal DB command to pull the schema and llm command
280+ # Measure end-to-end LLM command invocation (schema gathering and LLM call)
280281 start = time ()
281- context , sql = sql_using_llm (cur = cur , question = arg , verbose = verbose )
282+ result , sql , prompt_text = sql_using_llm (cur = cur , question = arg , verbose = is_verbose )
282283 end = time ()
283- if not verbose :
284- context = ""
284+ context = "" if is_succinct else result
285+ if is_verbose and prompt_text is not None :
286+ click .echo ("LLM Prompt:" )
287+ click .echo (prompt_text )
288+ click .echo ("---" )
285289 return context , sql , end - start
286290 except Exception as e :
287291 # Something went wrong. Raise an exception and bail.
@@ -298,7 +302,7 @@ def is_llm_command(command) -> bool:
298302
299303
300304@export
301- def sql_using_llm (cur , question = None , verbose = False ) -> Tuple [str , Optional [str ]]:
305+ def sql_using_llm (cur , question = None , verbose = False ) -> Tuple [str , Optional [str ], Optional [ str ] ]:
302306 if cur is None :
303307 raise RuntimeError ("Connect to a datbase and try again." )
304308 schema_query = """
@@ -331,7 +335,7 @@ def sql_using_llm(cur, question=None, verbose=False) -> Tuple[str, Optional[str]
331335
332336 args = [
333337 "--template" ,
334- "litecli" ,
338+ LLM_TEMPLATE_NAME ,
335339 "--param" ,
336340 "db_schema" ,
337341 db_schema ,
@@ -347,9 +351,16 @@ def sql_using_llm(cur, question=None, verbose=False) -> Tuple[str, Optional[str]
347351 _ , result = run_external_cmd ("llm" , * args , capture_output = True )
348352 click .echo ("Received response from the llm command" )
349353 match = re .search (_SQL_CODE_FENCE , result , re .DOTALL )
350- if match :
351- sql = match .group (1 ).strip ()
352- else :
353- sql = ""
354-
355- return result , sql
354+ sql = match .group (1 ).strip () if match else ""
355+
356+ # When verbose, build and return the rendered prompt text
357+ prompt_text = None
358+ if verbose :
359+ # Render the prompt by substituting schema, sample_data, and question
360+ prompt_text = PROMPT
361+ prompt_text = prompt_text .replace ("$db_schema" , db_schema )
362+ prompt_text = prompt_text .replace ("$sample_data" , pprint .pformat (sample_data ))
363+ prompt_text = prompt_text .replace ("$question" , question or "" )
364+ if verbose :
365+ return result , sql , prompt_text
366+ return result , sql , None
0 commit comments