TabulAIrity 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tabulairity/__init__.py +0 -0
- tabulairity/gsheetconnector.py +149 -0
- tabulairity/scrapertools.py +57 -0
- tabulairity/selfimprovement.py +392 -0
- tabulairity/tabulairity.py +614 -0
- tabulairity-1.1.0.dist-info/METADATA +19 -0
- tabulairity-1.1.0.dist-info/RECORD +10 -0
- tabulairity-1.1.0.dist-info/WHEEL +5 -0
- tabulairity-1.1.0.dist-info/licenses/LICENSE +674 -0
- tabulairity-1.1.0.dist-info/top_level.txt +1 -0
tabulairity/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import gspread
|
|
2
|
+
import feedparser
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import tabulairity as tb
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
|
|
9
|
+
from time import sleep
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
#########################################
|
|
13
|
+
# #
|
|
14
|
+
# GALERTS RSS FEED UTILS #
|
|
15
|
+
# #
|
|
16
|
+
#########################################
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
getGALink = lambda x: x.replace('https://www.google.com/url?rct=j&sa=t&url=','').split('&ct=ga')[0].split('%')[0]
|
|
20
|
+
stripHTML = lambda x: re.sub(r'<.*?>', '', x)
|
|
21
|
+
|
|
22
|
+
def feedToDf(feedURL):
|
|
23
|
+
"""Takes a google alerts feed url and returns an article df"""
|
|
24
|
+
keepCols = ['link','title','published','updated','summary']
|
|
25
|
+
feed = feedparser.parse(feedURL)
|
|
26
|
+
entries = feed['entries']
|
|
27
|
+
if entries == []:
|
|
28
|
+
return pd.DataFrame(keepCols)
|
|
29
|
+
feedDf = pd.DataFrame(entries)[keepCols]
|
|
30
|
+
feedDf.loc[:,'url'] = feedDf.link.apply(getGALink)
|
|
31
|
+
feedDf.summary = feedDf.summary.apply(stripHTML)
|
|
32
|
+
feedDf.title = feedDf.title.apply(stripHTML)
|
|
33
|
+
feedDf = feedDf.drop(['link'],axis=1)
|
|
34
|
+
return feedDf
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def checkViability(pageText):
|
|
38
|
+
"""Checks if a scraped page is viable for analysis"""
|
|
39
|
+
if len(pageText) < 200:
|
|
40
|
+
return False
|
|
41
|
+
failStrs = {'Error:',' 404 ',' 429 ', 'Javascript must be'}
|
|
42
|
+
for failStr in failStrs:
|
|
43
|
+
if failStr in pageText:
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def getGAlerts(alertsDf):
|
|
49
|
+
"""Takes a google alerts feed google sheet and returns a full feeds df with translated titles"""
|
|
50
|
+
feedDfs = []
|
|
51
|
+
for index, row in alertsDf.iterrows():
|
|
52
|
+
feedURL = row['rss feed']
|
|
53
|
+
feedDf = feedToDf(feedURL)
|
|
54
|
+
feedDf['rss feed'] = feedURL
|
|
55
|
+
feedDfs.append(feedDf)
|
|
56
|
+
|
|
57
|
+
mergedFeeds = pd.concat(feedDfs)
|
|
58
|
+
mergedFeeds = mergedFeeds.groupby('url').first().reset_index()
|
|
59
|
+
mergedFeeds = tb.autoTranslate(mergedFeeds,'title')
|
|
60
|
+
mergedFeeds = tb.autoTranslate(mergedFeeds,'summary')
|
|
61
|
+
mergedFeeds = mergedFeeds.merge(alertsDf)
|
|
62
|
+
mergedFeeds['scraped_text'] = mergedFeeds.url.apply(tb.cachePage)
|
|
63
|
+
mergedFeeds['viable_page'] = mergedFeeds.scraped_text.apply(checkViability)
|
|
64
|
+
mergedFeeds['domain'] = mergedFeeds.url.str.split('/').str[2]
|
|
65
|
+
mergedFeeds = mergedFeeds.groupby(['title','domain']).first().reset_index()
|
|
66
|
+
mergedFeeds = mergedFeeds.groupby('url').first().reset_index()
|
|
67
|
+
|
|
68
|
+
return mergedFeeds
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
#########################################
|
|
72
|
+
# #
|
|
73
|
+
# FEED DOWNLOADERS #
|
|
74
|
+
# #
|
|
75
|
+
#########################################
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def gSheetToDf(spreadsheetName: str,
|
|
79
|
+
worksheetName: str,
|
|
80
|
+
config: dict) -> pd.DataFrame:
|
|
81
|
+
"""
|
|
82
|
+
Pulls a table from a specified Google Sheet worksheet into a Pandas DataFrame.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
spreadsheetName: The name of the Google Sheet.
|
|
86
|
+
worksheetName: The name of the specific worksheet (tab) within the sheet.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
A Pandas DataFrame containing the data from the worksheet.
|
|
90
|
+
"""
|
|
91
|
+
print(f"Attempting to read worksheet '{worksheetName}' from spreadsheet '{spreadsheetName}'...")
|
|
92
|
+
try:
|
|
93
|
+
gc = gspread.service_account(filename=config['g_service_json_path'])
|
|
94
|
+
spreadsheet = gc.open(spreadsheetName)
|
|
95
|
+
worksheet = spreadsheet.worksheet(worksheetName)
|
|
96
|
+
data = worksheet.get_all_records()
|
|
97
|
+
df = pd.DataFrame(data)
|
|
98
|
+
|
|
99
|
+
print("Successfully loaded data into DataFrame.")
|
|
100
|
+
return df
|
|
101
|
+
except gspread.exceptions.SpreadsheetNotFound:
|
|
102
|
+
print(f"Error: Spreadsheet '{spreadsheetName}' not found.")
|
|
103
|
+
print("Please check the spreadsheetName variable and ensure you have access.")
|
|
104
|
+
return pd.DataFrame()
|
|
105
|
+
except gspread.exceptions.WorksheetNotFound:
|
|
106
|
+
print(f"Error: Worksheet '{worksheetName}' not found in '{spreadsheetName}'.")
|
|
107
|
+
return pd.DataFrame()
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"An unexpected error occurred: {e}")
|
|
110
|
+
return pd.DataFrame()
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def dfToGSheet(df: pd.DataFrame,
|
|
115
|
+
spreadsheetName: str,
|
|
116
|
+
worksheetName: str,
|
|
117
|
+
config: dict):
|
|
118
|
+
"""
|
|
119
|
+
Pushes a Pandas DataFrame to a new or existing worksheet in a Google Sheet.
|
|
120
|
+
This will overwrite any existing data in the target worksheet.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
df: The Pandas DataFrame to write.
|
|
124
|
+
spreadsheetName: The name of the Google Sheet.
|
|
125
|
+
worksheetName: The name of the worksheet to create/overwrite.
|
|
126
|
+
config: Dictionary containing path to seri
|
|
127
|
+
"""
|
|
128
|
+
print(f"Attempting to write DataFrame to worksheet '{worksheetName}' in '{spreadsheetName}'...")
|
|
129
|
+
try:
|
|
130
|
+
gc = gspread.service_account(filename=config['g_service_json_path'])
|
|
131
|
+
spreadsheet = gc.open(spreadsheetName)
|
|
132
|
+
try:
|
|
133
|
+
worksheet = spreadsheet.worksheet(worksheetName)
|
|
134
|
+
print(f"Worksheet '{worksheetName}' already exists. Clearing it before writing new data.")
|
|
135
|
+
worksheet.clear()
|
|
136
|
+
except gspread.exceptions.WorksheetNotFound:
|
|
137
|
+
# If it doesn't exist, create it
|
|
138
|
+
print(f"Worksheet '{worksheetName}' not found. Creating a new one.")
|
|
139
|
+
worksheet = spreadsheet.add_worksheet(title=worksheetName, rows="1000", cols="50")
|
|
140
|
+
|
|
141
|
+
# Write the DataFrame to the worksheet
|
|
142
|
+
# gspread.utils.dataframe_to_rows returns an iterator, so we convert it to a list
|
|
143
|
+
rows_to_insert = [df.columns.values.tolist()] + df.values.tolist()
|
|
144
|
+
worksheet.update(rows_to_insert, 'A1')
|
|
145
|
+
|
|
146
|
+
print("DataFrame successfully written to the worksheet.")
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"An error occurred while writing to the sheet: {e}")
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from bs4 import BeautifulSoup
|
|
5
|
+
from random import choice, uniform
|
|
6
|
+
from time import sleep
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def scrapePageText(url: str) -> str:
|
|
11
|
+
"""
|
|
12
|
+
Fetches a webpage's HTML, parses it with BeautifulSoup, and extracts
|
|
13
|
+
clean, sentence-structured text content.
|
|
14
|
+
|
|
15
|
+
This function is designed to strip away HTML tags, scripts, styles, and
|
|
16
|
+
non-prose text (like navigation links or footers) to return a clean
|
|
17
|
+
plain text version of the main content.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
url (str): The URL of the webpage to scrape.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: A string containing the cleaned, plain text version of the page,
|
|
24
|
+
or an error message if the page cannot be fetched or parsed.
|
|
25
|
+
"""
|
|
26
|
+
sleep(uniform(0.5,1.5))
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
requestHeaders = [{'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'},
|
|
30
|
+
{"User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0"},
|
|
31
|
+
{'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Safari/605.1.15'}]
|
|
32
|
+
|
|
33
|
+
response = requests.get(url, headers=choice(requestHeaders), timeout=10)
|
|
34
|
+
response.raise_for_status()
|
|
35
|
+
htmlContent = response.text
|
|
36
|
+
|
|
37
|
+
soup = BeautifulSoup(htmlContent, 'html.parser')
|
|
38
|
+
for scriptOrStyle in soup(['script', 'style', 'header', 'footer', 'nav', 'aside']):
|
|
39
|
+
scriptOrStyle.decompose()
|
|
40
|
+
|
|
41
|
+
pageText = soup.get_text()
|
|
42
|
+
textLines = (line.strip() for line in pageText.splitlines())
|
|
43
|
+
|
|
44
|
+
proseLines = []
|
|
45
|
+
for line in textLines:
|
|
46
|
+
if line and line.count(' ') > 2:
|
|
47
|
+
proseLines.append(line)
|
|
48
|
+
|
|
49
|
+
cleanedText = '\n'.join(proseLines)
|
|
50
|
+
cleanedText = re.sub(r'\n\s*\n', '\n\n', cleanedText)
|
|
51
|
+
|
|
52
|
+
return cleanedText.strip()
|
|
53
|
+
|
|
54
|
+
except requests.exceptions.RequestException as e:
|
|
55
|
+
return f"Error: Could not retrieve the webpage. Please check the URL and your connection. Details: {e}"
|
|
56
|
+
except Exception as e:
|
|
57
|
+
return f"An unexpected error occurred: {e}"
|
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import pandas as pd
|
|
3
|
+
from random import randint
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
|
|
6
|
+
import tabulairity as tb
|
|
7
|
+
import gsheetconnector as gs
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
verbosity = 1
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def getSeedParams(randomize, model):
|
|
14
|
+
"""Returns seed text and string depending on model"""
|
|
15
|
+
seedSupported = not model.startswith('gemini')
|
|
16
|
+
if not randomize:
|
|
17
|
+
seed = None
|
|
18
|
+
seedText = ''
|
|
19
|
+
if seedSupported:
|
|
20
|
+
seed = randint(0,9999)
|
|
21
|
+
seedText = ''
|
|
22
|
+
else:
|
|
23
|
+
seed = None
|
|
24
|
+
seedText = f'(random seed = {randint(0,9999)})'
|
|
25
|
+
return seed, seedText
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def getEvaluatorNet(supervisor='gemma3:27b'):
|
|
30
|
+
"""Pulls and preps true/false evaluation net"""
|
|
31
|
+
evaluatorNetDf = gs.gSheetToDf('Google Alerts Trackers',
|
|
32
|
+
'Answer Check Network',
|
|
33
|
+
tb.config)
|
|
34
|
+
evaluatorNetDf.loc[:,'model'] = supervisor
|
|
35
|
+
evaluatorNet = tb.buildChatNet(evaluatorNetDf)
|
|
36
|
+
|
|
37
|
+
return evaluatorNet
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def rewritePrompt(prompt,
|
|
42
|
+
persona,
|
|
43
|
+
model,
|
|
44
|
+
rewritePersona = False,
|
|
45
|
+
intentPrompt = None,
|
|
46
|
+
errorSummary = None,
|
|
47
|
+
randomize = True):
|
|
48
|
+
""""Takes a given prompt and persona and returns an LLM improved version of each"""
|
|
49
|
+
maxTries = 20
|
|
50
|
+
|
|
51
|
+
if intentPrompt is None:
|
|
52
|
+
intentPrompt = ''
|
|
53
|
+
else:
|
|
54
|
+
intentPrompt = ' ' + intentPrompt
|
|
55
|
+
if errorSummary is None:
|
|
56
|
+
errorSummary = ''
|
|
57
|
+
else:
|
|
58
|
+
errorSummary = ' ' + errorSummary
|
|
59
|
+
|
|
60
|
+
seed, seedText = getSeedParams(randomize,model)
|
|
61
|
+
keptAllVars = False
|
|
62
|
+
preservedIntent = False
|
|
63
|
+
originalVars = tb.extractChatVars(prompt)
|
|
64
|
+
tries = 0
|
|
65
|
+
|
|
66
|
+
while not (keptAllVars and preservedIntent) and tries != maxTries:
|
|
67
|
+
seed, seedText = getSeedParams(randomize,model)
|
|
68
|
+
questionPrompt = f"""Rewrite the following prompt text to maximize its performance according to the criteria below.{intentPrompt}{errorSummary}
|
|
69
|
+
|
|
70
|
+
Objectives:
|
|
71
|
+
|
|
72
|
+
* Expand and optimize the prompt so that it consistently produces clean, structured output containing only the required data.
|
|
73
|
+
* Ensure the rewritten prompt strictly forbids inclusion of any markdown, explanations, commentary, variable placeholders, or descriptive text.
|
|
74
|
+
* Explicitly state in the rewritten prompt what must be included and what must not be included in the model’s output.
|
|
75
|
+
* Preserve and correctly utilize all flag values indicated in square brackets [like_this].
|
|
76
|
+
* Preserve and implement the full intent of the prompt.
|
|
77
|
+
* Do not invent or modify any flag values.
|
|
78
|
+
* Do not add or include meta-instructions, comments, or contextual discussion in the final rewritten text.
|
|
79
|
+
|
|
80
|
+
The prompt being improved operates under the system prompt:
|
|
81
|
+
|
|
82
|
+
{persona}
|
|
83
|
+
|
|
84
|
+
Task:
|
|
85
|
+
Rewrite the following prompt text (provided after this instruction block) so that it performs optimally under the criteria above.
|
|
86
|
+
You absolutely must not provide descriptions or commentary.
|
|
87
|
+
{seedText}
|
|
88
|
+
Prompt to improve:
|
|
89
|
+
|
|
90
|
+
{prompt}"""
|
|
91
|
+
|
|
92
|
+
aiPrompt = tb.askChatQuestion(questionPrompt,
|
|
93
|
+
'You are a skilled prompt engineer.',
|
|
94
|
+
model = model,
|
|
95
|
+
seed = seed)
|
|
96
|
+
aiVars = tb.extractChatVars(prompt)
|
|
97
|
+
keptAllVars = aiVars == originalVars
|
|
98
|
+
preservedIntent = validatePromptIntent(aiPrompt,
|
|
99
|
+
intentPrompt.strip(),
|
|
100
|
+
model)
|
|
101
|
+
tries += 1
|
|
102
|
+
|
|
103
|
+
if tries == maxTries and not keptAllVars:
|
|
104
|
+
raise ValueError(f"Rewritten prompts failed to retain chatvars after {maxTries} attempts.")
|
|
105
|
+
elif tries == maxTries and not preservedIntent:
|
|
106
|
+
raise ValueError(f"Rewritten prompts failed to preserve intent after {maxTries} attempts.")
|
|
107
|
+
|
|
108
|
+
if not rewritePersona:
|
|
109
|
+
return aiPrompt, persona
|
|
110
|
+
|
|
111
|
+
personaPrompt = f"""Please consider the following prompt and revise our LLM system text to maximize prompt efficiency if needed.
|
|
112
|
+
The current system text is "{persona}". {intentPrompt}{errorSummary}If you decide to replace it, your replacement should be optimized for use with LLM APIs.
|
|
113
|
+
You must only return the recommended system text for our prompt.
|
|
114
|
+
You absolutely must not provide descriptions or commentary.
|
|
115
|
+
{seedText}
|
|
116
|
+
{prompt}"""
|
|
117
|
+
|
|
118
|
+
aiPersona = tb.askChatQuestion(personaPrompt,
|
|
119
|
+
'You are a skilled prompt engineer.',
|
|
120
|
+
model = model,
|
|
121
|
+
seed = seed)
|
|
122
|
+
|
|
123
|
+
return aiPrompt, aiPersona
|
|
124
|
+
|
|
125
|
+
def summarizeErrors(errors,
|
|
126
|
+
intent,
|
|
127
|
+
model):
|
|
128
|
+
persona = "You are a skilled evaluator."
|
|
129
|
+
seed, seedText = getSeedParams(True,model)
|
|
130
|
+
|
|
131
|
+
errorPrompt = f"""You will be given a bulleted list of previously logged prompt errors.
|
|
132
|
+
|
|
133
|
+
Your task is to:
|
|
134
|
+
1. Write a 1–2 sentence summary describing the general nature or cause of these errors.
|
|
135
|
+
- Your summary must begin with the phrase:
|
|
136
|
+
"This prompt has had issues with"
|
|
137
|
+
2. Follow the summary with a single sentence suggesting how the prompt could be improved to prevent these issues.
|
|
138
|
+
3. This sentence should not contradict the original intent of the prompt, stated as follows:
|
|
139
|
+
### INTENT STATEMENT:
|
|
140
|
+
{intent}
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
If the errors involve missing context or ambiguous instructions, your output might be:
|
|
144
|
+
"This prompt has had issues with unclear context and ambiguous task phrasing.
|
|
145
|
+
It could be improved by explicitly defining the input format and desired output behavior."
|
|
146
|
+
|
|
147
|
+
Now produce your summary and improvement suggestion based on the provided error list.
|
|
148
|
+
{seedText}
|
|
149
|
+
### ERROR LIST - BULLETED:
|
|
150
|
+
{errors}"""
|
|
151
|
+
errorSummary = tb.askChatQuestion(errorPrompt,
|
|
152
|
+
'You are a skilled evaluator.',
|
|
153
|
+
model = model,
|
|
154
|
+
seed = seed)
|
|
155
|
+
while '\n\n' in errorSummary:
|
|
156
|
+
errorSummary = errorSummary.replace('\n\n','\n')
|
|
157
|
+
errorSummary = errorSummary.replace('.\n','. ')
|
|
158
|
+
return errorSummary
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def evaluateAnswer(originalPrompt,
|
|
163
|
+
persona,
|
|
164
|
+
varsIn,
|
|
165
|
+
model,
|
|
166
|
+
evaluatorNet):
|
|
167
|
+
"""Evaluates the y/n correctness of an answer, returning an explanation of the error if found"""
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
tStart = datetime.now()
|
|
171
|
+
preppedPrompt = tb.insertChatVars(originalPrompt,varsIn)
|
|
172
|
+
answer = tb.askChatQuestion(preppedPrompt,
|
|
173
|
+
persona,
|
|
174
|
+
model,
|
|
175
|
+
tokens = 4000)
|
|
176
|
+
tFinish = datetime.now()
|
|
177
|
+
duration = (tFinish - tStart).total_seconds()
|
|
178
|
+
|
|
179
|
+
answerVars = {'preppedPrompt':preppedPrompt,
|
|
180
|
+
'answer':answer}
|
|
181
|
+
varsOut = dict(varsIn) | answerVars
|
|
182
|
+
evaluation = tb.walkChatNet(evaluatorNet,
|
|
183
|
+
varStore = varsOut,
|
|
184
|
+
verbosity = verbosity)
|
|
185
|
+
if tb.ynToBool(evaluation['Start']):
|
|
186
|
+
answeredCorrectly = True
|
|
187
|
+
explanation = None
|
|
188
|
+
else:
|
|
189
|
+
answeredCorrectly = False
|
|
190
|
+
explanation = evaluation['Explain error']
|
|
191
|
+
except Exception as e:
|
|
192
|
+
print("Error:",e)
|
|
193
|
+
answeredCorrectly = False
|
|
194
|
+
explanation = None
|
|
195
|
+
|
|
196
|
+
return answeredCorrectly, explanation, duration
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def extractIntent(prompt,
|
|
200
|
+
model):
|
|
201
|
+
"""Summarizes the intent of a prompt"""
|
|
202
|
+
evaluationPersona = "You are an expert evaluator."
|
|
203
|
+
intentPrompt = f"""You are an expert at analyzing instructions and identifying their underlying purpose.
|
|
204
|
+
You will be given a **Prompt**, and your task is to write **two or three sentences** that clearly describe its intent, define the underlying context for its use, and the domain-specific considerations needed to adhere to each.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Instructions
|
|
209
|
+
|
|
210
|
+
1. Read the provided **Prompt** carefully.
|
|
211
|
+
2. Determine what the user is ultimately asking the model to do and what needs must be met for its given context.
|
|
212
|
+
3. Write the first sentence beginning with:
|
|
213
|
+
"The intent of this prompt is to ..."
|
|
214
|
+
4. Do **not** include any other commentary, formatting, or analysis — only a description of the intent of the prompt.
|
|
215
|
+
5. Think deeply on this one to understand the purpose of the prompt, do not simply return data formatting requirements.
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## Input Format
|
|
220
|
+
"Prompt: {prompt}"
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Output Format
|
|
225
|
+
"The intent of this prompt is to [describe the task]."""
|
|
226
|
+
intent = tb.askChatQuestion(intentPrompt,
|
|
227
|
+
evaluationPersona,
|
|
228
|
+
model,
|
|
229
|
+
autoformatPersona = False)
|
|
230
|
+
return intent
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def validatePromptIntent(prompt,
|
|
235
|
+
intent,
|
|
236
|
+
model = 'gemma3:27b'):
|
|
237
|
+
evaluatorPrompt = """You are an intent–prompt alignment classifier. You will receive:
|
|
238
|
+
|
|
239
|
+
1. **Intent statement** – a description of the user's desired goal, constraints, boundaries, or outcomes.
|
|
240
|
+
2. **Prompt statement** – the actual prompt the user intends to provide to an AI system.
|
|
241
|
+
|
|
242
|
+
Your task is to determine whether the prompt statement **faithfully follows, reflects, and remains consistent with the full intent expressed**. This includes both explicit instructions and implicit meaning.
|
|
243
|
+
|
|
244
|
+
### Interpretation rules
|
|
245
|
+
- Consider the **entire** intent, not just surface keywords.
|
|
246
|
+
- Identify the user’s **goal**, **tone**, **scope**, **format requirements**, and any **limitations or exclusions** expressed in the intent.
|
|
247
|
+
- Determine whether the prompt:
|
|
248
|
+
- Accurately attempts to achieve the described goal.
|
|
249
|
+
- Remains within the boundaries and constraints of the intent.
|
|
250
|
+
- Maintains the correct domain, topic, or purpose.
|
|
251
|
+
- Does not introduce major deviations, contradictions, or irrelevant objectives.
|
|
252
|
+
- Does not omit any *critical* requirement from the intent.
|
|
253
|
+
- Minor differences in wording or style are acceptable **as long as** the core intent is preserved.
|
|
254
|
+
|
|
255
|
+
### Output rules
|
|
256
|
+
- Respond **only** with `"yes"` if the prompt broadly aligns with the user’s intent as a whole.
|
|
257
|
+
- Respond **only** with `"no"` if the prompt does not align, includes contradictions, or misses essential parts of the intent.
|
|
258
|
+
- Provide **no explanations, no reasoning, and no additional text** of any kind.
|
|
259
|
+
|
|
260
|
+
**Inputs:**
|
|
261
|
+
Intent: [intent]
|
|
262
|
+
Prompt: [prompt]
|
|
263
|
+
|
|
264
|
+
**Output:**
|
|
265
|
+
"yes" or "no"\n"""
|
|
266
|
+
evaluatorPrompt = tb.insertChatVars(evaluatorPrompt,{'intent':intent,
|
|
267
|
+
'prompt':prompt})
|
|
268
|
+
intentCheck = tb.askChatQuestion(evaluatorPrompt,
|
|
269
|
+
'You are a classifier.',
|
|
270
|
+
model = model)
|
|
271
|
+
|
|
272
|
+
result = {'yes':True,'no':False}[tb.getYN(intentCheck)]
|
|
273
|
+
return result
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def iteratePrompt(bestPrompt,
|
|
278
|
+
bestPersona,
|
|
279
|
+
testDfIn,
|
|
280
|
+
model,
|
|
281
|
+
depth=20,
|
|
282
|
+
intent=None,
|
|
283
|
+
supervisor=None):
|
|
284
|
+
"""Processes a tabulairity extraction for a single prompt and iteratively improves it"""
|
|
285
|
+
if supervisor is None:
|
|
286
|
+
supervisor = model
|
|
287
|
+
|
|
288
|
+
evaluatorNet = getEvaluatorNet(supervisor)
|
|
289
|
+
|
|
290
|
+
if intent is None:
|
|
291
|
+
intent = extractIntent(bestPrompt, supervisor)
|
|
292
|
+
print("Inferred intent:",intent)
|
|
293
|
+
|
|
294
|
+
testDf = testDfIn.copy(deep=True)
|
|
295
|
+
varsInData = set(testDf.columns)
|
|
296
|
+
varsInPrompt = set(tb.extractChatVars(bestPrompt))
|
|
297
|
+
missingVars = varsInPrompt.difference(varsInData)
|
|
298
|
+
#print("DEBOOO1",varsInPrompt)
|
|
299
|
+
#print("DEBOOO2",varsInData)
|
|
300
|
+
if len(missingVars) != 0:
|
|
301
|
+
print(f"Warning: some expected prompt vars were not found in the passed DataFrame, is this intentional?\n\t{missingVars}")
|
|
302
|
+
|
|
303
|
+
# Evaluate initial prompt
|
|
304
|
+
bestErrors = []
|
|
305
|
+
bestScores = []
|
|
306
|
+
bestTimes = []
|
|
307
|
+
for _, row in testDf.iterrows():
|
|
308
|
+
result, error, duration = evaluateAnswer(bestPrompt,
|
|
309
|
+
bestPersona,
|
|
310
|
+
row,
|
|
311
|
+
model,
|
|
312
|
+
evaluatorNet)
|
|
313
|
+
bestScores.append(result)
|
|
314
|
+
bestTimes.append(duration)
|
|
315
|
+
if error is not None:
|
|
316
|
+
bestErrors.append(error)
|
|
317
|
+
|
|
318
|
+
bestScore = float(sum(bestScores) / len(bestScores))
|
|
319
|
+
bestTime = float(sum(bestTimes) / len(bestTimes))
|
|
320
|
+
initialScore = bestScore
|
|
321
|
+
history = [{'prompt':bestPrompt,
|
|
322
|
+
'intent':intent,
|
|
323
|
+
'persona':bestPersona,
|
|
324
|
+
'score':bestScore,
|
|
325
|
+
'time':bestTime,
|
|
326
|
+
'iteration':0}]
|
|
327
|
+
|
|
328
|
+
if bestScore == 1:
|
|
329
|
+
print("All responses flagged as correct, returning finalized prompt and persona.")
|
|
330
|
+
return pd.DataFrame(history)
|
|
331
|
+
|
|
332
|
+
if bestErrors == []:
|
|
333
|
+
errorReport = "No errors were logged."
|
|
334
|
+
else:
|
|
335
|
+
errorReport = '\n'.join([f'* {e}' for e in set(bestErrors)])
|
|
336
|
+
|
|
337
|
+
# Begin iterative improvements
|
|
338
|
+
for i in range(depth):
|
|
339
|
+
print(f'\nModel: {model}\nIteration: {i}\nbest prompt: {bestPrompt}\n')
|
|
340
|
+
newPrompt, newPersona = rewritePrompt(bestPrompt,
|
|
341
|
+
bestPersona,
|
|
342
|
+
supervisor,
|
|
343
|
+
intentPrompt = intent,
|
|
344
|
+
errorSummary = errorReport)
|
|
345
|
+
|
|
346
|
+
newErrors = []
|
|
347
|
+
newScores = []
|
|
348
|
+
newTimes = []
|
|
349
|
+
for _, row in testDf.iterrows():
|
|
350
|
+
result, error, duration = evaluateAnswer(newPrompt,
|
|
351
|
+
newPersona,
|
|
352
|
+
row,
|
|
353
|
+
model,
|
|
354
|
+
evaluatorNet)
|
|
355
|
+
newScores.append(result)
|
|
356
|
+
newTimes.append(duration)
|
|
357
|
+
if error is not None:
|
|
358
|
+
newErrors.append(error)
|
|
359
|
+
|
|
360
|
+
newScore = float(sum(newScores) / len(newScores))
|
|
361
|
+
newTime = float(sum(newTimes) / len(newTimes))
|
|
362
|
+
print(f"Model: {model} Best score: {bestScore} New score: {newScore}")
|
|
363
|
+
|
|
364
|
+
if newScore > bestScore:
|
|
365
|
+
bestScore = newScore
|
|
366
|
+
bestPrompt = newPrompt
|
|
367
|
+
bestPersona = newPersona
|
|
368
|
+
bestErrors = newErrors
|
|
369
|
+
bestTime = newTime
|
|
370
|
+
|
|
371
|
+
history.append({'prompt':bestPrompt,
|
|
372
|
+
'intent':intent,
|
|
373
|
+
'persona':bestPersona,
|
|
374
|
+
'score':bestScore,
|
|
375
|
+
'time':bestTime,
|
|
376
|
+
'iteration':i})
|
|
377
|
+
|
|
378
|
+
if bestScore == 1:
|
|
379
|
+
print("All responses flagged as correct, returning finalized prompt and persona.")
|
|
380
|
+
return pd.DataFrame(history)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
errorList = '\n'.join([f'* {iError}' for iError in set(bestErrors)])
|
|
384
|
+
errorReport = summarizeErrors(errorList,
|
|
385
|
+
intent,
|
|
386
|
+
supervisor)
|
|
387
|
+
errorReport = f'The current prompt yields {round(bestScore,2)*100}% accuracy. {errorReport}'
|
|
388
|
+
print(errorReport)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
print(f"Iterations complete, initial score: {initialScore} final score: {bestScore}")
|
|
392
|
+
return pd.DataFrame(history)
|