Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a32952f
Fix screenshot asset
emmanuel-ferdman Jul 19, 2025
156b4f1
Merge pull request #413 from twitter/patch-1
jiansongcxai Feb 18, 2026
4db143c
Set max stabilization time to 60mins
jbaxter Feb 24, 2026
95a23e3
Merge pull request #414 from twitter/jbaxter/2026_02_24_update
jbaxter Feb 24, 2026
0d3a732
Generator update
jbaxter Mar 4, 2026
1c2416c
Merge pull request #417 from twitter/jbaxter/2026_03_04_generator_update
jbaxter Mar 4, 2026
7109c78
Update generator prompts to publish source upgrades.
jbaxter Mar 5, 2026
35e8e1a
Merge pull request #419 from twitter/jbaxter/2026_03_05_generator_pro…
jbaxter Mar 5, 2026
adff3e0
Doc upates to reflect quota changes
bradmiller Mar 5, 2026
7e2a2b2
Merge pull request #420 from twitter/bradm/quota_doc_updates
bradmiller Mar 5, 2026
4ac4787
Update scorer: gaussian topic scorer and gaussian core with topics sc…
jbaxter Mar 5, 2026
a83f68f
Update scorer: gaussian topic scorer and gaussian core with topics sc…
jbaxter Mar 5, 2026
324b988
Merge pull request #421 from twitter/jbaxter/2026_03_05_scorer_updates
jbaxter Mar 5, 2026
075b709
Update overview.md
jiansongcxai Mar 9, 2026
6681ed2
Update collaborative note prompt
jbaxter Mar 11, 2026
1070fa0
Merge pull request #424 from twitter/jbaxter/2026_03_10_collaborative…
jbaxter Mar 11, 2026
72dbac9
Update download-data.md
jbaxter Mar 13, 2026
261c0c2
Merge pull request #364 from emmanuel-ferdman/main
jbaxter Mar 19, 2026
553f6b3
Update live note generator to use media pipeline.
jbaxter Mar 20, 2026
cb382ef
Media pipeline
jbaxter Mar 20, 2026
39d64e3
Merge pull request #426 from twitter/jbaxter/2026_03_20_ln_media
jbaxter Mar 20, 2026
6c11789
topic updates
jbaxter Mar 20, 2026
728d1b9
Merge pull request #427 from twitter/jbaxter/2026_03_20_topics
jbaxter Mar 20, 2026
bdaeb51
Update overview.md
jiansongcxai Mar 27, 2026
5800cab
Update overview.md
jiansongcxai Mar 27, 2026
68a8729
Update overview.md
jiansongcxai Mar 27, 2026
63d52a8
Update overview.md
jiansongcxai Mar 27, 2026
59963be
Update overview.md
jiansongcxai Mar 27, 2026
b775e41
Update overview.md
jiansongcxai Mar 27, 2026
56b4eb8
Update overview.md
jiansongcxai Apr 23, 2026
a4fd7be
Update overview.md
jiansongcxai Apr 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 130 additions & 1 deletion collaborative-note-generator/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from dataclasses import dataclass
from dataclasses import asdict, dataclass
from enum import Enum
import json
import textwrap
from typing import Optional


Expand Down Expand Up @@ -34,6 +36,26 @@ def rating_status_from_string(status: Optional[str]) -> RatingStatus:
return RatingStatus(status)


def format_dataclass(obj) -> str:
data = asdict(obj)
# Render long text fields with real newlines for readability in logs
_TEXT_FIELDS = {"sources_considered", "long_live_note", "short_live_note", "story_assessment"}
for key in _TEXT_FIELDS:
if key in data and isinstance(data[key], str) and "\n" in data[key]:
data[key] = "<<MULTILINE>>\n" + data[key] + "\n<<END>>"
result = json.dumps(data, default=str, sort_keys=True, indent=2)
# Unescape the newlines inside our multiline markers
import re

result = re.sub(
r'"<<MULTILINE>>\\n(.*?)\\n<<END>>"',
lambda m: '"\n' + m.group(1).replace("\\n", "\n").replace('\\"', '"') + '\n"',
result,
flags=re.DOTALL,
)
return result


@dataclass
class ScoringResult:
intercept: float
Expand All @@ -49,6 +71,8 @@ class NoteContent:
created_at_ms: Optional[int] = None
final_status: Optional[str] = None
core_intercept: Optional[float] = None
rating_tag_summary: Optional[dict] = None # bucket → {tag: count}
rating_level_summary: Optional[dict] = None # bucket → {level: count}


@dataclass
Expand Down Expand Up @@ -91,6 +115,39 @@ class NotificationInfo:
users_whose_suggestions_were_accepted: list[int]


@dataclass
class LiveNoteTrackingStats:
generator_stats: dict[str, int]
generator_failure: Optional[str] = None
strato_failure: Optional[str] = None
tracking_start_ms: Optional[int] = None
tracking_end_ms: Optional[int] = None
intended_failure: bool = False


@dataclass
class Source:
url: Optional[str] = None
explanation: Optional[str] = None
created_at_ms: Optional[int] = None
source_type: Optional[str] = None
source_detail: Optional[str] = None


@dataclass
class GrokRejectorResult:
score: Optional[float] = None
reasoning: Optional[str] = None
error: Optional[str] = None


@dataclass
class Evaluation:
grok_rejector_results: Optional[list[GrokRejectorResult]] = None
mean_grok_rejector_score: Optional[float] = None
claim_opinion_model_score: Optional[float] = None


@dataclass
class LiveNoteVersion:
live_note_classification: str
Expand All @@ -106,6 +163,41 @@ class LiveNoteVersion:
suggestion_evaluations: Optional[dict[int, SuggestionEvaluation]] = None
notifications: Optional[NotificationInfo] = None
scoring_result: Optional[ScoringResult] = None
story_assessment: Optional[str] = None
rating_tag_summary: Optional[dict[str, int]] = None
rating_level_summary: Optional[dict[str, dict[str, int]]] = None # bucket → {HELPFUL: n, ...}
total_ratings: Optional[int] = None
parsed_sources: Optional[list[Source]] = None
num_rejector_samples: int = 0
evaluation: Optional[Evaluation] = None


@dataclass
class LiveNoteGenerationResult:
live_note_version: Optional[LiveNoteVersion]
tracking_stats: LiveNoteTrackingStats


class MediaMatchVerdict(Enum):
YES = "YES"
NO = "NO"
INCONCLUSIVE = "INCONCLUSIVE"


@dataclass
class MediaComparisonVotes:
yes_votes: int = 0
no_votes: int = 0
error_votes: int = 0


@dataclass
class UrlMediaComparisonResult:
url: str
same_media: MediaMatchVerdict
same_incident: MediaMatchVerdict
same_media_votes: Optional[MediaComparisonVotes] = None
same_incident_votes: Optional[MediaComparisonVotes] = None


@dataclass
Expand All @@ -114,3 +206,40 @@ class ContextForGeneration:
note_contents: list[NoteContent]
past_live_note_versions_with_suggestions: list[LiveNoteVersion]
live_note_version_id: Optional[int] = None
media_comparison_results: Optional[list[UrlMediaComparisonResult]] = None


# =============================================================================
# Logging utilities for formatting prompts and responses
# =============================================================================

_PROMPT_PREFIX = " » "
_PROMPT_CONTINUATION = " » "
_RESPONSE_PREFIX = " « "
_RESPONSE_CONTINUATION = " « "
_LOG_WRAP_WIDTH = 180


def _wrap_log_line(line: str, prefix: str, continuation: str) -> str:
"""Wrap a single long line, using prefix for first and continuation for rest."""
if len(prefix + line) <= _LOG_WRAP_WIDTH or not line.strip():
return prefix + line
wrapped = textwrap.wrap(line, width=_LOG_WRAP_WIDTH - len(prefix))
if not wrapped:
return prefix + line
return prefix + wrapped[0] + "".join(f"\n{continuation}{w}" for w in wrapped[1:])


def format_prompt_for_logging(prompt_text: str) -> str:
"""Format a prompt for logging with » prefix on each line."""
return "\n".join(
_wrap_log_line(line, _PROMPT_PREFIX, _PROMPT_CONTINUATION) for line in prompt_text.split("\n")
)


def format_response_for_logging(response_text: str) -> str:
"""Format a response for logging with « prefix on each line."""
return "\n".join(
_wrap_log_line(line, _RESPONSE_PREFIX, _RESPONSE_CONTINUATION)
for line in response_text.split("\n")
)
Loading