File size: 5,408 Bytes
f32c034 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 | #!/usr/bin/env python3
"""3-shot GPT-4o experiment on aggregation items only.
Tests whether few-shot prompting closes the aggregation gap vs TabuLM.
Usage:
python eval_llm_agg_fewshot.py --api-key sk-...
"""
import argparse
import csv
import json
import os
import random
import sys
import time
from typing import List, Optional
import requests
DATA_DIR = '/shared/scratch/0/tmp/v_ireddi_rakshitha_results/tabulm/data'
CODE_DIR = '/shared/scratch/0/tmp/v_ireddi_rakshitha_results/tabulm/code'
TABQA_FILE = os.path.join(DATA_DIR, 'tabqa_kin.json')
CSV_DIR = os.path.join(DATA_DIR, 'tables')
OPENAI_URL = "https://api.openai.com/v1/chat/completions"
sys.path.insert(0, CODE_DIR)
from tabular_serializer import serialize_csv, TableCell
from eval_llm_baseline import csv_to_markdown, find_gold_cell, em_match
FEW_SHOT_EXAMPLES = [
{
"table": "| Inzego | Umubare_w_Abaturage | Umubare_w_Inzu |\n|---|---|---|\n| Gasabo | 542000 | 89000 |\n| Kicukiro | 312000 | 54000 |\n| Nyarugenge | 274000 | 48000 |",
"question": "Ni iki gifite Umubare_w_Abaturage nyinshi?",
"answer": "Gasabo",
"explanation": "Gasabo has the highest Umubare_w_Abaturage (542000).",
},
{
"table": "| Igihingwa | Umusaruro_Toni | Akarere |\n|---|---|---|\n| Ibigori | 45200 | Musanze |\n| Ibishyimbo | 12300 | Musanze |\n| Ibirayi | 89000 | Musanze |",
"question": "Ni iki gifite Umusaruro_Toni nke cyane?",
"answer": "Ibishyimbo",
"explanation": "Ibishyimbo has the lowest Umusaruro_Toni (12300).",
},
{
"table": "| Umwaka | Umusaruro_Miliyari_Frw | Impinduka_% |\n|---|---|---|\n| 2020 | 1100.2 | +3.1 |\n| 2021 | 1189.5 | +8.1 |\n| 2022 | 1209.3 | +1.7 |",
"question": "Ni iki gifite Impinduka_% ntoya muri bose?",
"answer": "2022",
"explanation": "2022 has the lowest Impinduka_% (+1.7).",
},
]
def make_fewshot_prompt(table_md: str, question: str) -> str:
examples = ""
for ex in FEW_SHOT_EXAMPLES:
examples += (
f"Table:\n{ex['table']}\n\n"
f"Question: {ex['question']}\n"
f"Answer: {ex['answer']}\n\n"
)
return (
f"You are answering questions about data tables written in Kinyarwanda.\n"
f"The answer is always the NAME of an entity from the first column — NOT a number.\n"
f"Identify which entity has the highest or lowest value in the relevant column.\n\n"
f"Examples:\n\n{examples}"
f"Now answer the following:\n\n"
f"Table:\n{table_md}\n\n"
f"Question: {question}\n"
f"Answer:"
)
def call_openai(prompt: str, api_key: str, retries: int = 3) -> Optional[str]:
for attempt in range(retries):
try:
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
payload = {"model": "gpt-4o", "max_tokens": 64, "temperature": 0,
"messages": [{"role": "user", "content": prompt}]}
r = requests.post(OPENAI_URL, headers=headers, json=payload, timeout=30)
r.raise_for_status()
return r.json()["choices"][0]["message"]["content"].strip()
except Exception as e:
if attempt < retries - 1:
time.sleep(2 ** attempt)
else:
print(f" [API error] {e}")
return None
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--api-key', required=True)
parser.add_argument('--out', default=os.path.join(DATA_DIR, 'llm_agg_fewshot_results.json'))
args = parser.parse_args()
random.seed(42)
with open(TABQA_FILE) as f:
all_items = json.load(f)
random.shuffle(all_items)
dev_items = [x for x in all_items[int(0.8 * len(all_items)):]
if x.get('answer_type') == 'aggregation']
print(f'[FewShot-Agg] {len(dev_items)} aggregation dev items')
correct, total, skipped = 0, 0, 0
records = []
for i, item in enumerate(dev_items):
csv_path = os.path.join(CSV_DIR, item['table_file'])
if not os.path.exists(csv_path):
skipped += 1; continue
cells = serialize_csv(csv_path)
if not cells:
skipped += 1; continue
gold_rc = find_gold_cell(cells, item['answer'], item['question'])
if gold_rc is None:
skipped += 1; continue
table_md = csv_to_markdown(csv_path)
if not table_md:
skipped += 1; continue
prompt = make_fewshot_prompt(table_md, item['question'])
pred = call_openai(prompt, args.api_key)
if pred is None:
skipped += 1; continue
hit = int(em_match(pred, item['answer']))
correct += hit; total += 1
records.append({'question': item['question'], 'gold': item['answer'],
'pred': pred, 'hit': hit})
print(f' [{i+1}/{len(dev_items)}] gold={item["answer"]!r:20s} pred={pred!r:20s} hit={hit}')
em = correct / total if total else 0.0
print(f'\n[FewShot-Agg] EM = {em:.4f} ({correct}/{total}, {skipped} skipped)')
with open(args.out, 'w') as f:
json.dump({'em': round(em, 4), 'correct': correct, 'total': total,
'skipped': skipped, 'records': records}, f, indent=2, ensure_ascii=False)
print(f'[FewShot-Agg] Saved to {args.out}')
if __name__ == '__main__':
main()
|