@@ -412,7 +412,7 @@ class NeMoSFTJsonlAdapter(IteratorNode):
412412 "conversations": [
413413 {
414414 "value": str,
415- "from": "User" | "Assistant",
415+ "from": "System" | " User" | "Assistant",
416416 "canonical_form": str,
417417 "label": str | null
418418 },
@@ -1389,6 +1389,7 @@ class NeMoMultimodalConversationJsonlAdapter(IteratorNode):
13891389 index_pack_max_open_files : int = 32
13901390 skip_missing_manifest_entries : bool = False
13911391 fault_tolerant_audio_loading : bool = True
1392+ override_system_prompt : bool = False
13921393
13931394 def __post_init__ (self ):
13941395 raw_manifest_filepath = self .manifest_filepath
@@ -1543,8 +1544,7 @@ def _build_conversation_local(self, data: dict, manifest_path: str) -> NeMoMulti
15431544 raise RuntimeError (message ) from ex
15441545 if self .context is not None and turns [0 ].role == "user" and isinstance (turns [0 ], AudioTurn ):
15451546 turns = [TextTurn (role = "user" , value = self .context )] + turns
1546- if self .system_prompt is not None and turns [0 ].role != "system" :
1547- turns = [TextTurn (role = "system" , value = self .system_prompt )] + turns
1547+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt )
15481548 return NeMoMultimodalConversation (
15491549 id = data ["id" ],
15501550 turns = turns ,
@@ -1626,8 +1626,7 @@ def _build_conversation_tarred(self, data: dict, tar_reader, tar_path: str) -> N
16261626 ]
16271627 if self .context is not None and turns [0 ].role == "user" and isinstance (turns [0 ], AudioTurn ):
16281628 turns = [TextTurn (role = "user" , value = self .context )] + turns
1629- if self .system_prompt is not None and turns [0 ].role != "system" :
1630- turns = [TextTurn (role = "system" , value = self .system_prompt )] + turns
1629+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt )
16311630 return NeMoMultimodalConversation (
16321631 id = data ["id" ],
16331632 turns = turns ,
@@ -1764,8 +1763,7 @@ def _iter_tar(self):
17641763 ]
17651764 if self .context is not None and turns [0 ].role == "user" and isinstance (turns [0 ], AudioTurn ):
17661765 turns = [TextTurn (role = "user" , value = self .context )] + turns
1767- if self .system_prompt is not None and turns [0 ].role != "system" :
1768- turns = [TextTurn (role = "system" , value = self .system_prompt )] + turns
1766+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt )
17691767 yield NeMoMultimodalConversation (
17701768 id = data ["id" ],
17711769 turns = turns ,
@@ -1818,7 +1816,7 @@ class NeMoMultimodalConversationShareGPTJsonlAdapter(IteratorNode):
18181816 "conversations": [
18191817 {
18201818 "value": str, # text message, may contain <sound> or <speech> placeholder
1821- "from": "human" | "gpt",
1819+ "from": "system" | " human" | "gpt",
18221820 },
18231821 ...
18241822 ],
@@ -1850,6 +1848,8 @@ class NeMoMultimodalConversationShareGPTJsonlAdapter(IteratorNode):
18501848 excluded_manifest_lines : Sequence [int ] | None = None
18511849 excluded_manifest_lines_sha256 : str | None = None
18521850 approved_exclusion_audit_sha256 : str | None = None
1851+ system_prompt : str | None = None
1852+ override_system_prompt : bool = False
18531853
18541854 def __post_init__ (self ):
18551855 raw_manifest_filepath = self .manifest_filepath
@@ -2182,6 +2182,7 @@ def resolve_collection_cut(turn):
21822182 conversations ,
21832183 resolve_collection_cut ,
21842184 )
2185+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt )
21852186 if used_route_indexes != set (range (len (routes ))):
21862187 raise ValueError (
21872188 f"ShareGPT route row { route_row_idx } contains unused records: "
@@ -2195,24 +2196,26 @@ def resolve_collection_cut(turn):
21952196 if self ._tar_readers :
21962197 tar_reader = self ._tar_readers [shard_idx ]
21972198 tar_path = self .tarred_audio_filepaths [shard_idx ]
2199+ turns = _ShareGPTConversationParser .create_turns (
2200+ self .audio_locator_tag ,
2201+ conversations ,
2202+ lambda t : self ._resolve_cut_from_indexed_tar (t , tar_reader , tar_path ),
2203+ )
21982204 return NeMoMultimodalConversation (
21992205 id = data .get ("id" , "missing-example-id" ),
2200- turns = _ShareGPTConversationParser .create_turns (
2201- self .audio_locator_tag ,
2202- conversations ,
2203- lambda t : self ._resolve_cut_from_indexed_tar (t , tar_reader , tar_path ),
2204- ),
2206+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
22052207 token_equivalent_duration = self .token_equivalent_duration ,
22062208 )
22072209 if manifest_path is None :
22082210 manifest_path = self ._cuts_readers [shard_idx ].path
2211+ turns = _ShareGPTConversationParser .create_turns (
2212+ self .audio_locator_tag ,
2213+ conversations ,
2214+ lambda t , _p = manifest_path : self ._resolve_cut_from_path (t , _p ),
2215+ )
22092216 return NeMoMultimodalConversation (
22102217 id = data .get ("id" , "missing-example-id" ),
2211- turns = _ShareGPTConversationParser .create_turns (
2212- self .audio_locator_tag ,
2213- conversations ,
2214- lambda t , _p = manifest_path : self ._resolve_cut_from_path (t , _p ),
2215- ),
2218+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
22162219 token_equivalent_duration = self .token_equivalent_duration ,
22172220 )
22182221 except _SHAREGPT_AUDIO_LOADING_ERRORS as e :
@@ -2441,11 +2444,12 @@ def _iter_tar(self):
24412444 elif cntr == self .slice_length :
24422445 break
24432446
2447+ turns = _ShareGPTConversationParser .create_turns (
2448+ self .audio_locator_tag , conversations , lambda t : cuts .popleft ()
2449+ )
24442450 yield NeMoMultimodalConversation (
24452451 id = data .get ("id" , "missing-example-id" ),
2446- turns = _ShareGPTConversationParser .create_turns (
2447- self .audio_locator_tag , conversations , lambda t : cuts .popleft ()
2448- ),
2452+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
24492453 token_equivalent_duration = self .token_equivalent_duration ,
24502454 )
24512455 cntr += 1
@@ -2474,13 +2478,14 @@ def _iter_jsonl(self):
24742478 for data in jsonl_iter :
24752479 try :
24762480 conversations = _ShareGPTConversationParser (self .audio_placeholders , data ).transform ()
2481+ turns = _ShareGPTConversationParser .create_turns (
2482+ self .audio_locator_tag ,
2483+ conversations ,
2484+ lambda t , _p = path : self ._resolve_cut_from_path (t , _p ),
2485+ )
24772486 yield NeMoMultimodalConversation (
24782487 id = data .get ("id" , "missing-example-id" ),
2479- turns = _ShareGPTConversationParser .create_turns (
2480- self .audio_locator_tag ,
2481- conversations ,
2482- lambda t , _p = path : self ._resolve_cut_from_path (t , _p ),
2483- ),
2488+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
24842489 token_equivalent_duration = self .token_equivalent_duration ,
24852490 )
24862491 except _SHAREGPT_AUDIO_LOADING_ERRORS as e :
@@ -2537,6 +2542,8 @@ class NeMoMultimodalConversationShareGPTWebdatasetAdapter(IteratorNode):
25372542 index_pack_max_open_files : int = 32
25382543 skip_missing_manifest_entries : bool = False
25392544 fault_tolerant_audio_loading : bool = True
2545+ system_prompt : str | None = None
2546+ override_system_prompt : bool = False
25402547
25412548 def __post_init__ (self ):
25422549 if self .wds_sample_index_version not in (1 , 2 ):
@@ -2683,13 +2690,14 @@ def _yield_from_sample(self, json_data, audio_bytes, audio_name):
26832690 conversations = _ShareGPTConversationParser (self .audio_placeholders , json_data , audio_name ).transform ()
26842691 recording = _sharegpt_recording_from_bytes (audio_bytes , recording_id = sample_id )
26852692 base_cut = recording .to_cut ()
2693+ turns = _ShareGPTConversationParser .create_turns (
2694+ self .audio_locator_tag ,
2695+ conversations ,
2696+ lambda t : base_cut .truncate (offset = t .get ("offset" , 0.0 ), duration = t .get ("duration" )),
2697+ )
26862698 return NeMoMultimodalConversation (
26872699 id = json_data .get ("id" , sample_id ),
2688- turns = _ShareGPTConversationParser .create_turns (
2689- self .audio_locator_tag ,
2690- conversations ,
2691- lambda t : base_cut .truncate (offset = t .get ("offset" , 0.0 ), duration = t .get ("duration" )),
2692- ),
2700+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
26932701 token_equivalent_duration = self .token_equivalent_duration ,
26942702 )
26952703
@@ -2760,13 +2768,14 @@ def resolve_cut(turn):
27602768 decoded_cuts [member .name ] = cut
27612769 return cut .truncate (offset = turn .get ("offset" , 0.0 ), duration = turn .get ("duration" ))
27622770
2771+ turns = _ShareGPTConversationParser .create_turns (
2772+ self .audio_locator_tag ,
2773+ conversations ,
2774+ resolve_cut ,
2775+ )
27632776 return NeMoMultimodalConversation (
27642777 id = bundle .json_data .get ("id" , bundle .sample_key ),
2765- turns = _ShareGPTConversationParser .create_turns (
2766- self .audio_locator_tag ,
2767- conversations ,
2768- resolve_cut ,
2769- ),
2778+ turns = _apply_system_prompt (turns , self .system_prompt , self .override_system_prompt ),
27702779 token_equivalent_duration = self .token_equivalent_duration ,
27712780 )
27722781
@@ -2999,6 +3008,23 @@ def _normalize_audio_placeholders(val: Union[str, list[str], None]) -> list[str]
29993008 return [val ] if isinstance (val , str ) else list (val )
30003009
30013010
3011+ def _apply_system_prompt (
3012+ turns : list [TextTurn | AudioTurn ],
3013+ system_prompt : str | None ,
3014+ override_system_prompt : bool ,
3015+ ) -> list [TextTurn | AudioTurn ]:
3016+ """Apply a configured system prompt without changing the default data-first policy."""
3017+ if system_prompt is None :
3018+ return turns
3019+
3020+ configured_turn = TextTurn (role = "system" , value = system_prompt )
3021+ if override_system_prompt :
3022+ return [configured_turn ] + [turn for turn in turns if turn .role != "system" ]
3023+ if turns and turns [0 ].role == "system" :
3024+ return turns
3025+ return [configured_turn ] + turns
3026+
3027+
30023028class _ShareGPTConversationParser :
30033029 """Normalize ShareGPT multimodal records for the conversation adapters.
30043030
@@ -3168,7 +3194,12 @@ def count_audio_placeholders(cls, text: str, placeholders: list[str]) -> int:
31683194
31693195 @staticmethod
31703196 def role (turn : dict ) -> str :
3171- return "user" if turn ["from" ].lower () in ("human" , "user" ) else "assistant"
3197+ role = turn ["from" ].lower ()
3198+ if role in ("human" , "user" ):
3199+ return "user"
3200+ if role == "system" :
3201+ return "system"
3202+ return "assistant"
31723203
31733204 @classmethod
31743205 def turn_can_consume_audio (cls , turn : dict ) -> bool :
0 commit comments