@@ -41,30 +41,57 @@ def text(self) -> str:
4141 """
4242 return self ._text
4343
44+ def _extract_next_sentence (self ) -> Optional [str ]:
45+ """Extract the next complete sentence from the buffer.
46+
47+ Returns:
48+ The first complete sentence if a sentence boundary is found,
49+ or None if the buffer is empty or contains only incomplete text.
50+ """
51+ eos_end_marker = match_endofsentence (self ._text )
52+ if eos_end_marker :
53+ # Extract the first complete sentence
54+ sentence = self ._text [:eos_end_marker ]
55+ # Remove it from buffer
56+ self ._text = self ._text [eos_end_marker :]
57+ return sentence
58+
59+ return None
60+
4461 async def aggregate (self , text : str ) -> Optional [str ]:
45- """Aggregate text and return completed sentences .
62+ """Aggregate text and return the first completed sentence .
4663
47- Adds the new text to the buffer and checks for end-of-sentence markers.
48- When a sentence boundary is found, returns the completed sentence and
49- removes it from the buffer.
64+ Adds the new text to the buffer and checks for sentence boundaries.
65+ When a sentence boundary is found, returns the first completed sentence
66+ and removes it from the buffer. Subsequent calls (even with empty strings)
67+ will return additional complete sentences if they exist in the buffer.
68+
69+ This handles varying input patterns from different LLM providers:
70+ - Word-by-word tokens (e.g., 'Hello', '!', ' I', ' am', ' Doug.')
71+ - Chunks with one or more sentences (e.g., 'Hello! I am Doug. Nice to meet you!')
5072
5173 Args:
5274 text: New text to add to the aggregation buffer.
5375
5476 Returns:
55- A complete sentence if an end-of- sentence marker is found,
77+ The first complete sentence if a sentence boundary is found,
5678 or None if more text is needed to complete a sentence.
5779 """
58- result : Optional [str ] = None
59-
6080 self ._text += text
81+ return self ._extract_next_sentence ()
6182
62- eos_end_marker = match_endofsentence (self ._text )
63- if eos_end_marker :
64- result = self ._text [:eos_end_marker ]
65- self ._text = self ._text [eos_end_marker :]
83+ async def flush_next_sentence (self ) -> Optional [str ]:
84+ """Retrieve the next complete sentence from the buffer without adding new text.
85+
86+ This method extracts the next complete sentence from the internal buffer
87+ without requiring new input text. It's useful for draining multiple
88+ complete sentences that were received in a single chunk.
6689
67- return result
90+ Returns:
91+ The next complete sentence if one exists in the buffer, or None if
92+ the buffer is empty or contains only incomplete text.
93+ """
94+ return self ._extract_next_sentence ()
6895
6996 async def handle_interruption (self ):
7097 """Handle interruptions by clearing the text buffer.
0 commit comments