Skip to content

Commit fc79a00

Browse files
committed
UPDATE
1 parent 5871b37 commit fc79a00

File tree

3 files changed

+91
-17
lines changed

3 files changed

+91
-17
lines changed
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"headline": "Tech Giant Unveils New Smartphone with Innovative Features",
3+
"summary": "Today, a leading technology company announced the release of its latest smartphone model. Packed with state-of-the-art technology, including a groundbreaking camera and enhanced battery life, this new device promises to set a new benchmark in the industry. During a launch event, the CEO discussed the advanced features and their potential impact on daily life.",
4+
"publication_date": "November 15, 2023",
5+
"author": "Jamie Smith",
6+
"location": "California, USA",
7+
"key_people": [
8+
"CEO John Doe",
9+
"Product Director Emily Davis"
10+
],
11+
"organizations": [
12+
"ABC Technology Corp"
13+
],
14+
"category": "Technology",
15+
"sentiment": "Positive"
16+
}

examples/news_schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,6 @@
4444
"description": "Overall sentiment of the article (positive, negative, neutral)"
4545
}
4646
},
47-
"required": ["headline", "summary", "key_people", "organizations"],
47+
"required": ["headline", "summary", "publication_date", "author", "location", "key_people", "organizations", "category", "sentiment"],
4848
"additionalProperties": false
4949
}

src/structured_output_cookbook/cli.py

Lines changed: 74 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import sys
5+
from datetime import datetime
56
from pathlib import Path
67
from typing import Optional
78

@@ -19,6 +20,37 @@
1920
}
2021

2122

23+
def save_extraction_result(
24+
result_data: dict,
25+
template_name: str,
26+
output_path: Optional[str] = None,
27+
data_dir: str = "data"
28+
) -> str:
29+
"""Save extraction result to file and return the path."""
30+
if output_path:
31+
# Use specified path
32+
save_path = Path(output_path)
33+
else:
34+
# Generate automatic path in data directory
35+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
36+
filename = f"{template_name}_extraction_{timestamp}.json"
37+
38+
data_path = Path(data_dir)
39+
data_path.mkdir(exist_ok=True)
40+
save_path = data_path / filename
41+
42+
# Ensure parent directory exists
43+
save_path.parent.mkdir(parents=True, exist_ok=True)
44+
45+
# Save the file
46+
save_path.write_text(
47+
json.dumps(result_data, indent=2, ensure_ascii=False),
48+
encoding="utf-8"
49+
)
50+
51+
return str(save_path)
52+
53+
2254
@click.group()
2355
@click.option("--debug", is_flag=True, help="Enable debug logging")
2456
@click.pass_context
@@ -47,16 +79,20 @@ def list_templates() -> None:
4779
@click.argument("template", type=click.Choice(list(TEMPLATES.keys())))
4880
@click.option("--input-file", "-i", type=click.Path(exists=True), help="Input text file")
4981
@click.option("--text", "-t", help="Input text directly")
50-
@click.option("--output", "-o", type=click.Path(), help="Output JSON file")
82+
@click.option("--output", "-o", type=click.Path(), help="Output JSON file (default: auto-generated in data/)")
83+
@click.option("--data-dir", default="data", help="Directory for auto-generated outputs")
5184
@click.option("--pretty", is_flag=True, help="Pretty print JSON output")
85+
@click.option("--no-save", is_flag=True, help="Don't save to file, only print to stdout")
5286
@click.pass_context
5387
def extract(
5488
ctx: click.Context,
5589
template: str,
5690
input_file: Optional[str],
5791
text: Optional[str],
5892
output: Optional[str],
59-
pretty: bool
93+
data_dir: str,
94+
pretty: bool,
95+
no_save: bool
6096
) -> None:
6197
"""Extract data using a predefined template."""
6298
logger = ctx.obj["logger"]
@@ -82,20 +118,29 @@ def extract(
82118
click.echo(f"Extraction failed: {result.error}", err=True)
83119
sys.exit(1)
84120

121+
# Ensure we have data
122+
if result.data is None:
123+
click.echo("Error: Extraction succeeded but no data returned", err=True)
124+
sys.exit(1)
125+
85126
# Format output
86127
indent = 2 if pretty else None
87128
output_json = json.dumps(result.data, indent=indent, ensure_ascii=False)
88129

89-
# Write output
90-
if output:
91-
Path(output).write_text(output_json, encoding="utf-8")
92-
click.echo(f"Results saved to {output}")
93-
else:
130+
# Save to file unless --no-save is specified
131+
if not no_save:
132+
save_path = save_extraction_result(result.data, template, output, data_dir)
133+
click.echo(f"✅ Results saved to {save_path}")
134+
135+
# Always print to stdout if no output file specified or if pretty print requested
136+
if not output or pretty or no_save:
137+
click.echo("📄 Extraction Result:")
94138
click.echo(output_json)
95139

96140
# Show stats
97141
if result.tokens_used:
98-
logger.info(f"Tokens used: {result.tokens_used}")
142+
click.echo(f"📊 Tokens used: {result.tokens_used}")
143+
click.echo(f"💰 Estimated cost: ~${(result.tokens_used * 0.00001):.4f}") # Rough estimate
99144

100145

101146
@main.command()
@@ -104,8 +149,10 @@ def extract(
104149
@click.option("--prompt", help="System prompt text")
105150
@click.option("--input-file", "-i", type=click.Path(exists=True), help="Input text file")
106151
@click.option("--text", "-t", help="Input text directly")
107-
@click.option("--output", "-o", type=click.Path(), help="Output JSON file")
152+
@click.option("--output", "-o", type=click.Path(), help="Output JSON file (default: auto-generated in data/)")
153+
@click.option("--data-dir", default="data", help="Directory for auto-generated outputs")
108154
@click.option("--pretty", is_flag=True, help="Pretty print JSON output")
155+
@click.option("--no-save", is_flag=True, help="Don't save to file, only print to stdout")
109156
@click.pass_context
110157
def extract_custom(
111158
ctx: click.Context,
@@ -115,7 +162,9 @@ def extract_custom(
115162
input_file: Optional[str],
116163
text: Optional[str],
117164
output: Optional[str],
118-
pretty: bool
165+
data_dir: str,
166+
pretty: bool,
167+
no_save: bool
119168
) -> None:
120169
"""Extract data using a custom JSON schema."""
121170
logger = ctx.obj["logger"]
@@ -156,20 +205,29 @@ def extract_custom(
156205
click.echo(f"Extraction failed: {result.error}", err=True)
157206
sys.exit(1)
158207

208+
# Ensure we have data
209+
if result.data is None:
210+
click.echo("Error: Extraction succeeded but no data returned", err=True)
211+
sys.exit(1)
212+
159213
# Format output
160214
indent = 2 if pretty else None
161215
output_json = json.dumps(result.data, indent=indent, ensure_ascii=False)
162216

163-
# Write output
164-
if output:
165-
Path(output).write_text(output_json, encoding="utf-8")
166-
click.echo(f"Results saved to {output}")
167-
else:
217+
# Save to file unless --no-save is specified
218+
if not no_save:
219+
save_path = save_extraction_result(result.data, "custom", output, data_dir)
220+
click.echo(f"✅ Results saved to {save_path}")
221+
222+
# Always print to stdout if no output file specified or if pretty print requested
223+
if not output or pretty or no_save:
224+
click.echo("📄 Extraction Result:")
168225
click.echo(output_json)
169226

170227
# Show stats
171228
if result.tokens_used:
172-
logger.info(f"Tokens used: {result.tokens_used}")
229+
click.echo(f"📊 Tokens used: {result.tokens_used}")
230+
click.echo(f"💰 Estimated cost: ~${(result.tokens_used * 0.00001):.4f}") # Rough estimate
173231

174232

175233
if __name__ == "__main__":

0 commit comments

Comments
 (0)