File size: 6,071 Bytes
b18ef79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""
generate_l3_pairs.py
────────────────────
Builds Layer 3 training pairs from existing datasets.
No external LLM required.

Design
──────
  label=0 (manipulated) : attack text used as the "response"
  label=1 (compliant)   : benign text wrapped in answer-style framing

Usage
─────
  python generate_l3_pairs.py
  python layer3_semantic.py
"""

import json
import random
from pathlib import Path

SYSTEM_PROMPT = "Answer using only the provided knowledge base."
OUTPUT_PATH   = Path("data/l3_pairs.jsonl")
RANDOM_SEED   = 42


def _load_attacks(max_n: int = 2000) -> list[str]:
    attacks = []

    # HackAPrompt
    hp_path = Path("data/hackaprompt.jsonl")
    if hp_path.exists():
        with open(hp_path, encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    text = str(row.get("text", "")).strip()
                    if len(text) >= 10:
                        attacks.append(text)
                except json.JSONDecodeError:
                    continue
                if len(attacks) >= max_n // 2:
                    break
        print(f"  [+] HackAPrompt attacks loaded: {len(attacks)}")
    else:
        print("  [warn] hackaprompt.jsonl not found")

    # InjecAgent
    ia_path = Path("data/injecagent.jsonl")
    if ia_path.exists():
        ia_count = 0
        with open(ia_path, encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    text = str(row.get("text", "")).strip()
                    if len(text) >= 10:
                        attacks.append(text)
                        ia_count += 1
                except json.JSONDecodeError:
                    continue
        print(f"  [+] InjecAgent attacks loaded:  {ia_count}")
    else:
        print("  [warn] injecagent.jsonl not found")

    return attacks


def _load_benign(max_n: int = 2000) -> list[str]:
    benign = []

    # MS MARCO
    bq_path = Path("data/benign_queries.jsonl")
    if bq_path.exists():
        with open(bq_path, encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    text = str(row.get("text", "")).strip()
                    if len(text) >= 10:
                        benign.append(text)
                except json.JSONDecodeError:
                    continue
                if len(benign) >= max_n // 2:
                    break
        print(f"  [+] MS MARCO benign loaded:     {len(benign)}")
    else:
        print("  [warn] benign_queries.jsonl not found")

    # MultiNLI
    nli_path = Path("data/multinli_sample.jsonl")
    if nli_path.exists():
        nli_count = 0
        with open(nli_path, encoding="utf-8") as f:
            for line in f:
                try:
                    row = json.loads(line)
                    text = str(row.get("text", "")).strip()
                    if len(text) >= 15:
                        benign.append(text)
                        nli_count += 1
                except json.JSONDecodeError:
                    continue
                if nli_count >= max_n // 4:
                    break
        print(f"  [+] MultiNLI benign loaded:     {nli_count}")
    else:
        print("  [warn] multinli_sample.jsonl not found")

    return benign


def _build_clean_responses(benign_texts: list[str]) -> list[str]:
    """
    Wrap benign text in answer-style framing so clean responses
    look realistic for a RAG system.
    """
    templates = [
        "Based on the knowledge base: {text}",
        "According to the provided documents: {text}",
        "The information available indicates that {text}",
        "{text}",
        "From the retrieved content: {text}",
        "The knowledge base states: {text}",
    ]
    rng = random.Random(RANDOM_SEED)
    responses = []
    for text in benign_texts:
        template = rng.choice(templates)
        responses.append(template.format(text=text[:300]))
    return responses


def main():
    print("\n=== Generating Layer 3 Training Pairs ===\n")

    rng = random.Random(RANDOM_SEED)

    print("[+] Loading attack texts ...")
    attacks = _load_attacks(max_n=2000)

    print("\n[+] Loading benign texts ...")
    benign = _load_benign(max_n=2000)

    if not attacks:
        print("\n[error] No attack texts loaded.")
        print("        Check that data/hackaprompt.jsonl exists.")
        return

    if not benign:
        print("\n[error] No benign texts loaded.")
        print("        Check that data/benign_queries.jsonl exists.")
        return

    # Shuffle with fixed seed
    rng.shuffle(attacks)
    rng.shuffle(benign)

    # Balance classes
    n = min(len(attacks), len(benign), 1500)
    attacks = attacks[:n]
    benign  = benign[:n]

    print(f"\n[+] Building {n * 2} pairs ({n} attack + {n} benign) ...")

    clean_responses = _build_clean_responses(benign)

    pairs = []

    # label=0 β€” manipulated
    for text in attacks:
        pairs.append({
            "system_prompt": SYSTEM_PROMPT,
            "response":      text[:400],
            "label":         0,
        })

    # label=1 β€” compliant
    for response in clean_responses:
        pairs.append({
            "system_prompt": SYSTEM_PROMPT,
            "response":      response[:400],
            "label":         1,
        })

    # Shuffle pairs
    rng.shuffle(pairs)

    # Save
    OUTPUT_PATH.parent.mkdir(exist_ok=True)
    with open(OUTPUT_PATH, "w", encoding="utf-8") as f:
        for pair in pairs:
            f.write(json.dumps(pair) + "\n")

    n_attack = sum(1 for p in pairs if p["label"] == 0)
    n_clean  = sum(1 for p in pairs if p["label"] == 1)

    print(f"\n[ok] {len(pairs)} pairs saved to {OUTPUT_PATH}")
    print(f"     Manipulated (label=0): {n_attack}")
    print(f"     Compliant   (label=1): {n_clean}")
    print(f"\n[next] Run: python layer3_semantic.py")


if __name__ == "__main__":
    main()