sdg-hub 0.3.1__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. sdg_hub/_version.py +2 -2
  2. sdg_hub/core/blocks/__init__.py +2 -4
  3. sdg_hub/core/blocks/base.py +61 -6
  4. sdg_hub/core/blocks/filtering/column_value_filter.py +3 -2
  5. sdg_hub/core/blocks/llm/__init__.py +2 -4
  6. sdg_hub/core/blocks/llm/llm_chat_block.py +251 -265
  7. sdg_hub/core/blocks/llm/llm_chat_with_parsing_retry_block.py +216 -98
  8. sdg_hub/core/blocks/llm/llm_parser_block.py +320 -0
  9. sdg_hub/core/blocks/llm/text_parser_block.py +53 -152
  10. sdg_hub/core/flow/base.py +7 -4
  11. sdg_hub/flows/qa_generation/document_grounded_qa/enhanced_multi_summary_qa/detailed_summary/flow.yaml +51 -11
  12. sdg_hub/flows/qa_generation/document_grounded_qa/enhanced_multi_summary_qa/doc_direct_qa/__init__.py +0 -0
  13. sdg_hub/flows/qa_generation/document_grounded_qa/enhanced_multi_summary_qa/doc_direct_qa/flow.yaml +159 -0
  14. sdg_hub/flows/qa_generation/document_grounded_qa/enhanced_multi_summary_qa/extractive_summary/flow.yaml +51 -11
  15. sdg_hub/flows/qa_generation/document_grounded_qa/enhanced_multi_summary_qa/key_facts/flow.yaml +14 -2
  16. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/instructlab/flow.yaml +146 -26
  17. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/README.md +0 -0
  18. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/__init__.py +0 -0
  19. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/atomic_facts_ja.yaml +41 -0
  20. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/detailed_summary_ja.yaml +14 -0
  21. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/extractive_summary_ja.yaml +14 -0
  22. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/flow.yaml +304 -0
  23. sdg_hub/flows/qa_generation/document_grounded_qa/multi_summary_qa/multilingual/japanese/generate_questions_responses_ja.yaml +55 -0
  24. sdg_hub/flows/text_analysis/structured_insights/flow.yaml +28 -4
  25. {sdg_hub-0.3.1.dist-info → sdg_hub-0.4.0.dist-info}/METADATA +1 -1
  26. {sdg_hub-0.3.1.dist-info → sdg_hub-0.4.0.dist-info}/RECORD +29 -25
  27. sdg_hub/core/blocks/evaluation/__init__.py +0 -9
  28. sdg_hub/core/blocks/evaluation/evaluate_faithfulness_block.py +0 -323
  29. sdg_hub/core/blocks/evaluation/evaluate_relevancy_block.py +0 -323
  30. sdg_hub/core/blocks/evaluation/verify_question_block.py +0 -329
  31. sdg_hub/core/blocks/llm/client_manager.py +0 -472
  32. sdg_hub/core/blocks/llm/config.py +0 -337
  33. {sdg_hub-0.3.1.dist-info → sdg_hub-0.4.0.dist-info}/WHEEL +0 -0
  34. {sdg_hub-0.3.1.dist-info → sdg_hub-0.4.0.dist-info}/licenses/LICENSE +0 -0
  35. {sdg_hub-0.3.1.dist-info → sdg_hub-0.4.0.dist-info}/top_level.txt +0 -0
@@ -1,472 +0,0 @@
1
- # SPDX-License-Identifier: Apache-2.0
2
- """Client manager for LLM operations supporting all providers via LiteLLM."""
3
-
4
- # Standard
5
- from typing import Any, Optional, Union
6
- import asyncio
7
-
8
- # Third Party
9
- from litellm import acompletion, completion
10
- import litellm
11
-
12
- # Local
13
- from ...utils.logger_config import setup_logger
14
- from .config import LLMConfig
15
- from .error_handler import LLMErrorHandler
16
-
17
- logger = setup_logger(__name__)
18
-
19
-
20
- class LLMClientManager:
21
- """Client manager for LLM operations using LiteLLM.
22
-
23
- This class provides a unified interface for calling any LLM provider
24
- supported by LiteLLM, with robust error handling and retry logic.
25
-
26
- Parameters
27
- ----------
28
- config : LLMConfig
29
- Configuration for the LLM client.
30
- error_handler : Optional[LLMErrorHandler], optional
31
- Custom error handler. If None, a default one will be created.
32
- """
33
-
34
- def __init__(
35
- self, config: LLMConfig, error_handler: Optional[LLMErrorHandler] = None
36
- ) -> None:
37
- self.config = config
38
- self.error_handler = error_handler or LLMErrorHandler(
39
- max_retries=config.max_retries
40
- )
41
- self._is_loaded = False
42
-
43
- def load(self) -> None:
44
- """Load and configure the LLM client.
45
-
46
- This method sets up LiteLLM configuration and validates the setup.
47
- """
48
- if self._is_loaded:
49
- return
50
-
51
- # Configure LiteLLM
52
- self._configure_litellm()
53
-
54
- # Test the configuration
55
- self._validate_setup()
56
-
57
- self._is_loaded = True
58
-
59
- # Only log when model is actually configured
60
- if self.config.model:
61
- logger.info(
62
- f"Loaded LLM client for model '{self.config.model}'",
63
- extra={
64
- "model": self.config.model,
65
- "provider": self.config.get_provider(),
66
- "is_local": self.config.is_local_model(),
67
- "api_base": self.config.api_base,
68
- },
69
- )
70
-
71
- def unload(self) -> None:
72
- """Unload the client and clean up resources."""
73
- self._is_loaded = False
74
- try:
75
- logger.info(f"Unloaded LLM client for model '{self.config.model}'")
76
- except Exception:
77
- # Ignore logging errors during cleanup to prevent issues during shutdown
78
- pass
79
-
80
- def _configure_litellm(self) -> None:
81
- """Configure LiteLLM settings."""
82
- # Set global timeout for LiteLLM
83
- litellm.request_timeout = self.config.timeout
84
-
85
- # Note: API keys are now passed directly in completion calls
86
- # instead of modifying environment variables for thread-safety
87
-
88
- def _validate_setup(self) -> None:
89
- """Validate that the LLM setup is working."""
90
- try:
91
- # For testing/development, skip validation if using dummy API key
92
- if self.config.api_key == "test-key":
93
- logger.debug(
94
- f"Skipping validation for model '{self.config.model}' (test mode)"
95
- )
96
- return
97
-
98
- # TODO: Skip validation for now to avoid API calls during initialization
99
- # we might want to make a minimal test call
100
- logger.debug(
101
- f"Setup configured for model '{self.config.model}'. "
102
- f"Validation will occur on first actual call."
103
- )
104
-
105
- except Exception as e:
106
- logger.warning(
107
- f"Could not validate setup for model '{self.config.model}': {e}"
108
- )
109
-
110
- def _message_to_dict(self, message: Any) -> dict[str, Any]:
111
- """Convert a message to a dict."""
112
- if hasattr(message, "to_dict"):
113
- return message.to_dict()
114
- elif hasattr(message, "__dict__"):
115
- return message.__dict__
116
- else:
117
- return dict(message)
118
-
119
- def create_completion(
120
- self, messages: list[dict[str, Any]], **overrides: Any
121
- ) -> Union[dict, list[dict]]:
122
- """Create a completion using LiteLLM.
123
-
124
- Parameters
125
- ----------
126
- messages : List[Dict[str, Any]]
127
- Messages in OpenAI format.
128
- **overrides : Any
129
- Runtime parameter overrides.
130
-
131
- Returns
132
- -------
133
- Union[dict, List[dict]]
134
- The completion response(s). Returns a single response when n=1 or n is None,
135
- returns a list of responses when n>1. Response dicts contain 'content' and may contain 'reasoning_content'.
136
-
137
- Raises
138
- ------
139
- Exception
140
- If the completion fails after all retries.
141
- """
142
- if not self._is_loaded:
143
- self.load()
144
-
145
- # Merge configuration with overrides
146
- final_config = self.config.merge_overrides(**overrides)
147
- kwargs = self._build_completion_kwargs(messages, final_config)
148
-
149
- # Create retry wrapper
150
- context = {
151
- "model": final_config.model,
152
- "provider": final_config.get_provider(),
153
- "message_count": len(messages),
154
- }
155
-
156
- completion_func = self.error_handler.wrap_completion(
157
- self._call_litellm_completion, context=context
158
- )
159
-
160
- # Make the completion call
161
- response = completion_func(kwargs)
162
-
163
- # Extract message objects from response
164
- # Check if n > 1 to determine return type
165
- n_value = final_config.n or 1
166
- if n_value > 1:
167
- return [
168
- self._message_to_dict(choice.message) for choice in response.choices
169
- ]
170
- else:
171
- return self._message_to_dict(response.choices[0].message)
172
-
173
- async def acreate_completion(
174
- self,
175
- messages: Union[list[dict[str, Any]], list[list[dict[str, Any]]]],
176
- max_concurrency: Optional[int] = None,
177
- **overrides: Any,
178
- ) -> Union[dict, list[dict]] | list[Union[dict, list[dict]]]:
179
- """Create async completion(s) using LiteLLM with optional concurrency control.
180
-
181
- Parameters
182
- ----------
183
- messages : Union[List[Dict[str, Any]], List[List[Dict[str, Any]]]]
184
- Single message list or list of message lists.
185
- - For single: List[Dict[str, Any]] - returns Union[Any, List[Any]]
186
- - For multiple: List[List[Dict[str, Any]]] - returns List[Union[Any, List[Any]]]
187
- max_concurrency : Optional[int], optional
188
- Maximum number of concurrent requests when processing multiple messages.
189
- If None, all requests run concurrently.
190
- **overrides : Any
191
- Runtime parameter overrides.
192
-
193
- Returns
194
- -------
195
- Union[dict, List[dict], List[Union[dict, List[dict]]]]
196
- For single message: completion response (dict when n=1, List[dict] when n>1)
197
- For multiple messages: list of completion responses (each element can be dict or List[dict])
198
-
199
- Raises
200
- ------
201
- Exception
202
- If the completion fails after all retries.
203
- """
204
- # Detect if we have single message or multiple messages
205
- if not messages:
206
- raise ValueError("messages cannot be empty")
207
-
208
- # Check if first element is a dict (single message) or list (multiple messages)
209
- if isinstance(messages[0], dict):
210
- # Single message case
211
- return await self._acreate_single(messages, **overrides)
212
- else:
213
- # Multiple messages case
214
- messages_list = messages
215
-
216
- if max_concurrency is not None:
217
- if max_concurrency < 1:
218
- raise ValueError(
219
- "max_concurrency must be greater than 0, got {max_concurrency}"
220
- )
221
- # Adjust concurrency based on n parameter to avoid overwhelming API
222
- # when n > 1 (multiple completions per request)
223
- n_value = overrides.get("n") or self.config.n or 1
224
- if n_value > 1:
225
- # Warn if max_concurrency is less than n
226
- if max_concurrency < n_value:
227
- logger.warning(
228
- f"max_concurrency ({max_concurrency}) is less than n ({n_value}). "
229
- f"This may result in very low concurrency. Consider increasing max_concurrency "
230
- f"or reducing n for better performance."
231
- )
232
-
233
- # Reduce concurrency when generating multiple completions per request
234
- adjusted_concurrency = max(1, max_concurrency // n_value)
235
- logger.debug(
236
- f"Adjusted max_concurrency from {max_concurrency} to {adjusted_concurrency} "
237
- f"for n={n_value} completions per request"
238
- )
239
- else:
240
- adjusted_concurrency = max_concurrency
241
-
242
- # Use semaphore for concurrency control
243
- semaphore = asyncio.Semaphore(adjusted_concurrency)
244
-
245
- async def _create_with_semaphore(msgs):
246
- async with semaphore:
247
- return await self._acreate_single(msgs, **overrides)
248
-
249
- tasks = [_create_with_semaphore(msgs) for msgs in messages_list]
250
- return await asyncio.gather(*tasks)
251
- else:
252
- # No concurrency limit - process all at once
253
- tasks = [
254
- self._acreate_single(msgs, **overrides) for msgs in messages_list
255
- ]
256
- return await asyncio.gather(*tasks)
257
-
258
- async def _acreate_single(
259
- self, messages: list[dict[str, Any]], **overrides: Any
260
- ) -> Union[dict, list[dict]]:
261
- """Create a single async completion using LiteLLM.
262
-
263
- Parameters
264
- ----------
265
- messages : List[Dict[str, Any]]
266
- Messages in OpenAI format.
267
- **overrides : Any
268
- Runtime parameter overrides.
269
-
270
- Returns
271
- -------
272
- Union[dict, List[dict]]
273
- List of completion message objects. Each element is a dict when n=1 or n is None,
274
- or a list of dicts when n>1. Message dicts contain 'content' and may contain 'reasoning_content'.
275
- Raises
276
- ------
277
- Exception
278
- If the completion fails after all retries.
279
- """
280
- if not self._is_loaded:
281
- self.load()
282
-
283
- # Merge configuration with overrides
284
- final_config = self.config.merge_overrides(**overrides)
285
- kwargs = self._build_completion_kwargs(messages, final_config)
286
-
287
- # Create retry wrapper for async
288
- context = {
289
- "model": final_config.model,
290
- "provider": final_config.get_provider(),
291
- "message_count": len(messages),
292
- }
293
-
294
- completion_func = self.error_handler.wrap_completion(
295
- self._call_litellm_acompletion, context=context
296
- )
297
-
298
- # Make the async completion call
299
- response = await completion_func(kwargs)
300
-
301
- # Extract message objects from response
302
- # Check if n > 1 to determine return type
303
- n_value = final_config.n or 1
304
- if n_value > 1:
305
- return [
306
- self._message_to_dict(choice.message) for choice in response.choices
307
- ]
308
- else:
309
- return self._message_to_dict(response.choices[0].message)
310
-
311
- def create_completions_batch(
312
- self, messages_list: list[list[dict[str, Any]]], **overrides: Any
313
- ) -> list[Union[dict, list[dict]]]:
314
- """Create multiple completions in batch.
315
-
316
- Parameters
317
- ----------
318
- messages_list : List[List[Dict[str, Any]]]
319
- List of message lists to process.
320
- **overrides : Any
321
- Runtime parameter overrides.
322
-
323
- Returns
324
- -------
325
- List[dict] | List[List[dict]]
326
- List of completion responses. Each element is a dict when n=1 or n is None,
327
- or a list of dicts when n>1. Response dicts contain 'content' and may contain 'reasoning_content'.
328
- """
329
- results = []
330
- for messages in messages_list:
331
- result = self.create_completion(messages, **overrides)
332
- results.append(result)
333
- return results
334
-
335
- def _build_completion_kwargs(
336
- self, messages: list[dict[str, Any]], config: LLMConfig
337
- ) -> dict[str, Any]:
338
- """Build kwargs for LiteLLM completion call.
339
-
340
- Parameters
341
- ----------
342
- messages : List[Dict[str, Any]]
343
- Messages in OpenAI format.
344
- config : LLMConfig
345
- Final configuration after merging overrides.
346
-
347
- Returns
348
- -------
349
- Dict[str, Any]
350
- Kwargs for litellm.completion().
351
- """
352
- kwargs = {
353
- "model": config.model,
354
- "messages": messages,
355
- }
356
-
357
- # Add API configuration
358
- if config.api_key:
359
- kwargs["api_key"] = config.api_key
360
-
361
- if config.api_base:
362
- kwargs["api_base"] = config.api_base
363
-
364
- # Add generation parameters
365
- generation_kwargs = config.get_generation_kwargs()
366
- kwargs.update(generation_kwargs)
367
-
368
- return kwargs
369
-
370
- def _call_litellm_completion(self, kwargs: dict[str, Any]) -> Any:
371
- """Call LiteLLM completion with error handling.
372
-
373
- Parameters
374
- ----------
375
- kwargs : Dict[str, Any]
376
- Arguments for litellm.completion().
377
-
378
- Returns
379
- -------
380
- Any
381
- LiteLLM completion response.
382
- """
383
- logger.debug(
384
- f"Calling LiteLLM completion for model '{kwargs['model']}'",
385
- extra={
386
- "model": kwargs["model"],
387
- "message_count": len(kwargs["messages"]),
388
- "generation_params": {
389
- k: v
390
- for k, v in kwargs.items()
391
- if k in ["temperature", "max_tokens", "top_p", "n"]
392
- },
393
- },
394
- )
395
-
396
- response = completion(**kwargs)
397
-
398
- logger.debug(
399
- f"LiteLLM completion successful for model '{kwargs['model']}'",
400
- extra={
401
- "model": kwargs["model"],
402
- "choices_count": len(response.choices),
403
- },
404
- )
405
-
406
- return response
407
-
408
- async def _call_litellm_acompletion(self, kwargs: dict[str, Any]) -> Any:
409
- """Call LiteLLM async completion with error handling.
410
-
411
- Parameters
412
- ----------
413
- kwargs : Dict[str, Any]
414
- Arguments for litellm.acompletion().
415
-
416
- Returns
417
- -------
418
- Any
419
- LiteLLM completion response.
420
- """
421
- logger.debug(
422
- f"Calling LiteLLM async completion for model '{kwargs['model']}'",
423
- extra={
424
- "model": kwargs["model"],
425
- "message_count": len(kwargs["messages"]),
426
- },
427
- )
428
-
429
- response = await acompletion(**kwargs)
430
-
431
- logger.debug(
432
- f"LiteLLM async completion successful for model '{kwargs['model']}'",
433
- extra={
434
- "model": kwargs["model"],
435
- "choices_count": len(response.choices),
436
- },
437
- )
438
-
439
- return response
440
-
441
- def get_model_info(self) -> dict[str, Any]:
442
- """Get information about the configured model.
443
-
444
- Returns
445
- -------
446
- Dict[str, Any]
447
- Model information.
448
- """
449
- return {
450
- "model": self.config.model,
451
- "provider": self.config.get_provider(),
452
- "model_name": self.config.get_model_name(),
453
- "is_local": self.config.is_local_model(),
454
- "api_base": self.config.api_base,
455
- "is_loaded": self._is_loaded,
456
- }
457
-
458
- def __enter__(self):
459
- """Context manager entry."""
460
- self.load()
461
- return self
462
-
463
- def __exit__(self, _exc_type, _exc_val, _exc_tb):
464
- """Context manager exit."""
465
- self.unload()
466
-
467
- def __repr__(self) -> str:
468
- """String representation."""
469
- return (
470
- f"LLMClientManager(model='{self.config.model}', "
471
- f"provider='{self.config.get_provider()}', loaded={self._is_loaded})"
472
- )