mirror of
https://github.com/zenlm/zen-family.git
synced 2026-11-02 03:10:56 +00:00
155 lines
5.2 KiB
Python
155 lines
5.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Extract ALL conversations from Claude Code sessions
|
|
Properly handles the full 18B+ tokens
|
|
"""
|
|
|
|
import json
|
|
from pathlib import Path
|
|
from tqdm import tqdm
|
|
import re
|
|
|
|
def sanitize(text):
|
|
"""Remove PII"""
|
|
if not isinstance(text, str):
|
|
return str(text)
|
|
text = re.sub(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', '[EMAIL]', text)
|
|
text = re.sub(r'sk-[a-zA-Z0-9]{48}', '[API_KEY]', text)
|
|
text = re.sub(r'/Users/[^/\s]+', '/Users/[USER]', text)
|
|
text = re.sub(r'\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b', '[IP]', text)
|
|
text = re.sub(r'oauth[_-]token[_-]?secret?["\']?\s*[:=]\s*["\']?[a-f0-9:]+', 'oauth_token=[REDACTED]', text)
|
|
return text
|
|
|
|
def extract_text(content):
|
|
"""Extract text from content (string or array)"""
|
|
if isinstance(content, str):
|
|
return content
|
|
if isinstance(content, list):
|
|
texts = []
|
|
for item in content:
|
|
if isinstance(item, dict):
|
|
if item.get('type') == 'text':
|
|
texts.append(item.get('text', ''))
|
|
elif 'text' in item:
|
|
texts.append(item['text'])
|
|
elif isinstance(item, str):
|
|
texts.append(item)
|
|
return '\n'.join(filter(None, texts))
|
|
return str(content)
|
|
|
|
def process_file(path):
|
|
"""Process one JSONL file and extract ALL conversations"""
|
|
conversations = []
|
|
|
|
try:
|
|
with open(path, 'r') as f:
|
|
lines = f.readlines()
|
|
|
|
for line in lines:
|
|
if not line.strip():
|
|
continue
|
|
|
|
try:
|
|
data = json.loads(line)
|
|
|
|
# Only process user/assistant messages (not summaries, snapshots, etc)
|
|
if data.get('type') not in ['user', 'assistant']:
|
|
continue
|
|
|
|
msg = data.get('message', {})
|
|
if not msg:
|
|
continue
|
|
|
|
# Extract content
|
|
content = extract_text(msg.get('content', ''))
|
|
if not content or len(content) < 10: # Skip tiny messages
|
|
continue
|
|
|
|
# Get usage (might be in message.usage or top-level)
|
|
usage = msg.get('usage') or data.get('usage') or {}
|
|
|
|
conversations.append({
|
|
'role': data['type'],
|
|
'content': sanitize(content),
|
|
'timestamp': data.get('timestamp'),
|
|
'tokens': {
|
|
'input': usage.get('input_tokens', 0),
|
|
'output': usage.get('output_tokens', 0),
|
|
'cache_creation': usage.get('cache_creation_input_tokens', 0),
|
|
'cache_read': usage.get('cache_read_input_tokens', 0)
|
|
}
|
|
})
|
|
|
|
except (json.JSONDecodeError, KeyError, TypeError):
|
|
continue
|
|
|
|
except Exception:
|
|
pass
|
|
|
|
return conversations
|
|
|
|
def main():
|
|
print("🚀 Extracting ALL conversations from Claude Code")
|
|
print("=" * 70)
|
|
|
|
claude_dir = Path.home() / '.claude' / 'projects'
|
|
output_file = Path.home() / 'work' / 'zen' / 'zen-family' / 'training' / 'data' / 'claude_all_conversations.jsonl'
|
|
|
|
# Find all JSONL
|
|
files = list(claude_dir.rglob('*.jsonl'))
|
|
print(f"\nProcessing {len(files)} files...")
|
|
|
|
all_conversations = []
|
|
total_tokens = 0
|
|
|
|
for file_path in tqdm(files, desc="Extracting"):
|
|
convos = process_file(file_path)
|
|
all_conversations.extend(convos)
|
|
|
|
for c in convos:
|
|
t = c['tokens']
|
|
total_tokens += t['input'] + t['output'] + t['cache_creation'] + t['cache_read']
|
|
|
|
print(f"\n✅ Extracted:")
|
|
print(f" Total messages: {len(all_conversations):,}")
|
|
print(f" Total tokens: {total_tokens:,} ({total_tokens/1e9:.2f}B)")
|
|
|
|
# Create training pairs
|
|
print(f"\n📝 Creating training examples...")
|
|
examples = []
|
|
|
|
i = 0
|
|
while i < len(all_conversations) - 1:
|
|
if all_conversations[i]['role'] == 'user' and all_conversations[i+1]['role'] == 'assistant':
|
|
examples.append({
|
|
'messages': [
|
|
{'role': 'user', 'content': all_conversations[i]['content']},
|
|
{'role': 'assistant', 'content': all_conversations[i+1]['content']}
|
|
],
|
|
'metadata': {
|
|
'timestamp': all_conversations[i]['timestamp'],
|
|
'tokens': all_conversations[i+1]['tokens']
|
|
}
|
|
})
|
|
i += 2
|
|
else:
|
|
i += 1
|
|
|
|
# Save
|
|
output_file.parent.mkdir(parents=True, exist_ok=True)
|
|
with open(output_file, 'w') as f:
|
|
for ex in examples:
|
|
f.write(json.dumps(ex) + '\n')
|
|
|
|
size_mb = output_file.stat().st_size / 1024 / 1024
|
|
|
|
print(f"\n💾 Dataset saved:")
|
|
print(f" File: {output_file.name}")
|
|
print(f" Size: {size_mb:.2f} MB")
|
|
print(f" Examples: {len(examples):,}")
|
|
print(f" Tokens: {total_tokens:,} ({total_tokens/1e9:.2f}B)")
|
|
print(f"\n✅ Ready for training!")
|
|
|
|
if __name__ == '__main__':
|
|
main()
|