File size: 5,843 Bytes
61449ba
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any, Iterator

from .common import make_user_assistant_record, render_records, write_jsonl


ROLE_ALIASES = {
    "human": "user",
    "user": "user",
    "assistant": "assistant",
    "gpt": "assistant",
    "bot": "assistant",
    "system": "system",
}


def _missing_parquet_reader_error() -> ImportError:
    return ImportError(
        "ComplexReasoningBespoke.parquet needs a parquet reader. "
        "Install one of: pyarrow, polars, or pandas with parquet support. "
        "Recommended: pip install pyarrow"
    )


def _iter_rows_pyarrow(path: Path, *, batch_size: int) -> Iterator[dict[str, Any]]:
    import pyarrow.parquet as pq

    parquet_file = pq.ParquetFile(path)

    for batch in parquet_file.iter_batches(batch_size=batch_size):
        for row in batch.to_pylist():
            yield row


def _iter_rows_polars(path: Path) -> Iterator[dict[str, Any]]:
    import polars as pl

    frame = pl.read_parquet(path)

    for row in frame.iter_rows(named=True):
        yield row


def _iter_rows_pandas(path: Path) -> Iterator[dict[str, Any]]:
    import pandas as pd

    frame = pd.read_parquet(path)

    for row in frame.to_dict(orient="records"):
        yield row


def iter_parquet_rows(
    path: str | Path,
    *,
    batch_size: int = 512,
) -> Iterator[dict[str, Any]]:
    parquet_path = Path(path)
    errors: list[Exception] = []

    for loader in (_iter_rows_pyarrow, _iter_rows_polars, _iter_rows_pandas):
        try:
            if loader is _iter_rows_pyarrow:
                yield from loader(parquet_path, batch_size=batch_size)
            else:
                yield from loader(parquet_path)

            return
        except ModuleNotFoundError as exc:
            errors.append(exc)
        except ImportError as exc:
            errors.append(exc)

    raise _missing_parquet_reader_error() from (errors[-1] if errors else None)


def _coerce_conversations(value: Any) -> list[dict[str, Any]]:
    if value is None:
        return []

    if isinstance(value, str):
        try:
            value = json.loads(value)
        except json.JSONDecodeError:
            return []

    if not isinstance(value, list):
        return []

    return [turn for turn in value if isinstance(turn, dict)]


def _role(turn: dict[str, Any]) -> str:
    raw_role = turn.get("from", turn.get("role", ""))
    return ROLE_ALIASES.get(str(raw_role).strip().lower(), str(raw_role).strip().lower())


def _content(turn: dict[str, Any]) -> str:
    return str(turn.get("value", turn.get("content", ""))).strip()


def _iter_user_assistant_pairs(
    conversations: list[dict[str, Any]],
    *,
    system_prompt: str = "",
    include_system: bool = False,
) -> Iterator[tuple[str, str]]:
    pending_user = ""

    for turn in conversations:
        role = _role(turn)
        content = _content(turn)

        if not content:
            continue

        if role == "user":
            pending_user = content
            continue

        if role == "assistant" and pending_user:
            user = pending_user

            if include_system and system_prompt:
                user = f"System:\n{system_prompt.strip()}\n\nUser:\n{user}"

            yield user, content
            pending_user = ""


def iter_records(
    path: str | Path,
    *,
    include_system: bool = False,
    include_source: bool = False,
    limit: int | None = None,
    batch_size: int = 512,
) -> Iterator[dict[str, str]]:
    emitted = 0
    parquet_path = Path(path)

    for row in iter_parquet_rows(parquet_path, batch_size=batch_size):
        system_prompt = str(row.get("system", "") or "")
        conversations = _coerce_conversations(row.get("conversations"))

        for user, assistant in _iter_user_assistant_pairs(
            conversations,
            system_prompt=system_prompt,
            include_system=include_system,
        ):
            record = make_user_assistant_record(
                user,
                assistant,
                source=str(parquet_path),
                include_source=include_source,
            )

            if record is None:
                continue

            yield record
            emitted += 1

            if limit is not None and emitted >= limit:
                return


def load_records(
    path: str | Path,
    *,
    include_system: bool = False,
    include_source: bool = False,
    limit: int | None = None,
    batch_size: int = 512,
) -> list[dict[str, str]]:
    return list(
        iter_records(
            path,
            include_system=include_system,
            include_source=include_source,
            limit=limit,
            batch_size=batch_size,
        )
    )


def main() -> None:
    parser = argparse.ArgumentParser(
        description="Normalize ComplexReasoningBespoke parquet to user/assistant rows."
    )
    parser.add_argument("input", help="Input parquet path.")
    parser.add_argument("--output", help="Optional output JSONL path.")
    parser.add_argument("--take", type=int, default=0, help="Print the first N rows.")
    parser.add_argument("--limit", type=int, help="Stop after N emitted rows.")
    parser.add_argument("--batch-size", type=int, default=512)
    parser.add_argument("--include-system", action="store_true")
    parser.add_argument("--include-source", action="store_true")
    args = parser.parse_args()

    records = load_records(
        args.input,
        include_system=args.include_system,
        include_source=args.include_source,
        limit=args.limit,
        batch_size=args.batch_size,
    )

    if args.output:
        write_jsonl(records, args.output)

    if args.take:
        print(render_records(records[: args.take]))


if __name__ == "__main__":
    main()