ragbits-core 1.4.0.dev202509220622__py3-none-any.whl → 1.4.0.dev202511290233__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.
@@ -315,8 +315,13 @@ class LiteLLM(LLM[LiteLLMOptions], LazyLiteLLM):
315
315
  stream=True,
316
316
  stream_options={"include_usage": True},
317
317
  )
318
- if not response.completion_stream and not response.choices: # type: ignore
319
- raise LLMEmptyResponseError()
318
+
319
+ try:
320
+ if (not response.completion_stream and not response.choices) and not response.reasoning: # type: ignore
321
+ raise LLMEmptyResponseError()
322
+ except AttributeError:
323
+ # some providers might not include some parameters (i.e. Gemini -> choices)
324
+ pass
320
325
 
321
326
  async def response_to_async_generator(response: "CustomStreamWrapper") -> AsyncGenerator[dict, None]:
322
327
  nonlocal input_tokens, provider_calculated_usage
@@ -431,9 +436,12 @@ class LiteLLM(LLM[LiteLLMOptions], LazyLiteLLM):
431
436
  ) -> "ModelResponse | CustomStreamWrapper":
432
437
  entrypoint = self.router or self._create_router_from_self_and_options(options)
433
438
 
439
+ # Preprocess messages for Claude with reasoning enabled
440
+ processed_conversation = self._preprocess_messages_for_claude(conversation, options)
441
+
434
442
  # Prepare kwargs for the completion call
435
443
  completion_kwargs = {
436
- "messages": conversation,
444
+ "messages": processed_conversation,
437
445
  "model": self.model_name,
438
446
  "response_format": response_format,
439
447
  "tools": tools,
@@ -461,6 +469,101 @@ class LiteLLM(LLM[LiteLLMOptions], LazyLiteLLM):
461
469
  raise LLMResponseError() from exc
462
470
  return response
463
471
 
472
+ def _preprocess_messages_for_claude(self, conversation: ChatFormat, options: LiteLLMOptions) -> ChatFormat:
473
+ """
474
+ Preprocess messages for Claude when reasoning is enabled.
475
+
476
+ Claude + reasoning_effort + tool calls creates a conflict:
477
+ - LiteLLM validates messages against OpenAI format (rejects Claude native format)
478
+ - Claude requires thinking blocks when reasoning_effort is set (rejects OpenAI format)
479
+
480
+ Subject to removal after the following are resolved on LiteLLM's side:
481
+ Issue: https://github.com/BerriAI/litellm/issues/14194
482
+ Linked PR(s): https://github.com/BerriAI/litellm/pull/15220
483
+
484
+ Solution: Summarize tool call history and append to last user message.
485
+ This provides context to Claude without triggering validation errors.
486
+
487
+ Args:
488
+ conversation: The conversation in OpenAI format
489
+ options: LLM options including reasoning_effort
490
+
491
+ Returns:
492
+ Processed conversation with tool context included
493
+ """
494
+
495
+ def create_enhanced_user_message(
496
+ tool_summary_parts: list[str], original_user_msg: str | None
497
+ ) -> dict[str, Any]:
498
+ if tool_summary_parts and original_user_msg:
499
+ enhanced_message = original_user_msg
500
+ enhanced_message += "\n\n[Previous tool calls in this conversation:"
501
+
502
+ for summary in tool_summary_parts:
503
+ enhanced_message += f"\n- {summary}"
504
+ enhanced_message += "\nUse this information to provide your final answer.]"
505
+ return {"role": "user", "content": enhanced_message}
506
+
507
+ return {"role": "user", "content": original_user_msg}
508
+
509
+ # Only process for Claude models with reasoning enabled
510
+ is_claude = "anthropic" in self.model_name.lower() or "claude" in self.model_name.lower()
511
+ has_reasoning = options.reasoning_effort is not NOT_GIVEN and options.reasoning_effort is not None
512
+
513
+ if not (is_claude and has_reasoning):
514
+ return conversation
515
+
516
+ # Check if conversation has tool calls
517
+ has_tool_calls = any(msg.get("role") == "assistant" and msg.get("tool_calls") for msg in conversation)
518
+
519
+ if not has_tool_calls:
520
+ # No tool calls, conversation is fine as-is
521
+ return conversation
522
+
523
+ # Build tool call summary from conversation history
524
+ tool_summary_parts = []
525
+ i = 0
526
+ while i < len(conversation):
527
+ msg = conversation[i]
528
+ if msg.get("role") == "assistant" and msg.get("tool_calls"):
529
+ # Found assistant message with tool calls
530
+ for tool_call in msg["tool_calls"]:
531
+ tool_name = tool_call["function"]["name"]
532
+ tool_args = tool_call["function"]["arguments"]
533
+ tool_id = tool_call["id"]
534
+
535
+ # Find corresponding tool result
536
+ tool_result = None
537
+ for j in range(i + 1, len(conversation)):
538
+ if conversation[j].get("role") == "tool" and conversation[j].get("tool_call_id") == tool_id:
539
+ tool_result = conversation[j].get("content")
540
+ break
541
+
542
+ if tool_result:
543
+ tool_summary_parts.append(f"{tool_name}({tool_args}) returned: {tool_result}")
544
+ i += 1
545
+
546
+ # Build processed conversation
547
+ processed = []
548
+
549
+ # Keep system message if present
550
+ for msg in conversation:
551
+ if msg.get("role") == "system":
552
+ processed.append(msg)
553
+ break
554
+
555
+ # Get the original user message (first non-system)
556
+ original_user_msg = None
557
+ for msg in conversation:
558
+ if msg.get("role") == "user":
559
+ original_user_msg = msg.get("content", "")
560
+ break
561
+
562
+ # Create enhanced user message with tool context
563
+ processed.append(create_enhanced_user_message(tool_summary_parts, original_user_msg))
564
+
565
+ return processed
566
+
464
567
  def _get_response_format(
465
568
  self, output_schema: type[BaseModel] | dict | None, json_mode: bool
466
569
  ) -> type[BaseModel] | dict | None:
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from collections.abc import Iterable
3
3
  from pathlib import Path
4
- from typing import ClassVar
4
+ from typing import TYPE_CHECKING, ClassVar, TypeAlias
5
5
 
6
6
  from typing_extensions import Self
7
7
 
@@ -10,6 +10,11 @@ from ragbits.core.sources.base import Source, get_local_storage_dir
10
10
  from ragbits.core.sources.exceptions import SourceConnectionError, SourceNotFoundError
11
11
  from ragbits.core.utils.decorators import requires_dependencies
12
12
 
13
+ if TYPE_CHECKING:
14
+ from datasets import Dataset, DatasetDict, IterableDataset, IterableDatasetDict
15
+
16
+ HFDataset: TypeAlias = Dataset | DatasetDict | IterableDataset | IterableDatasetDict
17
+
13
18
 
14
19
  class HuggingFaceSource(Source):
15
20
  """
@@ -23,6 +28,7 @@ class HuggingFaceSource(Source):
23
28
 
24
29
  protocol: ClassVar[str] = "hf"
25
30
  path: str
31
+ name: str | None = None
26
32
  split: str = "train"
27
33
  row: int | None = None
28
34
 
@@ -33,6 +39,32 @@ class HuggingFaceSource(Source):
33
39
  """
34
40
  return f"hf:{self.path}/{self.split}{f'/{self.row}' if self.row is not None else ''}"
35
41
 
42
+ def _load_dataset(self, streaming: bool = False) -> "HFDataset":
43
+ """
44
+ Load the dataset from Hugging Face.
45
+
46
+ Args:
47
+ streaming: Whether to stream the dataset.
48
+
49
+ Returns:
50
+ The loaded dataset.
51
+
52
+ Raises:
53
+ SourceConnectionError: If the source connection fails.
54
+ SourceNotFoundError: If the source dataset is not found.
55
+ """
56
+ from datasets import load_dataset
57
+ from datasets.exceptions import DatasetNotFoundError
58
+
59
+ try:
60
+ if self.name is not None and str(self.name).strip():
61
+ return load_dataset(self.path, self.name, split=self.split, streaming=streaming)
62
+ return load_dataset(self.path, split=self.split, streaming=streaming)
63
+ except ConnectionError as exc:
64
+ raise SourceConnectionError() from exc
65
+ except DatasetNotFoundError as exc:
66
+ raise SourceNotFoundError(source_id=self.id) from exc
67
+
36
68
  @requires_dependencies(["datasets"], "hf")
37
69
  async def fetch(self) -> Path:
38
70
  """
@@ -45,17 +77,9 @@ class HuggingFaceSource(Source):
45
77
  SourceConnectionError: If the source connection fails.
46
78
  SourceNotFoundError: If the source document is not found.
47
79
  """
48
- from datasets import load_dataset
49
- from datasets.exceptions import DatasetNotFoundError
50
-
51
80
  with trace(path=self.path, split=self.split, row=self.row) as outputs:
52
81
  if self.row is not None:
53
- try:
54
- dataset = load_dataset(self.path, split=self.split, streaming=True)
55
- except ConnectionError as exc:
56
- raise SourceConnectionError() from exc
57
- except DatasetNotFoundError as exc:
58
- raise SourceNotFoundError(source_id=self.id) from exc
82
+ dataset = self._load_dataset(streaming=True)
59
83
 
60
84
  try:
61
85
  data = next(iter(dataset.skip(self.row).take(1)))
@@ -78,13 +102,7 @@ class HuggingFaceSource(Source):
78
102
  path = source_dir / f"{self.split}.json"
79
103
 
80
104
  if not path.is_file():
81
- try:
82
- dataset = load_dataset(self.path, split=self.split)
83
- except ConnectionError as exc:
84
- raise SourceConnectionError() from exc
85
- except DatasetNotFoundError as exc:
86
- raise SourceNotFoundError(source_id=self.id) from exc
87
-
105
+ dataset = self._load_dataset(streaming=False)
88
106
  dataset.to_json(path)
89
107
  outputs.path = path
90
108
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ragbits-core
3
- Version: 1.4.0.dev202509220622
3
+ Version: 1.4.0.dev202511290233
4
4
  Summary: Building blocks for rapid development of GenAI applications
5
5
  Project-URL: Homepage, https://github.com/deepsense-ai/ragbits
6
6
  Project-URL: Bug Reports, https://github.com/deepsense-ai/ragbits/issues
@@ -89,6 +89,8 @@ pip install ragbits-core
89
89
  ## Quick Start
90
90
 
91
91
  ```python
92
+ import asyncio
93
+
92
94
  from pydantic import BaseModel
93
95
  from ragbits.core.prompt import Prompt
94
96
  from ragbits.core.llms.litellm import LiteLLM
@@ -32,7 +32,7 @@ ragbits/core/llms/__init__.py,sha256=-XI2zHa-xt3EoQbgqMud_GCVZxBCQXGDMY62AAOOAGA
32
32
  ragbits/core/llms/base.py,sha256=CXAreuEzQ2BhmQw44Oud-OyguEXDbfxa1Ckog3ZLHc0,28295
33
33
  ragbits/core/llms/exceptions.py,sha256=NpYYAhCMifHNDFWQpshjF9in_KCEmo6qlswPJca9ykc,2301
34
34
  ragbits/core/llms/factory.py,sha256=T-heeY_7Llp5bOl6u9ycKK8ZwFnUE1ry4Q4X-O68hEQ,1506
35
- ragbits/core/llms/litellm.py,sha256=5VnVFgUQMFlhR_KPOFeANJZSrHoxRBVK6RYTLF0HnF8,21445
35
+ ragbits/core/llms/litellm.py,sha256=p6abYQ9-eW5UveEzj0dGX0Ny6nk6tSzhC0m_pxlJlik,25888
36
36
  ragbits/core/llms/local.py,sha256=B5woo538621mv6SJ9FosW3LcDBtmAA8gycMe4EvHbzE,11760
37
37
  ragbits/core/llms/mock.py,sha256=CebF0kpvVvluqRdj1HtXM3GoCKq_VQLcud5dvUFQNYs,5053
38
38
  ragbits/core/prompt/__init__.py,sha256=2e71_O4RreG1gfdN9KXSscbOvr5DKzqiDu4J8LTD9Bs,120
@@ -50,7 +50,7 @@ ragbits/core/sources/exceptions.py,sha256=gv1d0kX304_kqLye6OfZ9szKTwaqLRszMYJCHR
50
50
  ragbits/core/sources/gcs.py,sha256=eE-xi-PbHpEKEBlhoU-cJnqQMjr2jjzmeQPEcdJbHSE,4844
51
51
  ragbits/core/sources/git.py,sha256=HiXcADErCBE4XpoCtnNIwFhpiuQ7aKfkb5Cf5VTYiy8,7615
52
52
  ragbits/core/sources/google_drive.py,sha256=8cZ97ujNA5PlBPYWiQ22kVNYIabKvl_VDvIWVUX6GMQ,26213
53
- ragbits/core/sources/hf.py,sha256=xMzPRPrxlqEjD6wFYL9h4gMh3sF-s7VwuPpet3nahNA,5140
53
+ ragbits/core/sources/hf.py,sha256=5ATFHQG9jdcyETzh__9MHzDyPnqAGHjAjDuiW-rfixw,5690
54
54
  ragbits/core/sources/local.py,sha256=fAteiD8-L8lVQXO9lOHidVlUjf_jW3DUJlahHQiIl9k,2727
55
55
  ragbits/core/sources/s3.py,sha256=HEf_nlRofTJ5GuXOzz1maa_7mkHHSgk1TLGW9nYCZCE,7346
56
56
  ragbits/core/sources/web.py,sha256=daxifFHTBFK8QoCdtBWTWuoQ_D-LcQKoX2eHRje58DY,2717
@@ -74,6 +74,6 @@ ragbits/core/vector_stores/in_memory.py,sha256=igxIYmTSXOijYFsPolo5sbYzSgLBwF5Lt
74
74
  ragbits/core/vector_stores/pgvector.py,sha256=_XPPfOsxiWkk6-ISLbrLRXw5sjcXizEA2Q_-YlqU7DQ,19944
75
75
  ragbits/core/vector_stores/qdrant.py,sha256=7M41efCtD5dkJeWtPY8P62zdM6F6IDU72mlxlwTg6Zk,12817
76
76
  ragbits/core/vector_stores/weaviate.py,sha256=Xfa9tDEEILuD7EMhk2vWPC_fewJqe1YXaNtPxnD59Ys,18989
77
- ragbits_core-1.4.0.dev202509220622.dist-info/METADATA,sha256=f5e4jVMyFR6WbXNEOtNqa5DKrAsziI12ejnYGmBp4MY,5177
78
- ragbits_core-1.4.0.dev202509220622.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
79
- ragbits_core-1.4.0.dev202509220622.dist-info/RECORD,,
77
+ ragbits_core-1.4.0.dev202511290233.dist-info/METADATA,sha256=a8o0NsZZcTYmnAndeOoQ6WImZggRfCfopBThgqjsC9w,5193
78
+ ragbits_core-1.4.0.dev202511290233.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
79
+ ragbits_core-1.4.0.dev202511290233.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any