55if the LLM call fails.
66"""
77
8- import builtins
8+ import asyncio
99
1010from synthorg .core .types import NotBlankStr # noqa: TC001
1111from synthorg .memory .models import MemoryEntry # noqa: TC001
1515 DUAL_MODE_ABSTRACTIVE_SUMMARY ,
1616)
1717from synthorg .providers .enums import MessageRole
18+ from synthorg .providers .errors import ProviderError
1819from synthorg .providers .models import ChatMessage , CompletionConfig
1920from synthorg .providers .protocol import CompletionProvider # noqa: TC001
2021
@@ -41,20 +42,23 @@ class AbstractiveSummarizer:
4142
4243 Uses a ``CompletionProvider`` to generate concise summaries of
4344 conversational/narrative memory content. Falls back to truncation
44- if the LLM call fails.
45+ if the LLM call fails with a retryable error .
4546
4647 Args:
4748 provider: Completion provider for LLM calls.
4849 model: Model identifier to use for summarization.
4950 max_summary_tokens: Maximum tokens for the summary response.
5051 temperature: Sampling temperature for summarization.
52+
53+ Raises:
54+ ValueError: If ``model`` is empty or whitespace-only.
5155 """
5256
5357 def __init__ (
5458 self ,
5559 * ,
5660 provider : CompletionProvider ,
57- model : str ,
61+ model : NotBlankStr ,
5862 max_summary_tokens : int = 200 ,
5963 temperature : float = 0.3 ,
6064 ) -> None :
@@ -71,8 +75,9 @@ def __init__(
7175 async def summarize (self , content : str ) -> str :
7276 """Generate an abstractive summary of the given content.
7377
74- Falls back to truncation if the LLM call fails or returns
75- empty content.
78+ Falls back to truncation if the LLM call fails with a
79+ retryable error or returns empty content. Non-retryable
80+ provider errors (authentication, invalid model) propagate.
7681
7782 Args:
7883 content: The sparse/conversational text to summarize.
@@ -98,42 +103,63 @@ async def summarize(self, content: str) -> str:
98103 model = self ._model ,
99104 )
100105 return response .content .strip ()
101- except builtins . MemoryError , RecursionError :
106+ except MemoryError , RecursionError :
102107 raise
108+ except ProviderError as exc :
109+ if not exc .is_retryable :
110+ raise
111+ logger .warning (
112+ DUAL_MODE_ABSTRACTIVE_FALLBACK ,
113+ content_length = len (content ),
114+ error = str (exc ),
115+ error_type = type (exc ).__name__ ,
116+ )
117+ return _truncate_fallback (content )
103118 except Exception as exc :
104119 logger .warning (
105120 DUAL_MODE_ABSTRACTIVE_FALLBACK ,
106121 content_length = len (content ),
107122 error = str (exc ),
108123 error_type = type (exc ).__name__ ,
109124 )
125+ return _truncate_fallback (content )
110126
111- # Fallback: truncation
127+ # Fallback: empty/whitespace-only LLM response
112128 logger .debug (
113129 DUAL_MODE_ABSTRACTIVE_FALLBACK ,
114130 content_length = len (content ),
115- reason = "empty_or_failed " ,
131+ reason = "empty_response " ,
116132 )
117133 return _truncate_fallback (content )
118134
119135 async def summarize_batch (
120136 self ,
121137 entries : tuple [MemoryEntry , ...],
122138 ) -> tuple [tuple [NotBlankStr , str ], ...]:
123- """Summarize multiple entries.
139+ """Summarize multiple entries concurrently .
124140
125- Each entry is summarized independently. Failures for
126- individual entries fall back to truncation without aborting
127- the batch.
141+ Each entry is summarized independently via ``asyncio.TaskGroup``.
142+ Failures for individual entries fall back to truncation without
143+ aborting the batch.
128144
129145 Args:
130146 entries: Memory entries to summarize.
131147
132148 Returns:
133149 Tuple of ``(entry_id, summary)`` pairs in input order.
134150 """
135- results : list [tuple [NotBlankStr , str ]] = []
136- for entry in entries :
137- summary = await self .summarize (entry .content )
138- results .append ((entry .id , summary ))
139- return tuple (results )
151+ if not entries :
152+ return ()
153+
154+ results : dict [NotBlankStr , str ] = {}
155+ async with asyncio .TaskGroup () as tg :
156+ tasks : dict [NotBlankStr , asyncio .Task [str ]] = {}
157+ for entry in entries :
158+ tasks [entry .id ] = tg .create_task (
159+ self .summarize (entry .content ),
160+ )
161+
162+ for entry_id , task in tasks .items ():
163+ results [entry_id ] = task .result ()
164+
165+ return tuple ((entry .id , results [entry .id ]) for entry in entries )
0 commit comments