Skip to content

Commit 5a6f2b8

Browse files
authored
feat: create ragbits-agents package (#569)
1 parent dace285 commit 5a6f2b8

File tree

13 files changed

+287
-49
lines changed

13 files changed

+287
-49
lines changed

packages/ragbits-agents/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# CHANGELOG
2+
3+
## Unreleased
4+
5+
- Add Agent interface (#569)
6+
- Initial release of the package (#569)

packages/ragbits-agents/README.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Ragbits Agents
2+
3+
Ragbits Agents contains primitives for building agentic systems.
4+
5+
The package is in the experimental phase, the API may change in the future.
6+
7+
## Installation
8+
9+
To install the Ragbits Agents package, run:
10+
11+
```sh
12+
pip install ragbits-agents
13+
```
14+
15+
<!--
16+
TODO: Add a minimalistic example inspired by the Quickstart chapter on Ragbits Evaluate once it is ready.
17+
-->
18+
19+
<!--
20+
TODO:
21+
* Add link to the Quickstart chapter on Ragbits Evaluate once it is ready.
22+
* Add link to API Reference once classes from the Evaluate package are added to the API Reference.
23+
-->
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[project]
2+
name = "ragbits-agents"
3+
version = "0.18.0"
4+
description = "Building blocks for rapid development of GenAI applications"
5+
readme = "README.md"
6+
requires-python = ">=3.10"
7+
license = "MIT"
8+
authors = [
9+
{ name = "deepsense.ai", email = "ragbits@deepsense.ai"}
10+
]
11+
keywords = [
12+
"Retrieval Augmented Generation",
13+
"RAG",
14+
"Large Language Models",
15+
"LLMs",
16+
"Generative AI",
17+
"GenAI",
18+
"Agents",
19+
]
20+
classifiers = [
21+
"Development Status :: 4 - Beta",
22+
"Environment :: Console",
23+
"Intended Audience :: Science/Research",
24+
"License :: OSI Approved :: MIT License",
25+
"Natural Language :: English",
26+
"Operating System :: OS Independent",
27+
"Programming Language :: Python :: 3.10",
28+
"Programming Language :: Python :: 3.11",
29+
"Programming Language :: Python :: 3.12",
30+
"Programming Language :: Python :: 3.13",
31+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
32+
"Topic :: Software Development :: Libraries :: Python Modules",
33+
]
34+
dependencies = ["ragbits-core==0.18.0"]
35+
36+
[project.urls]
37+
"Homepage" = "https://github.com/deepsense-ai/ragbits"
38+
"Bug Reports" = "https://github.com/deepsense-ai/ragbits/issues"
39+
"Documentation" = "https://ragbits.deepsense.ai/"
40+
"Source" = "https://github.com/deepsense-ai/ragbits"
41+
42+
[build-system]
43+
requires = ["hatchling"]
44+
build-backend = "hatchling.build"
45+
46+
[tool.hatch.metadata]
47+
allow-direct-references = true
48+
49+
[tool.hatch.build.targets.wheel]
50+
packages = ["src/ragbits"]
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
from ragbits.agents._main import Agent, AgentOptions
2+
from ragbits.agents.types import QuestionAnswerAgent, QuestionAnswerPromptInput, QuestionAnswerPromptOutput
3+
4+
__all__ = ["Agent", "AgentOptions", "QuestionAnswerAgent", "QuestionAnswerPromptInput", "QuestionAnswerPromptOutput"]
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from dataclasses import dataclass
2+
from types import ModuleType
3+
from typing import Any, ClassVar, Generic, cast, overload
4+
5+
from ragbits import agents
6+
from ragbits.core.llms.base import LLM, LLMClientOptionsT
7+
from ragbits.core.options import Options
8+
from ragbits.core.prompt.prompt import Prompt, PromptInputT, PromptOutputT
9+
from ragbits.core.types import NOT_GIVEN, NotGiven
10+
from ragbits.core.utils.config_handling import ConfigurableComponent
11+
12+
13+
@dataclass
14+
class AgentResult(Generic[PromptOutputT]):
15+
"""
16+
Result of the agent run.
17+
"""
18+
19+
content: PromptOutputT
20+
"""The output content of the LLM."""
21+
metadata: dict
22+
"""The additional data returned by the LLM."""
23+
24+
25+
class AgentOptions(Options, Generic[LLMClientOptionsT]):
26+
"""
27+
Options for the agent run.
28+
"""
29+
30+
llm_options: LLMClientOptionsT | None | NotGiven = NOT_GIVEN
31+
"""The options for the LLM."""
32+
33+
34+
class Agent(
35+
ConfigurableComponent[AgentOptions[LLMClientOptionsT]], Generic[LLMClientOptionsT, PromptInputT, PromptOutputT]
36+
):
37+
"""
38+
Agent class that orchestrates the LLM and the prompt.
39+
40+
Current implementation is highly experimental, and the API is subject to change.
41+
"""
42+
43+
options_cls: type[AgentOptions] = AgentOptions
44+
default_module: ClassVar[ModuleType | None] = agents
45+
configuration_key: ClassVar[str] = "agent"
46+
47+
def __init__(
48+
self,
49+
llm: LLM[LLMClientOptionsT],
50+
prompt: type[Prompt[PromptInputT, PromptOutputT]],
51+
*,
52+
default_options: AgentOptions[LLMClientOptionsT] | None = None,
53+
) -> None:
54+
"""
55+
Initialize the agent instance.
56+
57+
Args:
58+
llm: The LLM to use.
59+
prompt: The prompt to use.
60+
default_options: The default options for the agent run.
61+
"""
62+
super().__init__(default_options)
63+
self.llm = llm
64+
self.prompt = prompt
65+
66+
@overload
67+
async def run(
68+
self: "Agent[LLMClientOptionsT, PromptInputT, PromptOutputT]",
69+
input: PromptInputT,
70+
options: AgentOptions[LLMClientOptionsT] | None = None,
71+
) -> AgentResult[PromptOutputT]: ...
72+
73+
@overload
74+
async def run(
75+
self: "Agent[LLMClientOptionsT, None, PromptOutputT]",
76+
options: AgentOptions[LLMClientOptionsT] | None = None,
77+
) -> AgentResult[PromptOutputT]: ...
78+
79+
async def run(self, *args: Any, **kwargs: Any) -> AgentResult[PromptOutputT]: # noqa: D417
80+
"""
81+
Run the agent. The method is experimental, inputs and outputs may change in the future.
82+
83+
Args:
84+
input: The input for the agent run.
85+
options: The options for the agent run.
86+
87+
Returns:
88+
The result of the agent run.
89+
"""
90+
input = cast(PromptInputT, args[0] if args else kwargs.get("input"))
91+
options = args[1] if len(args) > 1 else kwargs.get("options")
92+
93+
merged_options = (self.default_options | options) if options else self.default_options
94+
llm_options = merged_options.llm_options or None
95+
96+
prompt = self.prompt(input)
97+
response = await self.llm.generate_with_metadata(prompt, options=llm_options)
98+
99+
return AgentResult(
100+
content=response.content,
101+
metadata=response.metadata,
102+
)

packages/ragbits-agents/src/ragbits/agents/py.typed

Whitespace-only changes.
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Any, TypeVar
2+
3+
from pydantic import BaseModel
4+
5+
from ragbits.agents._main import Agent
6+
from ragbits.core.llms.base import LLMClientOptionsT
7+
8+
QuestionAnswerPromptInputT = TypeVar("QuestionAnswerPromptInputT", bound="QuestionAnswerPromptInput")
9+
QuestionAnswerPromptOutputT = TypeVar("QuestionAnswerPromptOutputT", bound="QuestionAnswerPromptOutput | str")
10+
11+
QuestionAnswerAgent = Agent[LLMClientOptionsT, QuestionAnswerPromptInputT, QuestionAnswerPromptOutputT]
12+
13+
14+
class QuestionAnswerPromptInput(BaseModel):
15+
"""
16+
Input for the question answer agent.
17+
"""
18+
19+
question: str
20+
"""The question to answer."""
21+
context: Any | None = None
22+
"""The context to answer the question."""
23+
24+
25+
class QuestionAnswerPromptOutput(BaseModel):
26+
"""
27+
Output for the question answer agent.
28+
"""
29+
30+
answer: str
31+
"""The answer to the question."""

packages/ragbits-core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- rename typevars InputT and OutputT to PromptInputT and PromptOutputT (#569)
6+
57
## 0.18.0 (2025-05-22)
68

79
- Allow to limit VectorStore results by metadata (#564)

packages/ragbits-core/src/ragbits/core/llms/base.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
BasePrompt,
1313
BasePromptWithParser,
1414
ChatFormat,
15-
OutputT,
15+
PromptOutputT,
1616
SimplePrompt,
1717
)
1818
from ragbits.core.utils.config_handling import ConfigurableComponent
@@ -30,12 +30,12 @@ class LLMType(enum.Enum):
3030
STRUCTURED_OUTPUT = "structured_output"
3131

3232

33-
class LLMResponseWithMetadata(BaseModel, Generic[OutputT]):
33+
class LLMResponseWithMetadata(BaseModel, Generic[PromptOutputT]):
3434
"""
3535
A schema of output with metadata
3636
"""
3737

38-
content: OutputT
38+
content: PromptOutputT
3939
metadata: dict
4040

4141

@@ -124,18 +124,18 @@ async def generate_raw(
124124
@overload
125125
async def generate(
126126
self,
127-
prompt: BasePromptWithParser[OutputT],
127+
prompt: BasePromptWithParser[PromptOutputT],
128128
*,
129129
options: LLMClientOptionsT | None = None,
130-
) -> OutputT: ...
130+
) -> PromptOutputT: ...
131131

132132
@overload
133133
async def generate(
134134
self,
135135
prompt: BasePrompt,
136136
*,
137137
options: LLMClientOptionsT | None = None,
138-
) -> OutputT: ...
138+
) -> PromptOutputT: ...
139139

140140
@overload
141141
async def generate(
@@ -158,7 +158,7 @@ async def generate(
158158
prompt: BasePrompt | str | ChatFormat,
159159
*,
160160
options: LLMClientOptionsT | None = None,
161-
) -> OutputT:
161+
) -> PromptOutputT:
162162
"""
163163
Prepares and sends a prompt to the LLM and returns the parsed response.
164164
@@ -177,7 +177,7 @@ async def generate(
177177
if isinstance(prompt, BasePromptWithParser):
178178
response = await prompt.parse_response(raw_response["response"])
179179
else:
180-
response = cast(OutputT, raw_response["response"])
180+
response = cast(PromptOutputT, raw_response["response"])
181181
raw_response["response"] = response
182182
outputs.response = raw_response
183183

@@ -186,26 +186,26 @@ async def generate(
186186
@overload
187187
async def generate_with_metadata(
188188
self,
189-
prompt: BasePromptWithParser[OutputT],
189+
prompt: BasePromptWithParser[PromptOutputT],
190190
*,
191191
options: LLMClientOptionsT | None = None,
192-
) -> LLMResponseWithMetadata[OutputT]: ...
192+
) -> LLMResponseWithMetadata[PromptOutputT]: ...
193193

194194
@overload
195195
async def generate_with_metadata(
196196
self,
197197
prompt: BasePrompt,
198198
*,
199199
options: LLMClientOptionsT | None = None,
200-
) -> LLMResponseWithMetadata[OutputT]: ...
200+
) -> LLMResponseWithMetadata[PromptOutputT]: ...
201201

202202
@overload
203203
async def generate_with_metadata(
204204
self,
205205
prompt: str,
206206
*,
207207
options: LLMClientOptionsT | None = None,
208-
) -> LLMResponseWithMetadata[OutputT]: ...
208+
) -> LLMResponseWithMetadata[PromptOutputT]: ...
209209

210210
@overload
211211
@overload
@@ -214,14 +214,14 @@ async def generate_with_metadata(
214214
prompt: ChatFormat,
215215
*,
216216
options: LLMClientOptionsT | None = None,
217-
) -> LLMResponseWithMetadata[OutputT]: ...
217+
) -> LLMResponseWithMetadata[PromptOutputT]: ...
218218

219219
async def generate_with_metadata(
220220
self,
221221
prompt: BasePrompt | str | ChatFormat,
222222
*,
223223
options: LLMClientOptionsT | None = None,
224-
) -> LLMResponseWithMetadata[OutputT]:
224+
) -> LLMResponseWithMetadata[PromptOutputT]:
225225
"""
226226
Prepares and sends a prompt to the LLM and returns response parsed to the
227227
output type of the prompt (if available).

packages/ragbits-core/src/ragbits/core/prompt/base.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing_extensions import TypeVar
66

77
ChatFormat = list[dict[str, Any]]
8-
OutputT = TypeVar("OutputT", default=str)
8+
PromptOutputT = TypeVar("PromptOutputT", default=str)
99

1010

1111
class BasePrompt(metaclass=ABCMeta):
@@ -47,22 +47,22 @@ def list_images(self) -> list[str]: # noqa: PLR6301
4747
return []
4848

4949

50-
class BasePromptWithParser(Generic[OutputT], BasePrompt, metaclass=ABCMeta):
50+
class BasePromptWithParser(Generic[PromptOutputT], BasePrompt, metaclass=ABCMeta):
5151
"""
5252
Base class for prompts that know how to parse the output from the LLM to their specific
5353
output type.
5454
"""
5555

5656
@abstractmethod
57-
async def parse_response(self, response: str) -> OutputT:
57+
async def parse_response(self, response: str) -> PromptOutputT:
5858
"""
5959
Parse the response from the LLM to the desired output type.
6060
6161
Args:
6262
response (str): The response from the LLM.
6363
6464
Returns:
65-
OutputT: The parsed response.
65+
PromptOutputT: The parsed response.
6666
6767
Raises:
6868
ResponseParsingError: If the response cannot be parsed.

0 commit comments

Comments
 (0)