๐Ÿฆ” QIL+LLM: The Code

Intent handling BEFORE sending the prompt to the LLM

๐Ÿ”™ Back to Project Description

๐Ÿ’พ Download My Code

I implemented this as a QIL function set. To download the code: Click Here.

The file includes a header comment showing how to use the class to qil your user prompts before sending them to the LLM.

๐Ÿ“‹ Overview

QIL+LLM simplifies prompt augmentation with a single, elegant function call. The system automatically detects matching utterances in user prompts and replaces them with pre-computed function outputs, eliminating unnecessary LLM requests.

Core API: Call the qil() function with your original prompt and receive an augmented prompt ready for LLM processing.

The function handles four key operations:

  1. Sentence Splitting: Intelligently divides the prompt into logical segments
  2. Pattern Matching: Compares each segment against registered utterances
  3. Function Execution: Calls associated functions for matched patterns
  4. Prompt Reconstruction: Rebuilds the prompt with computed results

An important optimization: if QIL handles the entire prompt, bypass the LLM entirely and return the result directly to the user.

After loading, _QIL_REGISTRY becomes a dictionary mapping utterances to function objects.

Depending on your needs, you could load the configuration as needed or per user session.

๐Ÿชš Sentence Splitting

Sentence splitting is deceptively tricky. Real user prompts often lack punctuation, contain runโ€‘ons, or mix multiple intents in a single line. The goal here isnโ€™t perfection. itโ€™s reliability without involving an LLM.

The following approach uses regexโ€‘based heuristics to segment text into meaningful units:

import re def sentence_splitter(text): starters = ["i", "what", "show", "get", "is", "can", "please", "summarize", "list"] starters_regex = "|".join(starters) # We wrap the punctuation part in () so it's included in the split result # We use a non-capturing group for the 'and/also' logic # Move (?i) to the very start pattern = rf"(?i)([\.\!\?\n]+|(?<=\s)(?:and|also|then)(?=\s+(?:{starters_regex})\b))" # re.split with a capturing group returns: [text, delim, text, delim...] parts = re.split(pattern, text) sentences = [] # Loop through and merge the text with its following delimiter for i in range(0, len(parts)-1, 2): combined = (parts[i] + parts[i+1]).strip() if len(combined) > 1: sentences.append(combined) # Catch any trailing text that didn't have a delimiter if len(parts) % 2 != 0 and len(parts[-1].strip()) > 1: sentences.append(parts[-1].strip()) return sentences

You provide a prompt, and you get back a list of sentences. Simple, predictable, and fast.

๐Ÿ“ž Calling a Function

Suppose you have a match. You need to call a function. This is easiest to do with a function calling function.

def execute_qil_function(func_name, extracted_value=None): # Get the function object from the global namespace # Note: Ensure your tool functions (get_balance, etc.) are imported or defined here func = globals().get(func_name) if not func: print(f"Error: Function {func_name} not found.") return None try: if extracted_value: # Requirements: var name must be lowercase in call # We assume the parameter name is 'customerid' for [CUSTOMERID] # This logic can be made more generic if you have multiple variables return func(customerid=extracted_value) else: return func() except Exception as e: print(f"Execution Error in {func_name}: {e}") return None

This purposely returns None on failure. That indicates that the original sentence should not be replaced with function output. Keep it in the original prompt for the LLM.

๐Ÿ› ๏ธ Processing the Prompt

Now we can use QIL to handle prompt queries without sending them to the LLM. There are many ways to compare an utterance to a sentence.

import re def qil(user_prompt, config_path="lib/qil.conf"): """ Main functional entry point. Returns (augmented_prompt, all_replaced_boolean) """ # Load config if not already loaded or if a custom path is provided global _CONFIG_LOADED if not _CONFIG_LOADED or config_path != "lib/qil.conf": _load_config(config_path) sentences = _sentence_splitter(user_prompt) untagged_count = len(sentences) final_output = user_prompt templates = list(_REGISTRY.keys()) for sentence in sentences: # Normalize the sentence for matching lsentence = re.sub(r'[^\w\s]', '', sentence.lower()) # Use MEWF similarity to find the best matching utterance best_utterance, score = find_best_utterance(lsentence, templates, threshold=95) if best_utterance: func_tuple = _REGISTRY[best_utterance] extracted_val = None # Extract wildcard values if template contains brackets if "[" in best_utterance: val_match = re.search(r'\b\d{3,}\b', sentence) if val_match: extracted_val = val_match.group(0) result_string = _execute_tool(func_tuple, extracted_val) if result_string is not None: final_output = final_output.replace(sentence, str(result_string) + "\n") untagged_count -= 1 return (final_output, untagged_count == 0)

This splits the prompt into sentences. Then, it finds the best matching utterance for every sentence. For each match, it calls the associated function. If the function call returns a value, the original sentence is replaced with the function's output. The updated prompt is returned. I include a Boolean value that is True if every sentance was replaced.

๐Ÿ” Finding a Match

I rally wanted to use a well-known common library to matching sentences to templates. For a while, RapidFuzz worked well, but a close examination showed that the window of false-positives and false-negatives was far too wide. I wrote the following function to mix concepts of Levenshtein distance and Monge-Elkan distance into a single function heavily customized for this project. The accompanying function is a wrapper that returns the best match and score.

import Levenshtein def similarity(user_query, utterance): """ This function calculates similarity between a user query and an utterance template. It is based on existing algorithms for Levenshtein distance and Monge-Elkan similarity. The function returns a similarity score between 0 and 100. """ # Normalize inputs as requested def normalize(text, keep_brackets=False): text = text.lower() if keep_brackets: text = re.sub(r'[^a-z0-9\s\[\]]', '', text) else: text = re.sub(r'[^a-z0-9\s]', '', text) return re.sub(r'\s+', ' ', text).strip() u_raw = normalize(user_query, keep_brackets=False) t_raw = normalize(utterance, keep_brackets=True) u_len = len(u_raw) t_len = len(t_raw) if u_len == 0 and t_len == 0: return 100.0 if u_len == 0 or t_len == 0: return 0.0 u_tokens = u_raw.split() t_tokens = t_raw.split() # 1. Remove Exact Matches (case-sensitive after normalization) # Use list() to avoid modification during iteration common = [] for token in list(u_tokens): if token in t_tokens: common.append(token) for token in common: if token in u_tokens and token in t_tokens: u_tokens.remove(token) t_tokens.remove(token) # 2. Separate Wildcards from Static Template Words wildcards = [t for t in t_tokens if t.startswith('[') and t.endswith(']')] static_templates = [t for t in t_tokens if t not in wildcards] total_cost = 0 # 3. Match Static Template words to closest User words (Greedy Pairing) # Continue until we run out of static template words OR user words while static_templates and u_tokens: # Find the pair with minimum Levenshtein distance min_cost = float('inf') best_t_idx = -1 best_u_idx = -1 for t_idx, t_word in enumerate(static_templates): for u_idx, u_word in enumerate(u_tokens): cost = Levenshtein.distance(t_word, u_word) if cost < min_cost: min_cost = cost best_t_idx = t_idx best_u_idx = u_idx total_cost += min_cost static_templates.pop(best_t_idx) u_tokens.pop(best_u_idx) # 4. Wildcard Fulfillment # Wildcards consume remaining user tokens at 0 cost while wildcards and u_tokens: wildcards.pop() u_tokens.pop(0) # 5. Penalties for unmatched words # Remaining user words total_cost += sum(len(u) for u in u_tokens) # Remaining static template words (already handled above, but kept for clarity) total_cost += sum(len(t) for t in static_templates) # Remaining wildcards (couldn't be matched) total_cost += sum(len(w) for w in wildcards) # 6. Normalize Score (0-100) score = 100 * (u_len + t_len - total_cost) / (u_len + t_len) return max(0.0, score) # Ensure non-negative def find_best_utterance(user_query, utterances, threshold=90): """ Find the best matching utterance for a user query using MEWF similarity. """ if not utterances: return (None, 0) best_utterance = None best_score = 0 for utterance in utterances: score = similarity(user_query, utterance) if score > best_score: best_score = score best_utterance = utterance if best_score >= threshold: return (best_utterance, best_score) else: return (None, best_score)

๐Ÿ“„ License

This project is open source and available under the MIT License.