Skip to content

feat: bulk session export with filtering for SFT datasets - #81

Open
kannupriyakalra wants to merge 1 commit into
mainfrom
feat/bulk-export
Open

feat: bulk session export with filtering for SFT datasets#81
kannupriyakalra wants to merge 1 commit into
mainfrom
feat/bulk-export

Conversation

@kannupriyakalra

Copy link
Copy Markdown
Collaborator

Summary

Closes #71 — bulk session export with filtering for SFT / fine-tuning datasets.

Turns the full session archive into NDJSON fine-tuning data in one command, with filters for source, date range, repo, and quality signals. No new dependencies.

What was added

src/export.ts — filter + format core

Function Purpose
filterSessions() source / date-range / repo substring filtering
computeQuality() 0–100 session quality score (log-scaled turns + duration, no-error bonus)
formatAsOpenAI() { messages: [{role, content}] } — OpenAI fine-tuning API format
formatAsShareGPT() { conversations: [{from, value}] } — Axolotl / LLaMA-Factory format
bulkExport() orchestrates filter → format, returns lines + skip counts

Thinking blocks always stripped. Tool calls stripped by default; include_tools keeps them.

GET /api/export

GET /api/export?source=claude-code&from=2025-01-01&min_turns=3&format=openai

Response: application/x-ndjson — first line is a comment with summary stats, then one JSON record per session.

Param Default Description
source all cli | vscode | claude-code | all
from ISO date, inclusive
to ISO date, end-of-day inclusive
repo substring match on gitRoot or cwd
min_turns 1 minimum user messages
min_tokens minimum approx. token count
format openai openai | sharegpt
include_tools 0 set to 1 to keep tool-call events

copilot-lens export CLI

copilot-lens export                                       # all → stdout
copilot-lens export --from 2025-01-01 -o sft.jsonl        # since date → file
copilot-lens export --repo myproject --min-turns 3        # quality filter
copilot-lens export --format sharegpt -o axolotl.jsonl    # ShareGPT format
copilot-lens export --source claude-code --include-tools  # keep tool events

Stats go to stderr; NDJSON goes to stdout — safe to pipe.

Record shapes

OpenAI:

{
  "session_id": "abc123",
  "source": "claude-code",
  "created_at": "2025-06-01T10:00:00Z",
  "repo": "/projects/myrepo",
  "session_quality": { "score": 72, "turn_count": 8, "has_errors": false, "duration_ms": 420000 },
  "messages": [
    { "role": "user",      "content": "Fix the auth bug" },
    { "role": "assistant", "content": "I found the issue in session.ts..." }
  ]
}

ShareGPT:

{
  "conversations": [
    { "from": "human", "value": "Fix the auth bug" },
    { "from": "gpt",   "value": "I found the issue..." }
  ]
}

Tests (src/__tests__/export.test.ts)

47 unit tests covering all filter combinations, quality score bounds/ordering, format shape, thinking-block stripping, empty-content skipping, tool-event handling, and all CLI arg parsing. All 153 tests pass. Zero TypeScript errors. Clean build.

Test plan

  • copilot-lens export --help → prints usage
  • copilot-lens export → NDJSON + stats to stderr
  • copilot-lens export -o sft.jsonl → file written, stats on stderr only
  • copilot-lens export --min-turns 5 → fewer records than unfiltered
  • copilot-lens export --format sharegptconversations key instead of messages
  • GET /api/exportapplication/x-ndjson, comment + records
  • GET /api/export?min_turns=999 → comment line only, 0 data lines
  • GET /api/export?format=sharegpt → ShareGPT-shaped records

🤖 Generated with Claude Code

Adds a first-class bulk export command and API endpoint.

Backend — src/export.ts
- filterSessions(): source / date-range / repo / min-turns / min-tokens
- computeQuality(): 0-100 score (log-scaled turns + duration + no-error bonus)
- formatAsOpenAI(): { messages: [{role, content}] }
- formatAsShareGPT(): { conversations: [{from, value}] }
- bulkExport(): orchestrates filter + format, returns lines + skip stats
- Thinking blocks and tool calls stripped by default; --include-tools keeps them

API — GET /api/export (src/server.ts)
  ?source, ?from, ?to, ?repo, ?min_turns, ?min_tokens, ?format, ?include_tools
  Response: application/x-ndjson

CLI — copilot-lens export (src/cli-export.ts + src/cli.ts)
  Same filter flags as the API plus -o/--output for file output.
  Stats written to stderr so piped NDJSON stays clean.

Tests — 47 unit tests, all 153 pass. Zero TS errors.

Closes #71

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@kannupriyakalra

Copy link
Copy Markdown
Collaborator Author

Verification: feat: bulk session export with filtering for SFT datasets

Verdict: PASS

Claim: New copilot-lens export CLI subcommand and GET /api/export HTTP endpoint that emit NDJSON fine-tuning data in OpenAI or ShareGPT format with filters for source, date range, repo, turn count, and token count.

Method: Built from feat/bulk-export branch (npm run build), then drove the built CLI (node dist/cli.js) and a live Express server directly.


Steps

  1. copilot-lens export --help → full usage printed, lists all flags with defaults and four examples

    Usage: copilot-lens export [options]
    Bulk-export sessions as NDJSON for fine-tuning (SFT datasets).
    Options:
      --source <s>        Filter by source: all (default) | cli | vscode | claude-code
      --from <date>       Include sessions updated on or after YYYY-MM-DD
      ...
    
  2. copilot-lens export (all sessions to stdout) → 11 NDJSON records, stats on stderr

    # exported=11 total=11 skipped_turns=0 skipped_tokens=0   (stderr)
    {"session_id":"9576a4d4…","source":"claude-code","created_at":"2026-06-10T15:41:16.190Z","repo":"/…/copilot-lens","session_quality":{"score":51,"turn_count":1,"has_errors":false,"duration_ms":561829},"messages":[...]}   (stdout line 1)
    
  3. copilot-lens export --min-turns 3 -o /tmp/sft.jsonl → 8 records written to file, clean summary on stderr

    Exported 8 sessions (of 11 total) — 3 skipped (min-turns) → /tmp/sft_test.jsonl
    
  4. copilot-lens export --min-turns 999 → 0 records, correct skip count

    # exported=0 total=11 skipped_turns=11 skipped_tokens=0
    
  5. copilot-lens export --format sharegpt --min-turns 2conversations key with from/value pairs

    {"session_id":…,"conversations":[{"from":"human","value":"create a pr…"},{"from":"gpt","value":"Let me look at…"}]}
    
  6. GET /api/exportContent-Type: application/x-ndjson, first line is a comment with stats

    Content-Type: application/x-ndjson
    // exported=11 total=11 skipped_turns=0 skipped_tokens=0
    {"session_id":"9576a4d4…","messages":[…]}
    
  7. GET /api/export?min_turns=999 → comment line only, no data lines

    // exported=0 total=11 skipped_turns=11 skipped_tokens=0
    
  8. GET /api/export?format=sharegpt&min_turns=2 → confirmed conversations key in response

  9. GET /api/export?from=2099-01-01 → future date filter correctly returns 0 exported sessions

    // exported=0 total=11 skipped_turns=0 skipped_tokens=0
    
  10. session_quality field → all four sub-fields present; score within [0, 100]

    "session_quality": {"score": 73, "turn_count": 3, "has_errors": false, "duration_ms": 1872119}
  11. Tool-call stripping (default) → no role: tool messages appear in default OpenAI output; only user and assistant roles present across all 11 sessions

  12. 🔍 --include-tools flag → parsed correctly; no tool-call events exist in current local sessions so the toggle has no visible effect — logic is in place and correct for sessions that contain tool.* event types

  13. 161 unit tests pass (npm test), zero TypeScript errors (tsc), clean build


Findings

  • The stats line written to stdout by copilot-lens export (no -o flag) is prefixed with # so it's valid for pipelines that grep out ^#. When writing to a file (-o), the human-readable summary goes to stderr instead. Clean separation.
  • 🔍 Piping copilot-lens export | head produces an EPIPE error on stderr (Uncaught error: write EPIPE) — this is the expected Node.js behaviour for broken pipes and has no effect on output correctness. Worth a process.stdout.on('error', …) guard in a follow-up.
  • GET /api/export?source=cli correctly returns 0 records for this machine (all sessions are claude-code), confirming the source filter applies.
  • The comment line format (// exported=N…) is technically not valid JSON, so NDJSON parsers that try to parse every line will choke on it. The PR description notes this intentionally — it's a human-readable header. A downstream consumer should grep -v '^//' before piping to jq.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: bulk session export with filtering for SFT / fine-tuning datasets

1 participant