-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtranscript_processing.py
More file actions
370 lines (294 loc) · 12.7 KB
/
transcript_processing.py
File metadata and controls
370 lines (294 loc) · 12.7 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
364
365
366
367
368
369
370
# Adapted from the python-apple-fm-sdk examples.
# Original examples: Copyright (C) 2026 Apple Inc. All Rights Reserved.
# Modifications: Copyright (c) 2026 Andres D. Pena. MIT License.
"""
Transcript Processing Example
This example demonstrates how to process transcripts exported from Swift apps
using the Foundation Models SDK for Python. This is a key evaluation workflow
for analyzing session data from your Swift application.
The example shows:
- Loading transcripts exported from Swift
- Analyzing session structure and content
- Extracting metrics and statistics
- Comparing multiple transcripts
"""
import argparse
import json
from pathlib import Path
from typing import Any
from examples._support import AppleFMSetupError, require_apple_fm
def load_transcript(file_path: str) -> dict[str, Any]:
"""
Load a transcript exported from a Swift app.
Swift code to export transcript:
```swift
import FoundationModels
let transcript = session.transcript
let jsonData = try JSONEncoder().encode(transcript)
try jsonData.write(to: URL(fileURLWithPath: "transcript.json"))
```
Args:
file_path: Path to the transcript JSON file
Returns:
Transcript dictionary with structure:
{
"version": 1,
"type": "FoundationModels.Transcript",
"transcript": {
"entries": [...]
}
}
"""
with open(file_path) as f:
return json.load(f)
def extract_text_from_contents(contents: list[dict[str, Any]]) -> str:
"""
Extract text from a contents array.
Args:
contents: List of content objects
Returns:
Combined text from all text-type contents
"""
text_parts = []
for content in contents:
if content.get("type") == "text":
text_parts.append(content.get("text", ""))
elif content.get("type") == "structure":
# For structured content, convert to string representation
structure = content.get("structure", {})
text_parts.append(json.dumps(structure.get("content", {})))
return " ".join(text_parts)
def analyze_transcript(transcript: dict[str, Any]) -> dict[str, Any]:
"""
Analyze a transcript and extract key metrics.
Args:
transcript: Transcript dictionary from Swift app with structure:
{
"version": 1,
"transcript": {"entries": [...]},
"type": "FoundationModels.Transcript"
}
Returns:
Analysis results dictionary
"""
entries = transcript.get("transcript", {}).get("entries", [])
# Count entry types by role
instructions_entries = [e for e in entries if e.get("role") == "instructions"]
user_entries = [e for e in entries if e.get("role") == "user"]
response_entries = [e for e in entries if e.get("role") == "response"]
tool_entries = [e for e in entries if e.get("role") == "tool"]
# Calculate content lengths
total_user_chars = sum(
len(extract_text_from_contents(e.get("contents", []))) for e in user_entries
)
total_response_chars = sum(
len(extract_text_from_contents(e.get("contents", []))) for e in response_entries
)
# Extract tool calls from response entries
tool_calls = []
for entry in response_entries:
if "toolCalls" in entry:
tool_calls.extend(entry["toolCalls"])
# Extract available tools from instructions
available_tools = []
for entry in instructions_entries:
if "tools" in entry:
available_tools.extend(entry["tools"])
# Check for structured output (responseFormat)
has_structured_output = any("responseFormat" in e for e in user_entries)
# Check for assets (model information)
assets = []
for entry in response_entries:
if "assets" in entry:
assets.extend(entry["assets"])
analysis = {
"total_entries": len(entries),
"instructions_entries": len(instructions_entries),
"user_entries": len(user_entries),
"response_entries": len(response_entries),
"tool_entries": len(tool_entries),
"total_user_chars": total_user_chars,
"total_response_chars": total_response_chars,
"avg_user_entry_length": total_user_chars / len(user_entries) if user_entries else 0,
"avg_response_entry_length": total_response_chars / len(response_entries)
if response_entries
else 0,
"tool_calls_count": len(tool_calls),
"has_tools": len(tool_calls) > 0,
"available_tools_count": len(available_tools),
"has_structured_output": has_structured_output,
"unique_assets": len(set(assets)),
}
return analysis
def print_transcript_summary(transcript: dict[str, Any], analysis: dict[str, Any]):
"""Print a formatted summary of the transcript."""
print("=" * 60)
print("TRANSCRIPT SUMMARY")
print("=" * 60)
# Transcript metadata
version = transcript.get("version", "N/A")
transcript_type = transcript.get("type", "N/A")
print(f"\nVersion: {version}")
print(f"Type: {transcript_type}")
# Entry statistics
print("\nEntry Statistics:")
print(f" Total entries: {analysis['total_entries']}")
print(f" Instructions entries: {analysis['instructions_entries']}")
print(f" User entries: {analysis['user_entries']}")
print(f" Response entries: {analysis['response_entries']}")
print(f" Tool entries: {analysis['tool_entries']}")
# Content statistics
print("\nContent Statistics:")
print(f" Total user characters: {analysis['total_user_chars']}")
print(f" Total response characters: {analysis['total_response_chars']}")
print(f" Avg user entry length: {analysis['avg_user_entry_length']:.1f} chars")
print(f" Avg response entry length: {analysis['avg_response_entry_length']:.1f} chars")
# Tool usage
if analysis["has_tools"]:
print("\nTool Usage:")
print(f" Available tools: {analysis['available_tools_count']}")
print(f" Tool calls made: {analysis['tool_calls_count']}")
# Structured output
if analysis["has_structured_output"]:
print("\nStructured Output: Yes (JSON Schema)")
# Model assets
if analysis["unique_assets"] > 0:
print(f"\nModel Assets: {analysis['unique_assets']} unique asset(s)")
print("=" * 60)
def print_transcript_entries(transcript: dict[str, Any], max_entries: int = 5):
"""Print the first few entries from the transcript."""
entries = transcript.get("transcript", {}).get("entries", [])
print(f"\nFirst {min(max_entries, len(entries))} entries:")
print("-" * 60)
for i, entry in enumerate(entries[:max_entries], 1):
role = entry.get("role", "unknown")
entry_id = entry.get("id", "N/A")
print(f"\n[{i}] {role.upper()} (ID: {entry_id[:8]}...)")
# Show contents
if "contents" in entry:
contents = entry["contents"]
text = extract_text_from_contents(contents)
if len(text) > 100:
text = text[:100] + "..."
if text:
print(f" Content: {text}")
# Show tool calls if present
if "toolCalls" in entry:
for tool_call in entry["toolCalls"]:
tool_name = tool_call.get("name", "unknown")
print(f" [Tool Call: {tool_name}]")
# Show tool name if this is a tool response
if "toolName" in entry:
print(f" [Tool Response: {entry['toolName']}]")
# Show available tools if present
if "tools" in entry:
tool_count = len(entry["tools"])
print(f" [Available Tools: {tool_count}]")
# Show response format if present
if "responseFormat" in entry:
format_type = entry["responseFormat"].get("type", "unknown")
print(f" [Response Format: {format_type}]")
if len(entries) > max_entries:
print(f"\n... and {len(entries) - max_entries} more entries")
print("-" * 60)
def compare_transcripts(transcripts: list[dict[str, Any]]) -> dict[str, Any]:
"""
Compare multiple transcripts and generate comparison metrics.
Args:
transcripts: List of transcript dictionaries
Returns:
Comparison results
"""
analyses = [analyze_transcript(t) for t in transcripts]
comparison = {
"count": len(transcripts),
"avg_entries": sum(a["total_entries"] for a in analyses) / len(analyses),
"avg_user_entries": sum(a["user_entries"] for a in analyses) / len(analyses),
"avg_response_entries": sum(a["response_entries"] for a in analyses) / len(analyses),
"avg_user_chars": sum(a["total_user_chars"] for a in analyses) / len(analyses),
"avg_response_chars": sum(a["total_response_chars"] for a in analyses) / len(analyses),
"tool_usage_rate": sum(1 for a in analyses if a["has_tools"]) / len(analyses),
"structured_output_rate": sum(1 for a in analyses if a["has_structured_output"])
/ len(analyses),
}
return comparison
def export_analysis_to_jsonl(transcripts: list[dict[str, Any]], output_file: str):
"""
Export transcript analyses to JSONL for further processing.
Args:
transcripts: List of transcript dictionaries
output_file: Path to output JSONL file
"""
with open(output_file, "w") as f:
for i, transcript in enumerate(transcripts, 1):
analysis = analyze_transcript(transcript)
analysis["transcript_id"] = i
analysis["version"] = transcript.get("version", 1)
analysis["type"] = transcript.get("type", "unknown")
f.write(json.dumps(analysis) + "\n")
print(f"\n✓ Exported {len(transcripts)} transcript analyses to {output_file}")
def _default_transcript_path() -> Path:
return Path(__file__).resolve().parents[1] / "datasets" / "transcript_sample.json"
def main(input_path: str | None = None):
"""Main function demonstrating transcript processing."""
require_apple_fm("transcript_processing.py")
print("Example: Processing Transcripts from Swift Apps\n")
print("This demonstrates how to analyze session data exported from")
print("your Swift app using the Foundation Models Framework.\n")
transcript_file = Path(input_path) if input_path is not None else _default_transcript_path()
if not transcript_file.exists():
raise FileNotFoundError(
f"Transcript file not found: {transcript_file}\n"
"Pass --input /path/to/transcript.json or use the bundled sample under datasets/."
)
# Load and analyze transcript
print("Loading transcript...")
transcript = load_transcript(str(transcript_file))
print("Analyzing transcript...")
analysis = analyze_transcript(transcript)
# Print summary
print_transcript_summary(transcript, analysis)
# Print entries
print_transcript_entries(transcript)
# Example: Compare multiple transcripts
print("\n" + "=" * 60)
print("COMPARING MULTIPLE TRANSCRIPTS")
print("=" * 60)
# Create a few more example transcripts by varying entry count
transcripts = [transcript]
for i in range(2, 4):
t = transcript.copy()
t["transcript"] = {"entries": transcript["transcript"]["entries"][: i + 1]}
transcripts.append(t)
comparison = compare_transcripts(transcripts)
print(f"\nCompared {comparison['count']} transcripts:")
print(f" Avg entries per session: {comparison['avg_entries']:.1f}")
print(f" Avg user entries: {comparison['avg_user_entries']:.1f}")
print(f" Avg response entries: {comparison['avg_response_entries']:.1f}")
print(f" Avg user chars: {comparison['avg_user_chars']:.1f}")
print(f" Avg response chars: {comparison['avg_response_chars']:.1f}")
print(f" Tool usage rate: {comparison['tool_usage_rate']:.1%}")
print(f" Structured output rate: {comparison['structured_output_rate']:.1%}")
# Export analyses
export_analysis_to_jsonl(transcripts, "transcript_analyses.jsonl")
print("\n" + "=" * 60)
print("Next steps:")
print("1. Export transcripts from your Swift app using the code shown above")
print("2. Load them with load_transcript()")
print("3. Analyze with analyze_transcript()")
print("4. Compare multiple sessions with compare_transcripts()")
print("5. Use insights to improve your Swift app's features")
print("=" * 60)
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Analyze a Foundation Models transcript export")
parser.add_argument(
"--input",
default=None,
help="Path to transcript JSON. Defaults to datasets/transcript_sample.json.",
)
args = parser.parse_args()
try:
main(args.input)
except AppleFMSetupError as exc:
print(exc)
raise SystemExit(2) from exc