Repository navigation
Expand file tree
/
Copy pathgenerate_sdg.py
More file actions
363 lines (294 loc) · 11.2 KB
/
Copy pathgenerate_sdg.py
File metadata and controls
363 lines (294 loc) · 11.2 KB
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
#!/usr/bin/env python3
"""
SDG File Generator
==================
Generates an SDG file for StreamDataGenerator using a text description.
The SDG file is stored by default in the same path as the text description.
An example is shown in examples_nl_descriptions/onlineretail.txt
The SDG file can then be used to create the dataset generator.
To create a good SDG file, we produce here a prompt that guides the LLM to produce
a good quality SDG file. The prompt template is stored in the file prompt_template.txt
The prompt template uses placeholders {description} and {examples} to be replaced
with the actual text description and with some examples of SDG files such as the ones
stored in the folder examples (ending in .sdg).
Note that this code requires an API token for authentication, as it uses an external LLM.
API Connection details available at file modelapi.py
Usage:
python generate_sdg.py <input_description_file> [-o <output_sdg_file>]
# Or using environment variables:
# LLM_PROVIDER=openai LLM_API_KEY=sk-... python generate_sdg.py description.txt
Examples:
python generate_sdg.py examples_nl_descriptions/onlineretail.txt
python generate_sdg.py description.txt -o my_generator.sdg
"""
import os
import re
import sys
import argparse
from pathlib import Path
from modelapi import create_provider, create_provider_from_env
# Default paths relative to this script
SCRIPT_DIR = Path(__file__).parent
EXAMPLES_DIR = SCRIPT_DIR / "examples"
PROMPT_TEMPLATE_FILE = SCRIPT_DIR / "prompt_template.txt"
def load_prompt_template(template_path):
"""
Load the prompt template from file.
Args:
template_path: Optional path to template file. Uses default if not provided.
Returns:
The prompt template string with {description} and {examples} placeholders.
"""
path = template_path or PROMPT_TEMPLATE_FILE
if not path.exists():
raise FileNotFoundError(f"Prompt template not found: {path}")
with open(path, 'r', encoding='utf-8') as f:
return f.read()
def load_examples(examples_dir, max_examples = 3):
"""
Load example SDG files to include in the prompt.
Args:
examples_dir: Directory containing .sdg example files
max_examples: Maximum number of examples to include
Returns:
Formatted string containing the examples
"""
path = examples_dir or EXAMPLES_DIR
if not path.exists():
print(f"Warning: Examples directory not found: {path}")
return ""
# Get all .sdg files
sdg_files = list(path.glob("*.sdg"))
if not sdg_files:
print(f"Warning: No .sdg files found in {path}")
return ""
# Select a diverse set of examples (loan, friedman, stagger are good choices)
preferred_files = ['loandatadescriptor.sdg', 'friedmandriftdescriptor.sdg',
'staggerdatadescriptor.sdg', 'mixeddatadescriptor.sdg']
selected = []
for preferred in preferred_files:
matching = [f for f in sdg_files if f.name == preferred]
if matching and len(selected) < max_examples:
selected.append(matching[0])
# Fill remaining slots with other files
for f in sdg_files:
if f not in selected and len(selected) < max_examples:
selected.append(f)
# Format examples
examples_text = []
for sdg_file in selected:
with open(sdg_file, 'r', encoding='utf-8') as f:
content = f.read()
examples_text.append(f"### Example: {sdg_file.name}\n```\n{content}\n```")
return "\n\n".join(examples_text)
def build_prompt(description, examples, template):
"""
Build the final prompt by replacing placeholders in the template.
Args:
description: The natural language description of the desired generator
examples: Formatted example SDG files
template: The prompt template
Returns:
The complete prompt ready to send to the LLM
"""
return template.replace("{description}", description).replace("{examples}", examples)
def extract_sdg_from_response(response):
"""
Extract the SDG content from the LLM response.
The response may contain the SDG within code blocks or as plain text.
Args:
response: Raw LLM response
Returns:
Cleaned SDG content
"""
# Try to extract from code block first (```sdg or ``` or ```text)
code_block_pattern = r'```(?:sdg|text|)?\s*\n(.*?)\n```'
matches = re.findall(code_block_pattern, response, re.DOTALL)
if matches:
# Return the largest match (likely the full SDG)
return max(matches, key=len).strip()
# If no code block, look for dataset ... end_dataset pattern
dataset_pattern = r'(dataset\s+\w+.*?end_dataset)'
matches = re.findall(dataset_pattern, response, re.DOTALL)
if matches:
return matches[0].strip()
# Return original response if no patterns found
return response.strip()
def generate_sdg(description, provider, template_path = None, examples_dir = None, max_examples = 3, temperature = 0.3, max_tokens = 4096):
"""
Generate an SDG file from a natural language description.
Args:
description: Natural language description of the desired data stream
provider: LLM provider instance
template_path: Optional custom prompt template path
examples_dir: Optional custom examples directory
max_examples: Number of examples to include in prompt
temperature: LLM temperature (lower = more deterministic)
max_tokens: Maximum response tokens
Returns:
Generated SDG file content
"""
# Load template and examples
template = load_prompt_template(template_path)
examples = load_examples(examples_dir, max_examples)
# Build the prompt
prompt = build_prompt(description, examples, template)
# Call the LLM
print("Generating SDG file...")
response = provider.chat(
message=prompt,
temperature=temperature,
max_tokens=max_tokens
)
# Extract and return SDG content
return extract_sdg_from_response(response)
def generate_sdg_from_file(input_file, output_file = None, provider = None, **kwargs):
"""
Generate an SDG file from a description file.
Args:
input_file: Path to text file containing the description
output_file: Optional output path. Defaults to same directory as input with .sdg extension
provider: Optional LLM provider. Creates from env if not provided
**kwargs: Additional arguments passed to generate_sdg()
Returns:
Path to the generated SDG file
"""
input_path = Path(input_file)
if not input_path.exists():
raise FileNotFoundError(f"Input file not found: {input_path}")
# Read description
with open(input_path, 'r', encoding='utf-8') as f:
description = f.read().strip()
if not description:
raise ValueError(f"Input file is empty: {input_path}")
# Determine output path
if output_file:
output_path = Path(output_file)
else:
output_path = input_path.with_suffix('.sdg')
# Create provider if not provided
if provider is None:
provider = create_provider_from_env()
# Generate SDG
sdg_content = generate_sdg(description, provider, **kwargs)
# Write output
with open(output_path, 'w', encoding='utf-8') as f:
f.write(sdg_content)
print(f"Generated SDG file: {output_path}")
return output_path
def main():
"""Main entry point for CLI usage."""
parser = argparse.ArgumentParser(
description="Generate SDG files from natural language descriptions",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python generate_sdg.py description.txt
python generate_sdg.py description.txt -o output.sdg
python generate_sdg.py description.txt --provider openai --model gpt-4
python generate_sdg.py description.txt --provider ollama --model llama3
Environment Variables:
LLM_PROVIDER Provider type: 'openai', 'ollama', 'custom'
LLM_API_KEY API key for authentication
LLM_BASE_URL Base URL for the API
LLM_MODEL Model name/ID
LLM_PROVIDER_NAME Provider name for custom API (e.g., 'gcp')
"""
)
parser.add_argument(
"input_file",
type=Path,
help="Path to text file containing natural language description"
)
parser.add_argument(
"-o", "--output",
type=Path,
default=None,
help="Output SDG file path (default: same as input with .sdg extension)"
)
parser.add_argument(
"--provider",
type=str,
choices=['openai', 'ollama', 'custom'],
default=None,
help="LLM provider type (overrides LLM_PROVIDER env var)"
)
parser.add_argument(
"--model",
type=str,
default=None,
help="Model name/ID (overrides LLM_MODEL env var)"
)
parser.add_argument(
"--api-key",
type=str,
default=None,
help="API key (overrides LLM_API_KEY env var)"
)
parser.add_argument(
"--base-url",
type=str,
default=None,
help="API base URL (overrides LLM_BASE_URL env var)"
)
parser.add_argument(
"--temperature",
type=float,
default=0.3,
help="LLM temperature (default: 0.3)"
)
parser.add_argument(
"--max-tokens",
type=int,
default=4096,
help="Maximum response tokens (default: 4096)"
)
parser.add_argument(
"--max-examples",
type=int,
default=3,
help="Number of examples to include in prompt (default: 3)"
)
args = parser.parse_args()
# Build provider kwargs from CLI arguments
provider_kwargs = {}
if args.api_key:
provider_kwargs['api_key'] = args.api_key
if args.base_url:
provider_kwargs['base_url'] = args.base_url
if args.model:
provider_kwargs['model'] = args.model
# Create provider
if args.provider:
provider = create_provider(args.provider, **provider_kwargs)
elif provider_kwargs:
# If any provider kwargs specified but no provider type, use env
config = {
'provider_type': os.environ.get('LLM_PROVIDER', 'openai'),
**provider_kwargs
}
provider = create_provider(config.pop('provider_type'), **config)
else:
provider = create_provider_from_env()
try:
output_path = generate_sdg_from_file(
input_file=args.input_file,
output_file=args.output,
provider=provider,
temperature=args.temperature,
max_tokens=args.max_tokens,
max_examples=args.max_examples
)
print(f"Success! SDG file written to: {output_path}")
return 0
except FileNotFoundError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except ValueError as e:
print(f"Error: {e}", file=sys.stderr)
return 1
except Exception as e:
print(f"Error generating SDG: {e}", file=sys.stderr)
return 1
if __name__ == "__main__":
sys.exit(main())