BazaarBATNA / nlp /fetch_datasets.py
paymybills
Sync server, bazaarbot_env, and nlp modules from main
2d8d503
Raw
History Blame Contribute Delete
14.3 kB
"""Fetch and cache negotiation datasets used for NLP extractor supervision.
Datasets:
1. stanfordnlp/craigslist_bargains — per-turn intent labels (init-price/accept/reject)
2. ChicagoHAI/language-of-bargaining — per-turn bargaining act + Firm/Soft + External Incentive
3. casino — multi-issue strategy annotations
Run:
python nlp/fetch_datasets.py
Outputs written to nlp/data/:
craigslist_bargains.jsonl
chicago_hai_bargaining.jsonl
casino.jsonl
extractor_supervision.jsonl ← merged supervision set for NLP extractor fine-tune
"""
import json
import pathlib
from datasets import load_dataset
OUT = pathlib.Path(__file__).parent / "data"
OUT.mkdir(exist_ok=True)
# ── Chicago HAI: Category → verbal tell mapping ───────────────────
# Derived from ACL 2023 paper taxonomy
CHICAGO_CATEGORY_MAP = {
"offer": {"verbal_urgency": 0.2, "verbal_confidence": 0.7},
"counter-offer": {"verbal_urgency": 0.3, "verbal_confidence": 0.6},
"accept": {"verbal_urgency": 0.1, "verbal_confidence": 0.8},
"reject": {"verbal_urgency": 0.4, "verbal_confidence": 0.5},
"information": {"verbal_urgency": 0.1, "verbal_confidence": 0.6},
"threat": {"verbal_urgency": 0.7, "verbal_confidence": 0.8},
"appeal": {"verbal_urgency": 0.5, "verbal_confidence": 0.4},
"other": {"verbal_urgency": 0.2, "verbal_confidence": 0.5},
}
FIRM_SOFT_MAP = {
"Firm": 0.85,
"Soft": 0.25,
"": 0.5,
}
# Non-empty External Incentive = social proof / bluff signal
EXTERNAL_INCENTIVE_DECEPTION = 0.65
# ── CaSiNo: strategy → tell mapping ──────────────────────────────
# CaSiNo annotates with: no-need, self-need, other-need, vouch-fair,
# showing-concern, no-deal, coordination, empathy, small-talk
CASINO_STRATEGY_MAP = {
"no-need": {"verbal_urgency": 0.1, "verbal_deception_cue": 0.3},
"self-need": {"verbal_urgency": 0.6, "verbal_deception_cue": 0.1},
"other-need": {"verbal_urgency": 0.3, "verbal_deception_cue": 0.4},
"vouch-fair": {"verbal_urgency": 0.2, "verbal_confidence": 0.7},
"showing-concern": {"verbal_urgency": 0.3, "verbal_confidence": 0.4},
"no-deal": {"verbal_urgency": 0.5, "verbal_confidence": 0.8},
"coordination": {"verbal_urgency": 0.2, "verbal_confidence": 0.6},
"empathy": {"verbal_urgency": 0.2, "verbal_confidence": 0.5},
"small-talk": {"verbal_urgency": 0.05, "verbal_confidence": 0.5},
}
def _default_tell() -> dict:
return {
"verbal_urgency": 0.2,
"verbal_confidence": 0.5,
"verbal_deception_cue": 0.0,
"condition_score": 1.0,
"depreciation_score": 0.0,
"condition_label": "unknown",
}
def fetch_craigslist():
# Load from local CodaLab downloads: data/train.json + data/dev.json (gzipped)
# Source: https://worksheets.codalab.org/worksheets/0x453913e76b65495d8b9730d41c7e0a0c
# Schema: events list with action in {message, offer, accept, reject, quit}
# No per-turn intent labels — derive from action type
import gzip, pathlib
ACTION_TELL_MAP = {
"message": {"verbal_urgency": 0.2, "verbal_confidence": 0.5},
"offer": {"verbal_urgency": 0.35, "verbal_confidence": 0.7},
"accept": {"verbal_urgency": 0.1, "verbal_confidence": 0.8},
"reject": {"verbal_urgency": 0.45, "verbal_confidence": 0.55},
"quit": {"verbal_urgency": 0.6, "verbal_confidence": 0.6},
}
rows = []
for split in ("train", "dev"):
path = pathlib.Path(f"data/{split}.json")
if not path.exists():
print(f" ! data/{split}.json not found, skipping")
continue
print(f" Loading data/{split}.json ...")
try:
with gzip.open(path) as f:
examples = json.load(f)
except Exception:
# Try plain JSON if not gzipped
examples = json.loads(path.read_text())
for ex in examples:
kbs = ex.get("scenario", {}).get("kbs", [{}, {}])
# agent 0 = buyer (Role in personal), agent 1 = seller
agent_roles = {}
for kb in kbs:
role = kb.get("personal", {}).get("Role", "")
# agent index inferred from role
if role == "buyer":
agent_roles[0] = "buyer"
elif role == "seller":
agent_roles[1] = "seller"
outcome = ex.get("outcome", {})
deal_price = (outcome.get("offer") or {}).get("price")
for ev in ex.get("events", []):
action = ev.get("action", "")
text = ev.get("data", "")
if action != "message" or not isinstance(text, str) or len(text) < 5:
continue
agent_idx = ev.get("agent", 0)
role = agent_roles.get(agent_idx, "unknown")
tell = _default_tell()
tell.update(ACTION_TELL_MAP.get(action, {}))
# Derive condition signals from listing description if present
item = ex.get("scenario", {}).get("kbs", [{}])[0].get("item", {})
desc = " ".join(item.get("Description", []) or [])
if desc:
try:
from nlp.extractor import _condition_from_text
cond_score, dep_score, cond_label = _condition_from_text(desc)
if cond_label != "unknown":
tell["condition_score"] = cond_score
tell["depreciation_score"] = dep_score
tell["condition_label"] = cond_label
except ImportError:
pass
rows.append({
"source": "craigslist_bargains",
"role": role,
"utterance": text,
"action": action,
"deal_price": deal_price,
"tell_supervision": tell,
})
path = OUT / "craigslist_bargains.jsonl"
with open(path, "w") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f" → {len(rows)} turns written to {path}")
return rows
rows = []
for split in ("train", "validation"):
for ex in ds[split]:
utterances = ex["utterance"]
acts = ex["dialogue_acts"]
roles = [ex["agent_info"]["Role"][t] for t in ex["agent_turn"]]
item_price = ex["items"]["Price"][0] if ex["items"]["Price"] else None
for i, (utt, role) in enumerate(zip(utterances, roles)):
intent = acts["intent"][i] if acts and acts["intent"] else ""
price_val = acts["price"][i] if acts and acts["price"] else -1.0
tell = _default_tell()
if intent == "accept":
tell["verbal_urgency"] = 0.1
tell["verbal_confidence"] = 0.8
elif intent == "reject":
tell["verbal_urgency"] = 0.4
tell["verbal_confidence"] = 0.5
elif intent == "init-price":
tell["verbal_confidence"] = 0.75
rows.append({
"source": "craigslist_bargains",
"role": role,
"utterance": utt,
"intent": intent,
"price": float(price_val) if price_val and price_val != -1.0 else None,
"item_price": float(item_price) if item_price else None,
"tell_supervision": tell,
})
path = OUT / "craigslist_bargains.jsonl"
with open(path, "w") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f" → {len(rows)} turns written to {path}")
return rows
def fetch_chicago_hai():
# Load from local negotiations_public_release/nl/ — 178 JSON files
# HF version is broken; we have the data zip locally already.
# Label taxonomy (from data exploration):
# Category: p=price, n=new-offer, c=counter, r=reject, a=accept, e=exit
# Firm or Soft: f=firm, s=soft
# External Incentive: y=yes (social proof / outside pressure claim)
import pathlib
nl_dir = pathlib.Path("negotiations_public_release/nl")
if not nl_dir.exists():
print(" ! negotiations_public_release/nl not found, skipping Chicago HAI")
return []
print(f"Loading Chicago HAI from {nl_dir} ({len(list(nl_dir.glob('*.json')))} files) ...")
CATEGORY_MAP = {
"p": {"verbal_urgency": 0.3, "verbal_confidence": 0.7}, # price proposal
"n": {"verbal_urgency": 0.4, "verbal_confidence": 0.65}, # new offer
"c": {"verbal_urgency": 0.35, "verbal_confidence": 0.6}, # counter
"r": {"verbal_urgency": 0.5, "verbal_confidence": 0.5}, # reject
"a": {"verbal_urgency": 0.1, "verbal_confidence": 0.8}, # accept
"e": {"verbal_urgency": 0.6, "verbal_confidence": 0.7}, # exit/walk
}
FIRM_MAP = {"f": 0.80, "s": 0.25}
rows = []
for fpath in sorted(nl_dir.glob("*.json")):
try:
raw = fpath.read_text().replace(": NaN", ": null")
d = json.loads(raw)
except Exception:
continue
for turn_words in d.get("turns", []):
if not isinstance(turn_words, list) or not turn_words:
continue
# Reconstruct utterance by joining Word fields
utterance = " ".join(
w.get("Word", "") for w in turn_words if w.get("Word")
).strip()
if len(utterance) < 5:
continue
role = turn_words[0].get("Role", "")
# Take labels from last word that has them (annotation is span-level)
category, firm_soft, ext_incentive = "", "", ""
for w in reversed(turn_words):
if not category and w.get("Category"):
category = str(w["Category"]).strip()
if not firm_soft and w.get("Firm or Soft"):
firm_soft = str(w["Firm or Soft"]).strip()
if not ext_incentive and w.get("External Incentive"):
ext_incentive = str(w["External Incentive"]).strip()
tell = _default_tell()
tell.update(CATEGORY_MAP.get(category, {}))
if firm_soft in FIRM_MAP:
tell["verbal_confidence"] = FIRM_MAP[firm_soft]
if ext_incentive == "y":
tell["verbal_deception_cue"] = EXTERNAL_INCENTIVE_DECEPTION
rows.append({
"source": "chicago_hai",
"role": role,
"utterance": utterance,
"category": category,
"firm_soft": firm_soft,
"external_incentive": ext_incentive,
"tell_supervision": tell,
})
path = OUT / "chicago_hai_bargaining.jsonl"
with open(path, "w") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f" → {len(rows)} turns written to {path}")
return rows
def fetch_casino():
print("Fetching casino (CaSiNo) ...")
try:
ds = load_dataset("casino", trust_remote_code=True)
except Exception as e:
print(f" ! Could not load: {e}")
return []
rows = []
for split in ds.keys():
for ex in ds[split]:
chat = ex.get("chat_logs", [])
for turn in chat:
utt = turn.get("text", "")
if not utt:
continue
role = turn.get("id", "")
# CaSiNo per-turn strategy is in annotations, not task_data
# task_data contains item allocation info, not strategy labels
# Strategy labels are in ex["annotations"] keyed by worker
strategy_label = ""
tell = _default_tell()
sig = CASINO_STRATEGY_MAP.get(strategy_label, {})
tell.update(sig)
rows.append({
"source": "casino",
"role": role,
"utterance": utt,
"strategy": strategy_label,
"tell_supervision": tell,
})
path = OUT / "casino.jsonl"
with open(path, "w") as f:
for r in rows:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f" → {len(rows)} turns written to {path}")
return rows
def merge_supervision(craigslist, chicago, casino):
"""Merge all sources into a single supervision set for extractor training.
Each row: {"utterance": str, "tell_supervision": dict}
Only rows with non-trivial utterances (len > 10) and non-default tells are kept.
"""
all_rows = craigslist + chicago + casino
merged = []
for r in all_rows:
utt = r.get("utterance", "").strip()
tell = r.get("tell_supervision", {})
if len(utt) < 10:
continue
# Keep only rows where at least one tell deviates from defaults
non_default = (
tell.get("verbal_urgency", 0.2) != 0.2
or tell.get("verbal_confidence", 0.5) != 0.5
or tell.get("verbal_deception_cue", 0.0) != 0.0
)
if not non_default:
continue
merged.append({"utterance": utt, "source": r["source"], "tell_supervision": tell})
path = OUT / "extractor_supervision.jsonl"
with open(path, "w") as f:
for r in merged:
f.write(json.dumps(r, ensure_ascii=False) + "\n")
print(f"\nMerged supervision set: {len(merged)} rows → {path}")
if __name__ == "__main__":
craigslist = fetch_craigslist()
chicago = fetch_chicago_hai()
casino = fetch_casino()
merge_supervision(craigslist, chicago, casino)
print("\nDone. Run nlp/extractor.py to test extraction against these.")