eq-layer 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- eq_layer/__init__.py +68 -0
- eq_layer/ab_eval.py +302 -0
- eq_layer/actions.py +242 -0
- eq_layer/affect.py +227 -0
- eq_layer/auditor.py +321 -0
- eq_layer/cli.py +174 -0
- eq_layer/coarse_discourse.py +107 -0
- eq_layer/data/intent_train.jsonl +299 -0
- eq_layer/dbdc3.py +197 -0
- eq_layer/dialogbank_iso.py +272 -0
- eq_layer/dialogue_reference.py +393 -0
- eq_layer/intent.py +199 -0
- eq_layer/intent_belief.py +276 -0
- eq_layer/interaction.py +314 -0
- eq_layer/policies.py +295 -0
- eq_layer/preferences.py +125 -0
- eq_layer/response_experiment.py +606 -0
- eq_layer/steer.py +281 -0
- eq_layer/tracker.py +111 -0
- eq_layer/trained_affect.py +246 -0
- eq_layer/trained_breakdown.py +143 -0
- eq_layer/trained_disagreement.py +148 -0
- eq_layer/trained_intent.py +291 -0
- eq_layer/trained_subtext.py +436 -0
- eq_layer-0.1.0.dist-info/METADATA +527 -0
- eq_layer-0.1.0.dist-info/RECORD +29 -0
- eq_layer-0.1.0.dist-info/WHEEL +4 -0
- eq_layer-0.1.0.dist-info/entry_points.txt +2 -0
- eq_layer-0.1.0.dist-info/licenses/LICENSE +21 -0
eq_layer/dbdc3.py
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import io
|
|
5
|
+
import json
|
|
6
|
+
import urllib.request
|
|
7
|
+
import zipfile
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
DBDC3_URL = "https://dbd-challenge.github.io/dbdc3/data/DBDC3.zip"
|
|
12
|
+
DBDC3_ARCHIVE_SHA256 = "736229795dc3732f8e6bb421f094dc820ef944fef9d9d320d4110ef992b60e85"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True)
|
|
16
|
+
class BreakdownTurn:
|
|
17
|
+
source_file: str
|
|
18
|
+
split: str
|
|
19
|
+
dialogue_id: str
|
|
20
|
+
turn_index: int
|
|
21
|
+
text: str
|
|
22
|
+
context: tuple[str, ...]
|
|
23
|
+
counts: dict[str, int]
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
def model_text(self) -> str:
|
|
27
|
+
context = "\n".join(self.context)
|
|
28
|
+
if context:
|
|
29
|
+
return f"{context}\n[SYSTEM]\n{self.text}"
|
|
30
|
+
return self.text
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def score(self) -> float:
|
|
34
|
+
total = sum(self.counts.values())
|
|
35
|
+
if not total:
|
|
36
|
+
return 0.0
|
|
37
|
+
weighted = (
|
|
38
|
+
self.counts.get("T", 0) * 0.5
|
|
39
|
+
+ self.counts.get("X", 0) * 1.0
|
|
40
|
+
)
|
|
41
|
+
return weighted / total
|
|
42
|
+
|
|
43
|
+
@property
|
|
44
|
+
def hard_breakdown(self) -> bool:
|
|
45
|
+
total = sum(self.counts.values())
|
|
46
|
+
if not total:
|
|
47
|
+
return False
|
|
48
|
+
return self.counts.get("X", 0) / total >= 0.5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def download_archive() -> bytes:
|
|
52
|
+
request = urllib.request.Request(
|
|
53
|
+
DBDC3_URL,
|
|
54
|
+
headers={"User-Agent": "eq-layer-dbdc3-audit/0.1"},
|
|
55
|
+
)
|
|
56
|
+
with urllib.request.urlopen(request, timeout=120) as response:
|
|
57
|
+
return response.read()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def sha256_bytes(data: bytes) -> str:
|
|
61
|
+
return hashlib.sha256(data).hexdigest()
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _iter_json_members(archive: zipfile.ZipFile) -> list[str]:
|
|
65
|
+
return sorted(
|
|
66
|
+
name
|
|
67
|
+
for name in archive.namelist()
|
|
68
|
+
if name.lower().endswith(".json")
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _english_member(name: str) -> bool:
|
|
73
|
+
low = name.lower().replace("\\", "/")
|
|
74
|
+
return "/en/" in f"/{low}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _effective_english_members(names: list[str]) -> list[str]:
|
|
78
|
+
english = [name for name in names if _english_member(name)]
|
|
79
|
+
revised = [
|
|
80
|
+
name
|
|
81
|
+
for name in english
|
|
82
|
+
if "/dbdc3_revised/en/" in name.lower().replace("\\", "/")
|
|
83
|
+
]
|
|
84
|
+
if revised:
|
|
85
|
+
return revised
|
|
86
|
+
return [
|
|
87
|
+
name
|
|
88
|
+
for name in english
|
|
89
|
+
if "/dbdc3/en/" in name.lower().replace("\\", "/")
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _split_from_member(name: str) -> str:
|
|
94
|
+
low = name.lower().replace("\\", "/")
|
|
95
|
+
if "/en/dev/" in low:
|
|
96
|
+
return "dev"
|
|
97
|
+
if "/en/eval/" in low or "/en/test/" in low:
|
|
98
|
+
return "eval"
|
|
99
|
+
return "unknown"
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _dialogue_id(payload: dict, member: str) -> str:
|
|
103
|
+
for key in ("dialogue-id", "dialogue_id", "dialog-id", "id"):
|
|
104
|
+
if payload.get(key) is not None:
|
|
105
|
+
return str(payload[key])
|
|
106
|
+
return member.rsplit("/", 1)[-1].rsplit(".", 1)[0]
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def parse_english_turns(data: bytes) -> tuple[list[BreakdownTurn], dict]:
|
|
110
|
+
turns: list[BreakdownTurn] = []
|
|
111
|
+
with zipfile.ZipFile(io.BytesIO(data)) as archive:
|
|
112
|
+
members = _iter_json_members(archive)
|
|
113
|
+
all_english = [name for name in members if _english_member(name)]
|
|
114
|
+
english = _effective_english_members(members)
|
|
115
|
+
|
|
116
|
+
for member in english:
|
|
117
|
+
with archive.open(member) as fh:
|
|
118
|
+
payload = json.load(fh)
|
|
119
|
+
|
|
120
|
+
dialogues = payload if isinstance(payload, list) else [payload]
|
|
121
|
+
for dialogue in dialogues:
|
|
122
|
+
if not isinstance(dialogue, dict):
|
|
123
|
+
continue
|
|
124
|
+
dialogue_turns = dialogue.get("turns")
|
|
125
|
+
if not isinstance(dialogue_turns, list):
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
history: list[str] = []
|
|
129
|
+
did = _dialogue_id(dialogue, member)
|
|
130
|
+
for position, turn in enumerate(dialogue_turns):
|
|
131
|
+
if not isinstance(turn, dict):
|
|
132
|
+
continue
|
|
133
|
+
text = str(turn.get("utterance") or "").strip()
|
|
134
|
+
speaker = str(turn.get("speaker") or "").upper()
|
|
135
|
+
annotations = turn.get("annotations") or []
|
|
136
|
+
|
|
137
|
+
if speaker == "S" and text:
|
|
138
|
+
counts = Counter()
|
|
139
|
+
for ann in annotations:
|
|
140
|
+
if not isinstance(ann, dict):
|
|
141
|
+
continue
|
|
142
|
+
label = str(ann.get("breakdown") or "").upper()
|
|
143
|
+
if label in {"O", "T", "X"}:
|
|
144
|
+
counts[label] += 1
|
|
145
|
+
if counts:
|
|
146
|
+
turns.append(
|
|
147
|
+
BreakdownTurn(
|
|
148
|
+
source_file=member,
|
|
149
|
+
split=_split_from_member(member),
|
|
150
|
+
dialogue_id=did,
|
|
151
|
+
turn_index=int(turn.get("turn-index", position)),
|
|
152
|
+
text=text,
|
|
153
|
+
context=tuple(history[-3:]),
|
|
154
|
+
counts=dict(counts),
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if text:
|
|
159
|
+
history.append(f"{speaker}: {text}")
|
|
160
|
+
|
|
161
|
+
meta = {
|
|
162
|
+
"zip_members": len(members),
|
|
163
|
+
"all_english_json_members": len(all_english),
|
|
164
|
+
"effective_english_json_members": len(english),
|
|
165
|
+
"effective_source": (
|
|
166
|
+
"dbdc3_revised"
|
|
167
|
+
if any("/dbdc3_revised/en/" in name.lower().replace("\\", "/") for name in english)
|
|
168
|
+
else "dbdc3"
|
|
169
|
+
),
|
|
170
|
+
"effective_dev_members": sum("/en/dev/" in name.lower().replace("\\", "/") for name in english),
|
|
171
|
+
"effective_eval_members": sum(
|
|
172
|
+
"/en/eval/" in name.lower().replace("\\", "/")
|
|
173
|
+
or "/en/test/" in name.lower().replace("\\", "/")
|
|
174
|
+
for name in english
|
|
175
|
+
),
|
|
176
|
+
}
|
|
177
|
+
return turns, meta
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def inventory(turns: list[BreakdownTurn]) -> dict:
|
|
181
|
+
aggregate = Counter()
|
|
182
|
+
hard = 0
|
|
183
|
+
for turn in turns:
|
|
184
|
+
aggregate.update(turn.counts)
|
|
185
|
+
hard += int(turn.hard_breakdown)
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"system_turns": len(turns),
|
|
189
|
+
"dialogues": len({turn.dialogue_id for turn in turns}),
|
|
190
|
+
"annotation_counts": dict(sorted(aggregate.items())),
|
|
191
|
+
"hard_breakdown_turns": hard,
|
|
192
|
+
"hard_breakdown_rate": round(hard / len(turns), 4) if turns else 0.0,
|
|
193
|
+
"mean_breakdown_score": round(
|
|
194
|
+
sum(turn.score for turn in turns) / len(turns),
|
|
195
|
+
4,
|
|
196
|
+
) if turns else 0.0,
|
|
197
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import html.parser
|
|
4
|
+
import re
|
|
5
|
+
import urllib.parse
|
|
6
|
+
import urllib.request
|
|
7
|
+
import xml.etree.ElementTree as ET
|
|
8
|
+
from collections import Counter
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
DIALOGBANK_BASE = "https://dialogbank.lsv.uni-saarland.de/"
|
|
12
|
+
DIALOGBANK_ENGLISH_PAGES = (
|
|
13
|
+
"https://dialogbank.lsv.uni-saarland.de/?page_id=90", # HCRC MapTask
|
|
14
|
+
"https://dialogbank.lsv.uni-saarland.de/?page_id=92", # Switchboard
|
|
15
|
+
"https://dialogbank.lsv.uni-saarland.de/?page_id=312", # DBOX
|
|
16
|
+
"https://dialogbank.lsv.uni-saarland.de/?page_id=280", # TRAINS
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
XML_ID = "{http://www.w3.org/XML/1998/namespace}id"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class _LinkParser(html.parser.HTMLParser):
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
super().__init__()
|
|
25
|
+
self.links: list[str] = []
|
|
26
|
+
|
|
27
|
+
def handle_starttag(self, tag: str, attrs: list[tuple[str, str | None]]) -> None:
|
|
28
|
+
if tag != "a":
|
|
29
|
+
return
|
|
30
|
+
for key, value in attrs:
|
|
31
|
+
if key == "href" and value:
|
|
32
|
+
self.links.append(value)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def fetch_text(url: str) -> str:
|
|
36
|
+
request = urllib.request.Request(
|
|
37
|
+
url,
|
|
38
|
+
headers={"User-Agent": "eq-layer-dialogbank-audit/0.1"},
|
|
39
|
+
)
|
|
40
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
41
|
+
return response.read().decode("utf-8")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def discover_diaml_urls() -> list[str]:
|
|
45
|
+
urls: set[str] = set()
|
|
46
|
+
for page in DIALOGBANK_ENGLISH_PAGES:
|
|
47
|
+
parser = _LinkParser()
|
|
48
|
+
parser.feed(fetch_text(page))
|
|
49
|
+
for href in parser.links:
|
|
50
|
+
absolute = urllib.parse.urljoin(DIALOGBANK_BASE, href)
|
|
51
|
+
if absolute.lower().endswith(".diaml"):
|
|
52
|
+
urls.add(absolute)
|
|
53
|
+
return sorted(urls)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class ISOExample:
|
|
58
|
+
dialogue: str
|
|
59
|
+
text: str
|
|
60
|
+
function: str
|
|
61
|
+
dimension: str
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _local(tag: str) -> str:
|
|
65
|
+
return tag.rsplit("}", 1)[-1]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _strip_ref(value: str | None) -> str:
|
|
69
|
+
return (value or "").lstrip("#")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _repair_xml_text(xml_text: str) -> str:
|
|
73
|
+
# Some legacy DialogBank files contain bare ampersands or control
|
|
74
|
+
# characters in transcribed speech. Repair only characters that XML 1.0
|
|
75
|
+
# cannot represent; do not alter annotation structure or labels.
|
|
76
|
+
xml_text = re.sub(
|
|
77
|
+
r"&(?!#\d+;|#x[0-9A-Fa-f]+;|[A-Za-z_][A-Za-z0-9_.:-]*;)",
|
|
78
|
+
"&",
|
|
79
|
+
xml_text,
|
|
80
|
+
)
|
|
81
|
+
return "".join(
|
|
82
|
+
ch
|
|
83
|
+
for ch in xml_text
|
|
84
|
+
if ch in "\t\n\r" or ord(ch) >= 0x20
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _parse_attrs(raw: str) -> dict[str, str]:
|
|
90
|
+
attrs: dict[str, str] = {}
|
|
91
|
+
for key, _quote, value in re.findall(
|
|
92
|
+
r"([A-Za-z_][\w:.-]*)\s*=\s*(['\"])(.*?)\2",
|
|
93
|
+
raw,
|
|
94
|
+
flags=re.DOTALL,
|
|
95
|
+
):
|
|
96
|
+
attrs[key] = html.unescape(value)
|
|
97
|
+
return attrs
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _plain_xml_text(raw: str) -> str:
|
|
101
|
+
without_tags = re.sub(r"<[^>]+>", "", raw)
|
|
102
|
+
return " ".join(html.unescape(without_tags).split())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _parse_diaml_legacy(xml_text: str, source_url: str) -> list[ISOExample]:
|
|
106
|
+
"""Extract the annotation graph without requiring globally valid XML.
|
|
107
|
+
|
|
108
|
+
A few legacy DialogBank files contain malformed transcript characters.
|
|
109
|
+
The annotation tags themselves are regular enough to recover the fields
|
|
110
|
+
EQ-Layer needs. This fallback never rewrites or fabricates labels.
|
|
111
|
+
"""
|
|
112
|
+
words: dict[str, str] = {}
|
|
113
|
+
for match in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?w\b([^>]*)>(.*?)</(?:[A-Za-z_][\w.-]*:)?w>", xml_text, flags=re.DOTALL):
|
|
114
|
+
attrs = _parse_attrs(match.group(1))
|
|
115
|
+
wid = attrs.get("xml:id")
|
|
116
|
+
if wid:
|
|
117
|
+
words[wid] = _plain_xml_text(match.group(2))
|
|
118
|
+
|
|
119
|
+
verbal_segments: dict[str, list[str]] = {}
|
|
120
|
+
for match in re.finditer(
|
|
121
|
+
r"<(?:[A-Za-z_][\w.-]*:)?spanGrp\b([^>]*)>(.*?)</(?:[A-Za-z_][\w.-]*:)?spanGrp>",
|
|
122
|
+
xml_text,
|
|
123
|
+
flags=re.DOTALL,
|
|
124
|
+
):
|
|
125
|
+
attrs = _parse_attrs(match.group(1))
|
|
126
|
+
if attrs.get("type") != "functionalVerbalSegment":
|
|
127
|
+
continue
|
|
128
|
+
sid = attrs.get("xml:id")
|
|
129
|
+
if not sid:
|
|
130
|
+
continue
|
|
131
|
+
ids: list[str] = []
|
|
132
|
+
for span in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?span\b([^>]*)/?>", match.group(2)):
|
|
133
|
+
span_attrs = _parse_attrs(span.group(1))
|
|
134
|
+
start = _strip_ref(span_attrs.get("from"))
|
|
135
|
+
end = _strip_ref(span_attrs.get("to"))
|
|
136
|
+
if start:
|
|
137
|
+
ids.append(start)
|
|
138
|
+
if end and end != start:
|
|
139
|
+
ids.append(end)
|
|
140
|
+
verbal_segments[sid] = ids
|
|
141
|
+
|
|
142
|
+
functional_to_verbal: dict[str, str] = {}
|
|
143
|
+
for match in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?fs\b([^>]*)>(.*?)</(?:[A-Za-z_][\w.-]*:)?fs>", xml_text, flags=re.DOTALL):
|
|
144
|
+
attrs = _parse_attrs(match.group(1))
|
|
145
|
+
if attrs.get("type") != "functionalSegment":
|
|
146
|
+
continue
|
|
147
|
+
fsid = attrs.get("xml:id")
|
|
148
|
+
if not fsid:
|
|
149
|
+
continue
|
|
150
|
+
for fmatch in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?f\b([^>]*)/?>", match.group(2)):
|
|
151
|
+
fattrs = _parse_attrs(fmatch.group(1))
|
|
152
|
+
if fattrs.get("name") == "verbalComponent":
|
|
153
|
+
functional_to_verbal[fsid] = _strip_ref(fattrs.get("fVal"))
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
examples: list[ISOExample] = []
|
|
157
|
+
for match in re.finditer(r"<(?:[A-Za-z_][\w.-]*:)?dialogueAct\b([^>]*)/?>", xml_text):
|
|
158
|
+
attrs = _parse_attrs(match.group(1))
|
|
159
|
+
function = attrs.get("communicativeFunction")
|
|
160
|
+
target = _strip_ref(attrs.get("target"))
|
|
161
|
+
if not function or not target:
|
|
162
|
+
continue
|
|
163
|
+
verbal = functional_to_verbal.get(target)
|
|
164
|
+
token_ids = verbal_segments.get(verbal or "", [])
|
|
165
|
+
text = " ".join(words.get(token, "") for token in token_ids).strip()
|
|
166
|
+
if not text:
|
|
167
|
+
continue
|
|
168
|
+
examples.append(
|
|
169
|
+
ISOExample(
|
|
170
|
+
dialogue=source_url,
|
|
171
|
+
text=text,
|
|
172
|
+
function=function,
|
|
173
|
+
dimension=attrs.get("dimension", ""),
|
|
174
|
+
)
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
if not examples:
|
|
178
|
+
tags = Counter(
|
|
179
|
+
re.findall(r"<\s*/?\s*([A-Za-z_][\w:.-]*)", xml_text)
|
|
180
|
+
).most_common(24)
|
|
181
|
+
raise ValueError(
|
|
182
|
+
f"Legacy DialogBank parser recovered no dialogue acts: {source_url}; "
|
|
183
|
+
f"top_tags={tags}"
|
|
184
|
+
)
|
|
185
|
+
return examples
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_diaml(xml_text: str, source_url: str) -> list[ISOExample]:
|
|
189
|
+
try:
|
|
190
|
+
root = ET.fromstring(xml_text)
|
|
191
|
+
except ET.ParseError:
|
|
192
|
+
try:
|
|
193
|
+
root = ET.fromstring(_repair_xml_text(xml_text))
|
|
194
|
+
except ET.ParseError:
|
|
195
|
+
return _parse_diaml_legacy(xml_text, source_url)
|
|
196
|
+
|
|
197
|
+
words: dict[str, str] = {}
|
|
198
|
+
for elem in root.iter():
|
|
199
|
+
if _local(elem.tag) != "w":
|
|
200
|
+
continue
|
|
201
|
+
wid = elem.attrib.get(XML_ID)
|
|
202
|
+
if wid:
|
|
203
|
+
words[wid] = "".join(elem.itertext()).strip()
|
|
204
|
+
|
|
205
|
+
verbal_segments: dict[str, list[str]] = {}
|
|
206
|
+
for elem in root.iter():
|
|
207
|
+
if _local(elem.tag) != "spanGrp" or elem.attrib.get("type") != "functionalVerbalSegment":
|
|
208
|
+
continue
|
|
209
|
+
sid = elem.attrib.get(XML_ID)
|
|
210
|
+
if not sid:
|
|
211
|
+
continue
|
|
212
|
+
ids = []
|
|
213
|
+
for span in elem:
|
|
214
|
+
if _local(span.tag) != "span":
|
|
215
|
+
continue
|
|
216
|
+
start = _strip_ref(span.attrib.get("from"))
|
|
217
|
+
end = _strip_ref(span.attrib.get("to"))
|
|
218
|
+
if start:
|
|
219
|
+
ids.append(start)
|
|
220
|
+
if end and end != start:
|
|
221
|
+
ids.append(end)
|
|
222
|
+
verbal_segments[sid] = ids
|
|
223
|
+
|
|
224
|
+
functional_to_verbal: dict[str, str] = {}
|
|
225
|
+
for elem in root.iter():
|
|
226
|
+
if _local(elem.tag) != "fs" or elem.attrib.get("type") != "functionalSegment":
|
|
227
|
+
continue
|
|
228
|
+
fsid = elem.attrib.get(XML_ID)
|
|
229
|
+
if not fsid:
|
|
230
|
+
continue
|
|
231
|
+
for child in elem:
|
|
232
|
+
if _local(child.tag) == "f" and child.attrib.get("name") == "verbalComponent":
|
|
233
|
+
functional_to_verbal[fsid] = _strip_ref(child.attrib.get("fVal"))
|
|
234
|
+
|
|
235
|
+
examples: list[ISOExample] = []
|
|
236
|
+
for elem in root.iter():
|
|
237
|
+
if _local(elem.tag) != "dialogueAct":
|
|
238
|
+
continue
|
|
239
|
+
function = elem.attrib.get("communicativeFunction")
|
|
240
|
+
target = _strip_ref(elem.attrib.get("target"))
|
|
241
|
+
if not function or not target:
|
|
242
|
+
continue
|
|
243
|
+
verbal = functional_to_verbal.get(target)
|
|
244
|
+
token_ids = verbal_segments.get(verbal or "", [])
|
|
245
|
+
text = " ".join(words.get(token, "") for token in token_ids).strip()
|
|
246
|
+
if not text:
|
|
247
|
+
continue
|
|
248
|
+
examples.append(
|
|
249
|
+
ISOExample(
|
|
250
|
+
dialogue=source_url,
|
|
251
|
+
text=text,
|
|
252
|
+
function=function,
|
|
253
|
+
dimension=elem.attrib.get("dimension", ""),
|
|
254
|
+
)
|
|
255
|
+
)
|
|
256
|
+
return examples
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def load_dialogbank_english() -> list[ISOExample]:
|
|
260
|
+
examples: list[ISOExample] = []
|
|
261
|
+
for url in discover_diaml_urls():
|
|
262
|
+
examples.extend(parse_diaml(fetch_text(url), url))
|
|
263
|
+
return examples
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def inventory(examples: list[ISOExample]) -> dict:
|
|
267
|
+
counts = Counter(example.function for example in examples)
|
|
268
|
+
return {
|
|
269
|
+
"dialogues": len({example.dialogue for example in examples}),
|
|
270
|
+
"examples": len(examples),
|
|
271
|
+
"functions": dict(sorted(counts.items())),
|
|
272
|
+
}
|