ragbits-cli 0.7.0__tar.gz → 0.9.0__tar.gz

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.
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.9.0 (2025-02-25)
6
+
7
+ ### Changed
8
+
9
+ - ragbits-core updated to version v0.9.0
10
+
11
+ ## 0.8.0 (2025-01-29)
12
+
13
+ ### Changed
14
+
15
+ - ragbits-core updated to version v0.8.0
16
+
5
17
  ## 0.7.0 (2025-01-21)
6
18
 
7
19
  ### Changed
@@ -0,0 +1,71 @@
1
+ Metadata-Version: 2.4
2
+ Name: ragbits-cli
3
+ Version: 0.9.0
4
+ Summary: A CLI application for ragbits - building blocks for rapid development of GenAI applications
5
+ Project-URL: Homepage, https://github.com/deepsense-ai/ragbits
6
+ Project-URL: Bug Reports, https://github.com/deepsense-ai/ragbits/issues
7
+ Project-URL: Documentation, https://ragbits.deepsense.ai/
8
+ Project-URL: Source, https://github.com/deepsense-ai/ragbits
9
+ Author-email: "deepsense.ai" <ragbits@deepsense.ai>
10
+ License-Expression: MIT
11
+ Keywords: GenAI,Generative AI,LLMs,Large Language Models,Prompt Management,RAG,Retrieval Augmented Generation
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Environment :: Console
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: ragbits-core==0.9.0
26
+ Requires-Dist: typer>=0.12.5
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Ragbits CLI
30
+
31
+ Ragbits CLI provides the `ragbits` command-line interface (CLI) tool that allows you to interact with Ragbits from the terminal. Other packages can extend the CLI by adding their own commands, so the exact set of available commands may vary depending on the installed packages.
32
+
33
+ ## Installation
34
+
35
+ To use the complete Ragbits stack, install the `ragbits` package:
36
+
37
+ ```sh
38
+ pip install ragbits
39
+ ```
40
+
41
+ ## Example Usage
42
+ The following example assumes that `ragbits-core` is installed and that the current ddirectory contains a `song_prompt.py` file with a prompt class named `SongPrompt`, as defined in the [Quickstart Guide](https://ragbits.deepsense.ai/quickstart/quickstart1_prompts/#making-the-prompt-dynamic).
43
+
44
+ The example demonstrates how to execute the prompt using the `ragbits` CLI tool.
45
+ The left side of the table shows the system and user prompts (rendered with placeholders replaced by the provided values), and the right side shows the generated response from the Large Language Model.
46
+
47
+ ```sh
48
+ $ ragbits prompts exec song_prompt:SongPrompt --payload '{"subject": "unicorns", "age_group": 12, "genre": "pop"}'
49
+
50
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
51
+ ┃ Question ┃ Answer ┃
52
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
53
+ │ [{'role': 'system', 'content': 'You │ (Verse 1) │
54
+ │ are a professional songwriter. │ In a land of rainbows and glitter, │
55
+ │ You only use language that is │ Where flowers bloom and skies are │
56
+ │ appropriate for children.'}, │ brighter, │
57
+ │ {'role': 'user', 'content': 'Write a │ There's a magical creature so rare, │
58
+ │ song about a unicorns for 12 years │ With a horn that sparkles in the air. │
59
+ │ old pop fans.'}] │ │
60
+ │ │ (Chorus) │
61
+ │ │ Unicorns, unicorns, oh so divine, │
62
+ │ │ With their mane that shines and eyes │
63
+ │ │ that shine, │
64
+ │ │ Gallop through the meadows, so free, │
65
+ │ │ In a world of wonder, just you and │
66
+ │ │ me. │
67
+ └───────────────────────────────────────┴───────────────────────────────────────┘
68
+ ```
69
+
70
+ ## Documentation
71
+ * [Documentation of the `ragbits` CLI](https://ragbits.deepsense.ai/cli/main/)
@@ -0,0 +1,43 @@
1
+ # Ragbits CLI
2
+
3
+ Ragbits CLI provides the `ragbits` command-line interface (CLI) tool that allows you to interact with Ragbits from the terminal. Other packages can extend the CLI by adding their own commands, so the exact set of available commands may vary depending on the installed packages.
4
+
5
+ ## Installation
6
+
7
+ To use the complete Ragbits stack, install the `ragbits` package:
8
+
9
+ ```sh
10
+ pip install ragbits
11
+ ```
12
+
13
+ ## Example Usage
14
+ The following example assumes that `ragbits-core` is installed and that the current ddirectory contains a `song_prompt.py` file with a prompt class named `SongPrompt`, as defined in the [Quickstart Guide](https://ragbits.deepsense.ai/quickstart/quickstart1_prompts/#making-the-prompt-dynamic).
15
+
16
+ The example demonstrates how to execute the prompt using the `ragbits` CLI tool.
17
+ The left side of the table shows the system and user prompts (rendered with placeholders replaced by the provided values), and the right side shows the generated response from the Large Language Model.
18
+
19
+ ```sh
20
+ $ ragbits prompts exec song_prompt:SongPrompt --payload '{"subject": "unicorns", "age_group": 12, "genre": "pop"}'
21
+
22
+ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
23
+ ┃ Question ┃ Answer ┃
24
+ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
25
+ │ [{'role': 'system', 'content': 'You │ (Verse 1) │
26
+ │ are a professional songwriter. │ In a land of rainbows and glitter, │
27
+ │ You only use language that is │ Where flowers bloom and skies are │
28
+ │ appropriate for children.'}, │ brighter, │
29
+ │ {'role': 'user', 'content': 'Write a │ There's a magical creature so rare, │
30
+ │ song about a unicorns for 12 years │ With a horn that sparkles in the air. │
31
+ │ old pop fans.'}] │ │
32
+ │ │ (Chorus) │
33
+ │ │ Unicorns, unicorns, oh so divine, │
34
+ │ │ With their mane that shines and eyes │
35
+ │ │ that shine, │
36
+ │ │ Gallop through the meadows, so free, │
37
+ │ │ In a world of wonder, just you and │
38
+ │ │ me. │
39
+ └───────────────────────────────────────┴───────────────────────────────────────┘
40
+ ```
41
+
42
+ ## Documentation
43
+ * [Documentation of the `ragbits` CLI](https://ragbits.deepsense.ai/cli/main/)
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "ragbits-cli"
3
- version = "0.7.0"
3
+ version = "0.9.0"
4
4
  description = "A CLI application for ragbits - building blocks for rapid development of GenAI applications"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -31,7 +31,7 @@ classifiers = [
31
31
  "Topic :: Scientific/Engineering :: Artificial Intelligence",
32
32
  "Topic :: Software Development :: Libraries :: Python Modules",
33
33
  ]
34
- dependencies = ["typer>=0.12.5", "ragbits-core==0.7.0"]
34
+ dependencies = ["typer>=0.12.5", "ragbits-core==0.9.0"]
35
35
 
36
36
  [project.urls]
37
37
  "Homepage" = "https://github.com/deepsense-ai/ragbits"
@@ -6,7 +6,7 @@ from pydantic.alias_generators import to_snake
6
6
  from rich.console import Console
7
7
 
8
8
  from ragbits.core.config import CoreConfig, core_config
9
- from ragbits.core.utils.config_handling import InvalidConfigError, NoDefaultConfigError, WithConstructionConfig
9
+ from ragbits.core.utils.config_handling import InvalidConfigError, NoPreferredConfigError, WithConstructionConfig
10
10
 
11
11
  WithConstructionConfigT_co = TypeVar("WithConstructionConfigT_co", bound=WithConstructionConfig, covariant=True)
12
12
 
@@ -16,8 +16,8 @@ WithConstructionConfigT_co = TypeVar("WithConstructionConfigT_co", bound=WithCon
16
16
  # to be used as types: https://github.com/python/mypy/issues/4717
17
17
  class WithConstructionConfigProtocol(Protocol[WithConstructionConfigT_co]):
18
18
  @classmethod
19
- def subclass_from_defaults(
20
- cls, defaults: CoreConfig, factory_path_override: str | None = None, yaml_path_override: Path | None = None
19
+ def preferred_subclass(
20
+ cls, config: CoreConfig, factory_path_override: str | None = None, yaml_path_override: Path | None = None
21
21
  ) -> WithConstructionConfigT_co: ...
22
22
 
23
23
 
@@ -26,11 +26,12 @@ def get_instance_or_exit(
26
26
  type_name: str | None = None,
27
27
  yaml_path: Path | None = None,
28
28
  factory_path: str | None = None,
29
+ config_override: CoreConfig | None = None,
29
30
  yaml_path_argument_name: str = "--yaml-path",
30
31
  factory_path_argument_name: str = "--factory-path",
31
32
  ) -> WithConstructionConfigT_co:
32
33
  """
33
- Returns an instance of the provided class, initialized using its `subclass_from_defaults` method.
34
+ Returns an instance of the provided class, initialized using its `preferred_subclass` method.
34
35
  If the instance can't be created, prints an error message and exits the program.
35
36
 
36
37
  Args:
@@ -39,6 +40,7 @@ def get_instance_or_exit(
39
40
  yaml_path: Path to a YAML configuration file to use for initialization.
40
41
  factory_path: Python path to a factory function to use for initialization.
41
42
  yaml_path_argument_name: The name of the argument to use in error messages for the YAML path.
43
+ config_override: A config instance to be used
42
44
  factory_path_argument_name: The name of the argument to use in error messages for the factory path.
43
45
  """
44
46
  if not isinstance(cls, type):
@@ -46,18 +48,19 @@ def get_instance_or_exit(
46
48
 
47
49
  type_name = type_name or to_snake(cls.__name__).replace("_", " ")
48
50
  try:
49
- return cls.subclass_from_defaults(
50
- core_config,
51
+ return cls.preferred_subclass(
52
+ config_override or core_config,
51
53
  factory_path_override=factory_path,
52
54
  yaml_path_override=yaml_path,
53
55
  )
54
- except NoDefaultConfigError as e:
56
+ except NoPreferredConfigError as e:
55
57
  Console(
56
58
  stderr=True
57
59
  ).print(f"""You need to provide the [b]{type_name}[/b] instance to be used. You can do this by either:
58
60
  - providing a path to a YAML configuration file with the [b]{yaml_path_argument_name}[/b] option
59
61
  - providing a Python path to a function that creates a vector store with the [b]{factory_path_argument_name}[/b] option
60
- - setting the default configuration or factory function in your project's [b]pyproject.toml[/b] file""")
62
+ - setting the preferred {type_name} configuration in your project's [b]pyproject.toml[/b] file
63
+ (see https://ragbits.deepsense.ai/how-to/core/component_preferrences/ for more information)""")
61
64
  raise typer.Exit(1) from e
62
65
  except InvalidConfigError as e:
63
66
  Console(stderr=True).print(e)
@@ -2,10 +2,12 @@ import json
2
2
  from collections.abc import Mapping, Sequence
3
3
  from dataclasses import dataclass
4
4
  from enum import Enum
5
- from typing import TypeVar
5
+ from types import UnionType
6
+ from typing import Optional, TypeVar, Union, get_args, get_origin
6
7
 
7
8
  import typer
8
9
  from pydantic import BaseModel
10
+ from pydantic.fields import FieldInfo
9
11
  from rich.console import Console
10
12
  from rich.table import Column, Table
11
13
 
@@ -48,39 +50,72 @@ def print_output_table(
48
50
  console.print("No results")
49
51
  return
50
52
 
51
- fields = {**data[0].model_fields, **data[0].model_computed_fields}
52
-
53
- # Human readable titles for columns
54
- titles = {
55
- key: value.get("title", key)
56
- for key, value in data[0].model_json_schema(mode="serialization")["properties"].items()
57
- }
53
+ base_fields = {**data[0].model_fields, **data[0].model_computed_fields}
58
54
 
59
55
  # Normalize the list of columns
60
56
  if columns is None:
61
- columns = {key: Column() for key in fields}
57
+ columns = {key: Column() for key in base_fields}
62
58
  elif isinstance(columns, str):
63
59
  columns = {key: Column() for key in columns.split(",")}
64
60
  elif isinstance(columns, Sequence):
65
61
  columns = {key: Column() for key in columns}
66
62
 
67
- # Add headers to columns if not provided
68
- for key in columns:
69
- if key not in fields:
70
- Console(stderr=True).print(f"Unknown column: {key}")
71
- raise typer.Exit(1)
72
-
73
- column = columns[key]
63
+ # check if columns are correct
64
+ for column_name in columns:
65
+ field = _get_nested_field(column_name, base_fields)
66
+ column = columns[column_name]
74
67
  if column.header == "":
75
- column.header = titles.get(key, key)
68
+ column.header = field.title if field.title else column_name.replace("_", " ").replace(".", " ").title()
76
69
 
77
70
  # Create and print the table
78
71
  table = Table(*columns.values(), show_header=True, header_style="bold magenta")
72
+
79
73
  for row in data:
80
- table.add_row(*[str(getattr(row, key)) for key in columns])
74
+ row_to_add = []
75
+ for key in columns:
76
+ *path_fragments, field_name = key.strip().split(".")
77
+ base_row = row
78
+ for fragment in path_fragments:
79
+ base_row = getattr(base_row, fragment)
80
+ z = getattr(base_row, field_name)
81
+ row_to_add.append(str(z))
82
+ table.add_row(*row_to_add)
83
+
81
84
  console.print(table)
82
85
 
83
86
 
87
+ def _get_nested_field(column_name: str, base_fields: dict) -> FieldInfo:
88
+ """
89
+ Check if column name exists in the model schema.
90
+
91
+ Args:
92
+ column_name: name of the column to check
93
+ base_fields: model fields
94
+ Returns:
95
+ field: nested field
96
+ """
97
+ fields = base_fields
98
+ *path_fragments, field_name = column_name.strip().split(".")
99
+ for fragment in path_fragments:
100
+ if fragment not in fields:
101
+ Console(stderr=True).print(
102
+ f"Unknown column: {'.'.join(path_fragments + [field_name])} ({fragment} not found)"
103
+ )
104
+ raise typer.Exit(1)
105
+ model_class = fields[fragment].annotation
106
+ if get_origin(model_class) in [UnionType, Optional, Union]:
107
+ types = get_args(model_class)
108
+ model_class = next((t for t in types if t is not type(None)), None)
109
+ if model_class and issubclass(model_class, BaseModel):
110
+ fields = {**model_class.model_fields, **model_class.model_computed_fields}
111
+ if field_name not in fields:
112
+ Console(stderr=True).print(
113
+ f"Unknown column: {'.'.join(path_fragments + [field_name])} ({field_name} not found)"
114
+ )
115
+ raise typer.Exit(1)
116
+ return fields[field_name]
117
+
118
+
84
119
  def print_output_json(data: Sequence[ModelT]) -> None:
85
120
  """
86
121
  Display data from Pydantic models in a JSON format.
@@ -0,0 +1,121 @@
1
+ from pathlib import Path
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pytest
5
+ import typer
6
+ from pydantic import BaseModel
7
+ from pydantic.fields import Field, FieldInfo
8
+ from rich.table import Column, Table
9
+
10
+ from ragbits.cli.state import OutputType, _get_nested_field, print_output, print_output_table
11
+ from ragbits.document_search.documents.sources import LocalFileSource
12
+
13
+
14
+ class InnerTestModel(BaseModel):
15
+ id: int
16
+ name: str = Field(title="Name of the inner model", description="Name of the inner model")
17
+ location: LocalFileSource
18
+
19
+
20
+ class OtherTestModel(BaseModel):
21
+ id: int
22
+ name: str
23
+ location: InnerTestModel
24
+
25
+
26
+ class MainTestModel(BaseModel):
27
+ id: int
28
+ name: str
29
+ model: OtherTestModel | None
30
+
31
+
32
+ data = [
33
+ MainTestModel(
34
+ id=1,
35
+ name="A",
36
+ model=OtherTestModel(
37
+ id=11,
38
+ name="aa",
39
+ location=InnerTestModel(id=111, name="aa1", location=LocalFileSource(path=Path("folder_1"))),
40
+ ),
41
+ ),
42
+ MainTestModel(
43
+ id=2,
44
+ name="B",
45
+ model=OtherTestModel(
46
+ id=22,
47
+ name="bb",
48
+ location=InnerTestModel(id=222, name="aa2", location=LocalFileSource(path=Path("folder_2"))),
49
+ ),
50
+ ),
51
+ ]
52
+
53
+
54
+ @patch("ragbits.cli.state.print_output_table")
55
+ @patch("ragbits.cli.state.print_output_json")
56
+ def test_print_output_text(mock_print_output_json: MagicMock, mock_print_output_table: MagicMock):
57
+ with patch("ragbits.cli.state.cli_state") as mock_cli_state:
58
+ mock_cli_state.output_type = OutputType.text
59
+ columns = {"id": Column(), "name": Column()}
60
+ print_output(data, columns=columns)
61
+ mock_print_output_table.assert_called_once_with(data, columns)
62
+ mock_print_output_json.assert_not_called()
63
+
64
+
65
+ @patch("ragbits.cli.state.print_output_table")
66
+ @patch("ragbits.cli.state.print_output_json")
67
+ def test_print_output_json(mock_print_output_json: MagicMock, mock_print_output_table: MagicMock):
68
+ with patch("ragbits.cli.state.cli_state") as mock_cli_state:
69
+ mock_cli_state.output_type = OutputType.json
70
+ print_output(data)
71
+ mock_print_output_table.assert_not_called()
72
+ mock_print_output_json.assert_called_once_with(data)
73
+
74
+
75
+ def test_print_output_unsupported_output_type():
76
+ with patch("ragbits.cli.state.cli_state") as mock_cli_state:
77
+ mock_cli_state.output_type = "unsupported_type"
78
+ with pytest.raises(ValueError, match="Unsupported output type: unsupported_type"):
79
+ print_output(data)
80
+
81
+
82
+ def test_print_output_table():
83
+ with patch("rich.console.Console.print") as mock_print:
84
+ columns = {"id": Column(), "model.location.location.path": Column(), "model.location.name": Column()}
85
+ print_output_table(data, columns)
86
+ mock_print.assert_called_once()
87
+ args, _ = mock_print.call_args_list[0]
88
+ printed_table = args[0]
89
+ assert isinstance(printed_table, Table)
90
+ assert printed_table.columns[0].header == "Id"
91
+ assert printed_table.columns[1].header == "Model Location Location Path"
92
+ assert printed_table.columns[2].header == "Name of the inner model"
93
+ assert printed_table.row_count == 2
94
+
95
+
96
+ def test_get_nested_field():
97
+ column = "model.location.location.path"
98
+ fields = {"name": FieldInfo(annotation=str), "model": FieldInfo(annotation=OtherTestModel)}
99
+
100
+ try:
101
+ result = _get_nested_field(column, fields)
102
+ assert result.annotation == Path
103
+ except typer.Exit:
104
+ pytest.fail("typer.Exit was raised unexpectedly")
105
+
106
+
107
+ def test_get_nested_field_wrong_field():
108
+ column_names = [
109
+ ("model.location.wrong_field", "wrong_field"),
110
+ ("model.wrong_path.location.path", "wrong_path"),
111
+ ("wrong_path.location.location.path", "wrong_path"),
112
+ ("model.location.path", "path"),
113
+ ("model.location.location.path.additional_field", "additional_field"),
114
+ ]
115
+ fields = {"name": FieldInfo(annotation=str), "model": FieldInfo(annotation=OtherTestModel)}
116
+
117
+ for wrong_column, wrong_fragment in column_names:
118
+ with patch("rich.console.Console.print") as mock_print:
119
+ with pytest.raises(typer.Exit, match="1"):
120
+ _get_nested_field(wrong_column, fields)
121
+ mock_print.assert_called_once_with(f"Unknown column: {wrong_column} ({wrong_fragment} not found)")
@@ -1,29 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: ragbits-cli
3
- Version: 0.7.0
4
- Summary: A CLI application for ragbits - building blocks for rapid development of GenAI applications
5
- Project-URL: Homepage, https://github.com/deepsense-ai/ragbits
6
- Project-URL: Bug Reports, https://github.com/deepsense-ai/ragbits/issues
7
- Project-URL: Documentation, https://ragbits.deepsense.ai/
8
- Project-URL: Source, https://github.com/deepsense-ai/ragbits
9
- Author-email: "deepsense.ai" <ragbits@deepsense.ai>
10
- License-Expression: MIT
11
- Keywords: GenAI,Generative AI,LLMs,Large Language Models,Prompt Management,RAG,Retrieval Augmented Generation
12
- Classifier: Development Status :: 4 - Beta
13
- Classifier: Environment :: Console
14
- Classifier: Intended Audience :: Science/Research
15
- Classifier: License :: OSI Approved :: MIT License
16
- Classifier: Natural Language :: English
17
- Classifier: Operating System :: OS Independent
18
- Classifier: Programming Language :: Python :: 3.10
19
- Classifier: Programming Language :: Python :: 3.11
20
- Classifier: Programming Language :: Python :: 3.12
21
- Classifier: Programming Language :: Python :: 3.13
22
- Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
23
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
24
- Requires-Python: >=3.10
25
- Requires-Dist: ragbits-core==0.7.0
26
- Requires-Dist: typer>=0.12.5
27
- Description-Content-Type: text/markdown
28
-
29
- # Ragbits CLI
@@ -1 +0,0 @@
1
- # Ragbits CLI
File without changes
File without changes