diff --git a/README.md b/README.md index 168ae1a3..5b2b55a7 100644 --- a/README.md +++ b/README.md @@ -155,10 +155,15 @@ For in-depth descriptions of the above queries, check out our [documentation](ht ```python import pandas as pd -from blendsql import blend, LLMMap, LLMQA, LLMJoin +import blendsql +from blendsql.ingredients import LLMMap, LLMQA, LLMJoin from blendsql.db import Pandas from blendsql.models import TransformersLLM, OpenaiLLM +# Optionally set how many async calls to allow concurrently +# This depends on your OpenAI/Anthropic/etc. rate limits +blendsql.config.set_async_limit(10) + # Load model # model = OpenaiLLM("gpt-4o") # If you have a .env present with OpenAI API keys model = TransformersLLM('Qwen/Qwen1.5-0.5B') @@ -190,7 +195,7 @@ db = Pandas( ) # Write BlendSQL query -blendsql = """ +query = """ SELECT * FROM w WHERE city = {{ LLMQA( @@ -200,8 +205,8 @@ WHERE city = {{ ) }} """ -smoothie = blend( - query=blendsql, +smoothie = blendsql.blend( + query=query, db=db, ingredients={LLMMap, LLMQA, LLMJoin}, default_model=model, diff --git a/blendsql/__init__.py b/blendsql/__init__.py index 2d2227c4..e0b73ae1 100644 --- a/blendsql/__init__.py +++ b/blendsql/__init__.py @@ -1,2 +1,3 @@ -from .ingredients.builtin import LLMMap, LLMQA, LLMJoin, LLMValidate, ImageCaption from .blend import blend +from .ingredients import LLMQA, LLMMap, LLMJoin, LLMValidate, ImageCaption +from . import _configure as config diff --git a/docs/quickstart.md b/docs/quickstart.md index e6f0068a..7d5a59af 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -7,42 +7,47 @@ hide: ```python import pandas as pd -from blendsql import blend, LLMMap, LLMQA, LLMJoin +import blendsql +from blendsql.ingredients import LLMMap, LLMQA, LLMJoin from blendsql.db import Pandas from blendsql.models import TransformersLLM, OpenaiLLM +# Optionally set how many async calls to allow concurrently +# This depends on your OpenAI/Anthropic/etc. rate limits +blendsql.config.set_async_limit(10) + # Load model # model = OpenaiLLM("gpt-4o") # If you have a .env present with OpenAI API keys model = TransformersLLM('Qwen/Qwen1.5-0.5B') # Prepare our local database db = Pandas( - { - "w": pd.DataFrame( - ( - ['11 jun', 'western districts', 'bathurst', 'bathurst ground', '11-0'], - ['12 jun', 'wallaroo & university nsq', 'sydney', 'cricket ground', - '23-10'], - ['5 jun', 'northern districts', 'newcastle', 'sports ground', '29-0'] - ), - columns=['date', 'rival', 'city', 'venue', 'score'] - ), - "documents": pd.DataFrame( - ( - ['bathurst, new south wales', - 'bathurst /ˈbæθərst/ is a city in the central tablelands of new south wales , australia . it is about 200 kilometres ( 120 mi ) west-northwest of sydney and is the seat of the bathurst regional council .'], - ['sydney', - 'sydney ( /ˈsɪdni/ ( listen ) sid-nee ) is the state capital of new south wales and the most populous city in australia and oceania . located on australia s east coast , the metropolis surrounds port jackson.'], - ['newcastle, new south wales', - 'the newcastle ( /ˈnuːkɑːsəl/ new-kah-səl ) metropolitan area is the second most populated area in the australian state of new south wales and includes the newcastle and lake macquarie local government areas .'] - ), - columns=['title', 'content'] - ) - } + { + "w": pd.DataFrame( + ( + ['11 jun', 'western districts', 'bathurst', 'bathurst ground', '11-0'], + ['12 jun', 'wallaroo & university nsq', 'sydney', 'cricket ground', + '23-10'], + ['5 jun', 'northern districts', 'newcastle', 'sports ground', '29-0'] + ), + columns=['date', 'rival', 'city', 'venue', 'score'] + ), + "documents": pd.DataFrame( + ( + ['bathurst, new south wales', + 'bathurst /ˈbæθərst/ is a city in the central tablelands of new south wales , australia . it is about 200 kilometres ( 120 mi ) west-northwest of sydney and is the seat of the bathurst regional council .'], + ['sydney', + 'sydney ( /ˈsɪdni/ ( listen ) sid-nee ) is the state capital of new south wales and the most populous city in australia and oceania . located on australia s east coast , the metropolis surrounds port jackson.'], + ['newcastle, new south wales', + 'the newcastle ( /ˈnuːkɑːsəl/ new-kah-səl ) metropolitan area is the second most populated area in the australian state of new south wales and includes the newcastle and lake macquarie local government areas .'] + ), + columns=['title', 'content'] + ) + } ) # Write BlendSQL query -blendsql = """ +query = """ SELECT * FROM w WHERE city = {{ LLMQA( @@ -52,14 +57,14 @@ WHERE city = {{ ) }} """ -smoothie = blend( - query=blendsql, - db=db, - ingredients={LLMMap, LLMQA, LLMJoin}, - default_model=model, - # Optional args below - infer_gen_constraints=True, - verbose=True +smoothie = blendsql.blend( + query=query, + db=db, + ingredients={LLMMap, LLMQA, LLMJoin}, + default_model=model, + # Optional args below + infer_gen_constraints=True, + verbose=True ) print(smoothie.df) # ┌────────┬───────────────────┬──────────┬─────────────────┬─────────┐ diff --git a/research/.gitignore b/research/.gitignore new file mode 100644 index 00000000..adbb97d2 --- /dev/null +++ b/research/.gitignore @@ -0,0 +1 @@ +data/ \ No newline at end of file diff --git a/research/README.md b/research/README.md deleted file mode 100644 index 4abf2d37..00000000 --- a/research/README.md +++ /dev/null @@ -1,2823 +0,0 @@ -# BlendSQL Research - -Example usage: -``` -caffeinate -is python -m research.run-evaluate research/configs/gpt-4-all.json -``` -## Initial Results - -Below experiments were done on the first 200 items in the WikiTableQuestions validation set. - -They included only 4 few-shot examples found in [./prompts/few_shot.py](./prompts/few_shot.py). - -Full model outputs are in [./outputs](./outputs). - -| **Parser** | **QA** | **Accuracy** | **# With Ingredients** | -|------------------|------------------|--------------|------------------------| -| gpt-4 | gpt-4 | 67.35 | 6 | -| gpt-4 | text-davinci-003 | 66.84 | 6 | -| text-davinci-003 | text-davinci-003 | 45.65 | 53 | -| text-davinci-003 | gpt-4 | 49.46 | 53 | - -Some can be solved with both BlendSQL and SQLite -"what is the largest penalty?" - -`SELECT MAX(CAST(SUBSTR("Penalties (P+P+S+S)", 0, 3) AS integer)) FROM w;` -`SELECT MAX({{LLMMap('total penalties?', 'w::penalties (p+p+s+s)')}}) FROM w` - - -WizardCoder + BlendSQL on whole WikiTQ dev set: -```json -{ - "metric_score": 0.46872166817769717, - "num_with_ingredients": 27, - "num_errors": 42, - "num_completed": 2164, - "time_elapsed": 489.1062750816345 -} -``` - -## Helpful Tips - -- Use `max_val_samples` for debugging, to restrict how many datapoints we iterate over during a `do_eval` loop. -- To add a dataset, do the following: - 1) Add a dataset script to [datasets/](./datasets) - 1) See [./datasets/wikitq.py](./datasets/wikitq/wikitq.py) for an example - 2) Add a metric script to [metrics/](./metrics) - 1) See [./metrics/wikitq.py](./metrics/wikitq/wikitq.py) for an example - 3) Put all dataset-specif utils in `./utils/{dataset_name}` - 1) As done in [./utils/wikitq/wikitq.py](./utils/wikitq/wikitq.py), this should also contain a pre-process function - 4) Modify `load_dataset()` in [./utils/dataset_loader.py](./utils/dataset_loader.py) with dataset-specific logic - - -## General Notes -- GPT-4 is much more capable at discerning when we need BlendSQL, vs. can just use native SQL - - Examples: - - using `SUBSTR` calls - - Leveraging table title, like if we have 'Hospitals in California' table, we don't need a condition `state = 'CA'` -- Using no `binary` argument in the LLM MapIngredient call improves results - - On initial 200: - - 0.479 vs 0.463 with text-davinci-003 as parser, gpt-4 as QA - - 0.432 vs 0.406 with text-davinci-003 as parser and QA - - This avoids the catastrophic parsing issue where we fail to include `binary=0` in our ingredient call - - But, puts more pressure on the QA model to be right - -The new `LLMQA` ingredient works, but we have some issues with response formatting. - -https://www.sqlite.org/fts3.html#appendix_a -"In SQLite, when a sub-query used in a join contains a LIMIT clause, the results of the sub-query are calculated and stored in temporary table before the main query is executed." - -https://neuml.hashnode.dev/building-an-efficient-sparse-keyword-index-in-python - -```python -metric.add( - prediction=["The same problem was oil pressure."], - reference={ - "answer_text": ["Oil pressure"], - "question": "what was the same problem that bernard collomb had as innes ireland?", - }, -) -``` -The above WikiTQ metric computation results in a score of 0.0 -We could either do some post-processing (e.g. Bridge to extract 'oil pressure') or better prompting. - -Could benefit from some external tools, such as below -For 'difference of points' - -```json - "sql": "SELECT MAX ( abs ( `Result` - `Result` ) ) FROM w", -"answer_text": [ - "29" -], -"input_program_args": { - "bridge_hints": "", - "question": "what was the largest difference of points in a single game?", - "serialized_db": "Table Description: 1960 Buffalo Bills season\nCREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"week\" INTEGER,\n \"date\" TEXT,\n \"opponent\" TEXT,\n \"result\" TEXT,\n \"game site\" TEXT,\n \"attendance\" INTEGER\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id week date opponent result game site attendance\n 0 0 1 1960-9-11 at new york titans l 27\u20133 polo grounds 10200\n 1 1 2 1960-9-18 denver broncos l 27\u201321 war memorial stadium 15229\n 2 2 3 1960-9-23 at boston patriots w 13\u20130 nickerson field 20732\n*/" -}, -"db_path": "research/db/wikitq/csv/203-csv/775.db", -"pred_text": [ - "8" -], -"pred_sql": "SELECT MAX({{LLMMap('difference of points', 'w::result')}}) FROM w", -"pred_has_ingredient": true, -``` - -## Good Examples -- [Example 1](#good_example_1) -- [Example 2](#good_example_2) -- [Example 3](#good_example_3) -- [Example 4](#good_example_4) -- [Example 5](#good_example_5) - -### Good Example 1 -```json -{ - "id": "nt-267", - "question": "total number of players whose home town was in north carolina (nc)", - "table_id": "csv/204-csv/526.csv", - "table": { - "page_title": "2010\u201311 North Carolina Tar Heels men's basketball team", - "header": [ - "Name", - "#", - "Position", - "Height", - "Weight", - "Year", - "Home Town", - "High School" - ], - "rows": [ - [ - "Harrison Barnes", - "40", - "Forward", - "6\u20138", - "210", - "Freshman", - "Ames, IA", - "Ames" - ], - [ - "Daniel Bolick", - "3", - "Guard", - "5\u201310", - "175", - "Senior", - "Carrboro, NC", - "Chapel Hill" - ], - [ - "Reggie Bullock", - "35", - "Guard", - "6\u20137", - "190", - "Freshman", - "Kinston, NC", - "Kinston" - ], - [ - "Stewart Cooper", - "15", - "Forward", - "6\u20135", - "205", - "Junior", - "Winston-Salem, NC", - "Forsyth County Day" - ], - [ - "Patrick Crouch", - "30", - "Guard", - "5\u201311", - "175", - "Junior", - "Asheville, NC", - "T.C. Roberson" - ], - [ - "Larry Drew II*", - "11", - "Guard", - "6\u20132", - "180", - "Junior", - "Encino, CA", - "Woodland Hills Taft" - ], - [ - "David Dupont", - "22", - "Forward", - "6\u20135", - "195", - "Junior", - "Greensboro, NC", - "Grimsley" - ], - [ - "Van Hatchell", - "13", - "Forward", - "6\u20134", - "185", - "Senior", - "Chapel Hill, NC", - "Cresset Christian" - ], - [ - "John Henson", - "31", - "Forward", - "6\u201310", - "210", - "Sophomore", - "Tampa, FL", - "Sickles" - ], - [ - "D.J. Johnston", - "32", - "Forward", - "6\u20134", - "195", - "Junior", - "Lower Gwynedd, PA", - "Germantown" - ], - [ - "Justin Knox", - "25", - "Forward", - "6\u20139", - "240", - "Graduate", - "Tuscaloosa, AL", - "Central" - ], - [ - "Kendall Marshall", - "5", - "Guard", - "6\u20133", - "186", - "Freshman", - "Dumfries, VA", - "Bishop O'Connell" - ], - [ - "Leslie McDonald", - "15", - "Guard", - "6\u20134", - "215", - "Sophomore", - "Memphis, TN", - "Briarcrest Christian" - ], - [ - "Dexter Strickland", - "1", - "Guard", - "6\u20133", - "180", - "Sophomore", - "Rahway, NJ", - "St. Patrick" - ], - [ - "Justin Watts", - "24", - "Guard", - "6\u20134", - "210", - "Junior", - "Durham, NC", - "Jordan" - ], - [ - "Tyler Zeller", - "44", - "Forward", - "7\u20130", - "250", - "Junior", - "Washington, IN", - "Washington" - ] - ] - }, - "sql": "SELECT COUNT ( `Name` ) FROM w WHERE `Home Town` = 'nc'", - "answer_text": [ - "7" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "total number of players whose home town was in north carolina (nc)", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"name\" TEXT,\n \"#\" INTEGER,\n \"position\" TEXT,\n \"height\" TEXT,\n \"weight\" INTEGER,\n \"year\" TEXT,\n \"home town\" TEXT,\n \"high school\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id name # position height weight year home town high school\n 0 0 harrison barnes 40 forward 6\u20138 210 freshman ames, ia ames\n 1 1 daniel bolick 3 guard 5\u201310 175 senior carrboro, nc chapel hill\n 2 2 reggie bullock 35 guard 6\u20137 190 freshman kinston, nc kinston\n*/" - }, - "db_path": "./research/tmp/8fa07b3b-f51d-4690-ad04-e908fe0cf071.db", - "pred_text": [ - 7 - ], - "pred_sql": "SELECT COUNT(*) FROM w WHERE {{Model('is this town in North Carolina?', 'w::home town')}} = 1" -} -``` -### Good Example 2 -```json -{ - "id": "nt-647", - "question": "in how many games did sri lanka score at least 2 goals?", - "table_id": "csv/204-csv/913.csv", - "table": { - "page_title": "Kasun Jayasuriya", - "header": [ - "#", - "Date", - "Venue", - "Opponent", - "Score", - "Result", - "Competition" - ], - "rows": [ - [ - "1.", - "1 May 2000", - "Rasmee Dhandu Stadium, Mal\u00e9, Maldives", - "Maldives", - "1\u20131", - "1\u20131", - "2000 MFF Golden Jubilee Tournament" - ], - [ - "2.", - "25 August 2000", - "Ho Chi Minh City, Vietnam", - "Vietnam", - "2\u20131", - "2\u20132", - "2000 HCM Cup" - ], - [ - "3.", - "28 March 2001", - "Colombo, Sri Lanka", - "Cambodia", - "1\u20130", - "1\u20130", - "Friendly" - ], - [ - "4.", - "17 May 2001", - "Beirut Municipal Stadium, Beirut, Lebanon", - "Pakistan", - "3\u20132", - "3\u20133", - "2002 FIFA World Cup qualification (AFC)" - ], - [ - "5.", - "30 May 2001", - "Suphachalasai Stadium, Bangkok, Thailand", - "Pakistan", - "1\u20130", - "3\u20131", - "2002 FIFA World Cup qualification (AFC)" - ], - [ - "6.", - "30 May 2001", - "Supachalasai Stadium, Bangkok, Thailand", - "Pakistan", - "3\u20130", - "3\u20131", - "2002 FIFA World Cup qualification (AFC)" - ], - [ - "7.", - "4 April 2002", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Maldives", - "1\u20130", - "1\u20130", - "Friendly" - ], - [ - "8.", - "21 March 2003", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Timor-Leste", - "1\u20131", - "3\u20132", - "2004 AFC Asian Cup qualification" - ], - [ - "9.", - "21 March 2003", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Timor-Leste", - "3\u20132", - "3\u20132", - "2004 AFC Asian Cup qualification" - ], - [ - "10.", - "3 December 2003", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Laos", - "2\u20130", - "3\u20130", - "2006 FIFA World Cup qualification (AFC)" - ], - [ - "11.", - "2 April 2006", - "MA Aziz Stadium, Chittagong, Bangladesh", - "Brunei", - "2\u20130", - "3\u20130", - "2006 AFC Challenge Cup" - ], - [ - "12.", - "12 April 2006", - "MA Aziz Stadium, Chittagong, Bangladesh", - "Nepal", - "1\u20130", - "1\u20131", - "2006 AFC Challenge Cup" - ], - [ - "13.", - "24 March 2007", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Malaysia", - "1\u20134", - "1\u20134", - "Friendly" - ], - [ - "14.", - "2 April 2008", - "Chungshan Stadium, Taipei, Taiwan", - "Guam", - "5\u20131", - "5\u20131", - "2008 AFC Challenge Cup qualification" - ], - [ - "15.", - "4 April 2008", - "Chungshan Stadium, Taipei, Taiwan", - "Pakistan", - "1\u20130", - "7\u20131", - "2008 AFC Challenge Cup qualification" - ], - [ - "16.", - "4 April 2008", - "Chungshan Stadium, Taipei, Taiwan", - "Pakistan", - "4\u20131", - "7\u20131", - "2008 AFC Challenge Cup qualification" - ], - [ - "17.", - "4 April 2008", - "Chungshan Stadium, Taipei, Taiwan", - "Pakistan", - "7\u20131", - "7\u20131", - "2008 AFC Challenge Cup qualification" - ], - [ - "18.", - "6 April 2008", - "Chungshan Stadium, Taipei, Taiwan", - "Chinese Taipei", - "2\u20132", - "2\u20132", - "2008 AFC Challenge Cup qualification" - ], - [ - "19.", - "2 August 2008", - "Gachibowli Athletic Stadium, Hyderabad, India", - "Myanmar", - "1\u20131", - "1\u20133", - "2008 AFC Challenge Cup" - ], - [ - "20.", - "4 April 2009", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Brunei", - "1\u20130", - "5\u20131", - "2010 AFC Challenge Cup qualification" - ], - [ - "21.", - "4 April 2009", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Brunei", - "3\u20130", - "5\u20131", - "2010 AFC Challenge Cup qualification" - ], - [ - "22.", - "4 April 2009", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Brunei", - "4\u20130", - "5\u20131", - "2010 AFC Challenge Cup qualification" - ], - [ - "23.", - "4 April 2009", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Brunei", - "5\u20130", - "5\u20131", - "2010 AFC Challenge Cup qualification" - ], - [ - "24.", - "6 April 2009", - "Sugathadasa Stadium, Colombo, Sri Lanka", - "Chinese Taipei", - "1\u20130", - "2\u20131", - "2010 AFC Challenge Cup qualification" - ], - [ - "25.", - "6 December 2009", - "Bangabandhu National Stadium, Dhaka, Bangladesh", - "Bhutan", - "3\u20130", - "6\u20130", - "2009 South Asian Football Federation Cup" - ], - [ - "26.", - "6 December 2009", - "Bangabandhu National Stadium, Dhaka, Bangladesh", - "Bhutan", - "4\u20130", - "6\u20130", - "2009 South Asian Football Federation Cup" - ], - [ - "27.", - "6 December 2009", - "Bangabandhu National Stadium, Dhaka, Bangladesh", - "Bhutan", - "5\u20130", - "6\u20130", - "2009 South Asian Football Federation Cup" - ] - ] - }, - "sql": "SELECT COUNT ( * ) FROM w WHERE `Score` >= 2", - "answer_text": [ - "16" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "in how many games did sri lanka score at least 2 goals?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"#\" REAL,\n \"date\" TEXT,\n \"venue\" TEXT,\n \"opponent\" TEXT,\n \"score\" TEXT,\n \"result\" TEXT,\n \"competition\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id # date venue opponent score result competition\n 0 0 1.0 2000-5-1 rasmee dhandu stadium, mal\u00e9, maldives maldives 1\u20131 1\u20131 2000 mff golden jubilee tournament\n 1 1 2.0 2000-8-25 ho chi minh city, vietnam vietnam 2\u20131 2\u20132 2000 hcm cup\n 2 2 3.0 2001-3-28 colombo, sri lanka cambodia 1\u20130 1\u20130 friendly\n*/" - }, - "db_path": "./research/tmp/4bd65c0e-5640-42e8-8af9-5274ea1f36d4.db", - "pred_text": [ - 16 - ], - "pred_sql": "SELECT COUNT(*) FROM w WHERE {{Model('how many goals did sri lanka score in this game?', 'w::score')}} >= 2" -} -``` - -### Good Example 3 -```json -{ - "id": "nt-373", - "question": "tell me the number of shows that only have one host per day.", - "table_id": "csv/204-csv/839.csv", - "table": { - "page_title": "List of programs broadcast by Fox News Channel", - "header": [ - "ET", - "Days available", - "Program", - "Host(s)", - "Description" - ], - "rows": [ - [ - "9am\u201310am", - "Monday\u2013Friday", - "The Morning Click", - "Jamie Colby (M,T), Harris Faulkner (W\u2013F)", - "Current events." - ], - [ - "10am\u201310:30am", - "Monday\u2013Friday", - "", - "Lauren Green (M), Uma Pemmaraju (T), Gregg Jarrett (W), Arthel Neville (F), Rick Folbaum (F), Heather Childers", - "Current events." - ], - [ - "11am\u201311:30am", - "Monday\u2013Friday", - "FBN Live", - "Lauren Simonetti", - "Business news and information." - ], - [ - "12pm\u20131pm", - "Monday\u2013Friday", - "On the Hunt", - "Jonathan Hunt", - "Current events." - ], - [ - "2pm\u20133pm", - "Monday", - "Entertainment Hour", - "Ashley Dvorkin", - "Entertainment news, musical performances." - ], - [ - "2pm\u20133pm", - "Tuesday", - "", - "Kimberly Guilfoyle", - "Crime and legal news." - ], - [ - "2pm\u20133pm", - "Wednesday", - "Defcon 3", - "Kathleen Troia McFarland", - "Military news and information." - ], - [ - "2pm\u20133pm", - "Thursday", - "Fox Car Report", - "Gary Gastelu", - "Automotive news and information." - ], - [ - "2pm\u20133pm", - "Friday", - "Spirited Debate", - "Lauren Green", - "Discussion on religious topics." - ] - ] - }, - "sql": "SELECT COUNT ( `Program` ) FROM w WHERE `Host(s)` = 1", - "answer_text": [ - "7" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT year FROM w WHERE opponent = 'elizabeth matory'", - "question": "What year was Elizabeth Matory the opponent of Charles Albert Ruppersberger?", - "serialized_db": "\nTable Description: Dutch Ruppersberger (Electoral history)\nCREATE TABLE w(\n\trow_id int,\n\tyear int,\n\toffice text,\n\telection text,\n\tfilledcolumnname real,\n\tsubject text,\n\tparty text,\n\tvotes text,\n\t% text,\n\tfilledcolumnname_2 real,\n\topponent text,\n\tparty_2 text,\n\tvotes_2 text,\n\t%_2 text)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tyear\toffice\telection\tfilledcolumnname\tsubject\tparty\tvotes\t%\tfilledcolumnname_2\topponent\tparty_2\tvotes_2\t%_2\n0\t1994\tbaltimore county executive\tgeneral\tnan\tdutch ruppersberger\tdemocratic\tn/a\tn/a\tnan\tn/a\tn/a\tn/a\tn/a\n1\t1998\tbaltimore county executive\tgeneral\tnan\tdutch ruppersberger\tdemocratic\t166482\t70.47\tnan\tjohn j. bishop\trepublican\t69449\t29.4\n2\t2002\tnone\tgeneral\tnan\tdutch ruppersberger\tdemocratic\t105718\t54.16\tnan\thelen delich bentley\trepublican\t88954\t45.57\n*/\n" - }, - { - "blendsql": "SELECT system FROM w WHERE system IN ('game boy advance', 'msx (japan only)') ORDER BY japan LIMIT 1", - "question": "Which system has a lower number for Japan of the virtual console systems: Game Boy Advance or the Japan-only console MSX?", - "serialized_db": "\nTable Description: Virtual Console (Titles)\nCREATE TABLE w (\n\trow_id int,\n\tsystem text,\n\tjapan int,\n\t[[list of virtual console games for wii u (north america)|north america]] real,\n\tpal region - europe real,\n\tpal region - australia real\n)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tsystem\tjapan\t[[list of virtual console games for wii u (north america)|north america]]\tpal region - europe\tpal region - australia\n0\tnes/famicom\t148\t94.0\t89.0\t89.0\n1\tsuper nes/super famicom\t101\t51.0\t49.0\t49.0\n2\tnintendo 64\t22\t21.0\t21.0\t21.0\n*/ \n " - }, - { - "blendsql": "SELECT player FROM w WHERE \n {{\n Model('Has crossed swords on its logo?'; 'w::gold coast titans')\n }} = 1\n ", - "question": "What player was transferred from the team that has crossed swords on its logo to the Warrington Wolves in the 2018 season?", - "serialized_db": "\nTable Description: 2018 Warrington Wolves season (Transfers | In)\nCREATE TABLE w(\n\trow_id int,\n\tplayer text,\n\tsigned from text,\n\tcontract length text,\n\tannounced text)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tplayer\tsigned from\tcontract length\tannounced\n0\tsitaleki akauola\tpenrith panthers\tp2y\t2017-08-01 00:00:00\n1\tbryson goodwin\tsouth sydney rabbitohs\tp2y\t2017-10-01 00:00:00\n2\ttyrone roberts\tgold coast titans\tp3y\t2017-10-01 00:00:00\n*/\nCREATE TABLE Images(\n\trow_id int,\n\tgold coast titans text)\n/*\nAll rows of the table:\nSELECT * FROM w;\nrow_id\tgold coast titans\n0\ta logo for the golden knights is painted on the beach.\n*/\n" - }, - { - "blendsql": "SELECT DISTINCT merchant FROM w WHERE\n {{Model('is this a pizza shop?', 'w::merchant'}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'w::description',\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shop merchants in MA", - "serialized_db": "\nTable Description: transactions\nCREATE TABLE w (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n/*\n3 example rows:\nSELECT * FROM transactions LIMIT 3\n index description amount merchant cash_flow parent_category child_category date confidence_score\n 0 Zelle payment from JUSTIN ORENIC BACucw5n1t14 2000.0 Zelle Expense Cash/ATM Online Payment Service 2022-06-20 0.698891\n 1 Zelle Transfer Conf UJ7QJF7TE Emilia Galindo 1500.0 Zelle Expense Cash/ATM Online Payment Service 2022-01-16 0.701658\n 2 Zelle payment from SOPHIA YANEZ WFCT0Q9R3CNR 1474.0 Zelle Expense Cash/ATM Online Payment Service 2022-03-27 0.861237\n*/\n)" - } - ], - "program": "\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL: {{gen \"result\" temperature=0.0}}\n", - "question": "tell me the number of shows that only have one host per day.", - "serialized_db": "Table Description: List of programs broadcast by Fox News Channel\nCREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"et\" TEXT,\n \"days available\" TEXT,\n \"program\" TEXT,\n \"host(s)\" TEXT,\n \"description\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id et days available program host(s) description\n 0 0 (t09,t10,pt1h) (xxxx-wxx-1,xxxx-wxx-5,p4d) the tmo click jamie colby (m,t), harris faulkner (w\u2013f) current events.\n 1 1 (t10,t10:30,pt30m) (xxxx-wxx-1,xxxx-wxx-5,p4d) none lauren green (m), uma pemmaraju (t), gregg jarrett (w), arthel neville (f), rick folbaum (f), heather childers current events.\n 2 2 (t11,t11:30,pt30m) (xxxx-wxx-1,xxxx-wxx-5,p4d) fbn live lauren simonetti business news and information.\n*/" - }, - "db_path": "./research/tmp/2838e14c-9b66-41b8-9fda-e8141a39617c.db", - "pred_text": [ - 7 - ], - "pred_sql": " SELECT COUNT(*) FROM w WHERE \n {{\n Model('How many hosts per day?', 'w::host(s)')\n }} = 1", - "pred_has_ingredient": true, - "example_map_outputs": [ - [ - "2", - "6", - "1", - "1", - "1", - "1", - "1", - "1", - "1" - ] - ] - } -``` -### Good Example 4 -```json -{ - "id": "nt-40", - "question": "what was the number of silver medals won by ukraine?", - "table_id": "csv/203-csv/175.csv", - "table": { - "page_title": "Sailing at the 2004 Summer Olympics", - "header": [ - "Rank", - "Nation", - "Gold", - "Silver", - "Bronze", - "Total" - ], - "rows": [ - [ - "1", - "Great Britain\u00a0(GBR)", - "2", - "1", - "2", - "5" - ], - [ - "2", - "Brazil\u00a0(BRA)", - "2", - "0", - "0", - "2" - ], - [ - "3", - "Spain\u00a0(ESP)", - "1", - "2", - "0", - "3" - ], - [ - "4", - "Austria\u00a0(AUT)", - "1", - "1", - "0", - "2" - ], - [ - "4", - "Greece\u00a0(GRE)", - "1", - "1", - "0", - "2" - ], - [ - "4", - "United States\u00a0(USA)", - "1", - "1", - "0", - "2" - ], - [ - "7", - "France\u00a0(FRA)", - "1", - "0", - "1", - "2" - ], - [ - "7", - "Israel\u00a0(ISR)", - "1", - "0", - "0", - "1" - ], - [ - "7", - "Norway\u00a0(NOR)", - "1", - "0", - "0", - "1" - ], - [ - "10", - "Ukraine\u00a0(UKR)", - "0", - "2", - "0", - "2" - ], - [ - "11", - "China\u00a0(CHN)", - "0", - "1", - "0", - "1" - ], - [ - "11", - "Czech Republic\u00a0(CZE)", - "0", - "1", - "0", - "1" - ], - [ - "11", - "Canada\u00a0(CAN)", - "0", - "1", - "0", - "1" - ], - [ - "14", - "Denmark\u00a0(DEN)", - "0", - "0", - "2", - "2" - ], - [ - "15", - "Argentina\u00a0(ARG)", - "0", - "0", - "1", - "1" - ], - [ - "15", - "Italy\u00a0(ITA)", - "0", - "0", - "1", - "1" - ], - [ - "15", - "Japan\u00a0(JPN)", - "0", - "0", - "1", - "1" - ], - [ - "15", - "Poland\u00a0(POL)", - "0", - "0", - "1", - "1" - ], - [ - "15", - "Slovenia\u00a0(SLO)", - "0", - "0", - "1", - "1" - ], - [ - "15", - "Sweden\u00a0(SWE)", - "0", - "0", - "1", - "1" - ], - [ - "Total", - "", - "11", - "11", - "11", - "33" - ] - ] - }, - "sql": "SELECT `Silver` FROM w WHERE `Nation` = 'ukraine'", - "answer_text": [ - "2" - ], - "input_program_args": { - "bridge_hints": "", - "endpoint_name": "gpt-4", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;',\n endpoint_name='gpt-4'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description', enpoint_name='gpt-4')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "what was the number of silver medals won by ukraine?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"rank\" TEXT,\n \"nation\" TEXT,\n \"gold\" INTEGER,\n \"silver\" INTEGER,\n \"bronze\" INTEGER,\n \"total\" INTEGER\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id rank nation gold silver bronze total\n 0 0 1 great britain\u00a0(gbr) 2 1 2 5\n 1 1 2 brazil\u00a0(bra) 2 0 0 2\n 2 2 3 spain\u00a0(esp) 1 2 0 3\n*/" - }, - "db_path": "./research/tmp/66b50e28-4ffd-4890-8236-5a7f842f284b.db", - "pred_text": [ - 2 - ], - "pred_sql": "SELECT silver FROM w WHERE {{Model('is this the country ukraine?', 'w::nation', endpoint_name='gpt-4')}} = 1" - } -``` - -### Good Example 5 -```json -{ - "id": "nt-353", - "question": "between bjorn ferry, simon elder and erik lesser - who had the most penalties?", - "table_id": "csv/204-csv/664.csv", - "table": { - "page_title": "Biathlon World Championships 2013 \u2013 Men's pursuit", - "header": [ - "Rank", - "Bib", - "Name", - "Nationality", - "Start", - "Penalties (P+P+S+S)", - "Time", - "Deficit" - ], - "rows": [ - [ - "", - "1", - "Emil Hegle Svendsen", - "Norway", - "0:00", - "1 (0+0+0+1)", - "32:35.5", - "" - ], - [ - "", - "2", - "Martin Fourcade", - "France", - "0:08", - "2 (0+1+1+0)", - "32:35.6", - "+0.1" - ], - [ - "", - "7", - "Anton Shipulin", - "Russia", - "0:33", - "1 (0+0+1+0)", - "32:39.1", - "+3.6" - ], - [ - "4", - "5", - "Dmitry Malyshko", - "Russia", - "0:23", - "0 (0+0+0+0)", - "32:40.9", - "+5.4" - ], - [ - "5", - "15", - "Dominik Landertinger", - "Austria", - "0:58", - "0 (0+0+0+0)", - "32:55.9", - "+20.4" - ], - [ - "6", - "3", - "Jakov Fak", - "Slovenia", - "0:11", - "1 (0+0+0+1)", - "33:05.2", - "+29.7" - ], - [ - "7", - "8", - "Fredrik Lindstr\u00f6m", - "Sweden", - "0:38", - "1 (0+0+1+0)", - "33:18.9", - "+43.4" - ], - [ - "8", - "6", - "Alexis B\u0153uf", - "France", - "0:25", - "2 (0+0+2+0)", - "33:22.1", - "+46.6" - ], - [ - "9", - "13", - "Bj\u00f6rn Ferry", - "Sweden", - "0:52", - "0 (0+0+0+0)", - "33:23.6", - "+48.1" - ], - [ - "10", - "4", - "Ole Einar Bj\u00f8rndalen", - "Norway", - "0:20", - "4 (2+0+1+1)", - "33:27.0", - "+51.5" - ], - [ - "11", - "14", - "Lukas Hofer", - "Italy", - "0:57", - "2 (0+2+0+0)", - "33:28.8", - "+53.3" - ], - [ - "12", - "10", - "Simon Eder", - "Austria", - "0:39", - "3 (0+0+2+1)", - "33:50.5", - "+1:15.0" - ], - [ - "13", - "32", - "Lowell Bailey", - "United States", - "1:38", - "0 (0+0+0+0)", - "33:56.4", - "+1:20.9" - ], - [ - "14", - "12", - "Erik Lesser", - "Germany", - "0:49", - "4 (1+0+2+1)", - "34:06.0", - "+1:30.5" - ], - [ - "15", - "36", - "Jean-Philippe Leguellec", - "Canada", - "1:45", - "1 (0+0+0+1)", - "34:08.7", - "+1:33.2" - ], - [ - "16", - "9", - "Evgeny Ustyugov", - "Russia", - "0:38", - "3 (0+1+2+0)", - "34:16.7", - "+1:41.2" - ], - [ - "17", - "18", - "Tarjei B\u00f8", - "Norway", - "1:14", - "3 (2+0+1+0)", - "34:18.9", - "+1:43.4" - ], - [ - "18", - "29", - "Simon Schempp", - "Germany", - "1:36", - "1 (0+0+0+1)", - "34:24.4", - "+1:48.9" - ], - [ - "19", - "11", - "Krasimir Anev", - "Bulgaria", - "0:48", - "2 (0+1+0+1)", - "34:24.7", - "+1:49.2" - ], - [ - "20", - "37", - "Henrik L'Abee-Lund", - "Norway", - "1:45", - "1 (0+0+0+1)", - "34:36.1", - "+2:00.6" - ], - [ - "21", - "16", - "Arnd Peiffer", - "Germany", - "1:08", - "3 (0+0+2+1)", - "34:39.8", - "+2:04.3" - ], - [ - "22", - "23", - "Andreas Birnbacher", - "Germany", - "1:25", - "3 (0+1+1+1)", - "34:41.7", - "+2:06.2" - ], - [ - "23", - "34", - "Simon Fourcade", - "France", - "1:42", - "3 (0+3+0+0)", - "34:51.1", - "+2:15.6" - ], - [ - "24", - "21", - "Klemen Bauer", - "Slovenia", - "1:16", - "4 (1+0+3+0)", - "34:55.4", - "+2:19.9" - ], - [ - "25", - "26", - "Christian De Lorenzi", - "Italy", - "1:30", - "1 (0+0+0+1)", - "34:57.5", - "+2:22.0" - ], - [ - "26", - "20", - "Jean-Guillaume B\u00e9atrix", - "France", - "1:15", - "4 (0+1+2+1)", - "34:58.4", - "+2:22.9" - ], - [ - "27", - "33", - "Daniel Mesotitsch", - "Austria", - "1:39", - "3 (0+0+2+1)", - "34:58.7", - "+2:23.2" - ], - [ - "28", - "19", - "Evgeniy Garanichev", - "Russia", - "1:14", - "5 (2+0+2+1)", - "35:04.2", - "+2:28.7" - ], - [ - "29", - "30", - "Matej Kaz\u00e1r", - "Slovakia", - "1:36", - "5 (2+1+1+1)", - "35:04.9", - "+2:29.4" - ], - [ - "30", - "17", - "Andriy Deryzemlya", - "Ukraine", - "1:13", - "4 (0+0+2+2)", - "35:05.3", - "+2:29.8" - ], - [ - "31", - "35", - "Ond\u0159ej Moravec", - "Czech Republic", - "1:42", - "3 (0+1+1+1)", - "35:05.3", - "+2:48.2" - ], - [ - "32", - "28", - "Tim Burke", - "United States", - "1:36", - "4 (0+1+2+1)", - "35:33.8", - "+2:58.3" - ], - [ - "33", - "58", - "Dominik Windisch", - "Italy", - "2:31", - "2 (0+0+1+1)", - "35:36.4", - "+3:00.9" - ], - [ - "34", - "54", - "Carl Johan Bergman", - "Sweden", - "2:19", - "2 (0+0+1+1)", - "35:37.0", - "+3:01.5" - ], - [ - "35", - "25", - "Benjamin Weger", - "Switzerland", - "1:28", - "3 (0+1+1+1)", - "36:13.1", - "+3:37.6" - ], - [ - "36", - "42", - "Artem Pryma", - "Ukraine", - "1:59", - "3 (1+2+0+0)", - "36:13.2", - "+3:37.7" - ], - [ - "37", - "50", - "Pavol Hurajt", - "Slovakia", - "2:14", - "2 (0+1+0+1)", - "36:18.9", - "+3:43.4" - ], - [ - "38", - "22", - "Tomas Kauk\u0117nas", - "Lithuania", - "1:20", - "4 (0+0+2+2)", - "36:33.4", - "+3:57.9" - ], - [ - "39", - "43", - "Vladimir Iliev", - "Bulgaria", - "2:03", - "5 (1+1+2+1)", - "36:43.4", - "+4:07.9" - ], - [ - "40", - "40", - "Edgars Piksons", - "Latvia", - "1:55", - "4 (1+0+2+1)", - "36:49.8", - "+4:14.3" - ], - [ - "41", - "60", - "Christian Martinelli", - "Italy", - "2:36", - "2 (1+0+0+1)", - "36:53.0", - "+4:17.5" - ], - [ - "42", - "27", - "Jaroslav Soukup", - "Czech Republic", - "1:32", - "6 (2+2+2+0)", - "36:59.8", - "+4:24.3" - ], - [ - "43", - "53", - "Leif Nordgren", - "United States", - "2:19", - "6 (2+2+2+0)", - "37:08.3", - "+4:32.8" - ], - [ - "44", - "56", - "Hidenori Isa", - "Japan", - "2:27", - "2 (0+0+0+2)", - "37:13.9", - "+4:38.4" - ], - [ - "45", - "41", - "Serhiy Semenov", - "Ukraine", - "1:55", - "5 (3+1+1+0)", - "37:14.0", - "+4:38.5" - ], - [ - "46", - "24", - "Zden\u011bk V\u00edtek", - "Czech Republic", - "1:25", - "7 (2+2+1+2)", - "37:21.0", - "+4:45.5" - ], - [ - "47", - "49", - "Milanko Petrovi\u0107", - "Serbia", - "2:14", - "6 (2+2+2+0)", - "37:26.6", - "+4:51.1" - ], - [ - "48", - "31", - "Kauri K\u00f5iv", - "Estonia", - "1:37", - "6 (2+1+2+1)", - "37:29.9", - "+4:54.4" - ], - [ - "49", - "47", - "Yan Savitskiy", - "Kazakhstan", - "2:11", - "5 (2+2+0+1)", - "37:51.8", - "+5:16.3" - ], - [ - "50", - "45", - "Scott Gow", - "Canada", - "2:06", - "5 (0+0+1+4)", - "37:57.4", - "+5:21.9" - ], - [ - "51", - "48", - "Claudio B\u00f6ckli", - "Switzerland", - "2:13", - "3 (2+0+0+1)", - "38:02.8", - "+5:27.3" - ], - [ - "52", - "44", - "Russell Currier", - "United States", - "2:05", - "7 (3+1+2+1)", - "38:06.2", - "+5:30.7" - ], - [ - "53", - "57", - "Vladimir Chepelin", - "Belarus", - "2:28", - "4 (0+1+2+1)", - "38:06.5", - "+5:31.0" - ], - [ - "54", - "46", - "Aliaksandr Babchyn", - "Belarus", - "2:06", - "6 (3+2+1+0)", - "38:19.8", - "+5:44.3" - ], - [ - "55", - "59", - "Miroslav Matia\u0161ko", - "Slovakia", - "2:36", - "6 (3+0+2+1)", - "38:54.6", - "+6:19.1" - ], - [ - "56", - "38", - "Jarkko Kauppinen", - "Finland", - "1:46", - "6 (1+1+3+1)", - "39:06.6", - "+6:31.1" - ], - [ - "57", - "51", - "Junji Nagai", - "Japan", - "2:17", - "7 (0+2+3+2)", - "39:49.9", - "+7:14.4" - ], - [ - "", - "55", - "Stefan Gavrila", - "Romania", - "2:25", - "10 (4+3+3)", - "LAP", - "" - ], - [ - "", - "39", - "Sergei Sednev", - "Ukraine", - "1:54", - "", - "DNS", - "" - ], - [ - "", - "52", - "Michal \u0160lesingr", - "Czech Republic", - "2:18", - "", - "DNS", - "" - ] - ] - }, - "sql": "SELECT `Name` FROM w WHERE `Name` IN ( 'bjorn ferry' , 'simon eder' , 'erik lesser' ) ORDER by `Penalties (P+P+S+S)` desc LIMIT 1", - "answer_text": [ - "Erik Lesser" - ], - "input_program_args": { - "bridge_hints": "name ( erik lesser )", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "between bjorn ferry, simon elder and erik lesser - who had the most penalties?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"rank\" REAL,\n \"bib\" INTEGER,\n \"name\" TEXT,\n \"nationality\" TEXT,\n \"start\" TEXT,\n \"penalties (p+p+s+s)\" TEXT,\n \"time\" TEXT,\n \"deficit\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id rank bib name nationality start penalties (p+p+s+s) time deficit\n 0 0 None 1 emil hegle svendsen norway 0:0 1 (0+0+0+1) 32:35.5 none\n 1 1 None 2 martin fourcade france 0:8 2 (0+1+1+0) 32:35.6 +0.1\n 2 2 None 7 anton shipulin russia 0:33 1 (0+0+1+0) 32:39.1 +3.6\n*/" - }, - "db_path": "./research/tmp/bc195c10-5a92-4b4c-a665-59548a2a822a.db", - "pred_text": [ - "erik lesser", - null - ], - "pred_sql": "SELECT name, MAX({{Model('how many penalties does this person have?', 'w::penalties (p+p+s+s)', binary=0, example_outputs='0;1;2;3;4;5;6;7;8;9;10;') }}) as penalties FROM w WHERE name IN ('bjorn ferry', 'simon elder', 'erik lesser') GROUP BY name ORDER BY penalties DESC LIMIT 1" -} -``` -## Interesting Mistakes -1) Correctly checks out the 'remarks' column, but should be 'how many pylons does this remark mention?' -```json -{ - "id": "nt-53", - "question": "what is the total number of pylons listed?", - "table_id": "csv/203-csv/375.csv", - "table": { - "page_title": "Aerial lift pylon", - "header": [ - "Name", - "Year built", - "Country", - "Town", - "Height", - "Remarks" - ], - "rows": [ - [ - "Glacial Aerial Tramway Kaprun III", - "1966", - "Austria", - "Kaprun", - "113.6 m", - "The tallest pylon is on the third section" - ], - [ - "Mississippi Aerial River Transit", - "1984", - "USA", - "New Orleans", - "109 m", - "The tallest pylon on a gondola lift; on 87 m pile foundations; demolished 1994" - ], - [ - "Torre Jaume I", - "1931", - "Spain", - "Barcelona", - "107 m", - "Intermediate stop of the harbour aerial tramway, also observation tower" - ], - [ - "Gant Hohtaelli aerial tramway", - "", - "Switzerland", - "Zermatt", - "94 m", - "One pylon" - ], - [ - "London Emirates Air Line", - "Early 2012", - "UK", - "London", - "88 m", - "North Main Tower. River Thames aerial crossing between Greenwich Peninsular and Royal Docks" - ], - [ - "Singapore cable car", - "1972", - "Singapore", - "Singapore", - "88 m", - "Pylon I" - ], - [ - "Eibsee Aerial Tramway", - "1962", - "Germany", - "Garmisch-Partenkirchen", - "85 m", - "Pylon II" - ], - [ - "Nizhny Novgorod Volga Aerial Tramway, Tower 4 & 5", - "2012", - "Russia", - "Nizhny Novgorod", - "82 m", - "" - ], - [ - "Mittersill goods aerial tramway", - "194?", - "Austria", - "Mittersill", - "80 m", - "Two pylons for a tramway that never went in service and was demolished in the 1950s. One of the pylons was built of timber, the other of steel." - ], - [ - "Singapore cable car", - "1972", - "Singapore", - "Singapore", - "80 m", - "Pylon II" - ], - [ - "3S Aerial Tramway", - "2004", - "Austria", - "Kitzb\u00fchel", - "80 m", - "One pylon" - ], - [ - "Torre Sant Sebastia", - "1931", - "Spain", - "Barcelona", - "78 m", - "Terminal of harbour aerial tramway" - ], - [ - "Roosevelt Island Tramway", - "1976", - "USA", - "New York City", - "76 m", - "Central pylon of commuter tramway" - ], - [ - "Wendelstein Aerial Tramway", - "1970", - "Germany", - "Bayerischzell-Osterhofen", - "75 m", - "" - ], - [ - "Vinpearl Cable Car", - "2007", - "Vietnam", - "Nha Trang", - "75 m", - "7 pylons standing in the sea. Total height from sea bed is 115 m" - ], - [ - "Sandia Peak Tramway", - "1965", - "USA", - "Albuquerque", - "70.7 m", - "Pylon 1, inclined at an angle of 18 degrees" - ], - [ - "Eibsee Aerial Tramway", - "1962", - "Germany", - "Garmisch-Partenkirchen", - "65 m", - "Pylon I" - ] - ] - }, - "sql": "SELECT COUNT ( `Name` ) FROM w", - "answer_text": [ - "17" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "what is the total number of pylons listed?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"name\" TEXT,\n \"year built\" TEXT,\n \"country\" TEXT,\n \"town\" TEXT,\n \"height\" TEXT,\n \"remarks\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id name year built country town height remarks\n 0 0 glacial aerial tramway kaprun iii 1966 austria kaprun 113.6 m the tallest pylon is on the third section\n 1 1 mississippi aerial river transit 1984 usa new orleans 109 m the tallest pylon on a gondola lift; on 87 m pile foundations; demolished 1994\n 2 2 torre jaume i 1931 spain barcelona 107 m intermediate stop of the harbour aerial tramway, also observation tower\n*/" - }, - "db_path": "./research/tmp/ec0862aa-6fa1-4032-acb0-e340eba39301.db", - "pred_text": [ - 12 - ], - "pred_sql": "SELECT COUNT(*) FROM w WHERE {{Model('does this remark mention a pylon?', 'w::remarks')}} = 1" - } -``` - -2) Failing to add arguments `binary=0` -```json -{ - "id": "nt-395", - "question": "what is the pylon with the least height listed here?", - "table_id": "csv/203-csv/375.csv", - "table": { - "page_title": "Aerial lift pylon", - "header": [ - "Name", - "Year built", - "Country", - "Town", - "Height", - "Remarks" - ], - "rows": [ - [ - "Glacial Aerial Tramway Kaprun III", - "1966", - "Austria", - "Kaprun", - "113.6 m", - "The tallest pylon is on the third section" - ], - [ - "Mississippi Aerial River Transit", - "1984", - "USA", - "New Orleans", - "109 m", - "The tallest pylon on a gondola lift; on 87 m pile foundations; demolished 1994" - ], - [ - "Torre Jaume I", - "1931", - "Spain", - "Barcelona", - "107 m", - "Intermediate stop of the harbour aerial tramway, also observation tower" - ], - [ - "Gant Hohtaelli aerial tramway", - "", - "Switzerland", - "Zermatt", - "94 m", - "One pylon" - ], - [ - "London Emirates Air Line", - "Early 2012", - "UK", - "London", - "88 m", - "North Main Tower. River Thames aerial crossing between Greenwich Peninsular and Royal Docks" - ], - [ - "Singapore cable car", - "1972", - "Singapore", - "Singapore", - "88 m", - "Pylon I" - ], - [ - "Eibsee Aerial Tramway", - "1962", - "Germany", - "Garmisch-Partenkirchen", - "85 m", - "Pylon II" - ], - [ - "Nizhny Novgorod Volga Aerial Tramway, Tower 4 & 5", - "2012", - "Russia", - "Nizhny Novgorod", - "82 m", - "" - ], - [ - "Mittersill goods aerial tramway", - "194?", - "Austria", - "Mittersill", - "80 m", - "Two pylons for a tramway that never went in service and was demolished in the 1950s. One of the pylons was built of timber, the other of steel." - ], - [ - "Singapore cable car", - "1972", - "Singapore", - "Singapore", - "80 m", - "Pylon II" - ], - [ - "3S Aerial Tramway", - "2004", - "Austria", - "Kitzb\u00fchel", - "80 m", - "One pylon" - ], - [ - "Torre Sant Sebastia", - "1931", - "Spain", - "Barcelona", - "78 m", - "Terminal of harbour aerial tramway" - ], - [ - "Roosevelt Island Tramway", - "1976", - "USA", - "New York City", - "76 m", - "Central pylon of commuter tramway" - ], - [ - "Wendelstein Aerial Tramway", - "1970", - "Germany", - "Bayerischzell-Osterhofen", - "75 m", - "" - ], - [ - "Vinpearl Cable Car", - "2007", - "Vietnam", - "Nha Trang", - "75 m", - "7 pylons standing in the sea. Total height from sea bed is 115 m" - ], - [ - "Sandia Peak Tramway", - "1965", - "USA", - "Albuquerque", - "70.7 m", - "Pylon 1, inclined at an angle of 18 degrees" - ], - [ - "Eibsee Aerial Tramway", - "1962", - "Germany", - "Garmisch-Partenkirchen", - "65 m", - "Pylon I" - ] - ] - }, - "sql": "SELECT `Name` FROM w ORDER by `Height` asc LIMIT 1", - "answer_text": [ - "Eibsee Aerial Tramway" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "what is the pylon with the least height listed here?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"name\" TEXT,\n \"year built\" TEXT,\n \"country\" TEXT,\n \"town\" TEXT,\n \"height\" TEXT,\n \"remarks\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id name year built country town height remarks\n 0 0 glacial aerial tramway kaprun iii 1966 austria kaprun 113.6 m the tallest pylon is on the third section\n 1 1 mississippi aerial river transit 1984 usa new orleans 109 m the tallest pylon on a gondola lift; on 87 m pile foundations; demolished 1994\n 2 2 torre jaume i 1931 spain barcelona 107 m intermediate stop of the harbour aerial tramway, also observation tower\n*/" - }, - "db_path": "./research/tmp/4820622f-ef25-43f9-aa93-6501cd7db235.db", - "pred_text": [ - "mississippi aerial river transit", - "109 m" - ], - "pred_sql": "SELECT name, height FROM w ORDER BY {{Model('what is the numerical value of this height?', 'w::height')}} ASC LIMIT 1" -} -``` - -3) Could have reused previous ingredient (maybe bad few-shot prompts at fault here) -```json -{ - "id": "nt-397", - "question": "how many provinces have the same number of districts as kabul?", - "table_id": "csv/204-csv/704.csv", - "table": { - "page_title": "Outline of Afghanistan", - "header": [ - "Province", - "Map #", - "ISO 3166-2:AF", - "Centers", - "Population", - "Area (km\u00b2)", - "Language", - "Notes", - "U.N. Region" - ], - "rows": [ - [ - "Badakhshan", - "30", - "AF-BDS", - "Fayzabad", - "823,000", - "44,059", - "Dari Persian,", - "Composed of 29 districts", - "North East Afghanistan" - ], - [ - "Badghis", - "4", - "AF-BDG", - "Qala i Naw", - "429,500", - "20,591", - "Dari Persian, Pashto", - "7 districts", - "West Afghanistan" - ], - [ - "Baghlan", - "19", - "AF-BGL", - "Puli Khumri", - "779,000", - "21,118", - "Dari Persian Uzbek, Turkmen, Pashto", - "16 districts", - "North East Afghanistan" - ], - [ - "Balkh", - "13", - "AF-BAL", - "Mazari Sharif", - "1,096,100", - "17,249", - "Dari Persian,", - "15 districts", - "North Afghanistan" - ], - [ - "Bamyan", - "15", - "AF-BAM", - "Bamyan", - "387,300", - "14,175", - "Dari Persian", - "7 districts", - "East Central Afghanistan" - ], - [ - "Daykundi", - "10", - "AF-DAY", - "Nili", - "399,600", - "8,088", - "Dari Persian", - "8 districts\\nFormed from Or\u016bzg\u0101n Province in 2004", - "South West Afghanistan" - ], - [ - "Farah", - "2", - "AF-FRA", - "Farah", - "438,000", - "48,471", - "Pashto, Dari Persian, Baloch", - "11 districts", - "West Afghanistan" - ], - [ - "Faryab", - "5", - "AF-FYB", - "Maymana", - "858,600", - "20,293", - "Dari Persian, Turkmen and Uzbek", - "14 districts", - "West Afghanistan" - ], - [ - "Ghazni", - "16", - "AF-GHA", - "Ghazni", - "931,000", - "22,915", - "Pashto, Dari Persian", - "19 districts", - "South Afghanistan" - ], - [ - "Ghor", - "6", - "AF-GHO", - "Chaghcharan", - "485,000", - "36,479", - "Dari Persian", - "10 districts", - "East Central Afghanistan" - ], - [ - "Helmand", - "7", - "AF-HEL", - "Lashkar Gah", - "745,000", - "58,584", - "Pashto,Baloch", - "13 districts", - "South West Afghanistan" - ], - [ - "Herat", - "1", - "AF-HER", - "Herat", - "1,182,000", - "54,778", - "Dari Persian, Pashto", - "15 districts", - "West Afghanistan" - ], - [ - "Jowzjan", - "8", - "AF-JOW", - "Sheberghan", - "441,000", - "11,798", - "Uzbek, Dari Persian", - "9 districts", - "North Afghanistan" - ], - [ - "Kabul", - "22", - "AF-KAB", - "Kabul", - "3,314,000", - "4,462", - "Pashto, Dari Persian, Turkmen, Baloch and Uzbek", - "15 districts", - "Central Afghanistan" - ], - [ - "Kandahar", - "12", - "AF-KAN", - "Kandahar", - "886,000", - "54,022", - "Pashto, Dari Persian,Baloch", - "16 districts", - "South West Afghanistan" - ], - [ - "Kapisa", - "29", - "AF-KAP", - "Mahmud-i-Raqi", - "360,000", - "1,842", - "Dari Persian", - "7 districts", - "Central Afghanistan" - ], - [ - "Khost", - "26", - "AF-KHO", - "Khost", - "300,000", - "4,152", - "Pashto", - "13 districts", - "South Afghanistan" - ], - [ - "Kunar", - "34", - "AF-KNR", - "Asadabad", - "321,000", - "4,942", - "Pashto", - "15 districts", - "East Afghanistan" - ], - [ - "Kunduz", - "18", - "AF-KDZ", - "Kunduz", - "820,000", - "8,040", - "Dari Persian, Uzbek, Turkmen, Pashto", - "7 districts", - "North East Afghanistan" - ], - [ - "Laghman", - "32", - "AF-LAG", - "Mihtarlam District", - "373,000", - "3,843", - "Pashto, Dari Persian and Pashai", - "5 districts", - "East Afghanistan" - ], - [ - "Lowgar", - "23", - "AF-LOW", - "Pul-i-Alam", - "292,000", - "3,880", - "Dari Persian, Pashto", - "7 districts", - "Central Afghanistan" - ], - [ - "Nangarhar", - "33", - "AF-NAN", - "Jalalabad", - "1,089,000", - "7,727", - "Pashto, Dari Persian", - "23 districts", - "East Afghanistan" - ], - [ - "Nimruz", - "3", - "AF-NIM", - "Zaranj", - "149,000", - "41,005", - "Balochi, Dari Persian and Balochi", - "5 districts", - "South West Afghanistan" - ], - [ - "Nurestan", - "31", - "AF-NUR", - "Parun", - "112,000", - "9,225", - "Nuristani, Pashto, Dari Persian", - "7 districts", - "East Afghanistan" - ], - [ - "Or\u016bzg\u0101n", - "11", - "AF-ORU", - "Tarin Kowt", - "627,000", - "22,696", - "Pashto", - "6 districts", - "South West Afghanistan" - ], - [ - "Paktia", - "24", - "AF-PIA", - "Gardez", - "415,000", - "6,432", - "Pashto, Dari Persian", - "11 districts", - "South Afghanistan" - ], - [ - "Paktika", - "25", - "AF-PKA", - "Sharan", - "352,000", - "19,482", - "Pashto", - "15 districts", - "South Afghanistan" - ], - [ - "Panjshir", - "28", - "AF-PAN", - "Bazarak", - "128,620", - "3,610", - "Dari Persian", - "5 districts\\nCreated in 2004 from Parwan Province", - "Central Afghanistan" - ], - [ - "Parwan", - "20", - "AF-PAR", - "Charikar", - "726,000", - "5,974", - "Dari Persian", - "Composed of 9 districts", - "Central Afghanistan" - ], - [ - "Samangan", - "14", - "AF-SAM", - "Aybak", - "378,000", - "11,262", - "Dari Persian, Uzbek", - "5 districts", - "North Afghanistan" - ], - [ - "Sar-e Pol", - "9", - "AF-SAR", - "Sar-e Pol", - "468,000", - "15,999", - "Dari Persian and Uzbek", - "6 districts", - "North Afghanistan" - ], - [ - "Takhar", - "27", - "AF-TAK", - "Taloqan", - "810,800", - "12,333", - "Dari Persian, and Uzbek", - "12 districts", - "North East Afghanistan" - ], - [ - "Wardak", - "21", - "AF-WAR", - "Meydan Shahr", - "413,000", - "8,938", - "Pashto, Dari Persian", - "9 districts", - "Central Afghanistan" - ], - [ - "Zabul", - "17", - "AF-ZAB", - "Qalat", - "365,920", - "17,343", - "Pashto", - "9 districts", - "South West Afghanistan" - ] - ] - }, - "sql": "SELECT COUNT ( `Province` ) FROM w WHERE `Province` != 'kabul' and `Notes` = ( SELECT `Notes` FROM w WHERE `Province` = 'kabul' )", - "answer_text": [ - "4" - ], - "input_program_args": { - "bridge_hints": "province ( kabul )\ncenters ( kabul )", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "how many provinces have the same number of districts as kabul?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"province\" TEXT,\n \"map #\" INTEGER,\n \"iso 3166-2:af\" TEXT,\n \"centers\" TEXT,\n \"population\" INTEGER,\n \"area (km\u00b2)\" INTEGER,\n \"language\" TEXT,\n \"notes\" TEXT,\n \"u.n. region\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id province map # iso 3166-2:af centers population area (km\u00b2) language notes u.n. region\n 0 0 badakhshan 30 af-bds fayzabad 823000 44059 dari persian, composed of 29 districts north east afghanistan\n 1 1 badghis 4 af-bdg qala i naw 429500 20591 dari persian, pashto 7 districts west afghanistan\n 2 2 baghlan 19 af-bgl puli khumri 779000 21118 dari persian uzbek, turkmen, pashto 16 districts north east afghanistan\n*/" - }, - "db_path": "./research/tmp/874c2ef9-1a93-49e3-99c9-4b7f79949c74.db", - "pred_text": [ - 34 - ], - "pred_sql": "SELECT COUNT(DISTINCT province) FROM w WHERE\n {{Model('how many districts does this province have?', 'w::notes')}} = \n {{Model('how many districts does this province have?', 'w::notes', example_inputs='kabul')}}" -} -``` -4) **IMPORTANT** Didn't know what 'that year' means if we don't pass in table header -```json -{ - "id": "nt-624", - "question": "how many players made an appearance that year?", - "table_id": "csv/203-csv/456.csv", - "table": { - "page_title": "1968 in Japanese football", - "header": [ - "Player", - "-1967", - "03.30", - "03.31", - "04.04", - "10.14", - "1968", - "Total" - ], - "rows": [ - [ - "Shigeo Yaegashi", - "41(11)", - "O", - "O", - "O", - "O", - "4(0)", - "45(11)" - ], - [ - "Mitsuo Kamata", - "37(2)", - "O", - "-", - "O", - "O", - "3(0)", - "40(2)" - ], - [ - "Masakatsu Miyamoto", - "36(1)", - "-", - "O", - "-", - "O", - "2(0)", - "38(1)" - ], - [ - "Masashi Watanabe", - "34(11)", - "-", - "O", - "O", - "-", - "2(0)", - "36(11)" - ], - [ - "Teruki Miyamoto", - "33(15)", - "O", - "O", - "O", - "O", - "4(0)", - "37(15)" - ], - [ - "Ryuichi Sugiyama", - "31(11)", - "O", - "O", - "O(1)", - "O", - "4(1)", - "35(12)" - ], - [ - "Hiroshi Katayama", - "26(0)", - "O", - "-", - "O", - "O", - "3(0)", - "29(0)" - ], - [ - "Ryozo Suzuki", - "22(0)", - "O", - "-", - "O", - "-", - "2(0)", - "24(0)" - ], - [ - "Kunishige Kamamoto", - "17(21)", - "O(2)", - "O", - "O(2)", - "O(3)", - "4(7)", - "21(28)" - ], - [ - "Aritatsu Ogi", - "16(5)", - "O", - "-", - "O", - "O", - "3(0)", - "19(5)" - ], - [ - "Yoshitada Yamaguchi", - "16(0)", - "O", - "O", - "O", - "-", - "3(0)", - "19(0)" - ], - [ - "Kenzo Yokoyama", - "16(0)", - "O", - "-", - "O", - "O", - "3(0)", - "19(0)" - ], - [ - "Hisao Kami", - "13(0)", - "O", - "O", - "-", - "-", - "2(0)", - "15(0)" - ], - [ - "Takaji Mori", - "9(1)", - "O", - "O", - "O", - "O", - "4(0)", - "13(1)" - ], - [ - "Ikuo Matsumoto", - "7(1)", - "O", - "-", - "-", - "O", - "2(0)", - "9(1)" - ], - [ - "Yasuyuki Kuwahara", - "5(3)", - "-", - "O(1)", - "-", - "O", - "2(1)", - "7(4)" - ], - [ - "Takeo Kimura", - "3(1)", - "O", - "O", - "-", - "-", - "2(0)", - "5(1)" - ], - [ - "Kiyoshi Tomizawa", - "3(0)", - "-", - "O", - "-", - "-", - "1(0)", - "4(0)" - ], - [ - "Koji Funamoto", - "1(0)", - "-", - "O", - "-", - "-", - "1(0)", - "2(0)" - ], - [ - "Junji Kawano", - "0(0)", - "-", - "O", - "-", - "-", - "1(0)", - "1(0)" - ] - ] - }, - "sql": "SELECT COUNT ( `Player` ) FROM w", - "answer_text": [ - "20" - ], - "input_program_args": { - "bridge_hints": "", - "examples": [ - { - "blendsql": "SELECT DISTINCT description, merchant FROM transactions WHERE\n {{Model('is this a pizza shop?', 'transactions::merchant', endpoint_name='gpt-4')}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'transactions::description',\n binary=0,\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shops in MA", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - }, - { - "blendsql": "SELECT DISTINCT description, merchant, child_category, date FROM transactions WHERE\n {{Model('would this gift be appropriate for a child?', 'transactions::description')}} = 1\n AND child_category = 'Gifts'", - "question": "Show gifts for my kid", - "serialized_db": "CREATE TABLE \"transactions\" (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n)" - } - ], - "program": "\n{{#system~}}\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{~/system}}\n\n{{#user~}}\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL:\n{{~/user}}\n\n{{#assistant~}}\n{{gen \"result\" temperature=0.0}}\n{{~/assistant}}\n", - "question": "how many players made an appearance that year?", - "serialized_db": "CREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"player\" TEXT,\n \"-1967\" TEXT,\n \"03.30\" TEXT,\n \"03.31\" TEXT,\n \"04.04\" TEXT,\n \"10.14\" TEXT,\n \"1968\" TEXT,\n \"total\" TEXT\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id player -1967 03.30 03.31 04.04 10.14 1968 total\n 0 0 shigeo yaegashi 41(11) o o o o 4(0) 45(11)\n 1 1 mitsuo kamata 37(2) o none o o 3(0) 40(2)\n 2 2 masakatsu miyamoto 36(1) none o none o 2(0) 38(1)\n*/" - }, - "db_path": "./research/tmp/26258ef4-49b5-406a-84c0-50271e2aa092.db", - "pred_text": [ - 12 - ], - "pred_sql": "SELECT COUNT(DISTINCT player) FROM w WHERE {{Model('did this player make an appearance this year?', 'w::total')}} = 1" -} -``` -5) Unnecessary use of BlendSQL -```json -{ - "id": "nt-683", - "question": "what is the total of deaths in internment camps and during the wartime flight?", - "table_id": "csv/203-csv/234.csv", - "table": { - "page_title": "Demographic estimates of the flight and expulsion of Germans", - "header": [ - "Description", - "Total", - "Poland", - "Baltic States(Resettled in Poland during war)", - "Resettled in Poland during war(from USSR and Romania)", - "Sudetendeutsch", - "SE Europe\\n(Hungary, Romania, Yugoslavia & Slovakia)" - ], - "rows": [ - [ - "Total Population Before Flight & Expulsion", - "16,199,086", - "11,038,826", - "145,615", - "365,622", - "3,160,216", - "1,488,807" - ], - [ - "Violent Deaths", - "58,256", - "44,603", - "383", - "747", - "5,596", - "6,927" - ], - [ - "Suicides", - "14,356", - "10,330", - "157", - "84", - "3,411", - "374" - ], - [ - "Deported( Forced Labor USSR)", - "49,542", - "32,947", - "1,566", - "6,465", - "705", - "7,859" - ], - [ - "In Internment Camps", - "80,522", - "27,847", - "952", - "1,037", - "6,615", - "44,071" - ], - [ - "During the wartime Flight", - "93,283", - "86,860", - "2,394", - "738", - "629", - "2,662" - ], - [ - "In the course of the Expulsions", - "63,876", - "57,814", - "3,510", - "561", - "1,481", - "510" - ], - [ - "Cause undetermined", - "112,612", - "106,991", - "64", - "3,116", - "379", - "2,062" - ], - [ - "Other Misc.", - "566", - "-", - "38", - "141", - "73", - "314" - ], - [ - "Total Confirmed Deaths", - "473,013", - "367,392", - "9,064", - "12,889", - "18,889", - "64,779" - ], - [ - "Total Unsolved Cases", - "1,905,991", - "1,404,993", - "19,374", - "73,794", - "287,169", - "120,661" - ] - ] - }, - "sql": "SELECT SUM ( `Total` ) FROM w WHERE `Description` IN ( 'in internment camps' , 'during the wartime flight' )", - "answer_text": [ - "173,805" - ], - "input_program_args": { - "bridge_hints": "description ( during the wartime flight , in internment camps )", - "examples": [ - { - "blendsql": "SELECT year FROM w WHERE opponent = 'elizabeth matory'", - "question": "What year was Elizabeth Matory the opponent of Charles Albert Ruppersberger?", - "serialized_db": "\nTable Description: Dutch Ruppersberger (Electoral history)\nCREATE TABLE w(\n\trow_id int,\n\tyear int,\n\toffice text,\n\telection text,\n\tfilledcolumnname real,\n\tsubject text,\n\tparty text,\n\tvotes text,\n\t% text,\n\tfilledcolumnname_2 real,\n\topponent text,\n\tparty_2 text,\n\tvotes_2 text,\n\t%_2 text)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tyear\toffice\telection\tfilledcolumnname\tsubject\tparty\tvotes\t%\tfilledcolumnname_2\topponent\tparty_2\tvotes_2\t%_2\n0\t1994\tbaltimore county executive\tgeneral\tnan\tdutch ruppersberger\tdemocratic\tn/a\tn/a\tnan\tn/a\tn/a\tn/a\tn/a\n1\t1998\tbaltimore county executive\tgeneral\tnan\tdutch ruppersberger\tdemocratic\t166482\t70.47\tnan\tjohn j. bishop\trepublican\t69449\t29.4\n2\t2002\tnone\tgeneral\tnan\tdutch ruppersberger\tdemocratic\t105718\t54.16\tnan\thelen delich bentley\trepublican\t88954\t45.57\n*/\n" - }, - { - "blendsql": "SELECT system FROM w WHERE system IN ('game boy advance', 'msx (japan only)') ORDER BY japan LIMIT 1", - "question": "Which system has a lower number for Japan of the virtual console systems: Game Boy Advance or the Japan-only console MSX?", - "serialized_db": "\nTable Description: Virtual Console (Titles)\nCREATE TABLE w (\n\trow_id int,\n\tsystem text,\n\tjapan int,\n\t[[list of virtual console games for wii u (north america)|north america]] real,\n\tpal region - europe real,\n\tpal region - australia real\n)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tsystem\tjapan\t[[list of virtual console games for wii u (north america)|north america]]\tpal region - europe\tpal region - australia\n0\tnes/famicom\t148\t94.0\t89.0\t89.0\n1\tsuper nes/super famicom\t101\t51.0\t49.0\t49.0\n2\tnintendo 64\t22\t21.0\t21.0\t21.0\n*/ \n " - }, - { - "blendsql": "SELECT player FROM w WHERE \n {{\n Model('Has crossed swords on its logo?'; 'w::gold coast titans')\n }} = 1\n ", - "question": "What player was transferred from the team that has crossed swords on its logo to the Warrington Wolves in the 2018 season?", - "serialized_db": "\nTable Description: 2018 Warrington Wolves season (Transfers | In)\nCREATE TABLE w(\n\trow_id int,\n\tplayer text,\n\tsigned from text,\n\tcontract length text,\n\tannounced text)\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3;\nrow_id\tplayer\tsigned from\tcontract length\tannounced\n0\tsitaleki akauola\tpenrith panthers\tp2y\t2017-08-01 00:00:00\n1\tbryson goodwin\tsouth sydney rabbitohs\tp2y\t2017-10-01 00:00:00\n2\ttyrone roberts\tgold coast titans\tp3y\t2017-10-01 00:00:00\n*/\nCREATE TABLE Images(\n\trow_id int,\n\tgold coast titans text)\n/*\nAll rows of the table:\nSELECT * FROM w;\nrow_id\tgold coast titans\n0\ta logo for the golden knights is painted on the beach.\n*/\n" - }, - { - "blendsql": "SELECT DISTINCT merchant FROM w WHERE\n {{Model('is this a pizza shop?', 'w::merchant'}} = 1\n AND {{\n Model(\n 'what state is this transaction from? Choose -1 when N.A.',\n 'w::description',\n example_outputs='TX;CA;MA;-1;'\n )\n }} = 'MA'\n AND parent_category = 'Food'", - "question": "Pizza shop merchants in MA", - "serialized_db": "\nTable Description: transactions\nCREATE TABLE w (\n \"index\" INTEGER,\n \"description\" TEXT,\n \"amount\" REAL,\n \"merchant\" TEXT,\n \"cash_flow\" TEXT,\n \"parent_category\" TEXT,\n \"child_category\" TEXT,\n \"date\" TEXT,\n/*\n3 example rows:\nSELECT * FROM transactions LIMIT 3\n index description amount merchant cash_flow parent_category child_category date confidence_score\n 0 Zelle payment from JUSTIN ORENIC BACucw5n1t14 2000.0 Zelle Expense Cash/ATM Online Payment Service 2022-06-20 0.698891\n 1 Zelle Transfer Conf UJ7QJF7TE Emilia Galindo 1500.0 Zelle Expense Cash/ATM Online Payment Service 2022-01-16 0.701658\n 2 Zelle payment from SOPHIA YANEZ WFCT0Q9R3CNR 1474.0 Zelle Expense Cash/ATM Online Payment Service 2022-03-27 0.861237\n*/\n)" - } - ], - "program": "\nGenerate BlendSQL given the question, table, passages, image captions to answer the question correctly.\nBlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite.\nThese external ingredients should be wrapped in double curly brackets.\n\nIf question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column with clean content by a new grammar:\n `Model('question', '{table}::{column})'`\n\nExamples:\n{{~#each examples}}\n{{this.serialized_db}}\nQuestion: {{this.question}}\nBlendSQL: {{this.blendsql}}\n{{/each}}\n\n{{serialized_db}}\n\n{{#if bridge_hints}}\nHere are some values that may be helpful:\n{{bridge_hints}}\n{{/if}}\n\nQuestion: {{question}}\nBlendSQL: {{gen \"result\" temperature=0.0}}\n", - "question": "what is the total of deaths in internment camps and during the wartime flight?", - "serialized_db": "Table Description: Demographic estimates of the flight and expulsion of Germans\nCREATE TABLE \"w\" (\n\"index\" INTEGER,\n \"row_id\" INTEGER,\n \"description\" TEXT,\n \"total\" INTEGER,\n \"poland\" TEXT,\n \"baltic states(resettled in poland during war)\" INTEGER,\n \"resettled in poland during war(from ussr and romania)\" INTEGER,\n \"sudetendeutsch\" INTEGER,\n \"se europe\\n(hungary, romania, yugoslavia & slovakia)\" INTEGER\n)\n\n/*\n3 example rows:\nSELECT * FROM w LIMIT 3\n index row_id description total poland baltic states(resettled in poland during war) resettled in poland during war(from ussr and romania) sudetendeutsch se europe\\n(hungary, romania, yugoslavia & slovakia)\n 0 0 total population before flight & expulsion 16199086 11038826 145615 365622 3160216 1488807\n 1 1 violent deaths 58256 44603 383 747 5596 6927\n 2 2 suicides 14356 10330 157 84 3411 374\n*/" - }, - "db_path": "./research/tmp/431a7265-d77f-4fdc-b506-3293988744ce.db", - "pred_text": [ - 173805 - ], - "pred_sql": " SELECT SUM(\"total\") FROM w WHERE \n {{\n Model('is this a death during the wartime flight or in internment camps?', 'w::description', example_outputs='during the wartime flight;in internment camps;')\n }} IN ('during the wartime flight', 'in internment camps')", - "pred_has_ingredient": true -} -``` \ No newline at end of file diff --git a/research/__init__.py b/research/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/configs/fetaqa/get-train.json b/research/configs/fetaqa/get-train.json deleted file mode 100644 index 5202e65e..00000000 --- a/research/configs/fetaqa/get-train.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": false, - "overwrite_output_dir": true, - "use_bridge_encoder": false, - "bypass_models": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": false, - "do_predict": false, - "do_train": true, - "dataset": "fetaqa", - "fetaqa_dataset_url": "fetaqa.zip", - "long_answer": true, - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/fetaqa-test/train-set", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/feverous/blendsql.json b/research/configs/feverous/blendsql.json deleted file mode 100644 index f930d73c..00000000 --- a/research/configs/feverous/blendsql.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "feverous", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/feverous/gpt-4-blendsql-only", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/feverous/get-train.json b/research/configs/feverous/get-train.json deleted file mode 100644 index 782ed601..00000000 --- a/research/configs/feverous/get-train.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "bypass_models": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": false, - "do_predict": false, - "do_train": true, - "dataset": "feverous", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/feverous-test/train-set", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/blendsql-deepseek-33b-instruct-GPTQ.json b/research/configs/hybridqa/blendsql-deepseek-33b-instruct-GPTQ.json deleted file mode 100644 index aba53152..00000000 --- a/research/configs/hybridqa/blendsql-deepseek-33b-instruct-GPTQ.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": false, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "dlsg-ds-deepseek-coder-33b-instruct-GPTQ-v1-1-ept", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/hybridqa/gpt-4-blendsql-deepseek-coder-33b-GPTQ", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/blendsql-deepseek-coder-7b.json b/research/configs/hybridqa/blendsql-deepseek-coder-7b.json deleted file mode 100644 index 8cc7d205..00000000 --- a/research/configs/hybridqa/blendsql-deepseek-coder-7b.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": false, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "dlsg-ds-deepseek-coder-7b-instruct-v1-1-ept", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/hybridqa/gpt-4-blendsql-deepseek-coder-temp0", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/blendsql-starcoder2-15b-instruct.json b/research/configs/hybridqa/blendsql-starcoder2-15b-instruct.json deleted file mode 100644 index b758186d..00000000 --- a/research/configs/hybridqa/blendsql-starcoder2-15b-instruct.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "dlsg-ds-starcoder2-15b-instruct-v1-fp16-ept", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/hybridqa/gpt-4-blendsql-starcoder2-instruct", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/blendsql-starcoder2-15b.json b/research/configs/hybridqa/blendsql-starcoder2-15b.json deleted file mode 100644 index 658ba85f..00000000 --- a/research/configs/hybridqa/blendsql-starcoder2-15b.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "dlsg-ds-starcoder2-15b-v1-fp16-ept", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/hybridqa/gpt-4-blendsql-starcoder2-temp0", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/blendsql.json b/research/configs/hybridqa/blendsql.json deleted file mode 100644 index b32b2e11..00000000 --- a/research/configs/hybridqa/blendsql.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/hybridqa/gpt-4-blendsql-only-run5-no-list-options ", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/get-train.json b/research/configs/hybridqa/get-train.json deleted file mode 100644 index 8dbfe8cd..00000000 --- a/research/configs/hybridqa/get-train.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "bypass_models": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": false, - "do_predict": false, - "do_train": true, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/hybridqa-test/train-set", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/testing-blendsql-plus-pp.json b/research/configs/hybridqa/testing-blendsql-plus-pp.json deleted file mode 100644 index ea01601c..00000000 --- a/research/configs/hybridqa/testing-blendsql-plus-pp.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "prompt_and_pray_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": true, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/hybridqa-test/blendsql-ablation-test/gpt-4-blendsql-plus-pp", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/hybridqa/testing-pp.json b/research/configs/hybridqa/testing-pp.json deleted file mode 100644 index 03675708..00000000 --- a/research/configs/hybridqa/testing-pp.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "prompt_and_pray_only": true, - "parser_model_name_or_path": "", - "prompt_and_pray_model_name_or_path": "gpt-4", - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "hybridqa", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/hybridqa-test/blendsql-ablation-test/gpt-4-pp-only", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/ottqa/blendsql.json b/research/configs/ottqa/blendsql.json deleted file mode 100644 index 06fbe805..00000000 --- a/research/configs/ottqa/blendsql.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "overwrite_cache": false, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": true, - "do_predict": false, - "do_train": false, - "dataset": "ottqa", - "schema_qualify": false, - "num_serialized_rows": 5, - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/new-paper-results/ottqa/gpt-4-blendsql-only-3-tables", - "report_to": [] -} \ No newline at end of file diff --git a/research/configs/ottqa/get-train.json b/research/configs/ottqa/get-train.json deleted file mode 100644 index 1632b260..00000000 --- a/research/configs/ottqa/get-train.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "max_val_samples": 200, - "overwrite_cache": true, - "overwrite_output_dir": true, - "use_bridge_encoder": true, - "bypass_models": true, - "parser_model_name_or_path": "gpt-4", - "blender_model_name_or_path": "gpt-4", - "fallback_to_prompt_and_pray": false, - "run_name": "base", - "do_eval": false, - "do_predict": false, - "do_train": true, - "dataset": "ottqa", - "ottqa_dataset_url": "ottqa.zip", - "source_prefix": "", - "schema_serialization_type": "code", - "normalize_query": true, - "output_dir": "./research/outputs/ottqa-test/train-set", - "report_to": [] -} \ No newline at end of file diff --git a/research/constants.py b/research/constants.py deleted file mode 100644 index 33ad848a..00000000 --- a/research/constants.py +++ /dev/null @@ -1,16 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class EvalField: - QUESTION = "question" - GOLD_ANSWER = "gold_answer" - PREDICTION = "prediction" - PRED_BLENDSQL = "pred_blendsql" - UID = "uid" - DB_PATH = "db_path" - - -SINGLE_TABLE_NAME = "w" -DOCS_TABLE_NAME = "documents" -CREATE_VIRTUAL_TABLE_CMD = f"CREATE VIRTUAL TABLE {DOCS_TABLE_NAME} USING fts5(title, content, tokenize = 'trigram');" diff --git a/research/datasets/fetaqa/evaluator.py b/research/datasets/fetaqa/evaluator.py deleted file mode 100644 index b2d27e92..00000000 --- a/research/datasets/fetaqa/evaluator.py +++ /dev/null @@ -1,89 +0,0 @@ -from datasets import load_metric -import nltk - - -# the code below refers to the https://github.com/Yale-LILY/FeTaQA/blob/main/end2end/train.py -def postprocess_text(preds, labels, metric_name): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # rougeLSum expects newline after each sentence - if metric_name == "rouge": - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - elif metric_name == "sacrebleu": # sacrebleu - labels = [[label] for label in labels] - elif metric_name == "bleu": - preds = [pred.split(" ") for pred in preds] - labels = [[label.split(" ")] for label in labels] - else: - pass - - return preds, labels - - -class EvaluateTool(object): - def __init__(self, args): - self.args = args - - def evaluate(self, preds, golds, section): - summary = {} - - gold_text = [item["seq_out"] for item in golds] - - assert len(preds) == len(gold_text) - - metric_list = [] - if section in ["train", "dev"]: - metric_list = ["sacrebleu"] - elif section == "test": - metric_list = ["sacrebleu", "rouge", "meteor", "bertscore", "bleurt"] - - for metric_name in metric_list: - metric = load_metric(metric_name) - processed_preds, processed_golds = postprocess_text( - preds, gold_text, metric_name - ) - - if metric_name == "bertscore": - res = metric.compute( - predictions=processed_preds, references=processed_golds, lang="en" - ) - for k, v in res.items(): - if k == "hashcode": - continue - summary[f"{metric_name}_{k}"] = round(1.0 * sum(v) / len(v), 2) - - else: - res = metric.compute( - predictions=processed_preds, references=processed_golds - ) - if metric_name == "sacrebleu": - summary[metric_name] = ( - res["score"] * 0.01 - ) # limit it to range of [0, 1] for unifying - elif metric_name == "bleurt": - summary["bleurt"] = round( - 1.0 * sum(res["scores"]) / len(res["scores"]), 2 - ) - elif metric_name == "rouge": - for sub_metric_name in res.keys(): - for i, key in enumerate(["precision", "recall", "fmeasure"]): - summary["{}_{}".format(sub_metric_name, key)] = res[ - sub_metric_name - ][1][i] - # this the the fmeasure('f-score') from the mid('mean aggregation') - else: - summary[metric_name] = res[metric_name] - return summary - - -if __name__ == "__main__": - import json - - with open("predictions_predict.json") as f: - test_data = json.load(f) - preds = [item["prediction"] for item in test_data] - evaluator = EvaluateTool(args=None) - score = evaluator.evaluate(preds, test_data, section="test") - print(score) diff --git a/research/datasets/fetaqa/fetaqa.py b/research/datasets/fetaqa/fetaqa.py deleted file mode 100644 index 7f334f3a..00000000 --- a/research/datasets/fetaqa/fetaqa.py +++ /dev/null @@ -1,124 +0,0 @@ -# coding=utf-8 -# Copyright 2021 The HuggingFace Datasets Authors, The Google AI Language Team Authors and the current dataset script contributor. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# TODO: This code can be push to HuggingFace as a new contribution. -"""FeTaQA, a Free-form Table Question Answering dataset""" -import os -import json -import datasets - -from research.constants import EvalField - -# Find for instance the citation on arxiv or on the dataset repo/website -_CITATION = """\ -@article{nan2021fetaqa, - title={FeTaQA: Free-form Table Question Answering}, - author={Nan, Linyong and Hsieh, Chiachun and Mao, Ziming and Lin, Xi Victoria and Verma, Neha and Zhang, Rui and Kry{\'s}ci{\'n}ski, Wojciech and Schoelkopf, Nick and Kong, Riley and Tang, Xiangru and others}, - journal={arXiv preprint arXiv:2104.00369}, - year={2021} -} -""" - -_DESCRIPTION = """\ -FeTaQA is a Free-form Table Question Answering dataset with 10K Wikipedia-based - {table, question, free-form answer, supporting table cells} pairs. It yields a -more challenging table QA setting because it requires generating free-form text - answers after retrieval, inference, and integration of multiple discontinuous -facts from a structured knowledge source. Unlike datasets of generative QA over - text in which answers are prevalent with copies of short text spans from the source, - answers in our dataset are human-generated explanations involving entities and their - high-level relations. - -This dataset is obtained from the official release of the FETAQA. -""" - -_HOMEPAGE = "https://github.com/Yale-LILY/FeTaQA/" - -_LICENSE = "CC-BY-SA-4.0 License" - -_URL = "https://github.com/Yale-LILY/FeTaQA/raw/main/data/" -_TRAINING_FILE = "fetaQA-v1_train.jsonl" -_DEV_FILE = "fetaQA-v1_dev.jsonl" -_TEST_FILE = "fetaQA-v1_test.jsonl" - - -class FETAQA(datasets.GeneratorBasedBuilder): - """The FETAQA dataset""" - - def __init__( - self, *args, writer_batch_size=None, fetaqa_dataset_url=_URL, **kwargs - ) -> None: - super().__init__(*args, writer_batch_size=writer_batch_size, **kwargs) - self._url = fetaqa_dataset_url - - def _info(self): - return datasets.DatasetInfo( - description=_DESCRIPTION, - features=datasets.Features( - { - EvalField.UID: datasets.Value("int32"), - EvalField.QUESTION: datasets.Value("string"), - "table_id": datasets.Value("string"), - "table": { - "header": datasets.features.Sequence(datasets.Value("string")), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - }, - "meta": datasets.Value("string"), - EvalField.GOLD_ANSWER: datasets.Value("string"), - } - ), - supervised_keys=None, - homepage=_HOMEPAGE, - license=_LICENSE, - citation=_CITATION, - ) - - def _split_generators(self, dl_manager): - """Returns SplitGenerators.""" - downloaded_files = dl_manager.download_and_extract(self._url) - return [ - datasets.SplitGenerator( - name=datasets.Split.TRAIN, - gen_kwargs={"filepath": os.path.join(downloaded_files, _TRAINING_FILE)}, - ), - datasets.SplitGenerator( - name=datasets.Split.VALIDATION, - gen_kwargs={"filepath": os.path.join(downloaded_files, _DEV_FILE)}, - ), - datasets.SplitGenerator( - name=datasets.Split.TEST, - gen_kwargs={"filepath": os.path.join(downloaded_files, _TEST_FILE)}, - ), - ] - - def _generate_examples(self, filepath): - """Yields examples.""" - with open(filepath, encoding="utf-8") as f: - for idx, line in enumerate(f): - example = json.loads(line) - yield idx, { - EvalField.UID: example["feta_id"], - EvalField.QUESTION: example["question"], - "table_id": example["table_source_json"], - "table": { - "header": example["table_array"][0], - "rows": example["table_array"][1:], - }, - "meta": example["table_page_title"] - + " | " - + example["table_section_title"], - EvalField.GOLD_ANSWER: example["answer"], - } diff --git a/research/datasets/feverous/feverous.py b/research/datasets/feverous/feverous.py deleted file mode 100644 index 0b4a573e..00000000 --- a/research/datasets/feverous/feverous.py +++ /dev/null @@ -1,439 +0,0 @@ -import warnings - -warnings.simplefilter(action="ignore", category=FutureWarning) - -import json -import os -import sqlite3 -from collections import defaultdict -import pandas as pd -import numpy as np -from collections import Counter -from typing import Dict, List - -import datasets -from wikiextractor.extract import Extractor, ignoreTag, resetIgnoredTags -from research.constants import EvalField - -_CITATION = """\ -@article{aly2021feverous, - title={FEVEROUS: Fact Extraction and VERification Over Unstructured and Structured information}, - author={Aly, Rami and Guo, Zhijiang and Schlichtkrull, Michael and Thorne, James and Vlachos, Andreas and Christodoulopoulos, Christos and Cocarascu, Oana and Mittal, Arpit}, - journal={arXiv preprint arXiv:2106.05707}, - year={2021} -} -""" - -_DESCRIPTION = """\ -This dataset is obtained from the official release of the FEVEROUS. -""" - -_HOMEPAGE = "https://fever.ai/dataset/feverous.html" - -_LICENSE = "" - -_URL = "https://fever.ai/download/feverous/" -_TRAINING_FILE = "feverous_train_challenges.jsonl" -_DEV_FILE = "feverous_dev_challenges.jsonl" -_DATABASE = "feverous-wiki-pages-db.zip" - -_URLS = { - "train": f"{_URL}{_TRAINING_FILE}", - "dev": f"{_URL}{_DEV_FILE}", - "database": f"{_URL}{_DATABASE}", -} - -EVIDENCE_TYPES = ["sentence", "cell", "header_cell", "table_caption", "item"] - -extractor = Extractor(0, "", [], "", "") - - -def clean_markup(markup, keep_links=False, ignore_headers=True): - """ - Clean Wikimarkup to produce plaintext. - - :param keep_links: Set to True to keep internal and external links - :param ignore_headers: if set to True, the output list will not contain - headers, only - - Returns a list of paragraphs (unicode strings). - """ - - if not keep_links: - ignoreTag("a") - - # returns a list of strings - paragraphs = extractor.clean_text(markup) - resetIgnoredTags() - - if ignore_headers: - paragraphs = filter(lambda s: not s.startswith("## "), paragraphs) - - return " ".join(list(paragraphs)) - - -def get_table_id(meta): - """ - meta types: - - table_caption_18 - - cell_18_1_1 - - header_cell_18_0_0 - """ - if meta.startswith("table_caption"): - return meta.split("_")[-1] - if meta.startswith("header_cell") or meta.startswith("cell"): - return meta.split("_")[-3] - - -def get_list_id(meta): - """ " - meta types: - - item_4_25 - """ - return meta.split("_")[1] - - -def set_first_row_as_header(df: pd.DataFrame): - df.columns = df.iloc[0] - df = df.iloc[1:] - return df - - -def format_nested_table_json(table_json: dict): - """ - TODO: how to handle table 'type'? - """ - # Create numpy array - # row_start column_start - # 0 0 - # 1 0 - tablename = None - flattened_values = [] - start_indices_arr = [] - span_indices_arr = [] - for row in table_json["table"]: - start_indices_arr.extend( - [list(map(lambda x: int(x), item["id"].split("_")[-2:])) for item in row] - ) - span_indices_arr.extend( - [[int(item["column_span"]) - 1, int(item["row_span"]) - 1] for item in row] - ) - flattened_values.extend([clean_markup(item["value"]) for item in row]) - start_indices_arr, span_indices_arr, flattened_values = ( - np.array(start_indices_arr), - np.array(span_indices_arr), - np.array(flattened_values), - ) - n_rows = start_indices_arr[:, 0].max() - - naive_df_data = [] - to_add_queue = [] - for row in range(n_rows): - row_entry = [to_add_queue.pop()] if len(to_add_queue) > 0 else [] - indices = np.where(start_indices_arr[:, 0] == row)[0] - spans = span_indices_arr[indices] - values = flattened_values[indices] - for _pos in range(indices.shape[0]): - for _ in range(spans[_pos][0] + 1): - row_entry.append(values[_pos]) - for _idx in range(spans[_pos][1]): - to_add_queue.append(values[_pos]) - naive_df_data.append(row_entry) - naive_df = pd.DataFrame(naive_df_data) - naive_df = naive_df.replace("", np.nan) - naive_df = naive_df.ffill() - naive_df = naive_df.fillna("") - if len(naive_df.columns) == 2: - # Transpose, so Model gets whole `attribute` context - # naive_df.columns = ["attribute", "value"] - naive_df = naive_df.T - try: - return set_first_row_as_header(naive_df) - except: - return naive_df - - # Simplest case: if less than 3 cells span multiple indices - # But, if it has only 2 columns, use 'attribute', 'value' formatting - is_simple_table = span_indices_arr[span_indices_arr > 0].shape[0] < 3 - if is_simple_table: - if len(naive_df.columns) == 2: - naive_df.columns = ["attribute", "value"] - return (tablename, naive_df) - try: - return (tablename, set_first_row_as_header(naive_df)) - except: - return (tablename, naive_df) - try: - reformatted_df = {} - handled_rows = set() - for idx, row in naive_df.iterrows(): - if idx in handled_rows: - continue - handled = False - values_as_set = set(row.values) - if len(values_as_set) == 1: - # This should be tablename - tablename = values_as_set.pop() - continue - for i in range(row.values.shape[0]): - if handled or i == row.values.shape[0] - 1: - break - _values = list(dict.fromkeys(row.values[i:].tolist())) - # Check if they have any words in common - tokenized_overlapping_values = [i.split(" ") for i in _values] - tokens_in_common: set = set.intersection( - *map(set, tokenized_overlapping_values) - ) - if len(tokens_in_common) > 0: - # We have some tokens in common - # Only get difference, and assign as column/values - columnname = " ".join(tokens_in_common) - values = [ - " ".join([tok for tok in item if tok not in tokens_in_common]) - for item in tokenized_overlapping_values - ] - reformatted_df[columnname] = values - handled = True - if not handled: - # Check if values are repeated even number of times - # E.g. ['Number', 'Percent', 'Number', 'Percent'] - values_counter = Counter(row.values) - duplicate_values = {(k, v) for k, v in values_counter.items() if v > 1} - if len(duplicate_values) > 1: - evenly_duplicated_values = [i[0] for i in duplicate_values] - num_duplications = [i[1] for i in duplicate_values][0] - subtable = pd.DataFrame(naive_df.iloc[idx:, :]) - handled_rows.update(range(idx, len(naive_df))) - subtable = set_first_row_as_header(subtable) - seen_columns = set() - for columnname in subtable.columns: - if columnname in seen_columns: - continue - if columnname in evenly_duplicated_values: - if columnname not in reformatted_df: - reformatted_df[columnname] = [] - for _, row in subtable[columnname].T.iterrows(): - reformatted_df[columnname].extend(row.values.tolist()) - else: - # Make this a new column too - reformatted_df[columnname] = [ - i - for i in subtable[columnname].tolist() - if i != columnname - ] * num_duplications - seen_columns.add(columnname) - handled = True - max_v = max(len(v) for v in reformatted_df.values()) - for k, values in reformatted_df.items(): - if len(values) != max_v: - assert max_v % len(values) == 0 - mult = max_v // len(values) - multiplied_values = [ - x for xs in [[v] * mult for v in values] for x in xs - ] - reformatted_df[k] = multiplied_values - return (tablename, pd.DataFrame(reformatted_df)) - except: - try: - if len(set(naive_df.iloc[0].values.tolist())) == 1: - tablename = naive_df.iloc[0].values[0] - naive_df = set_first_row_as_header(naive_df.iloc[1:, :]) - if len(set(naive_df.columns)) == 1 and len(naive_df.columns) == 2: - tablename = f"{tablename} - {naive_df.columns[0]}" - naive_df.columns = ["Attribute", "Value"] - except: - pass - return (tablename, naive_df) - - -def retrieve_context(example, cur): - pages = {} - evidences = [] - # Collect all page - """ - meta types: - - table_caption_18 - - cell_18_1_1 - - header_cell_18_0_0 - - sentence_0 - - item_4_25 - """ - tables = [] - for evidence in example["evidence"][:1]: - content = evidence["content"] - for item in content: - # Example: 'Michael Folivi_header_cell_1_0_0' - # page_id = Michael Folivi - # meta = header_cell_1_0_0 - page_id, meta = item.split("_", 1) - if page_id not in pages: - data = cur.execute( - """ - SELECT data FROM wiki WHERE id = "{}" - """.format( - page_id - ) - ) - for item in data.fetchall(): - pages[page_id] = json.loads(item[0]) - if ( - meta.startswith("table_caption") - or meta.startswith("cell") - or meta.startswith("header_cell") - ): - table_id = get_table_id(meta) - if table_id in tables: - continue - else: - tables.append(table_id) - table_json = pages[page_id]["table_{}".format(table_id)] - evidences.append({"table": table_json, "tablename": page_id}) - elif meta.startswith("item"): - list_id = get_list_id(meta) - context = None - for item in pages[page_id]["list_{}".format(list_id)]["list"]: - if item["id"] == meta: - context = item["value"] - if context is not None: - evidences.append( - {"content": clean_markup(context), "title": page_id} - ) - else: - context = pages[page_id][meta] - evidences.append({"content": clean_markup(context), "title": page_id}) - - table_list, context_list = [], [] - title_to_content: Dict[str, List[str]] = {} - for evidence in evidences: - if "table" in evidence: - df = format_nested_table_json(evidence["table"]) - df_dict = df.to_dict(orient="split") - table_list.append( - { - "header": df_dict["columns"], - "rows": df_dict["data"], - "table_description": evidence["tablename"], - } - ) - else: - if evidence["title"] not in title_to_content: - title_to_content[evidence["title"]] = [] - title_to_content[evidence["title"]].append(evidence["content"]) - context_list.extend( - [{"title": k, "content": " ".join(v)} for k, v in title_to_content.items()] - ) - # Remove overlaps - filtered_context_list = [] - context_list_titles = [item["title"] for item in context_list] - for title in set(context_list_titles): - content_candidates = [] - for item in context_list: - if item["title"] == title: - content_candidates.append(item["content"]) - chosen_content = sorted(content_candidates, key=len, reverse=True)[0] - filtered_context_list.append({"title": title, "content": chosen_content}) - return table_list, filtered_context_list - - -def is_table_involved(example): - # Check if the example is involving table. - # We only consider the first evidence - for evidence in example["evidence"][:1]: # list - is_valid = False - content = evidence["content"] - evidence_type_count = defaultdict(int) - for item in content: - page_id, meta = item.split("_", 1) - for evidence_type in EVIDENCE_TYPES: - if meta.startswith(evidence_type): - evidence_type_count[evidence_type] += 1 - for evidence_type in evidence_type_count: - if evidence_type in ["cell", "header_cell", "table_caption"]: - is_valid = True - if is_valid: - return True - return False - - -class FEVEROUS(datasets.GeneratorBasedBuilder): - """The FEVEROUS dataset""" - - def _info(self): - return datasets.DatasetInfo( - description=_DESCRIPTION, - features=datasets.Features( - { - EvalField.UID: datasets.Value("string"), - EvalField.QUESTION: datasets.Value("string"), - "table": datasets.features.Sequence( - { - "header": datasets.features.Sequence( - datasets.Value("string") - ), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - "table_description": datasets.Value("string"), - } - ), - "context": datasets.features.Sequence( - { - "title": datasets.Value("string"), - "content": datasets.Value("string"), - } - ), - EvalField.GOLD_ANSWER: datasets.Value("string"), - } - ), - supervised_keys=None, - homepage=_HOMEPAGE, - license=_LICENSE, - citation=_CITATION, - ) - - def _split_generators(self, dl_manager): - """Returns SplitGenerators.""" - downloaded_files = dl_manager.download_and_extract(_URLS) - - return [ - datasets.SplitGenerator( - name=datasets.Split.TRAIN, - gen_kwargs={ - "filepath": downloaded_files["train"], - "database": os.path.join( - downloaded_files["database"], "feverous_wikiv1.db" - ), - }, - ), - datasets.SplitGenerator( - name=datasets.Split.VALIDATION, - gen_kwargs={ - "filepath": downloaded_files["dev"], - "database": os.path.join( - downloaded_files["database"], "feverous_wikiv1.db" - ), - }, - ), - ] - - def _generate_examples(self, filepath, database): - con = sqlite3.connect(database) - cur = con.cursor() - with open(filepath, "r") as f: - count = -1 - for _idx, line in enumerate(f): - example = json.loads(line) - statement = example["claim"] - label = example["label"] - # possible label: "NOT ENOUGH INFO", "REFUTES", "SUPPORTS" - if is_table_involved(example): - # Retrieve related context from database - tables, contexts = retrieve_context(example, cur) - count += 1 - yield count, { - EvalField.UID: str(example["id"]), - EvalField.QUESTION: statement, - "table": tables, - "context": contexts, - EvalField.GOLD_ANSWER: label, - } diff --git a/research/datasets/hybridqa/hybridqa.py b/research/datasets/hybridqa/hybridqa.py deleted file mode 100644 index 6c7dead2..00000000 --- a/research/datasets/hybridqa/hybridqa.py +++ /dev/null @@ -1,221 +0,0 @@ -import json -from typing import Dict -import datasets -import os -import nltk - -from research.constants import EvalField - -_CITATION = """\ -@article{chen2020hybridqa, - title={Hybridqa: A dataset of multi-hop question answering over tabular and textual data}, - author={Chen, Wenhu and Zha, Hanwen and Chen, Zhiyu and Xiong, Wenhan and Wang, Hong and Wang, William}, - journal={arXiv preprint arXiv:2004.07347}, - year={2020} -} -""" - -_DESCRIPTION = """\ -This dataset is obtained from the official release of the HybridQA. -""" - -_HOMEPAGE = "https://github.com/wenhuchen/HybridQA" - -_LICENSE = "MIT License" - -_URL = "https://raw.githubusercontent.com/wenhuchen/HybridQA/master/released_data/" -_TRAINING_FILE = "train.traced.json" -_DEV_FILE = "dev.traced.json" -_CONTEXT_FILE_URL = ( - "https://github.com/wenhuchen/WikiTables-WithLinks/archive/refs/heads/master.zip" -) - -_URLS = { - "train": f"{_URL}{_TRAINING_FILE}", - "dev": f"{_URL}{_DEV_FILE}", - "context": _CONTEXT_FILE_URL, -} - -WINDOW_SIZE = 3 - -""" -TODO: - - use json files in tables_tok to create hyperlinks from values -""" - - -class HybridQA(datasets.GeneratorBasedBuilder): - """The Hybrid dataset""" - - def _info(self): - return datasets.DatasetInfo( - description=_DESCRIPTION, - features=datasets.Features( - { - EvalField.UID: datasets.Value("string"), - EvalField.QUESTION: datasets.Value("string"), - "table_id": datasets.Value("string"), - "table": { - "header": datasets.features.Sequence(datasets.Value("string")), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - }, - "passages": { - "header": datasets.features.Sequence(datasets.Value("string")), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - }, - "gold_passage": datasets.Value("string"), - "context": datasets.Value("string"), - EvalField.GOLD_ANSWER: datasets.Value("string"), - } - ), - supervised_keys=None, - homepage=_HOMEPAGE, - license=_LICENSE, - citation=_CITATION, - ) - - def _split_generators(self, dl_manager): - """Returns SplitGenerators.""" - downloaded_files = dl_manager.download_and_extract(_URLS) - - return [ - datasets.SplitGenerator( - name=datasets.Split.TRAIN, - gen_kwargs={ - "filepath": downloaded_files["train"], - "contextpath": downloaded_files["context"], - }, - ), - datasets.SplitGenerator( - name=datasets.Split.VALIDATION, - gen_kwargs={ - "filepath": downloaded_files["dev"], - "contextpath": downloaded_files["context"], - }, - ), - ] - - def _generate_examples(self, filepath, contextpath): - """Yields examples.""" - # data_id, question, table_id, gold_result_str - table_tok_path = os.path.join( - contextpath, "WikiTables-WithLinks-master", "tables_tok" - ) - passage_tok_path = os.path.join( - contextpath, "WikiTables-WithLinks-master", "request_tok" - ) - with open(filepath, encoding="utf-8") as f: - data = json.load(f) - for idx, example in enumerate(data): - answer_node = example["answer-node"] - table_id = example["table_id"] - table = json.load( - open(os.path.join(table_tok_path, "{}.json".format(table_id))) - ) - passages = json.load( - open(os.path.join(passage_tok_path, "{}.json".format(table_id))) - ) - answer = example["answer-text"] - # how to construct context? - # keep all cells and appending the sentences that contains answer span into the cell - header, rows, gold_passage = self.construct_expanded_table( - table, passages, answer, answer_node - ) - passage_headers, passage_data = self.preprocess_passages(passages) - yield idx, { - EvalField.UID: example["question_id"], - EvalField.QUESTION: example["question"], - "table_id": example["table_id"], - "table": {"header": header, "rows": rows}, - "passages": {"header": passage_headers, "rows": passage_data}, - "gold_passage": gold_passage, - "context": table["title"] - + " | " - + table["section_title"] - + " | " - + table["section_text"] - + " | " - + table["intro"], - EvalField.GOLD_ANSWER: example["answer-text"], - } - - # @staticmethod - # def preprocess_passages(passages: Dict[str, str], rows: List[List[str]]) -> tuple: - # """If the passage title isn't a match to a value, - # use rapidfuzz to align to closest value and set that as `title` field. - # """ - # def preprocess(text: str): - # text = text.lower() - # text = re.sub(r'\s+', ' ', text) - # text = re.sub(r'\([^)]*\)', '', text) - # text = re.sub(r"\b(a|an|the)\b", " ", text, flags=re.UNICODE) - # text = "".join(ch for ch in text if ch not in set(string.punctuation)) - # return text.strip() - # - # def flatten_and_lower(xss): - # return set([x for xs in xss for x in xs]) - # - # values_as_set = flatten_and_lower(rows) - # norm_value_to_value = {preprocess(value): value for value in values_as_set} - # - # header = ["title", "content"] - # data = [] - # for k, v in passages.items(): - # title = k.split("/")[-1].replace("_", " ") - # if title not in values_as_set: - # # re.sub to remove stuff in parentheses - # norm_title = preprocess(title) - # aligned_value, score, _ = process.extractOne(norm_title, list(norm_value_to_value.keys())) - # if score >= 95: - # title = norm_value_to_value[aligned_value] - # data.append([title, v]) - # return (header, data) - - @staticmethod - def preprocess_passages(passages: Dict[str, str]) -> tuple: - """If the passage title isn't a match to a value, - use rapidfuzz to align to closest value and set that as `title` field. - """ - header = ["title", "content"] - data = [] - for k, v in passages.items(): - title = k.split("/")[-1].replace("_", " ") - data.append([title, v]) - return (header, data) - - @staticmethod - def construct_expanded_table(table, passages, answer, answer_nodes): - def process_link(link): - return link.split("/")[-1].replace("_", " ") - - header = [column[0] for column in table["header"]] - data = [[cell[0] for cell in row] for row in table["data"]] - selected_passage = {} - for answer_node in answer_nodes: - link = answer_node[2] - type_ = answer_node[3] - if type_ == "passage": - # Get passage and locate the sentence of answer - passage_text = passages[link] - sents = nltk.sent_tokenize(passage_text) - has_answer_sent_idx = -1 - for idx, sent in enumerate(sents): - if " " + answer.lower() + " " in " " + sent.lower() + " ": - has_answer_sent_idx = idx - selected_sents = sents[ - max(0, has_answer_sent_idx - (WINDOW_SIZE - 1) // 2) : min( - len(sents) - 1, has_answer_sent_idx + (WINDOW_SIZE - 1) // 2 - ) - ] - selected_passage[process_link(link)] = " ".join(selected_sents) - else: - pass - # linearize selected passgae - passage_context_str = "passages: " - for key in selected_passage: - passage_context_str += "{}: {} | ".format(key, selected_passage[key]) - return header, data, passage_context_str diff --git a/research/datasets/ottqa/ottqa.py b/research/datasets/ottqa/ottqa.py deleted file mode 100644 index a0633e81..00000000 --- a/research/datasets/ottqa/ottqa.py +++ /dev/null @@ -1,281 +0,0 @@ -import os -import json -import nltk -import datasets -from pathlib import Path - -from research.constants import EvalField - -logger = datasets.logging.get_logger(__name__) - -_CITATION = """\ -@article{chen2020open, - title={Open question answering over tables and text}, - author={Chen, Wenhu and Chang, Ming-Wei and Schlinger, Eva and Wang, William and Cohen, William W}, - journal={arXiv preprint arXiv:2010.10439}, - year={2020} -} -""" - -_DESCRIPTION = """\ -This dataset is obtained from the official release of the OTT-QA. -""" - -_HOMEPAGE = "https://ott-qa.github.io" - -_LICENSE = "MIT License" - -_URL = "https://github.com/wenhuchen/OTT-QA/raw/a14ec408b2c22e24a44622b01e4242d95b7ecf08/released_data/" -_TRAINING_FILE = "train.traced.json" -_DEV_FILE = "dev.traced.json" - -_URLS = { - "tables": "https://opendomainhybridqa.s3-us-west-2.amazonaws.com/all_plain_tables.json", - "passages": "https://opendomainhybridqa.s3-us-west-2.amazonaws.com/all_passages.json", -} - -WINDOW_SIZE = 3 - - -class OTTQA(datasets.GeneratorBasedBuilder): - """The OTTQA dataset""" - - def __init__( - self, - *args, - db_output_dir: str, - writer_batch_size=None, - ottqa_dataset_url=_URL, - **kwargs, - ) -> None: - super().__init__(*args, writer_batch_size=writer_batch_size, **kwargs) - - self._url = ottqa_dataset_url - self.db_output_dir = Path(db_output_dir) - - def _info(self): - return datasets.DatasetInfo( - description=_DESCRIPTION, - features=datasets.Features( - { - EvalField.UID: datasets.Value("string"), - EvalField.DB_PATH: datasets.Value("string"), - EvalField.QUESTION: datasets.Value("string"), - "table_id": datasets.Value("string"), - "table": { - "header": datasets.features.Sequence(datasets.Value("string")), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - }, - "passage": datasets.Value("string"), - "context": datasets.Value("string"), - EvalField.GOLD_ANSWER: datasets.Value("string"), - } - ), - supervised_keys=None, - homepage=_HOMEPAGE, - license=_LICENSE, - citation=_CITATION, - ) - - def _split_generators(self, dl_manager): - """Returns SplitGenerators.""" - downloaded_files = dl_manager.download_and_extract(_URLS) - data_dir = dl_manager.download_and_extract(self._url) - train_filepath = os.path.join(data_dir, "train.traced.json") - dev_filepath = os.path.join(data_dir, "dev.traced.json") - return [ - datasets.SplitGenerator( - name=datasets.Split.TRAIN, - gen_kwargs={ - "filepath": train_filepath, - "tablepath": downloaded_files["tables"], - "passagepath": downloaded_files["passages"], - "data_dir": data_dir, - }, - ), - datasets.SplitGenerator( - name=datasets.Split.VALIDATION, - gen_kwargs={ - "filepath": dev_filepath, - "tablepath": downloaded_files["tables"], - "passagepath": downloaded_files["passages"], - "data_dir": data_dir, - }, - ), - ] - - def _generate_examples(self, filepath, tablepath, passagepath, data_dir): - """Yields examples.""" - # data_id, question, table_id, gold_result_str - - with open(tablepath, encoding="utf-8") as f: - tables = json.load(f) - with open(passagepath, encoding="utf-8") as f: - passages = json.load(f) - - # Format to database file - if not self.db_output_dir.is_dir(): - self.db_output_dir.mkdir(parents=True) - - # dataset_split = Path(filepath).stem.split(".")[0] - # output_db_filepath = None - # output_db_filepath = self.db_output_dir / "ottqa.db" - # add_tables = False - # add_documents = False - # if dataset_split == "train": - # db_filename = f"ottqa.db" - # output_db_filepath = self.db_output_dir / db_filename - # # if not output_db_filepath.is_file(): - # if add_tables: - # logger.info(f"\nConstructing {db_filename} in {data_dir}...") - # tablename_to_table_json = {} - # tablename_to_unique_idx = {} - # for _table_id, table_data in tqdm( - # tables.items(), total=len(tables), desc="Formatting tables..." - # ): - # _tablename = table_data["title"] - # if _tablename not in tablename_to_unique_idx: - # tablename_to_unique_idx[_tablename] = 0 - # - # tablename = f"{_tablename} ({tablename_to_unique_idx[_tablename]})" - # tablename_to_table_json[tablename] = { - # "header": table_data["header"], - # "rows": table_data["data"], - # } - # tablename_to_unique_idx[_tablename] += 1 - # - # csv_output_dir = self.db_output_dir / "csv" - # if not csv_output_dir.is_dir(): - # csv_output_dir.mkdir(parents=True) - # - # for tablename, table_json in tqdm( - # tablename_to_table_json.items(), - # total=len(tablename_to_table_json), - # desc="Saving tables to csv...", - # ): - # csv_save_path = ( - # self.db_output_dir - # / "csv" - # / f"{tablename.replace('/', ' ')}.csv" - # ) - # if csv_save_path.is_file(): - # continue - # df = prepare_df_for_neuraldb_from_table( - # table_json, add_row_id=False - # ) - # df.to_csv(csv_save_path, index=False) - # - # # Use csvs-to-sqlite to create many sqlite tables from our csvs - # # https://github.com/simonw/csvs-to-sqlite - # # error_bad_lines deprecated: https://github.com/simonw/csvs-to-sqlite/issues/88 - # # need to run `pip install 'pandas==1.4.0'` - # # os.system("csvs-to-sqlite ") - # - # # try: - # # df.to_sql( - # # tablename, sqlite_conn, if_exists="fail" - # # ) - # # except ValueError: - # # logger.info(f"FAILED ON TABLE {tablename}") - # - # if add_documents: - # documents_table_json = {"header": ["title", "content"], "rows": []} - # for doc_id, content in tqdm( - # passages.items(), - # total=len(passages), - # desc="Formatting documents...", - # ): - # title = doc_id.split("/")[-1].replace("_", " ") - # documents_table_json["rows"].append([title, content]) - # - # # Put into database - # sqlite_conn = sqlite3.connect( - # str(output_db_filepath), check_same_thread=True - # ) - # - # chunksize = 10000 - # - # def chunker(seq, size): - # return (seq[pos : pos + size] for pos in range(0, len(seq), size)) - # - # documents_df = pd.DataFrame( - # data=documents_table_json["rows"], - # columns=documents_table_json["header"], - # ) - # c = sqlite_conn.cursor() - # c.execute(CREATE_VIRTUAL_TABLE_CMD) - # c.close() - # with tqdm( - # total=len(documents_df), desc="Uploading documents to db..." - # ) as pbar: - # for _i, cdf in enumerate(chunker(documents_df, chunksize)): - # cdf.to_sql( - # DOCS_TABLE_NAME, - # sqlite_conn, - # method="multi", - # if_exists="append", - # index=False, - # ) - # pbar.update(chunksize) - # - # sqlite_conn.close() - # - # logger.info("\nFinished.") - - with open(filepath, encoding="utf-8") as f: - data = json.load(f) - for idx, example in enumerate(data): - table = tables[example["table_id"]] - answer_node = example["answer-node"] - answer = example["answer-text"] - header, data, passage_context_str = self.construct_expanded_table( - table, passages, answer, answer_node - ) - yield idx, { - EvalField.UID: example["question_id"], - EvalField.DB_PATH: str(self.db_output_dir / "ottqa.db"), - EvalField.QUESTION: example["question"], - "table_id": example["table_id"], - "table": {"header": header, "rows": data}, - "passage": passage_context_str, - "context": table["title"] - + " | " - + table["section_title"] - + " | " - + table["section_text"] - + " | " - + table["intro"], - EvalField.GOLD_ANSWER: example["answer-text"], - } - - def construct_expanded_table(self, table, passages, answer, answer_nodes): - def process_link(link): - return link.split("/")[-1].replace("_", " ") - - selected_passage = {} - for answer_node in answer_nodes: - link = answer_node[2] - type_ = answer_node[3] - if type_ == "passage": - # Get passage and locate the sentence of answer - passage_text = passages[link] - sents = nltk.sent_tokenize(passage_text) - has_answer_sent_idx = -1 - for idx, sent in enumerate(sents): - if " " + answer.lower() + " " in " " + sent.lower() + " ": - has_answer_sent_idx = idx - selected_sents = sents[ - max(0, has_answer_sent_idx - (WINDOW_SIZE - 1) // 2) : min( - len(sents) - 1, has_answer_sent_idx + (WINDOW_SIZE - 1) // 2 - ) - ] - selected_passage[process_link(link)] = " ".join(selected_sents) - else: - pass - # linearize selected passgae - passage_context_str = "passages: " - for key in selected_passage: - passage_context_str += "{}: {} | ".format(key, selected_passage[key]) - return table["header"], table["data"], passage_context_str diff --git a/research/datasets/wikitq/wikitq.py b/research/datasets/wikitq/wikitq.py deleted file mode 100644 index 2ad840cf..00000000 --- a/research/datasets/wikitq/wikitq.py +++ /dev/null @@ -1,267 +0,0 @@ -# coding=utf-8 -# Copyright 2021 The HuggingFace Datasets Authors, The Google AI Language Team Authors and the current dataset script contributor. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Squall: On the Potential of Lexico-logical Alignments for Semantic Parsing to SQL Queries""" - -import json -import os -import datasets -import shutil - -try: - from ...wikitq.utils import _load_table_w_page as _load_table - from ...wikitq.wikitq.utils import ( - WTQDBEngine, - process_table_structure, - retrieve_wtq_query_answer, - ) - from ....constants import EvalField -except ModuleNotFoundError: - from research.utils.wikitq.utils import _load_table_w_page as _load_table - from research.utils.wikitq.utils import ( - WTQDBEngine, - process_table_structure, - retrieve_wtq_query_answer, - ) - from research.constants import EvalField - -logger = datasets.logging.get_logger(__name__) -# Find for instance the citation on arxiv or on the dataset repo/website -_CITATION = """\ -@inproceedings{Shi:Zhao:Boyd-Graber:Daume-III:Lee-2020, - Title = {On the Potential of Lexico-logical Alignments for Semantic Parsing to {SQL} Queries}, - Author = {Tianze Shi and Chen Zhao and Jordan Boyd-Graber and Hal {Daum\'{e} III} and Lillian Lee}, - Booktitle = {Findings of EMNLP}, - Year = {2020}, -} -""" - -_DESCRIPTION = """\ -Two important aspects of semantic parsing for question answering are the breadth of the knowledge source and the depth of -logical compositionality. While existing work trades off one aspect for another, this paper simultaneously makes progress -on both fronts through a new task: answering complex questions on semi-structured tables using question-answer pairs as -supervision. The central challenge arises from two compounding factors: the broader domain results in an open-ended set -of relations, and the deeper compositionality results in a combinatorial explosion in the space of logical forms. We -propose a logical-form driven parsing algorithm guided by strong typing constraints and show that it obtains significant - improvements over natural baselines. For evaluation, we created a new dataset of 22,033 complex questions on Wikipedia - tables, which is made publicly available. -""" - -_HOMEPAGE = "https://github.com/tzshi/squall" - -_LICENSE = "CC-BY-SA-4.0 License" - -_URL = "https://github.com/ppasupat/WikiTableQuestions/archive/refs/heads/master.zip" -_SQUALL_URL = "https://github.com/tzshi/squall/archive/refs/heads/main.zip" - - -class WikiTableQuestion(datasets.GeneratorBasedBuilder): - """The Squall dataset""" - - VERSION = datasets.Version("1.0.0") - - BUILDER_CONFIGS = [ - datasets.BuilderConfig( - name="squall", - version=VERSION, - description="SQUALL: On the Potential of Lexico-logical Alignments for Semantic Parsing to {SQL} Queries", - ), - ] - - def __init__( - self, - *args, - writer_batch_size=None, - wikitq_dataset_url=_URL, - squall_dataset_url=_SQUALL_URL, - **kwargs - ) -> None: - super().__init__(*args, writer_batch_size=writer_batch_size, **kwargs) - - self._url = wikitq_dataset_url - self._squall_url = squall_dataset_url - - def _info(self): - return datasets.DatasetInfo( - description=_DESCRIPTION, - features=datasets.Features( - { - EvalField.UID: datasets.Value("string"), - EvalField.QUESTION: datasets.Value("string"), - "table_id": datasets.Value("string"), - "table": { - "page_title": datasets.Value("string"), - "header": datasets.features.Sequence(datasets.Value("string")), - "rows": datasets.features.Sequence( - datasets.features.Sequence(datasets.Value("string")) - ), - }, - "sql": datasets.Value("string"), - EvalField.GOLD_ANSWER: datasets.features.Sequence( - datasets.Value("string") - ), - } - ), - supervised_keys=None, - homepage=_HOMEPAGE, - license=_LICENSE, - citation=_CITATION, - ) - - def _split_generators(self, dl_manager): - """Returns SplitGenerators.""" - data_dir = os.path.join( - dl_manager.download_and_extract(self._url), "WikiTableQuestions-master" - ) - squall_dir = os.path.join( - dl_manager.download_and_extract(self._squall_url), "squall-main" - ) - - return [ - datasets.SplitGenerator( - name=datasets.Split.TRAIN, - gen_kwargs={ - "filepath": os.path.join(data_dir, "data/random-split-1-train.tsv"), - "data_dir": data_dir, - "squall_path": os.path.join(squall_dir, "data/squall.json"), - "squall_tables_path": os.path.join(squall_dir, "tables/json"), - "squall_db_path": os.path.join(squall_dir, "tables/db"), - "squall_tmp_db_path": os.path.join(squall_dir, "tables/tmp_db"), - }, - ), - datasets.SplitGenerator( - name=datasets.Split.VALIDATION, - gen_kwargs={ - "filepath": os.path.join(data_dir, "data/random-split-1-dev.tsv"), - "data_dir": data_dir, - "squall_path": os.path.join(squall_dir, "data/squall.json"), - "squall_tables_path": os.path.join(squall_dir, "tables/json"), - "squall_db_path": os.path.join(squall_dir, "tables/db"), - "squall_tmp_db_path": os.path.join(squall_dir, "tables/tmp_db"), - }, - ), - datasets.SplitGenerator( - name=datasets.Split.TEST, - gen_kwargs={ - "filepath": os.path.join( - data_dir, "data/pristine-unseen-tables.tsv" - ), - "data_dir": data_dir, - "squall_path": os.path.join(squall_dir, "data/squall.json"), - "squall_tables_path": os.path.join(squall_dir, "tables/json"), - "squall_db_path": os.path.join(squall_dir, "tables/db"), - "squall_tmp_db_path": os.path.join(squall_dir, "tables/tmp_db"), - }, - ), - ] - - def _generate_examples( - self, - filepath, - data_dir, - squall_path, - squall_tables_path, - squall_db_path, - squall_tmp_db_path, - ): - """https://huggingface.co/docs/datasets/v1.2.1/add_dataset.html""" - if not os.path.exists(squall_tmp_db_path): - os.makedirs(squall_tmp_db_path) - - # source table should not be truncated! - src_table_content_map = {} - # tgt table should be truncated! - tgt_table_content_map = {} - table_drop_rows_map = {} - db_engine_map = {} - - for table_json_file in os.listdir(squall_tables_path): - table_id = table_json_file[:-5] - check_table_file = open( - os.path.join(squall_tables_path, table_json_file), "r", encoding="utf8" - ) - src_table_content = json.load(check_table_file) - src_table_content = process_table_structure(src_table_content) - src_table_content_map[table_id] = json.loads(json.dumps(src_table_content)) - tgt_table_content_map[table_id] = src_table_content - - for table_db_file in os.listdir(squall_db_path): - table_id = table_db_file[:-3] - # copy table db file into a temp file since we may delete some rows - database_path = os.path.join(squall_db_path, table_db_file) - temp_database_path = os.path.join(squall_tmp_db_path, table_db_file) - if os.path.exists(temp_database_path): - os.remove(temp_database_path) - # future operations on the temp db to avoid effecting the original database - shutil.copy(database_path, temp_database_path) - db_engine_map[table_id] = WTQDBEngine(temp_database_path) - if ( - table_id in table_drop_rows_map - and len(table_drop_rows_map[table_id]) != 0 - ): - table_drop_rows = table_drop_rows_map[table_id] - db_engine_map[table_id].delete_rows(table_drop_rows) - - """Yields examples.""" - squall_id_map = {} - with open(squall_path) as f: - squall_data = json.load(f) - for squall_item in squall_data: - squall_id_map[squall_item["nt"]] = squall_item - - # data_id, question, table_id, gold_result_str - with open(filepath, encoding="utf-8") as f: - for idx, line in enumerate(f): - # skip the header - if idx == 0: - continue - data_id, question, table_id, gold_result_str = line.strip("\n").split( - "\t" - ) - - if data_id in squall_id_map.keys(): - # Data annotation from WikiTableQuestion dataset - table = _load_table( - os.path.join(data_dir, table_id.replace(".csv", ".tsv")) - ) - gold_result = gold_result_str.split("|") - - # Data annotation from Squall dataset. - squall_data_item = squall_id_map[data_id] - squall_table_id = squall_data_item["tbl"] - sql_struct = squall_data_item["sql"] - engine, src_table_content = ( - db_engine_map[squall_table_id], - src_table_content_map[squall_table_id], - ) - try: - encode_sql_str, _, exec_sql_str = retrieve_wtq_query_answer( - engine, table, sql_struct - ) - except IndexError: - # In case header is modified. - encode_sql_str, _, exec_sql_str = retrieve_wtq_query_answer( - engine, src_table_content, sql_struct - ) - - yield idx, { - EvalField.UID: data_id, - EvalField.QUESTION: question, - "table_id": table_id, - "table": table, - "sql": encode_sql_str, - EvalField.GOLD_ANSWER: gold_result, - } - else: - continue diff --git a/research/metrics/__init__.py b/research/metrics/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/metrics/fetaqa/evaluator.py b/research/metrics/fetaqa/evaluator.py deleted file mode 100644 index b2d27e92..00000000 --- a/research/metrics/fetaqa/evaluator.py +++ /dev/null @@ -1,89 +0,0 @@ -from datasets import load_metric -import nltk - - -# the code below refers to the https://github.com/Yale-LILY/FeTaQA/blob/main/end2end/train.py -def postprocess_text(preds, labels, metric_name): - preds = [pred.strip() for pred in preds] - labels = [label.strip() for label in labels] - - # rougeLSum expects newline after each sentence - if metric_name == "rouge": - preds = ["\n".join(nltk.sent_tokenize(pred)) for pred in preds] - labels = ["\n".join(nltk.sent_tokenize(label)) for label in labels] - elif metric_name == "sacrebleu": # sacrebleu - labels = [[label] for label in labels] - elif metric_name == "bleu": - preds = [pred.split(" ") for pred in preds] - labels = [[label.split(" ")] for label in labels] - else: - pass - - return preds, labels - - -class EvaluateTool(object): - def __init__(self, args): - self.args = args - - def evaluate(self, preds, golds, section): - summary = {} - - gold_text = [item["seq_out"] for item in golds] - - assert len(preds) == len(gold_text) - - metric_list = [] - if section in ["train", "dev"]: - metric_list = ["sacrebleu"] - elif section == "test": - metric_list = ["sacrebleu", "rouge", "meteor", "bertscore", "bleurt"] - - for metric_name in metric_list: - metric = load_metric(metric_name) - processed_preds, processed_golds = postprocess_text( - preds, gold_text, metric_name - ) - - if metric_name == "bertscore": - res = metric.compute( - predictions=processed_preds, references=processed_golds, lang="en" - ) - for k, v in res.items(): - if k == "hashcode": - continue - summary[f"{metric_name}_{k}"] = round(1.0 * sum(v) / len(v), 2) - - else: - res = metric.compute( - predictions=processed_preds, references=processed_golds - ) - if metric_name == "sacrebleu": - summary[metric_name] = ( - res["score"] * 0.01 - ) # limit it to range of [0, 1] for unifying - elif metric_name == "bleurt": - summary["bleurt"] = round( - 1.0 * sum(res["scores"]) / len(res["scores"]), 2 - ) - elif metric_name == "rouge": - for sub_metric_name in res.keys(): - for i, key in enumerate(["precision", "recall", "fmeasure"]): - summary["{}_{}".format(sub_metric_name, key)] = res[ - sub_metric_name - ][1][i] - # this the the fmeasure('f-score') from the mid('mean aggregation') - else: - summary[metric_name] = res[metric_name] - return summary - - -if __name__ == "__main__": - import json - - with open("predictions_predict.json") as f: - test_data = json.load(f) - preds = [item["prediction"] for item in test_data] - evaluator = EvaluateTool(args=None) - score = evaluator.evaluate(preds, test_data, section="test") - print(score) diff --git a/research/metrics/fetaqa/fetaqa.py b/research/metrics/fetaqa/fetaqa.py deleted file mode 100644 index 7b7b8685..00000000 --- a/research/metrics/fetaqa/fetaqa.py +++ /dev/null @@ -1,137 +0,0 @@ -"""Spider concept metrics.""" - -from typing import Optional, Union -import datasets -from datasets import load_metric -from .evaluator import postprocess_text - -# Find for instance the citation on arxiv or on the dataset repo/website -_CITATION = """\ -@article{Nan2021FeTaQAFT, - title={FeTaQA: Free-form Table Question Answering}, - author={Nan, Linyong and Hsieh, Chiachun and Mao, Ziming and Lin, Xi Victoria and Verma, Neha and Zhang, Rui and Kryściński, Wojciech and Schoelkopf, Hailey and Kong, Riley and Tang, Xiangru and Mutuma, Mutethia and Rosand, Ben and Trindade, Isabel and Bandaru, Renusree and Cunningham, Jacob and Xiong, Caiming and Radev, Dragomir}, - journal={Transactions of the Association for Computational Linguistics}, - year={2022}, - volume={10}, - pages={35-49} -} -""" - -_DESCRIPTION = """\ -FeTaQA is a Free-form Table Question Answering dataset with 10K Wikipedia-based {table, -question, free-form answer, supporting table cells} pairs. It yields a more challenging table -QA setting because it requires generating free-form text answers after retrieval, inference, -and integration of multiple discontinuous facts from a structured knowledge source. -Unlike datasets of generative QA over text in which answers are prevalent with copies of -short text spans from the source, answers in our dataset are human-generated explanations -involving entities and their high-level relations. -""" - -_HOMEPAGE = "https://github.com/Yale-LILY/FeTaQA" - -_LICENSE = "CC-BY-SA-4.0 License" - - -@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION) -class FetaQAQuestion(datasets.Metric): - def __init__( - self, - config_name: Optional[str] = None, - keep_in_memory: bool = False, - cache_dir: Optional[str] = None, - num_process: int = 1, - process_id: int = 0, - seed: Optional[int] = None, - experiment_id: Optional[str] = None, - max_concurrent_cache_files: int = 10000, - timeout: Union[int, float] = 100, - **kwargs, - ): - super().__init__( - config_name=config_name, - keep_in_memory=keep_in_memory, - cache_dir=cache_dir, - num_process=num_process, - process_id=process_id, - seed=seed, - experiment_id=experiment_id, - max_concurrent_cache_files=max_concurrent_cache_files, - timeout=timeout, - **kwargs, - ) - - def _info(self): - return datasets.MetricInfo( - description=_DESCRIPTION, - citation=_CITATION, - features=datasets.Features( - { - "predictions": datasets.features.Sequence(datasets.Value("string")), - "references": datasets.features.Features( - { - "answer_text": datasets.features.Sequence( - datasets.Value("string") - ), - "question": datasets.Value("string"), - } - ), - } - ), - reference_urls=[""], - ) - - @staticmethod - def eval_metric(preds, labels, metric_name="bertscore"): - summary = {} - if metric_name == "all": - metric_list = ["sacrebleu", "rouge", "meteor", "bertscore", "bleurt"] - else: - metric_list = [metric_name] - - for metric_name in metric_list: - metric = load_metric(metric_name) - pred, gold = postprocess_text(preds, labels, metric_name) - - if metric_name == "bertscore": - res = metric.compute(predictions=pred, references=gold, lang="en") - for k, v in res.items(): - if k == "hashcode": - continue - summary[f"{metric_name}_{k}"] = round(1.0 * sum(v) / len(v), 2) - else: - res = metric.compute(predictions=pred, references=gold) - if metric_name == "sacrebleu": - summary[metric_name] = res["score"] * 0.01 - # return res["score"] * 0.01 # limit it to range of [0, 1] for unifying - elif metric_name == "bleurt": - summary["bleurt"] = round( - 1.0 * sum(res["scores"]) / len(res["scores"]), 2 - ) - # return round(1.0 * sum(res["scores"]) / len(res["scores"]), 2) - elif metric_name == "rouge": - for sub_metric_name in res.keys(): - for i, key in enumerate(["precision", "recall", "fmeasure"]): - summary["{}_{}".format(sub_metric_name, key)] = res[ - sub_metric_name - ][1][i] - # return res[sub_metric_name][1][-1] #'fmeasure' - # this the the fmeasure('f-score') from the mid('mean aggregation') - else: - summary[metric_name] = res[metric_name] - # return res[metric_name] - return summary - - def _compute(self, predictions, references, allow_semantic: bool = True, **kwargs): - assert len(predictions) == len(references) - n_total_samples = len(predictions) - n_correct_dict = None - for pred, ref in zip(predictions, references): - score = self.eval_metric( - preds=pred, labels=ref["answer_text"], metric_name=self.config_name - ) - if n_correct_dict: - for key, value in score.items(): - n_correct_dict[key] += value / n_total_samples - else: - n_correct_dict = {k: v / n_total_samples for k, v in score.items()} - return n_correct_dict diff --git a/research/metrics/feverous/evaluator.py b/research/metrics/feverous/evaluator.py deleted file mode 100644 index b18073a6..00000000 --- a/research/metrics/feverous/evaluator.py +++ /dev/null @@ -1,23 +0,0 @@ -import numpy as np - - -class EvaluateTool(object): - def __init__(self, args=None): - self.args = args - - def evaluate(self, preds, golds, section=None): - summary = {} - all_match = [] - - for pred, gold_item in zip(preds, golds): - # IMPORTANT! - # Below we ignore "NOT ENOUGH INFO" - # Consider this when comparing to other results - if gold_item["seq_out"] == "NOT ENOUGH INFO": - continue - match_or_not = pred == gold_item["seq_out"] - all_match.append(match_or_not) - - summary["all"] = float(np.mean(all_match)) - - return summary diff --git a/research/metrics/feverous/feverous.py b/research/metrics/feverous/feverous.py deleted file mode 100644 index 0dee3f73..00000000 --- a/research/metrics/feverous/feverous.py +++ /dev/null @@ -1,69 +0,0 @@ -from typing import Optional, Union -import datasets - -try: - from .evaluator import EvaluateTool -except: - from research.metrics.feverous.evaluator import EvaluateTool - -_CITATION = """\ -@article{aly2021feverous, - title={FEVEROUS: Fact Extraction and VERification Over Unstructured and Structured information}, - author={Aly, Rami and Guo, Zhijiang and Schlichtkrull, Michael and Thorne, James and Vlachos, Andreas and Christodoulopoulos, Christos and Cocarascu, Oana and Mittal, Arpit}, - journal={arXiv preprint arXiv:2106.05707}, - year={2021} -} -""" - -_DESCRIPTION = """\ -This dataset is obtained from the official release of the FEVEROUS. -""" - - -@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION) -class FEVEROUS(datasets.Metric): - def __init__( - self, - config_name: Optional[str] = None, - keep_in_memory: bool = False, - cache_dir: Optional[str] = None, - num_process: int = 1, - process_id: int = 0, - seed: Optional[int] = None, - experiment_id: Optional[str] = None, - max_concurrent_cache_files: int = 10000, - timeout: Union[int, float] = 100, - **kwargs - ): - super().__init__( - config_name=config_name, - keep_in_memory=keep_in_memory, - cache_dir=cache_dir, - num_process=num_process, - process_id=process_id, - seed=seed, - experiment_id=experiment_id, - max_concurrent_cache_files=max_concurrent_cache_files, - timeout=timeout, - **kwargs - ) - self.evaluator = EvaluateTool() - - def _info(self): - return datasets.MetricInfo( - description=_DESCRIPTION, - citation=_CITATION, - features=datasets.Features( - { - "predictions": datasets.Value("string"), - "references": datasets.features.Features( - {"seq_out": datasets.Value("string")} - ), - } - ), - reference_urls=[""], - ) - - def _compute(self, predictions, references, **kwargs): - assert len(predictions) == len(references) - return self.evaluator.evaluate(preds=predictions, golds=references) diff --git a/research/metrics/hybridqa/evaluator.py b/research/metrics/hybridqa/evaluator.py deleted file mode 100644 index abd866e8..00000000 --- a/research/metrics/hybridqa/evaluator.py +++ /dev/null @@ -1,170 +0,0 @@ -import re -import collections -import string -from ...utils.normalizer import str_normalize -from ..wikitq.evaluator import to_value_list, check_denotation - -# copy from https://github.com/wenhuchen/HybridQA/blob/master/evaluate_script.py - - -def normalize_answer(s): - """Lower text and remove punctuation, articles and extra whitespace.""" - - def remove_articles(text): - regex = re.compile(r"\b(a|an|the)\b", re.UNICODE) - return re.sub(regex, " ", text) - - def white_space_fix(text): - return " ".join(text.split()) - - def remove_punc(text): - exclude = set(string.punctuation) - return "".join(ch for ch in text if ch not in exclude) - - def lower(text): - return text.lower() - - return white_space_fix(remove_articles(remove_punc(lower(s)))) - - -def get_tokens(s): - if not s: - return [] - return normalize_answer(s).split() - - -def compute_exact(a_gold, a_pred): - return int(normalize_answer(a_gold) == normalize_answer(a_pred)) - - -def compute_f1(a_gold, a_pred): - gold_toks = get_tokens(a_gold) - pred_toks = get_tokens(a_pred) - common = collections.Counter(gold_toks) & collections.Counter(pred_toks) - num_same = sum(common.values()) - if len(gold_toks) == 0 or len(pred_toks) == 0: - # If either is no-answer, then F1 is 1 if they agree, 0 otherwise - return int(gold_toks == pred_toks) - if num_same == 0: - return 0 - precision = 1.0 * num_same / len(pred_toks) - recall = 1.0 * num_same / len(gold_toks) - f1 = (2 * precision * recall) / (precision + recall) - return f1 - - -def eval_ex_match(pred, gold, allow_semantic=True, question=None): - """Taken from the Binder codebase, used to evaluate WikiTQ dataset.""" - if not isinstance(pred, list): - pred = [pred] - gold = [gold] - - pred = [str(p).lower().strip() for p in pred] - gold = [str(g).lower().strip() for g in gold] - if not allow_semantic: - # WikiTQ eval w. string normalization using recognizer - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - else: - assert isinstance(question, str) - question = re.sub("\s+", " ", question).strip().lower() - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = sorted(list(set(pred))) - gold = sorted(list(set(gold))) - # (1) 0 matches 'no', 1 matches 'yes'; 0 matches 'more', 1 matches 'less', etc. - if len(pred) == 1 and len(gold) == 1: - if (pred[0] == "0" and gold[0] == "no") or ( - pred[0] == "1" and gold[0] == "yes" - ): - return True - question_tokens = question.split() - try: - pos_or = question_tokens.index("or") - token_before_or, token_after_or = ( - question_tokens[pos_or - 1], - question_tokens[pos_or + 1], - ) - if (pred[0] == "0" and gold[0] == token_after_or) or ( - pred[0] == "1" and gold[0] == token_before_or - ): - return True - except Exception: - pass - # (2) Number value (allow units) and Date substring match - if len(pred) == 1 and len(gold) == 1: - NUMBER_UNITS_PATTERN = re.compile( - "^\$*[+-]?([0-9]*[.])?[0-9]+(\s*%*|\s+\w+)$" - ) - DATE_PATTERN = re.compile( - "[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}\s*([0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2})?" - ) - DURATION_PATTERN = re.compile("(P|PT)(\d+)(Y|M|D|H|S)") - p, g = pred[0], gold[0] - # Restore `duration` type, e.g., from 'P3Y' -> '3' - if re.match(DURATION_PATTERN, p): - p = re.match(DURATION_PATTERN, p).group(2) - if re.match(DURATION_PATTERN, g): - g = re.match(DURATION_PATTERN, g).group(2) - match = False - num_flag, date_flag = False, False - # Number w. unit match after string normalization. - # Either pred or gold being number w. units suffices it. - if re.match(NUMBER_UNITS_PATTERN, p) or re.match(NUMBER_UNITS_PATTERN, g): - num_flag = True - # Date match after string normalization. - # Either pred or gold being date suffices it. - if re.match(DATE_PATTERN, p) or re.match(DATE_PATTERN, g): - date_flag = True - if num_flag: - p_set, g_set = set(p.split()), set(g.split()) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if date_flag: - p_set, g_set = set(p.replace("-", " ").split()), set( - g.replace("-", " ").split() - ) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if match: - return True - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - - -class EvaluateTool(object): - def __init__(self, args: dict = None): - self.args = args - - def evaluate(self, preds, golds, section=None): - summary = {} - exact_scores = {} - f1_scores = {} - denotation_scores = {} - for pred, gold in zip(preds, golds): - qas_id = gold["id"] - gold_answers = [gold["answer_text"]] - - exact_scores[qas_id] = max(compute_exact(a, pred) for a in gold_answers) - f1_scores[qas_id] = max(compute_f1(a, pred) for a in gold_answers) - denotation_scores[qas_id] = max( - eval_ex_match(a, pred, question=gold["question"]) for a in gold_answers - ) - - total = len(golds) - qid_list = list(exact_scores.keys()) - - summary["acc"] = sum(exact_scores[k] for k in qid_list) / total - summary["f1"] = sum(f1_scores[k] for k in qid_list) / total - summary["denotation_acc"] = sum(denotation_scores[k] for k in qid_list) / total - return summary - - -if __name__ == "__main__": - evaluator = EvaluateTool() - evaluator.evaluate(preds=["1"], golds=[{"answer_text": "1", "id": 0}]) - print() diff --git a/research/metrics/hybridqa/hybridqa.py b/research/metrics/hybridqa/hybridqa.py deleted file mode 100644 index c2bbff6c..00000000 --- a/research/metrics/hybridqa/hybridqa.py +++ /dev/null @@ -1,78 +0,0 @@ -from typing import Optional, Union -import datasets - -try: - from .evaluator import EvaluateTool -except: - from research.metrics.hybridqa.evaluator import EvaluateTool - -# Find for instance the citation on arxiv or on the dataset repo/website -_CITATION = """\ -@article{chen2020hybridqa, - title={HybridQA: A Dataset of Multi-Hop Question Answering over Tabular and Textual Data}, - author={Chen, Wenhu and Zha, Hanwen and Chen, Zhiyu and Xiong, Wenhan and Wang, Hong and Wang, William}, - journal={Findings of EMNLP 2020}, - year={2020} -} -""" - -_DESCRIPTION = """\ -Existing question answering datasets focus on dealing with homogeneous information, based either only on text or KB/Table information alone. However, as human knowledge is distributed over heterogeneous forms, using homogeneous information alone might lead to severe coverage problems. To fill in the gap, we present HybridQA, a new large-scale question-answering dataset that requires reasoning on heterogeneous information. Each question is aligned with a Wikipedia table and multiple free-form corpora linked with the entities in the table. The questions are designed to aggregate both tabular information and text information, i.e., lack of either form would render the question unanswerable. We test with three different models: 1) a table-only model. 2) text-only model. 3) a hybrid model that combines heterogeneous information to find the answer. The experimental results show that the EM scores obtained by two baselines are below 20%, while the hybrid model can achieve an EM over 40%. This gap suggests the necessity to aggregate heterogeneous information in HybridQA. However, the hybrid model’s score is still far behind human performance. Hence, HybridQA can serve as a challenging benchmark to study question answering with heterogeneous information. -""" - -_HOMEPAGE = "https://hybridqa.github.io/" - -_LICENSE = "CC-BY-SA-4.0 License" - - -@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION) -class HybridQA(datasets.Metric): - def __init__( - self, - config_name: Optional[str] = None, - keep_in_memory: bool = False, - cache_dir: Optional[str] = None, - num_process: int = 1, - process_id: int = 0, - seed: Optional[int] = None, - experiment_id: Optional[str] = None, - max_concurrent_cache_files: int = 10000, - timeout: Union[int, float] = 100, - **kwargs - ): - super().__init__( - config_name=config_name, - keep_in_memory=keep_in_memory, - cache_dir=cache_dir, - num_process=num_process, - process_id=process_id, - seed=seed, - experiment_id=experiment_id, - max_concurrent_cache_files=max_concurrent_cache_files, - timeout=timeout, - **kwargs - ) - self.evaluator = EvaluateTool() - - def _info(self): - return datasets.MetricInfo( - description=_DESCRIPTION, - citation=_CITATION, - features=datasets.Features( - { - "predictions": datasets.Value("string"), - "references": datasets.features.Features( - { - "answer_text": datasets.Value("string"), - "id": datasets.Value("string"), - "question": datasets.Value("string"), - } - ), - } - ), - reference_urls=[""], - ) - - def _compute(self, predictions, references, **kwargs): - assert len(predictions) == len(references) - return self.evaluator.evaluate(predictions, references) diff --git a/research/metrics/ottqa/evaluator.py b/research/metrics/ottqa/evaluator.py deleted file mode 100644 index 8d5494b3..00000000 --- a/research/metrics/ottqa/evaluator.py +++ /dev/null @@ -1,163 +0,0 @@ -import re -import collections -import string -from ...utils.normalizer import str_normalize -from ..wikitq.evaluator import to_value_list, check_denotation - -# copy from https://github.com/wenhuchen/OTT-QA/blob/master/evaluate_script.py - - -def normalize_answer(s): - """Lower text and remove punctuation, articles and extra whitespace.""" - - def remove_articles(text): - regex = re.compile(r"\b(a|an|the)\b", re.UNICODE) - return re.sub(regex, " ", text) - - def white_space_fix(text): - return " ".join(text.split()) - - def remove_punc(text): - exclude = set(string.punctuation) - return "".join(ch for ch in text if ch not in exclude) - - def lower(text): - return text.lower() - - return white_space_fix(remove_articles(remove_punc(lower(s)))) - - -def get_tokens(s): - if not s: - return [] - return normalize_answer(s).split() - - -def compute_exact(a_gold, a_pred): - return int(normalize_answer(a_gold) == normalize_answer(a_pred)) - - -def compute_f1(a_gold, a_pred): - gold_toks = get_tokens(a_gold) - pred_toks = get_tokens(a_pred) - common = collections.Counter(gold_toks) & collections.Counter(pred_toks) - num_same = sum(common.values()) - if len(gold_toks) == 0 or len(pred_toks) == 0: - # If either is no-answer, then F1 is 1 if they agree, 0 otherwise - return int(gold_toks == pred_toks) - if num_same == 0: - return 0 - precision = 1.0 * num_same / len(pred_toks) - recall = 1.0 * num_same / len(gold_toks) - f1 = (2 * precision * recall) / (precision + recall) - return f1 - - -def eval_ex_match(pred, gold, allow_semantic=True, question=None): - """Taken from the Binder codebase, used to evaluate WikiTQ dataset.""" - if not isinstance(pred, list): - pred = [pred] - gold = [gold] - - pred = [str(p).lower().strip() for p in pred] - gold = [str(g).lower().strip() for g in gold] - if not allow_semantic: - # WikiTQ eval w. string normalization using recognizer - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - else: - assert isinstance(question, str) - question = re.sub("\s+", " ", question).strip().lower() - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = sorted(list(set(pred))) - gold = sorted(list(set(gold))) - # (1) 0 matches 'no', 1 matches 'yes'; 0 matches 'more', 1 matches 'less', etc. - if len(pred) == 1 and len(gold) == 1: - if (pred[0] == "0" and gold[0] == "no") or ( - pred[0] == "1" and gold[0] == "yes" - ): - return True - question_tokens = question.split() - try: - pos_or = question_tokens.index("or") - token_before_or, token_after_or = ( - question_tokens[pos_or - 1], - question_tokens[pos_or + 1], - ) - if (pred[0] == "0" and gold[0] == token_after_or) or ( - pred[0] == "1" and gold[0] == token_before_or - ): - return True - except Exception: - pass - # (2) Number value (allow units) and Date substring match - if len(pred) == 1 and len(gold) == 1: - NUMBER_UNITS_PATTERN = re.compile( - "^\$*[+-]?([0-9]*[.])?[0-9]+(\s*%*|\s+\w+)$" - ) - DATE_PATTERN = re.compile( - "[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}\s*([0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2})?" - ) - DURATION_PATTERN = re.compile("(P|PT)(\d+)(Y|M|D|H|S)") - p, g = pred[0], gold[0] - # Restore `duration` type, e.g., from 'P3Y' -> '3' - if re.match(DURATION_PATTERN, p): - p = re.match(DURATION_PATTERN, p).group(2) - if re.match(DURATION_PATTERN, g): - g = re.match(DURATION_PATTERN, g).group(2) - match = False - num_flag, date_flag = False, False - # Number w. unit match after string normalization. - # Either pred or gold being number w. units suffices it. - if re.match(NUMBER_UNITS_PATTERN, p) or re.match(NUMBER_UNITS_PATTERN, g): - num_flag = True - # Date match after string normalization. - # Either pred or gold being date suffices it. - if re.match(DATE_PATTERN, p) or re.match(DATE_PATTERN, g): - date_flag = True - if num_flag: - p_set, g_set = set(p.split()), set(g.split()) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if date_flag: - p_set, g_set = set(p.replace("-", " ").split()), set( - g.replace("-", " ").split() - ) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if match: - return True - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - - -class EvaluateTool(object): - def __init__(self, args=None): - self.args = args - - def evaluate(self, preds, golds, section=None): - summary = {} - exact_scores = {} - f1_scores = {} - denotation_scores = {} - for pred, gold in zip(preds, golds): - qas_id = gold["id"] - gold_answers = [gold["answer_text"]] - - exact_scores[qas_id] = max(compute_exact(a, pred) for a in gold_answers) - f1_scores[qas_id] = max(compute_f1(a, pred) for a in gold_answers) - denotation_scores[qas_id] = max( - eval_ex_match(a, pred, question=gold["question"]) for a in gold_answers - ) - total = len(golds) - qid_list = list(exact_scores.keys()) - - summary["exact"] = sum(exact_scores[k] for k in qid_list) / total - summary["f1"] = sum(f1_scores[k] for k in qid_list) / total - summary["denotation_acc"] = sum(denotation_scores[k] for k in qid_list) / total - return summary diff --git a/research/metrics/ottqa/ottqa.py b/research/metrics/ottqa/ottqa.py deleted file mode 100644 index 091b84c1..00000000 --- a/research/metrics/ottqa/ottqa.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Optional, Union -import datasets - -try: - from .evaluator import EvaluateTool -except: - from research.metrics.ottqa.evaluator import EvaluateTool - -_CITATION = """\ -@article{chen2020open, - title={Open question answering over tables and text}, - author={Chen, Wenhu and Chang, Ming-Wei and Schlinger, Eva and Wang, William and Cohen, William W}, - journal={arXiv preprint arXiv:2010.10439}, - year={2020} -} -""" - -_DESCRIPTION = """\ -This dataset is obtained from the official release of the OTT-QA. -""" - - -@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION) -class OTTQA(datasets.Metric): - def __init__( - self, - config_name: Optional[str] = None, - keep_in_memory: bool = False, - cache_dir: Optional[str] = None, - num_process: int = 1, - process_id: int = 0, - seed: Optional[int] = None, - experiment_id: Optional[str] = None, - max_concurrent_cache_files: int = 10000, - timeout: Union[int, float] = 100, - **kwargs - ): - super().__init__( - config_name=config_name, - keep_in_memory=keep_in_memory, - cache_dir=cache_dir, - num_process=num_process, - process_id=process_id, - seed=seed, - experiment_id=experiment_id, - max_concurrent_cache_files=max_concurrent_cache_files, - timeout=timeout, - **kwargs - ) - self.evaluator = EvaluateTool() - - def _info(self): - return datasets.MetricInfo( - description=_DESCRIPTION, - citation=_CITATION, - features=datasets.Features( - { - "predictions": datasets.Value("string"), - "references": datasets.features.Features( - { - "answer_text": datasets.Value("string"), - "id": datasets.Value("string"), - "question": datasets.Value("string"), - } - ), - } - ), - reference_urls=[""], - ) - - def _compute(self, predictions, references, **kwargs): - assert len(predictions) == len(references) - return self.evaluator.evaluate(preds=predictions, golds=references) diff --git a/research/metrics/wikitq/evaluator.py b/research/metrics/wikitq/evaluator.py deleted file mode 100644 index 6a74863c..00000000 --- a/research/metrics/wikitq/evaluator.py +++ /dev/null @@ -1,369 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -"""Official Evaluator for WikiTableQuestions Dataset - -There are 3 value types -1. String (unicode) -2. Number (float) -3. Date (a struct with 3 fields: year, month, and date) - Some fields (but not all) can be left unspecified. However, if only the year - is specified, the date is automatically converted into a number. - -Target denotation = a set of items -- Each item T is a raw unicode string from Mechanical Turk -- If T can be converted to a number or date (via Stanford CoreNLP), the - converted value (number T_N or date T_D) is precomputed - -Predicted denotation = a set of items -- Each item P is a string, a number, or a date -- If P is read from a text file, assume the following - - A string that can be converted into a number (float) is converted into a - number - - A string of the form "yyyy-mm-dd" is converted into a date. Unspecified - fields can be marked as "xx". For example, "xx-01-02" represents the date - January 2nd of an unknown year. - - Otherwise, it is kept as a string - -The predicted denotation is correct if -1. The sizes of the target denotation and the predicted denotation are equal -2. Each item in the target denotation matches an item in the predicted - denotation - -A target item T matches a predicted item P if one of the following is true: -1. normalize(raw string of T) and normalize(string form of P) are identical. - The normalize method performs the following normalizations on strings: - - Remove diacritics (é → e) - - Convert smart quotes (‘’´`“”) and dashes (‐‑‒–—−) into ASCII ones - - Remove citations (trailing •♦†‡*#+ or [...]) - - Remove details in parenthesis (trailing (...)) - - Remove outermost quotation marks - - Remove trailing period (.) - - Convert to lowercase - - Collapse multiple whitespaces and strip outermost whitespaces -2. T can be interpreted as a number T_N, P is a number, and P = T_N -3. T can be interpreted as a date T_D, P is a date, and P = T_D - (exact match on all fields; e.g., xx-01-12 and 1990-01-12 do not match) -""" -__version__ = "1.0.2" - -import re -import unicodedata -from math import isnan, isinf -from abc import ABCMeta, abstractmethod - - -################ String Normalization ################ - - -def normalize(x): - if not isinstance(x, str): - x = x.decode("utf8", errors="ignore") - # Remove diacritics - x = "".join( - c for c in unicodedata.normalize("NFKD", x) if unicodedata.category(c) != "Mn" - ) - # Normalize quotes and dashes - x = re.sub(r"[‘’´`]", "'", x) - x = re.sub(r"[“”]", '"', x) - x = re.sub(r"[‐‑‒–—−]", "-", x) - while True: - old_x = x - # Remove citations - x = re.sub(r"((? backslash + n - vertical bar (0x7C) -> backslash + p - backslash (0x5C) -> backslash + backslash - - Args: - x (str or unicode) - Returns: - a unicode - """ - return x.replace(r"\n", "\n").replace(r"\p", "|").replace("\\\\", "\\") - - -def tsv_unescape_list(x): - """Unescape a list in the TSV file. - List items are joined with vertical bars (0x5C) - - Args: - x (str or unicode) - Returns: - a list of unicodes - """ - return [tsv_unescape(y) for y in x.split("|")] - - -def main(): - pred_answer = ["ABC"] - gold_answer = ["Abc"] - pred_answer_val = to_value_list(pred_answer) - gold_answer_val = to_value_list(gold_answer) - correct = check_denotation(pred_answer_val, gold_answer_val) - print(pred_answer_val) - print(gold_answer_val) - print(correct) - - -if __name__ == "__main__": - main() diff --git a/research/metrics/wikitq/wikitq.py b/research/metrics/wikitq/wikitq.py deleted file mode 100644 index 064119b1..00000000 --- a/research/metrics/wikitq/wikitq.py +++ /dev/null @@ -1,191 +0,0 @@ -"""Spider concept metrics.""" - -from typing import Optional, Union -import re -import datasets - -try: - from .evaluator import to_value_list, check_denotation - from ...utils.normalizer import str_normalize -except: - from research.metrics.wikitq.evaluator import to_value_list, check_denotation - from research.utils.normalizer import str_normalize - -# Find for instance the citation on arxiv or on the dataset repo/website -_CITATION = """\ -@inproceedings{pasupat-liang-2015-compositional, - title = "Compositional Semantic Parsing on Semi-Structured Tables", - author = "Pasupat, Panupong and - Liang, Percy", - booktitle = "Proceedings of the 53rd Annual Meeting of the Association for Computational Linguistics and the 7th International Joint Conference on Natural Language Processing (Volume 1: Long Papers)", - month = jul, - year = "2015", - address = "Beijing, China", - publisher = "Association for Computational Linguistics", - url = "https://aclanthology.org/P15-1142", - doi = "10.3115/v1/P15-1142", - pages = "1470--1480", -} -""" - -_DESCRIPTION = """\ -Two important aspects of semantic parsing for question answering are the breadth of the knowledge source and the depth of -logical compositionality. While existing work trades off one aspect for another, this paper simultaneously makes progress -on both fronts through a new task: answering complex questions on semi-structured tables using question-answer pairs as -supervision. The central challenge arises from two compounding factors: the broader domain results in an open-ended set -of relations, and the deeper compositionality results in a combinatorial explosion in the space of logical forms. We -propose a logical-form driven parsing algorithm guided by strong typing constraints and show that it obtains significant - improvements over natural baselines. For evaluation, we created a new dataset of 22,033 complex questions on Wikipedia - tables, which is made publicly available. -""" - -_HOMEPAGE = "https://ppasupat.github.io/WikiTableQuestions/" - -_LICENSE = "CC-BY-SA-4.0 License" - - -@datasets.utils.file_utils.add_start_docstrings(_DESCRIPTION) -class WikiTableQuestion(datasets.Metric): - def __init__( - self, - config_name: Optional[str] = None, - keep_in_memory: bool = False, - cache_dir: Optional[str] = None, - num_process: int = 1, - process_id: int = 0, - seed: Optional[int] = None, - experiment_id: Optional[str] = None, - max_concurrent_cache_files: int = 10000, - timeout: Union[int, float] = 100, - **kwargs - ): - super().__init__( - config_name=config_name, - keep_in_memory=keep_in_memory, - cache_dir=cache_dir, - num_process=num_process, - process_id=process_id, - seed=seed, - experiment_id=experiment_id, - max_concurrent_cache_files=max_concurrent_cache_files, - timeout=timeout, - **kwargs - ) - - def _info(self): - return datasets.MetricInfo( - description=_DESCRIPTION, - citation=_CITATION, - features=datasets.Features( - { - "predictions": datasets.features.Sequence(datasets.Value("string")), - "references": datasets.features.Features( - { - "answer_text": datasets.features.Sequence( - datasets.Value("string") - ), - "question": datasets.Value("string"), - } - ), - } - ), - reference_urls=[""], - ) - - @staticmethod - def eval_ex_match(pred, gold, allow_semantic=True, question=None): - if not isinstance(pred, list): - pred = [pred] - gold = [gold] - - pred = [str(p).lower().strip() for p in pred] - gold = [str(g).lower().strip() for g in gold] - if not allow_semantic: - # WikiTQ eval w. string normalization using recognizer - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - else: - assert isinstance(question, str) - question = re.sub("\s+", " ", question).strip().lower() - pred = [str_normalize(span) for span in pred] - gold = [str_normalize(span) for span in gold] - pred = sorted(list(set(pred))) - gold = sorted(list(set(gold))) - # (1) 0 matches 'no', 1 matches 'yes'; 0 matches 'more', 1 matches 'less', etc. - if len(pred) == 1 and len(gold) == 1: - if (pred[0] == "0" and gold[0] == "no") or ( - pred[0] == "1" and gold[0] == "yes" - ): - return True - question_tokens = question.split() - try: - pos_or = question_tokens.index("or") - token_before_or, token_after_or = ( - question_tokens[pos_or - 1], - question_tokens[pos_or + 1], - ) - if (pred[0] == "0" and gold[0] == token_after_or) or ( - pred[0] == "1" and gold[0] == token_before_or - ): - return True - except Exception: - pass - # (2) Number value (allow units) and Date substring match - if len(pred) == 1 and len(gold) == 1: - NUMBER_UNITS_PATTERN = re.compile( - "^\$*[+-]?([0-9]*[.])?[0-9]+(\s*%*|\s+\w+)$" - ) - DATE_PATTERN = re.compile( - "[0-9]{4}-[0-9]{1,2}-[0-9]{1,2}\s*([0-9]{1,2}:[0-9]{1,2}:[0-9]{1,2})?" - ) - DURATION_PATTERN = re.compile("(P|PT)(\d+)(Y|M|D|H|S)") - p, g = pred[0], gold[0] - # Restore `duration` type, e.g., from 'P3Y' -> '3' - if re.match(DURATION_PATTERN, p): - p = re.match(DURATION_PATTERN, p).group(2) - if re.match(DURATION_PATTERN, g): - g = re.match(DURATION_PATTERN, g).group(2) - match = False - num_flag, date_flag = False, False - # Number w. unit match after string normalization. - # Either pred or gold being number w. units suffices it. - if re.match(NUMBER_UNITS_PATTERN, p) or re.match( - NUMBER_UNITS_PATTERN, g - ): - num_flag = True - # Date match after string normalization. - # Either pred or gold being date suffices it. - if re.match(DATE_PATTERN, p) or re.match(DATE_PATTERN, g): - date_flag = True - if num_flag: - p_set, g_set = set(p.split()), set(g.split()) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if date_flag: - p_set, g_set = set(p.replace("-", " ").split()), set( - g.replace("-", " ").split() - ) - if p_set.issubset(g_set) or g_set.issubset(p_set): - match = True - if match: - return True - pred = to_value_list(pred) - gold = to_value_list(gold) - return check_denotation(pred, gold) - - def _compute(self, predictions, references, allow_semantic: bool = True, **kwargs): - assert len(predictions) == len(references) - n_total_samples = len(predictions) - n_correct_samples = 0 - for pred, ref in zip(predictions, references): - score = self.eval_ex_match( - pred=pred, - gold=ref["answer_text"], - allow_semantic=allow_semantic, - question=ref["question"], - ) - n_correct_samples += score - return n_correct_samples / n_total_samples diff --git a/research/notebooks/hybridqa-explore.ipynb b/research/notebooks/hybridqa-explore.ipynb deleted file mode 100644 index 46a5eac1..00000000 --- a/research/notebooks/hybridqa-explore.ipynb +++ /dev/null @@ -1,178 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 42, - "metadata": { - "collapsed": true, - "ExecuteTime": { - "end_time": "2024-04-12T18:12:40.638283Z", - "start_time": "2024-04-12T18:12:39.955076Z" - } - }, - "outputs": [], - "source": [ - "import json\n", - "from recognizers_number import recognize_number\n", - "from recognizers_date_time import recognize_datetime, Culture\n", - "import re" - ] - }, - { - "cell_type": "code", - "execution_count": 34, - "outputs": [], - "source": [ - "with open(\"../outputs/new-paper-results/hybridqa/gpt-4-blendsql-only-run5/rerun-predictions.json\", \"r\") as f:\n", - " predictions = json.load(f)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T18:11:04.473632Z", - "start_time": "2024-04-12T18:11:04.414918Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 51, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "101 natural language numbers\n", - "697 digit numbers\n" - ] - } - ], - "source": [ - "nl_answers = []\n", - "digit_answers = []\n", - "money_answers = []\n", - "for item in predictions:\n", - " answer = item[\"gold_answer\"]\n", - " if len(recognize_number(answer, Culture.English)) > 0:\n", - " if not re.search('\\d+', answer):\n", - " nl_answers.append(answer)\n", - " else:\n", - " if not recognize_datetime(answer, Culture.English):\n", - " digit_answers.append(answer)\n", - " if \"$\" in answer:\n", - " money_answers.append(answer)\n", - "print(f\"{len(nl_answers)} natural language numbers\")\n", - "print(f\"{len(digit_answers)} digit numbers\")" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T18:17:49.003375Z", - "start_time": "2024-04-12T18:17:39.481520Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 52, - "outputs": [ - { - "data": { - "text/plain": "['$ 37 billion',\n '$ 20 million',\n '$ 30,689',\n '$ 375,000',\n '$ 116,230',\n '$ 103.2 million',\n '$ 1.59 billion',\n '$ 240',\n '$ 450,000',\n '$ 1.4 billion',\n 'US $ 476 million',\n '$ 1887/-',\n '$ 5,490,187',\n '$ 675,000',\n '$ 900',\n '$ 90 million']" - }, - "execution_count": 52, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "money_answers" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T18:17:49.947237Z", - "start_time": "2024-04-12T18:17:49.941482Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 50, - "outputs": [ - { - "data": { - "text/plain": "['five',\n 'seven times',\n 'Seven',\n 'two',\n 'three campuses',\n 'Three',\n 'over one million people',\n 'than twenty Brahmanas',\n 'five',\n 'Seven',\n 'Three',\n 'Four',\n 'number one',\n 'ten',\n 'Four',\n 'Murdered his wife , mother-in-law and three children',\n 'three',\n 'twelve',\n 'a quarter',\n 'two',\n 'nine',\n 'Nine federated states',\n 'four',\n 'fourteen',\n 'six',\n 'five',\n 'three',\n 'twelve',\n 'five',\n 'Big Ten',\n 'thirteen',\n 'two',\n 'two',\n 'nine',\n 'four teams',\n \"One , B'Day was her second\",\n 'three',\n 'Twenty',\n 'ten',\n 'four',\n 'Three Great Gardens of Japan',\n 'three',\n 'number one',\n 'nine',\n 'seven',\n 'Two miles',\n 'six',\n 'fourteen',\n 'one',\n 'fourteen',\n 'Twenty Plenty outlet',\n 'Three',\n 'two',\n 'two years',\n 'four',\n 'five',\n 'Two',\n 'two',\n 'five friends',\n 'Network Ten',\n 'two-thirds',\n 'one',\n 'twenty-three',\n 'seven',\n 'six',\n 'every four years',\n 'three',\n 'twenty-six',\n 'four',\n 'three',\n 'four times',\n 'eighteen',\n 'two',\n 'nine',\n 'five million',\n 'twelve',\n 'one-day Olympics',\n 'eight',\n 'three disciplines',\n 'Want One',\n 'thirty-two',\n 'two',\n 'three years',\n 'six',\n 'six Primetime Emmy Awards',\n 'three',\n 'six',\n 'seven million',\n 'Sixteen in total',\n 'nine members',\n 'Twenty Twenty',\n 'five',\n 'two',\n 'Seven Beauties',\n 'The process of digestion has three stages',\n 'nine',\n 'six',\n 'five',\n 'four',\n 'three',\n 'three']" - }, - "execution_count": 50, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "nl_answers" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T18:16:02.922668Z", - "start_time": "2024-04-12T18:16:02.917306Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 49, - "outputs": [ - { - "data": { - "text/plain": "['503 Peeples Street SW',\n '32',\n '3',\n '524 km',\n '2',\n '$ 37 billion',\n '5270',\n '75 million',\n '139.25',\n '3.37',\n '9',\n '47 acres',\n '28,108',\n '2,549',\n '4',\n '34,932',\n '24',\n '76101-054',\n '11 players',\n 'Masheka Mogilev 2',\n '2,500',\n '9.412',\n '20 million albums',\n '84',\n '$ 20 million',\n '2,448 km2',\n '171',\n '1.1',\n '185,038',\n 'No . 1',\n '342',\n '60',\n '6',\n '274,905',\n '4 million',\n '30 km',\n '3',\n '37.3M',\n '5,230,330',\n '5',\n '37',\n '9× Platinum',\n '5,000',\n '9,461,105',\n '5,400',\n '10',\n '2',\n '2 hourly',\n '580,367 square kilometres ( 224,081 sq mi )',\n '2',\n '8000 BC',\n 'US 20',\n '47',\n '9 Watergate Street',\n '21,397,000',\n '23',\n 'Volume 1',\n 'United States Army UH-60 Black Hawk',\n '7.48',\n 'Proposition 13',\n '124-acre',\n 'from 237 to 227 million years ago',\n '407',\n '3',\n '32',\n '65',\n '3',\n '6,000',\n '15',\n '7',\n '3,674',\n '15',\n '36',\n '566',\n '107 Nobel laureates',\n '58',\n '12,960',\n 'Red 121 , White 167',\n '88',\n '40',\n '306',\n '25',\n '2,514',\n 'CR 202',\n '308,580 hectares',\n '586',\n 'Thailand Route 4106',\n '15',\n '18',\n '7',\n '1-2',\n '250',\n '16',\n '3',\n '12 seasons',\n '15,941,000',\n '36,969',\n '18,000',\n \"Men 's 50 m backstroke\",\n '46',\n '11,249-capacity',\n '261',\n '1.5 million',\n '354 cc',\n '300-foot',\n '25,062',\n 'under-48-kilogram',\n '16 kilometres',\n 'Over 40%',\n '11,200',\n '38',\n '4',\n '5,527',\n '16',\n 'Highway 2',\n \"Half of the world 's 20 largest drugmakers\",\n '3.1 million hectares',\n '24+',\n '4',\n '3',\n '7',\n '5343 Southeast Abshier Boulevard',\n '522.59 km',\n '12.5 km',\n '39,519,400',\n 'No . 2',\n '30',\n '2',\n '$ 30,689',\n '# 70000667',\n '111',\n '56°43′55″N 2°39′42″W / 56.73194°N 2.66167°W / 56.73194 ; -2.66167 ( Brechin Cathedral )',\n '121',\n '4,612 km²',\n '1,163',\n '30',\n '1,500-meter freestyle',\n '338',\n '38.5',\n '$ 375,000',\n '19',\n '22',\n '25',\n '1',\n 'NA-13 ( Mansehra-I )',\n '16',\n '85 million',\n '0',\n '46.15%',\n '149.4',\n '47,774',\n '9.98',\n '15',\n '7',\n '0-11 ( 11 )',\n '151,801',\n '80',\n '3,800',\n '44634',\n '22,000',\n '3',\n '79112-054',\n '2',\n '12,711',\n '22',\n '3,000',\n '5',\n '56 square miles',\n '36,474',\n '17',\n '26',\n '24 miles',\n '27',\n 'FR-07',\n '3',\n '0.3',\n '26 million',\n '750',\n '16',\n '10',\n '5',\n '46',\n 'batting .358',\n '21',\n '76',\n '6,863',\n '17',\n '10,888',\n '6',\n '12',\n '631,486',\n '2,138',\n '4',\n 'The city was initially founded between 1771 and 1776',\n '30°41′22″N 88°2′37″W / 30.68944°N 88.04361°W / 30.68944 ; -88.04361 ( Government Street Presbyterian Church )',\n 'K League 1',\n '3,000',\n 'the National Premier Leagues Victoria Division 1',\n '36',\n '10',\n '2500',\n '44',\n '558,797',\n 'nearly 600',\n '2',\n '37',\n '15',\n '3.8 million square miles',\n '1.12 square miles',\n 'Amalgam Comics # 1',\n '4',\n '$ 116,230',\n '19',\n '6',\n '4.05',\n '60',\n '24,000',\n '3',\n '4',\n '2 million',\n 'Cyborg 009 : Legend of the Super Galaxy',\n '4',\n \"Men 's freestyle 74 kg\",\n '77',\n '40-0',\n '95550',\n '125 metres',\n 'KNM-LT 329',\n '4,298,655',\n '707 ha',\n '80827',\n '2',\n '146,100 square kilometres ( 56,400 sq mi )',\n '1',\n '2,766.6',\n '3,500',\n '16',\n 'KVVV 15',\n '0',\n '0',\n 'CR 43',\n '3',\n '5.3 miles',\n 'Guitar Hero Track Pack 1',\n '2,168',\n '6,211',\n '13.75',\n 'roughly 400',\n '4,935',\n '6',\n '17.34 million',\n '1,800',\n '3',\n '2',\n '51%',\n '2,411',\n '378',\n '5',\n '5-2',\n '46,800',\n '37.9 million',\n 'Over 40%',\n '4',\n '79,343',\n '130 km',\n '5,000',\n '114',\n '86',\n '20',\n '19',\n '6',\n '19.6',\n '72,465',\n '# 06000989',\n '120 steps per minute',\n '637.4 square miles',\n '6 ft 8\\u200b3⁄4 in',\n '17',\n '51,700',\n '3,857 km2 ( 1,489 sq mi )',\n '4',\n '230 square kilometres',\n '2,000 kilometres',\n '1.4-acre ( 0.57 ha )',\n '20,000',\n \"Men 's 10 m platform\",\n '47',\n 'No . 1',\n 'League 1',\n '15 miles',\n '80-inch',\n '83,500',\n '11',\n '34',\n '3,905',\n '1,884',\n '24.3 km',\n '1.8 million square kilometres',\n \"Men 's 200 m\",\n '37,188',\n '649,021',\n '491,918',\n '2',\n '84,298',\n '3000 m steeplechase',\n '18 AF/AFTRANS',\n '1',\n '1008211',\n 'U.S . Routes 20 and 169',\n '100 Philip Aziz Avenue',\n '6,763',\n '11,406',\n '6',\n '13',\n '80,093',\n '47.6 million people',\n '1,550 m',\n '1,864',\n '7',\n '310',\n '13,486',\n '8,055',\n '3,550',\n '3',\n '20,000',\n '10',\n '38°03′25″N 83°56′26″W / 38.056944°N 83.940556°W / 38.056944 ; -83.940556 ( Methodist Episcopal Church South )',\n '4.5 million',\n '17',\n '17',\n '203',\n '58.5',\n '8',\n '11 miles',\n '2.6',\n '24-32 ( 10-20 )',\n '2,317,105',\n '55°56′58″N 3°11′27″W / 55.94944°N 3.19083°W / 55.94944 ; -3.19083 ( St. Giles Cathedral )',\n '2',\n '8.84',\n '40',\n '€757,3 million',\n '# 76001760',\n '3',\n 'USD 2 Million',\n '68',\n '9',\n '9',\n '253',\n '18',\n 'More than 11,000',\n 'A1A-2',\n '542,868',\n '65 million',\n '6',\n '15',\n '450,000',\n '82',\n '2.1',\n '7,500',\n '2',\n '3',\n '2.11',\n '103,608',\n '82,418',\n '23,450㎡',\n '20%',\n '20',\n '7.4 million',\n 'Airbus A319-100',\n 'more than 19,000 students',\n '1-20 ( 23 )',\n 'slight frame , weighing just 70 kilograms',\n '8.1 percent',\n '82.684 miles',\n '760',\n '50,000 square meters',\n '7,866',\n '4',\n '22-16-3',\n '5',\n '3',\n '9',\n '12',\n '143',\n '2',\n '34,000',\n '7,500',\n '6',\n '8',\n \"Women 's freestyle 48 kg\",\n '2,954 meters',\n '3,776.24 m',\n '2',\n '10',\n '25,000',\n '2',\n '1 ( 120 )',\n '51 feet long',\n '6',\n '2',\n '$ 103.2 million',\n '968',\n '2.09',\n '25',\n '55',\n '572',\n 'CR 132',\n '20%',\n '505,526',\n '9,000',\n '4',\n '60',\n '9',\n '29 teams from the United States and 1 from Canada',\n '5.6 million',\n '55,000',\n '90',\n '$ 1.59 billion',\n '22,970 square kilometres',\n '47,803.7',\n '4 million',\n '128,624 inhabitants',\n 'between 14.5 and 17.4 million',\n '7,500',\n '5',\n '570',\n 'between 18 and 24° C',\n '2.6 nautical miles',\n '44 races',\n '81',\n '17',\n '2',\n '2',\n '50,782',\n '250 feet',\n '76 points',\n '715 miles',\n 'Thai League 2',\n '634,300',\n '979',\n '3',\n '10 miles',\n '2,193',\n '512358',\n '8',\n '1',\n 'between 60 and 64 kilograms',\n '3 ft 4 in',\n '10,768',\n '5',\n '18',\n '25-44',\n '37',\n '278',\n '3 times',\n '14',\n '12',\n '26,512',\n '5608',\n '26,700',\n '35,938',\n '656-acre',\n '500 m',\n '266',\n '2',\n '7,536,383',\n '30 national championships',\n 'Highway 25',\n '4',\n '27,000',\n '17,448',\n '93,348',\n '$ 240',\n '1.428 billion',\n '368',\n 'over 500,000 copies',\n '$ 450,000',\n '19-22',\n '70',\n '4',\n '1',\n '10 km',\n '21,000',\n '104',\n '45,750',\n '$ 1.4 billion',\n '3',\n 'around 600 B.C',\n '5,735',\n '2.655 million',\n '12',\n '501.66',\n '15,133',\n '1,614',\n '5.7 square miles',\n '22.42',\n '11',\n '147',\n '# 91000775',\n '38.245',\n '7,289',\n '2',\n '5',\n '619',\n '19,000',\n '24,000',\n '15',\n '34,533',\n 'US $ 476 million',\n '110',\n '20 million',\n '5',\n '6',\n '200',\n '18',\n '903,366.2',\n '5',\n '2',\n '21 seasons',\n '3',\n '21',\n '42',\n 'Finding Teddy 2',\n '7',\n '14',\n '309 beds',\n '1,081',\n '41°49′51″N 90°50′20″W / 41.830833°N 90.838889°W / 41.830833 ; -90.838889 ( City Hotel )',\n '9.2 million',\n '114,922',\n '7,163,416',\n '85,000',\n '99.72',\n '4',\n '4',\n '117 mi',\n '1,200',\n '4.5×40mmR',\n '506,535',\n 'Mortal Kombat 9',\n '2.9 million',\n '4,883 points',\n '3',\n '10',\n '2',\n '316,000 listeners',\n '3',\n '631,313',\n '539 BCE',\n '1,521',\n '20',\n '18.25',\n '3',\n '0',\n '88 km',\n '9000',\n '7',\n '4',\n '20,600',\n '3',\n '50 miles',\n '17-county area',\n '15',\n '0',\n '6p21.3',\n '0',\n '110',\n 'Sesame Street 4-D Movie Magic',\n 'Avengers # 9',\n '8,987',\n '1,174,209',\n '20',\n '492 m',\n '80% of its staff',\n '10',\n '17,606',\n '43',\n '8',\n '34',\n '8,500',\n '11.7',\n '15',\n '86 episodes',\n '3,933',\n '1,630',\n '2',\n '302 Calumet Avenue',\n '1 , 2 , 3',\n '581 feet',\n '65',\n '8,540',\n '13',\n '13',\n '43,013',\n '8,029',\n '107 mm',\n '$ 5,490,187',\n '742',\n '6',\n '3',\n '23',\n '26',\n '20',\n '9.5 million',\n '112,525',\n '48',\n '476,179',\n '1,178 feet ( 359 m )',\n '4',\n '$ 675,000',\n '8,020',\n '35,715',\n '142',\n '6',\n '22.6-mile-long',\n '40%',\n 'Up to 250 aircraft of all types',\n '5579 sq mi',\n '50-mile',\n '4000',\n \"6 ' 0\",\n '20',\n '16,686',\n '30',\n '2370-2371',\n '4.5 million',\n '57',\n '251 registered members',\n '7',\n '1,961,269',\n '500',\n '264,091',\n '5',\n '70',\n '2',\n '146.7 million',\n '1.47 million',\n '749',\n '5',\n '1,827,165',\n '4',\n '885,287',\n '190 miles',\n '300,000',\n '36 states',\n '$ 900',\n '50 km',\n '183.64',\n '19',\n '8.95',\n '806 km',\n '50 centimetres',\n '32',\n '10,277',\n '34,240',\n '357,386',\n '240,342',\n '8.595',\n '18',\n '10.2',\n '100 and 200 metres',\n '57,449 people',\n '12,491,300',\n '5 MW',\n '57 million yuan',\n '10,942',\n '7',\n '242',\n '2,238,537',\n '5',\n '3',\n '25',\n '74',\n '44',\n '8,000',\n '4',\n 'Bill C-168',\n '5,642',\n '9.837',\n '3,300 acres',\n '2',\n '$ 90 million',\n '9.575',\n '5,500',\n '2',\n '17,000',\n '21 Avondale St',\n '14,462',\n '4 - 3',\n '100',\n '+8.37%',\n '13',\n '17,317',\n '71',\n '10.8%',\n '14']" - }, - "execution_count": 49, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "digit_answers" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T18:15:56.691142Z", - "start_time": "2024-04-12T18:15:56.686177Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/research/notebooks/open-model-eval.ipynb b/research/notebooks/open-model-eval.ipynb deleted file mode 100644 index 8059db82..00000000 --- a/research/notebooks/open-model-eval.ipynb +++ /dev/null @@ -1,316 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 1, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "04/12/2024 13:02:54 - INFO - datasets - PyTorch version 2.2.0 available.\n", - "04/12/2024 13:02:54 - INFO - datasets - Apache Beam version 2.50.0 available.\n" - ] - } - ], - "source": [ - "import json\n", - "from collections import Counter\n", - "import pandas as pd\n", - "from research.constants import EvalField\n", - "from research.utils.hybridqa.hybridqa import hybridqa_metric_format_func\n", - "from datasets import load_metric\n", - "import copy\n", - "import re\n", - "import seaborn as sns\n", - "import matplotlib.pyplot as plt" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:03:09.544800Z", - "start_time": "2024-04-12T17:02:40.867171Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 12, - "outputs": [], - "source": [ - "with open(\"outputs/new-paper-results/hybridqa/gpt-4-blendsql-starcoder2/rerun-predictions.json\", \"r\") as f:\n", - " starcoder = json.load(f)\n", - "with open(\"outputs/new-paper-results/hybridqa/gpt-4-blendsql-only-run5/rerun-predictions.json\", \"r\") as f:\n", - " gpt4 = json.load(f)\n", - "with open(\"outputs/new-paper-results/hybridqa/gpt-4-blendsql-deepseek-coder/rerun-predictions.json\", \"r\") as f:\n", - " deepseek = json.load(f)\n", - "results = {\n", - " \"starcoder\": starcoder,\n", - " \"gpt4\": gpt4,\n", - " \"deepseek\": deepseek\n", - "}" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:05:13.839260Z", - "start_time": "2024-04-12T17:05:12.915098Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 36, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/a720504/miniconda3/envs/blendsql/lib/python3.9/site-packages/datasets/load.py:848: FutureWarning: The repository for hybridqa contains custom code which must be executed to correctly load the metric. You can inspect the repository content at ./metrics/hybridqa/hybridqa.py\n", - "You can avoid this message in future by passing the argument `trust_remote_code=True`.\n", - "Passing `trust_remote_code=True` will be mandatory to load this metric from the next major release of `datasets`.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "def is_empty_blendsql_pred(p: list):\n", - " if len(p) == 0 or (\n", - " any(x in str(p[0]) for x in [\"table\", \"passage\", \"text\"])\n", - " and any(x in str(p[0]) for x in [\"not\", \"empty\"])\n", - " ) or str(p[0]).strip() == \"\":\n", - " return True\n", - " return False\n", - "\n", - "def cleanup_error(error: str, prediction: list):\n", - " if error is None:\n", - " if prediction in [[], ['']]:\n", - " return \"No Results\"\n", - " return error\n", - " error = error.lower()\n", - " if error == \"empty subtable passed to qaingredient!\":\n", - " return \"No Results\"\n", - " if error.startswith(\"the model attempted to generate\"):\n", - " return \"Constrained Decoding Error\"\n", - " elif any(x in error for x in {\"no such column\", \"unknown column\"}):\n", - " return \"Hallucinated Column\"\n", - " elif \"fts5: syntax error\" in error:\n", - " return \"Generic Syntax Error\"\n", - " elif \"maximum context length is\" in error:\n", - " return \"Context Overflow\"\n", - " elif any(x in error for x in {\"error tokenizing\", \"unbalanced parenthesis\", \"ambiguous column name\", \"syntax error\", \"alias already used\", \"unterminated subpattern\", \"invalid expression\", \"invalid format\", \"duplicate column name\"}):\n", - " return \"Generic Syntax Error\"\n", - " elif error.startswith(\"expecting\"):\n", - " return \"Generic Syntax Error\"\n", - " elif \"the response was filtered due to the prompt triggering\" in error:\n", - " return None\n", - " elif error[0] == \"'\" and error[-1] == \"'\":\n", - " return \"Generic Syntax Error\"\n", - " elif error.startswith(\"expected }\") or 'unrecognized token: \"{\"' in error:\n", - " return \"Generic Syntax Error\"\n", - " elif \"no such function\" in error:\n", - " return \"Generic Syntax Error\"\n", - " elif \"execution failed on sql\" in error:\n", - " return \"Generic Syntax Error\"\n", - " elif \"required keyword\" in error:\n", - " return \"Generic Syntax Error\"\n", - " elif re.search(r'\\{\\{[a-z]\\(\\)\\}\\}', error):\n", - " return \"Generic Syntax Error\"\n", - " return \"Generic Syntax Error\"\n", - "\n", - "m = load_metric(path=\"./metrics/hybridqa/\")\n", - "metric_format_func = hybridqa_metric_format_func" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:53:53.263112Z", - "start_time": "2024-04-12T17:53:53.239129Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 4, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/k_/rqtwz3wd3g19jrnz5dkt4d609b2zbl/T/ipykernel_68562/2857013869.py:43: FutureWarning: load_metric is deprecated and will be removed in the next major version of datasets. Use 'evaluate.load' instead, from the new library 🤗 Evaluate: https://huggingface.co/docs/evaluate\n", - " m = load_metric(path=\"./metrics/hybridqa/\")\n", - "/Users/a720504/miniconda3/envs/blendsql/lib/python3.9/site-packages/datasets/load.py:848: FutureWarning: The repository for hybridqa contains custom code which must be executed to correctly load the metric. You can inspect the repository content at ./metrics/hybridqa/hybridqa.py\n", - "You can avoid this message in future by passing the argument `trust_remote_code=True`.\n", - "Passing `trust_remote_code=True` will be mandatory to load this metric from the next major release of `datasets`.\n", - " warnings.warn(\n" - ] - } - ], - "source": [ - "for name, predictions in results.items():\n", - " print(f\"HybridQA {name} results:\")\n", - " for item in predictions:\n", - " _item = copy.deepcopy(item)\n", - " if is_empty_blendsql_pred(_item[EvalField.PREDICTION]):\n", - " _item[EvalField.PREDICTION] = \"\"\n", - " m.add(**hybridqa_metric_format_func(item))\n", - " print(json.dumps(m.compute(), indent=4))\n", - " print()" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T02:55:20.541707Z", - "start_time": "2024-04-12T02:55:20.471770Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 38, - "outputs": [], - "source": [ - "# Create error df\n", - "df_list = []\n", - "for name, predictions in results.items():\n", - " cleaned_errors = filter(lambda x: x is not None, [cleanup_error(i[\"error\"], i[EvalField.PREDICTION]) for i in predictions])\n", - " for error_type, count in Counter(cleaned_errors).items():\n", - " df_list.append(\n", - " {\n", - " \"Model\": name,\n", - " \"Error Type\": error_type,\n", - " \"Count\": count\n", - " }\n", - " )\n", - "df = pd.DataFrame(df_list)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:53:58.625037Z", - "start_time": "2024-04-12T17:53:58.623046Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 26, - "outputs": [ - { - "data": { - "text/plain": " count\nmodel \ndeepseek 0.498413\ngpt4 0.234921\nstarcoder 0.552092", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
count
model
deepseek0.498413
gpt40.234921
starcoder0.552092
\n
" - }, - "execution_count": 26, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df.groupby(\"model\").sum(\"count\") / len(predictions)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:50:34.061414Z", - "start_time": "2024-04-12T17:50:34.050435Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 27, - "outputs": [ - { - "data": { - "text/plain": " count\nmodel \ndeepseek 0.498413\ngpt4 0.234921\nstarcoder 0.552092", - "text/html": "
\n\n\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
count
model
deepseek0.498413
gpt40.234921
starcoder0.552092
\n
" - }, - "execution_count": 27, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df[~df['error_type'].isin(('empty_results', 'openai_content_management', 'context_overflow'))].groupby(\"model\").sum(\"count\") / len(predictions)" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:50:34.655884Z", - "start_time": "2024-04-12T17:50:34.655195Z" - } - } - }, - { - "cell_type": "code", - "execution_count": 45, - "outputs": [ - { - "data": { - "text/plain": "
", - "image/png": "iVBORw0KGgoAAAANSUhEUgAABPsAAAQXCAYAAACJYm7OAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjcuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8pXeV/AAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdeZhXZd0/8PfMMOwCiiwuuOGK4YaBYomKitrTk0uaaIrLk2S2aLmvuaUtlhnmmpG4hGuaqVGKGkIqiIoroAgKypYCsjPz/f3hb6ZoBgVBlsPrdV1cI+d87nN/zvfrPNfju/ucu6xUKpUCAAAAAKzxyld1AwAAAADAiiHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAAqiwapugPqNHDkypVIplZWVq7oVAAAAAFahhQsXpqysLDvvvPOn1gr7VlOlUimlUmlVtwEAAADAKrYsGZGwbzVVs6Kvc+fOq7gTAAAAAFalUaNGLXWtd/YBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCaLCqGwAAAABYFqVSKQsXLkx1dfWqbgWWSXl5eSorK1NWVva5zSHsAwAAANYIVVVVmTZtWmbOnJkFCxau6nbgM2nYsDItWrTI+uuvn4qKihV+fWEfAAAAsNqrqqrKO++8kzlz5qZJk2Zp3rxVystXfFACn6fq6qrMmzc306f/K3Pnzk2HDh1WeOAn7AMAAABWe9OmTcucOXPTunW7NGzYaFW3A59Z48ZN07Rp80yfPjnTpk1Lu3btVuj1bdABAAAArNZKpVJmzpyZJk2aCfoohIYNG6Vx42aZOXNmSqXSCr22sA8AAABYrS1cuDALFixM48ZNVnUrsMI0adIkCxYszMKFK/b9k8I+AAAAYLVWs+uud/RRJDX/Pq/oXaWFfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAsBqZNGlSdtttl9o/V131s6Uad/vtt9aO+epXD/icu0xuuun67LbbLjnppBNWyPUOPvgr2W23XfLAA/evkOutrYR9AAAAAKuxJ554LKVS6VPr/v73QSuhG1Z3wj4AAACA1VRFRYNMnTo1L774wifWTZz4bl577dWV0xSrNWEfAAAAwGpq1113TZI8/vjfP7GuZlXf1ltv+7n3xOpN2AcAAACwmurZc/8kyeDBn/wo79//Pijl5eXZd9/9VlZrrKYarOoGAAAAAKjfTjvtnPXXXz9Tp07JSy+9mB133KlOzfjxb2fMmNHZddcvZr31Wi/xWq+99moGDrwjI0c+n+nTp6Vp06bp2HGrHHTQV3LQQV9NRUVFnTHV1dV5+OE/54EH7s+4cW8lSXbYYad861vf/tTeR44ckbvu+mNeeunFzJjxYdZZp0U6d94hRxxxZHbdtevSfwgsE2EfAAAAwGqqrKwse+3VM/fcMzCPP/63esO+mkd499231xKvM2BA/1x3Xb9UV1enWbPm2WqrrfPhhx9m5MgRGTlyRB5++C/5+c9/mebN16kds3Dhwpx//tl58snBSZKNN944TZs2z7PP/jPPPvvPdOr0hSXOd+2112TAgP5JkhYtWmTLLbfK1KlT89RTT+Spp57IN7/ZJ9/97g8+wyfCp/EYLwAAAMBqrObR3MGDH6/3Ud6//31QGjRokL337lnv+McffyzXXntNqqurc8IJ38ojj/w9v//9bbn//ody7bU3ZL31WmfkyBG5+OILFxt3221/yJNPDk7z5s1zzTXX5Z57Hsytt96R++//Szp33jEvvfRCvfPdf/89GTCgf9ZZZ538+MeXZdCgJ9K//+156KG/5tJLr0iTJk1y221/yIMP/mm5PhfqJ+wDAAAAWI3tuOPOadOmbaZMmZxRo15a7NzYsWMybtxb6dp1t7Rs2bLe8TfccG2S5OCDD81JJ52chg0b1p7r0uWL+elPf5Ek+cc/nswLL4xMkixatDC3335rkuS0085I167dase0adMmP/3pVWnZslWduRYuXJibbrohSXLeeRflgAMOqj1XVlaW/fbrle9+99QkyU03XZ9FixYty0fBUhD2AQAAAKzGysrKss8+H6/ae/zxvy12ruYR3v3227/esRMmTMj48W8nSb7xjaPqrencecd07rxjkuSpp55Ikrzwwsh89NFHadSoUfbbr+7jwS1atKh3M5CXXnox//rX9DRt2ix77rlXvfMdcMCBKS8vz9SpU/LGG6/XW8Nn5519AAAAAKu5nj33z8CBd+bxxx/LD37wo5SVlSVJHnvsb2nUqFH23HPveseNHz8uSdK4ceNsvvkWS7z+tttul1GjXqwNBsePH58k2XjjDoutBPxPW221TZ1jb701NsnHKwO//e3/W+J85eXlqa6uzvjxb2f77Zf87j+WnbAPAAAAYDXXufMOadeufSZPfj8vv/xSOnfeMa+//lreeWdC9t67Z5o1a1bvuNmzZydJmjVr/onXrxk/Z86cJMmsWTOTJE2aNFnimBYtWtQ59tFHHyVJFixYsMR3+v2nWbNmfWoNy0bYBwAAALCaKysry95798wf/3h7Hnvs7+ncecf/eIR3ybvwNm36cYg3e/ZHn3j9mnCvadOmSVL7/r+asLA+8+fPq3OsJhzcdtvt0r//7Z84J58P7+wDAAAAWAP8e1fex1IqlfLYY39L06ZN0737l5Y4ZrPNNkuSzJs3L+PGvbXEutdeey1J0qHDJkmSTTb5eNw770zI3Llz6x3z1lt1r1czbsKECUvcfKNUKmXEiOcyYcKELFy4cIk98dkI+wAAAADWAF/4wg5p336DTJ78fu65Z2Dee29SvvzlHmncuPESx2yyyabZdNPNkiQDB95Rb81LL72YV199OUmy++57JEl22mnnrLvuelm0aFEefPD+OmPmzZubQYMerXN85513SfPmzTNnzuw89NCD9c73178+klNO6Zsjjzw0kye//4n3zLIT9sFaoLq6elW3sEqsrfcNAAAU1z777Jskue66a5N88iO8NU466eQkyZ/+dF9uuum6LFiwoPbciBHDc845ZyRJdtute7p27ZYkqaioSN++H4/77W9/k7/97a+1Y2bM+DDnnXdWvUFdkyZNcuyxJyRJfvWrX+Shhx5Y7L/NnnrqifzsZz9JkvTsuV823rjDUt45S8s7+2AtUF5enhuevDWTZkxe1a2sNBu2bJe+PY5d1W0AAACsUD177pc77hiQOXNmp0WLFtltt92Xasy7776T66+/Nr/73U354x/vzKabbpoPPvgg7703KUmy885dcvHFl9Xu8pskBx98WMaOHZt77hmYCy44J9de++usu+56eeutN7NgwYL06LF3nnxycJ35jjmmTyZOfCcPPHB/Lrvs4vTr9+tsuOFGmTp1SqZOnZok2WGHnXLuuReuoE+F/yTsg7XEpBmTM376u6u6DQAAAJbD9tt/IRtuuFEmTZqYHj32SYMGlUs1rk+fE7Lrrl0zcOCdeeGF5zNmzOg0b75Odt21aw466H9ywAEHpby87gOgp59+Vr74xa65++4/ZvTo0ZkxY1y2265Tjj/+W/nXv6bXG/aVlZXlnHMuyD777Jv77rsnL788Km+88UYaNWqUL3yhc/bf/8Accshhqaxcut5ZNmWlUqm0qpugrlGjRiVJOnfuvIo7oSguevDna1XYt2nrjXPx/56xqtsAAABWgHnz5uXNN9/K+uu3T8OGjVZ1O7BCLFgwP9OmvZ+OHbf4xPcuJsuWE3lnHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIBqs6gYAAAAAVoSysrKUl5et6jaWWXV1KaVSaZX2UCqVUla25n121CXsAwAAANZ4ZWVlWWedxqmoWPMeYqyqqs6sWfNWSeD3/vvv5Te/uTqHHXZEdtmly0qff2W56abr87vf3Zivf/2InH762au6nc+VsA8AAABY45WXl6WiojzX3vl0Jk6ZsarbWWobtW2ZU3rvkfLyslRVrfyw78wzf5TRo1/PoYcevtLn5vMh7AMAAAAKY+KUGXl74geruo01RqlUvapbYAVb89a2AgAAAAD1srIPAAAAoGCeeWZY7rrrzrz55thMnz49LVq0TOfOO+Qb3+idnXfukhEjhueUU06qra/552uvvTFduuyaJPnggw8ycOAdGTbs6Uyc+G7mzp2XFi3Wybbbdsrhh38j3bt/abE5d9ttl2y88ca57LIr85OfXJpx497Keuu1ztlnn5fdd98jSTJu3Fv54x9vz7PPPpN//Wt6WrdeP50775Djjjsxm2++xWLXW7hwYe6996789a+P5O23x6W6upSNN944PXvulyOPPDpNmzatc99vvjk2f/jDLXn++eGZNeujbLnlljnuuBM/8bOaNGlSBgz4ff75z2GZNm1qmjVrlh122CnHHNMnnTvvuFhtzbv/LrzwkowdOyYPPnh/qqurs/POXXLVVb9eym/n87VGrux7++23s9NOO+WSSy6p9/zcuXPz29/+Nl/72tey0047ZYcddsiBBx6YX/ziF/nwww/rHbNo0aIMHDgwhx56aHbZZZd07do1J554Yp555pkl9jFjxoxcddVVOeCAA7LDDjvkS1/6Us4+++y88847K+I2AQAAAJbZoEGP5tRTv5thw4amXbv2+fKXe6Rt27Z54onH853vnJS//31Q1ltvvfTqdWBatGiRJPniF7ulV68Ds9566yVJ3n33nRx77JHp3/93mTlzZnbeuUt22233VFY2zLBhT+eHP/x+HnnkL3XmnjXro5x22vcyZ87s2jBwu+06JUmefHJwjjvum3nggfvTpEmT7LHHl9O0adP89a+P5LjjvpmXX36p9joffTQrffuekKuvvirjx7+dnXfukm7ddsvUqVNz443X5fjjv5kpU6YsNvdzzz2T//u/Phk06NG0arVu9tjjS5k5c2bOOOO0DB78WL2f1YgRw3PMMUfm/vvvTUVFebp3/1I6dNg0//jHk+nb98T86U/31jvuD3+4JQMH3pkddtgpHTtulc0222zZvqTP0Rq3sm/atGn5zne+k7lz59Z7/sMPP8wxxxyT0aNHZ5111snOO++cioqKjBo1KjfddFMefvjh3HHHHWnfvn3tmFKplLPOOisPPfRQWrZsme7du+fDDz/M0KFD8/TTT+fyyy/PYYcdVmeeo48+OmPHjs0mm2ySvfbaK+PGjcv999+fv/3tb7n99tuz7bbbfq6fBQAAAMB/u/HG36ZUKuXqq/ulW7fda4//6U/35sorL89NN12fgQPvy8UXX55jjjkyM2fOzHHHnVi7oi9J+vX7daZOnZpDDz08Z5xxdsrKypJ8vFjqqqt+mvvvvzd33XVnDjzwK4vNPWPGh9lxx51y7bU3pEGDylRXV6e8vDzTp0/LZZf9OAsWzM8551yQr33tkNoxt99+a37zm6tz8cUX5u67/5Qk+elPf5JXX30lnTvvmJ/97JdZd911kyRz5szJxRdfkCefHJwLLjg7N9xwS5Jk3ry5ufTSH2fu3Ln53vdOzdFHH5skqa6uzrXXXpPbb7+1zuc0Y8aMnHvumZk9+6P88Idn5vDDv1F7ny+8MDKnn/6D/PznP812222fbbZZPOMZP/7t/PKXv0n37nvUzrO6WKNW9r322ms56qij8uabby6x5uc//3lGjx6drl27ZtCgQfn973+fm2++OX/729/y5S9/ORMnTswFF1yw2Jh77703Dz30ULbbbrsMGjQo/fr1y2233ZZbbrklDRs2zMUXX5z33ntvsTE/+clPMnbs2Bx66KF59NFHc8011+TPf/5zTjvttHz00Uc544wzVqsvGgAAAFg7TJ06LUnStm27xY7/7/8ektNOOz3f/vYpKZU+eeffNm3aZrfdds+3v31KbQCWJA0aNKjduXfSpEn1jv3617+RBg0qkyTl5R9HTw8//FBmzZqVnj33WyzoS5Kjjz42O+ywU1q1WjdTpkzO5Mnv5+9/H5TKyspcfvlPa4O+JGnatGl+/OPLst56rfPiiy/khRdGJkmeeurJTJkyOTvssFNt0Fcz/ymnfD8dO25Zp88///lPmTHjw+y3X68cccSRi93nTjvtnOOOOzFVVYtyxx0D6ozddNPNaoO+/7zP1cHq08knmDFjRn7+85/niCOOyPjx47PxxhvXWzdv3rw89NBDSZIrr7yydulpkrRo0SI//elPU1ZWln/84x/54IN/78xzww03JEnOP//8tGrVqvb47rvvnj59+mT+/PkZMODfX+y7776bhx56KC1atMh5552XioqK2nPf/va3s9NOO2X06NH5xz/+sULuHwAAAGBp1azQO+WUvvnNb36V4cOfzYIFC1JeXp5vfOOo7L13z8WCrfr86Edn5uqrr619zDdJZs+enZdffqn2kdiFCxfUO3brrbepc2z48GeTJHvuuVe9Y2688ZbcdNPv07Ztuzz//IiUSqXsuOPOadu2bZ3aJk2aZM89eyRJRox4brGfX/rSnnXqy8vLs9de+yyxp65du9Xb0x57fHmxa/+n+u5xdbFGPMZ766235uabb0779u1z0UUX5ZVXXkm/fv3q1E2fPj3bb799SqVSNtpoozrnW7dunZYtW+bDDz/MlClTsu6662bs2LGZMGFCWrdunS5dutQZ06tXr9x444157LHHcuaZZyZJnnzyyVRVVaV79+5p3rx5vWNeeOGFPPbYY+nRo8cK+AQAAAAAls4551yQc889I6NGvZTbbx+Q228fkMaNG2fXXbtm//0PyL777r9UK9HGj3879913d1555eVMnPhu7cKpmqBwSasD11mnRZ1jU6dOTZK0a9e+zrn/Nm3ax7Ubblg326mx4YYbL1b77+u3q7e+voVj77//fpLk8ssvyeWX178vxMdzTMuiRQtrVysm9d/j6mKNCPvat2+fs846K0cddVQaN26cV155pd66jTbaKHfccccSrzN+/Ph8+OGHKS8vr/3yR48enSTZaqut6k21a45PmDAhc+fOTZMmTWrHbL311vXOs+WW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q1atcsMNt+See+7KoEGP5Pnnh6e8vCIbb9whRx/dJ4cf/o06/7237bbbpX//2/KHP9ySp58ekiFDnkqHDpvkggsuTnl5eV566cWUlS3e25e/vGcGDPhjbrvt1jz77D/zz38OTfPm6+QLX+ic//3fQ9Kr14GLhbA1n9t/X+ezKC8vS1lZeZo0afKJr5f7z3mXRiHDvgkTJuS4447LxIkTs/HGG+f666/PVlttVW9tzSO9Y8aMqff8mDFjUiqVsskmm9QGb582piZt/c/HhQEAAIDPT3V1KVVV1Tml9x6rupVlVlX18fsIV6QvfKFzrrji559a17Zt21x9db86x5s1a56TT/5eTj75e/WO+9nPflnn2D//+fynzrfVVlvnkkt+8ql1ycd7KBx99DE5+uhjlqo+Sdq33yBnnXVevecOPPAr9R7fZJNNc+65FyzV9b/1rW/nW9/69lL3syoULuybPHlyjj322Lz33nvZYYcdcv3116d169ZLrN90002z2Wab5e23384LL7yQnXbaabHzjz76aJJkr732qj225557pry8PEOGDMmcOXPqrL7761//miTZe++9V8xNAQAAAJ+oVCpl1qx5KS9f815l9PFGDV4/xIqxZj/QXo8zzjgj7733XrbZZpv079//E4O+Gscee2yS5IILLsi0adNqjw8bNiy33nprKisrc8IJJ9Qeb9euXfbbb798+OGHueiii7JgwYLaczfccENeeOGFbLnllunZc/GtqQEAAIDPT6n08eq+Ne2PoI8VqVAr+55++uk888wzSZJ11lknF1100RJrf/CDH6RDhw5Jkt69e+epp57KE088kV69eqVbt26ZNWtWhg8fnlKplCuvvDIbbLDBYuPPP//8vPrqq3nwwQczfPjwdO7cOePGjcvo0aPTokWL/PKXv1zrN0YAAAAAYOUqVNg3ePDg2n8ePnz4J9b26dOnNuwrLy9Pv379ctttt+W+++7LkCFD0rx583Tv3j19+/ZN165d64xv27Zt7rrrrvz2t7/N448/nscffzxt27bNIYccklNOOaX22gAAAACwspSVrBVdLY0aNSpJ0rlz51XcCUVx0YM/z/jp767qNlaaTVtvnIv/94xV3QYAALACzJs3L2+++VbWX799GjZstKrbgRViwYL5mTbt/XTsuMWn7sa7LDlR4d7ZBwAAAABrK2EfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBANVnUDAAAAACtCWVlZysvLVnUby6y6upRSqbSq26AghH0AAADAGq+srCwtWjRKeXnFqm5lmVVXV2XmzPlrZOBXKpVSVrbmBaxFJuwDAAAA1njl5WUpL6/IuIduytzp763qdpZak9YbZPP/+VbKy8tSVbXmhH0ffvhBbrzx+nTuvEMOPPArn1i7aNGifPvbJ+bll0fl2mtvTJcuu66kLtdOwj4AAACgMOZOfy9zJ09Y1W0U3hVXXJYnnxycTp22/9Tam2++IS+/PGoldEVigw4AAAAAltHSPnI8cuSI3Hrr7z/nbvhPwj4AAAAAVriZM2fmxz++IG3bts1GG228qttZawj7AAAAAAro4YcfyoknHpuePffMfvv1yBlnnJYxY0bn8ssvyW677ZIRI4YnSU4++VvZbbddMmHChAwceGeOOOKQ7LnnbjnkkP/JVVf9LNOmTa295qRJk7LbbrvkqaeeSJJcdtmPs9tuu+Shhx6sM/+VV16WqVOn5MILL03Tpk1Xyj3jnX0AAAAAhXPppRflL3/5cxo2bJhddumSBg0qM2LE8HzrW8elQ4dN6h3Tr9/VeeqpJ7LVVlvnS1/aMy+/PCp33/3HPPnk4Fx77Q3p0GGTNG3aJL16HZiRI5/PlCmT07nzDtlww42y8caLr9x78ME/5fHH/55jjz0+u+zSZWXcMv+fsA8AAACgQB599OH85S9/Tvv27XPNNddnk00+Dvf+9a9/5fTTf5BXX32l3nFPPfVEfvjDM3PEEUcmSRYsWJDLLvtxBg16NFdeeVmuvfbGtGq1bi6++PKceeYPM2XK5Hzta4fmf/7nfxe7zoQJ4/OrX/0822yzbU466duf781Sh8d4AQAAAArkjjtuS5KcfvrZtUFfkqy33nq55JKfpLy8/jho77171gZ9SdKwYcOce+4FadmyVUaMGJ633nrzU+detGhhLrzw3JRKpVxyyU/SoEHlct4Ny0rYBwAAAFAQH3zwQUaPfj2NGzfO7rvvUef8xht3yHbbdap37P77H1DnWOPGTdKt2+5Jkueee/ZT57/uun55/fXX8v3v/zCbbrrZsjXPCiHsAwAAACiIyZPfS5K0adM2FRUV9dZsuOFG9R7fZJNN6z3evn37JMnUqVM+ce7nnnsmd9xxW/bY48s59NCvL23LrGDe2QcAAABQEIsWLUqSlEqlJdYs6dySHu+tqV9SeFjjV7/6RUqlUqqqFuWii85b7Nzkye8nSfr3/10efPD+7LXXPtl7756feD0+G2EfAAAAQEG0a7dBkmTq1KmpqqqqN6CrCd7+25QpU7L55lvUOT5p0sQkyQYbbPCJc8+ZMydJ8s9/DltizXPPPZPk48eJhX2fD2EfAAAAQEG0adMmm222ed5+e1yeeeaf6d598ff2TZkyOa+++mq9Y59++h/p1m23xY7Nnj07zz77zyTJbrt1rz1eVlZWZ/yf/vSXJfZ1zDFHZsyY0bn22hvTpcuuS30/LDvv7AMAAAAokKOPPiZJctVVP83Eie/WHp81a1Yuuuj8VFV9/Kjvfwd29913d4YOfbr27/Pmzcull16UWbNmZd9990/79v9e2dewYcP/f82Zn9t98NlY2QcAAAAURpPWn/yo6erm8+j3f/7na/nnP4flscf+lqOOOjw779wljRo1ysiRI1JdXZ111103H3zwQRo0WDwWat16/fzwh9/LDjvslNatW+ell17M9OnTsvXW2+RHPzprsdoOHTZJkvzudzfmxRdfyEEH/U/23HOvFX4vLDthHwAAALDGq64upbq6Kpv/z7dWdSvLrLq6KtXVS95QY1mVlZXlkkt+kp133iUPPvinjBz5fCorG2TXXbvm5JO/m3PPPTMffPBBmjdvvti47373B5k0aWIeeOD+vPHGa2nffoMcdtgR6d376DRp0mSx2qOPPiYTJozPsGFDM2zY0Gy66WbCvtWEsA8AAABY45VKpcycOT/l5XXfJbe6q64ufeLuuctqzJjRadmyZQ477Ih8/evfWOzcwoULM2XK5FRUVGTDDTdc7FxFRUX69Dkhffqc8KlzNGvWPJddduVS9zRgwB+XupblI+wDAAAACqFUKqWqasWFZmuqq676aV54YWTOOeeCfO1rh9Qer66uzm9/+5vMmjUre+zx5TRu3OQTrsKaStgHAAAAUCDHHHNcRo0alSuuuDR33XVnNtlk0yxcuDBvvPFapk6dmg033ChnnXXeqm6Tz4mwDwAAAKBA9tjjy7nllltzzz0DM3Lk8xk27OlUVDTIhhtumK997dD07n10mjVr/ukXYo0k7AMAAAAomG222TbnnXfRUtVed91Nn3M3rEzlq7oBAAAAAGDFEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCaLCqGwAAAABYEcrKylJeXraq21hm1dWllEqlVd0GBSHsAwAAANZ4ZWVlWadF41SUr3kPMVZVV2fWzHkrLfDbbbddkiSPPvpYWrVad6XMWRSXXHJRHn74z/ne907N0Ucfu6rbqZewDwAAAFjjlZeXpaK8PDc8eWsmzZi8qttZahu2bJe+PY5NeXlZqqqs7mP5CfsAAACAwpg0Y3LGT393VbcBq8yat7YVAAAAAKiXlX0AAAAABVNVVZV77707Dz30QCZMGJ9mzZpn7733yUknfafe+lKplIcffih//vOfMmbMmCxatDAbbbRx9t13//Tu/c00adKkzpjZs2dn4MA78thjf8u7776bioqKbLXVVjn44ENzwAFfSVnZvzdLGTFieE455aT06nVg/u//+uY3v7k6zz8/PEnSseOW+cY3js4++/SsM8czzwzLXXfdmTffHJvp06enRYuW6dx5h3zjG72z885d6tR/8MEHue22/vnHP57K+++/l0aNGmW77bbPkUcele7dv1TvvY8dOyYDBvTP888Pz4cffpgWLVqmS5cv5vjjT8zmm2+xVJ/300//I2effXqqq0u55JLL07Pnfks17vMg7AMAAAAokKqqqpx55ml5+ukhadKkSXbdtWsWLVqYBx/8U0aOfL7e+nPPPTNPPjk4jRo1TqdOndKiRcu89NKLufHG6zJ48GPp1+/6tGzZqnbM1KlT893v9s348W9n3XXXTZcuu6a6uiojR47MxRdfmGeffSYXXnjJYoFfkrz33nv51reOy4IFC9KlyxczZ87svPDCyLz44hk59tjj853vfK+2dtCgR3PRReelrKwsnTvvkE6dvpD33puUJ554PE8+OTiXXnpF9t13/9r6N98cmx/84DuZNm1a2rRpm65dd8vcuXPy/PPD8+yz/8xxx52Yb3/7lMX6+dvf/ppLLrkwCxcuzBZbdMz223fOe+9NyqBBj+TJJx/P5Zf/NF/60p6f+Hk/88ywnHPOGSmVSrn88iuz1177LMvXtcIJ+wAAAAAK5J577srTTw/JJptsmn79bkjbtm2TJG+/PS7f+97Jder79/9dnnxycLbeettceeUvsuGG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2BwvvvhCbrjhtxk7dsynPqr8jW8clU6dts/UqVNz9dW/+MTalcHKPgAAAIACOfDA/8kLL4zMAw/cn2OOOTJdunwx5eXlGTHiuay//vpZb73W+de/ptfW9+17SsaMGZNnn/1nevf+erbddru0arVu3njjtbz//vtp1Khxrrji54u9u+7SS6/Id7/77Tz88EN5+ukh2W677VJeXpGXXnohH330UTbeeONccMHFdXpr0aJFXnxxZA477KvZaadd8uGHH+SFF0amvLw8P/rRWbU79ZaVleX88y/KD35wSq6++qrcf/+92XzzLTJ37py8+OILmTdvXg488CvZZZcuSZJGjRrliit+ntNO+15uvfX3eeSRh7L11ttk0aJFeeGFkZk/f346dfpCvv/902p72WqrrXPGGWfn5z+/Iueee2Y222zzbLrpZvnXv6bn5ZdHpVQq5dBDD0+vXgd+4uddXl6ec865IMcd98385S9/Ts+e+6/S3XmFfQAAAEBhbNiy3apuYZl8Xv2ec84F2WmnXXLvvXflxRdHpkGDyuy559753vdOzcknn5h//evftQ0bNsyvfvWb/PnPD+SRRx7K2LFjs2jRwrRr1z5f+9ohOfroPtlkk00Wu/5GG22cP/zhjvzxj7fnyScHZ+TIkamsbJANNtgwe+/dM1//+jfq3Z12/fXb5OKLL8+1116TZ54ZlsrKhtljjy/nmGOOy4477rRYbZcuX8z11/8ut99+a0aNejH/+MdTadq0SbbbrlO++tWDc+CBX1msvlOn7XPbbQNz++1/yNNPD8lzzz2bxo2bpGPHLdOr10H52tcOSePGjRcbc/DBh2abbbbNnXfelpEjR+Tpp/+RddddN7vu2jVf//oRdR6DXpKttto63/zmMfnDH36fK6+8LHfccVeaN181j/OWlWq2O2G1MmrUqCRJ586dP7c5zv31w2vVO/s222jd/OQHB63qNlaZix78ecZPf3dVt7HSbNp641z8v2es6jYAAIAVYN68eXnzzbey/vrt07Bh/bumlpWVZZ0WjVOxhN1eV2dV1dWZNXNeihzRjBgxPKecclK22KJj7rjj7lXdzmphwYL5mTbt/XTsuEWdEPK/LUtOZGUfAAAAsMYrlUqZNXNeysvLVnUry6y6ulTooI+VS9gHAAAAFEKpVEpVldCMtduat7YVAAAAAKiXlX0AAAAAfK66dNk1//zn86u6jbWClX0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAVmsNGjRIkixcuHAVdwIrTs2/zzX/fq8owj4AAABgtdagQYM0b94ss2fPTHV11apuB5ZbdXVVZs+emebNm63wsG/FXg0AAADgc9CmTZtMmPBOpkx5L02bNkvDho1TXm4NE2uW6urqLFgwL3PmzE55edK2bdsVPoewDwAAAFjtNW3aNFtssXmmTJmS2bNn56OPZq7qluAzadCgIuus0yxt27ZNw4YNV/z1V/gVAQAAAD4HDRs2zMYbb5xSqZSFCxemurp6VbcEy6S8vDyVlZUpKyv73OYQ9gEAAABrlLKyss9lRRQUgYfbAQAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoiDUy7Hv77bez00475ZJLLllizdChQ3P88cdn9913z84775zDDjssd999d0qlUr31ixYtysCBA3PooYdml112SdeuXXPiiSfmmWeeWeIcM2bMyFVXXZUDDjggO+ywQ770pS/l7LPPzjvvvLPc9wgAAAAAy2qNC/umTZuW73znO5k7d+4Sa+68884cf/zxee6559KpU6d069Ytb775Zs4///yce+65depLpVLOOuusXHjhhXn33XfTvXv3bL311hk6dGj69OmTe++9t86YDz/8MEcddVRuvPHGVFVVZa+99sq6666b+++/PwcffHBef/31FXrfAAAAAPBpGqzqBpbFa6+9lh/84AcZP378EmvGjRuXSy+9NM2bN8+AAQPSqVOnJMmkSZPSp0+f3HfffenRo0cOOOCA2jH33ntvHnrooWy33Xbp379/WrVqlSQZNmxY+vbtm4svvjjdu3fPBhtsUDvmJz/5ScaOHZtDDz00l112WSoqKpIk119/fX71q1/ljDPOyAMPPJDy8jUuTwUA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AAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABREg1XdwOfpySefTP/+/TNq1KjMmzcv7dq1y1577ZWTTz4566+/fp36oUOH5qabbsrrr7+eefPmZYsttsiRRx6Zr3/96ykrK6tTv2jRotx7770ZOHBg3n777TRo0CCdO3fOSSedlG7duq2MWwQAAACAWoVd2ff73/8+J510UoYNG5atttoqPXr0yIIFC3Lbbbfl4IMPzoQJExarv/POO3P88cfnueeeS6dOndKtW7e8+eabOf/883PuuefWuX6pVMpZZ52VCy+8MO+++266d++erbfeOkOHDk2fPn1y7733rqxbBQAAAIAkBV3ZN3HixFx11VVp2LBhbr755tpVdgsWLMiZZ56ZRx55JJdffnluuOGGJMm4ceNy6aWXpnnz5hkwYEA6deqUJJk0aVL69OmT++67Lz169MgBBxxQO8e9996bhx56KNttt1369++fVq1aJUmGDRuWvn375uKLL0737t2zwQYbrNybBwAAAGCtVciVfcOGDcvChQuzxx57LPY4bcOGDXPqqacmSZ555pna4zfffHOqqqpy4okn1gZ9SbLhhhvmwgsvrK35TzVB4fnnn18b9CXJ7rvvnj59+mT+/PkZMGDAir41AAAAAFiiQoZ9FRUVSZIpU6bUOTdt2rQkybrrrlt7bPDgwUmS/fffv0599+7ds84662TUqFGZPHlykmTs2LGZMGFCWrdunS5dutQZ06tXryTJY489tpx3AgAAAABLr5Bh3+67757Kysq88sorueiiizJx4sTMnTs3w4YNyznnnJMkOemkk5J8HP5Nnz49lZWV2WKLLepcq6Kiovb4G2+8kSQZPXp0kmSrrbaqd+OOmuMTJkzI3LlzP5d7BAAAAID/Vsiwr3379vnVr36Vli1b5o9//GP22Wef7LTTTjnuuOMya9as9OvXL717907y79V/66+/fsrL6/842rZtu1htzc927drVW9+oUaO0aNEi1dXVtSsJAQAAAODzVsgNOpKkU6dO2X///XP//fenc+fOadWqVV555ZVMmTIlN954Y7bddtt06NAhc+bMSZI0btx4iddq1KhRktTWzp49e6nH1NR+FqVSqXbOFamsrCxNmjRZ4dddU8ydOzelUmlVt7HS+L7Xru8bAACA4imVSvU+XVqfQoZ9r732Wo4//vg0atQod999d+2mGwsXLswvf/nL3HLLLTnmmGPy8MMP167mW5oPrCYwqHkn4NJ+yJ/VwoUL89prr63w6zZp0mSxjUjWNuPGjVurHq/2fa9d3zcAAADF1LBhw6WqK2TYd9lll+WDDz7Itddeu1jIUVlZmTPPPDMvvvhiRowYkXvuuad2t9558+Yt8Xrz589PkjRt2jRJ0qxZs6UeszwrqiorK7Plllt+5vFL8nmHlKu7zTfffK1a6eX7Xru+bwAAAIpn7NixS11buLBv/vz5ef7551NWVpY99tijzvmysrL06NEjI0aMyMsvv5z//d//TfLxRh1LWhJZ846+mnf31byrb+rUqUvsYebMmSkrK0ubNm0+872UlZXVBoysOGvzI61rI983AAAAa7plWchTuA06Zs6cmerq6pSVldU+bvvfao4vWrQorVq1Srt27bJgwYKMHz++Tm1VVVXeeuutJMk222yz2M8xY8bUe/0xY8akVCqlQ4cOwjoAAAAAVprChX2tW7dOq1atUl1dnSeeeKLemqeffjpJst122yVJevTokSQZNGhQvbWzZs3Ktttum/bt2ydJNt1002y22WaZMmVKXnjhhTpjHn300STJXnvttZx3AwAAAABLr3BhX3l5eXr37p0kufzyyzN69Ojac9XV1enXr1+GDh2aFi1a5LDDDkuSHH300amoqMgNN9ywWHg3adKkXHrppUmSvn37LjbPsccemyS54IILMm3atNrjw4YNy6233prKysqccMIJn8s9AgAAAEB9CvfOviQ55ZRT8vrrr2fw4MH52te+ll122SUtW7bM66+/nokTJ6Zp06b59a9/nfXWWy9J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yYwZM7LPPvvk17/+dSorKxc737lz53z961/P97///TzxxBO588478/3vf3+5GgYAAAAA6rdcj/E++eSTadCgQS677LI6QV+NysrKXHbZZamoqMjf//735ZkOAAAAAPgEyxX2TZo0KVtvvXXWW2+9T6xr3bp1tt5660ycOHF5pgMAAAAAPsFyhX1lZWVZuHDhUtUuXLgw1dXVyzMdAAAAAPAJlivs22yzzfLWW2996oq9d999N2+++WY23XTT5ZkOAAAAAPgEyxX29ezZM1VVVTnjjDMya9asemtmzZqV008/PaVSKT179lye6QAAAACAT7Bcu/Eee+yxGThwYEaOHJkDDzwwhxxySLbffvuss846mTVrVl555ZXcf//9mTZtWtq2bZs+ffqsqL4BAAAAgP+yXGFfixYtcvPNN+f//u//MnXq1Nx88811akqlUtq1a5frr78+LVq0WJ7pAAAAAIBPsFxhX5Jss802efTRR3P77bdn8ODBeeuttzJ79uw0a9Ysm2++efbZZ5/07t0766yzzoroFwAAAABYguUO+5KkWbNmOemkk3LSSSfVHlu4cGEqKytXxOUBAAAAgKXwmTboePXVV3PGGWdk3rx5S6z56le/mu9+97t59dVXP3NzAAAAAMDSW+aw75prrsnhhx+ehx56KCNHjqy3ZuLEiXn77bfz2GOP5fDDD89111233I0CAAAAAJ9smcK+66+/Ptddd12qqqrSvn37JdY1bdo0p512WjbaaKNUVVXlmmuuyS233LLczQIAAAAAS7bUYd/bb7+dfv36JUlOOeWUDBo0KLvvvnu9teuuu2769u2bRx55JH369EmpVMrVV1+dd955Z8V0DQAAAADUsdRh3x//+McsWrQoRx11VL73ve8t1eYblZWVOeecc/I///M/WbhwYQYOHLhczQIAAAAAS7bUYd+wYcPSoEGDnHzyycs8yWmnnZZSqZShQ4cu81hYUVqu0zil6upV3QYAAADA56bB0ha+8847adeuXdZff/1lnmSjjTbKpptumgkTJizzWFhRmjVumLLy8ox76KbMnf7eqm5npWm5+Rey0Z6Hruo2AAAAgJVgqcO+BQsWpGXLlp95ohYtWmTixImfeTysKHOnv5e5k9ee4LnxekveTAcAAAAolqUO+1q1apVJkyZ95onee++9NGvW7DOP/yz+9a9/5aabbsrjjz+eSZMmpXHjxtlhhx3yf//3f/VuLjJ06NDcdNNNef311zNv3rxsscUWOfLII/P1r389ZWVldeoXLVqUe++9NwMHDszbb7+dBg0apHPnzjnppJPSrVu3lXGLAAAAAFBrqd/Zt80222TGjBl58803l3mSsWPHZtq0adl0002Xeexn9dZbb+VrX/tabrnllixcuDA9evTIRhttlCFDhuT444/PoEGDFqu/8847c/zxx+e5555Lp06d0q1bt7z55ps5//zzc+6559a5fqlUyllnnZULL7ww7777brp3756tt946Q4cOTZ8+fXLvvfeurFsFAAAAgCTLEPbtvffeKZVK+e1vf7vMk1x33XUpKytL165dl3nsZ7Fo0aL88Ic/zJQpU3Lsscfmb3/7W/r165c//elP+elPf5pSqZSzzz478+fPT5KMGzcul156aZo3b5677rorv/vd73L99dfn4YcfziabbJL77rsvjz766GJz3HvvvXnooYey3XbbZdCgQenXr19uu+223HLLLWnYsGEuvvjivPfe2vNeOAAAAABWvaUO+w4++OC0atUqDz/8cPr167fUE1x//fX5y1/+koqKihxxxBGfqcll9be//S2vvfZaunTpknPPPTcVFRW15w4++ODsueeeWW+99fLaa68lSW6++eZUVVXlxBNPTKdOnWprN9xww1x44YW1Nf/phhtuSJKcf/75adWqVe3x3XffPX369Mn8+fMzYMCAz+sWAQAAAKCOpQ77mjdvniuuuCKlUinXXnttjjzyyDz++OOZPXt2ndqPPvoof//739O7d+/8+te/TllZWU4//fRssskmK7T5JXnkkUeSJCeeeGK979q76aab8ve//z077bRTkmTw4MFJkv33379Obffu3bPOOutk1KhRmTx5cpKPH0ueMGFCWrdunS5dutQZ06tXryTJY489tkLuBwAAAACWxlJv0JF8/CjvmWeemauuuiovvvhiTjnllFRUVGSjjTbKuuuum0WLFuWDDz7I5MmTU1VVlVKplLKysvTt2zfHHXfc53QLdb388stJkp133jkffvhhHn744bz++utp0KBBdt111/Tq1at2td+0adMyffr0VFZWZosttqhzrYqKimyxxRZ58cUX88Ybb6Rdu3YZPXp0kmSrrbaqN0ysOT5hwoTMnTs3TZo0+RzvFgAAAAA+tkxh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CPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAWxVoV93//+97PNNtvkvvvuq/f80KFDc/zxx2f33XfPzjvvnMMOOyx33313SqVSvfWLFi3KwIEDc+ihh2aXXXZJ165dc+KJJ+aZZ575PG8DAAAAAOq11oR9d999d/76178u8fydd96Z448/Ps8991w6deqUbt265c0338z555+fc889t059qVTKWWedlQsvvDDvvvtuunfvnq233jpDhw5Nnz59cu+9936etwMAAAAAdTRY1Q2sDOPGjctPfvKTTzx/6aWXpnnz5hkwYEA6deqUJJk0aVL69OmT++67Lz169MgBBxxQO+bee+/NQw89lO222y79+/dPq1atkiTDhg1L3759c/HFF6d79+7ZYIMNPtd7AwAAAIAahV/Zt2DBgvzoRz9KeXl5bYj3326++eZUVVXlxBNPXKxmww03zIUXXlhb859uuOGGJMn5559fG/Qlye67754+ffpk/vz5GTBgwAq+GwAAAABYssKHfb/61a/yyiuv5MILL1ziKrvBgwcnSfbff/8657p375511lkno0aNyuTJk5MkY8eOzYQJE9K6det06dKlzphevXolSR577LEVdRsAAAAA8KkKHfYNHTo0v//97/OVr3wlX/va1+qtmTZtWqZPn57KyspsscUWdc5XVFTUHn/jjTeSJKNHj06SbLXVVikrK6szpub4hAkTMnfu3BV1OwAAAADwiQob9v3rX//KmWeemfbt2+fHP/7xEuumTJmSJFl//fVTXl7/x9G2bdvFamt+tmvXrt76Ro0apUWLFqmurs60adM+6y0AAAAAwDIp7AYd5513XqZPn57+/funRYsWS6ybM2dOkqRx48ZLrGnUqNFitbNnz17qMTW1n0WpVKqdc0UqKytLkyZNVvh1YXU0d+7clEqlVd0GAAAAfGalUqnep0vrU8iw7/bbb8/jjz+eb33rW+nWrdsn1tas5luaD6wmMKioqFjqMctj4cKFee2111b4dZs0abLEzUqgaMaNG+dxegAAANZ4DRs2XKq6woV9Y8aMyc9+9rNsv/32+cEPfvCp9c2aNUuSzJs3b4k18+fPT5I0bdp0mccszwq6ysrKbLnllp95/JJ83iElrE4233xzK/sAAABYo40dO3apawsX9v3iF7/IvHnz0rhx45xzzjmLnXvllVeSJHfddVeGDh2aL37xi7U7506bNm2JSyJr3tFX8+6+mnf1TZ06td4e5s+fn5kzZ6asrCxt2rT5zPdSVlZWGzACn41H1gEAAFjTLcvCrcKFfTXvuBsxYkRGjBhRb83IkSMzcuTINGjQIN/4xjfSrl27TJ48OePHj89mm222WG1VVVXeeuutJMk222yz2M8xY8bUe/0xY8akVCplk002EdYBAAAAsNIUbjfeAQMG5I033qj3T8+ePZMkV1xxRd54441ceeWVSZIePXokSQYNGlTnek8//XRmzZqVbbfdNu3bt0+SbLrpptlss80yZcqUvPDCC3XGPProo0mSvfba63O4QwAAAACoX+HCvs/i6KOPTkVFRW644YbFwrtJkybl0ksvTZL07dt3sTHHHntskuSCCy7ItGnTao8PGzYst956ayorK3PCCSd8/s0DAAAAwP9XuMd4P4ttt902p556aq666qocddRR6dq1axo3bpxnnnkmc+bMyeGHH56DDjposTG9e/fOU089lSeeeCK9evVKt27dMmvWrAwfPjylUilXXnllNthgg1V0RwAAAACsjYR9/99JJ52Ujh07pn///nnppZdSVlaWjh07pnfv3jnkkEPq1JeXl6dfv3657bbbct9992XIkCFp3rx5unfvnr59+6Zr166r4C4AAAAAWJutVWHfb3/7208837Nnz9r3+i2NysrKHH/88Tn++OOXtzUAAAAAWG7e2QcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwAAAICCEPYBAAAAQEEI+wAAAACgIIR9AAAAAFAQwj4AAAAAKAhhHwAAAAAUhLAPAAAAAApC2AcAAAAABSHsAwBYBtXVpVXdwiqxtt43AMCapsGqbgAAYE1SXl6Wa+98OhOnzFjVraw0G7VtmVN677Gq2wAAYCkI+wAAltHEKTPy9sQPVnUbAABQh8d4AQAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYBwAAAAAFIewDAAAAgIIQ9gEAAABAQQj7AAAAAKAghH0AAAAAUBDCPgAAAAAoCGEfAAAAABSEsA8AAAAACkLYB7Aaqq4ureoWVom19b4BAABWlAarugEA6iovL8u1dz6diVNmrOpWVpqN2rbMKb33WNVtAAAArNGEfQCrqYlTZuTtiR+s6jYAAABYg3iMFwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFISwDwAAAAAKQtgHAAAAAAUh7AMAAACAghD2AQAAAEBBCPsAAAAAoCCEfQAAAABQEMI+AAAAACgIYR8AAAAAFESDVd3A5+mBBx7IPffck9dffz1z585N69ats9tuu+Wkk05Kx44d69QPHTo0N910U15//fXMmzcvW2yxRY488sh8/etfT1lZWZ36RYsW5d57783AgQPz9ttvp0GDBuncuXNOOumkdOvWbWXcIgAAAADUKuTKvlKplB/96Ec588wz8/zzz6djx47Zc889U1FRkT/96U859NBDM2TIkMXG3HnnnTn++OPz3HPPpVOnTunWrVvefPPNnH/++Tn33HPrneOss87KhRdemHfffTfdu3fP1ltvnaFDh6ZPnz659957V9btAgAAAECSgq7se/DBB/PQQw+lTZs2ufnmm7PtttsmSaqqqnLNNdfk+uuvz5lnnpm//e1vadasWcaNG5dLL700zZs3z4ABA9KpU6ckyaRJk9KnT5/cd9996dGjRw444IDaOe6999489NBD2W677dK/f/+0atUqSTJs2LD07ds3F198cbp3754NNthgpd8/AAAAAGunQq7su+eee5IkP/rRj2qDviSpqKjIqaeemq222irTp0/P008/nSS5+eabU1VVlRNPPLE26EuSDTfcMBdeeGFtzX+64YYbkiTnn39+bdCXJLvvvnv69OmT+fPnZ8CAAZ/L/QEAAABAfQoZ9rVo0SIdO3bMrrvuWudcWVlZNt988yTJ5MmTkySDBw9Okuy///516rt375511lkno0aNqq0fO3ZsJkyYkNatW6dLly51xvTq1StJ8thjj62YGwIAYJWorq5e1S2sEmvrfQNAERTyMd5rr712ieeqqqryyiuvJEk22GCDTJs2LdOnT09lZWW22GKLOvUVFRXZYost8uKLL+aNN95Iu3btMnr06CTJVlttVe/GHTXHJ0yYkLlz56ZJkyYr6M4AAFiZysvLc8OTt2bSjMmrupWVZsOW7dK3x7Grug0A4DMqZNj3Se64445MnDgxrVq1yu67757x48cnSdZff/2Ul9e/0LFt27ZJkilTpiz2s127dvXWN2rUKC1atMiMGTMybdq0dOjQYUXfBgAAK8mkGZMzfvq7q7oNAIClslaFfcOGDcvPfvazJMnpp5+eZs2aZc6cOUmSxo0bL3Fco0aNkqS2dvbs2Us9pqb2syiVSrVzrkhlZWVWG7LWmDt3bkql0qpuY5ms7b+ja+J3xtrD7+fa9fvp+167vm8AWJ2VSqV6ny6tz1oT9g0ePDinnnpqFixYkN69e+fwww9PktrVfEvzgdX8PzsVFRVLPWZ5LFy4MK+99toKv26TJk0W24gEimzcuHGZO3fuqm5jmaztv6Nr4nfG2sPv59r1++n7Xru+bwBY3TVs2HCp6taKsG/AgAG54oorUlVVlaOPPjoXXHBB7blmzZolSebNm7fE8fPnz0+SNG3adJnHLM//GlxZWZktt9zyM49fks87pITVyeabb77GrUpY239H18TvjLWH38+16/fT9712fd8AsDobO3bsUtcWOuxbtGhRLrnkkgwcODBlZWU57bTT8u1vf3uxmpr37k2bNm2JSyJr3tFX8+6+mjFTp06td9758+dn5syZKSsrS5s2bT5z/2VlZbUBI/DZrM2PX62pfGew+vL7uXbxfQPA6mNZ/kfI+nekKIB58+alb9++GThwYJo0aZKrr766TtCXJK1atUq7du2yYMGC2s06/lNVVVXeeuutJMk222yz2M8xY8bUO/eYMWNSKpXSoUMHYR0AAAAAK00hw76qqqqccsopGTJkSFq3bp1bb701BxxwwBLre/TokSQZNGhQnXNPP/10Zs2alW233Tbt27dPkmy66abZbLPNMmXKlLzwwgt1xjz66KNJkr322mv5bwYAAAAAllIhw77rrrsuQ4YMSdOmTfOHP/whO+ywwyfWH3300amoqMgNN9ywWHg3adKkXHrppUmSvn37Ljbm2GOPTZJccMEFmTZtWu3xYcOG5dZbb01lZWVOOOGEFXRHAAAAAPDpCvfOvhkzZuR3v/tdko/fsXfDDTcssfarX/1qevTokW233Tannnpqrrrqqhx11FHp2rVrGjdunGeeeSZz5szJ4YcfnoMOOmixsb17985TTz2VJ554Ir169Uq3bt0ya9asDB8+PKVSKVdeeWU22GCDz/VeAQAAAOA/FS7se/bZZzNnzpwkydtvv5233357ibXbbbdd7SO8J510Ujp27Jj+/fvnpZdeSllZWTp27JjevXvnkEMOqTO2vLw8/fr1y2233Zb77rsvQ4YMSfPmzdO9e/f07ds3Xbt2/VzuDwAAAACWpHBh33777Zc33njjM43t2bNnevbsudT1/4+9+46v6f7jOP66N5tEiB0kiBF716pSpVo6tHatmLFqS1BarVGr9kzsPbpbrVJVJYi9Z6lVOwiyk3t/f3jc80vQRbjt9X7+8yPn3Nvv9/HzzTnnfb7fz9fFxYV27drRrl27R/rviYiIiIiIiIiIpCeHrNknIiIiIiIiIiLyLFLYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiYmcWi8XeTbCLZ7XfIiIiIiJPkrO9GyAiIvKsM5vNzN60iIvRV+zdlKfG1zsnwTXb2LsZIiIiIiIOR2GfiIjIv8DF6Cucjbpg72aIiIiIiMh/nJbxioiIiIiIiIiIOAiFfSIiIiIiIiIiIg5CYZ+IiIiIiIiIiIiDUNgnIiIiIiIiIiLiIBT2iYiIiIiIiIiIOAiFfSIiIiIiIiIiIg5CYZ+IiIiIiIiIiIiDUNgnIiIiIiIiIiLiIBT2iYiIiIiIiIiIOAiFfSIiIiIiIiIiIg5CYZ+IiIiIiIiIiIiDUNgnIiIiIiIiIiLiIBT2iYiIiIiIiIiIOAiFfSIiIiIiIiIiIg5CYZ+IiIiIiIiIiIiDUNgnIiIiIiIiIiLiIBT2iYiIiIiIiIiIOAiFfSIiIiIi4jAsFqu9m2AXz2q/RUTkQc72boCIiIiIiEh6MZtNTF8ewe9Xo+3dlKcmTw5vureobu9miIjIv4TCPhERERERcSi/X43mzO837d0MERERu9AyXhEREREREREREQehsE9ERERERERERMRBKOwTERERERERERFxEAr7REREREREREREHITCPhEREREREflPslgs9m6CXTyr/RaRv0e78YqIiIiIiMh/ktlsZvamRVyMvmLvpjw1vt45Ca7Zxt7NeCQWixWz2WTvZjx1z2q/xX4U9omIiIiIiMh/1sXoK5yNumDvZsjfYDabmL48gt+vRtu7KU9NnhzedG9R3d7NkGeMwj4REREREREReSp+vxrNmd9v2rsZIg5NNftEREREREREREQchMI+ERERERERERERB6GwT0RERERERERExEEo7BMREREREREREXEQCvtEREREREREREQchMI+ERERERERERERB6GwT0RERERERERExEEo7BMREREREREREXEQCvtEREREREREREQchMI+ERERERERERERB6GwT0RERERERERExEEo7BMREREREREReQK8vdyxWiz2boZdPKv9/jdwtncDREREREREREQcUUZ3V0xmM799G05c1CV7N+ep8ciamwKvdbJ3M55ZCvtERERERERERJ6guKhLxF05Z+9myDNCy3hFREREREREREQchMI+ERERERERERERB6GwT0RERERERERExEEo7BMREREREREREXEQCvtERERE5E95e7ljtVjs3QwRERER+Ru0G6+IiIiI/KmM7q6YzGZ++zacuKhL9m7OU+NdoCR5Xnjb3s0QERER+UcU9omIiIjI3xIXdYm4K+fs3Yynxt0nl72bICIiIvKPaRmviIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiIiIiIOQmGfiIiIiIiIiIiIg1DYJyIiIiIiIiIi4iAU9omIiIiIiIiIiDgIhX0iIiIiIiL/Yd5e7lgtFns3Q0RE/iWc7d0AEREREREReXQZ3V0xmc389m04cVGX7N2cp8a7QEnyvPC2vZshIvKvo7BPRERERETEAcRFXSLuyjl7N+OpcffJZe8miIj8K2kZr4iIiIiIiIiIiINQ2CciIiIiIiIiIuIgFPaJiIiIiIiIiIg4CIV9IiIiIiIiIiIiDkJhn4iIiIiIiIiIiINQ2CciIv8K3l7uWC0WezdDRERERETkP83Z3g0QEREByOjuisls5rdvw4mLumTv5jw13gVKkueFt+3dDBERERERcRAK+0RE5F8lLuoScVfO2bsZT427Ty57N0FERERERByIlvGKiIiIiIiIiEi6cc6YCcszWqLn39Bvzex7TL/99hvTp09n9+7dREVFkStXLl599VWCg4PJkCGDvZsnIiIiIiIiIvJUObtlwGw2M3vTIi5GX7F3c54aX++cBNdsY+9mKOx7HAcOHKBt27bExsZSunRpSpUqxZ49e5g1axYbN25k2bJleHp62ruZIiIiIiIiIiJP3cXoK5yNumDvZjxztIz3ESUnJ9O3b19iY2MZOXIkq1evZsqUKfz444/Url2b48ePM2HCBHs3U0REREREREREniEK+x7RmjVrOH/+PFWrVqVx48bGz93d3Rk1ahQZMmRg1apVREdH27GVIiIiIiIiIiLyLFHY94h++uknAOrWrfvAsSxZslC5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yu7du1m1ahUVK1akfPnyPPPMM8ZrqDJBRERERERE5K8p7BORNDFixAhmz56Nh4cHlStXZvPmzbRu3ZpBgwbh6emZKvCrVq0a/fr1o3LlylgslruCvDsDQFDQJyIiIiIiIvJPKewTkYdyZxAXGRnJZ599BkDp0qUZOnQoFStWNNr+VeD354BPRERERERERB6cSmVE5KGYzWbsdjsA169fN0K7Y8eOcezYMRISEoy2gYGBDB48mCpVqrBjxw4mTJjAnj17sNlsCvpERERERERE0oDCPhF5aGazmcTERHLlysULL7xA69atSUpK4oMPPuD777/nxo0bRts/B36TJk1i165d2Gy2dByBiIiIiIiISMagabwi8q/ca9ptcnIyKSkpZMuWjc8++4yIiAiyZMnCwIEDad68OdmyZTOed/jwYT766CN27NhBmTJlCAsLw8/PL51GIyIiIiIiIpIxKOwTkQdms9mwWCwAnD17lgsXLmC32/H398fb29toN378eMLDw8mSJQsDBgygefPmeHp6Go8fPXqUkJAQWrRoweuvv/7YxyEiIiIiIiKS0SjsE5EHcueGHNHR0SxcuJCjR48C0KxZM0aOHImrq6vR5s7Ab+DAgbzyyiscOnSIs2fPUq9ePW7dukWOHDmAe1cLioiIiIiIiMg/p7BPRP6xO8O4sWPHEhUVRb58+ahSpQrJycnUqFGDVq1akSVLllRtnVN6PTw8ePbZZ9mxYwcpKSlERERQqVKlu84tIiIiIiIiIv+Owj4ReWBz585lxIgR1KlTh+DgYAICAoA/Aru4uDgAsmbNSpYsWQCYOHEi06ZNIzExEYDQ0FC6dOmSLv0XERERERERyagU9onIA4mLi6Nnz56cOXOGsLAwKlSoYIR8a9euZd26daxevRqz2cxLL71EixYtKF68OAA//PADN2/exNvbmwYNGgCppwWLiIiIiIiIyMNxSe8OiMiT5dq1axw8eJD69etToUIFrFYrJ0+eZPHixURERADg4eFBYmIi0dHReHt7G2Ff06ZNU51LQZ+IiIiIiIhI2lLYJyIPxGw2kyNHDrZu3cp3333HgQMH2L17N7/88gsmk4l3332X6tWrs2/fPoYPH863335L27ZtyZo16z3PJSIiIiIiIiJpR9N4ReSenFNz77Vxxvvvv8+CBQuM3728vHjmmWf4v//7P6pXr24cr1+/PtmzZ+err77Cw8PjsfVdREREREREJLNSZZ+I3MVms2GxWACIjY3l1q1bZM2aldy5cwMwYsQIfHx8iI2N5fr163Tr1o0CBQoYjwOsW7eOc+fOUadOHdzc3DRlV0REREREROQxUNgnIqnY7XYj6Js5cybff/89p0+fxt3dnTfeeIM6derg7+9Pv379jPZms5mUlBTjHDt37iQ8PBxXV1caNmxonE9EREREREREHi1N4xWRe/r000+ZMmUKAPny5ePcuXNkyZKFNm3a8OqrrxqbbgBs27aNTz75hBdffJGrV6+ycOFCLl68yKBBg+jcuXN6DUFEREREREQk01Fln4gApFqbb/ny5URHR1O3bl169uxJmTJlmDFjBvPmzWPevHnYbDY6duxIsWLFSE5OZsWKFRw4cIADBw4AkCdPHj744APatm0LaNddERERERERkcdFYZ+IpArjHA4HZ86cIUuWLPTp04cKFSoA0LNnT7y9vYmMjOTrr78GMAK/wYMHU7FiRa5fv06uXLkoWrQoZcqUuevcIiIiIiIiIvJoaRqviBjGjh3LmTNnOHbsGAULFuSLL74AwGq14uJy+7uBr7/+msjISM6fP0+bNm1o3749JUuWvOf57rWTr4iIiIiIiIg8OqrsExEALl68yE8//cSpU6fIlSsX/v7+wO2deV1cXIwKPefU3MjISObPn4/FYqFdu3ap1vBzUtAnIiIiIiIi8nhpbp2IAODj48O4ceN4+umnuXz5MmvWrOGXX37BYrHgcDgwm83Y7XYA2rZtS1BQEAUKFGD27NlERUVx7dq1dB6BiIiIiIiIiCjsE8nEHA6HEeCZTCYCAwN59913qV27NsnJyQwYMICjR49iMpmMyr47A7+uXbvi5eVFmTJlyJEjR3oORURERERERETQmn0imc6fN8xISkrC3d2d5ORk3NzcsFqtHDlyhNGjR7N9+3bKlCnDxx9/TIkSJVIFfs5zHD161FizT2v0iYiIiIiIiKQvhX0imYjNZsNisQCwatUqdu7cybZt2/D29iZnzpx06dKFcuXKAXDo0CFGjhzJjh07UgV+zkDvz6Ghdt0VERERERERSX8K+0QyiTvDuM8++4yoqCgj/HN1deXWrVt4eHjQu3dvXn75ZfLkycOhQ4cYNWrUfSv8REREREREROS/RZ/WRTIJZzj3xRdfEBERQbVq1Zg+fTqrV69m0aJFtG/fnpSUFCZOnMiCBQtISEggMDCQ0NBQqlevzi+//MLbb7/N4cOHFfSJiIiIiIiI/EfpE7tIJrJz505mzJhByZIlGTRoEDVr1sTPz48iRYowZMgQ3nnnHTw8PIiIiGDTpk2YTCZKlSpFaGgoVapU4ejRo/z222/pPQwRERERERERuQ+X9O6AiDw+J0+e5Nq1a/Tr14+AgADjuHM6b5cuXbh+/TqTJ09mzJgxVK5cGV9fX6PC79y5czz33HPpOAIRERERERER+Suq7BPJIO63/Kbdbjd+3rdvHw6Hg9y5cwNgtVoBsFgsRrs+ffpQtWpVLl26RExMDAAmk4ny5csbQd+d5xQRERERERGR/w6FfSIZRFJSEvHx8WzcuJEtW7awZ88egFTr67m7uwPw888/A+Di8kdxr9lsNsK/fPnykZKSwvnz5+/5WlqzT0REREREROS/SdN4RTKAQ4cOER0dzbZt21IFdG3atKFv3774+PgA0KhRIxYsWMCWLVvYu3cvlSpVMto6HA4j/DObzWTLlo2SJUs+1nGIiIiIiIiIyMNR2CfyhNu6dStvv/028fHxVKxYkTJlypCUlMSBAwcwmUzGlF2AIkWKULt2bX766Sdmz56Nh4cHpUqVwmQy4XA4MJlMbN++ndWrVxMQEECuXLnScWQiIiIiIiIi8qAU9ok8wfbu3Uvv3r3x9PRk0KBBdO7cGbi9pt7Zs2fx9fXFbDYbQV7evHlp3749J06cYMmSJSQmJvL888/z0ksvYTab2bRpE2FhYVy/fp327dsbFYEiIiIiIiIi8mQwOe63qr+I/KedPXuW4OBgTpw4waBBg2jZsiVwezqu3W7HYrEAf+y0a7fbjbX21q1bx8SJEzl06BA2m43AwEBsNhu//fYbDoeD0NBQunTpYpzPZDKlxxBFRERERERE5AGpsk/kCeMM3/bu3cvevXvp1q2bEfQ5Az1n0AcYP5vNZpKSkkhMTKRevXr4+fmxdu1a5s6dy9mzZzGZTDz//PM0a9aMxo0bpzqfiIiIiIiIiDwZVNkn8oTq0aMHW7duZenSpRQsWPBvg7mkpCSWLFnCunXraNmyJQ0aNAAgISGB5ORkAHLkyGFs0qGgT0REREREROTJo8o+kSeQ1Wrl5MmT5MuXD19fX4C/DeaSkpJYvnw5GzZsIFu2bDRo0AC73Y6np6fRxpn9OxwOBX0iIiIiIiIiTyB9mhd5wtjtdq5du8aZM2e4efMmV69exWq1/u3zcuTIwSuvvIKLiwtnzpwhKSnprkDPuTaf1ugTEREREREReTIp7BN5wphMJnLmzEmxYsWIi4vj9OnTuLi48E9m5OfMmRObzca1a9ew2WyPobciIiIiIiIi8jgp7BN5wphMJsxmM+XKlcNqtRIeHs6lS5f+shrPbrcD4OrqisPhIGvWrKk28RARERERERGRjEFhn8gTxlnB16pVK/z8/Ni3bx8rVqwgISHhvu2d03X37NkDQNOmTXF3d/9H1YAiIiIiIiIi8uRQ2CfyhHFW8JUoUYJatWoRHx/Pl19+yfLly7l27RpwO+BzOBzYbDaj/Y4dO5g2bRp58uShXLlyqc4lIiIiIiIiIhmDwj6RJ1TOnDnp06cPlStX5vjx44SHhzNt2jRiYmIwmUyYTCZjqu7GjRv59NNPuXjxIv369aNSpUrp23kREREREREReSRMDs3jE3minT59muHDh7N161asVisFCxakc+fO5M6dm5s3b3LkyBG++uorbt26RWhoKF26dAFuV/+psk9EREREREQkY1HYJ5IBXLx4kW+//ZbVq1fz888/p3rMxcWFYsWKERQUxIsvvgjc3rDDuY6fiIiIiIiIiGQcCvtEMgir1UpiYiLff/89Fy9e5MyZM7i7u1O/fn38/f0pXrw4oKBPREREREREJCNT2CeSiWjqroiIiIiIiEjGpvIekUxEQZ+IiIiIiIhIxqawT0REREREREREJINQ2CciIiIiIiIiIpJBKOwTERERERERERHJIBT2iYiIiIiIiIiIZBAK+0RERERERERERDIIhX0iIiIiIiIiIiIZhMI+ERERERERERGRDEJhn4iIiIiIiIiISAahsE9ERERERERERCSDcEnvDoiIiIjIwwkICHig9tmzZ2fnzp2PqDeP3unTp2nYsOG/eu6vv/6axr0RERER+W9R2CciIiKSQRQtWhRvb++/bZctW7bH0JtHx93dnaeeeuqu4wkJCRw5cgSAcuXK4ebm9ri7JiIiIpLuTA6Hw5HenRARERGRf89Z2Tdq1ChatWqVzr1JP9u2beO1114DYNWqVRQsWDCdeyQiIiLy+GnNPhERERERERERkQxCYZ+IiIiIiIiIiEgGobBPREREJJMLDQ0lICCAuXPn8tVXX/Hss89Svnx5nnvuObZt28a2bdsICAigbdu2HDt2jHbt2lG+fHlq1qzJmDFjUp3rxx9/5I033qBGjRqUK1eOOnXq0KdPH7Zs2XLP1w4ICCAgIIDY2FjeeecdKleuTJUqVXjttdewWq1pOs4NGzYQEBBA2bJluXz58j3b3Lp1iypVqhAQEMDPP/8MQKdOnQgICOCnn35iz549dOvWjapVq1K1alVeeeUVFi5ciN1uv+/r/vTTTwQFBVGzZk3KlStH3bp1CQkJ4eDBg2k6PhERERFQ2CciIiIi/9/333/PkCFDcDgcFC1alEuXLlG6dGnj8bi4ODp37syhQ4coUaIESUlJFC1aFICUlBTeeust+vbty4YNG3BxcSEwMBCr1cqKFSvo0qULI0eOvO9r9+nThyVLllCoUCE8PDzw8fHBxSVt95KrXbs2+fLlw2q1smTJknu2WblyJQkJCRQvXpyKFSumemzjxo106tSJrVu3UrBgQby8vNi7dy/vvfce/fr1Izk5OVV7q9XKO++8w5tvvsm6deswmUwEBASQnJzMkiVLaNOmDbNnz07TMYqIiIhoN14RERERAWD37t107NiR9957D4vFQlxcHDly5DAej4mJoXDhwixcuBA/Pz9u3LhhBHKjR49m5cqVZM2alVGjRtGkSRMAbDYb8+bNY+TIkcyYMYP8+fPTpUuXu177wIEDzJo1i2rVqmG327l27Vqaj89sNtOyZUvCwsJYtGgRnTt3vqvNd999B3DPjU7mzp1L6dKlmThxIoUKFQJg9erVhISEsGLFCqZPn06PHj2M9hMmTGDx4sXkzZuXESNGULduXeD232TOnDmMHj2aESNG4O/vT+3atdN8vCIiIpI5qbJPREREJIMYNGiQMS32r/5t27btns93d3cnJCQEi8UCgLe3911tevTogZ+fHwDZsmXD3d2d8+fPM2/ePAA+/PBDI+gDsFgsdOjQgX79+gEwadIkbty4cdd5X3jhBapVqwbcDuVy5sz57/8Qf6F169aYTCYOHjzIsWPHUj124cIFNm/ejMVioUWLFnc9N1u2bERERBhBH0CDBg0IDQ0FIDIy0qjuu3z5MtHR0QB88cUXRtAHt/8mnTp1okuXLjgcDsaPH5/GoxQREZHMTGGfiIiISAZRtGhRnnrqqb/9lz179ns+v0yZMmTNmvUvX6NKlSp3HVu/fj1WqxUfHx+aNm16z+d17NgRV1dXrl+/zvbt2//ReR+FQoUKGaGis4rPadGiRdjtdp555hl8fHzuem7Tpk2NoPNOL7/8Mu7u7iQkJBhB6rp160hOTqZEiRKULVv2nn1xBor79u277xqCIiIiIg9K03hFREREMogePXrcc/rpP3WvgOuftDl+/DgApUuXxmy+93fJWbNmxd/fnyNHjnDixAnq16//wK+dVlq3bs327dtZvHgxwcHBmEwm4HbYB/eewgtQoUKFex53c3PD39+fw4cPc/LkSerWrcvRo0cBOH/+PO3atbvn8xwOh/Hz8ePHyZ07978ek4iIiIiTwj4RERERAW5P4/07WbJkuetYQkICwH0rBp08PT0B7jmN917nfVSaNGnChx9+yLlz59i2bRs1atRg//79/Pbbb+TKleuuINLJy8vrvud0VkQ61xq8fv06cPtvs3v37r/t06NYo1BEREQyJ4V9IiIiIvJQsmXLBvwRcN2PM9Bytk8vWbJkoVmzZnz11VcsXryYGjVqGFV9L730Eq6urvd8XmJi4n3P6Qw8ndV5Hh4eADz//PN8/vnnadl9ERERkb+kNftERERE5KEUK1YMgEOHDmG32+/ZJiEhgZMnTwJQpEiRx9W1+2rdujUAq1atwmazsWrVqlTH7+XIkSP3PJ6YmMiJEycAKFGiBAD+/v4AxnTe+z1v+/btxMTEYLPZHnwQIiIiIvegsE9EREREHsozzzyDi4sLly5d4ocffrhnm9mzZ2O1WvHw8ODpp59+zD28W8WKFSlZsiTx8fHMmjWLs2fPUqZMGQIDA+/7nKVLl96zum/hwoWkpKTg4+PDU089BUC9evWwWCwcP36cTZs23fN80dHRdOrUiRYtWvxl1aCIiIjIg1DYJyIiIiIPJV++fLRt2xaAwYMHs3z5cuMxu93OnDlzmDhxIgC9e/f+27X9HhdnFd+ECROA+2/M4XT+/Hnefvtt4uPjjWPLli3jk08+ASA4ONjYoKRAgQK0adPGOL569WrjOXa7nfnz5zNp0iQAOnToYKxnKCIiIvKwtGafiIiISAYRERHB/Pnz/1Hbnj17Uq9evTR77UGDBnHhwgVWrVpFv3798PX1JW/evMTExBjhWMeOHenevXuavebDatGiBZ9++ik3b97E1dWVF1988S/blyxZkrVr11KvXj1KlixJXFwcZ8+eBaBr1653hYXvvfceFy5cYM2aNfTq1QtfX1/8/Pw4c+YMcXFxwO01/fr37/9IxiciIiKZk8I+ERERkQzi5MmTxrp4f+fy5ctp+tpubm5MnjyZZcuWsXDhQg4cOMChQ4fw8fGhVq1avPLKK1SvXj1NX/NheXt788wzz7Bq1SoaNGhArly5/rJ9w4YNGTRoEGFhYRw4cIAsWbLwzDPP8Nprr1G3bt272ru7uxMWFsayZcv45ptvjL9JtmzZqF69Oq1ataJ58+ZGNaCIiIhIWjA5HA5HendCRERERCQ9tGrVioMHDxIZGXnfSsdOnTqxfft2evbsydtvv/2YeygiIiLyYPQ1ooiIiIhkSgcPHuTgwYPky5ePOnXqpHd3RERERNKEpvGKiIiISKYRExMDQGxsLKGhoQC89tprWCyW9OyWiIiISJpR2CciIiIimcYPP/zAuHHjjN9LlSpFx44d07FHIiIiImlL03hFREREJNMIDAzE19eXrFmz0qhRI6ZNm4abm1t6d0tEREQkzWiDDhERERERERERkQxClX0iIiIiIiIiIiIZhMI+ERERERERERGRDEJhn4iIiIiIiIiISAahsE9ERERERERERCSDUNgnIiIiIiIiIiKSQSjsExERERERERERySAU9omIiIiIiIiIiGQQCvtEREREREREREQyCIV9IiIiIiIiIiIiGcT/Ay5sJ3nxJCJ4AAAAAElFTkSuQmCC" - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "sns.set_style(\"whitegrid\")\n", - "fig, ax = plt.subplots(figsize=(15, 10))\n", - "sns.set(font_scale = 1.5)\n", - "sns.barplot(\n", - " data=df,\n", - " x=\"Error Type\",\n", - " y=\"Count\",\n", - " hue=\"Model\"\n", - ")\n", - "plt.xticks(rotation=45, ha=\"right\")\n", - "plt.savefig(\n", - " f\"syntax_error_comparisons.png\",\n", - " dpi=200,\n", - " bbox_inches=\"tight\",\n", - " pad_inches=0.2,\n", - " facecolor=\"w\",\n", - " )" - ], - "metadata": { - "collapsed": false, - "ExecuteTime": { - "end_time": "2024-04-12T17:57:39.414713Z", - "start_time": "2024-04-12T17:57:38.910449Z" - } - } - }, - { - "cell_type": "code", - "execution_count": null, - "outputs": [], - "source": [], - "metadata": { - "collapsed": false - } - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 2 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython2", - "version": "2.7.6" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} diff --git a/research/prompts/__init__.py b/research/prompts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/prompts/_prompts.py b/research/prompts/_prompts.py new file mode 100644 index 00000000..cb3e7a78 --- /dev/null +++ b/research/prompts/_prompts.py @@ -0,0 +1,100 @@ +from dataclasses import dataclass +from pathlib import Path +from attr import attrs, attrib +from typing import List, Iterable, Type, Set + +from blendsql.ingredients import Ingredient +from blendsql.grammars._peg_grammar import grammar as peg_grammar + + +@attrs +class Examples: + """Class for holding few-shot examples. + + Examples: + ```python + from blendsql.prompts import FewShot, Examples + fewshot_prompts: Examples = FewShot.hybridqa + print(fewshot_prompts[:2]) + ``` + ```text + Examples: + + This is the first example + + --- + + This is the second example + ``` + """ + + data: str = attrib() + + split_data: List[str] = attrib(init=False) + + def __attrs_post_init__(self): + self.data = self.data.strip() + self.split_data: list = self.data.split("---") + + def __getitem__(self, subscript): + newline = ( + "\n\n" + if (isinstance(subscript, int) and subscript == 0) + or (isinstance(subscript, slice) and subscript.start in {0, None}) + else "" + ) + return "Examples:" + newline + "---".join(self.split_data[subscript]) + + def __repr__(self): + return "Examples:\n\n" + self.data + + def __str__(self): + return "Examples:\n\n" + self.data + + def __len__(self): + return len(self.split_data) + + def is_valid_query(self, query: str, ingredient_names: Set[str]) -> bool: + """Checks if a given query is valid given the ingredient_names passed. + A query is invalid if it includes an ingredient that is not specified in ingredient_names. + """ + stack = [query] + while len(stack) > 0: + for res, _start, _end in peg_grammar.scanString(stack.pop()): + if res.get("function").upper() not in ingredient_names: + return False + for arg in res.get("args"): + stack.append(arg) + return True + + def filter(self, ingredients: Iterable[Type[Ingredient]]) -> "Examples": + """Retrieve only those prompts which do not include any ingredient not specified in `ingredients`.""" + ingredient_names: Set[str] = { + ingredient.__name__.upper() for ingredient in ingredients + } + filtered_split_data = [] + for d in self.split_data: + if self.is_valid_query(d, ingredient_names=ingredient_names): + filtered_split_data.append(d) + return Examples("---".join(filtered_split_data)) + + +@dataclass +class FewShot: + """A collection of few-shot examples, with some utility functions for easy manipulation. + + Examples: + ```python + from blendsql import LLMMap, LLMQA + from blendsql.prompts import FewShot, Examples + # Fetch the examples for HybridQA + fewshot_prompts: Examples = FewShot.hybridqa + print(f"We have {len(fewshot_prompts)} examples") + # We can select a subset by indexing + first_three_examples = fewshot_prompts[:3] + # Additionally, we can filter to keep only those examples using specified ingredients + filtered_fewshot = fewshot_prompts.filter({LLMQA, LLMMap}) + ``` + """ + + hybridqa = Examples(open(Path(__file__).parent / "./few_shot/hybridqa.txt").read()) diff --git a/research/prompts/end_to_end_program.py b/research/prompts/end_to_end_program.py deleted file mode 100644 index f0b458d0..00000000 --- a/research/prompts/end_to_end_program.py +++ /dev/null @@ -1,20 +0,0 @@ -from blendsql._program import Program -from guidance import gen - -BASE_SYSTEM_PROMPT = """ -This is a hybrid question answering task. The goal of this task is to answer the question given a table (`w`) and corresponding passages (`documents`). -Be as succinct as possible in answering the given question, do not include explanation. -""" - - -class EndtoEndProgram(Program): - def __call__(self, serialized_db: str, question: str, **kwargs): - with self.systemcontext: - self.model += BASE_SYSTEM_PROMPT - with self.usercontext: - self.model += f"Context:\n{serialized_db}\n\n" - self.model += f"Question: {question}\n" - self.model += f"Answer:\n" - with self.assistantcontext: - self.model += gen(name="result", **self.gen_kwargs) - return self.model diff --git a/research/prompts/feverous/few_shot.txt b/research/prompts/feverous/few_shot.txt deleted file mode 100644 index a88e3437..00000000 --- a/research/prompts/feverous/few_shot.txt +++ /dev/null @@ -1,415 +0,0 @@ -Examples: - -Table Description: Oyedaea -CREATE TABLE "w0" ( - "index" INTEGER, - "oyedaea" TEXT, - "scientific classification" TEXT, - "kingdom:" TEXT, - "(unranked):" TEXT, - "(unranked):_2" TEXT, - "(unranked):_3" TEXT, - "order:" TEXT, - "family:" TEXT, - "tribe:" TEXT, - "genus:" TEXT, - "type species" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index oyedaea scientific classification kingdom: (unranked): (unranked):_2 (unranked):_3 order: family: tribe: genus: type species - 0 oyedaea scientific classification plantae angiosperms eudicots asterids asterales asteraceae heliantheae oyedaea dc. type species -*/ - -Here are some values that may be useful: w0.oyedaea ( oyedaea ) , w0.order: ( asterales ) , w0.family: ( asteraceae ) -Q: Oyedaea is part of the family Asteraceae in the order Asterales. -BlendSQL: -SELECT EXISTS ( - SELECT * FROM w0 WHERE "family:" = 'asteraceae' AND "order:" = 'asterales' -) - ---- - -Table Description: Lindfield railway station -CREATE TABLE "w0" ( - "index" INTEGER, - "platform" INTEGER, - "line" TEXT, - "stopping pattern" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index platform line stopping pattern notes - 0 1 t1 services to emu plains via central & richmond notes - 1 1 t9 services to hornsby via strathfield notes - 2 2 t1 terminating services to/from penrith & richmond notes -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: documents.title ( lindfield railway station ) -Q: Lindfield railway station has 3 bus routes, in which the first platform services routes to Emu plains via Central and Richmond and Hornbys via Strathfield. -BlendSQL: -SELECT EXISTS ( - SELECT * FROM w0 WHERE platform = 1 AND {{LLMMap('Does this service to Emu plains via Central and Richmond?', 'w0::stopping pattern')}} = TRUE -) AND EXISTS ( - SELECT * FROM w0 WHERE platform = 1 AND {{LLMMap('Does this service to Hornbys via Strathfield?', 'w0::stopping pattern')}} = TRUE -) AND EXISTS ( - SELECT * FROM docs WHERE {{LLMMap('How many bus routes operated by Transdev?', 'documents::content')}} = 3 -) - ---- - -Table Description: Mukaradeeb -CREATE TABLE "w0" ( - "index" INTEGER, - "mukaradeeb \u0645\u0642\u0631 \u0627\u0644\u062f\u064a\u0628" TEXT, - "country" TEXT, - "province" TEXT, - "district" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index mukaradeeb \u0645\u0642\u0631 \u0627\u0644\u062f\u064a\u0628 country province district - 0 mukaradeeb \u0645\u0642\u0631 \u0627\u0644\u062f\u064a\u0628 iraq al-anbar al-qa\'im -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.country ( iraq ) , w0.province ( al-anbar ) , w0.district ( al-qa'im ) , documents.title ( mukaradeeb ) -Q: Mukaradeeb('Wolf's Den') is a city in Iraq near the Syrian border, in the district of Al-Qa'im, province of Al-Anbar. -BlendSQL: -SELECT ( - {{ - LLMValidate( - 'Is Mukaradeeb near the Syrian border?', - (SELECT * FROM documents) - ) - }} -) AND EXISTS ( - SELECT * FROM w0 WHERE "district" = 'al-qa''im' AND "province" = 'al-anbar' -) - ---- - -Table Description: 2006\u201307 San Jose Sharks season -CREATE TABLE "w0" ( - "index" INTEGER, - "no." INTEGER, - "cr" INTEGER, - "filledcolumnname" TEXT, - "gp" INTEGER, - "w" INTEGER, - "l" INTEGER, - "otl" INTEGER, - "gf" INTEGER, - "ga" INTEGER, - "pts" INTEGER -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index no. cr filledcolumnname gp w l otl gf ga pts - 0 1 2 anaheim ducks 82 48 20 14 258 208 110 - 1 2 5 san jose sharks 82 51 26 5 258 199 107 - 2 3 6 dallas stars 82 50 25 7 226 197 107 -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.filledcolumnname ( san jose sharks ) -Q: The 2006-07 San Jose Sharks season, the 14th season of operation (13th season of play) for the National Hockey League (NHL) franchise, scored the most points in the Pacific Division. -BlendSQL: -SELECT ( - {{ - LLMValidate( - 'Is the Sharks 2006-07 season the 14th season (13th season of play)?', - (SELECT * FROM documents) - ) - }} -) AND ( - SELECT (SELECT filledcolumnname FROM w0 ORDER BY pts DESC LIMIT 1) = 'san jose sharks' -) - ---- - -Table Description: Saunders College of Business -CREATE TABLE "w0" ( - "index" INTEGER, - "motto" TEXT, - "type" TEXT, - "established" INTEGER, - "parent institution" TEXT, - "dean" TEXT, - "academic staff" INTEGER, - "students" TEXT, - "postgraduates" INTEGER, - "location" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index motto type established parent institution dean academic staff students postgraduates location - 0 business and technology - unlocked private 1910 rochester institute of technology jacqueline r. mozrall 30 2400+ 346 rochester, new york, united states -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.parent institution ( rochester institute of technology ) , w0.dean ( jacqueline r. mozrall ) , documents.title ( saunders college of business ) -Q: Saunders College of Business, which is accredited by the Association to Advance Collegiate Schools of Business International, is one of the colleges of Rochester Institute of Technology established in 1910 and is currently under the supervision of Dean Jacqueline R. Mozrall. -BlendSQL: -SELECT EXISTS( - SELECT * FROM w0 - WHERE "parent institution" = 'rochester institute of technology' - AND "established" = '1910' - AND "dean" = 'jacqueline r. mozrall' -) AND ( - {{ - LLMValidate( - 'Is Saunders College of Business (SCB) accredited by the Association to Advance Collegiate Schools of Business International (AACSB)?', - (SELECT * FROM documents) - ) - }} -) - ---- - -Table Description: 2018 Mexican general election -CREATE TABLE "w0" ( - "index" INTEGER, - "candidate" TEXT, - "candidate_2" TEXT, - "party" TEXT, - "alliance" TEXT, - "votes" INTEGER, - "%" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index candidate candidate_2 party alliance votes % - 0 candidate mauricio vila dosal national action party por yucata\u0301n al frente 447753 39.6 - 1 candidate mauricio sahui\u0301 rivero institutional revolutionary party todos por yucata\u0301n 407802 36.09 - 2 candidate joaqui\u0301n di\u0301az mena national regeneration movement juntos haremos historia 231330 20.46 -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.candidate_2 ( mauricio vila dosal ) , w0.party ( national action party ) , documents.title ( 2018 mexican general election ) -Q: Mauricio Vila Dosal of the National Action Party overwhelmingly won the race for Governor of Yucatán during the 2018 Mexican general election. -BlendSQL: -SELECT ( - {{ - LLMValidate( - 'Did Mauricio Vila Dosal of the National Action Party overwhelmingly win the race for Governor of Yucatán during the 2018 Mexican general election?', - (SELECT * FROM w0) - ) - }} -) - ---- - -Table Description: Johannes Voggenhuber -CREATE TABLE "w0" ( - "index" INTEGER, - "attribute" TEXT, - "value" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index attribute value - 0 johannes voggenhuber johannes voggenhuber - 1 spokesman of the green party spokesman of the green party - 2 preceded by freda meissner-blau -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.attribute ( spokesman of the green party , johannes voggenhuber ) , w0.value ( spokesman of the green party , johannes voggenhuber ) , documents.title ( johannes voggenhuber ) -BlendSQL: -SELECT ( - {{ - LLMValidate( - 'Was Johannes Voggenhuber born on 5 June 1950?', - (SELECT * FROM documents) - ) - }} -) AND ( - {{ - LLMValidate( - 'Was Johannes Voggenhuber former Member of the European Parliament (MEP) for the Austrian Green Party, which is part of the European Greens?', - (SELECT * FROM documents) - ) - }} -) - ---- - -Table Description: Jan Moir -CREATE TABLE "w0" ( - "index" INTEGER, - "jan moir" TEXT, - "born" TEXT, - "nationality" TEXT, - "occupation" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index jan moir born nationality occupation - 0 jan moir 1958-8 (age 62) british columnist, restaurant reviewer -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.jan moir ( jan moir ) , documents.title ( journalist , jan moir ) -Q: Sixty two year old Welsh journalist Jan Moir worked for a couple other papers before working at Daily Mail as an opinion columnist and has won several awards for her writing. -BlendSQL: -SELECT ( - SELECT {{LLMMap('What age?', 'w0::born')}} = 62 FROM w0 -) AND ( - {{ - LLMValidate( - 'Did Jan Moir work at a couple other papers before working at Daily Mail as an opinion columnist?', - (SELECT * FROM documents) - ) - }} -) AND ( - {{ - LLMValidate( - 'Has Jan Moir won several awards for her writing?', - (SELECT * FROM documents) - ) - }} -) - ---- - -Table Description: Paspels -CREATE TABLE "w0" ( - "index" INTEGER, - "languages in paspels" TEXT, - "languages in paspels_2" TEXT, - "languages in paspels_3" TEXT, - "languages in paspels_4" TEXT, - "languages in paspels_5" TEXT, - "languages in paspels_6" TEXT, - "languages in paspels_7" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index languages in paspels languages in paspels_2 languages in paspels_3 languages in paspels_4 languages in paspels_5 languages in paspels_6 languages in paspels_7 - 0 languages census 1980 census 1980 census 1990 census 1990 census 2000 census 2000 - 1 languages number percent number percent number percent - 2 german 246 77.36% 320 89.39% 386 94.38% -*/ - -Here are some values that may be useful: w0.languages in paspels ( romanish , languages ) -Q: Paspels use languages including German, and Romanish only and has recorded a total of 94.83% of German speakers in the 2000 census. -BlendSQL: -SELECT NOT EXISTS ( - SELECT * FROM w0 WHERE "languages in paspels" NOT IN ('german', 'romanish', 'languages') -) AND ( - SELECT "languages in paspels_7" = '94.83%' FROM w0 WHERE "languages in paspels" = 'german' -) - ---- - -Table Description: Adrian Barbullushi -CREATE TABLE "w0" ( - "index" INTEGER, - "personal information" TEXT, - "personal information_2" TEXT, - "personal information_3" TEXT, - "personal information_4" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index personal information personal information_2 personal information_3 personal information_4 - 0 date of birth personal information personal information personal information - 1 place of birth albania albania albania - 2 playing position(s) midfielder midfielder midfielder -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Here are some values that may be useful: w0.personal information_2 ( ionikos , egaleo ) , w0.personal information_3 ( albania ) , w0.personal information_4 ( albania ) , documents.title ( adrian barbullushi ) -Q: Retired Albanian football player Adrian Barbullushi never played for Egaleo or Ionikos. -BlendSQL: -SELECT NOT EXISTS( - SELECT * FROM w0 WHERE "personal information_2" = 'egaleo' -) AND NOT EXISTS ( - SELECT * FROM w0 WHERE "personal information_2" = 'ionikos' -) - ---- - -Table Description: 1994 Temple Owls football team -CREATE TABLE "w0" ( - "index" INTEGER, - "date" TEXT, - "time" TEXT, - "opponent" TEXT, - "site" TEXT, - "result" TEXT, - "attendance" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index date time opponent site result attendance - 0 september 3 time at akron* site w 32\u20137 attendance - 1 september 17 t18:0 east carolina* site l 14\u201331 9137 - 2 september 24 t18:0 at army* site w 23\u201320 9137 -*/ - -Here are some values that may be useful: w0.date ( october 22 , november 5 ) -Q: As part of their schedule, the Temple Owls football team played at Miami on November 5, 1994, losing 21–38, and played at Syracuse on October 22, losing 42–49. -BlendSQL: -SELECT EXISTS( - SELECT * FROM w0 WHERE date = 'november 5' AND {{LLMMap('Is this in Miami?', 'w0::opponent')}} = TRUE AND {{LLMMap('Did they lose 21-38?', 'w0::result')}} = TRUE -) AND EXISTS( - SELECT * FROM w0 WHERE date = 'october 22' AND {{LLMMap('Is this Syracuse?', 'w0::opponent')}} = TRUE AND {{LLMMap('Did they lose 42-49?', 'w0::result')}} = TRUE -) - ---- - -Table Description: Leon Haslam -CREATE TABLE "w0" ( - "index" INTEGER, - "season" INTEGER, - "series" TEXT, - "motorcycle" TEXT, - "team" TEXT, - "race" INTEGER, - "win" INTEGER, - "podium" INTEGER, - "pole" INTEGER, - "flap" INTEGER, - "pts" INTEGER, - "plcd" TEXT -) -/* -3 example rows: -SELECT * FROM "w0" LIMIT 3 - index season series motorcycle team race win podium pole flap pts plcd - 0 1998 125cc honda rs125r honda britain 1 0 0 0 0 0 nc - 1 1999 125cc honda rs125r honda britain 1 0 0 0 0 0 nc - 2 2000 125cc italjet f125 italjet moto 15 0 0 0 0 6 27th -*/ - -Here are some values that may be useful: w0.series ( british superbike , superbike ) -Q: Leon Haslam raced in the British Superbike Championship four years in a row, from 2005-2008, placing second in both 2006 and 2008. -BlendSQL: -SELECT (SELECT COUNT(DISTINCT season) = 4 FROM w0 WHERE series = 'british superbike' AND season BETWEEN 2005 AND 2008) -AND (SELECT plcd = '2nd' FROM w0 WHERE series = 'british superbike' AND season = 2006) -AND (SELECT plcd = '2nd' FROM w0 WHERE series = 'british superbike' AND season = 2008) - ---- \ No newline at end of file diff --git a/research/prompts/feverous/ingredients.txt b/research/prompts/feverous/ingredients.txt deleted file mode 100644 index be520ca2..00000000 --- a/research/prompts/feverous/ingredients.txt +++ /dev/null @@ -1,6 +0,0 @@ -If question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column using the scalar function: - >>> `{{LLMMap('question', 'table::column')}}` - -If mapping to a new column still cannot answer the question with valid SQL, turn to an end-to-end solution using the aggregate function: - >>> `{{LLMValidate('question', (blendsql))}}` -This function will take a question and BlendSQL context, and return either `0` or `1` depending on the truth value of the question. \ No newline at end of file diff --git a/research/prompts/hybridqa/few_shot.txt b/research/prompts/few_shot/hybridqa.txt similarity index 93% rename from research/prompts/hybridqa/few_shot.txt rename to research/prompts/few_shot/hybridqa.txt index 7203acfe..5e103808 100644 --- a/research/prompts/hybridqa/few_shot.txt +++ b/research/prompts/few_shot/hybridqa.txt @@ -1,5 +1,3 @@ -Examples: - CREATE TABLE "w" ( "index" INTEGER, "name" TEXT, @@ -17,8 +15,6 @@ SELECT * FROM w LIMIT 3 2 omar al-farooq mosque kandahar province kandahar 2014 built on the site that was a popular cinema of kandahar . [ 1 ] */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Here are some values that may be useful: w.city ( herat ) , documents.title ( herat , fire temple ) Q: Who were the builders of the mosque in Herat with fire temples ? BlendSQL: @@ -40,6 +36,38 @@ BlendSQL: --- +CREATE TABLE "w" ( + "index" INTEGER, + "camp name" TEXT, + "council" TEXT, + "location" TEXT, + "status" TEXT, + "notes" TEXT +) +/* +3 example rows: +SELECT * FROM w LIMIT 3 + index camp name council location status notes + 0 camp aquila ( formerly camp mauwehu ) connecticut yankee council candlewood lake , sherman , ct closed located on candlewood lake in sherman , ct. , the camp was sold in 1982 along with camp toquam in goshen + 1 camp cochipianee bristol area council goshen , ct closed the camp was founded in 1928 by the bristol area council and was sold after the new britain area council and the bristol area council were merged in 1972 + 2 camp irving housatonic council shelton , ct closed the camp was located in shelton in the birchbank area along the housatonic river . it was closed in 1945 and the buildings were razed in 1948 +*/ + +Q: What is the status of the camp in the town that split from Stonington in 1724 ? +BlendSQL: +SELECT status FROM w WHERE location = {{ + LLMQA( + 'Which town split from Stonington in 1724?', + ( + SELECT title, content FROM documents + WHERE documents MATCH 'stonington' + ), + options='w::location' + ) +}} + +--- + CREATE TABLE "w" ( "index" INTEGER, "no" INTEGER, @@ -56,8 +84,6 @@ SELECT * FROM w LIMIT 3 2 5 colin edwards castrol honda honda rc45 */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Q: After what season did the number 7 competitor retire ? BlendSQL: {{ @@ -94,8 +120,6 @@ SELECT * FROM w LIMIT 3 2 1963-64 bob brinkworth center rensselaer */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Here are some values that may be useful: w.year ( 1971-72 ) Q: What year was the 1971-72 ECAC Hockey Player of the Year born ? BlendSQL: @@ -133,8 +157,6 @@ SELECT * FROM w LIMIT 3 2 anthony booth 80 80th regiment of foot 1879-3-12 12 march 1879 battle of intombe , transvaal */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Q: What battle did the man born on 7 December 1839 fight in ? BlendSQL: SELECT {{LLMMap('Name of the battle?', 'w::place of action')}} FROM w WHERE name = {{ @@ -169,8 +191,6 @@ SELECT * FROM w LIMIT 3 2 banks s stadium walsall football stadium */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Here are some values that may be useful: w.name ( kia oval ) Q: What is the borough in which Kia Oval is located ? BlendSQL: @@ -206,8 +226,6 @@ SELECT * FROM w LIMIT 3 2 igman , veliko polje biathlon , cross-country skiing , nordic combined ( cross-country skiing ) not listed */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Q: What is the capacity of the venue that was named in honor of Juan Antonio Samaranch in 2010 after his death ? BlendSQL: SELECT capacity FROM w WHERE venue = {{ @@ -236,8 +254,6 @@ SELECT * FROM w LIMIT 3 2 3 ( 98 ) aaron pribanic right-handed pitcher university of nebraska */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Here are some values that may be useful: w.school ( university of georgia ) , documents.title ( university of georgia ) Q: Which teams has the player drafted by the Seattle Mariners in 2008 out of University of Georgia played for in the MLB ? BlendSQL: @@ -277,8 +293,6 @@ SELECT * FROM w LIMIT 3 2 3 boss 2 : back to rule 2017 baba yadav jeetz fireworks walzen media works jaaz multimedia \u20b9 10.5 crore ( us $ 1500000 ) */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Q: The story of a cab driver witnessing a murder by a criminal kingpin leads to extensive loss in an Indian film directed by one of the leading ad film makers in Kolkata who has made how many ad films in his career ? BlendSQL: {{ @@ -304,40 +318,6 @@ BlendSQL: --- -CREATE TABLE "w" ( - "index" INTEGER, - "camp name" TEXT, - "council" TEXT, - "location" TEXT, - "status" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM w LIMIT 3 - index camp name council location status notes - 0 camp aquila ( formerly camp mauwehu ) connecticut yankee council candlewood lake , sherman , ct closed located on candlewood lake in sherman , ct. , the camp was sold in 1982 along with camp toquam in goshen - 1 camp cochipianee bristol area council goshen , ct closed the camp was founded in 1928 by the bristol area council and was sold after the new britain area council and the bristol area council were merged in 1972 - 2 camp irving housatonic council shelton , ct closed the camp was located in shelton in the birchbank area along the housatonic river . it was closed in 1945 and the buildings were razed in 1948 -*/ - -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - -Q: What is the status of the camp in the town that split from Stonington in 1724 ? -BlendSQL: -SELECT status FROM w WHERE location = {{ - LLMQA( - 'Which town split from Stonington in 1724?', - ( - SELECT title, content FROM documents - WHERE documents MATCH 'stonington' - ), - options='w::location' - ) -}} - ---- - CREATE TABLE "w" ( "index" INTEGER, "medal" TEXT, @@ -356,8 +336,6 @@ SELECT * FROM "w" LIMIT 3 2 gold ryan lochte swimming men s 200 m freestyle 1:44.44 july 26 */ -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - Q: What is the name of the oldest person whose result , not including team race , was above 2 minutes ? BlendSQL: {{ @@ -398,8 +376,6 @@ SELECT * FROM w LIMIT 3 2 2013 online film & television association award best supporting actor in a motion picture or miniseries political animals nominated */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Q: How many social media sites are used to gather votes for the 2016 award ? BlendSQL: {{ @@ -435,8 +411,6 @@ SELECT * FROM w LIMIT 3 2 ca . 1100 bce hittite anatolian anatolia */ -CREATE VIRTUAL TABLE "documents" USING fts5(title, content, tokenize = \'trigram\') - Here are some values that may be useful: w.region ( mesopotamia ) , documents.title ( mesopotamia ) Q: What was the language family that was used in Hattusa , as well as parts of the northern Levant and Upper Mesopotamia ? BlendSQL: @@ -447,6 +421,4 @@ WHERE language = {{ (SELECT title, content FROM documents WHERE documents MATCH 'hattusa'), options='w::language' ) -}} - ---- \ No newline at end of file +}} \ No newline at end of file diff --git a/research/prompts/hybridqa/ingredients.txt b/research/prompts/hybridqa/ingredients.txt deleted file mode 100644 index ea144864..00000000 --- a/research/prompts/hybridqa/ingredients.txt +++ /dev/null @@ -1,10 +0,0 @@ -If question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column using the scalar function: - >>> `{{LLMMap('question', 'table::column')}}` - -If mapping to a new column still cannot answer the question with valid SQL, turn to an end-to-end solution using the aggregate function: - >>> `{{LLMQA('question', (blendsql))}}` - Optionally, this function can take an `options` argument to restrict its output to an existing SQL column. - For example: `... WHERE column = {{LLMQA('question', (blendsql), options='table::column)}}` - -If we need to do a `join` operation where there is imperfect alignment between table values, use the new function: - >>> `{{LLMJoin(left_on='table::column', right_on='table::column')}}` \ No newline at end of file diff --git a/research/prompts/ottqa/few_shot.txt b/research/prompts/ottqa/few_shot.txt deleted file mode 100644 index 455e00de..00000000 --- a/research/prompts/ottqa/few_shot.txt +++ /dev/null @@ -1,601 +0,0 @@ -When possible, use the alias `t` to refer to relevant table context, and `d` to refer to relevant document context. - -Examples: - -CREATE TABLE "./List of Rangers F.C. records and statistics (0)" ( - "#" INTEGER, - "player" TEXT, - "to" TEXT, - "fee" TEXT, - "date" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of Rangers F.C. records and statistics (0)" LIMIT 3 - # player to fee date - 1 alan hutton tottenham hotspur \u00a39000000 2008-1-30 - 2 giovanni van bronckhorst arsenal \u00a38500000 2001-6-20 - 3 jean-alain boumsong newcastle united \u00a38000000 2005-1-1 -*/ - -CREATE TABLE "./List of Rangers F.C. records and statistics (1)" ( - "#" INTEGER, - "player" TEXT, - "from" TEXT, - "fee" TEXT, - "date" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of Rangers F.C. records and statistics (1)" LIMIT 3 - # player from fee date - 1 tore andr\u00e9 flo chelsea \u00a312000000 2000-11-23 - 2 ryan kent liverpool \u00a36500000 2019-9-2 - 2 michael ball everton \u00a36500000 2001-8-20 -*/ - -CREATE TABLE "./List of Rangers F.C. players (2)" ( - "inductee" TEXT, - "induction year" TEXT, - "position" TEXT, - "rangers career" TEXT, - "appearances" INTEGER, - "honours" TEXT, - "interntional caps" INTEGER -) -/* -3 example rows: -SELECT * FROM "./List of Rangers F.C. players (2)" LIMIT 3 - inductee induction year position rangers career appearances honours interntional caps - moses mcneil 2000 mf 1872-1882 34 none 2 - peter mcneil 2010 mf 1872-1877 7 none 0 -peter campbell 2010 fw 1872-1879 24 none 2 -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: When was the third highest paid Rangers F.C . player born ? -BlendSQL: -{{ - LLMQA( - 'When was the Rangers Player born?' - ( - WITH t AS ( - SELECT player FROM ( - SELECT * FROM "./List of Rangers F.C. records and statistics (0)" - UNION ALL SELECT * FROM "./List of Rangers F.C. records and statistics (1)" - ) ORDER BY trim(fee, '£') DESC LIMIT 1 OFFSET 2 - ), d AS ( - SELECT * FROM documents JOIN t WHERE documents MATCH '"' || t.player || '"' || ' OR rangers OR fc' ORDER BY rank LIMIT 5 - ) SELECT d.content, t.player AS 'Rangers Player' FROM d JOIN t - ) - ) -}} - ---- - -CREATE TABLE "./2006 League of Ireland Premier Division (1)" ( - "team" TEXT, - "manager" TEXT, - "main sponsor" TEXT, - "kit supplier" TEXT, - "stadium" TEXT, - "capacity" INTEGER -) -/* -3 example rows: -SELECT * FROM "./2006 League of Ireland Premier Division (1)" LIMIT 3 - team manager main sponsor kit supplier stadium capacity - bohemians gareth farrelly des kelly carpets o\'neills dalymount park 8500 -bray wanderers eddie gormley slevin group adidas carlisle grounds 7000 - cork city damien richardson nissan o\'neills turners cross 8000 -*/ - -CREATE TABLE "./2006 League of Ireland Premier Division (5)" ( - "team" TEXT, - "manager" TEXT, - "main sponsor" TEXT, - "kit supplier" TEXT, - "stadium" TEXT, - "capacity" INTEGER -) -/* -3 example rows: -SELECT * FROM "./2006 League of Ireland Premier Division (5)" LIMIT 3 - team manager main sponsor kit supplier stadium capacity - bohemians gareth farrelly des kelly carpets o\'neills dalymount park 8500 -bray wanderers eddie gormley slevin group adidas carlisle grounds 7000 - cork city damien richardson nissan o\'neills turners cross 8000 -*/ - -CREATE TABLE "./2006 SK Brann season (2)" ( - "date" TEXT, - "host" TEXT, - "agg" TEXT, - "visitor" TEXT, - "ground" TEXT, - "attendance" TEXT, - "tournament" TEXT -) -/* -3 example rows: -SELECT * FROM "./2006 SK Brann season (2)" LIMIT 3 - date host agg visitor ground attendance tournament - 2 july ham-kam 4-0 brann briskeby gressbane , hamar 6218 tippeligaen - 5 july brann 3-1 levanger il brann stadion , bergen 1948 norwegian cup -13 july glentoran 0-1 brann the oval , belfast 1743 uefa cup qualifying round -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: The home stadium of the Bray Wanderers of 2006 League of Ireland is situated behind what station ? -BlendSQL: -{{ - LLMQA( - 'What station is the Bray Wanderers home stadium situated behind?', - ( - WITH t AS ( - SELECT stadium FROM "./2006 League of Ireland Premier Division (1)" WHERE team = 'bray wanderers' - ), d AS ( - SELECT * FROM documents JOIN t WHERE documents MATCH '"' || t.stadium || '"' ORDER BY rank LIMIT 5 - ) SELECT d.content, t.stadium AS 'Home Stadium' FROM d JOIN t - ) - ) -}} - ---- - -CREATE TABLE "./List of medical recipients of the Victoria Cross (0)" ( - "name" TEXT, - "regiment/corps" TEXT, - "date" TEXT, - "conflict" TEXT, - "location" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of medical recipients of the Victoria Cross (0)" LIMIT 3 - name regiment/corps date conflict location -harold ackroyd royal berkshire regiment (xxxx-7-311917-8-1,p-38715d) first world war passchendaele - william allen royal artillery 1916-9-3 first world war near mesnil - henry andrews indian medical services 1919-10-22 waziristan campaign khajuri post -*/ - -CREATE TABLE "./List of living recipients of the George Cross (0)" ( - "name" TEXT, - "year of award" INTEGER, - "location of gallantry" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of living recipients of the George Cross (0)" LIMIT 3 - name year of award location of gallantry - henry flintoff 1944 farndale , north yorkshire - alf lowe 1949 portland harbour , dorset -margaret purves nee vaughan 1949 near sully island , vale of glamorgan -*/ - -CREATE TABLE "./List of Australian Victoria Cross recipients (0)" ( - "name" TEXT, - "date of action" TEXT, - "conflict" TEXT, - "unit" TEXT, - "place of action" TEXT, - "location of medal" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of Australian Victoria Cross recipients (0)" LIMIT 3 - name date of action conflict unit place of action location of medal -charles anderson 1942 second world war 2/19th battalion muar river , malaya awm - thomas axford 1918 first world war 16th battalion hamel wood , france awm - peter badcoe 1967* vietnam war australian army training team huong tra , vietnam awm -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: What nationality was the winner of the 1945 award of the Victoria Cross ? -BlendSQL: -{{ - LLMQA( - "What was the Victoria Cross recipient's nationality?", - ( - WITH t AS ( - SELECT name FROM "./List of medical recipients of the Victoria Cross (0)" - WHERE SUBSTR(date, 0, 5) = '1945' - ), d AS ( - SELECT * FROM documents JOIN t WHERE documents MATCH '"' || t.name || '"' || ' OR victoria cross' ORDER BY rank LIMIT 5 - ) SELECT d.content, t.name AS recipient FROM d JOIN t - ) - ) -}} - ---- - -CREATE TABLE "./Atlantic Hockey Player of the Year (1)" ( - "year" TEXT, - "winner" TEXT, - "position" TEXT, - "school" TEXT -) -/* -3 example rows: -SELECT * FROM "./Atlantic Hockey Player of the Year (1)" LIMIT 3 - year winner position school -2019-20 jason cotton forward sacred heart -2018-19 joseph duszak defenceman mercyhurst -2017-18 dylan mclaughlin forward cansius -*/ - -CREATE TABLE "./List of Atlantic Hockey Most Valuable Player in Tournament (0)" ( - "year" INTEGER, - "winner" TEXT, - "position" TEXT, - "school" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of Atlantic Hockey Most Valuable Player in Tournament (0)" LIMIT 3 - year winner position school - 2004 greg kealey forward holy cross - 2005 scott champagne left wing mercyhurst - 2006 james sixsmith left wing holy cross -*/ - -CREATE TABLE "./Atlantic Hockey Player of the Year (0)" ( - "year" TEXT, - "winner" TEXT, - "position" TEXT, - "school" TEXT -) -/* -3 example rows: -SELECT * FROM "./Atlantic Hockey Player of the Year (0)" LIMIT 3 - year winner position school -2018-19 joseph duszak defenceman mercyhurst -2017-18 dylan mclaughlin forward cansius -2016-17 charles williams goaltender cansius -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: Which NHL team has the Player of the Year of Atlantic Hockey for the season ending in 2019 signed a agreement with ? -BlendSQL: -{{ - LLMQA( - 'Which team has the NHL player signed with?', - ( - WITH t AS ( - SELECT winner FROM ( - SELECT * FROM "./Atlantic Hockey Player of the Year (0)" - UNION ALL SELECT * FROM "./Atlantic Hockey Player of the Year (1)" - ) AS w WHERE {{LLMMap('Does this end in 2019?', 'w::year')}} = TRUE - ), d AS ( - SELECT * FROM documents JOIN t WHERE documents MATCH '"' || t.winner || '"' || ' OR hockey' ORDER BY rank LIMIT 5 - ) SELECT d.content, t.winner AS 'NHL Player' FROM d JOIN t - ) - ) -}} - ---- - -CREATE TABLE "./Cuba at the UCI Track Cycling World Championships (1)" ( - "name" TEXT, - "event" TEXT, - "result" TEXT, - "rank" TEXT -) -/* -3 example rows: -SELECT * FROM "./Cuba at the UCI Track Cycling World Championships (1)" LIMIT 3 - name event result rank -lisandra guerra women \'s sprint 11.121 ( q ) , 18 -lisandra guerra women \'s 500 m time trial 34.226 9 - marlies mejias women \'s individual pursuit 3:35.57 8 -*/ - -CREATE TABLE "./Cuba at the UCI Track Cycling World Championships (2)" ( - "medal" TEXT, - "championship" TEXT, - "name" TEXT, - "event" TEXT -) -/* -3 example rows: -SELECT * FROM "./Cuba at the UCI Track Cycling World Championships (2)" LIMIT 3 - medal championship name event - gold 2003 stuttgart yoanka gonz\u00e1lez women \'s points race - gold 2004 melbourne yoanka gonz\u00e1lez women \'s scratch -bronze 2006 bordeaux lisandra guerra women \'s 500 m time trial -*/ - -CREATE TABLE "./Cuba at the UCI Track Cycling World Championships (0)" ( - "name" TEXT, - "event" TEXT, - "result" TEXT, - "rank" INTEGER -) -/* -3 example rows: -SELECT * FROM "./Cuba at the UCI Track Cycling World Championships (0)" LIMIT 3 - name event result rank -lisandra guerra rodriguez women \'s sprint none 30 -lisandra guerra rodriguez women \'s 500 m time trial pt34.692s 9 -lisandra guerra rodriguez women \'s keirin none 13 -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: In which Track Cycling World Championships event was the person born in Matanzas , Cuba ranked highest ? -BlendSQL: -{{ - LLMQA( - 'In what event was the cyclist ranked highest?', - ( - SELECT * FROM ( - SELECT * FROM "./Cuba at the UCI Track Cycling World Championships (2)" - ) as w WHERE w.name = {{ - LLMQA( - "Which cyclist was born in Matanzas, Cuba?", - ( - SELECT * FROM documents - WHERE documents MATCH 'matanzas OR cycling OR track OR born' - ORDER BY rank LIMIT 3 - ), - options="w::name" - ) - }} - ), - options='w::event' - ) -}} - ---- - -CREATE TABLE "./2011 Thai Premier League (1)" ( - "team" TEXT, - "sponsor" TEXT, - "kit maker" TEXT, - "team captain" TEXT, - "head coach" TEXT -) -/* -3 example rows: -SELECT * FROM "./2011 Thai Premier League (1)" LIMIT 3 - team sponsor kit maker team captain head coach - army united chang pan wanchana rattana adul rungruang - bangkok glass leo beer umbro amnaj kaewkiew arjhan srong-ngamsub -bec tero sasana 3000 battery fbt teeratep winothai phayong khunnaen -*/ - -CREATE TABLE "./2013 Thai Premier League (5)" ( - "team" TEXT, - "head coach" TEXT, - "captain" TEXT, - "kit manufacturer" TEXT, - "shirt sponsor" TEXT -) -/* -3 example rows: -SELECT * FROM "./2013 Thai Premier League (5)" LIMIT 3 - team head coach captain kit manufacturer shirt sponsor - army united alexandr\u00e9 p\u00f6lking chaiwat nak-iem pan chang - bangkok glass attaphol buspakom teeratep winothai umbro leo beer -bangkok united sasom pobprasert nattaporn phanrit fbt true -*/ - -CREATE TABLE "./2012 Thai Premier League (0)" ( - "team" TEXT, - "sponsor" TEXT, - "kit maker" TEXT, - "team captain" TEXT, - "head coach" TEXT -) -/* -3 example rows: -SELECT * FROM "./2012 Thai Premier League (0)" LIMIT 3 - team sponsor kit maker team captain head coach - army united chang pan tatree sing-ha paniphon kerdyam - bangkok glass leo beer umbro amnart kaewkiew phil stubbins -bec tero sasana channel 3 fbt rangsan viwatchaichok sven-g\u00f6ran eriksson -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: What is the home stadium of the team Buriram United whose team captain is Apichet Puttan ? - -BlendSQL: -{{ - LLMQA( - 'What is the home stadium of Buriram United?', - ( - SELECT * FROM documents WHERE documents MATCH 'buriram united' ORDER BY rank LIMIT 5 - ) - ) -}} - ---- - -CREATE TABLE "./List of fictional canines in animation (2)" ( - "name" TEXT, - "species" TEXT, - "origin" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of fictional canines in animation (2)" LIMIT 3 - name species origin notes - aleu wolfdog balto ii : wolf quest aleu is a wolfdog like her father balto -balto wolfdog balto balto is a wolf-dog hybrid , shunned by both humans and dogs in the town of nome . he is a rugged spirit , adventurer of his social domain ; a rebel soul , no 1 to turn to but himself . his only friends are boris , a russian goose , jenna , a siberian husky and muk and luk , 2 polar bears . balto and boris live on a grounded boat outside nome , while muk and luk are occasional visitors -danny dingo blinky bill oldest brother of the family and main antagonist of the first season -*/ - -CREATE TABLE "./List of fictional canines in animation (1)" ( - "name" TEXT, - "origin" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of fictional canines in animation (1)" LIMIT 3 - name origin notes - antoine d\'coolette sonic the hedgehog none -bent-tail the coyote various walt disney cartoons a brown coyote who appeared as a nemesis of pluto in a few cartoons - bent-tail junior various walt disney cartoons bent-tail \'s unintelligent son -*/ - -CREATE TABLE "./List of fictional canines in animation (0)" ( - "name" TEXT, - "origin" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of fictional canines in animation (0)" LIMIT 3 - name origin notes -brother fox song of the south fox who tries to eat br\'er rabbit and often collaborates with br\'er fox to achieve this goal -brother fox coonskin a satirical subversion of joel chandler harris and disney \'s similar character from song of the south , reimagined as an african-american - cajun fox courage the cowardly dog none -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: What is the setting of the animated series featuring the fictional canine Daisy the Dingo ? -BlendSQL: -{{ - LLMQA( - 'Where is the animated TV series set?', - ( - WITH t AS ( - SELECT origin FROM "./List of fictional canines in animation (2)" AS w - WHERE w.name = 'daisy' AND w.species = 'dingo' - ), d AS ( - SELECT * FROM documents JOIN t WHERE documents MATCH '"' || t.origin || '"' || ' OR animated OR set' ORDER BY rank LIMIT 5 - ) SELECT d.content, t.origin AS 'Animated TV Series' FROM d JOIN t - ) - ) -}} - ---- - -CREATE TABLE "./Primera B Nacional (0)" ( - "season" TEXT, - "champion" TEXT, - "runner-up" TEXT, - "third place" TEXT -) -/* -3 example rows: -SELECT * FROM "./Primera B Nacional (0)" LIMIT 3 - season champion runner-up third place -1986-87 deportivo armenio banfield belgrano -1987-88 deportivo mandiy\u00fa san martin ( t ) chaco for ever -1988-89 chaco for ever uni\u00f3n col\u00f3n -*/ - -CREATE TABLE "./Categor\u00eda Primera B (2)" ( - "season" TEXT, - "champion ( title count )" TEXT, - "runner-up" TEXT, - "third place" TEXT -) -/* -3 example rows: -SELECT * FROM "./Categor\u00eda Primera B (2)" LIMIT 3 -season champion ( title count ) runner-up third place - 1991 envigado ( 1 ) alianza llanos atl\u00e9tico huila - 1992 atl\u00e9tico huila ( 1 ) alianza llanos cortulu\u00e1 - 1993 cortulu\u00e1 ( 1 ) fiorentina de florencia atl\u00e9tico palmira -*/ - -CREATE TABLE "./Primera B Nacional (1)" ( - "team" TEXT, - "titles" INTEGER, - "years won" TEXT -) -/* -3 example rows: -SELECT * FROM "./Primera B Nacional (1)" LIMIT 3 - team titles years won -banfield 3 1992-93 , 2000-1 , 2013-14 - olimpo 3 2001-2 , 2006-7 , 2009-10 - hurac\u00e1n 2 1989-90 , 1999-0 -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: Which Primera B Nacional team finished second in the year the club founded on 21 January 1896 finished third ? -BlendSQL: -SELECT "runner-up" FROM "./Primera B Nacional (0)" AS w -WHERE "third place" = {{ - LLMQA( - 'Which club was founded on 21 January 1896?' - (SELECT * FROM documents WHERE documents MATCH 'primera OR founded OR (club AND 1896)' ORDER BY rank LIMIT 5) - options='w::third place' - ) -}} - ---- - -CREATE TABLE "./List of African films (4)" ( - "year" INTEGER, - "title" TEXT, - "director" TEXT, - "genre" TEXT -) -/* -3 example rows: -SELECT * FROM "./List of African films (4)" LIMIT 3 - year title director genre - 1972 kouami metonou do kokou short - 1979 au rendez-vous du r\u00eave ab\u00eati kodjo goncalves short documentary - 1986 the blooms of banjeli carlyn saltman short documentary -*/ - -CREATE TABLE "./Cinema of Chad (0)" ( - "year" INTEGER, - "title" TEXT, - "director" TEXT, - "genre" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "./Cinema of Chad (0)" LIMIT 3 - year title director genre notes - 1958 the roots of heaven john huston drama , action american film partly shot in chad - 1960 les tonnes de l\'audace - mission t\u00e9n\u00e9r\u00e9 tchad ren\u00e9 quinet & louis sommet documentary none - 1966 p\u00eacheurs du chari edouard sailly ethnographic short english title : fishers of the chari -*/ - -CREATE TABLE "./Cinema of Chad (1)" ( - "year" INTEGER, - "title" TEXT, - "director" TEXT, - "genre" TEXT, - "notes" TEXT -) -/* -3 example rows: -SELECT * FROM "./Cinema of Chad (1)" LIMIT 3 - year title director genre notes - 1958 the roots of heaven john huston drama , action american film partly shot in chad - 1960 les tonnes de l\'audace - mission t\u00e9n\u00e9r\u00e9 tchad ren\u00e9 quinet & louis sommet documentary none - 1966 p\u00eacheurs du chari edouard sailly ethnographic short english title : fishers of the chari -*/ - -CREATE VIRTUAL TABLE documents USING fts5(title, content, tokenize = \'trigram\') - -Q: Who is the director the Togolese film that was a 30 minute film that was shot in 16mm ? -BlendSQL: -SELECT director FROM "./List of African films (4)" as w -WHERE title = {{ - LLMQA( - 'What is the name of the Togolese film that was 30 minutes and shot in 16mm?' - (SELECT * FROM documents WHERE documents MATCH 'togolese OR 30 OR 16mm OR film' ORDER BY rank LIMIT 5) - options='w::title' - ) -}} - ---- \ No newline at end of file diff --git a/research/prompts/ottqa/ingredients.txt b/research/prompts/ottqa/ingredients.txt deleted file mode 100644 index ea144864..00000000 --- a/research/prompts/ottqa/ingredients.txt +++ /dev/null @@ -1,10 +0,0 @@ -If question-relevant column(s) contents are not suitable for SQL comparisons or calculations, map it to a new column using the scalar function: - >>> `{{LLMMap('question', 'table::column')}}` - -If mapping to a new column still cannot answer the question with valid SQL, turn to an end-to-end solution using the aggregate function: - >>> `{{LLMQA('question', (blendsql))}}` - Optionally, this function can take an `options` argument to restrict its output to an existing SQL column. - For example: `... WHERE column = {{LLMQA('question', (blendsql), options='table::column)}}` - -If we need to do a `join` operation where there is imperfect alignment between table values, use the new function: - >>> `{{LLMJoin(left_on='table::column', right_on='table::column')}}` \ No newline at end of file diff --git a/research/prompts/parser_program.py b/research/prompts/parser_program.py deleted file mode 100644 index 9617f06d..00000000 --- a/research/prompts/parser_program.py +++ /dev/null @@ -1,43 +0,0 @@ -from blendsql._program import Program -from guidance import gen - -BASE_SYSTEM_PROMPT = """ -Generate BlendSQL given the question, table, and passages to answer the question correctly. -BlendSQL is a superset of SQLite, which adds external function calls for information not found within native SQLite. -These external functions should be wrapped in double curly brackets. - -{ingredients_prompt} - -Additionally, we have the table `documents` at our disposal, which contains Wikipedia articles providing more details about the values in our table. -ONLY use BlendSQL ingredients if necessary. -Answer parts of the question in vanilla SQL, if possible. -""" - - -class ParserProgram(Program): - def __call__( - self, - ingredients_prompt: str, - few_shot_prompt: str, - serialized_db: str, - question: str, - bridge_hints: str = None, - **kwargs, - ): - with self.systemcontext: - self.model += BASE_SYSTEM_PROMPT.format( - ingredients_prompt=ingredients_prompt - ) - with self.usercontext: - self.model += f"{few_shot_prompt}\n\n" - self.model += f"{serialized_db}\n\n" - if bridge_hints: - self.model += ( - f"Here are some values that may be useful: {bridge_hints}\n" - ) - self.model += f"Q: {question}\n" - self.model += f"BlendSQL:\n" - print(self.model._current_prompt()) - with self.assistantcontext: - self.model += gen(name="result", **self.gen_kwargs) - return self.model diff --git a/research/requirements.txt b/research/requirements.txt new file mode 100644 index 00000000..3bc88c04 --- /dev/null +++ b/research/requirements.txt @@ -0,0 +1,4 @@ +blendsql +grammar-guide +torch +transformers \ No newline at end of file diff --git a/research/run-evaluate.py b/research/run-evaluate.py index 6328c5ce..e0d300e3 100644 --- a/research/run-evaluate.py +++ b/research/run-evaluate.py @@ -1,578 +1,45 @@ -import copy -import os -import logging -import sys -import shutil - -import sqlglot -from colorama import Fore -import re -import textwrap - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -import sys from pathlib import Path -import json -from tqdm import tqdm -from dataclasses import asdict -import guidance -import time -import numpy as np -from typing import List, Union, Callable -from attr import attrs, attrib -from sqlglot import parse_one, exp - -import datasets -from datasets.metric import Metric -from transformers.training_args_seq2seq import Seq2SeqTrainingArguments -from transformers.hf_argparser import HfArgumentParser +import blendsql +from blendsql.models import OpenaiLLM +from blendsql.ingredients import LLMQA, LLMMap from blendsql.db import SQLite -from blendsql.db.utils import double_quote_escape -from blendsql import LLMMap, LLMQA, LLMJoin, LLMValidate, blend -from blendsql._dialect import FTS5SQLite -from blendsql._smoothie import Smoothie -from blendsql.grammars._peg_grammar import grammar -from blendsql.models import AzureOpenaiLLM -from blendsql.utils import sub_tablename - -from research.utils.dataset import DataArguments, DataTrainingArguments -from research.utils.dataset_loader import load_dataset -from research.utils.args import ModelArguments -from research.constants import SINGLE_TABLE_NAME, EvalField -from research.prompts.parser_program import ParserProgram -from research.prompts.sagemaker_program import SageMakerLLM, get_sagemaker_prompt - - -class NpEncoder(json.JSONEncoder): - def default(self, obj): - if isinstance(obj, np.integer): - return int(obj) - if isinstance(obj, np.floating): - return float(obj) - if isinstance(obj, np.ndarray): - return obj.tolist() - return super(NpEncoder, self).default(obj) - - -def fewshot_parse_to_blendsql(model: "Model", **input_program_args) -> str: - """Calls an endpoint_name and generates a BlendSQL query.""" - if isinstance(model, SageMakerLLM): - prompt = get_sagemaker_prompt(**input_program_args) - return model.predict(prompt) - else: - # Dedent str args - for k, v in input_program_args.items(): - if isinstance(v, str): - input_program_args[k] = textwrap.dedent(v) - res = model.predict(program=ParserProgram, **input_program_args) - return textwrap.dedent(res["result"]) - - -def post_process_blendsql(blendsql: str, db: SQLite, use_tables=None) -> str: - """Clean up some common mistakes made by Model parser. - This includes: - - Aligning hallucinated columns to their closest match in the database - - Wrapping all column references in double quotes - - ONLY if it's not already within quotes (', ") - """ - - def parse_str_and_add_columns( - s: str, valid_columns: set, real_colname_to_hallucinated: dict - ): - """Splits on underscores, and adds this as a possible fix for hallucinated columns.""" - try: - node = parse_one(s, dialect=FTS5SQLite) - for n in node.find_all(exp.Column): - if n.name not in valid_columns: - split_on_underscores = " ".join(n.name.split("_")) - if split_on_underscores in valid_columns: - real_colname_to_hallucinated[split_on_underscores] = n.name - except sqlglot.ParseError: - pass - return real_colname_to_hallucinated - - if use_tables is None: - use_tables = set( - filter(lambda x: not x.startswith("documents"), list(db.iter_tables())) - ) - blendsql = blendsql.replace("`", "'") - blendsql = blendsql.replace("{{LLM(", "{{LLMMap(") - # Below fixes case where we miss a ')' - # SELECT MAX({{LLMMap('total penalties?', 'w::penalties (p+p+s+s)'}}) FROM w - blendsql = re.sub(r"((MAX|MIN)\(\{\{.*?)(\'}}\))", r"\1')}})", blendsql) - blendsql = re.sub("'}}", "')}}", blendsql) - # Handle mistakes like {{LLMMap('field goal percentage?'; 'w::field goal\xa0%')}} - blendsql = re.sub(r"(\'|\"); ", r"\1,", blendsql) - - # Fix escaped quotes - # for match in [i for i in re.finditer(r'(?<=\=) ?((\'|\").*?(\'|\"))(\s|$)', blendsql)][::-1]: - # text = match.group(1) - # if text[0] == '"': - # blendsql = blendsql[:match.start()] + text[0] + re.sub('"', ' ', text[1:-1]) + text[-1] + blendsql[match.end():] - # elif text[0] == "'": - # blendsql = blendsql[:match.start()] + text[0] + re.sub("'", ' ', text[1:-1]) + text[-1] + blendsql[match.end():] - # # blendsql = blendsql[:match.start()] + " " + re.sub(r'\\.', ' ', text) + " " + blendsql[match.end():] - # - # # Fix escaped strings in single quotes - # for match in [i for i in re.finditer(r'(\'[^\']*?\')(,)', blendsql)][::-1]: - # text = match.group(1) - # if "'" in text[1:-1]: - # blendsql = re.sub(text[1:-1], text[1:-1].replace("'", " "), blendsql) - - # Fix common non-alphanumeric FTS5 mistakes - # for match in re.finditer(r'(?<=MATCH )(\'|\").*(\'|\")', blendsql): - # fts5_q = match.group() - # blendsql = re.sub(r'MATCH {}'.format(fts5_q), f'MATCH \'{re.sub(r"-", " ", fts5_q[1:-1])}\'', blendsql) - # blendsql = re.sub(r'MATCH {}'.format(fts5_q), f'MATCH \'{re.sub(r"[^0-9a-zA-Z ]", "", fts5_q[1:-1])}\'', blendsql) - - quotes_start_end = [i.start() for i in re.finditer(r"(\'|\")", blendsql)] - quotes_start_end_spans = list(zip(*(iter(quotes_start_end),) * 2)) - - # Find some hallucinated column names - flatten = lambda xss: set([x for xs in xss for x in xs]) - valid_columns = flatten( - [list(i) for i in list(db.iter_columns(table) for table in use_tables)] - ) - try: - real_colname_to_hallucinated = {} - real_colname_to_hallucinated = parse_str_and_add_columns( - blendsql, valid_columns, real_colname_to_hallucinated - ) - - for parse_results, _, _ in grammar.scanString(blendsql): - parsed_results_dict = parse_results.as_dict() - for arg_type in {"args", "kwargs"}: - for idx in range(len(parsed_results_dict[arg_type])): - curr_arg = parsed_results_dict[arg_type][idx] - if not isinstance(curr_arg, str): - continue - parsed_results_dict[arg_type][idx] = re.sub( - r"(^\()(.*)(\)$)", r"\2", curr_arg - ).strip() - if len(parsed_results_dict["args"]) > 0: - blendsql = re.sub( - re.escape(parsed_results_dict["args"][0]), - re.sub(r"(\'|\")", "", parsed_results_dict["args"][0]), - blendsql, - ) - if len(parsed_results_dict["args"]) > 1: - potential_subquery = re.sub( - r"JOIN(\s+){{.+}}", - "", - parsed_results_dict["args"][1], - flags=re.DOTALL, - ) - try: - real_colname_to_hallucinated = parse_str_and_add_columns( - potential_subquery, valid_columns, real_colname_to_hallucinated - ) - except: - pass - - for k, v in real_colname_to_hallucinated.items(): - blendsql = sub_tablename(v, k, blendsql) - except: - pass - # Put all tablenames in quotes - for tablename in db.iter_tables(use_tables=use_tables): - for columnname in sorted( - list(db.iter_columns(tablename)), key=lambda x: len(x), reverse=True - ): - # Reverse finditer so we don't mess up indices when replacing - # Only sub if surrounded by: whitespace, comma, or parentheses - # Or, prefaced by period (e.g. 'p.Current_Value') - # AND it's not already in quotes - for m in list( - re.finditer( - r"(?<=(\s|,|\(|\.)){}(?=(\s|,|\)|;|$))".format( - re.escape(columnname) - ), - blendsql, - ) - )[::-1]: - # Check if m.start already occurs within quotes (' or ") - # If it does, don't add quotes - if any( - start + 1 < m.start() < end - for (start, end) in quotes_start_end_spans - ): - continue - blendsql = ( - blendsql[: m.start()] - + '"' - + double_quote_escape( - blendsql[m.start() : m.start() + (m.end() - m.start())] - ) - + '"' - + blendsql[m.end() :] - ) - return blendsql - - -@attrs -class BlendSQLEvaluation: - output_dir: Union[str, Path] = attrib() - split: datasets.Split = attrib() - split_name: str = attrib() - parser_endpoint: Union[AzureOpenaiLLM, None] = attrib() - blender_endpoint: Union[AzureOpenaiLLM, None] = attrib() - prompt_and_pray_endpoint: Union[AzureOpenaiLLM, None] = attrib() - model_args: ModelArguments = attrib() - data_args: DataArguments = attrib() - data_training_args: DataTrainingArguments = attrib() - db: SQLite = attrib(default=None) - - results: List[dict] = attrib(init=False) - results_dict: dict = attrib(init=False) - num_with_ingredients: int = attrib(init=False) - num_errors: int = attrib(init=False) - def __attrs_post_init__(self): - self.output_dir = Path(self.output_dir) - self.results = [] - self.num_with_ingredients = 0 - self.num_errors = 0 - def _init_results_dict(self): - return { - EvalField.UID: None, - "dataset_vars": None, - "idx": None, - "input_program_args": None, - EvalField.DB_PATH: None, - EvalField.PRED_BLENDSQL: None, - "num_few_shot_examples": None, - EvalField.PREDICTION: [""], - EvalField.GOLD_ANSWER: None, - "solver": None, - "error": None, - "num_prompt_tokens": 0, - } - - def iter_eval(self): - logger.info("*** Evaluate ***") - logger.info(f"--- {len(self.split)} examples ---") - for _idx, item in tqdm( - enumerate(self.split), - desc=f"Running over {self.split_name}_split...", - total=len(self.split), - ): - if (_idx % self.data_training_args.save_every) == 0: - print( - Fore.WHITE + "Saving predictions.json as checkpoint..." + Fore.RESET - ) - with open(self.output_dir / "predictions.json", "w") as f: - json.dump(self.results, f, indent=4, cls=NpEncoder) - self.results_dict = self._init_results_dict() - _item = copy.deepcopy(item) - for v in [ - value - for name, value in vars(EvalField).items() - if not name.startswith("_") - ]: - if v in _item: - self.results_dict[v] = _item.pop(v) - self.results_dict["dataset_vars"] = { - k: v - for k, v in _item.items() - if k not in {"passages", "table", "input_program_args"} - } - self.results_dict["idx"] = _idx - entire_serialized_db = None - if "entire_serialized_db" in item["input_program_args"]: - entire_serialized_db = item["input_program_args"].pop( - "entire_serialized_db" - ) - self.results_dict["input_program_args"] = { - k: v - for k, v in item["input_program_args"].items() - if k - not in { - "examples", - "program", - "endpoint_name", - "few_shot_prompt", - "ingredient_prompt", - } - } - self.results_dict[EvalField.DB_PATH] = item[EvalField.DB_PATH] - if self.db is None: - db = SQLite(item[EvalField.DB_PATH]) - else: - db = self.db - if not self.data_training_args.bypass_models: - if not self.data_training_args.prompt_and_pray_only: - pred_text = self._get_blendsql_prediction(item, db) - if self.data_training_args.fallback_to_prompt_and_pray: - # Fallback to end-to-end QA prompt - if ( - any(x in pred_text for x in ["table", "passage", "text"]) - and any(x in pred_text for x in ["not", "empty"]) - ) or pred_text.strip() == "": - if entire_serialized_db is None: - raise ValueError( - "Trying to fallback to end-to-end, but no `entire_serialized_db` variable found!" - ) - _ = self._get_prompt_and_pray_prediction( - item, entire_serialized_db - ) - elif self.data_training_args.prompt_and_pray_only: - _ = self._get_prompt_and_pray_prediction(item, entire_serialized_db) - self.results.append(self.results_dict) - # Log predictions to console - print() - print(Fore.MAGENTA + item[EvalField.QUESTION] + Fore.RESET) - if self.results_dict[EvalField.PRED_BLENDSQL] is not None: - print( - Fore.CYAN + self.results_dict[EvalField.PRED_BLENDSQL] + Fore.RESET - ) - print( - Fore.MAGENTA + f"ANSWER: '{self.results_dict[EvalField.GOLD_ANSWER]}'" - ) - if self.results_dict[EvalField.PREDICTION] is not None: - print( - Fore.CYAN - + str(self.results_dict[EvalField.PREDICTION]) - + Fore.RESET - ) - print() - with open(self.output_dir / "predictions.json", "w") as f: - json.dump(self.results, f, indent=4, cls=NpEncoder) - - def _get_prompt_and_pray_prediction(self, item: dict, entire_serialized_db: str): - try: - to_add = {"solver": "prompt-and-pray"} - res = self.prompt_and_pray_endpoint.predict( - program=programs.zero_shot_qa_program_chat, - question=item["input_program_args"]["question"], - serialized_db=entire_serialized_db, - ) - final_str_pred: str = [res.get("result", "")] - to_add[EvalField.PREDICTION] = final_str_pred - self.results_dict = self.results_dict | to_add - except Exception as error: - print(Fore.RED + "Error in get_prompt_and_pray prediction" + Fore.RESET) - print(Fore.RED + str(error) + Fore.RESET) - self.results_dict = self.results_dict | to_add - return [""] - - def _get_blendsql_prediction(self, item: dict, db: SQLite) -> List[str]: - to_add = {"solver": "blendsql"} - try: - blendsql = fewshot_parse_to_blendsql( - model=self.parser_endpoint, - **item["input_program_args"], - ) - to_add[EvalField.PRED_BLENDSQL] = blendsql - try: - blendsql = post_process_blendsql( - blendsql=blendsql, - db=db, - use_tables=item["input_program_args"].get("use_tables", None), - ) - except: - pass - to_add[EvalField.PRED_BLENDSQL] = blendsql - res: Smoothie = blend( - query=blendsql, - db=db, - ingredients=( - {LLMMap, LLMQA, LLMJoin, LLMValidate} - if self.model_args.blender_model_name_or_path is not None - else set() - ), - default_model=self.blender_endpoint, - table_to_title={ - SINGLE_TABLE_NAME: item["table"].get("page_title", None) - }, - infer_gen_constraints=True, - verbose=True, - schema_qualify=self.data_training_args.schema_qualify, - ) - to_add["num_prompt_tokens"] = res.meta.num_prompt_tokens - pred_has_ingredient = res.meta.contains_ingredient - self.num_with_ingredients += pred_has_ingredient - to_add["pred_has_ingredient"] = pred_has_ingredient - to_add["example_map_outputs"] = res.meta.example_map_outputs - prediction = [i for i in res.df.values.flat if i is not None] - to_add[EvalField.PREDICTION] = prediction - self.results_dict = self.results_dict | to_add - return prediction - except Exception as error: - print(Fore.RED + "Error in get_blendsql prediction" + Fore.RESET) - print(Fore.RED + str(error) + Fore.RESET) - self.results_dict = self.results_dict | to_add - self.results_dict["error"] = str(error) - return [""] - - def save_metrics(self, metric: Metric, metric_format_func: Callable): - # Finally, read from predictions.json and calculate metrics - with open(self.output_dir / "predictions.json", "r") as f: - predictions = json.load(f) - for item in predictions: - metric.add(**metric_format_func(item | item["dataset_vars"])) - with open(self.output_dir / "metrics.json", "w") as f: - json.dump( - { - "metric_score": metric.compute(), - "num_with_ingredients": self.num_with_ingredients, - "num_errors": self.num_errors, - "num_completed": len(self.results), - "split_size": len(self.split), - }, - f, - indent=4, - ) - combined_args_dict = { - **asdict(self.model_args), - **asdict(self.data_args), - **asdict(self.data_training_args), - # **training_args.to_sanitized_dict(), - } - - with open(self.output_dir / "combined_args.json", "w") as f: - json.dump(combined_args_dict, f, indent=4) - - print(Fore.GREEN + f"Saved outputs to {self.output_dir}" + Fore.RESET) - - -def main() -> None: - time.time() - # See all possible arguments by passing the --help flag to this script. - parser = HfArgumentParser( - (DataArguments, DataTrainingArguments, Seq2SeqTrainingArguments, ModelArguments) - ) - data_args: DataArguments - if len(sys.argv) == 2 and sys.argv[1].endswith(".json"): - # If we pass only one argument to the script and it's the path to a json file, - # let's parse it to get our arguments. - ( - data_args, - data_training_args, - training_args, - model_args, - ) = parser.parse_json_file( - json_file=os.path.abspath(sys.argv[1]), allow_extra_keys=True - ) - elif ( - len(sys.argv) == 3 - and sys.argv[1].startswith("--local_rank") - and sys.argv[2].endswith(".json") - ): - data = json.loads(Path(os.path.abspath(sys.argv[2])).read_text()) - data.update({"local_rank": int(sys.argv[1].split("=")[1])}) - (data_args, data_training_args, training_args, model_args) = parser.parse_dict( - args=data - ) - else: - ( - data_args, - data_training_args, - training_args, - model_args, - ) = parser.parse_args_into_dataclasses() - if data_training_args.clear_guidance_cache: - guidance.llms.OpenAI.cache.clear() - if data_training_args.overwrite_cache: - # Remove the appropriate directory containing our save db files - if data_args.dataset == "wikitq": - dataset_db_path = Path(data_training_args.db_url) / "wikitq" - if dataset_db_path.is_dir(): - shutil.rmtree(str(dataset_db_path)) - - # Load dataset - metric, dataset_splits, metric_format_func = load_dataset( - data_args=data_args, - data_training_args=data_training_args, - model_args=model_args, - training_args=training_args, +def load_tag_database(name: str) -> SQLite: + return SQLite( + Path("./research/data/bird-sql/dev_20240627/dev_databases/") + / name + / f"{name}.sqlite" ) - if ( - not training_args.do_train - and not training_args.do_eval - and not training_args.do_predict - ): - logger.info( - "There is nothing to do. Please pass `do_train`, `do_eval` and/or `do_predict`." - ) - return - output_dir = Path(training_args.output_dir) - - if not output_dir.is_dir(): - output_dir.mkdir(parents=True) - elif not training_args.overwrite_output_dir: - raise ValueError("output_dir is not empty, and overwrite_output_dir is False!") - - parser_endpoint = AzureOpenaiLLM(model_args.parser_model_name_or_path, caching=True) - parser_endpoint.gen_kwargs["temperature"] = model_args.parser_temperature - # parser_endpoint = SageMakerLLM(model_args.parser_model_name_or_path) - - if data_training_args.bypass_models: - parser_endpoint.predict = lambda *args, **kwargs: {"result": "SELECT TRUE;"} - blender_endpoint = None - - if model_args.blender_model_name_or_path is not None: - blender_endpoint = AzureOpenaiLLM(model_args.blender_model_name_or_path) - blender_endpoint.gen_kwargs["temperature"] = model_args.blender_temperature - if data_training_args.bypass_models: - blender_endpoint.predict = lambda *args, **kwargs: {"result": ""} - - prompt_and_pray_endpoint = None - if model_args.prompt_and_pray_model_name_or_path is not None: - prompt_and_pray_endpoint = AzureOpenaiLLM( - model_args.prompt_and_pray_model_name_or_path - ) - - splits = {} - if training_args.do_eval: - splits["eval"] = dataset_splits.eval_split - elif training_args.do_train: - splits["train"] = dataset_splits.train_split - elif training_args.do_predict: - splits["test"] = dataset_splits.test_split - - if data_args.dataset == "ottqa": - # Load the massive db only once - db = SQLite("./research/db/ottqa/ottqa.db") - else: - db = None - - for curr_split_name, curr_split in splits.items(): - bse = BlendSQLEvaluation( - split=curr_split, - split_name=curr_split_name, - output_dir=output_dir, - parser_endpoint=parser_endpoint, - blender_endpoint=blender_endpoint, - prompt_and_pray_endpoint=prompt_and_pray_endpoint, - model_args=model_args, - data_args=data_args, - data_training_args=data_training_args, - db=db, - ) - try: - bse.iter_eval() - except Exception as error: - raise error - finally: - bse.save_metrics(metric=metric, metric_format_func=metric_format_func) if __name__ == "__main__": - import os - - os.environ["HTTP_PROXY"] = "http://http.proxy.fmr.com:8000" - os.environ["HTTPS_PROXY"] = "http://http.proxy.fmr.com:8000" - os.environ["https_proxy"] = "http://http.proxy.fmr.com:8000" - from dotenv import load_dotenv - - load_dotenv(".env") - main() + # df = pd.read_csv("./research/data/tag-benchmark/tag_queries.csv") + # for _, row in df.iterrows(): + # db = load_tag_database(row['DB used']) + # print() + """ + 7.95 seconds with 1 + 4.7155 with 5 + """ + blendsql.config.set_async_limit(5) + ingredients = {LLMQA, LLMMap.from_args(batch_size=2)} + db = load_tag_database("california_schools") + blendsql_query = """ + SELECT s.Phone + FROM satscores ss + JOIN schools s ON ss.cds = s.CDSCode + WHERE {{LLMMap('Is this county in Southern California?', 's::County')}} = TRUE + ORDER BY ss.AvgScrRead ASC + LIMIT 1 + """ + smoothie = blendsql.blend( + query=blendsql_query, + default_model=OpenaiLLM("gpt-4o-mini", caching=False), + ingredients=ingredients, + db=db, + verbose=True, + ) + print(smoothie.df) + print(smoothie.meta.process_time_seconds) diff --git a/research/utils/__init__.py b/research/utils/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/args.py b/research/utils/args.py deleted file mode 100644 index 36be2a2b..00000000 --- a/research/utils/args.py +++ /dev/null @@ -1,88 +0,0 @@ -from dataclasses import dataclass, field -from typing import Optional - - -@dataclass -class ModelArguments: - """ - Arguments pertaining to which model/config/tokenizer we are going to fine-tune from. - """ - - # parser_model_type: str = field( - # metadata={ - # "help": "Model type of the parser model. Accepted values are openai (default), hf and sagemaker" - # } - # ) - # blender_model_type: str = field( - # metadata={ - # "help": "Model type of the blender model. Accepted values are openai (default), hf and sagemaker" - # } - # ) - parser_model_name_or_path: str = field( - metadata={ - "help": "Path to pretrained model or model identifier from huggingface.co/models" - } - ) - prompt_and_pray_model_type: str = field( - default=None, - metadata={ - "help": "Model type of the prompt and pray model. Accepted values are openai (default), hf and sagemaker" - }, - ) - blender_model_name_or_path: str = field( - default=None, - metadata={ - "help": "Path to pretrained model or model identifier from huggingface.co/models" - }, - ) - prompt_and_pray_model_name_or_path: str = field( - default=None, - metadata={ - "help": "Path to pretrained model or model identifier from huggingface.co/models" - }, - ) - parser_temperature: float = field( - default=0.0, - metadata={"help": "Temperature to use for parser"}, - ) - blender_temperature: float = field( - default=0.0, - metadata={"help": "Temperature to use for blender"}, - ) - config_name: Optional[str] = field( - default=None, - metadata={ - "help": "Pretrained config name or path if not the same as model_name" - }, - ) - tokenizer_name: Optional[str] = field( - default=None, - metadata={ - "help": "Pretrained tokenizer name or path if not the same as model_name" - }, - ) - cache_dir: Optional[str] = field( - default=None, - metadata={ - "help": "Where to store the pretrained models downloaded from huggingface.co" - }, - ) - use_fast_tokenizer: bool = field( - default=True, - metadata={ - "help": "Whether to use one of the fast tokenizer (backed by the tokenizers library) or not." - }, - ) - model_revision: str = field( - default="main", - metadata={ - "help": "The specific model version to use (can be a branch name, tag name or commit id)." - }, - ) - use_auth_token: bool = field( - default=False, - metadata={ - "help": "Will use the token generated when running `transformers-cli login` (necessary to use this script " - "with private models)." - }, - ) diff --git a/research/utils/bridge_content_encoder.py b/research/utils/bridge_content_encoder.py deleted file mode 100644 index 3005d379..00000000 --- a/research/utils/bridge_content_encoder.py +++ /dev/null @@ -1,381 +0,0 @@ -""" - Copyright (c) 2020, salesforce.com, inc. - All rights reserved. - SPDX-License-Identifier: BSD-3-Clause - For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/BSD-3-Clause - - Encode DB content. -""" - -import difflib -import functools -import sqlite3 -from typing import List, Optional, Tuple - -from rapidfuzz import fuzz -import re -from diskcache import Cache - - -def double_quote_escape(s): - return re.sub(r'(?<=[^"])"(?=[^"])', '""', s) - - -cache = Cache() - -# fmt: off -_stopwords = {'who', 'ourselves', 'down', 'only', 'were', 'him', 'at', "weren't", 'has', 'few', "it's", 'm', 'again', - 'd', 'haven', 'been', 'other', 'we', 'an', 'own', 'doing', 'ma', 'hers', 'all', "haven't", 'in', 'but', - "shouldn't", 'does', 'out', 'aren', 'you', "you'd", 'himself', "isn't", 'most', 'y', 'below', 'is', - "wasn't", 'hasn', 'them', 'wouldn', 'against', 'this', 'about', 'there', 'don', "that'll", 'a', 'being', - 'with', 'your', 'theirs', 'its', 'any', 'why', 'now', 'during', 'weren', 'if', 'should', 'those', 'be', - 'they', 'o', 't', 'of', 'or', 'me', 'i', 'some', 'her', 'do', 'will', 'yours', 'for', 'mightn', 'nor', - 'needn', 'the', 'until', "couldn't", 'he', 'which', 'yourself', 'to', "needn't", "you're", 'because', - 'their', 'where', 'it', "didn't", 've', 'whom', "should've", 'can', "shan't", 'on', 'had', 'have', - 'myself', 'am', "don't", 'under', 'was', "won't", 'these', 'so', 'as', 'after', 'above', 'each', 'ours', - 'hadn', 'having', 'wasn', 's', 'doesn', "hadn't", 'than', 'by', 'that', 'both', 'herself', 'his', - "wouldn't", 'into', "doesn't", 'before', 'my', 'won', 'more', 'are', 'through', 'same', 'how', 'what', - 'over', 'll', 'yourselves', 'up', 'mustn', "mustn't", "she's", 're', 'such', 'didn', "you'll", 'shan', - 'when', "you've", 'themselves', "mightn't", 'she', 'from', 'isn', 'ain', 'between', 'once', 'here', - 'shouldn', 'our', 'and', 'not', 'too', 'very', 'further', 'while', 'off', 'couldn', "hasn't", 'itself', - 'then', 'did', 'just', "aren't"} -# fmt: on - -_commonwords = {"no", "yes", "many"} - - -def is_number(s: str) -> bool: - try: - float(s.replace(",", "")) - return True - except: - return False - - -def is_stopword(s: str) -> bool: - return s.strip() in _stopwords - - -def is_commonword(s: str) -> bool: - return s.strip() in _commonwords - - -def is_common_db_term(s: str) -> bool: - return s.strip() in ["id"] - - -class Match(object): - def __init__(self, start: int, size: int) -> None: - self.start = start - self.size = size - - -def is_span_separator(c: str) -> bool: - return c in "'\"()`,.?! " - - -def split(s: str) -> List[str]: - return [c.lower() for c in s.strip()] - - -def prefix_match(s1: str, s2: str) -> bool: - i, j = 0, 0 - for i in range(len(s1)): - if not is_span_separator(s1[i]): - break - for j in range(len(s2)): - if not is_span_separator(s2[j]): - break - if i < len(s1) and j < len(s2): - return s1[i] == s2[j] - elif i >= len(s1) and j >= len(s2): - return True - else: - return False - - -def get_effective_match_source(s: str, start: int, end: int) -> Match: - _start = -1 - - for i in range(start, start - 2, -1): - if i < 0: - _start = i + 1 - break - if is_span_separator(s[i]): - _start = i - break - - if _start < 0: - return None - - _end = -1 - for i in range(end - 1, end + 3): - if i >= len(s): - _end = i - 1 - break - if is_span_separator(s[i]): - _end = i - break - - if _end < 0: - return None - - while _start < len(s) and is_span_separator(s[_start]): - _start += 1 - while _end >= 0 and is_span_separator(s[_end]): - _end -= 1 - - return Match(_start, _end - _start + 1) - - -def get_matched_entries( - s: str, field_values: List[str], m_theta: float = 0.85, s_theta: float = 0.85 -) -> Optional[List[Tuple[str, Tuple[str, str, float, float, int]]]]: - """ - field_values is a list of all unique values for a particular column - """ - if not field_values: - return None - if isinstance(s, str): - n_grams = split(s) - else: - n_grams = s - matched = dict() - for field_value in field_values: - if not isinstance(field_value, str): - continue - fv_tokens = split(field_value) - sm = difflib.SequenceMatcher(None, n_grams, fv_tokens) - match = sm.find_longest_match(0, len(n_grams), 0, len(fv_tokens)) - if match.size > 0: - source_match = get_effective_match_source( - n_grams, match.a, match.a + match.size - ) - if source_match and source_match.size > 1: - match_str = field_value[match.b : match.b + match.size] - source_match_str = s[ - source_match.start : source_match.start + source_match.size - ] - c_match_str = match_str.lower().strip() - c_source_match_str = source_match_str.lower().strip() - c_field_value = field_value.lower().strip() - if ( - c_match_str - and not is_number(c_match_str) - and not is_common_db_term(c_match_str) - ): - if ( - is_stopword(c_match_str) - or is_stopword(c_source_match_str) - or is_stopword(c_field_value) - ): - continue - if c_source_match_str.endswith(c_match_str + "'s"): - match_score = 1.0 - else: - if prefix_match(c_field_value, c_source_match_str): - match_score = ( - fuzz.ratio(c_field_value, c_source_match_str) / 100 - ) - else: - match_score = 0 - if ( - is_commonword(c_match_str) - or is_commonword(c_source_match_str) - or is_commonword(c_field_value) - ) and match_score < 1: - continue - s_match_score = match_score - if match_score >= m_theta and s_match_score >= s_theta: - if field_value.isupper() and match_score * s_match_score < 1: - continue - matched[match_str] = ( - field_value, - source_match_str, - match_score, - s_match_score, - match.size, - ) - - if not matched: - return None - else: - return sorted( - matched.items(), - key=lambda x: (1e16 * x[1][2] + 1e8 * x[1][3] + x[1][4]), - reverse=True, - ) - - -@functools.lru_cache(maxsize=1000, typed=False) -def get_column_picklist(table_name: str, column_name: str, db_path: str) -> list: - fetch_sql = 'SELECT DISTINCT `{}` FROM "{}"'.format( - column_name, double_quote_escape(table_name) - ) - try: - conn = sqlite3.connect(db_path) - conn.text_factory = bytes - c = conn.cursor() - c.execute(fetch_sql) - picklist = set() - for x in c.fetchall(): - if isinstance(x[0], str): - picklist.add(x[0].encode("utf-8")) - elif isinstance(x[0], bytes): - try: - picklist.add(x[0].decode("utf-8")) - except UnicodeDecodeError: - picklist.add(x[0].decode("latin-1")) - else: - picklist.add(x[0]) - picklist = list(picklist) - finally: - conn.close() - return picklist - - -def get_column_picklist_with_db(table_name: str, column_name: str, db) -> list: - """ - Useful for OTT-QA setting, where we don't want to repeatedly open/close connection - to a large db. - """ - key = (table_name, column_name) - if key in cache: - return cache[key] - fetch_sql = 'SELECT DISTINCT `{}` FROM "{}"'.format( - column_name, double_quote_escape(table_name) - ) - picklist = set(db.execute_to_df(fetch_sql).values.flat) - picklist = list(picklist) - cache[key] = picklist - return picklist - - -def align_title_to_content(contents: List[str], db_path: str) -> list: - titles = [] - for content in contents: - fetch_sql = "SELECT title FROM docs WHERE content = '{}'".format(content) - try: - conn = sqlite3.connect(db_path) - conn.text_factory = bytes - c = conn.cursor() - c.execute(fetch_sql) - for x in c.fetchall(): - if isinstance(x[0], str): - titles.append(x[0].encode("utf-8")) - elif isinstance(x[0], bytes): - try: - titles.append(x[0].decode("utf-8")) - except UnicodeDecodeError: - titles.append(x[0].decode("latin-1")) - else: - titles.append(x[0]) - finally: - conn.close() - return titles - - -def flatten(xss): - return [x for xs in xss for x in xs] - - -# @functools.lru_cache(maxsize=1000, typed=False) -# def get_picklist_embeds(st_model, picklist: Tuple[str]): -# return st_model.encode(picklist, convert_to_tensor=True, show_progress_bar=False) - - -# def get_database_matches_docs( -# question: str, -# table_name: str, -# column_name: str, -# db_path: str, -# top_k_matches: int = 2, -# match_threshold: float = 0.75, -# ): -# picklist = get_column_picklist( -# table_name=table_name, column_name=column_name, db_path=db_path -# ) # Gets all DISTINCT values from column -# matches = [] -# # Match doc content on cosine similarity, not text similarity -# titles = align_title_to_content(picklist, db_path) -# _picklist = [nltk.sent_tokenize(i) for i in picklist] -# sent_to_title = {} -# for content_l, title in zip(_picklist, titles): -# for sent in content_l: -# sent_to_title[sent] = title -# flatten = lambda xss: [x for xs in xss for x in xs] -# picklist = flatten(_picklist) -# question_embed = st_model.encode([question], convert_to_tensor=True) -# context_embeds = get_picklist_embeds(st_model=st_model, picklist=tuple(picklist)) -# cosine_scores = util.cos_sim(question_embed, context_embeds).flatten(0) -# for score, idx in topk(cosine_scores, top_k_matches): -# if score < match_threshold: -# break -# chosen_sent = picklist[int(idx.item())] -# matches.append((sent_to_title[chosen_sent], chosen_sent)) -# return matches[::-1] - - -def get_database_matches( - question: str, - table_name: str, - column_name: str, - top_k_matches: int = 2, - match_threshold: float = 0.85, - db_path: str = None, - db=None, -) -> List[str]: - if db is None: - picklist = get_column_picklist( - table_name=table_name, column_name=column_name, db_path=db_path - ) # Gets all DISTINCT values from column - else: - picklist = get_column_picklist_with_db( - table_name=table_name, column_name=column_name, db=db - ) - matches = [] - if picklist and isinstance(picklist[0], str): - matched_entries = get_matched_entries( - s=question, - field_values=picklist, - m_theta=match_threshold, - s_theta=match_threshold, - ) - if matched_entries: - num_values_inserted = 0 - for _match_str, ( - field_value, - _s_match_str, - match_score, - s_match_score, - _match_size, - ) in matched_entries: - if "name" in column_name and match_score * s_match_score < 1: - continue - if table_name != "sqlite_sequence": # Spider database artifact - matches.append(field_value) - num_values_inserted += 1 - if num_values_inserted >= top_k_matches: - break - return matches - - -if __name__ == "__main__": - print() - print( - get_database_matches( - question="Carribean", - table_name="Country", - column_name="Region", - db_path="../../../data/spider/database/world_1/world_1.sqlite", - ) - ) - print( - get_database_matches( - question="How much surface area do the countires in the middle east cover together?", - table_name="Country", - column_name="Region", - db_path="../../../data/spider/database/world_1/world_1.sqlite", - ) - ) - print() diff --git a/research/utils/database.py b/research/utils/database.py deleted file mode 100644 index 4cd7fa25..00000000 --- a/research/utils/database.py +++ /dev/null @@ -1,66 +0,0 @@ -from typing import Iterable, Set -import re -from ..constants import DOCS_TABLE_NAME -from blendsql.db.utils import double_quote_escape - - -def to_serialized( - db: "SQLite", - ignore_tables: Iterable[str] = None, - use_tables: Set[str] = None, - num_rows: int = 0, - tablename_to_description: dict = None, - whole_table: bool = False, - truncate_content: int = None, -) -> str: - if all(x is not None for x in [ignore_tables, use_tables]): - raise ValueError("Both `ignore_tables` and `use_tables` cannot be passed!") - if ignore_tables is None: - ignore_tables = set() - serialized_db = [] - if use_tables: - _create_clause_iter = [db.create_clause(tablename) for tablename in use_tables] - else: - _create_clause_iter = db.create_clauses() - for tablename, create_clause in _create_clause_iter: - # Check if it's an artifact of virtual table - if re.search(r"^{}_".format(DOCS_TABLE_NAME), tablename): - continue - if tablename in ignore_tables: - continue - if use_tables is not None and tablename not in use_tables: - continue - if tablename_to_description is not None: - if tablename in tablename_to_description: - if tablename_to_description[tablename] is not None: - serialized_db.append( - f"Table Description: {tablename_to_description[tablename]}" - ) - if not whole_table: - serialized_db.append(create_clause) - if (num_rows > 0 and not tablename.startswith(DOCS_TABLE_NAME)) or whole_table: - get_rows_query = ( - f'SELECT * FROM "{double_quote_escape(tablename)}" LIMIT {num_rows}' - if not whole_table - else f'SELECT * FROM "{double_quote_escape(tablename)}"' - ) - serialized_db.append("/*") - if whole_table: - serialized_db.append("Entire table:") - else: - serialized_db.append(f"{num_rows} example rows:") - serialized_db.append(f"{get_rows_query}") - rows = db.execute_to_df(get_rows_query) - if truncate_content is not None: - # Truncate long strings - rows = rows.map( - lambda x: ( - f"{str(x)[:truncate_content]}..." - if isinstance(x, str) and len(str(x)) > truncate_content - else x - ) - ) - serialized_db.append(f"{rows.to_string(index=False)}") - serialized_db.append("*/\n") - serialized_db = "\n".join(serialized_db).strip() - return serialized_db diff --git a/research/utils/dataset.py b/research/utils/dataset.py deleted file mode 100644 index f0ff7d33..00000000 --- a/research/utils/dataset.py +++ /dev/null @@ -1,481 +0,0 @@ -import os -import random -from dataclasses import dataclass, field -from typing import Callable, Dict, List, Optional -from pathlib import Path - -from datasets.arrow_dataset import Dataset -from datasets.dataset_dict import DatasetDict -from transformers.training_args import TrainingArguments - -from .bridge_content_encoder import get_database_matches -from .args import ModelArguments - -BASE_PATH = Path(os.path.dirname(os.path.realpath(__file__))) / ".." - - -@dataclass -class DataTrainingArguments: - """ - Arguments pertaining to what data we are going to input our model for training and eval. - """ - - overwrite_cache: bool = field( - default=False, - metadata={"help": "Overwrite the cached training and evaluation sets"}, - ) - clear_guidance_cache: bool = field( - default=False, - metadata={"help": "Clear internal guidance gptcache"}, - ) - preprocessing_num_workers: Optional[int] = field( - default=None, - metadata={"help": "The number of processes to use for the preprocessing."}, - ) - max_source_length: Optional[int] = field( - default=512, - metadata={ - "help": "The maximum total input sequence length after tokenization. Sequences longer " - "than this will be truncated, sequences shorter will be padded." - }, - ) - max_target_length: Optional[int] = field( - default=512, - metadata={ - "help": "The maximum total sequence length for target text after tokenization. Sequences longer " - "than this will be truncated, sequences shorter will be padded." - }, - ) - val_max_target_length: Optional[int] = field( - default=None, - metadata={ - "help": "The maximum total sequence length for validation target text after tokenization. Sequences longer " - "than this will be truncated, sequences shorter will be padded. Will default to `max_target_length`." - "This argument is also used to override the ``max_length`` param of ``model.generate``, which is used " - "during ``evaluate`` and ``predict``." - }, - ) - val_max_time: Optional[int] = field( - default=None, - metadata={ - "help": "The maximum allowed time in seconds for generation of one example. This setting can be used to stop " - "generation whenever the full generation exceeds the specified amount of time." - }, - ) - max_train_samples: Optional[int] = field( - default=None, - metadata={ - "help": "For debugging purposes or quicker training, truncate the number of training examples to this " - "value if set." - }, - ) - max_val_samples: Optional[int] = field( - default=None, - metadata={ - "help": "For debugging purposes or quicker training, truncate the number of test examples to this " - "value if set." - }, - ) - - max_test_samples: Optional[int] = field( - default=None, - metadata={ - "help": "For debugging purposes or quicker training, truncate the number of test examples to this " - "value if set." - }, - ) - - bypass_models: Optional[bool] = field( - default=False, - metadata={ - "help": "Don't call models. For debugging, to get datapoints in predictions.json as fast as possible." - }, - ) - fallback_to_prompt_and_pray: Optional[bool] = field( - default=False, - metadata={ - "help": "In the case of a bad BlendSQL output, fallback to prompt and pray answering." - }, - ) - prompt_and_pray_only: Optional[bool] = field( - default=False, - metadata={"help": "Only do end-to-end table answering, no BlendSQL"}, - ) - include_doc_bridge_hints: Optional[bool] = field( - default=False, - metadata={"help": "Use vector cosine sim to include top docs in bridge hints"}, - ) - num_beams: int = field( - default=1, - metadata={ - "help": "Number of beams to use for evaluation. This argument will be passed to ``model.generate``, " - "which is used during ``evaluate`` and ``predict``." - }, - ) - num_beam_groups: int = field( - default=1, - metadata={ - "help": "Number of beam groups to use for evaluation. This argument will be passed to ``model.generate``, " - "which is used during ``evaluate`` and ``predict``." - }, - ) - diversity_penalty: Optional[float] = field( - default=None, - metadata={ - "help": "Diversity penalty to use for evaluation. This argument will be passed to ``model.generate``, " - "which is used during ``evaluate`` and ``predict``." - }, - ) - num_return_sequences: Optional[int] = field( - default=None, - metadata={ - "help": "The number of sequences to generate during evaluation. This argument will be passed to " - "``model.generate``, which is used during ``evaluate`` and ``predict``." - }, - ) - ignore_pad_token_for_loss: bool = field( - default=True, - metadata={ - "help": "Whether or not to ignore the tokens corresponding to padded labels in the loss computation or not." - }, - ) - source_prefix: Optional[str] = field( - default=None, - metadata={ - "help": "A prefix to add before every source text (useful for T5 models)." - }, - ) - schema_serialization_type: str = field( - default="code", - metadata={ - "help": "Choose between ``code`` and ``peteshaw`` schema serialization." - }, - ) - schema_serialization_randomized: bool = field( - default=False, - metadata={"help": "Whether or not to randomize the order of tables."}, - ) - schema_serialization_with_db_id: bool = field( - default=True, - metadata={ - "help": "Whether or not to add the database id to the context. Needed for Picard." - }, - ) - schema_serialization_with_db_content: bool = field( - default=True, - metadata={ - "help": "Whether or not to use the database content to resolve field matches." - }, - ) - normalize_query: bool = field( - default=True, - metadata={ - "help": "Whether to normalize the SQL queries with the process in the 'Decoupling' paper" - }, - ) - use_bridge_encoder: Optional[bool] = field( - default=True, - metadata={ - "help": "Whether to use Bridge Content Encoder during input serialization" - }, - ) - db_path: Optional[List[str]] = field( - default="research/db", - metadata={"help": "Where to save temp SQLite databases"}, - ) - num_serialized_rows: Optional[int] = field( - default=3, - metadata={ - "help": "How many example rows to include in serialization of database" - }, - ) - save_every: Optional[int] = field( - default=50, - metadata={"help": "Save results to predictions.json every n datapoints"}, - ) - truncate_content: Optional[int] = field( - default=None, - metadata={ - "help": "Optionally limit serialized database value to this character length" - }, - ) - - schema_qualify: Optional[bool] = field( - default=True, - metadata={ - "help": "Whether to use sqlglot to qualify schema columns when calling `blend()`" - }, - ) - - def __post_init__(self): - if self.val_max_target_length is None: - self.val_max_target_length = self.max_target_length - - -@dataclass -class DataArguments: - dataset: str = field( - default="wikitq", - metadata={"help": "The dataset to be used. Choose between `wikitq``."}, - ) - dataset_paths: Dict[str, str] = field( - default_factory=lambda: { - "wikitq": str(BASE_PATH / "./datasets/wikitq"), - "hybridqa": str(BASE_PATH / "./datasets/hybridqa"), - "feverous": str(BASE_PATH / "./datasets/feverous"), - "ottqa": str(BASE_PATH / "./datasets/ottqa"), - "fetaqa": str(BASE_PATH / "./datasets/fetaqa"), - }, - metadata={"help": "Paths of the dataset modules."}, - ) - wikitq_dataset_url: str = field( - default="", - metadata={"help": "Path of wikitq.zip, relative to dataset path."}, - ) - squall_dataset_url: str = field( - default="", - metadata={"help": "Path of squall.zip, relative to dataset path."}, - ) - ottqa_dataset_url: str = field( - default="", - metadata={"help": "Path of ottqa.zip, relative to dataset path."}, - ) - fetaqa_dataset_url: str = field( - default="", - metadata={"help": "Path of squall.zip, relative to dataset path."}, - ) - - metric_config: str = field( - default="both", - metadata={ - "help": "Choose between ``exact_match``, ``sacrebleu``, '', or ``both``." - }, - ) - long_answer: bool = field( - default=False, - metadata={"help": "whether or not should the model return long answer"}, - ) - metric_paths: Dict[str, str] = field( - default_factory=lambda: { - "wikitq": str(BASE_PATH / "./metrics/wikitq"), - "hybridqa": str(BASE_PATH / "./metrics/hybridqa"), - "feverous": str(BASE_PATH / "./metrics/feverous"), - "ottqa": str(BASE_PATH / "./metrics/ottqa"), - "fetaqa": str(BASE_PATH / "./metrics/fetaqa"), - }, - metadata={"help": "Paths of the metric modules."}, - ) - data_config_file: Optional[str] = field( - default=None, - metadata={ - "help": "Path to data configuration file (specifying the database splits)" - }, - ) - test_sections: Optional[List[str]] = field( - default=None, - metadata={"help": "Sections from the data config to use for testing"}, - ) - - -@dataclass -class TrainSplit(object): - dataset: Dataset - schemas: Dict[str, dict] - - -@dataclass -class EvalSplit(object): - dataset: Dataset - examples: Dataset - - -@dataclass -class DatasetSplits(object): - train_split: Optional[TrainSplit] - eval_split: Optional[EvalSplit] - test_split: Optional[Dict[str, EvalSplit]] - - -def _prepare_eval_split( - dataset: Dataset, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, - pre_process_function: Callable[[dict, Optional[int], Optional[int]], dict], - max_example, -) -> EvalSplit: - if max_example is not None and max_example < len(dataset): - eval_examples = dataset.select(range(max_example)) - else: - eval_examples = dataset - eval_dataset = eval_examples.map( - lambda batch: pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ), - batched=True, - num_proc=data_training_args.preprocessing_num_workers, - load_from_cache_file=not data_training_args.overwrite_cache, - ) - return eval_dataset - - -def prepare_splits( - dataset_dict: DatasetDict, - data_args: DataArguments, - training_args: TrainingArguments, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, - pre_process_function: Callable[[dict, Optional[int], Optional[int]], dict], -) -> DatasetSplits: - train_split, eval_split, test_split = None, None, None - - if training_args.do_eval: - eval_split = _prepare_eval_split( - dataset_dict["validation"], - data_training_args=data_training_args, - model_args=model_args, - pre_process_function=pre_process_function, - max_example=data_training_args.max_val_samples, - ) - - if training_args.do_predict: - test_split = _prepare_eval_split( - dataset_dict["test"], - data_training_args=data_training_args, - model_args=model_args, - pre_process_function=pre_process_function, - max_example=data_training_args.max_test_samples, - ) - - if training_args.do_train: - # For now, treat `train` like `validation` - train_split = _prepare_eval_split( - dataset_dict["train"], - data_training_args=data_training_args, - model_args=model_args, - pre_process_function=pre_process_function, - max_example=data_training_args.max_train_samples, - ) - - return DatasetSplits( - train_split=train_split, - eval_split=eval_split, - test_split=test_split, - ) - - -def serialize_schema( - question: str, - db_path: str, - db_id: str, - db_column_names: Dict[str, str], - db_table_names: List[str], - schema_serialization_type: str = "peteshaw", - schema_serialization_randomized: bool = False, - schema_serialization_with_db_id: bool = True, - schema_serialization_with_db_content: bool = False, - normalize_query: bool = True, - use_gold_concepts: bool = False, - query: str = None, -) -> str: - if use_gold_concepts and not query: - raise ValueError( - "If use_gold_concepts is True, need to pass gold SQL query as well" - ) - if schema_serialization_type == "verbose": - db_id_str = "Database: {db_id}. " - table_sep = ". " - table_str = "Table: {table}. Columns: {columns}" - column_sep = ", " - column_str_with_values = "{column} ({values})" - column_str_without_values = "{column}" - value_sep = ", " - elif schema_serialization_type == "peteshaw": - # see https://github.com/google-research/language/blob/master/language/nqg/tasks/spider/append_schema.py#L42 - db_id_str = " | {db_id}" - table_sep = "" - table_str = " | {table} : {columns}" - column_sep = " , " - column_str_with_values = "{column} ( {values} )" - column_str_without_values = "{column}" - value_sep = " , " - else: - raise NotImplementedError - - def get_column_str( - table_name: str, column_name: str, gold_values: List[str] = None - ) -> str: - column_name_str = column_name.lower() if normalize_query else column_name - if schema_serialization_with_db_content: - if use_gold_concepts: - # Encode the gold values from query - if gold_values: - return column_str_with_values.format( - column=column_name_str, values=value_sep.join(gold_values) - ) - else: - return column_str_without_values.format(column=column_name_str) - else: - matches = get_database_matches( - question=question, - table_name=table_name, - column_name=column_name, - db_path=(db_path + "/" + db_id + "/" + db_id + ".sqlite"), - ) - if matches: - return column_str_with_values.format( - column=column_name_str, values=value_sep.join(matches) - ) - else: - return column_str_without_values.format(column=column_name_str) - else: - return column_str_without_values.format(column=column_name_str) - - if use_gold_concepts: - # Run SpiderSQL.to_gold_concepts to filter down schema - # only to those concepts included in gold SQL - ssql = SpiderSQL( - data_dir="../data/spider/", - db_path_fmt="database/{db_id}/{db_id}.sqlite", - ) - try: - items = ssql.to_gold_concepts(query, db_id=db_id) - db_column_names = items.get("db_column_names") - db_table_names = items.get("db_table_names") - except: - print(f"ERROR: {question}") - else: - # Just use the full 'db_column_names', 'db_table_names' we passed into this function - pass - - tables = [ - table_str.format( - table=table_name.lower() if normalize_query else table_name, - columns=column_sep.join( - map( - lambda y: get_column_str( - table_name=table_name, column_name=y[1], gold_values=y[2] - ), - filter( - lambda y: y[0] == table_id, - zip( - db_column_names["table_id"], - db_column_names["column_name"], - db_column_names.get( - "values", [None] * len(db_column_names["column_name"]) - ), - ), - ), - ) - ), - ) - for table_id, table_name in enumerate(db_table_names) - ] - if schema_serialization_randomized: - random.shuffle(tables) - if schema_serialization_with_db_id: - serialized_schema = db_id_str.format(db_id=db_id) + table_sep.join(tables) - else: - serialized_schema = table_sep.join(tables) - return serialized_schema diff --git a/research/utils/dataset_loader.py b/research/utils/dataset_loader.py deleted file mode 100644 index 59c0bb12..00000000 --- a/research/utils/dataset_loader.py +++ /dev/null @@ -1,230 +0,0 @@ -import logging -from pathlib import Path - -from datasets.arrow_dataset import Dataset -from datasets.metric import Metric -from typing import Tuple, Callable -from datasets.dataset_dict import DatasetDict -import datasets.load -from transformers.training_args import TrainingArguments - -from .dataset import DataArguments, DataTrainingArguments, DatasetSplits, prepare_splits -from ..utils.args import ModelArguments -from .wikitq.wikitq import wikitq_pre_process_function, wikitq_metric_format_func -from .hybridqa.hybridqa import ( - hybridqa_pre_process_function, - hybridqa_metric_format_func, -) -from .feverous.feverous import ( - feverous_pre_process_function, - feverous_metric_format_func, -) -from .ottqa.ottqa import ottqa_pre_process_function, ottqa_metric_format_func - -from .fetaqa.fetaqa import ( - fetaqa_pre_process_function, - fetaqa_metric_format_func, -) - -logger = logging.getLogger(__name__) - - -def _log_duplicate_count(dataset: Dataset, dataset_name: str, split: str) -> None: - d = dataset.to_dict() - d_t = [ - tuple((k, tuple(str(v))) for k, v in zip(d.keys(), vs)) - for vs in zip(*d.values()) - ] - d_t_ = set(d_t) - num_examples = len(d_t) - duplicate_count = num_examples - len(d_t_) - if duplicate_count > 0: - logger.warning( - f"The split ``{split}`` of the dataset ``{dataset_name}`` contains {duplicate_count} duplicates out of {num_examples} examples" - ) - - -def load_dataset( - data_args: DataArguments, - data_training_args: DataTrainingArguments, - training_args: TrainingArguments, - model_args: ModelArguments, -) -> Tuple[Metric, DatasetSplits]: - # [dataset loader] - _wikitq_dataset_dict: Callable[ - [], DatasetDict - ] = lambda: datasets.load.load_dataset( - path=data_args.dataset_paths["wikitq"], - wikitq_dataset_url=data_args.wikitq_dataset_url, - squall_dataset_url=data_args.squall_dataset_url, - ) - _hybridqa_dataset_dict: Callable[ - [], DatasetDict - ] = lambda: datasets.load.load_dataset( - path=data_args.dataset_paths["hybridqa"], - ) - _feverous_dataset_dict: Callable[ - [], DatasetDict - ] = lambda: datasets.load.load_dataset( - path=data_args.dataset_paths["feverous"], - ) - _ottqa_dataset_dict: Callable[[], DatasetDict] = lambda: datasets.load.load_dataset( - path=data_args.dataset_paths["ottqa"], - db_output_dir=Path("research/db/ottqa").resolve(), - ottqa_dataset_url=data_args.ottqa_dataset_url, - ) - _fetaqa_dataset_dict: Callable[ - [], DatasetDict - ] = lambda: datasets.load.load_dataset( - path=data_args.dataset_paths["fetaqa"], - fetaqa_dataset_url=data_args.fetaqa_dataset_url, - ) - - # [preprocessing func] - _wikitq_pre_process_function = ( - lambda batch, data_training_args, model_args: wikitq_pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ) - ) - _hybridqa_pre_process_function = ( - lambda batch, data_training_args, model_args: hybridqa_pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ) - ) - _feverous_pre_process_function = ( - lambda batch, data_training_args, model_args: feverous_pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ) - ) - _ottqa_pre_process_function = ( - lambda batch, data_training_args, model_args: ottqa_pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ) - ) - _fetaqa_pre_process_function = ( - lambda batch, data_training_args, model_args: fetaqa_pre_process_function( - batch=batch, - data_training_args=data_training_args, - model_args=model_args, - ) - ) - - # [dataset metric] - _wikitq_metric: Callable[[], Metric] = lambda: datasets.load.load_metric( - path=data_args.metric_paths["wikitq"], - config_name=data_args.metric_config, - ) - _hybridqa_metric: Callable[[], Metric] = lambda: datasets.load.load_metric( - path=data_args.metric_paths["hybridqa"], - config_name=data_args.metric_config, - ) - _feverous_metric: Callable[[], Metric] = lambda: datasets.load.load_metric( - path=data_args.metric_paths["feverous"], - config_name=data_args.metric_config, - ) - _ottqa_metric: Callable[[], Metric] = lambda: datasets.load.load_metric( - path=data_args.metric_paths["ottqa"], - config_name=data_args.metric_config, - ) - _fetaqa_metric: Callable[[], Metric] = lambda: datasets.load.load_metric( - path=data_args.metric_paths["fetaqa"], - config_name=data_args.metric_config, - ) - - # [dataset metric format] - _wikitq_metric_format_func: Callable = lambda item: wikitq_metric_format_func( - item=item - ) - _hybridqa_metric_format_func: Callable = lambda item: hybridqa_metric_format_func( - item=item - ) - _feverous_metric_format_func: Callable = lambda item: feverous_metric_format_func( - item=item - ) - _ottqa_metric_format_func: Callable = lambda item: ottqa_metric_format_func( - item=item - ) - _fetaqa_metric_format_func: Callable = lambda item: fetaqa_metric_format_func( - item=item - ) - - _prepare_splits_kwargs = { - "data_args": data_args, - "training_args": training_args, - "data_training_args": data_training_args, - "model_args": model_args, - } - if data_args.dataset == "wikitq": - metric = _wikitq_metric() - dataset_splits = prepare_splits( - dataset_dict=_wikitq_dataset_dict(), - pre_process_function=_wikitq_pre_process_function, - **_prepare_splits_kwargs, - ) - metric_format_func = _wikitq_metric_format_func - elif data_args.dataset == "hybridqa": - metric = _hybridqa_metric() - dataset_splits = prepare_splits( - dataset_dict=_hybridqa_dataset_dict(), - pre_process_function=_hybridqa_pre_process_function, - **_prepare_splits_kwargs, - ) - metric_format_func = _hybridqa_metric_format_func - elif data_args.dataset == "feverous": - metric = _feverous_metric() - dataset_splits = prepare_splits( - dataset_dict=_feverous_dataset_dict(), - pre_process_function=_feverous_pre_process_function, - **_prepare_splits_kwargs, - ) - metric_format_func = _feverous_metric_format_func - elif data_args.dataset == "ottqa": - metric = _ottqa_metric() - dataset_splits = prepare_splits( - dataset_dict=_ottqa_dataset_dict(), - pre_process_function=_ottqa_pre_process_function, - **_prepare_splits_kwargs, - ) - metric_format_func = _ottqa_metric_format_func - elif data_args.dataset == "fetaqa": - metric = _fetaqa_metric() - dataset_splits = prepare_splits( - dataset_dict=_fetaqa_dataset_dict(), - pre_process_function=_fetaqa_pre_process_function, - **_prepare_splits_kwargs, - ) - metric_format_func = _fetaqa_metric_format_func - import nltk - - nltk.download("punkt") - else: - raise ValueError( - f"data_args.dataset {data_args.dataset} not currently supported!" - ) - if dataset_splits.train_split is not None: - _log_duplicate_count( - dataset=dataset_splits.train_split, - dataset_name=data_args.dataset, - split="train", - ) - if dataset_splits.eval_split is not None: - _log_duplicate_count( - dataset=dataset_splits.eval_split, - dataset_name=data_args.dataset, - split="eval", - ) - if dataset_splits.test_split is not None: - _log_duplicate_count( - dataset=dataset_splits.test_split, - dataset_name=data_args.dataset, - split="test", - ) - return metric, dataset_splits, metric_format_func diff --git a/research/utils/fetaqa/__init__.py b/research/utils/fetaqa/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/fetaqa/fetaqa.py b/research/utils/fetaqa/fetaqa.py deleted file mode 100644 index d8bcb81d..00000000 --- a/research/utils/fetaqa/fetaqa.py +++ /dev/null @@ -1,139 +0,0 @@ -import logging -import sys -from typing import Tuple -from pathlib import Path -import sqlite3 - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -from ..dataset import DataTrainingArguments -from ...utils.args import ModelArguments -from ...prompts.few_shot.fetaqa import blendsql_examples, sql_examples - - -from ...utils.bridge_content_encoder import get_database_matches -from ...constants import SINGLE_TABLE_NAME, EvalField -from ..normalizer import prepare_df_for_neuraldb_from_table -from blendsql.db import SQLite - - -def fetaqa_metric_format_func(item: dict) -> dict: - prediction = item.get(EvalField.PREDICTION, None) - if prediction is not None: - if len(prediction) < 1: - pred = "" - else: - pred = prediction[0] - else: - pred = "" - return { - "prediction": [str(pred)], - "reference": { - "answer_text": [item["answer_text"]], - "question": item["question"], - }, - } - - -def fetaqa_get_input( - question: str, - title: dict, - table: str, - table_id: str, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, -) -> Tuple[str, dict]: - """Prepares input for WikiTableQuestions dataset. - - Returns: - Tuple containing: - - str path to sqlite database - - dict containing arguments to be passed to guidance program - """ - # table_id in format csv/204-csv/772.csv - table_id = Path(table_id) - db_path = ( - Path(data_training_args.db_path) - / "fetaqa" - / table_id.parent - / f"{table_id.stem}.db" - ) - if not db_path.is_file(): - # Create db - if not db_path.parent.is_dir(): - db_path.parent.mkdir(parents=True) - sqlite_conn = sqlite3.connect(db_path) - prepare_df_for_neuraldb_from_table(table, add_row_id=False).to_sql( - SINGLE_TABLE_NAME, sqlite_conn - ) - db_path = str(db_path) - db = SQLite(db_path) - serialized_db = db.to_serialized( - num_rows=data_training_args.num_serialized_rows, - table_description=title, - ) - bridge_hints = None - if data_training_args.use_bridge_encoder: - bridge_hints = [] - column_str_with_values = "{column} ( {values} )" - value_sep = " , " - for table_name in db.iter_tables(): - for column_name in db.iter_columns(table_name): - matches = get_database_matches( - question=question, - table_name=table_name, - column_name=column_name, - db_path=db_path, - ) - if matches: - bridge_hints.append( - column_str_with_values.format( - column=column_name, values=value_sep.join(matches) - ) - ) - bridge_hints = "\n".join(bridge_hints) - db.con.close() - return ( - db_path, - { - "examples": ( - blendsql_examples - if model_args.blender_model_name_or_path is not None - else sql_examples - ), - "question": question, - "serialized_db": serialized_db, - "bridge_hints": bridge_hints, - "extra_task_description": "Provide concrete reasoning to the answer", - }, - ) - - -def fetaqa_pre_process_function( - batch: dict, data_training_args: DataTrainingArguments, model_args: ModelArguments -) -> dict: - db_path, input_program_args = zip( - *[ - fetaqa_get_input( - question=question, - title=title, - table=table, - table_id=table_id, - data_training_args=data_training_args, - model_args=model_args, - ) - for question, table, title, table_id in zip( - batch[EvalField.QUESTION], - batch["table"], - batch["meta"], - batch["table_id"], - ) - ] - ) - return {"input_program_args": list(input_program_args), "db_path": list(db_path)} diff --git a/research/utils/feverous/__init__.py b/research/utils/feverous/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/feverous/feverous.py b/research/utils/feverous/feverous.py deleted file mode 100644 index 64633297..00000000 --- a/research/utils/feverous/feverous.py +++ /dev/null @@ -1,175 +0,0 @@ -# Set up logging -import logging -import sys -from typing import Tuple, List -from pathlib import Path -import sqlite3 -import re - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -from ..database import to_serialized -from ..dataset import DataTrainingArguments -from ...utils.args import ModelArguments -from ...utils.bridge_content_encoder import ( - get_database_matches, -) -from ...constants import ( - SINGLE_TABLE_NAME, - CREATE_VIRTUAL_TABLE_CMD, - DOCS_TABLE_NAME, - EvalField, -) -from ..normalizer import prepare_df_for_neuraldb_from_table -from blendsql.db import SQLite - - -def feverous_metric_format_func(item: dict) -> dict: - prediction = item[EvalField.PREDICTION] - if prediction is not None: - if len(prediction) < 1: - pred = "" - else: - pred = prediction[0] - else: - pred = "" - # Map `True` to 'SUPPORTS', `False` to 'REFUTES' - pred = "SUPPORTS" if pred else "REFUTES" - return { - "prediction": str(pred), - "reference": {"seq_out": item[EvalField.GOLD_ANSWER]}, - } - - -def feverous_get_input( - statement: str, - table: dict, - context: List[str], - uid: str, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, -) -> Tuple[str, dict]: - # Below uid is unique for each datapoint - # But, might be better to consider table_id instead - db_path = Path(data_training_args.db_path) / "feverous" / f"{uid}.db" - tablename_to_description = {} - contains_documents = not all(len(x) == 0 for x in context.values()) - if not db_path.is_file(): - # Create db - if not db_path.parent.is_dir(): - db_path.parent.mkdir(parents=True) - sqlite_conn = sqlite3.connect(db_path) - for idx, (table_description, header, rows) in enumerate( - zip(table["table_description"], table["header"], table["rows"]) - ): - tablename = f"{SINGLE_TABLE_NAME}{idx}" - prepare_df_for_neuraldb_from_table( - {"header": header, "rows": rows}, add_row_id=False - ).to_sql(tablename, sqlite_conn) - tablename_to_description[tablename] = table_description - if contains_documents: - # Create virtual table to search over - c = sqlite_conn.cursor() - c.execute(CREATE_VIRTUAL_TABLE_CMD) - c.close() - # Add content - prepare_df_for_neuraldb_from_table( - { - "header": ["title", "content"], - "rows": [ - [title, content] - for title, content in set( - tuple(zip(context["title"], context["content"])) - ) - ], - }, - add_row_id=False, - ).to_sql(DOCS_TABLE_NAME, sqlite_conn, if_exists="append", index=False) - sqlite_conn.close() - db_path = str(db_path) - db = SQLite(db_path) - serialized_db = to_serialized( - db=db, - num_rows=data_training_args.num_serialized_rows, - tablename_to_description=tablename_to_description, - ) - entire_serialized_db = to_serialized( - db=db, - num_rows=data_training_args.num_serialized_rows, - tablename_to_description=tablename_to_description, - whole_table=True, - truncate_content=300, - ) - bridge_hints = None - if data_training_args.use_bridge_encoder: - bridge_hints = [] - column_str_with_values = "{table}.{column} ( {values} )" - value_sep = " , " - for table_name in db.iter_tables(): - if re.search(r"^{}_".format(DOCS_TABLE_NAME), table_name): - continue - for column_name in db.iter_columns(table_name): - matches = get_database_matches( - question=statement, - table_name=table_name, - column_name=column_name, - db_path=db_path, - ) - if matches: - bridge_hints.append( - column_str_with_values.format( - table=table_name, - column=column_name, - values=value_sep.join(matches), - ) - ) - bridge_hints = "\n".join(bridge_hints) - db.con.close() - return ( - db_path, - { - "few_shot_prompt": open("./research/prompts/feverous/few_shot.txt").read(), - "ingredients_prompt": open( - "./research/prompts/feverous/ingredients.txt" - ).read(), - "question": statement, - "serialized_db": serialized_db, - "entire_serialized_db": entire_serialized_db, - "bridge_hints": bridge_hints, - "extra_task_description": ( - f"Additionally, we have the table `{DOCS_TABLE_NAME}` at our disposal, which contains Wikipedia articles providing more details about the values in our table." - if contains_documents - else "" - ), - }, - ) - - -def feverous_pre_process_function( - batch: dict, data_training_args: DataTrainingArguments, model_args: ModelArguments -) -> dict: - db_path, input_program_args = zip( - *[ - feverous_get_input( - statement=statement, - table=table, - context=context, - uid=uid, - data_training_args=data_training_args, - model_args=model_args, - ) - for statement, table, context, uid in zip( - batch[EvalField.QUESTION], - batch["table"], - batch["context"], - batch[EvalField.UID], - ) - ] - ) - return {"input_program_args": list(input_program_args), "db_path": list(db_path)} diff --git a/research/utils/hybridqa/__init__.py b/research/utils/hybridqa/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/hybridqa/hybridqa.py b/research/utils/hybridqa/hybridqa.py deleted file mode 100644 index 0047e60e..00000000 --- a/research/utils/hybridqa/hybridqa.py +++ /dev/null @@ -1,172 +0,0 @@ -# Set up logging -import logging -import sys -from typing import Tuple -from pathlib import Path -import sqlite3 -import re - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -from ..database import to_serialized -from ..dataset import DataTrainingArguments -from ...utils.args import ModelArguments -from ...utils.bridge_content_encoder import ( - get_database_matches, -) -from ...constants import ( - SINGLE_TABLE_NAME, - DOCS_TABLE_NAME, - CREATE_VIRTUAL_TABLE_CMD, - EvalField, -) -from ..normalizer import prepare_df_for_neuraldb_from_table -from blendsql.db import SQLite - - -def hybridqa_metric_format_func(item: dict) -> dict: - prediction = item[EvalField.PREDICTION] - if isinstance(prediction, str): - prediction = [prediction] - if prediction is not None: - if len(prediction) < 1: - pred = "" - else: - pred = prediction[0] - else: - pred = "" - return { - "prediction": str(pred), - "reference": { - "answer_text": item[EvalField.GOLD_ANSWER], - "id": item[EvalField.UID], - "question": item[EvalField.QUESTION], - }, - } - - -def preprocess_hybridqa_table(table: dict) -> dict: - """Preprocesses wikitq headers to make them easier to parse in text-to-SQL task. - TODO: This is causing some encoding issues - """ - preprocessed_table = {"header": [], "rows": []} - for v in table["header"]: - preprocessed_table["header"].append(re.sub(r"(\'|\")", "", v)) - for v in table["rows"]: - preprocessed_table["rows"].append([re.sub(r"(\'|\")", "", item) for item in v]) - return preprocessed_table - - -def hybridqa_get_input( - question: str, - table: dict, - passages: dict, - table_id: str, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, -) -> Tuple[str, dict]: - """Prepares input for HybridQA dataset. - - Returns: - Tuple containing: - - str path to sqlite database - - dict containing arguments to be passed to guidance program - """ - db_path = Path(data_training_args.db_path) / "hybridqa" / f"{table_id}.db" - if not db_path.is_file(): - # Create db - if not db_path.parent.is_dir(): - db_path.parent.mkdir(parents=True) - sqlite_conn = sqlite3.connect(db_path) - prepare_df_for_neuraldb_from_table( - preprocess_hybridqa_table(table), add_row_id=False - ).to_sql(SINGLE_TABLE_NAME, sqlite_conn) - # Create virtual table to search over - c = sqlite_conn.cursor() - c.execute(CREATE_VIRTUAL_TABLE_CMD) - c.close() - # Add content - prepare_df_for_neuraldb_from_table( - preprocess_hybridqa_table(passages), add_row_id=False - ).to_sql(DOCS_TABLE_NAME, sqlite_conn, if_exists="append", index=False) - sqlite_conn.close() - db_path = str(db_path) - db = SQLite(db_path) - serialized_db = to_serialized( - db=db, - num_rows=data_training_args.num_serialized_rows, - ) - entire_serialized_db = to_serialized( - db=db, - num_rows=data_training_args.num_serialized_rows, - whole_table=True, - truncate_content=data_training_args.truncate_content, - ) - bridge_hints = None - if data_training_args.use_bridge_encoder: - bridge_hints = [] - column_str_with_values = "{table}.{column} ( {values} )" - value_sep = " , " - for table_name in db.iter_tables(): - if re.search(r"^{}_".format(DOCS_TABLE_NAME), table_name): - continue - for column_name in db.iter_columns(table_name): - matches = get_database_matches( - question=question, - table_name=table_name, - column_name=column_name, - db_path=db_path, - ) - if matches: - bridge_hints.append( - column_str_with_values.format( - table=table_name, - column=column_name, - values=value_sep.join(matches), - ) - ) - bridge_hints = " , ".join(bridge_hints) - db.con.close() - return ( - db_path, - { - "few_shot_prompt": open("./research/prompts/hybridqa/few_shot.txt").read(), - "ingredients_prompt": open( - "./research/prompts/hybridqa/ingredients.txt" - ).read(), - "question": question, - "serialized_db": serialized_db, - "entire_serialized_db": entire_serialized_db, - "bridge_hints": bridge_hints, - }, - ) - - -def hybridqa_pre_process_function( - batch: dict, data_training_args: DataTrainingArguments, model_args: ModelArguments -) -> dict: - db_path, input_program_args = zip( - *[ - hybridqa_get_input( - question=question, - table=table, - passages=passages, - table_id=table_id, - data_training_args=data_training_args, - model_args=model_args, - ) - for question, table, passages, table_id in zip( - batch[EvalField.QUESTION], - batch["table"], - batch["passages"], - batch["table_id"], - ) - ] - ) - return {"input_program_args": list(input_program_args), "db_path": list(db_path)} diff --git a/research/utils/normalizer.py b/research/utils/normalizer.py deleted file mode 100644 index 31ce6cbc..00000000 --- a/research/utils/normalizer.py +++ /dev/null @@ -1,559 +0,0 @@ -import warnings - -warnings.simplefilter(action="ignore", category=UserWarning) -from typing import Dict -import pandas as pd -import recognizers_suite -from recognizers_suite import Culture -import re -import unicodedata -from rapidfuzz import fuzz - -from .sql.extraction_from_sql import extract_partial_template_from_sql, tokenize -from .sql.all_keywords import ALL_KEY_WORDS - -culture = Culture.English - - -def str_normalize(user_input, recognition_types=None): - """A string normalizer which recognize and normalize value based on recognizers_suite""" - user_input = str(user_input) - user_input = user_input.replace("\\n", "; ") - - def replace_by_idx_pairs(orig_str, strs_to_replace, idx_pairs): - assert len(strs_to_replace) == len(idx_pairs) - last_end = 0 - to_concat = [] - for idx_pair, str_to_replace in zip(idx_pairs, strs_to_replace): - to_concat.append(orig_str[last_end : idx_pair[0]]) - to_concat.append(str_to_replace) - last_end = idx_pair[1] - to_concat.append(orig_str[last_end:]) - return "".join(to_concat) - - if recognition_types is None: - recognition_types = [ - "datetime", - "number", - # "ordinal", - # "percentage", - # "age", - # "currency", - # "dimension", - # "temperature", - ] - - for recognition_type in recognition_types: - if re.match("\d+/\d+", user_input): - # avoid calculating str as 1991/92 - continue - recognized_list = getattr( - recognizers_suite, "recognize_{}".format(recognition_type) - )( - user_input, culture - ) # may match multiple parts - strs_to_replace = [] - idx_pairs = [] - for recognized in recognized_list: - if not recognition_type == "datetime": - recognized_value = recognized.resolution["value"] - if str(recognized_value).startswith("P"): - # if the datetime is a period: - continue - else: - strs_to_replace.append(recognized_value) - idx_pairs.append((recognized.start, recognized.end + 1)) - else: - if recognized.resolution: # in some cases, this variable could be none. - if len(recognized.resolution["values"]) == 1: - strs_to_replace.append( - recognized.resolution["values"][0]["timex"] - ) # We use timex as normalization - idx_pairs.append((recognized.start, recognized.end + 1)) - - if len(strs_to_replace) > 0: - user_input = replace_by_idx_pairs(user_input, strs_to_replace, idx_pairs) - - if re.match("(.*)-(.*)-(.*) 00:00:00", user_input): - user_input = user_input[: -len("00:00:00") - 1] - # '2008-04-13 00:00:00' -> '2008-04-13' - return user_input - - -def prepare_df_for_neuraldb_from_table( - table: Dict, add_row_id=True, normalize=True, lower_case=True -): - header, rows = table["header"], table["rows"] - if add_row_id and "row_id" not in header: - header = ["row_id"] + header - rows = [["{}".format(i)] + row for i, row in enumerate(rows)] - if normalize: - df = convert_df_type( - pd.DataFrame(data=rows, columns=header), lower_case=lower_case - ) - else: - df = pd.DataFrame(data=rows, columns=header) - - return df - - -def convert_df_type(df: pd.DataFrame, lower_case=True): - """ - A simple converter of dataframe data type from string to int/float/datetime. - """ - - def get_table_content_in_column(table): - if isinstance(table, pd.DataFrame): - header = table.columns.tolist() - rows = table.values.tolist() - else: - # Standard table dict format - header, rows = table["header"], table["rows"] - all_col_values = [] - for i in range(len(header)): - one_col_values = [] - for _row in rows: - one_col_values.append(_row[i]) - all_col_values.append(one_col_values) - return all_col_values - - # Rename empty columns - new_columns = [] - for _idx, header in enumerate(df.columns): - if header == "": - new_columns.append( - "FilledColumnName" - ) # Fixme: give it a better name when all finished! - else: - new_columns.append(header) - df.columns = new_columns - - # Rename duplicate columns - new_columns = [] - for _idx, header in enumerate(df.columns): - if header in new_columns: - new_header, suffix = header, 2 - while new_header in new_columns: - new_header = header + "_" + str(suffix) - suffix += 1 - new_columns.append(new_header) - else: - new_columns.append(header) - df.columns = new_columns - - # Recognize null values like "-" - null_tokens = ["", "-", "/"] - for header in df.columns: - df[header] = df[header].map(lambda x: str(None) if x in null_tokens else x) - - # Convert the null values in digit column to "NaN" - all_col_values = get_table_content_in_column(df) - for col_i, one_col_values in enumerate(all_col_values): - all_number_flag = True - for _row_i, cell_value in enumerate(one_col_values): - try: - float(cell_value) - except Exception: - if not cell_value in [str(None), str(None).lower()]: - # None or none - all_number_flag = False - if all_number_flag: - _header = df.columns[col_i] - df[_header] = df[_header].map( - lambda x: "NaN" if x in [str(None), str(None).lower()] else x - ) - - # Normalize cell values. - for header in df.columns: - df[header] = df[header].map(lambda x: str_normalize(x)) - - # Strip the mis-added "01-01 00:00:00" - all_col_values = get_table_content_in_column(df) - for col_i, one_col_values in enumerate(all_col_values): - all_with_00_00_00 = True - all_with_01_00_00_00 = True - all_with_01_01_00_00_00 = True - for _row_i, cell_value in enumerate(one_col_values): - if not str(cell_value).endswith(" 00:00:00"): - all_with_00_00_00 = False - if not str(cell_value).endswith("-01 00:00:00"): - all_with_01_00_00_00 = False - if not str(cell_value).endswith("-01-01 00:00:00"): - all_with_01_01_00_00_00 = False - if all_with_01_01_00_00_00: - _header = df.columns[col_i] - df[_header] = df[_header].map(lambda x: x[: -len("-01-01 00:00:00")]) - continue - - if all_with_01_00_00_00: - _header = df.columns[col_i] - df[_header] = df[_header].map(lambda x: x[: -len("-01 00:00:00")]) - continue - - if all_with_00_00_00: - _header = df.columns[col_i] - df[_header] = df[_header].map(lambda x: x[: -len(" 00:00:00")]) - continue - - # Do header and cell value lower case - if lower_case: - new_columns = [] - for header in df.columns: - lower_header = str(header).lower() - if lower_header in new_columns: - new_header, suffix = lower_header, 2 - while new_header in new_columns: - new_header = lower_header + "-" + str(suffix) - suffix += 1 - new_columns.append(new_header) - else: - new_columns.append(lower_header) - df.columns = new_columns - for header in df.columns: - # df[header] = df[header].map(lambda x: str(x).lower()) - df[header] = df[header].map(lambda x: str(x).lower().strip()) - - # Recognize header type - for header in df.columns: - float_able = False - int_able = False - datetime_able = False - - # Recognize int & float type - try: - df[header].astype("float") - float_able = True - except: - pass - - if float_able: - try: - if all(df[header].astype("float") == df[header].astype(int)): - int_able = True - except: - pass - - if float_able: - if int_able: - df[header] = df[header].astype(int) - else: - df[header] = df[header].astype(float) - - # Recognize datetime type - try: - df[header].astype("datetime64") - datetime_able = True - except: - pass - - if datetime_able: - df[header] = df[header].astype("datetime64") - - return df - - -def normalize(x): - """Normalize string.""" - # Copied from WikiTableQuestions dataset official evaluator. - if x is None: - return None - # Remove diacritics - x = "".join( - c for c in unicodedata.normalize("NFKD", x) if unicodedata.category(c) != "Mn" - ) - # Normalize quotes and dashes - x = re.sub("[‘’´`]", "'", x) - x = re.sub("[“”]", '"', x) - x = re.sub("[‐‑‒–—−]", "-", x) - while True: - old_x = x - # Remove citations - x = re.sub("((?= fuzz_threshold: - matched_cells.append((cell, fuzz_score)) - - matched_cells = sorted(matched_cells, key=lambda x: x[1], reverse=True) - return matched_cells - - def _check_valid_fuzzy_match(value_str, matched_cell): - """ - Check if the fuzzy match is valid, now considering: - 1. The number/date should not be disturbed, but adding new number or deleting number is valid. - """ - number_pattern = "[+]?[.]?[\d]+(?:,\d\d\d)*[\.]?\d*(?:[eE][-+]?\d+)?" - numbers_in_value = re.findall(number_pattern, value_str) - numbers_in_matched_cell = re.findall(number_pattern, matched_cell) - try: - numbers_in_value = [ - float(num.replace(",", "")) for num in numbers_in_value - ] - except: - print( - f"Can't convert number string {numbers_in_value} into float in _check_valid_fuzzy_match()." - ) - try: - numbers_in_matched_cell = [ - float(num.replace(",", "")) for num in numbers_in_matched_cell - ] - except: - print( - f"Can't convert number string {numbers_in_matched_cell} into float in _check_valid_fuzzy_match()." - ) - numbers_in_value = set(numbers_in_value) - numbers_in_matched_cell = set(numbers_in_matched_cell) - - if numbers_in_value.issubset( - numbers_in_matched_cell - ) or numbers_in_matched_cell.issubset(numbers_in_value): - return True - else: - return False - - # Drop trailing '\n```', a pattern that may appear in Codex SQL generation - # TODO: I (Parker) commented this out - # sql_str = sql_str.rstrip("```").rstrip("\n") - - # Replace QA module with placeholder - qa_pattern = "QA\(.+?;.*?`.+?`.*?\)" - qas = re.findall(qa_pattern, sql_str) - for idx, qa in enumerate(qas): - sql_str = sql_str.replace(qa, f"placeholder{idx}") - - # Parse and replace SQL value with table contents - sql_tokens = tokenize(sql_str) - sql_template_tokens = extract_partial_template_from_sql(sql_str) - # Fix 'between' keyword bug in parsing templates - fixed_sql_template_tokens = [] - sql_tok_bias = 0 - for idx, sql_templ_tok in enumerate(sql_template_tokens): - sql_tok = sql_tokens[idx + sql_tok_bias] - if sql_tok == "between" and sql_templ_tok == "[WHERE_OP]": - fixed_sql_template_tokens.extend(["[WHERE_OP]", "[VALUE]", "and"]) - sql_tok_bias += 2 # pass '[VALUE]', 'and' - else: - fixed_sql_template_tokens.append(sql_templ_tok) - sql_template_tokens = fixed_sql_template_tokens - for idx, tok in enumerate(sql_tokens): - if tok in ALL_KEY_WORDS: - sql_tokens[idx] = tok.upper() - - if verbose: - print(sql_tokens) - print(sql_template_tokens) - - assert len(sql_tokens) == len(sql_template_tokens) - value_indices = [ - idx - for idx in range(len(sql_template_tokens)) - if sql_template_tokens[idx] == "[VALUE]" - ] - for value_idx in value_indices: - # Skip the value if the where condition column is QA module - if value_idx >= 2 and sql_tokens[value_idx - 2].startswith("placeholder"): - continue - value_str = sql_tokens[value_idx] - # Drop \"\" for fuzzy match - is_string = False - if value_str[0] == '"' and value_str[-1] == '"': - value_str = value_str[1:-1] - is_string = True - # If already fuzzy match, skip - if value_str[0] == "%" or value_str[-1] == "%": - continue - value_str = value_str.lower() - # Fuzzy Match - matched_cells = _get_matched_cells(value_str, df) - - if verbose: - print(matched_cells) - - new_value_str = value_str - if matched_cells: - # new_value_str = matched_cells[0][0] - for matched_cell, fuzz_score in matched_cells: - if _check_valid_fuzzy_match(value_str, matched_cell): - new_value_str = matched_cell - if verbose and new_value_str != value_str: - print( - "\tfuzzy match replacing!", - value_str, - "->", - matched_cell, - f"fuzz_score:{fuzz_score}", - ) - break - if is_string: - new_value_str = f'"{new_value_str}"' - sql_tokens[value_idx] = new_value_str - # Compose new sql string - # Clean column name in SQL since columns may have been tokenized in the postprocessing, e.g., (ppp) -> ( ppp ) - new_sql_str = " ".join(sql_tokens) - sql_columns = re.findall("`\s(.*?)\s`", new_sql_str) - for sql_col in sql_columns: - matched_columns = [] - for col in df.columns: - score = fuzz.ratio(sql_col.lower(), col) - if score == 100: - matched_columns = [(col, score)] - break - if score >= 80: - matched_columns.append((col, score)) - matched_columns = sorted(matched_columns, key=lambda x: x[1], reverse=True) - if matched_columns: - matched_col = matched_columns[0][0] - new_sql_str = new_sql_str.replace(f"` {sql_col} `", f"`{matched_col}`") - else: - new_sql_str = new_sql_str.replace(f"` {sql_col} `", f"`{sql_col}`") - - # Restore QA modules - for idx, qa in enumerate(qas): - new_sql_str = new_sql_str.replace(f"placeholder{idx}", qa) - - # Fix '<>' when composing the new sql - new_sql_str = new_sql_str.replace("< >", "<>") - - return new_sql_str - - sql_str = basic_fix(sql_str, list(df.columns), table_title) - - if process_program_with_fuzzy_match_on_db: - try: - sql_str = fuzzy_match_process(sql_str, df, verbose) - except: - pass - - return sql_str diff --git a/research/utils/ottqa/.gitignore b/research/utils/ottqa/.gitignore deleted file mode 100644 index 4423af26..00000000 --- a/research/utils/ottqa/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*.json -OTT-QA/*.json \ No newline at end of file diff --git a/research/utils/ottqa/OTT-QA/README.md b/research/utils/ottqa/OTT-QA/README.md deleted file mode 100644 index 3802f589..00000000 --- a/research/utils/ottqa/OTT-QA/README.md +++ /dev/null @@ -1,26 +0,0 @@ -Download the predictions from https://huggingface.co/kaixinm/CORE/blob/main/data/retriever_data.zip here. - -From the work https://github.com/Mayer123/UDT-QA - -``` -@inproceedings{ma-etal-2022-open-domain, - title = "Open-domain Question Answering via Chain of Reasoning over Heterogeneous Knowledge", - author = "Ma, Kaixin and - Cheng, Hao and - Liu, Xiaodong and - Nyberg, Eric and - Gao, Jianfeng", - editor = "Goldberg, Yoav and - Kozareva, Zornitsa and - Zhang, Yue", - booktitle = "Findings of the Association for Computational Linguistics: EMNLP 2022", - month = dec, - year = "2022", - address = "Abu Dhabi, United Arab Emirates", - publisher = "Association for Computational Linguistics", - url = "https://aclanthology.org/2022.findings-emnlp.392", - doi = "10.18653/v1/2022.findings-emnlp.392", - pages = "5360--5374", - abstract = "We propose a novel open-domain question answering (ODQA) framework for answering single/multi-hop questions across heterogeneous knowledge sources. The key novelty of our method is the introduction of the intermediary modules into the current retriever-reader pipeline. Unlike previous methods that solely rely on the retriever for gathering all evidence in isolation,our intermediary performs a chain of reasoning over the retrieved set. Specifically, our method links the retrieved evidence with its related global context into graphs and organizes them into a candidate list of evidence chains. Built upon pretrained language models, our system achieves competitive performance on two ODQA datasets, OTT-QA and NQ, against tables and passages from Wikipedia.In particular, our model substantially outperforms the previous state-of-the-art on OTT-QA with an exact match score of 47.3 (45{\%} relative gain).", -} -``` \ No newline at end of file diff --git a/research/utils/ottqa/__init__.py b/research/utils/ottqa/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/ottqa/ottqa.py b/research/utils/ottqa/ottqa.py deleted file mode 100644 index 0bf27154..00000000 --- a/research/utils/ottqa/ottqa.py +++ /dev/null @@ -1,156 +0,0 @@ -# Set up logging -import logging -import sys -from typing import Tuple - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -from ..database import to_serialized -from ..dataset import DataTrainingArguments -from ...utils.args import ModelArguments -from ...constants import DOCS_TABLE_NAME, EvalField -from ...utils.bridge_content_encoder import ( - get_database_matches, -) - -from blendsql.db import SQLite -from pathlib import Path -import json -from diskcache import Cache - -ottqa_db_path = "./research/db/ottqa/ottqa.db" -db = SQLite(ottqa_db_path) -cache = Cache() - -ottqa_question_id_to_retriever_results = {} -for filename in Path("./research/utils/ottqa/OTT-QA").iterdir(): - if filename.suffix != ".json": - continue - with open(filename, "r") as f: - d = json.load(f) - for item in d: - id_field = "question_id" if "question_id" in item else "id" - ottqa_question_id_to_retriever_results[item[id_field]] = item["results"] - -with open("./research/utils/ottqa/table_id_to_tablename.json", "r") as f: - table_id_to_tablename = json.load(f) - - -def ottqa_metric_format_func(item: dict) -> dict: - prediction = item[EvalField.PREDICTION] - if prediction is not None: - if len(prediction) < 1: - pred = "" - else: - pred = prediction[0] - else: - pred = "" - return { - "prediction": str(pred), - "reference": { - "answer_text": item[EvalField.GOLD_ANSWER], - "id": item[EvalField.UID], - "question": item[EvalField.QUESTION], - }, - } - - -def ottqa_get_input( - question: str, - question_id: str, - db_path: str, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, -) -> Tuple[str, dict]: - if "docs_tablesize" not in cache: - cache["docs_tablesize"] = db.execute_to_df( - f"SELECT COUNT(*) FROM {DOCS_TABLE_NAME}" - ).values[0][0] - cache["docs_tablesize"] - chosen_tables = ottqa_question_id_to_retriever_results[question_id] - chosen_tables = [ - table_id_to_tablename["_".join(item["title"].split("_")[:-1])] - for item in chosen_tables - ] - chosen_tables = [f"./{i}" for i in chosen_tables] - - # filter unique and select top-n - seen_tables = set() - final_chosen_tables = [] - for t in chosen_tables: - if t in seen_tables: - continue - final_chosen_tables.append(t) - seen_tables.add(t) - chosen_tables = final_chosen_tables[:3] + [DOCS_TABLE_NAME] - - serialized_db = to_serialized( - db=db, - num_rows=data_training_args.num_serialized_rows, - use_tables=chosen_tables, - truncate_content=500, - ) - bridge_hints = None - if data_training_args.use_bridge_encoder: - bridge_hints = [] - column_str_with_values = "{table}.{column} ( {values} )" - value_sep = " , " - for table_name in chosen_tables: - if table_name == DOCS_TABLE_NAME: - continue - for column_name in db.iter_columns(table_name): - matches = get_database_matches( - question=question, - table_name=table_name, - column_name=column_name, - db=db, - ) - if matches: - bridge_hints.append( - column_str_with_values.format( - table=table_name, - column=column_name, - values=value_sep.join(matches), - ) - ) - bridge_hints = "\n".join(bridge_hints) - return ( - db_path, - { - "few_shot_prompt": open("./research/prompts/ottqa/few_shot.txt").read(), - "ingredients_prompt": open( - "./research/prompts/ottqa/ingredients.txt" - ).read(), - "question": question, - "serialized_db": serialized_db, - "entire_serialized_db": None, - "bridge_hints": bridge_hints, - "use_tables": chosen_tables, - }, - ) - - -def ottqa_pre_process_function( - batch: dict, data_training_args: DataTrainingArguments, model_args: ModelArguments -) -> dict: - db_path, input_program_args = zip( - *[ - ottqa_get_input( - question=question, - question_id=question_id, - db_path=db_path, - data_training_args=data_training_args, - model_args=model_args, - ) - for question, db_path, question_id in zip( - batch[EvalField.QUESTION], batch["db_path"], batch[EvalField.UID] - ) - ] - ) - return {"input_program_args": list(input_program_args), "db_path": list(db_path)} diff --git a/research/utils/sql/__init__.py b/research/utils/sql/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/sql/all_keywords.py b/research/utils/sql/all_keywords.py deleted file mode 100644 index 61da4624..00000000 --- a/research/utils/sql/all_keywords.py +++ /dev/null @@ -1,31 +0,0 @@ -CLAUSE_KEYWORDS = ( - "select", - "from", - "where", - "group", - "order", - "limit", - "intersect", - "union", - "except", -) -JOIN_KEYWORDS = ("join", "on", "as") - -WHERE_OPS = ( - "not", - "between", - "=", - ">", - "<", - ">=", - "<=", - "!=", - "in", - "like", - "is", - "exists", -) -UNIT_OPS = ("none", "-", "+", "*", "/") -AGG_OPS = ("none", "max", "min", "count", "sum", "avg") - -ALL_KEY_WORDS = CLAUSE_KEYWORDS + JOIN_KEYWORDS + WHERE_OPS + UNIT_OPS + AGG_OPS diff --git a/research/utils/sql/extraction_from_sql.py b/research/utils/sql/extraction_from_sql.py deleted file mode 100644 index 51b6c153..00000000 --- a/research/utils/sql/extraction_from_sql.py +++ /dev/null @@ -1,674 +0,0 @@ -import argparse -import json -from .process_sql import ( - tokenize, - CLAUSE_KEYWORDS, - WHERE_OPS, - COND_OPS, - UNIT_OPS, - AGG_OPS, - JOIN_KEYWORDS, - ORDER_OPS, - skip_semicolon, - SQL_OPS, -) - -KEPT_WHERE_OP = ("not", "in", "exists") - - -def parse_table_unit(toks, start_idx, tables_with_alias): - idx = start_idx - len_ = len(toks) - key = toks[idx] - - if idx + 1 < len_ and toks[idx + 1] == "as": - tables_with_alias[toks[idx + 2]] = toks[idx] - idx += 3 - else: - idx += 1 - - return idx, key - - -def parse_col(toks, start_idx, tables_with_alias, schema, default_tables=None): - """ - :returns next idx, column id - """ - tok = toks[start_idx] - if tok == "*": - return start_idx + 1 - - if "." in tok: # if token is a composite - alias, col = tok.split(".") - # key = tables_with_alias[alias] + "." + col - table = tables_with_alias[alias] - """ - Add schema - """ - if table not in schema: - schema[table] = [] - schema[table].append(col) - # We also want to normalize the column - toks[start_idx] = "{}.{}".format(table, col) - """ - END - """ - return start_idx + 1 - - assert ( - default_tables is not None and len(default_tables) > 0 - ), "Default tables should not be None or empty" - - # assert len(default_tables) == 1, "Default table should only have one time" - - """ - Add schema - """ - - # Find the best table here - def choose_best_table(default_tables, tok): - lower_tok = tok.lower() - candidate = process.extractOne( - lower_tok, [table.lower() for table in default_tables] - )[0] - return candidate - - if len(default_tables) != 1: - # print(default_tables) - table = choose_best_table(default_tables, tok) - # assert len(default_tables) == 1, "Default table should only have one time" - else: - table = default_tables[0] - if table not in schema: - schema[table] = [] - schema[table].append(tok) - toks[start_idx] = "{}.{}".format(table, tok) - return start_idx + 1 - - # for alias in default_tables: - # table = tables_with_alias[alias] - # if tok in schema.schema[table]: - # key = table + "." + tok - # return start_idx + 1, schema.idMap[key] - - # assert False, "Error col: {}".format(tok) - - -def parse_col_unit( - toks, start_idx, tables_with_alias, schema, default_tables=None, end_idx=None -): - """ - :returns next idx, (agg_op id, col_id) - """ - idx = start_idx - if end_idx is not None: - len_ = len(toks[start_idx:end_idx]) - else: - len_ = len(toks) - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] in AGG_OPS: - AGG_OPS.index(toks[idx]) - idx += 1 - assert idx < len_ and toks[idx] == "(" - idx += 1 - if toks[idx] == "distinct": - idx += 1 - idx = parse_col(toks, idx, tables_with_alias, schema, default_tables) - assert idx < len_ and toks[idx] == ")" - idx += 1 - return idx - - if toks[idx] == "distinct": - idx += 1 - AGG_OPS.index("none") - idx = parse_col(toks, idx, tables_with_alias, schema, default_tables) - - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - - return idx - - -def parse_val_unit(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - UNIT_OPS.index("none") - - idx = parse_col_unit(toks, idx, tables_with_alias, schema, default_tables) - if idx < len_ and toks[idx] in UNIT_OPS: - UNIT_OPS.index(toks[idx]) - idx += 1 - idx = parse_col_unit(toks, idx, tables_with_alias, schema, default_tables) - - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - - return idx - - -def parse_value(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] == "select": - idx = parse_sql(toks, idx, schema) - elif '"' in toks[idx]: # token is a string value - toks[idx] - # Replace with placeholder - toks[idx] = "_str_value_" - idx += 1 - else: - try: - float(toks[idx]) - toks[idx] = "_num_value_" - idx += 1 - except: - end_idx = idx - while ( - end_idx < len_ - and toks[end_idx] != "," - and toks[end_idx] != ")" - and toks[end_idx] != "and" - and toks[end_idx] not in CLAUSE_KEYWORDS - and toks[end_idx] not in JOIN_KEYWORDS - ): - end_idx += 1 - - # idx = parse_col_unit(toks[start_idx: end_idx], 0, tables_with_alias, schema, default_tables) - idx = parse_col_unit( - toks, - start_idx, - tables_with_alias, - schema, - default_tables, - end_idx=end_idx, - ) - idx = end_idx - - if isBlock: - assert toks[idx] == ")" - idx += 1 - - return idx - - -def parse_condition(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - # conds = [] - - while idx < len_: - idx = parse_val_unit(toks, idx, tables_with_alias, schema, default_tables) - if toks[idx] == "not": - idx += 1 - - assert ( - idx < len_ and toks[idx] in WHERE_OPS - ), "Error condition: idx: {}, tok: {}".format(idx, toks[idx]) - op_id = WHERE_OPS.index(toks[idx]) - idx += 1 - val1 = val2 = None - if op_id == WHERE_OPS.index( - "between" - ): # between..and... special case: dual values - idx = parse_value(toks, idx, tables_with_alias, schema, default_tables) - assert toks[idx] == "and" - idx += 1 - idx = parse_value(toks, idx, tables_with_alias, schema, default_tables) - else: # normal case: single value - idx = parse_value(toks, idx, tables_with_alias, schema, default_tables) - - # conds.append((not_op, op_id, val_unit, val1, val2)) - - if idx < len_ and ( - toks[idx] in CLAUSE_KEYWORDS - or toks[idx] in (")", ";") - or toks[idx] in JOIN_KEYWORDS - ): - break - - if idx < len_ and toks[idx] in COND_OPS: - # conds.append(toks[idx]) - idx += 1 # skip and/or - return idx # , conds - - -def parse_from(toks, start_idx, schema): - assert "from" in toks[start_idx:], "'from' not found" - tables_with_alias = {} - - len_ = len(toks) - idx = toks.index("from", start_idx) + 1 - default_tables = [] - # print(idx, len_) - while idx < len_: - # print("idx", idx, toks[idx]) - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] == "select": - idx = parse_sql(toks, idx, schema) - # table_units.append((TABLE_TYPE['sql'], sql)) - else: - if idx < len_ and toks[idx] == "join": - idx += 1 # skip join - idx, table_name = parse_table_unit(toks, idx, tables_with_alias) - # print(table_name) - # table_units.append((TABLE_TYPE['table_unit'], table_unit)) - default_tables.append(table_name) - """ - Add schema - """ - if table_name not in schema: - schema[table_name] = [] - """ - END - """ - - if idx < len_ and toks[idx] == "on": - idx += 1 # skip on - idx = parse_condition(toks, idx, tables_with_alias, schema, default_tables) - # if len(conds) > 0: - # conds.append('and') - # conds.extend(this_conds) - - if isBlock: - assert toks[idx] == ")" - idx += 1 - - if idx < len_ and (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - break - - return idx, default_tables, tables_with_alias - - -def parse_select(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - - assert toks[idx] == "select", "'select' not found" - idx += 1 - if idx < len_ and toks[idx] == "distinct": - idx += 1 - - while idx < len_ and toks[idx] not in CLAUSE_KEYWORDS: - AGG_OPS.index("none") - if toks[idx] in AGG_OPS: - AGG_OPS.index(toks[idx]) - idx += 1 - idx = parse_val_unit(toks, idx, tables_with_alias, schema, default_tables) - # val_units.append((agg_id, val_unit)) - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - - return idx - - -def parse_where(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - - if idx >= len_ or toks[idx] != "where": - return idx - - idx += 1 - idx = parse_condition(toks, idx, tables_with_alias, schema, default_tables) - return idx - - -def parse_group_by(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - - if idx >= len_ or toks[idx] != "group": - return idx - - idx += 1 - assert toks[idx] == "by" - idx += 1 - - while idx < len_ and not (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - idx = parse_col_unit(toks, idx, tables_with_alias, schema, default_tables) - # col_units.append(col_unit) - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - else: - break - - return idx - - -def parse_having(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - - if idx >= len_ or toks[idx] != "having": - return idx - - idx += 1 - idx = parse_condition(toks, idx, tables_with_alias, schema, default_tables) - return idx - - -def parse_order_by(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - order_type = "asc" # default type is 'asc' - - if idx >= len_ or toks[idx] != "order": - return idx - - idx += 1 - assert toks[idx] == "by" - idx += 1 - - while idx < len_ and not (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - idx = parse_val_unit(toks, idx, tables_with_alias, schema, default_tables) - # val_units.append(val_unit) - if idx < len_ and toks[idx] in ORDER_OPS: - toks[idx] - idx += 1 - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - else: - break - - return idx - - -def parse_limit(toks, start_idx): - idx = start_idx - len_ = len(toks) - - if idx < len_ and toks[idx] == "limit": - idx += 2 - toks[idx - 1] = "_limit_value_" - # make limit value can work, cannot assume put 1 as a fake limit number - if type(toks[idx - 1]) != int: - return idx - - return idx - - return idx - - -def parse_sql(toks, start_idx, schema): - isBlock = False # indicate whether this is a block of sql/sub-sql - len_ = len(toks) - idx = start_idx - - if toks[idx] == "(": - isBlock = True - idx += 1 - - from_end_idx, default_tables, tables_with_alias = parse_from( - toks, start_idx, schema - ) - - _ = parse_select(toks, idx, tables_with_alias, schema, default_tables) - idx = from_end_idx - - idx = parse_where(toks, idx, tables_with_alias, schema, default_tables) - idx = parse_group_by(toks, idx, tables_with_alias, schema, default_tables) - idx = parse_having(toks, idx, tables_with_alias, schema, default_tables) - idx = parse_order_by(toks, idx, tables_with_alias, schema, default_tables) - idx = parse_limit(toks, idx) - # - idx = skip_semicolon(toks, idx) - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - idx = skip_semicolon(toks, idx) - - # for op in SQL_OPS: # initialize IUE - # sql[op] = None - if idx < len_ and toks[idx] in SQL_OPS: - toks[idx] - idx += 1 - idx = parse_sql(toks, idx, schema) - # sql[sql_op] = IUE_sql - return idx - - -def extract_schema_from_sql(schema, sql): - toks = tokenize(sql) - parse_sql(toks=toks, start_idx=0, schema=schema) - return toks - - -def extract_template_from_sql(sql, schema=None): - if schema is None: - schema = {} - try: - toks = tokenize(sql) - except: - print("Tokenization error for {}".format(sql)) - toks = [] - # print(toks) - template = [] - # ignore_follow_up_and = False - len_ = len(toks) - idx = 0 - while idx < len_: - tok = toks[idx] - if tok == "from": - template.append(tok) - if toks[idx + 1] != "(": - template.append("[FROM_PART]") - idx += 1 - while idx < len_ and ( - toks[idx] not in CLAUSE_KEYWORDS and toks[idx] != ")" - ): - idx += 1 - continue - elif tok in CLAUSE_KEYWORDS: - template.append(tok) - elif tok in AGG_OPS: - template.append(tok) - elif tok in [",", "*", "(", ")", "having", "by", "distinct"]: - template.append(tok) - elif tok in ["asc", "desc"]: - template.append("[ORDER_DIRECTION]") - elif tok in WHERE_OPS: - if tok in KEPT_WHERE_OP: - template.append(tok) - else: - template.append("[WHERE_OP]") - if tok == "between": - idx += 2 - elif tok in COND_OPS: - template.append(tok) - elif template[-1] == "[WHERE_OP]": - template.append("[VALUE]") - elif template[-1] == "limit": - template.append("[LIMIT_VALUE]") - elif template[-1] != "[MASK]": # value, schema, join on as - template.append("[MASK]") - idx += 1 - return template - - -def extract_partial_template_from_sql(sql, schema=None): - if schema is None: - schema = {} - toks = tokenize(sql) - # print(toks) - template = [] - # ignore_follow_up_and = False - len_ = len(toks) - idx = 0 - while idx < len_: - tok = toks[idx] - if tok == "from": - template.append(tok) - if toks[idx + 1] != "(": - # template.append("[FROM_PART]") - idx += 1 - while idx < len_ and ( - toks[idx] not in CLAUSE_KEYWORDS and toks[idx] != ")" - ): - template.append(toks[idx]) - idx += 1 - continue - elif tok in CLAUSE_KEYWORDS: - template.append(tok) - elif tok in AGG_OPS: - template.append(tok) - elif tok in [",", "*", "(", ")", "having", "by", "distinct"]: - template.append(tok) - elif tok in ["asc", "desc"]: - template.append("[ORDER_DIRECTION]") - elif tok in WHERE_OPS: - if tok in KEPT_WHERE_OP: - template.append(tok) - else: - template.append("[WHERE_OP]") - if tok == "between": - idx += 2 - elif tok in COND_OPS: - template.append(tok) - elif template[-1] == "[WHERE_OP]": - template.append("[VALUE]") - elif template[-1] == "limit": - template.append("[LIMIT_VALUE]") - else: - template.append(tok) - idx += 1 - return template - - -def is_valid_schema(schema): - # There is no "." and " " in the column name - for table in schema: - if "." in table: - return False - if any([keyword == table for keyword in CLAUSE_KEYWORDS]): - return False - for column in schema[table]: - if "." in column or " " in column or '"' in column or "'" in column: - return False - return True - - -def clean_sql(sql): - while "JOIN JOIN" in sql: - sql = sql.replace("JOIN JOIN", "JOIN") - if "JOIN WHERE" in sql: - sql = sql.replace("JOIN WHERE", "WHERE") - if "JOIN GROUP BY" in sql: - sql = sql.replace("JOIN GROUP BY", "GROUP BY") - return sql - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("--input_file", type=str) - parser.add_argument("--output_file", type=str) - parser.add_argument("--mode", type=str, choices=["debug", "verbose", "silent"]) - parser.add_argument( - "--task", type=str, choices=["template_extraction", "schema_extraction"] - ) - args = parser.parse_args() - - if args.task == "schema_extraction": - if args.mode == "debug": - sql = "SELECT count(*) FROM games" - sql = sql + " INTERSECT " + "SELECT sacks, year FROM players" - sql = ( - sql - + " EXCEPT " - + 'SELECT T1.year, T1.sacks FROM players AS T1 JOIN tackles AS T2 ON T1.id = T2.player_id WHERE T2.manager = "A" and T2.season NOT IN (SELECT season FROM match WHERE match_name = "IVL" INTERSECT SELECT T1.year, T1.sacks FROM sack AS T1) GROUP BY T1.year, T1.sacks HAVING count(T1.coach) > 10 ORDER BY T2.score LIMIT 5' - ) - sql = "SELECT T1.pld FROM pld AS T1 JOIN games AS T2 ON T1.crs_code = T2.crs_code JOIN GROUP BY T1.pld WHERE T2.gf = '8' AND T2.gf = '9'" - sql = 'select * from head where height = "6-0" or height = "6-0" order by height asc' - schema = {} - extract_schema_from_sql(schema, sql) - print(schema, is_valid_schema(schema)) - elif args.mode == "verbose": - fout = open(args.output_file, "w") - with open(args.input_file) as fin: - for line in fin: - example = json.loads(line) - schema = {} - try: - sql = example["sql"] if "sql" in example else example["pred"] - sql = clean_sql(sql) - example["sql"] = sql - extract_schema_from_sql(schema, sql) - - except: - # print(sql) - continue - for table in schema: - schema[table] = list(set(schema[table])) - if is_valid_schema(schema): - example["extracted_schema"] = schema - fout.write(json.dumps(example) + "\n") - elif args.mode == "verbose": - fout = open(args.output_file, "w") - with open(args.input_file) as fin: - for line in fin: - example = json.loads(line) - schema = {} - sql = example["sql"] if "sql" in example else example["pred"] - sql = clean_sql(sql) - example["sql"] = sql - extract_schema_from_sql(schema, sql) - for table in schema: - schema[table] = list(set(schema[table])) - example["extracted_schema"] = schema - fout.write(json.dumps(example) + "\n") - if is_valid_schema(schema): - example["extracted_schema"] = schema - fout.write(json.dumps(example) + "\n") - elif args.task == "template_extraction": - if args.mode == "debug": - sql = "SELECT avg(T1.Votes) FROM seats AS T1 JOIN votes AS T2 ON T1.Seat_ID = T2.Seat_ID WHERE T1.seats BETWEEN 1 AND 2 and T1.Seats = 1 AND T2.Votes = 10" - print(extract_template_from_sql(sql)) - print(extract_partial_template_from_sql(sql)) - elif args.mode == "verbose": - fout_json = open(args.output_file + ".json", "w") - fout_txt = open(args.output_file + ".txt", "w") - low_freq_txt = open(args.output_file + ".low_freq", "w") - high_freq_txt = open(args.output_file + ".high_freq", "w") - all_templates = set() - # for input_file in args.input_file.split(","): - templates = {} - with open(args.input_file) as fin: - for line in fin: - example = json.loads(line) - sql = example["sql"] if "sql" in example else example["pred"] - if isinstance(sql, list): - sql = sql[-1] - template = extract_template_from_sql(sql) - template_str = " ".join(template) - if template_str not in templates: - templates[template_str] = [] - templates[template_str].append(sql) - print("{} has template {}".format(args.input_file, len(templates))) - - json.dump(templates, fout_json) - for template in sorted(templates.keys()): - if len(templates[template]) > 1: - high_freq_txt.write(template + "\n") - else: - low_freq_txt.write(template + "\n") - fout_txt.write(template + "\n") diff --git a/research/utils/sql/process_sql.py b/research/utils/sql/process_sql.py deleted file mode 100644 index ea2c7cf5..00000000 --- a/research/utils/sql/process_sql.py +++ /dev/null @@ -1,676 +0,0 @@ -################################ -# Assumptions: -# 1. sql is correct -# 2. only table name has alias -# 3. only one intersect/union/except -# -# val: number(float)/string(str)/sql(dict) -# col_unit: (agg_id, col_id, isDistinct(bool)) -# val_unit: (unit_op, col_unit1, col_unit2) -# table_unit: (table_type, col_unit/sql) -# cond_unit: (not_op, op_id, val_unit, val1, val2) -# condition: [cond_unit1, 'and'/'or', cond_unit2, ...] -# sql { -# 'select': (isDistinct(bool), [(agg_id, val_unit), (agg_id, val_unit), ...]) -# 'from': {'table_units': [table_unit1, table_unit2, ...], 'conds': condition} -# 'where': condition -# 'groupBy': [col_unit1, col_unit2, ...] -# 'orderBy': ('asc'/'desc', [val_unit1, val_unit2, ...]) -# 'having': condition -# 'limit': None/limit value -# 'intersect': None/sql -# 'except': None/sql -# 'union': None/sql -# } -################################ - -import json -import sqlite3 -from nltk import word_tokenize - -CLAUSE_KEYWORDS = ( - "select", - "from", - "where", - "group", - "order", - "limit", - "intersect", - "union", - "except", -) -JOIN_KEYWORDS = ("join", "on", "as") - -WHERE_OPS = ( - "not", - "between", - "=", - ">", - "<", - ">=", - "<=", - "!=", - "in", - "like", - "is", - "exists", -) -UNIT_OPS = ("none", "-", "+", "*", "/") -AGG_OPS = ("none", "max", "min", "count", "sum", "avg") -TABLE_TYPE = { - "sql": "sql", - "table_unit": "table_unit", -} - -COND_OPS = ("and", "or") -SQL_OPS = ("intersect", "union", "except") -ORDER_OPS = ("desc", "asc") - - -class Schema: - """ - Simple schema which maps table&column to a unique identifier - """ - - def __init__(self, schema): - self._schema = schema - self._idMap = self._map(self._schema) - - @property - def schema(self): - return self._schema - - @property - def idMap(self): - return self._idMap - - def _map(self, schema): - idMap = {"*": "__all__"} - id = 1 - for key, vals in schema.items(): - for val in vals: - idMap[key.lower() + "." + val.lower()] = ( - "__" + key.lower() + "." + val.lower() + "__" - ) - id += 1 - - for key in schema: - idMap[key.lower()] = "__" + key.lower() + "__" - id += 1 - - return idMap - - -def get_schema(db): - """ - Get database's schema, which is a dict with table name as key - and list of column names as value - :param db: database path - :return: schema dict - """ - - schema = {} - conn = sqlite3.connect(db) - cursor = conn.cursor() - - # fetch table names - cursor.execute("SELECT name FROM sqlite_master WHERE type='table';") - tables = [str(table[0].lower()) for table in cursor.fetchall()] - - # fetch table info - for table in tables: - cursor.execute("PRAGMA table_info({})".format(table)) - schema[table] = [str(col[1].lower()) for col in cursor.fetchall()] - - return schema - - -def get_schema_from_json(fpath): - with open(fpath) as f: - data = json.load(f) - - schema = {} - for entry in data: - table = str(entry["table"].lower()) - cols = [str(col["column_name"].lower()) for col in entry["col_data"]] - schema[table] = cols - - return schema - - -def tokenize(string): - string = str(string) - string = string.replace( - "'", '"' - ) # ensures all string values wrapped by "" problem?? - quote_idxs = [idx for idx, char in enumerate(string) if char == '"'] - assert len(quote_idxs) % 2 == 0, "Unexpected quote" - - # keep string value as token - vals = {} - for i in range(len(quote_idxs) - 1, -1, -2): - qidx1 = quote_idxs[i - 1] - qidx2 = quote_idxs[i] - val = string[qidx1 : qidx2 + 1] - key = "__val_{}_{}__".format(qidx1, qidx2) - string = string[:qidx1] + key + string[qidx2 + 1 :] - vals[key] = val - - # tokenize sql - toks_tmp = [word.lower() for word in word_tokenize(string)] - toks = [] - for tok in toks_tmp: - if tok.startswith("=__val_"): - tok = tok[1:] - toks.append("=") - toks.append(tok) - - # replace with string value token - for i in range(len(toks)): - if toks[i] in vals: - toks[i] = vals[toks[i]] - - # find if there exists !=, >=, <= - eq_idxs = [idx for idx, tok in enumerate(toks) if tok == "="] - eq_idxs.reverse() - prefix = ("!", ">", "<") - for eq_idx in eq_idxs: - pre_tok = toks[eq_idx - 1] - if pre_tok in prefix: - toks = toks[: eq_idx - 1] + [pre_tok + "="] + toks[eq_idx + 1 :] - - return toks - - -def scan_alias(toks): - """Scan the index of 'as' and build the map for all alias""" - as_idxs = [idx for idx, tok in enumerate(toks) if tok == "as"] - alias = {} - for idx in as_idxs: - alias[toks[idx + 1]] = toks[idx - 1] - return alias - - -def get_tables_with_alias(schema, toks): - tables = scan_alias(toks) - for key in schema: - assert key not in tables, "Alias {} has the same name in table".format(key) - tables[key] = key - return tables - - -def parse_col(toks, start_idx, tables_with_alias, schema, default_tables=None): - """ - :returns next idx, column id - """ - tok = toks[start_idx] - if tok == "*": - return start_idx + 1, schema.idMap[tok] - - if "." in tok: # if token is a composite - alias, col = tok.split(".") - key = tables_with_alias[alias] + "." + col - return start_idx + 1, schema.idMap[key] - - assert ( - default_tables is not None and len(default_tables) > 0 - ), "Default tables should not be None or empty" - - for alias in default_tables: - table = tables_with_alias[alias] - if tok in schema.schema[table]: - key = table + "." + tok - return start_idx + 1, schema.idMap[key] - - raise AssertionError("Error col: {}".format(tok)) - - -def parse_col_unit(toks, start_idx, tables_with_alias, schema, default_tables=None): - """ - :returns next idx, (agg_op id, col_id) - """ - idx = start_idx - len_ = len(toks) - isBlock = False - isDistinct = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] in AGG_OPS: - agg_id = AGG_OPS.index(toks[idx]) - idx += 1 - assert idx < len_ and toks[idx] == "(" - idx += 1 - if toks[idx] == "distinct": - idx += 1 - isDistinct = True - idx, col_id = parse_col(toks, idx, tables_with_alias, schema, default_tables) - assert idx < len_ and toks[idx] == ")" - idx += 1 - return idx, (agg_id, col_id, isDistinct) - - if toks[idx] == "distinct": - idx += 1 - isDistinct = True - agg_id = AGG_OPS.index("none") - idx, col_id = parse_col(toks, idx, tables_with_alias, schema, default_tables) - - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - - return idx, (agg_id, col_id, isDistinct) - - -def parse_val_unit(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - col_unit1 = None - col_unit2 = None - unit_op = UNIT_OPS.index("none") - - idx, col_unit1 = parse_col_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - if idx < len_ and toks[idx] in UNIT_OPS: - unit_op = UNIT_OPS.index(toks[idx]) - idx += 1 - idx, col_unit2 = parse_col_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - - return idx, (unit_op, col_unit1, col_unit2) - - -def parse_table_unit(toks, start_idx, tables_with_alias, schema): - """ - :returns next idx, table id, table name - """ - idx = start_idx - len_ = len(toks) - key = tables_with_alias[toks[idx]] - - if idx + 1 < len_ and toks[idx + 1] == "as": - idx += 3 - else: - idx += 1 - - return idx, schema.idMap[key], key - - -def parse_value(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] == "select": - idx, val = parse_sql(toks, idx, tables_with_alias, schema) - elif '"' in toks[idx]: # token is a string value - val = toks[idx] - idx += 1 - else: - try: - val = float(toks[idx]) - idx += 1 - except: - end_idx = idx - while ( - end_idx < len_ - and toks[end_idx] != "," - and toks[end_idx] != ")" - and toks[end_idx] != "and" - and toks[end_idx] not in CLAUSE_KEYWORDS - and toks[end_idx] not in JOIN_KEYWORDS - ): - end_idx += 1 - - idx, val = parse_col_unit( - toks[start_idx:end_idx], 0, tables_with_alias, schema, default_tables - ) - idx = end_idx - - if isBlock: - assert toks[idx] == ")" - idx += 1 - - return idx, val - - -def parse_condition(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - conds = [] - - while idx < len_: - idx, val_unit = parse_val_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - not_op = False - if toks[idx] == "not": - not_op = True - idx += 1 - - assert ( - idx < len_ and toks[idx] in WHERE_OPS - ), "Error condition: idx: {}, tok: {}".format(idx, toks[idx]) - op_id = WHERE_OPS.index(toks[idx]) - idx += 1 - val1 = val2 = None - if op_id == WHERE_OPS.index( - "between" - ): # between..and... special case: dual values - idx, val1 = parse_value( - toks, idx, tables_with_alias, schema, default_tables - ) - assert toks[idx] == "and" - idx += 1 - idx, val2 = parse_value( - toks, idx, tables_with_alias, schema, default_tables - ) - else: # normal case: single value - idx, val1 = parse_value( - toks, idx, tables_with_alias, schema, default_tables - ) - val2 = None - - conds.append((not_op, op_id, val_unit, val1, val2)) - - if idx < len_ and ( - toks[idx] in CLAUSE_KEYWORDS - or toks[idx] in (")", ";") - or toks[idx] in JOIN_KEYWORDS - ): - break - - if idx < len_ and toks[idx] in COND_OPS: - conds.append(toks[idx]) - idx += 1 # skip and/or - - return idx, conds - - -def parse_select(toks, start_idx, tables_with_alias, schema, default_tables=None): - idx = start_idx - len_ = len(toks) - - assert toks[idx] == "select", "'select' not found" - idx += 1 - isDistinct = False - if idx < len_ and toks[idx] == "distinct": - idx += 1 - isDistinct = True - val_units = [] - - while idx < len_ and toks[idx] not in CLAUSE_KEYWORDS: - agg_id = AGG_OPS.index("none") - if toks[idx] in AGG_OPS: - agg_id = AGG_OPS.index(toks[idx]) - idx += 1 - idx, val_unit = parse_val_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - val_units.append((agg_id, val_unit)) - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - - return idx, (isDistinct, val_units) - - -def parse_from(toks, start_idx, tables_with_alias, schema): - """ - Assume in the from clause, all table units are combined with join - """ - assert "from" in toks[start_idx:], "'from' not found" - - len_ = len(toks) - idx = toks.index("from", start_idx) + 1 - default_tables = [] - table_units = [] - conds = [] - - while idx < len_: - isBlock = False - if toks[idx] == "(": - isBlock = True - idx += 1 - - if toks[idx] == "select": - idx, sql = parse_sql(toks, idx, tables_with_alias, schema) - table_units.append((TABLE_TYPE["sql"], sql)) - else: - if idx < len_ and toks[idx] == "join": - idx += 1 # skip join - idx, table_unit, table_name = parse_table_unit( - toks, idx, tables_with_alias, schema - ) - table_units.append((TABLE_TYPE["table_unit"], table_unit)) - default_tables.append(table_name) - if idx < len_ and toks[idx] == "on": - idx += 1 # skip on - idx, this_conds = parse_condition( - toks, idx, tables_with_alias, schema, default_tables - ) - if len(conds) > 0: - conds.append("and") - conds.extend(this_conds) - - if isBlock: - assert toks[idx] == ")" - idx += 1 - if idx < len_ and (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - break - - return idx, table_units, conds, default_tables - - -def parse_where(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - - if idx >= len_ or toks[idx] != "where": - return idx, [] - - idx += 1 - idx, conds = parse_condition(toks, idx, tables_with_alias, schema, default_tables) - return idx, conds - - -def parse_group_by(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - col_units = [] - - if idx >= len_ or toks[idx] != "group": - return idx, col_units - - idx += 1 - assert toks[idx] == "by" - idx += 1 - - while idx < len_ and not (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - idx, col_unit = parse_col_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - col_units.append(col_unit) - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - else: - break - - return idx, col_units - - -def parse_order_by(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - val_units = [] - order_type = "asc" # default type is 'asc' - - if idx >= len_ or toks[idx] != "order": - return idx, val_units - - idx += 1 - assert toks[idx] == "by" - idx += 1 - - while idx < len_ and not (toks[idx] in CLAUSE_KEYWORDS or toks[idx] in (")", ";")): - idx, val_unit = parse_val_unit( - toks, idx, tables_with_alias, schema, default_tables - ) - val_units.append(val_unit) - if idx < len_ and toks[idx] in ORDER_OPS: - order_type = toks[idx] - idx += 1 - if idx < len_ and toks[idx] == ",": - idx += 1 # skip ',' - else: - break - - return idx, (order_type, val_units) - - -def parse_having(toks, start_idx, tables_with_alias, schema, default_tables): - idx = start_idx - len_ = len(toks) - - if idx >= len_ or toks[idx] != "having": - return idx, [] - - idx += 1 - idx, conds = parse_condition(toks, idx, tables_with_alias, schema, default_tables) - return idx, conds - - -def parse_limit(toks, start_idx): - idx = start_idx - len_ = len(toks) - - if idx < len_ and toks[idx] == "limit": - idx += 2 - # make limit value can work, cannot assume put 1 as a fake limit number - if type(toks[idx - 1]) != int: - return idx, 1 - - return idx, int(toks[idx - 1]) - - return idx, None - - -def parse_sql(toks, start_idx, tables_with_alias, schema): - isBlock = False # indicate whether this is a block of sql/sub-sql - len_ = len(toks) - idx = start_idx - - sql = {} - if toks[idx] == "(": - isBlock = True - idx += 1 - - # parse from clause in order to get default tables - from_end_idx, table_units, conds, default_tables = parse_from( - toks, start_idx, tables_with_alias, schema - ) - sql["from"] = {"table_units": table_units, "conds": conds} - # select clause - _, select_col_units = parse_select( - toks, idx, tables_with_alias, schema, default_tables - ) - idx = from_end_idx - sql["select"] = select_col_units - # where clause - idx, where_conds = parse_where(toks, idx, tables_with_alias, schema, default_tables) - sql["where"] = where_conds - # group by clause - idx, group_col_units = parse_group_by( - toks, idx, tables_with_alias, schema, default_tables - ) - sql["groupBy"] = group_col_units - # having clause - idx, having_conds = parse_having( - toks, idx, tables_with_alias, schema, default_tables - ) - sql["having"] = having_conds - # order by clause - idx, order_col_units = parse_order_by( - toks, idx, tables_with_alias, schema, default_tables - ) - sql["orderBy"] = order_col_units - # limit clause - idx, limit_val = parse_limit(toks, idx) - sql["limit"] = limit_val - - idx = skip_semicolon(toks, idx) - if isBlock: - assert toks[idx] == ")" - idx += 1 # skip ')' - idx = skip_semicolon(toks, idx) - - # intersect/union/except clause - for op in SQL_OPS: # initialize IUE - sql[op] = None - if idx < len_ and toks[idx] in SQL_OPS: - sql_op = toks[idx] - idx += 1 - idx, IUE_sql = parse_sql(toks, idx, tables_with_alias, schema) - sql[sql_op] = IUE_sql - return idx, sql - - -def load_data(fpath): - with open(fpath) as f: - data = json.load(f) - return data - - -def get_sql(schema, query): - toks = tokenize(query) - tables_with_alias = get_tables_with_alias(schema.schema, toks) - _, sql = parse_sql(toks, 0, tables_with_alias, schema) - - return sql - - -def skip_semicolon(toks, start_idx): - idx = start_idx - while idx < len(toks) and toks[idx] == ";": - idx += 1 - return idx - - -def get_schemas_from_json(fpath): - with open(fpath) as f: - data = json.load(f) - db_names = [db["db_id"] for db in data] - - tables = {} - schemas = {} - for db in data: - db_id = db["db_id"] - schema = {} # {'table': [col.lower, ..., ]} * -> __all__ - column_names_original = db["column_names_original"] - table_names_original = db["table_names_original"] - tables[db_id] = { - "column_names_original": column_names_original, - "table_names_original": table_names_original, - } - for i, tabn in enumerate(table_names_original): - table = str(tabn.lower()) - cols = [str(col.lower()) for td, col in column_names_original if td == i] - schema[table] = cols - schemas[db_id] = schema - - return schemas, db_names, tables diff --git a/research/utils/wikitq/__init__.py b/research/utils/wikitq/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/research/utils/wikitq/utils.py b/research/utils/wikitq/utils.py deleted file mode 100644 index ea242597..00000000 --- a/research/utils/wikitq/utils.py +++ /dev/null @@ -1,184 +0,0 @@ -import re -import json -import records -from typing import List, Dict -from sqlalchemy.exc import SQLAlchemyError -from ..sql.all_keywords import ALL_KEY_WORDS - - -# Below is taken from Binder (utils/wtq/utils.py -class WTQDBEngine: - def __init__(self, fdb): - self.db = records.Database("sqlite:///{}".format(fdb)) - self.conn = self.db.get_connection() - - def execute_wtq_query(self, sql_query: str): - out = self.conn.query(sql_query) - results = out.all() - merged_results = [] - for i in range(len(results)): - merged_results.extend(results[i].values()) - return merged_results - - def delete_rows(self, row_indices: List[int]): - sql_queries = [ - "delete from w where id == {}".format(row) for row in row_indices - ] - for query in sql_queries: - self.conn.query(query) - - -def process_table_structure(_wtq_table_content: Dict, _add_all_column: bool = False): - # remove id and agg column - headers = [_.replace("\n", " ").lower() for _ in _wtq_table_content["headers"][2:]] - header_map = {} - for i in range(len(headers)): - header_map["c" + str(i + 1)] = headers[i] - header_types = _wtq_table_content["types"][2:] - - all_headers = [] - all_header_types = [] - vertical_content = [] - for column_content in _wtq_table_content["contents"][2:]: - # only take the first one - if _add_all_column: - for i in range(len(column_content)): - column_alias = column_content[i]["col"] - # do not add the numbered column - if "_number" in column_alias: - continue - vertical_content.append( - [ - str(_).replace("\n", " ").lower() - for _ in column_content[i]["data"] - ] - ) - if "_" in column_alias: - first_slash_pos = column_alias.find("_") - column_name = ( - header_map[column_alias[:first_slash_pos]] - + " " - + column_alias[first_slash_pos + 1 :].replace("_", " ") - ) - else: - column_name = header_map[column_alias] - all_headers.append(column_name) - if column_content[i]["type"] == "TEXT": - all_header_types.append("text") - else: - all_header_types.append("number") - else: - vertical_content.append( - [str(_).replace("\n", " ").lower() for _ in column_content[0]["data"]] - ) - row_content = list(map(list, zip(*vertical_content))) - - if _add_all_column: - ret_header = all_headers - ret_types = all_header_types - else: - ret_header = headers - ret_types = header_types - return { - "header": ret_header, - "rows": row_content, - "types": ret_types, - "alias": list(_wtq_table_content["is_list"].keys()), - } - - -def retrieve_wtq_query_answer(_engine, _table_content, _sql_struct: List): - # do not append id / agg - headers = _table_content["header"] - - def flatten_sql(_ex_sql_struct: List): - # [ "Keyword", "select", [] ], [ "Column", "c4", [] ] - _encode_sql = [] - _execute_sql = [] - for _ex_tuple in _ex_sql_struct: - keyword = str(_ex_tuple[1]) - # upper the keywords. - if keyword in ALL_KEY_WORDS: - keyword = str(keyword).upper() - - # extra column, which we do not need in result - if keyword == "w" or keyword == "from": - # add 'FROM w' make it executable - _encode_sql.append(keyword) - elif re.fullmatch(r"c\d+(_.+)?", keyword): - # only take the first part - index_key = int(keyword.split("_")[0][1:]) - 1 - # wrap it with `` to make it executable - _encode_sql.append("`{}`".format(headers[index_key])) - else: - _encode_sql.append(keyword) - # c4_list, replace it with the original one - if "_address" in keyword or "_list" in keyword: - keyword = re.findall(r"c\d+", keyword)[0] - - _execute_sql.append(keyword) - - return " ".join(_execute_sql), " ".join(_encode_sql) - - _exec_sql_str, _encode_sql_str = flatten_sql(_sql_struct) - try: - _sql_answers = _engine.execute_wtq_query(_exec_sql_str) - except SQLAlchemyError: - _sql_answers = [] - _norm_sql_answers = [ - str(_).replace("\n", " ") for _ in _sql_answers if _ is not None - ] - if "none" in _norm_sql_answers: - _norm_sql_answers = [] - return _encode_sql_str, _norm_sql_answers, _exec_sql_str - - -def _load_table(table_path) -> dict: - """ - attention: the table_path must be the .tsv path. - Load the WikiTableQuestion from csv file. Result in a dict format like: - {"header": [header1, header2,...], "rows": [[row11, row12, ...], [row21,...]... [...rownm]]} - """ - - def __extract_content(_line: str): - _vals = [_.replace("\n", " ").strip() for _ in _line.strip("\n").split("\t")] - return _vals - - with open(table_path, "r") as f: - lines = f.readlines() - - rows = [] - for i, line in enumerate(lines): - line = line.strip("\n") - if i == 0: - header = line.split("\t") - else: - rows.append(__extract_content(line)) - - table_item = {"header": header, "rows": rows} - - # Defense assertion - for i in range(len(rows) - 1): - if not len(rows[i]) == len(rows[i - 1]): - raise ValueError("some rows have diff cols.") - - return table_item - - -def _load_table_w_page(table_path, page_title_path=None) -> dict: - """ - attention: the table_path must be the .tsv path. - Load the WikiTableQuestion from csv file. Result in a dict format like: - {"header": [header1, header2,...], "rows": [[row11, row12, ...], [row21,...]... [...rownm]]} - """ - - table_item = _load_table(table_path) - - # Load page title - if not page_title_path: - page_title_path = table_path.replace("csv", "page").replace(".tsv", ".json") - with open(page_title_path, "r") as f: - page_title = json.load(f)["title"] - table_item["page_title"] = page_title - - return table_item diff --git a/research/utils/wikitq/wikitq.py b/research/utils/wikitq/wikitq.py deleted file mode 100644 index 0ab0b79c..00000000 --- a/research/utils/wikitq/wikitq.py +++ /dev/null @@ -1,150 +0,0 @@ -# Set up logging -import logging -import sys -from typing import Tuple, List -from pathlib import Path -import re -import sqlite3 - -logging.basicConfig( - format="%(asctime)s - %(levelname)s - %(name)s - %(message)s", - datefmt="%m/%d/%Y %H:%M:%S", - handlers=[logging.StreamHandler(sys.stdout)], - level=logging.INFO, -) -logger = logging.getLogger(__name__) - -from ..dataset import DataTrainingArguments -from ...utils.args import ModelArguments -from ...utils.bridge_content_encoder import get_database_matches -from ...constants import DOCS_TABLE_NAME, EvalField, SINGLE_TABLE_NAME -from ...prompts.few_shot.wikitq import blendsql_examples, sql_examples -from ..normalizer import prepare_df_for_neuraldb_from_table -from blendsql.db import SQLite - - -def wikitq_metric_format_func(item: dict) -> dict: - prediction = item.get(EvalField.PREDICTION, None) - if prediction is not None: - if len(prediction) < 1: - pred = "" - else: - pred = prediction[0] - else: - pred = "" - return { - "prediction": [pred], - "reference": {"answer_text": item["answer_text"], "question": item["question"]}, - } - - -def preprocess_wikitq_headers(headers: List[str]) -> List[str]: - """Preprocesses wikitq headers to make them easier to parse in text-to-SQL task. - TODO: This is causing some encoding issues - """ - # headers = [re.sub(r"(\\n)", " ", header, flags=re.UNICODE) for header in headers] - headers = [re.sub(r"(\'|\")", "", header) for header in headers] - return headers - - -def wikitq_get_input( - question: str, - title: dict, - table: str, - table_id: str, - data_training_args: DataTrainingArguments, - model_args: ModelArguments, -) -> Tuple[str, dict]: - """Prepares input for WikiTableQuestions dataset. - - Returns: - Tuple containing: - - str path to sqlite database - - dict containing arguments to be passed to guidance program - """ - # table_id in format csv/204-csv/772.csv - table_id = Path(table_id) - db_path = ( - Path(data_training_args.db_path) - / "wikitq" - / table_id.parent - / f"{table_id.stem}.db" - ) - table["header"] = preprocess_wikitq_headers(table["header"]) - if not db_path.is_file(): - # Create db - if not db_path.parent.is_dir(): - db_path.parent.mkdir(parents=True) - sqlite_conn = sqlite3.connect(db_path) - prepare_df_for_neuraldb_from_table(table, add_row_id=False).to_sql( - SINGLE_TABLE_NAME, sqlite_conn - ) - db_path = str(db_path) - db = SQLite(db_path) - serialized_db = db.to_serialized( - num_rows=data_training_args.num_serialized_rows, - table_description=title, - ) - bridge_hints = None - if data_training_args.use_bridge_encoder: - bridge_hints = [] - column_str_with_values = "{column} ( {values} )" - value_sep = " , " - for table_name in db.iter_tables(): - if re.search(r"^{}_".format(DOCS_TABLE_NAME), table_name): - continue - for column_name in db.iter_columns(table_name): - matches = get_database_matches( - question=question, - table_name=table_name, - column_name=column_name, - db_path=db_path, - ) - if matches: - bridge_hints.append( - column_str_with_values.format( - column=column_name, values=value_sep.join(matches) - ) - ) - bridge_hints = "\n".join(bridge_hints) - db.con.close() - return ( - db_path, - { - "examples": ( - blendsql_examples - if model_args.blender_model_name_or_path is not None - else sql_examples - ), - "question": question, - "serialized_db": serialized_db, - "bridge_hints": bridge_hints, - "extra_task_description": None, - }, - ) - - -def wikitq_get_target(query: str) -> str: - return query - - -def wikitq_pre_process_function( - batch: dict, data_training_args: DataTrainingArguments, model_args: ModelArguments -) -> dict: - titles = [item["page_title"] for item in batch["table"]] - db_path, input_program_args = zip( - *[ - wikitq_get_input( - question=question, - title=title, - table=table, - table_id=table_id, - data_training_args=data_training_args, - model_args=model_args, - ) - for question, title, table, table_id in zip( - batch[EvalField.QUESTION], titles, batch["table"], batch["table_id"] - ) - ] - ) - return {"input_program_args": list(input_program_args), "db_path": list(db_path)}