Spaces:
Sleeping
Sleeping
Docs cleanup
#1
by kenleeyx - opened
- .gitignore +0 -4
- README.md +0 -44
- app.py +120 -258
- prompts/base_prompt.txt +0 -17
- prompts/prompt_030725.txt +0 -21
- requirements.txt +2 -4
- user_instructions.txt +0 -22
.gitignore
DELETED
|
@@ -1,4 +0,0 @@
|
|
| 1 |
-
venv/
|
| 2 |
-
.env
|
| 3 |
-
output.xlsx
|
| 4 |
-
.gradio/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -8,50 +8,6 @@ sdk_version: 5.9.1
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
short_description: Tags phrases from study respondents.
|
| 11 |
-
python_version: 3.13.1
|
| 12 |
---
|
| 13 |
|
| 14 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
| 15 |
-
|
| 16 |
-
# TFT Tagging app by Kenneth Lee, PercepTech.AI
|
| 17 |
-
|
| 18 |
-
## What it does
|
| 19 |
-
Uses a LLM to tag each quote in a column of an Excel file with zero or more tags from a given list, and returns the quotes tags on the app and also in a new file, along with some statistics and translations of randomly selected quotes for each tag.
|
| 20 |
-
|
| 21 |
-
Inputs:
|
| 22 |
-
-Single sheet Excel file
|
| 23 |
-
-Column header to identify which column of data should be tagged
|
| 24 |
-
-List of tags to be assigned to the data
|
| 25 |
-
-List of columns to retain in the output file(these will not be changed from the input file)
|
| 26 |
-
|
| 27 |
-
Outputs:
|
| 28 |
-
-List of quotes and tagged tags
|
| 29 |
-
-Count of number of instances tagged for eachtag
|
| 30 |
-
-Excel file containing the above data together with translations of some quotes corresponding to each tag that was used
|
| 31 |
-
|
| 32 |
-
## Notables
|
| 33 |
-
### Authentication:
|
| 34 |
-
- Private spaces require a HF account to access, hence this space is set as public. However anyone without a username and password will be stuck at a login screen.
|
| 35 |
-
- The username and password are stored as secrets on HF itself. You may also refer to emails I have sent.
|
| 36 |
-
- A temporary username, password, and login expiry time may be set in the secrets in order to give temporary access to TFT contractors. The time should be entered as Singapore time in ISO8601 format and any logins with the temporary account after this time will be prevented.
|
| 37 |
-
Progress bar only takes into account tagging time and not translation time; thus it may hang for a short while at 100% while doing translation.
|
| 38 |
-
|
| 39 |
-
### Quality of tagging:
|
| 40 |
-
- Ideally we want the LLM's tags to match exactly what a trained market researcher would tag. There are however two difficulties in this:
|
| 41 |
-
1)even two market researchers will not give identical tags for the same tags and the same dataset, and
|
| 42 |
-
2)the LLM has some variation in tags even when running the same tags and dataset twice, due to its non-deterministic nature.
|
| 43 |
-
- As such, to address the above difficulties, we assessed the quality of the LLM's tags by calculating precision, recall, and F1 of 8 aggregated runs of the LLM vs a trained market researcher(credit to Yong Li of TFT for providing the data), and also by calculating these for myself vs Yong Li.
|
| 44 |
-
- By doing so we are able to get an estimate of the amount of agreement between humans on the same tags and dataset, and if the LLM is able to match this on average, then it can be considered to be equal to a human for this task.
|
| 45 |
-
- We achieved an F1 of approximately 90 for myself vs Yong Li and about 70 for the AI vs Yong Li, indicating that the AI has room for improvement.
|
| 46 |
-
- Refer to Perceptech AI driver/TFT tagging app reference material for the data
|
| 47 |
-
|
| 48 |
-
### Sample input file and corresponding output:
|
| 49 |
-
- Refer to Perceptech AI driver/TFT tagging app reference material
|
| 50 |
-
|
| 51 |
-
### Branching behaviour:
|
| 52 |
-
- HF doesn't allow storage of any branches other than main online, to my knowledge. As such I usually update the repo by git push origin main - so be sure it works before pushing!
|
| 53 |
-
|
| 54 |
-
## Future work
|
| 55 |
-
- In the future we hope to improve the tagging so that the LLM-human F1 score matches human-human F1 scores.
|
| 56 |
-
- I had explored fine tuning using 10 responses from the OE US dataset and Yong Li's model answers; this improves performance on the training set from F1 70 to F1 85 but I do not have a test set, the model name is "ft:gpt-4o-mini-2024-07-18:percepsense::BsDwvH1E"; just substitute it where the model eg "gpt-4o-mini" is specified
|
| 57 |
-
- If TFT is able to retrieve any historical tagging data of their own human researchers on survey responses, this may be good training material also.
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
short_description: Tags phrases from study respondents.
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
app.py
CHANGED
|
@@ -1,283 +1,145 @@
|
|
| 1 |
-
|
| 2 |
-
import
|
| 3 |
-
import
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
import
|
| 7 |
-
import json # For conversion of OpenAI responses into json/dictionary objects so the contents can be extracted
|
| 8 |
-
from dotenv import load_dotenv # For loading environment variables in local environment
|
| 9 |
-
from collections import Counter # For tabulating tag occurrences
|
| 10 |
-
import logging
|
| 11 |
-
import time
|
| 12 |
-
import random
|
| 13 |
-
from datetime import datetime
|
| 14 |
-
from typing import Generator
|
| 15 |
-
from concurrent.futures import ThreadPoolExecutor, as_completed
|
| 16 |
|
| 17 |
-
logger = logging.getLogger()
|
| 18 |
-
logger.setLevel(logging.INFO)
|
| 19 |
-
logging.basicConfig(level=logging.INFO, force=True)
|
| 20 |
-
|
| 21 |
-
# Load environment variables from local .env file if it exists; otherwise this does nothing
|
| 22 |
-
load_dotenv()
|
| 23 |
-
|
| 24 |
-
# Import prompt for requesting the tags from OpenAI
|
| 25 |
-
with open("prompts/prompt_030725.txt", "r") as prompt_file:
|
| 26 |
-
PROMPT = prompt_file.read()
|
| 27 |
-
logger.info(f"Loaded prompt: {PROMPT}")
|
| 28 |
-
|
| 29 |
-
# Import user instructions for display on screen
|
| 30 |
-
with open("user_instructions.txt", "r") as user_instruction_file:
|
| 31 |
-
INSTRUCTIONS = user_instruction_file.read()
|
| 32 |
-
|
| 33 |
-
#Initialising the OpenAI client
|
| 34 |
client = OpenAI(
|
| 35 |
api_key=os.getenv('OPENAI_KEY'),
|
| 36 |
organization=os.getenv('ORG_KEY'),
|
| 37 |
project=os.getenv('PROJ_KEY')
|
| 38 |
)
|
| 39 |
-
logger.info("Initialised OpenAI client")
|
| 40 |
-
|
| 41 |
-
# Function to send the prompt with quote and tag list to OpenAI and get the tags for that quote back
|
| 42 |
-
def tag_quote(quote: str, tags_list: list) -> list:
|
| 43 |
-
"""
|
| 44 |
-
Generates a list of tags for a given quote based on a predefined list of potential tags.
|
| 45 |
-
|
| 46 |
-
This function uses a GPT-based language model to analyze the input quote and determine
|
| 47 |
-
the most relevant tags from the provided list. The response is parsed from the JSON
|
| 48 |
-
output of the model and returned as a list of tags. This list is checked to ensure
|
| 49 |
-
all tags tagged are taken from the input tags_list.
|
| 50 |
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
response = client.chat.completions.create(
|
| 60 |
model = "gpt-4o-mini",
|
| 61 |
response_format={"type": "json_object"},
|
| 62 |
messages=[
|
| 63 |
{"role": "system", "content": "You are a helpful assistant designed to output JSON."},
|
| 64 |
-
{"role": "user", "content":
|
| 65 |
]
|
| 66 |
)
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
valid_tags = []
|
| 70 |
-
for tag in tags: # filter out any hallucinated tags
|
| 71 |
-
if tag in tags_list:
|
| 72 |
-
valid_tags.append(tag)
|
| 73 |
-
else:
|
| 74 |
-
logger.warning(f"Invalid tag {tag} found and has been filtered out.")
|
| 75 |
-
return valid_tags
|
| 76 |
-
|
| 77 |
-
def translate_quote(quote: str) -> str:
|
| 78 |
-
"""
|
| 79 |
-
Translates a quote to English.
|
| 80 |
-
"""
|
| 81 |
-
logger.info(f"Translating quote {quote}")
|
| 82 |
-
response = client.chat.completions.create(
|
| 83 |
-
model = "gpt-4o-mini",
|
| 84 |
-
messages=[
|
| 85 |
-
{"role": "user", "content": f"Translate the following quote into English. Do not return anything other than the translated quote. {quote}"}
|
| 86 |
-
]
|
| 87 |
-
)
|
| 88 |
-
logger.info("Content")
|
| 89 |
-
logger.info(response.choices[0].message.content)
|
| 90 |
-
return response.choices[0].message.content
|
| 91 |
-
|
| 92 |
-
def count_tags(tags_list: list, tags_col: pd.Series )->pd.DataFrame:
|
| 93 |
-
"""
|
| 94 |
-
Creates a DataFrame indicating number of occurences of each tag from a DataFrame column containing lists of tags.
|
| 95 |
-
|
| 96 |
-
This function also takes in a tags_list; all tags in the tags_list will be in the output
|
| 97 |
-
DataFrame even if they do not occur in the input tags_col. There may be some tags appearing
|
| 98 |
-
in the output which were not in the original tag_list; these will be marked with a ! prefix.
|
| 99 |
-
|
| 100 |
-
Args:
|
| 101 |
-
tags_list (list): The list of tags given by the user
|
| 102 |
-
tags_col (pd.Series): A column of lists where each list contains tags which are
|
| 103 |
-
(ideally but not always; depending on OpenAI) selected from the tags_list.
|
| 104 |
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
# Initialise Counter hash table
|
| 111 |
-
tags_counter = Counter({tag: 0 for tag in tags_list})
|
| 112 |
-
# Iterate over the lists in tags_col
|
| 113 |
-
for sublist in tags_col:
|
| 114 |
-
# Iterate over the tags in each list
|
| 115 |
-
for tag in sublist:
|
| 116 |
-
# Update the tags_counter for each tag
|
| 117 |
-
if tag in tags_list:
|
| 118 |
-
tags_counter.update([tag])
|
| 119 |
-
# If the tag was not in the tags_list given by the user, prefix it with a ! before updating
|
| 120 |
-
else:
|
| 121 |
-
tags_counter.update([f"!{tag}"])
|
| 122 |
-
# Convert the tags_counter to a DataFrame and return it
|
| 123 |
-
tags_counter_df = pd.DataFrame(tags_counter.items(), columns=['Tag', 'Count'])
|
| 124 |
-
return tags_counter_df
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
# Function that takes in a list of tags and an Excel file of quotes, calls tag_quote() on each quote, and returns all the quotes and tags in a DataFrame
|
| 128 |
-
def process_quotes(quotes_file_path: str, quotes_col_name: str, retained_columns: str, tags_string: str) -> Generator[tuple[str, pd.DataFrame, pd.DataFrame, str]]:
|
| 129 |
-
"""
|
| 130 |
-
Processes quotes from an Excel file and assigns relevant tags to each quote.
|
| 131 |
-
|
| 132 |
-
This function reads an Excel file containing quotes, validates the column containing
|
| 133 |
-
the quotes, and applies the `tag_quote` function to assign tags to each quote.
|
| 134 |
-
The tags are derived from a user-provided newline-separated string.
|
| 135 |
-
|
| 136 |
-
Args:
|
| 137 |
-
quotes_file_path (str): Path to the Excel file containing the quotes.
|
| 138 |
-
quotes_col_name (str): The name of the column containing the quotes.
|
| 139 |
-
retained_columns (str): The names of the columns in the Excel file which are to be added to the output file.
|
| 140 |
-
tags_string (str): A newline-separated string of potential tags.
|
| 141 |
-
|
| 142 |
-
Yields:
|
| 143 |
-
tuple: A 4-element tuple containing:
|
| 144 |
-
- str: A progress indicator (or "Not running" if tagging is complete)
|
| 145 |
-
- pd.DataFrame: A DataFrame with two columns: (or None if tagging is incomplete)
|
| 146 |
-
- The original column containing the quotes.
|
| 147 |
-
- A new column 'Tags' with the tags assigned to each quote.
|
| 148 |
-
- pd.DataFrame: A DataFrame with two columns: (or None if tagging is incomplete)
|
| 149 |
-
-"Tag" - The list of tags that was passed in.
|
| 150 |
-
-"Count" - The total number of times each tag was used in tagging all the quotes.
|
| 151 |
-
- str: A path to an Excel file containing sheets derived from the previous 2 DataFrames. (or None if tagging is incomplete)
|
| 152 |
-
|
| 153 |
-
Raises:
|
| 154 |
-
gr.Error: If the specified column name does not exist or is not unique.
|
| 155 |
-
"""
|
| 156 |
-
tags_list = tags_string.split('\n')
|
| 157 |
tags_list = [tag.strip() for tag in tags_list]
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
quotes_df = pd.read_excel(quotes_file_path, header=None, skiprows=1)
|
| 171 |
-
# Set the extracted first row as the header for the DataFrame resultant from the other rows
|
| 172 |
-
quotes_df.columns = quotes_df_cols
|
| 173 |
-
# Verify that all column names given are found in the quotes DF exactly once each
|
| 174 |
-
for colname in retained_cols_list + [quotes_col_name]:
|
| 175 |
-
count = quotes_df.columns.tolist().count(colname)
|
| 176 |
-
if count == 0:
|
| 177 |
-
raise gr.Error(f"No columns with name {colname} found, check your inputs")
|
| 178 |
-
elif count > 1:
|
| 179 |
-
raise gr.Error(f"Multiple columns with name {colname} found, please rename these columns to something unique")
|
| 180 |
-
|
| 181 |
quotes_data = quotes_df[quotes_col_name]
|
|
|
|
|
|
|
| 182 |
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
while len(sample_quotes) < 2:
|
| 221 |
-
sample_quotes.append(None)
|
| 222 |
-
translated_quotes.append(None)
|
| 223 |
-
[tags_counter_df.loc[tags_counter_df['Tag'] == tag, 'Quote 1'], tags_counter_df.loc[tags_counter_df['Tag'] == tag, 'Quote 2']] = sample_quotes
|
| 224 |
-
[tags_counter_df.loc[tags_counter_df['Tag'] == tag, 'Translated Quote 1'], tags_counter_df.loc[tags_counter_df['Tag'] == tag, 'Translated Quote 2']] = translated_quotes
|
| 225 |
-
|
| 226 |
-
#Convert values in tags column from list to str
|
| 227 |
-
quotes_df['Tags'] = quotes_df["Tags"].apply(lambda x: ", ".join(x))
|
| 228 |
-
|
| 229 |
-
# Return only the quotes column, the new tags column, and any other specified cols to retain
|
| 230 |
-
output_df = quotes_df[retained_cols_list+[quotes_col_name, 'Tags']+tags_list]
|
| 231 |
-
output_file_path = "output.xlsx"
|
| 232 |
-
with pd.ExcelWriter(output_file_path) as writer:
|
| 233 |
-
output_df.to_excel(writer, sheet_name='Coded Quotes', index=False)
|
| 234 |
-
tags_counter_df.to_excel(writer, sheet_name='Tag Count', index=False)
|
| 235 |
-
logger.info('Results written to Excel')
|
| 236 |
-
yield ("Not running", output_df[[quotes_col_name, 'Tags']], tags_counter_df, output_file_path)
|
| 237 |
-
|
| 238 |
-
def check_auth(username:str, password:str):
|
| 239 |
-
"""
|
| 240 |
-
Authenticate the user.
|
| 241 |
-
|
| 242 |
-
Verifies the user's credentials against the values stored in the environment variables.
|
| 243 |
-
User may authenticate with permanent username and password(for TFT team) or temporary username and password.
|
| 244 |
-
For temporary username and password, they will only be valid before the expiry time as set in the environment variables.
|
| 245 |
-
|
| 246 |
-
Returns True or False depending on authentication success.
|
| 247 |
-
"""
|
| 248 |
-
# Check permanent credentials
|
| 249 |
-
if username == os.getenv('APP_USERNAME') and password == os.getenv('APP_PASSWORD'):
|
| 250 |
-
return True
|
| 251 |
-
|
| 252 |
-
# Check temporary credentials
|
| 253 |
-
if (
|
| 254 |
-
username == os.getenv('TEMP_USERNAME') and
|
| 255 |
-
password == os.getenv('TEMP_PASSWORD') and
|
| 256 |
-
time.time() < datetime.fromisoformat(os.getenv('TEMP_EXPIRY_TIME_SG_ISO_8601').replace("Z", "+08:00")).timestamp()
|
| 257 |
-
):
|
| 258 |
-
return True
|
| 259 |
-
|
| 260 |
-
# Invalid credentials
|
| 261 |
-
return False
|
| 262 |
-
|
| 263 |
-
# Define user interface structure
|
| 264 |
demo = gr.Interface(
|
| 265 |
fn=process_quotes,
|
| 266 |
inputs=[
|
| 267 |
-
gr.File(label="Quotes Excel File"),
|
| 268 |
-
gr.Textbox(label="Name of quotes column"),
|
| 269 |
-
gr.Textbox(label = "
|
| 270 |
-
gr.Textbox(label = "List of tags, each tag on a new line"),
|
| 271 |
],
|
| 272 |
-
outputs=[
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
gr.Dataframe(headers=["Tag", "Count"], label='Tag Count'),
|
| 276 |
-
gr.File(label="Output data in file format")
|
| 277 |
-
|
| 278 |
-
],
|
| 279 |
-
title="Automated Research Code Tagger",
|
| 280 |
-
description=INSTRUCTIONS
|
| 281 |
)
|
| 282 |
|
| 283 |
-
demo.launch(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from openai import OpenAI
|
| 5 |
+
import openpyxl
|
| 6 |
+
import json
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
client = OpenAI(
|
| 9 |
api_key=os.getenv('OPENAI_KEY'),
|
| 10 |
organization=os.getenv('ORG_KEY'),
|
| 11 |
project=os.getenv('PROJ_KEY')
|
| 12 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
+
# Static Username and Password
|
| 15 |
+
VALID_USERNAME = "tft@perceptech.ai"
|
| 16 |
+
VALID_PASSWORD = "Perceptech@2024!"
|
| 17 |
+
|
| 18 |
+
#need to give info on how to convert to CSV
|
| 19 |
+
title = "Automated Research Code Tagger"
|
| 20 |
+
description = """
|
| 21 |
+
ABOUT:\n
|
| 22 |
+
This automated tagger takes in a list of tags and a list of input quotes. Each input quote is individually fed to OpenAI's ChatGPT together with the list of tags,
|
| 23 |
+
and ChatGPT will respond with the subset of the input tags which are related to the content of the quote.\n
|
| 24 |
+
|
| 25 |
+
HOW TO USE:\n
|
| 26 |
+
1)Upload a single sheet Excel file containing quotes in a column.(It is ok for the file to contain other data also)\n
|
| 27 |
+
2)Type in the name of the column where the quotes are located\n
|
| 28 |
+
3)Type in a list of tags separated by commas. For proper names/slogans/other tags that should be treated as an inseparable unit eg. Nike's "Just Do It", add a * in front of the tag eg. tag1, *Just Do It, tag3, etc.
|
| 29 |
+
This will ensure only quotes containing "Just Do It" exactly are tagged and not other quotes about doing other things.\n
|
| 30 |
+
4)All the responses from ChatGPT will be collated and displayed in the table on the right, together with the original quotes.
|
| 31 |
+
You may then copy them into an Excel file for further processing. Please allow 5-10 min for processing, especially if you are giving upwards of 100 quotes!\n
|
| 32 |
+
|
| 33 |
+
Please bear in mind that the tags are AI generated so check your results to ensure they make sense before using them.
|
| 34 |
+
I will not be responsible for mistakes made by the AI, but I can try to fix them if you alert me.
|
| 35 |
+
-Kenneth
|
| 36 |
+
"""
|
| 37 |
+
|
| 38 |
+
prompt = """
|
| 39 |
+
Given the quote below and the regular tag list below, evaluate each tag in the tag list and determine if the meaning of the quote can be described by that tag topic.
|
| 40 |
+
If so, return the relevant tag in your response. Use only the tags provided in the list. Under no circumstances should you create new tag names.
|
| 41 |
+
|
| 42 |
+
For the tags starting with a *, these tags should be treated as proper nouns(usually product names or slogans) and should not be used unless the quote explicitly contains the entire tag.
|
| 43 |
+
For quotes with meanings that are more ambiguous and can relate to multiple tags, make no assumptions about their meanings and only add tags if the topic of the tag is actually mentioned in the quote.
|
| 44 |
+
If there are no relevant tags to the quote, return an empty list.
|
| 45 |
+
|
| 46 |
+
Quote:
|
| 47 |
+
{quote}
|
| 48 |
+
|
| 49 |
+
Tag list:
|
| 50 |
+
{tags_list}
|
| 51 |
+
|
| 52 |
+
Respond in the following format:
|
| 53 |
+
{{
|
| 54 |
+
"tags":[<tagName1>, <tagName2>]
|
| 55 |
+
}}
|
| 56 |
+
"""
|
| 57 |
+
def tag_quote(quote, tags_list):
|
| 58 |
response = client.chat.completions.create(
|
| 59 |
model = "gpt-4o-mini",
|
| 60 |
response_format={"type": "json_object"},
|
| 61 |
messages=[
|
| 62 |
{"role": "system", "content": "You are a helpful assistant designed to output JSON."},
|
| 63 |
+
{"role": "user", "content": prompt.format(tags_list=tags_list, quote=quote)}
|
| 64 |
]
|
| 65 |
)
|
| 66 |
+
print(response.choices[0].message.content)
|
| 67 |
+
return json.loads(response.choices[0].message.content)['tags']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
|
| 69 |
+
def process_quotes(quotes_file_path, quotes_col_name, tags_string):
|
| 70 |
+
print(quotes_file_path)
|
| 71 |
+
print(quotes_col_name)
|
| 72 |
+
print(tags_string)
|
| 73 |
+
tags_list = tags_string.split(',')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
tags_list = [tag.strip() for tag in tags_list]
|
| 75 |
|
| 76 |
+
#next 3 lines are necessary as pd.read_excel will rename duplicate columns found in the excel file eg foo -> foo.1, hence we need to extract the first row alone and not as header, and then set it as header for the rest of the DF later.
|
| 77 |
+
quotes_df_cols= pd.read_excel(quotes_file_path, header=None, nrows=1).values[0] #creates a df without header from the excel and takes the first row
|
| 78 |
+
quotes_df = pd.read_excel(quotes_file_path, header=None, skiprows=1) # converts row 2 onwards into the DF, without specifying a header
|
| 79 |
+
quotes_df.columns = quotes_df_cols # sets the first row of excel file as header
|
| 80 |
+
|
| 81 |
+
count = quotes_df.columns.tolist().count(quotes_col_name)
|
| 82 |
+
if count == 0:
|
| 83 |
+
raise gr.Error("No columns with this name found")
|
| 84 |
+
elif count > 1:
|
| 85 |
+
print("Count>1!!")
|
| 86 |
+
raise gr.Error("Multiple columns with this name found, please rename to something unique")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
quotes_data = quotes_df[quotes_col_name]
|
| 88 |
+
quotes_df['Tags'] = quotes_data.apply(tag_quote, args=(tags_list,))
|
| 89 |
+
return quotes_df[[quotes_col_name, 'Tags']]
|
| 90 |
|
| 91 |
+
# def authenticate(username, password):
|
| 92 |
+
# """Authenticate the user using static username and password"""
|
| 93 |
+
# if username == VALID_USERNAME and password == VALID_PASSWORD:
|
| 94 |
+
# return True
|
| 95 |
+
# else:
|
| 96 |
+
# return False
|
| 97 |
+
|
| 98 |
+
# def auth_interface(username, password):
|
| 99 |
+
# """Handle the authentication and proceed with the main function if valid"""
|
| 100 |
+
# if authenticate(username, password):
|
| 101 |
+
# return gr.Interface(
|
| 102 |
+
# fn=process_quotes,
|
| 103 |
+
# inputs=[
|
| 104 |
+
# gr.File(label="Quotes Excel File"), # File as generated by TFT software
|
| 105 |
+
# gr.Textbox(label="Name of quotes column"), # use this to identify the col with the quotes
|
| 106 |
+
# gr.Textbox(label="List of tags separated by commas")
|
| 107 |
+
# ],
|
| 108 |
+
# outputs=gr.Dataframe(headers=["Quote", "Tags"], column_widths=["70%", "30%"], scale=2),
|
| 109 |
+
# title=title,
|
| 110 |
+
# description=description
|
| 111 |
+
# ).launch()
|
| 112 |
+
# else:
|
| 113 |
+
# return "Invalid username or password!"
|
| 114 |
+
|
| 115 |
+
# # Create the authentication fields before launching the main app
|
| 116 |
+
# auth_app = gr.Interface(
|
| 117 |
+
# fn=auth_interface,
|
| 118 |
+
# inputs=[
|
| 119 |
+
# gr.Textbox(label="Username", type="text"),
|
| 120 |
+
# gr.Textbox(label="Password", type="password")
|
| 121 |
+
# ],
|
| 122 |
+
# outputs="text",
|
| 123 |
+
# title="Login to Automated Research Code Tagger",
|
| 124 |
+
# description="Please enter the correct username and password to access the tool."
|
| 125 |
+
# )
|
| 126 |
+
|
| 127 |
+
# auth_app.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
demo = gr.Interface(
|
| 129 |
fn=process_quotes,
|
| 130 |
inputs=[
|
| 131 |
+
gr.File(label="Quotes Excel File"), # File as generated by TFT software
|
| 132 |
+
gr.Textbox(label="Name of quotes column"), # use this to identify the col with the quotes
|
| 133 |
+
gr.Textbox(label = "List of tags separated by commas")
|
|
|
|
| 134 |
],
|
| 135 |
+
outputs=gr.Dataframe(headers=["Quote", "Tags"], column_widths=["70%", "30%"], scale=2),
|
| 136 |
+
title=title,
|
| 137 |
+
description=description
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 138 |
)
|
| 139 |
|
| 140 |
+
demo.launch()
|
| 141 |
+
|
| 142 |
+
# For later when I enable usage of own API key
|
| 143 |
+
# api_key = gr.Textbox(
|
| 144 |
+
# type="password", label="Enter your OpenAI API key here (Optional for Perceptech users)"
|
| 145 |
+
# )
|
prompts/base_prompt.txt
DELETED
|
@@ -1,17 +0,0 @@
|
|
| 1 |
-
Given the quote below and the regular tag list below, evaluate each tag in the tag list and determine if the meaning of the quote can be described by that tag topic.
|
| 2 |
-
If so, return the relevant tag in your response. Use only the tags provided in the list. Under no circumstances should you create new tag names.
|
| 3 |
-
|
| 4 |
-
For the tags starting with a *, these tags should be treated as proper nouns(usually product names or slogans) and should not be used unless the quote explicitly contains the entire tag.
|
| 5 |
-
For quotes with meanings that are more ambiguous and can relate to multiple tags, make no assumptions about their meanings and only add tags if the topic of the tag is actually mentioned in the quote.
|
| 6 |
-
If there are no relevant tags to the quote, return an empty list.
|
| 7 |
-
|
| 8 |
-
Quote:
|
| 9 |
-
{quote}
|
| 10 |
-
|
| 11 |
-
Tag list:
|
| 12 |
-
{tags_list}
|
| 13 |
-
|
| 14 |
-
Respond in the following format:
|
| 15 |
-
{{
|
| 16 |
-
"tags":[<tagName1>, <tagName2>]
|
| 17 |
-
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
prompts/prompt_030725.txt
DELETED
|
@@ -1,21 +0,0 @@
|
|
| 1 |
-
Given the quote below and the regular tag list below, evaluate each tag in the tag list and determine if the meaning of the quote can be described by that tag topic.
|
| 2 |
-
If so, return the relevant tag in your response. Use only the tags provided in the list. Under no circumstances should you create new tag names.
|
| 3 |
-
|
| 4 |
-
IMPORTANT: Do NOT infer the respondent's meaning based on brand reputation, public knowledge, or commonly associated attributes (e.g., don't assume "natural" just because a brand is known for it). Only use a tag if the specific idea or topic is **explicitly stated** or clearly implied **by the respondent**, not by external associations.
|
| 5 |
-
|
| 6 |
-
Tags starting with a * are proper nouns (usually product names or slogans) and should only be used if the quote contains the entire tag exactly.
|
| 7 |
-
|
| 8 |
-
If the quote is vague or ambiguous, do not guess. Only add tags if the topic of the tag is clearly and unambiguously expressed in the quote.
|
| 9 |
-
|
| 10 |
-
If there are no relevant tags, return an empty list.
|
| 11 |
-
|
| 12 |
-
Quote:
|
| 13 |
-
{quote}
|
| 14 |
-
|
| 15 |
-
Tag list:
|
| 16 |
-
{tags_list}
|
| 17 |
-
|
| 18 |
-
Respond in the following format:
|
| 19 |
-
{{
|
| 20 |
-
"tags":[<tagName1>, <tagName2>]
|
| 21 |
-
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
|
@@ -1,5 +1,3 @@
|
|
| 1 |
-
gradio==
|
| 2 |
openai==1.59.3
|
| 3 |
-
openpyxl==3.1.5
|
| 4 |
-
python-dotenv==1.0.1
|
| 5 |
-
pydantic==2.10.6
|
|
|
|
| 1 |
+
gradio==4.31.5
|
| 2 |
openai==1.59.3
|
| 3 |
+
openpyxl==3.1.5
|
|
|
|
|
|
user_instructions.txt
DELETED
|
@@ -1,22 +0,0 @@
|
|
| 1 |
-
ABOUT:
|
| 2 |
-
This automated tagger takes in a list of tags and a list of input quotes. Each input quote is individually fed to OpenAI's ChatGPT together with the list of tags,
|
| 3 |
-
and ChatGPT will respond with the subset of the input tags which are related to the content of the quote.
|
| 4 |
-
|
| 5 |
-
HOW TO USE:<br>
|
| 6 |
-
1)Upload a single sheet Excel file containing quotes in a column.(It is ok for the file to contain other data also)<br>
|
| 7 |
-
2)Type in the name of the column where the quotes are located<br>
|
| 8 |
-
3)Type in the names of any other columns which you wish to retain in the output <br>
|
| 9 |
-
4)Type in a list of tags, each tag on a new line. For proper names/slogans/other tags that should be treated as an inseparable unit eg. Nike's "Just Do It", add a * in front of the tag eg. tag1, *Just Do It, tag3, etc. <br>
|
| 10 |
-
This will ensure only quotes containing "Just Do It" exactly are tagged and not other quotes about doing other things.<br>
|
| 11 |
-
Please allow 5-10 min for processing, especially if you are giving upwards of 100 quotes!<br>
|
| 12 |
-
|
| 13 |
-
READING THE OUTPUT(in the right-side column): <br>
|
| 14 |
-
Progress bar: Indicates which quote is currently being processed <br>
|
| 15 |
-
First table: All the responses are collated and displayed here, together with the original quotes.<br>
|
| 16 |
-
Second table: Displays all the tags used and the number of occurrences of each tag. <br>
|
| 17 |
-
You may also retrieve both tables in a single Excel file via the link below them.<br>
|
| 18 |
-
|
| 19 |
-
DISCLAIMER:<br>
|
| 20 |
-
Please bear in mind that the tags are AI generated so check your results to ensure they make sense before using them.
|
| 21 |
-
I will not be responsible for mistakes made by the AI, but I can try to fix them if you alert me.<br>
|
| 22 |
-
-Kenneth Lee, Perceptech.AI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|