174 lines
7.2 KiB
Python
174 lines
7.2 KiB
Python
|
|
""" This script illustrates options for parsing csv and tsv files into a Library in LLMWare, including methods for
|
|
mapping custom configured csv or tsv file, which is intended for use with 'pseudo-db' CSV files
|
|
|
|
Option #1 - Standard CSV/TSV Parsing
|
|
|
|
-- when using a bulk ingest Parsing method, the parser will route csv files to the 'standard'
|
|
TextParser - which will look to extract and 'aggregate' the text from the csv as source content, and
|
|
add to "text" and/or "table" attributes -> no "structure" information or targeted keys are captured
|
|
|
|
-- the standard TextParser() is designed for ad hoc extraction of text content
|
|
|
|
-- if file is csv, then by default delimiter assumed to be ',' (which can be adjusted)
|
|
-- if file is tsv, then by default delimited assumed to be '\t' (which can be adjusted)
|
|
|
|
-- this is illustrated in example 1 and example 2 below.
|
|
|
|
Option #2 - Custom Configured CSV/TSV Parsing
|
|
|
|
-- if the CSV file is a pseudo-db with structured attributes, then it can be configured using a special
|
|
parsing method, as outlined below in examples 3 and 4.
|
|
|
|
-- the benefit of this custom mapping is that key column attributes will be saved as "metadata" in addition
|
|
to the option to provide a custom over-write of doc_ID and block_ID parameters for indexing of text
|
|
chunks in the database
|
|
|
|
"""
|
|
|
|
from llmware.parsers import Parser, TextParser
|
|
from llmware.library import Library
|
|
from llmware.retrieval import Query
|
|
from llmware.configs import LLMWareConfig
|
|
|
|
import time
|
|
import ast
|
|
|
|
# All three text databases supported (mongo, postgres, and sqlite)
|
|
# if it is highly varied unstructured content, we would recommend Mongo given its flexibility
|
|
# if any validation errors with Postgres or SQLite, then we would recommend either further preprocessing the csv or
|
|
# ... trying with Mongo
|
|
|
|
LLMWareConfig().set_active_db("mongo")
|
|
|
|
|
|
def standard_csv_parsing(fp, fn, delimiter=","):
|
|
|
|
""" Example #1 - This example shows the 'standard' text handler for csv """
|
|
|
|
# the standard csv parser will interpret the output as a table, trying to preserve the 'row-by-row' and
|
|
# 'cell-by-cell' structure (without any keys/labels). There are several options how the output text will
|
|
# be aggregated and saved as a single 'text' or 'table' entry:
|
|
|
|
# --batch_size: this is the number of rows that will be aggregated into each text entry
|
|
# e.g., if batch_size == 1, then each row will be a single entry in the database
|
|
# if batch_size == 10, then 10 rows will be aggregated into a single 'text' entry in the db
|
|
|
|
# --interpret_as_table:
|
|
# if true, then text will be packaged as a string wrapping a nested list.
|
|
# if false, then text will be packaged as a single text stream separated by "\t" between entries
|
|
|
|
# --optional parameters allow configuration of encoding ('utf-8-sig'), errors ('ignore'),
|
|
# and separator ("\n") (applied at end of each row)
|
|
|
|
# to experiment with the expected output, try the method below, which will not write to the DB, but outputs
|
|
# a list in memory
|
|
|
|
output = TextParser().csv_file_handler(fp,fn,interpret_as_table=False,batch_size=1, delimiter=delimiter)
|
|
|
|
return output
|
|
|
|
|
|
def standard_csv_parsing_into_library(fp, library_name):
|
|
|
|
""" Example #2 - building on the first example, this example will parse a set of 'standard' CSV files
|
|
directly into the library - if file type is 'tsv' then delimiter automatically applied as '\t' """
|
|
|
|
# create new library
|
|
lib = Library().create_new_library(library_name)
|
|
|
|
# create parser object, and pass the library to use to write the parsing output
|
|
parser = Parser(lib)
|
|
|
|
# directly call the parse_text method, which will parse text files (csv, tsv, json, jsonl, txt, md)
|
|
# this parsed output will be saved to the database by default
|
|
|
|
output = parser.parse_text(fp, interpret_as_table=False,batch_size=1,delimiter=",", encoding="utf-8-sig",
|
|
write_to_db=True)
|
|
|
|
return output
|
|
|
|
|
|
def configured_csv_parsing(fp, fn,library_name):
|
|
|
|
""" Example #3 - This example shows how to use mappings for a customized csv """
|
|
|
|
# metadata is a dictionary mapping of key names to columns in the csv file
|
|
# the 'keys' correspond to the keys that will be added to the library
|
|
# the 'values' correspond to the columns found in the source CSV (starting with 0 index)
|
|
|
|
# metadata map must have "text" mapping
|
|
# if "doc_ID" or "block_ID" mapping provided, then will "over-write" the default doc_ID and block_ID and
|
|
# use the mapping provided in the source CSV
|
|
|
|
# for all other attributes (e.g., not text, doc_ID, block_ID), the keys will be stored in "special_field1" of
|
|
# the database. For Mongo, the keys will be stored directly as a dictionary, while for Postgres and SQLite,
|
|
# it will be stored as text string, which must be converted upon use back into a dictionary (see below for
|
|
# retrieval example)
|
|
|
|
# step 1 - create metadata mapping,
|
|
# e.g., number indexes map to columns in the csv, 0-index and negative slicing supported (-1 is last column)
|
|
metadata = {"text": -1, "doc_ID": 0, "key1": 1, "key2": 2, "key3": 3, "key4": 4}
|
|
columns = 6
|
|
|
|
# step 2 - create library
|
|
lib = Library().create_new_library(library_name)
|
|
parser = Parser(lib)
|
|
|
|
# step 3 - invoke parse_csv_config method
|
|
# -- note: if file is not comma delimited, then set delimiter
|
|
# -- if file is tab delimited, e.g. tsv, then delimiter = "\t"
|
|
|
|
print("step 1 - parsing")
|
|
t0 = time.time()
|
|
parser_output = parser.parse_csv_config(fp, fn, cols=columns, mapping_dict=metadata,delimiter=",")
|
|
print(f"done parsing - time - {time.time() - t0} - summary - {parser_output}")
|
|
|
|
return parser_output
|
|
|
|
|
|
def example4_run_query_configured_input(library_name=None, query=""):
|
|
|
|
""" Example #4 - once the custom csv/tsv is parsed into a Library, it can be used like any other content with the
|
|
additional attributes available in special_field1- which can be retrieved as demonstrated below.
|
|
|
|
-- note: the example below illustrates a 'text_query' but will apply exactly the same for a 'semantic_query'
|
|
|
|
"""
|
|
|
|
# run query
|
|
lib = Library().load_library(library_name)
|
|
|
|
q = Query(lib).text_query(query)
|
|
|
|
for j, results in enumerate(q):
|
|
|
|
meta = ""
|
|
doc_id = -1
|
|
|
|
# the metadata attributes are saved in the database under "special_field1" column
|
|
if "special_field1" in results:
|
|
meta = results["special_field1"]
|
|
if isinstance(meta, str):
|
|
try:
|
|
meta = ast.literal_eval(meta)
|
|
except:
|
|
print(f"could not convert meta string back into dictionary - {meta}")
|
|
|
|
if "doc_ID" in results:
|
|
doc_id = results["doc_ID"]
|
|
|
|
text = results["text"]
|
|
|
|
if len(text) > 200:
|
|
text = text[0:200]
|
|
|
|
print(f"\nresults - {j} - query - {query}")
|
|
print(f"results - text - {text}")
|
|
print(f"results - doc_ID - {doc_id} - metadata - {meta}")
|
|
|
|
print("done")
|
|
|
|
return 0
|
|
|